2023-10-24 14:58:31 +08:00
//go:build !remote
2021-04-11 01:44:51 +08:00
package libimage
import (
"context"
2022-07-11 22:11:52 +08:00
"errors"
2021-12-02 03:08:25 +08:00
"fmt"
2021-04-11 01:44:51 +08:00
"path/filepath"
2025-09-17 03:55:13 +08:00
"slices"
2021-04-11 01:44:51 +08:00
"sort"
2021-04-30 15:16:03 +08:00
"strings"
2021-04-11 01:44:51 +08:00
"time"
2024-08-30 19:07:24 +08:00
"github.com/containerd/platforms"
2021-04-11 01:44:51 +08:00
"github.com/hashicorp/go-multierror"
"github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
2025-08-29 20:55:12 +08:00
"go.podman.io/common/libimage/platform"
"go.podman.io/image/v5/docker/reference"
"go.podman.io/image/v5/image"
"go.podman.io/image/v5/manifest"
storageTransport "go.podman.io/image/v5/storage"
"go.podman.io/image/v5/types"
"go.podman.io/storage"
2021-04-11 01:44:51 +08:00
)
// Image represents an image in the containers storage and allows for further
// operations and data manipulation.
type Image struct {
2023-04-06 21:00:52 +08:00
// ListData that is being set by (*Runtime).ListImages(). Note that
// the data may be outdated.
ListData struct {
// Dangling indicates if the image is dangling. Use
// `IsDangling()` to compute the latest state.
IsDangling * bool
// Parent points to the parent image. Use `Parent()` to
// compute the latest state.
Parent * Image
}
2021-04-11 01:44:51 +08:00
// Backwards pointer to the runtime.
runtime * Runtime
// Counterpart in the local containers storage.
storageImage * storage . Image
// Image reference to the containers storage.
storageReference types . ImageReference
// All fields in the below structure are cached. They may be cleared
// at any time. When adding a new field, please make sure to clear
// it in `(*Image).reload()`.
cached struct {
// Image source. Cached for performance reasons.
imageSource types . ImageSource
// Inspect data we get from containers/image.
partialInspectData * types . ImageInspectInfo
// Fully assembled image data.
2021-04-30 15:16:03 +08:00
completeInspectData * ImageData
2021-04-11 01:44:51 +08:00
// Corresponding OCI image.
ociv1Image * ociv1 . Image
2021-12-02 03:08:25 +08:00
// Names() parsed into references.
namesReferences [ ] reference . Reference
2022-10-20 05:36:20 +08:00
// Calculating the Size() is expensive, so cache it.
size * int64
2021-04-11 01:44:51 +08:00
}
}
2024-01-26 23:27:18 +08:00
// reload the image and pessimistically clear all cached data.
2021-04-11 01:44:51 +08:00
func ( i * Image ) reload ( ) error {
logrus . Tracef ( "Reloading image %s" , i . ID ( ) )
img , err := i . runtime . store . Image ( i . ID ( ) )
if err != nil {
2022-07-11 22:11:52 +08:00
return fmt . Errorf ( "reloading image: %w" , err )
2021-04-11 01:44:51 +08:00
}
i . storageImage = img
i . cached . imageSource = nil
i . cached . partialInspectData = nil
i . cached . completeInspectData = nil
i . cached . ociv1Image = nil
2021-12-02 03:08:25 +08:00
i . cached . namesReferences = nil
2022-10-20 05:36:20 +08:00
i . cached . size = nil
2021-04-11 01:44:51 +08:00
return nil
}
2021-07-02 20:26:20 +08:00
// isCorrupted returns an error if the image may be corrupted.
2024-01-26 23:27:18 +08:00
func ( i * Image ) isCorrupted ( ctx context . Context , name string ) error {
2021-07-02 20:26:20 +08:00
// If it's a manifest list, we're good for now.
if _ , err := i . getManifestList ( ) ; err == nil {
return nil
}
ref , err := i . StorageReference ( )
if err != nil {
return err
}
2024-01-26 23:27:18 +08:00
img , err := ref . NewImage ( ctx , nil )
2023-08-31 15:22:36 +08:00
if err != nil {
2021-09-02 18:38:39 +08:00
if name == "" {
name = i . ID ( ) [ : 12 ]
}
2022-07-11 22:11:52 +08:00
return fmt . Errorf ( "Image %s exists in local storage but may be corrupted (remove the image to resolve the issue): %v" , name , err )
2021-07-02 20:26:20 +08:00
}
2023-08-31 15:22:36 +08:00
return img . Close ( )
2021-07-02 20:26:20 +08:00
}
2021-04-11 01:44:51 +08:00
// Names returns associated names with the image which may be a mix of tags and
// digests.
func ( i * Image ) Names ( ) [ ] string {
return i . storageImage . Names
}
2025-06-30 19:40:22 +08:00
// setEphemeralNames sets the names of the image.
//
// WARNING: this only affects the in-memory values, they are not written into the backing storage.
func ( i * Image ) setEphemeralNames ( names [ ] string ) {
i . storageImage . Names = names
}
2021-12-02 03:08:25 +08:00
// NamesReferences returns Names() as references.
func ( i * Image ) NamesReferences ( ) ( [ ] reference . Reference , error ) {
if i . cached . namesReferences != nil {
return i . cached . namesReferences , nil
}
refs := make ( [ ] reference . Reference , 0 , len ( i . Names ( ) ) )
for _ , name := range i . Names ( ) {
ref , err := reference . Parse ( name )
if err != nil {
return nil , err
}
refs = append ( refs , ref )
}
i . cached . namesReferences = refs
return refs , nil
}
2021-04-11 01:44:51 +08:00
// StorageImage returns the underlying storage.Image.
func ( i * Image ) StorageImage ( ) * storage . Image {
return i . storageImage
}
// NamesHistory returns a string array of names previously associated with the
// image, which may be a mixture of tags and digests.
func ( i * Image ) NamesHistory ( ) [ ] string {
return i . storageImage . NamesHistory
}
// ID returns the ID of the image.
func ( i * Image ) ID ( ) string {
return i . storageImage . ID
}
// Digest is a digest value that we can use to locate the image, if one was
2021-05-18 16:02:48 +08:00
// specified at creation-time. Typically it is the digest of one among
// possibly many digests that we have stored for the image, so many
// applications are better off using the entire list returned by Digests().
2021-04-11 01:44:51 +08:00
func ( i * Image ) Digest ( ) digest . Digest {
2023-07-14 01:57:20 +08:00
// TODO: we return the image digest or the one of the manifest list
// which can lead to issues depending on the callers' assumptions.
// Hence, deprecate in favor of Digest_s_.
2021-04-11 01:44:51 +08:00
return i . storageImage . Digest
}
// Digests is a list of digest values of the image's manifests, and possibly a
// manually-specified value, that we can use to locate the image. If Digest is
// set, its value is also in this list.
func ( i * Image ) Digests ( ) [ ] digest . Digest {
return i . storageImage . Digests
}
2023-07-14 01:57:20 +08:00
// hasDigest returns whether the specified value matches any digest of the
// image.
2023-07-22 02:30:14 +08:00
func ( i * Image ) hasDigest ( wantedDigest digest . Digest ) bool {
2025-09-17 03:55:13 +08:00
return slices . Contains ( i . Digests ( ) , wantedDigest )
2023-07-14 01:57:20 +08:00
}
2023-10-03 18:56:14 +08:00
// containsDigestPrefix returns whether the specified value matches any digest of the
// image. It checks for the prefix and not a full match.
func ( i * Image ) containsDigestPrefix ( wantedDigestPrefix string ) bool {
for _ , d := range i . Digests ( ) {
if strings . HasPrefix ( d . String ( ) , wantedDigestPrefix ) {
return true
}
}
return false
}
2021-04-11 01:44:51 +08:00
// IsReadOnly returns whether the image is set read only.
func ( i * Image ) IsReadOnly ( ) bool {
return i . storageImage . ReadOnly
}
2021-07-19 19:49:02 +08:00
// IsDangling returns true if the image is dangling, that is an untagged image
2025-04-17 23:36:04 +08:00
// without children and not used in a manifest list.
2021-07-19 19:49:02 +08:00
func ( i * Image ) IsDangling ( ctx context . Context ) ( bool , error ) {
2025-04-17 23:36:04 +08:00
images , layers , err := i . runtime . getImagesAndLayers ( )
if err != nil {
return false , err
}
tree , err := i . runtime . newLayerTreeFromData ( images , layers , true )
if err != nil {
return false , err
}
return i . isDangling ( ctx , tree )
2021-12-02 03:08:25 +08:00
}
// isDangling returns true if the image is dangling, that is an untagged image
2025-04-17 23:36:04 +08:00
// without children and not used in a manifest list. If tree is nil, it will created for this invocation only.
2021-12-02 03:08:25 +08:00
func ( i * Image ) isDangling ( ctx context . Context , tree * layerTree ) ( bool , error ) {
2021-07-19 19:49:02 +08:00
if len ( i . Names ( ) ) > 0 {
return false , nil
}
2021-12-02 03:08:25 +08:00
children , err := i . getChildren ( ctx , false , tree )
2021-07-19 19:49:02 +08:00
if err != nil {
return false , err
}
2025-04-17 23:36:04 +08:00
_ , usedInManfiestList := tree . manifestListDigests [ i . Digest ( ) ]
return ( len ( children ) == 0 && ! usedInManfiestList ) , nil
2021-04-11 01:44:51 +08:00
}
// IsIntermediate returns true if the image is an intermediate image, that is
2021-07-19 19:49:02 +08:00
// an untagged image with children.
2021-04-11 01:44:51 +08:00
func ( i * Image ) IsIntermediate ( ctx context . Context ) ( bool , error ) {
2021-12-02 03:08:25 +08:00
return i . isIntermediate ( ctx , nil )
}
// isIntermediate returns true if the image is an intermediate image, that is
// an untagged image with children. If tree is nil, it will created for this
// invocation only.
func ( i * Image ) isIntermediate ( ctx context . Context , tree * layerTree ) ( bool , error ) {
2021-07-19 19:49:02 +08:00
if len ( i . Names ( ) ) > 0 {
2021-04-11 01:44:51 +08:00
return false , nil
}
2021-12-02 03:08:25 +08:00
children , err := i . getChildren ( ctx , false , tree )
2021-04-11 01:44:51 +08:00
if err != nil {
return false , err
}
return len ( children ) != 0 , nil
}
// Created returns the time the image was created.
func ( i * Image ) Created ( ) time . Time {
return i . storageImage . Created
}
// Labels returns the label of the image.
func ( i * Image ) Labels ( ctx context . Context ) ( map [ string ] string , error ) {
data , err := i . inspectInfo ( ctx )
if err != nil {
2021-04-30 15:16:03 +08:00
isManifestList , listErr := i . IsManifestList ( ctx )
2021-04-11 01:44:51 +08:00
if listErr != nil {
2022-07-11 22:11:52 +08:00
err = fmt . Errorf ( "fallback error checking whether image is a manifest list: %v: %w" , err , err )
2021-04-11 01:44:51 +08:00
} else if isManifestList {
logrus . Debugf ( "Ignoring error: cannot return labels for manifest list or image index %s" , i . ID ( ) )
return nil , nil
}
return nil , err
}
return data . Labels , nil
}
2025-09-17 03:55:13 +08:00
// TopLayer returns the top layer id as a string.
2021-04-11 01:44:51 +08:00
func ( i * Image ) TopLayer ( ) string {
return i . storageImage . TopLayer
}
2025-09-17 03:55:13 +08:00
// Parent returns the parent image or nil if there is none.
2021-04-11 01:44:51 +08:00
func ( i * Image ) Parent ( ctx context . Context ) ( * Image , error ) {
2024-08-30 19:07:24 +08:00
tree , err := i . runtime . newFreshLayerTree ( )
2021-04-11 01:44:51 +08:00
if err != nil {
return nil , err
}
2023-04-06 21:00:52 +08:00
return i . parent ( ctx , tree )
}
func ( i * Image ) parent ( ctx context . Context , tree * layerTree ) ( * Image , error ) {
2021-04-11 01:44:51 +08:00
return tree . parent ( ctx , i )
}
// HasChildren returns indicates if the image has children.
func ( i * Image ) HasChildren ( ctx context . Context ) ( bool , error ) {
2021-12-02 03:08:25 +08:00
children , err := i . getChildren ( ctx , false , nil )
2021-04-11 01:44:51 +08:00
if err != nil {
return false , err
}
return len ( children ) > 0 , nil
}
// Children returns the image's children.
func ( i * Image ) Children ( ctx context . Context ) ( [ ] * Image , error ) {
2021-12-02 03:08:25 +08:00
children , err := i . getChildren ( ctx , true , nil )
2021-04-11 01:44:51 +08:00
if err != nil {
return nil , err
}
return children , nil
}
// getChildren returns a list of imageIDs that depend on the image. If all is
2021-12-02 03:08:25 +08:00
// false, only the first child image is returned. If tree is nil, it will be
// created for this invocation only.
func ( i * Image ) getChildren ( ctx context . Context , all bool , tree * layerTree ) ( [ ] * Image , error ) {
if tree == nil {
2024-08-30 19:07:24 +08:00
t , err := i . runtime . newFreshLayerTree ( )
2021-12-02 03:08:25 +08:00
if err != nil {
return nil , err
}
tree = t
2021-04-11 01:44:51 +08:00
}
return tree . children ( ctx , i , all )
}
// Containers returns a list of containers using the image.
func ( i * Image ) Containers ( ) ( [ ] string , error ) {
var containerIDs [ ] string
containers , err := i . runtime . store . Containers ( )
if err != nil {
return nil , err
}
imageID := i . ID ( )
for i := range containers {
if containers [ i ] . ImageID == imageID {
containerIDs = append ( containerIDs , containers [ i ] . ID )
}
}
return containerIDs , nil
}
// removeContainers removes all containers using the image.
2021-12-02 03:08:25 +08:00
func ( i * Image ) removeContainers ( options * RemoveImagesOptions ) error {
if ! options . Force && ! options . ExternalContainers {
// Nothing to do.
return nil
}
if options . Force && options . RemoveContainerFunc != nil {
2021-04-11 01:44:51 +08:00
logrus . Debugf ( "Removing containers of image %s with custom removal function" , i . ID ( ) )
2021-12-02 03:08:25 +08:00
if err := options . RemoveContainerFunc ( i . ID ( ) ) ; err != nil {
2021-04-30 15:16:03 +08:00
return err
}
2021-04-11 01:44:51 +08:00
}
containers , err := i . Containers ( )
if err != nil {
return err
}
2021-12-02 03:08:25 +08:00
if ! options . Force && options . ExternalContainers {
// All containers must be external ones.
for _ , cID := range containers {
isExternal , err := options . IsExternalContainerFunc ( cID )
if err != nil {
return fmt . Errorf ( "checking if %s is an external container: %w" , cID , err )
}
if ! isExternal {
return fmt . Errorf ( "cannot remove container %s: not an external container" , cID )
}
}
}
2021-04-11 01:44:51 +08:00
logrus . Debugf ( "Removing containers of image %s from the local containers storage" , i . ID ( ) )
var multiE error
for _ , cID := range containers {
if err := i . runtime . store . DeleteContainer ( cID ) ; err != nil {
// If the container does not exist anymore, we're good.
2022-07-11 22:11:52 +08:00
if ! errors . Is ( err , storage . ErrContainerUnknown ) {
2021-04-11 01:44:51 +08:00
multiE = multierror . Append ( multiE , err )
}
}
}
return multiE
}
// RemoveContainerFunc allows for customizing the removal of containers using
// an image specified by imageID.
type RemoveContainerFunc func ( imageID string ) error
2025-09-17 03:55:13 +08:00
// RemoveImageReport is the assembled data from removing *one* image.
2021-04-30 15:16:03 +08:00
type RemoveImageReport struct {
// ID of the image.
ID string
// Image was removed.
Removed bool
// Size of the removed image. Only set when explicitly requested in
// RemoveImagesOptions.
Size int64
// The untagged tags.
Untagged [ ] string
2021-04-11 01:44:51 +08:00
}
2021-04-30 15:16:03 +08:00
// remove removes the image along with all dangling parent images that no other
2021-04-11 01:44:51 +08:00
// image depends on. The image must not be set read-only and not be used by
2021-07-19 19:49:02 +08:00
// containers. Returns IDs of removed/untagged images in order.
2021-04-11 01:44:51 +08:00
//
// If the image is used by containers return storage.ErrImageUsedByContainer.
// Use force to remove these containers.
2021-04-30 15:16:03 +08:00
//
// NOTE: the rmMap is used to assemble image-removal data across multiple
// invocations of this function. The recursive nature requires some
// bookkeeping to make sure that all data is aggregated correctly.
//
// This function is internal. Users of libimage should always use
// `(*Runtime).RemoveImages()`.
2021-07-19 19:49:02 +08:00
func ( i * Image ) remove ( ctx context . Context , rmMap map [ string ] * RemoveImageReport , referencedBy string , options * RemoveImagesOptions ) ( [ ] string , error ) {
processedIDs := [ ] string { }
return i . removeRecursive ( ctx , rmMap , processedIDs , referencedBy , options )
}
func ( i * Image ) removeRecursive ( ctx context . Context , rmMap map [ string ] * RemoveImageReport , processedIDs [ ] string , referencedBy string , options * RemoveImagesOptions ) ( [ ] string , error ) {
2021-04-30 15:16:03 +08:00
// If referencedBy is empty, the image is considered to be removed via
// `image remove --all` which alters the logic below.
// The removal logic below is complex. There is a number of rules
// inherited from Podman and Buildah (and Docker). This function
// should be the *only* place to extend the removal logic so we keep it
// sealed in one place. Make sure to add verbose comments to leave
// some breadcrumbs for future readers.
2021-04-11 01:44:51 +08:00
logrus . Debugf ( "Removing image %s" , i . ID ( ) )
2021-04-30 15:16:03 +08:00
2021-04-11 01:44:51 +08:00
if i . IsReadOnly ( ) {
2022-07-11 22:11:52 +08:00
return processedIDs , fmt . Errorf ( "cannot remove read-only image %q" , i . ID ( ) )
2021-04-11 01:44:51 +08:00
}
2021-05-18 16:02:48 +08:00
if i . runtime . eventChannel != nil {
2021-07-02 20:26:20 +08:00
defer i . runtime . writeEvent ( & Event { ID : i . ID ( ) , Name : referencedBy , Time : time . Now ( ) , Type : EventTypeImageRemove } )
2021-05-18 16:02:48 +08:00
}
2021-04-30 15:16:03 +08:00
// Check if already visisted this image.
report , exists := rmMap [ i . ID ( ) ]
if exists {
// If the image has already been removed, we're done.
if report . Removed {
2021-07-19 19:49:02 +08:00
return processedIDs , nil
2021-04-30 15:16:03 +08:00
}
} else {
report = & RemoveImageReport { ID : i . ID ( ) }
rmMap [ i . ID ( ) ] = report
}
// The image may have already been (partially) removed, so we need to
// have a closer look at the errors. On top, image removal should be
// tolerant toward corrupted images.
handleError := func ( err error ) error {
2023-06-27 16:26:02 +08:00
if ErrorIsImageUnknown ( err ) {
2022-07-11 22:11:52 +08:00
// The image or layers of the image may already have been removed
// in which case we consider the image to be removed.
2021-04-30 15:16:03 +08:00
return nil
}
2022-07-11 22:11:52 +08:00
return err
2021-04-11 01:44:51 +08:00
}
2021-04-30 15:16:03 +08:00
// Calculate the size if requested. `podman-image-prune` likes to
// report the regained size.
if options . WithSize {
size , err := i . Size ( )
if handleError ( err ) != nil {
2021-07-19 19:49:02 +08:00
return processedIDs , err
2021-04-30 15:16:03 +08:00
}
report . Size = size
}
skipRemove := false
numNames := len ( i . Names ( ) )
2025-04-17 23:36:04 +08:00
// NOTE: the `numNames != 1` check is not only a performance
2023-06-15 17:48:42 +08:00
// optimization but also preserves existing Podman/Docker behaviour.
2021-04-30 15:16:03 +08:00
// If image "foo" is used by a container and has only this tag/name,
// an `rmi foo` will not untag "foo" but instead attempt to remove the
// entire image. If there's a container using "foo", we should get an
// error.
2025-04-17 23:36:04 +08:00
if referencedBy != "" && numNames != 1 {
2021-04-30 15:16:03 +08:00
byID := strings . HasPrefix ( i . ID ( ) , referencedBy )
byDigest := strings . HasPrefix ( referencedBy , "sha256:" )
2021-06-21 16:29:14 +08:00
if ! options . Force {
if byID && numNames > 1 {
2022-07-11 22:11:52 +08:00
return processedIDs , fmt . Errorf ( "unable to delete image %q by ID with more than one tag (%s): please force removal" , i . ID ( ) , i . Names ( ) )
2021-06-21 16:29:14 +08:00
} else if byDigest && numNames > 1 {
// FIXME - Docker will remove the digest but containers storage
// does not support that yet, so our hands are tied.
2022-07-11 22:11:52 +08:00
return processedIDs , fmt . Errorf ( "unable to delete image %q by digest with more than one tag (%s): please force removal" , i . ID ( ) , i . Names ( ) )
2021-06-21 16:29:14 +08:00
}
2021-04-30 15:16:03 +08:00
}
// Only try to untag if we know it's not an ID or digest.
if ! byID && ! byDigest {
if err := i . Untag ( referencedBy ) ; handleError ( err ) != nil {
2021-07-19 19:49:02 +08:00
return processedIDs , err
2021-04-30 15:16:03 +08:00
}
report . Untagged = append ( report . Untagged , referencedBy )
// If there's still tags left, we cannot delete it.
skipRemove = len ( i . Names ( ) ) > 0
}
}
2021-07-19 19:49:02 +08:00
processedIDs = append ( processedIDs , i . ID ( ) )
2021-04-30 15:16:03 +08:00
if skipRemove {
2021-07-19 19:49:02 +08:00
return processedIDs , nil
2021-04-30 15:16:03 +08:00
}
2021-12-02 03:08:25 +08:00
// Perform the container removal, if needed.
if err := i . removeContainers ( options ) ; err != nil {
return processedIDs , err
2021-04-11 01:44:51 +08:00
}
2021-04-30 15:16:03 +08:00
// Podman/Docker compat: we only report an image as removed if it has
// no children. Otherwise, the data is effectively still present in the
// storage despite the image being removed.
hasChildren , err := i . HasChildren ( ctx )
if err != nil {
// We must be tolerant toward corrupted images.
// See containers/podman commit fd9dd7065d44.
2021-12-02 03:08:25 +08:00
logrus . Warnf ( "Failed to determine if an image is a parent: %v, ignoring the error" , err )
2021-04-30 15:16:03 +08:00
hasChildren = false
}
2021-04-11 01:44:51 +08:00
// If there's a dangling parent that no other image depends on, remove
// it recursively.
parent , err := i . Parent ( ctx )
if err != nil {
2021-04-30 15:16:03 +08:00
// We must be tolerant toward corrupted images.
// See containers/podman commit fd9dd7065d44.
2021-12-02 03:08:25 +08:00
logrus . Warnf ( "Failed to determine parent of image: %v, ignoring the error" , err )
2021-04-30 15:16:03 +08:00
parent = nil
2021-04-11 01:44:51 +08:00
}
2021-04-30 15:16:03 +08:00
if _ , err := i . runtime . store . DeleteImage ( i . ID ( ) , true ) ; handleError ( err ) != nil {
2022-09-20 15:14:36 +08:00
if errors . Is ( err , storage . ErrImageUsedByContainer ) {
err = fmt . Errorf ( "%w: consider listing external containers and force-removing image" , err )
}
2021-07-19 19:49:02 +08:00
return processedIDs , err
2021-04-11 01:44:51 +08:00
}
2022-09-20 15:14:36 +08:00
2021-04-30 15:16:03 +08:00
report . Untagged = append ( report . Untagged , i . Names ( ) ... )
2022-09-20 15:14:36 +08:00
if i . runtime . eventChannel != nil {
for _ , name := range i . Names ( ) {
i . runtime . writeEvent ( & Event { ID : i . ID ( ) , Name : name , Time : time . Now ( ) , Type : EventTypeImageUntag } )
}
}
2021-04-30 15:16:03 +08:00
if ! hasChildren {
report . Removed = true
}
2022-09-20 15:14:36 +08:00
// Do not delete any parents if NoPrune is true
if options . NoPrune {
return processedIDs , nil
}
2021-04-30 15:16:03 +08:00
// Check if can remove the parent image.
if parent == nil {
2021-07-19 19:49:02 +08:00
return processedIDs , nil
2021-04-30 15:16:03 +08:00
}
2021-04-11 01:44:51 +08:00
2021-07-19 19:49:02 +08:00
// Only remove the parent if it's dangling, that is being untagged and
// without children.
danglingParent , err := parent . IsDangling ( ctx )
2021-04-30 15:16:03 +08:00
if err != nil {
// See Podman commit fd9dd7065d44: we need to
// be tolerant toward corrupted images.
2021-12-02 03:08:25 +08:00
logrus . Warnf ( "Failed to determine if an image is a parent: %v, ignoring the error" , err )
2021-07-19 19:49:02 +08:00
danglingParent = false
2021-04-30 15:16:03 +08:00
}
2021-07-19 19:49:02 +08:00
if ! danglingParent {
return processedIDs , nil
2021-04-30 15:16:03 +08:00
}
// Recurse into removing the parent.
2021-07-19 19:49:02 +08:00
return parent . removeRecursive ( ctx , rmMap , processedIDs , "" , options )
2021-04-11 01:44:51 +08:00
}
2021-08-10 16:30:27 +08:00
var errTagDigest = errors . New ( "tag by digest not supported" )
2021-04-11 01:44:51 +08:00
// Tag the image with the specified name and store it in the local containers
// storage. The name is normalized according to the rules of NormalizeName.
func ( i * Image ) Tag ( name string ) error {
2021-08-10 16:30:27 +08:00
if strings . HasPrefix ( name , "sha256:" ) { // ambiguous input
2022-07-11 22:11:52 +08:00
return fmt . Errorf ( "%s: %w" , name , errTagDigest )
2021-08-10 16:30:27 +08:00
}
2021-04-11 01:44:51 +08:00
ref , err := NormalizeName ( name )
if err != nil {
2022-07-11 22:11:52 +08:00
return fmt . Errorf ( "normalizing name %q: %w" , name , err )
2021-04-11 01:44:51 +08:00
}
2021-08-10 16:30:27 +08:00
if _ , isDigested := ref . ( reference . Digested ) ; isDigested {
2022-07-11 22:11:52 +08:00
return fmt . Errorf ( "%s: %w" , name , errTagDigest )
2021-08-10 16:30:27 +08:00
}
2021-04-11 01:44:51 +08:00
logrus . Debugf ( "Tagging image %s with %q" , i . ID ( ) , ref . String ( ) )
2021-05-18 16:02:48 +08:00
if i . runtime . eventChannel != nil {
2021-07-02 20:26:20 +08:00
defer i . runtime . writeEvent ( & Event { ID : i . ID ( ) , Name : name , Time : time . Now ( ) , Type : EventTypeImageTag } )
2021-05-18 16:02:48 +08:00
}
2021-04-11 01:44:51 +08:00
2023-10-24 14:58:31 +08:00
if err := i . runtime . store . AddNames ( i . ID ( ) , [ ] string { ref . String ( ) } ) ; err != nil {
2021-04-11 01:44:51 +08:00
return err
}
return i . reload ( )
}
2021-04-30 15:16:03 +08:00
// to have some symmetry with the errors from containers/storage.
var errTagUnknown = errors . New ( "tag not known" )
// TODO (@vrothberg) - `docker rmi sha256:` will remove the digest from the
// image. However, that's something containers storage does not support.
var errUntagDigest = errors . New ( "untag by digest not supported" )
2021-04-11 01:44:51 +08:00
// Untag the image with the specified name and make the change persistent in
// the local containers storage. The name is normalized according to the rules
// of NormalizeName.
func ( i * Image ) Untag ( name string ) error {
2021-08-10 16:30:27 +08:00
if strings . HasPrefix ( name , "sha256:" ) { // ambiguous input
2022-07-11 22:11:52 +08:00
return fmt . Errorf ( "%s: %w" , name , errUntagDigest )
2021-04-30 15:16:03 +08:00
}
2021-04-11 01:44:51 +08:00
ref , err := NormalizeName ( name )
if err != nil {
2023-10-24 14:58:31 +08:00
return err
2021-04-11 01:44:51 +08:00
}
2021-08-10 16:30:27 +08:00
2021-08-20 17:48:53 +08:00
// FIXME: this is breaking Podman CI but must be re-enabled once
2024-01-26 23:27:18 +08:00
// c/storage supports altering the digests of an image. Then,
2021-08-20 17:48:53 +08:00
// Podman will do the right thing.
//
// !!! Also make sure to re-enable the tests !!!
//
2022-07-11 22:11:52 +08:00
// if _, isDigested := ref.(reference.Digested); isDigested {
// return fmt.Errorf("%s: %w", name, errUntagDigest)
// }
2021-08-10 16:30:27 +08:00
2021-04-11 01:44:51 +08:00
name = ref . String ( )
2023-10-24 14:58:31 +08:00
// Return an error if the name is not found, the c/storage
// RemoveNames() API does not create one if no match is found.
2025-09-17 03:55:13 +08:00
if ! slices . Contains ( i . Names ( ) , name ) {
2022-07-11 22:11:52 +08:00
return fmt . Errorf ( "%s: %w" , name , errTagUnknown )
2021-04-11 01:44:51 +08:00
}
2023-10-24 14:58:31 +08:00
logrus . Debugf ( "Untagging %q from image %s" , ref . String ( ) , i . ID ( ) )
if i . runtime . eventChannel != nil {
defer i . runtime . writeEvent ( & Event { ID : i . ID ( ) , Name : name , Time : time . Now ( ) , Type : EventTypeImageUntag } )
}
if err := i . runtime . store . RemoveNames ( i . ID ( ) , [ ] string { name } ) ; err != nil {
2021-04-11 01:44:51 +08:00
return err
}
return i . reload ( )
}
// RepoTags returns a string slice of repotags associated with the image.
func ( i * Image ) RepoTags ( ) ( [ ] string , error ) {
namedTagged , err := i . NamedTaggedRepoTags ( )
if err != nil {
return nil , err
}
repoTags := make ( [ ] string , len ( namedTagged ) )
for i := range namedTagged {
repoTags [ i ] = namedTagged [ i ] . String ( )
}
return repoTags , nil
}
2021-04-30 15:16:03 +08:00
// NamedTaggedRepoTags returns the repotags associated with the image as a
2021-04-11 01:44:51 +08:00
// slice of reference.NamedTagged.
func ( i * Image ) NamedTaggedRepoTags ( ) ( [ ] reference . NamedTagged , error ) {
2022-05-12 20:29:35 +08:00
repoTags := make ( [ ] reference . NamedTagged , 0 , len ( i . Names ( ) ) )
2021-04-11 01:44:51 +08:00
for _ , name := range i . Names ( ) {
2021-04-30 15:16:03 +08:00
parsed , err := reference . Parse ( name )
2021-04-11 01:44:51 +08:00
if err != nil {
return nil , err
}
2021-04-30 15:16:03 +08:00
named , isNamed := parsed . ( reference . Named )
if ! isNamed {
continue
}
tagged , isTagged := named . ( reference . NamedTagged )
if ! isTagged {
continue
}
repoTags = append ( repoTags , tagged )
}
return repoTags , nil
}
// NamedRepoTags returns the repotags associated with the image as a
// slice of reference.Named.
func ( i * Image ) NamedRepoTags ( ) ( [ ] reference . Named , error ) {
2023-07-14 01:57:20 +08:00
// FIXME: the NamedRepoTags name is a bit misleading as it can return
// repo@digest values if that’ s how an image was pulled.
2021-04-30 15:16:03 +08:00
var repoTags [ ] reference . Named
for _ , name := range i . Names ( ) {
parsed , err := reference . Parse ( name )
if err != nil {
return nil , err
}
if named , isNamed := parsed . ( reference . Named ) ; isNamed {
repoTags = append ( repoTags , named )
2021-04-11 01:44:51 +08:00
}
}
return repoTags , nil
}
2023-07-22 02:30:14 +08:00
// referenceFuzzilyMatchingRepoAndTag checks if the image’ s repo (and tag if requiredTag != "") matches a fuzzy short input,
// and if so, returns the matching reference.
//
// DO NOT ADD ANY NEW USERS OF THIS SEMANTICS. Rely on existing libimage calls like LookupImage instead,
// and handle unqualified the way it does (c/image/pkg/shortnames).
func ( i * Image ) referenceFuzzilyMatchingRepoAndTag ( requiredRepo reference . Named , requiredTag string ) ( reference . Named , error ) {
2021-04-30 15:16:03 +08:00
repoTags , err := i . NamedRepoTags ( )
if err != nil {
return nil , err
}
2023-07-22 02:30:14 +08:00
name := requiredRepo . Name ( )
2023-07-14 01:57:20 +08:00
for _ , r := range repoTags {
2023-07-22 02:30:14 +08:00
if requiredTag != "" {
2023-07-14 01:57:20 +08:00
tagged , isTagged := r . ( reference . NamedTagged )
2023-07-22 02:30:14 +08:00
if ! isTagged || tagged . Tag ( ) != requiredTag {
2023-07-14 01:57:20 +08:00
continue
}
2021-04-30 15:16:03 +08:00
}
2023-07-14 01:57:20 +08:00
repoName := r . Name ( )
if ! strings . HasSuffix ( repoName , name ) {
2021-04-30 15:16:03 +08:00
continue
}
2023-07-14 01:57:20 +08:00
if len ( repoName ) == len ( name ) { // full match
return r , nil
2021-04-30 15:16:03 +08:00
}
2023-07-14 01:57:20 +08:00
if repoName [ len ( repoName ) - len ( name ) - 1 ] == '/' { // matches at repo
return r , nil
2021-04-30 15:16:03 +08:00
}
}
return nil , nil
}
// RepoDigests returns a string array of repodigests associated with the image.
2021-04-11 01:44:51 +08:00
func ( i * Image ) RepoDigests ( ) ( [ ] string , error ) {
2021-04-30 15:16:03 +08:00
repoDigests := [ ] string { }
2021-04-11 01:44:51 +08:00
added := make ( map [ string ] struct { } )
for _ , name := range i . Names ( ) {
for _ , imageDigest := range append ( i . Digests ( ) , i . Digest ( ) ) {
if imageDigest == "" {
continue
}
named , err := reference . ParseNormalizedNamed ( name )
if err != nil {
return nil , err
}
canonical , err := reference . WithDigest ( reference . TrimNamed ( named ) , imageDigest )
if err != nil {
return nil , err
}
if _ , alreadyInList := added [ canonical . String ( ) ] ; ! alreadyInList {
repoDigests = append ( repoDigests , canonical . String ( ) )
added [ canonical . String ( ) ] = struct { } { }
}
}
}
sort . Strings ( repoDigests )
return repoDigests , nil
}
// Mount the image with the specified mount options and label, both of which
// are directly passed down to the containers storage. Returns the fully
// evaluated path to the mount point.
2023-06-15 17:48:42 +08:00
func ( i * Image ) Mount ( _ context . Context , mountOptions [ ] string , mountLabel string ) ( string , error ) {
2021-05-18 16:02:48 +08:00
if i . runtime . eventChannel != nil {
2021-07-02 20:26:20 +08:00
defer i . runtime . writeEvent ( & Event { ID : i . ID ( ) , Name : "" , Time : time . Now ( ) , Type : EventTypeImageMount } )
2021-05-18 16:02:48 +08:00
}
2021-04-11 01:44:51 +08:00
mountPoint , err := i . runtime . store . MountImage ( i . ID ( ) , mountOptions , mountLabel )
if err != nil {
return "" , err
}
mountPoint , err = filepath . EvalSymlinks ( mountPoint )
if err != nil {
return "" , err
}
logrus . Debugf ( "Mounted image %s at %q" , i . ID ( ) , mountPoint )
return mountPoint , nil
}
2021-04-30 15:16:03 +08:00
// Mountpoint returns the path to image's mount point. The path is empty if
// the image is not mounted.
func ( i * Image ) Mountpoint ( ) ( string , error ) {
2025-04-17 23:36:04 +08:00
for _ , layerID := range append ( [ ] string { i . TopLayer ( ) } , i . storageImage . MappedTopLayers ... ) {
mountedTimes , err := i . runtime . store . Mounted ( layerID )
if err != nil {
if errors . Is ( err , storage . ErrLayerUnknown ) {
// Can happen, Podman did it, but there's no
// explanation why.
continue
}
return "" , err
}
if mountedTimes > 0 {
layer , err := i . runtime . store . Layer ( layerID )
if err != nil {
return "" , err
}
return filepath . EvalSymlinks ( layer . MountPoint )
2021-04-30 15:16:03 +08:00
}
}
2025-04-17 23:36:04 +08:00
return "" , nil
2021-04-30 15:16:03 +08:00
}
2021-04-11 01:44:51 +08:00
// Unmount the image. Use force to ignore the reference counter and forcefully
// unmount.
func ( i * Image ) Unmount ( force bool ) error {
2021-05-18 16:02:48 +08:00
if i . runtime . eventChannel != nil {
2021-07-02 20:26:20 +08:00
defer i . runtime . writeEvent ( & Event { ID : i . ID ( ) , Name : "" , Time : time . Now ( ) , Type : EventTypeImageUnmount } )
2021-05-18 16:02:48 +08:00
}
2021-04-11 01:44:51 +08:00
logrus . Debugf ( "Unmounted image %s" , i . ID ( ) )
_ , err := i . runtime . store . UnmountImage ( i . ID ( ) , force )
return err
}
// Size computes the size of the image layers and associated data.
func ( i * Image ) Size ( ) ( int64 , error ) {
2022-10-20 05:36:20 +08:00
if i . cached . size != nil {
return * i . cached . size , nil
}
size , err := i . runtime . store . ImageSize ( i . ID ( ) )
i . cached . size = & size
return size , err
2021-04-11 01:44:51 +08:00
}
2021-09-21 16:32:27 +08:00
// HasDifferentDigestOptions allows for customizing the check if another
// (remote) image has a different digest.
type HasDifferentDigestOptions struct {
// containers-auth.json(5) file to use when authenticating against
// container registries.
AuthFilePath string
2023-07-09 19:35:05 +08:00
// Allow contacting registries over HTTP, or HTTPS with failed TLS
// verification. Note that this does not affect other TLS connections.
InsecureSkipTLSVerify types . OptionalBool
2021-09-21 16:32:27 +08:00
}
2021-04-11 01:44:51 +08:00
// HasDifferentDigest returns true if the image specified by `remoteRef` has a
// different digest than the local one. This check can be useful to check for
// updates on remote registries.
2021-09-21 16:32:27 +08:00
func ( i * Image ) HasDifferentDigest ( ctx context . Context , remoteRef types . ImageReference , options * HasDifferentDigestOptions ) ( bool , error ) {
2021-04-11 01:44:51 +08:00
// We need to account for the arch that the image uses. It seems
// common on ARM to tweak this option to pull the correct image. See
// github.com/containers/podman/issues/6613.
inspectInfo , err := i . inspectInfo ( ctx )
if err != nil {
return false , err
}
2021-04-30 15:16:03 +08:00
sys := i . runtime . systemContextCopy ( )
2021-04-11 01:44:51 +08:00
sys . ArchitectureChoice = inspectInfo . Architecture
// OS and variant may not be set, so let's check to avoid accidental
// overrides of the runtime settings.
if inspectInfo . Os != "" {
sys . OSChoice = inspectInfo . Os
}
if inspectInfo . Variant != "" {
sys . VariantChoice = inspectInfo . Variant
}
2023-07-09 19:35:05 +08:00
if options != nil {
if options . AuthFilePath != "" {
sys . AuthFilePath = options . AuthFilePath
}
if options . InsecureSkipTLSVerify != types . OptionalBoolUndefined {
sys . DockerInsecureSkipTLSVerify = options . InsecureSkipTLSVerify
sys . OCIInsecureSkipTLSVerify = options . InsecureSkipTLSVerify == types . OptionalBoolTrue
sys . DockerDaemonInsecureSkipTLSVerify = options . InsecureSkipTLSVerify == types . OptionalBoolTrue
}
2021-09-21 16:32:27 +08:00
}
return i . hasDifferentDigestWithSystemContext ( ctx , remoteRef , sys )
}
func ( i * Image ) hasDifferentDigestWithSystemContext ( ctx context . Context , remoteRef types . ImageReference , sys * types . SystemContext ) ( bool , error ) {
2021-04-30 15:16:03 +08:00
remoteImg , err := remoteRef . NewImage ( ctx , sys )
2021-04-11 01:44:51 +08:00
if err != nil {
return false , err
}
2023-08-31 15:22:36 +08:00
defer remoteImg . Close ( )
2021-04-11 01:44:51 +08:00
2021-05-18 16:02:48 +08:00
rawManifest , rawManifestMIMEType , err := remoteImg . Manifest ( ctx )
2021-04-11 01:44:51 +08:00
if err != nil {
return false , err
}
2021-05-18 16:02:48 +08:00
// If the remote ref's manifest is a list, try to zero in on the image
// in the list that we would eventually try to pull.
var remoteDigest digest . Digest
if manifest . MIMETypeIsMultiImage ( rawManifestMIMEType ) {
list , err := manifest . ListFromBlob ( rawManifest , rawManifestMIMEType )
if err != nil {
return false , err
}
remoteDigest , err = list . ChooseInstance ( sys )
if err != nil {
return false , err
}
} else {
remoteDigest , err = manifest . Digest ( rawManifest )
if err != nil {
return false , err
}
2021-04-11 01:44:51 +08:00
}
2021-05-18 16:02:48 +08:00
// Check if we already have that image's manifest in this image. A
// single image can have multiple manifests that describe the same
// config blob and layers, so treat any match as a successful match.
for _ , digest := range append ( i . Digests ( ) , i . Digest ( ) ) {
if digest . Validate ( ) != nil {
continue
}
if digest . String ( ) == remoteDigest . String ( ) {
return false , nil
}
}
// No matching digest found in the local image.
return true , nil
2021-04-11 01:44:51 +08:00
}
2025-09-17 03:55:13 +08:00
// driverData gets the driver data from the store on a layer.
2021-04-30 15:16:03 +08:00
func ( i * Image ) driverData ( ) ( * DriverData , error ) {
2021-04-11 01:44:51 +08:00
store := i . runtime . store
layerID := i . TopLayer ( )
driver , err := store . GraphDriver ( )
if err != nil {
return nil , err
}
metaData , err := driver . Metadata ( layerID )
if err != nil {
return nil , err
}
if mountTimes , err := store . Mounted ( layerID ) ; mountTimes == 0 || err != nil {
delete ( metaData , "MergedDir" )
}
2021-04-30 15:16:03 +08:00
return & DriverData {
2021-04-11 01:44:51 +08:00
Name : driver . String ( ) ,
Data : metaData ,
} , nil
}
// StorageReference returns the image's reference to the containers storage
// using the image ID.
func ( i * Image ) StorageReference ( ) ( types . ImageReference , error ) {
if i . storageReference != nil {
return i . storageReference , nil
}
ref , err := storageTransport . Transport . ParseStoreReference ( i . runtime . store , "@" + i . ID ( ) )
if err != nil {
return nil , err
}
i . storageReference = ref
return ref , nil
}
// source returns the possibly cached image reference.
func ( i * Image ) source ( ctx context . Context ) ( types . ImageSource , error ) {
if i . cached . imageSource != nil {
return i . cached . imageSource , nil
}
ref , err := i . StorageReference ( )
if err != nil {
return nil , err
}
2021-04-30 15:16:03 +08:00
src , err := ref . NewImageSource ( ctx , i . runtime . systemContextCopy ( ) )
2021-04-11 01:44:51 +08:00
if err != nil {
return nil , err
}
i . cached . imageSource = src
return src , nil
}
2021-04-30 15:16:03 +08:00
// rawConfigBlob returns the image's config as a raw byte slice. Users need to
2025-09-17 03:55:13 +08:00
// unmarshal it to the corresponding type (OCI, Docker v2s{1,2}).
2021-04-30 15:16:03 +08:00
func ( i * Image ) rawConfigBlob ( ctx context . Context ) ( [ ] byte , error ) {
ref , err := i . StorageReference ( )
if err != nil {
return nil , err
}
imageCloser , err := ref . NewImage ( ctx , i . runtime . systemContextCopy ( ) )
if err != nil {
return nil , err
}
defer imageCloser . Close ( )
return imageCloser . ConfigBlob ( ctx )
}
// Manifest returns the raw data and the MIME type of the image's manifest.
func ( i * Image ) Manifest ( ctx context . Context ) ( rawManifest [ ] byte , mimeType string , err error ) {
src , err := i . source ( ctx )
if err != nil {
return nil , "" , err
}
2025-03-20 18:38:51 +08:00
return image . UnparsedInstance ( src , nil ) . Manifest ( ctx )
2021-04-30 15:16:03 +08:00
}
2021-07-30 07:58:43 +08:00
// getImageID creates an image object and uses the hex value of the config
2025-09-17 03:55:13 +08:00
// blob's digest (if it has one) as the image ID for parsing the store reference.
2021-07-30 07:58:43 +08:00
func getImageID ( ctx context . Context , src types . ImageReference , sys * types . SystemContext ) ( string , error ) {
2021-04-11 01:44:51 +08:00
newImg , err := src . NewImage ( ctx , sys )
if err != nil {
return "" , err
}
defer func ( ) {
if err := newImg . Close ( ) ; err != nil {
2021-12-02 03:08:25 +08:00
logrus . Errorf ( "Failed to close image: %q" , err )
2021-04-11 01:44:51 +08:00
}
} ( )
imageDigest := newImg . ConfigInfo ( ) . Digest
if err = imageDigest . Validate ( ) ; err != nil {
2022-07-11 22:11:52 +08:00
return "" , fmt . Errorf ( "getting config info: %w" , err )
2021-04-11 01:44:51 +08:00
}
2021-07-30 07:58:43 +08:00
return "@" + imageDigest . Encoded ( ) , nil
2021-04-11 01:44:51 +08:00
}
2023-10-11 23:30:49 +08:00
// Checks whether the image matches the specified platform.
// Returns
// - 1) a matching error that can be used for logging (or returning) what does not match
// - 2) a bool indicating whether architecture, os or variant were set (some callers need that to decide whether they need to throw an error)
// - 3) a fatal error that occurred prior to check for matches (e.g., storage errors etc.)
func ( i * Image ) matchesPlatform ( ctx context . Context , os , arch , variant string ) ( error , bool , error ) {
2024-01-26 23:27:18 +08:00
if err := i . isCorrupted ( ctx , "" ) ; err != nil {
2023-10-11 23:30:49 +08:00
return err , false , nil
}
inspectInfo , err := i . inspectInfo ( ctx )
if err != nil {
return nil , false , fmt . Errorf ( "inspecting image: %w" , err )
}
customPlatform := len ( os ) + len ( arch ) + len ( variant ) != 0
expected , err := platforms . Parse ( platform . ToString ( os , arch , variant ) )
if err != nil {
return nil , false , fmt . Errorf ( "parsing host platform: %v" , err )
}
fromImage , err := platforms . Parse ( platform . ToString ( inspectInfo . Os , inspectInfo . Architecture , inspectInfo . Variant ) )
if err != nil {
return nil , false , fmt . Errorf ( "parsing image platform: %v" , err )
}
if platforms . NewMatcher ( expected ) . Match ( fromImage ) {
return nil , customPlatform , nil
}
return fmt . Errorf ( "image platform (%s) does not match the expected platform (%s)" , platforms . Format ( fromImage ) , platforms . Format ( expected ) ) , customPlatform , nil
}