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
"os"
"strings"
2021-04-30 15:16:03 +08:00
deepcopy "github.com/jinzhu/copier"
2021-12-02 03:08:25 +08:00
jsoniter "github.com/json-iterator/go"
2023-07-14 01:57:20 +08:00
"github.com/opencontainers/go-digest"
2021-04-11 01:44:51 +08:00
"github.com/sirupsen/logrus"
2025-08-29 20:55:12 +08:00
"go.podman.io/common/libimage/define"
"go.podman.io/common/libimage/platform"
"go.podman.io/common/pkg/config"
"go.podman.io/image/v5/docker/reference"
"go.podman.io/image/v5/pkg/shortnames"
storageTransport "go.podman.io/image/v5/storage"
"go.podman.io/image/v5/transports/alltransports"
"go.podman.io/image/v5/types"
"go.podman.io/storage"
2021-04-11 01:44:51 +08:00
)
2021-12-02 03:08:25 +08:00
// Faster than the standard library, see https://github.com/json-iterator/go.
var json = jsoniter . ConfigCompatibleWithStandardLibrary
// tmpdir returns a path to a temporary directory.
2022-05-12 20:29:35 +08:00
func tmpdir ( ) ( string , error ) {
var tmpdir string
defaultContainerConfig , err := config . Default ( )
if err == nil {
tmpdir , err = defaultContainerConfig . ImageCopyTmpDir ( )
if err == nil {
return tmpdir , nil
}
2021-12-02 03:08:25 +08:00
}
2022-05-12 20:29:35 +08:00
return tmpdir , err
2021-12-02 03:08:25 +08:00
}
2021-04-11 01:44:51 +08:00
// RuntimeOptions allow for creating a customized Runtime.
type RuntimeOptions struct {
2021-05-18 16:02:48 +08:00
// The base system context of the runtime which will be used throughout
// the entire lifespan of the Runtime. Certain options in some
// functions may override specific fields.
2021-04-11 01:44:51 +08:00
SystemContext * types . SystemContext
}
// setRegistriesConfPath sets the registries.conf path for the specified context.
func setRegistriesConfPath ( systemContext * types . SystemContext ) {
if systemContext . SystemRegistriesConfPath != "" {
return
}
if envOverride , ok := os . LookupEnv ( "CONTAINERS_REGISTRIES_CONF" ) ; ok {
systemContext . SystemRegistriesConfPath = envOverride
return
}
if envOverride , ok := os . LookupEnv ( "REGISTRIES_CONFIG_PATH" ) ; ok {
systemContext . SystemRegistriesConfPath = envOverride
return
}
}
// Runtime is responsible for image management and storing them in a containers
// storage.
type Runtime struct {
2021-05-18 16:02:48 +08:00
// Use to send events out to users.
eventChannel chan * Event
2021-04-11 01:44:51 +08:00
// Underlying storage store.
store storage . Store
// Global system context. No pointer to simplify copying and modifying
// it.
systemContext types . SystemContext
2021-04-30 15:16:03 +08:00
}
2025-09-17 03:55:13 +08:00
// SystemContext returns a copy of the runtime's system context.
2021-07-02 20:26:20 +08:00
func ( r * Runtime ) SystemContext ( ) * types . SystemContext {
return r . systemContextCopy ( )
}
2021-04-30 15:16:03 +08:00
// Returns a copy of the runtime's system context.
func ( r * Runtime ) systemContextCopy ( ) * types . SystemContext {
var sys types . SystemContext
2022-04-21 04:21:48 +08:00
_ = deepcopy . Copy ( & sys , & r . systemContext )
2021-04-30 15:16:03 +08:00
return & sys
2021-04-11 01:44:51 +08:00
}
2021-05-18 16:02:48 +08:00
// EventChannel creates a buffered channel for events that the Runtime will use
// to write events to. Callers are expected to read from the channel in a
// timely manner.
// Can be called once for a given Runtime.
func ( r * Runtime ) EventChannel ( ) chan * Event {
if r . eventChannel != nil {
return r . eventChannel
}
r . eventChannel = make ( chan * Event , 100 )
return r . eventChannel
}
2021-04-11 01:44:51 +08:00
// RuntimeFromStore returns a Runtime for the specified store.
func RuntimeFromStore ( store storage . Store , options * RuntimeOptions ) ( * Runtime , error ) {
if options == nil {
options = & RuntimeOptions { }
}
var systemContext types . SystemContext
if options . SystemContext != nil {
systemContext = * options . SystemContext
} else {
systemContext = types . SystemContext { }
}
2021-06-21 16:29:14 +08:00
if systemContext . BigFilesTemporaryDir == "" {
2022-05-12 20:29:35 +08:00
tmpdir , err := tmpdir ( )
if err != nil {
return nil , err
}
systemContext . BigFilesTemporaryDir = tmpdir
2021-06-21 16:29:14 +08:00
}
2021-04-11 01:44:51 +08:00
setRegistriesConfPath ( & systemContext )
return & Runtime {
store : store ,
systemContext : systemContext ,
} , nil
}
// RuntimeFromStoreOptions returns a return for the specified store options.
func RuntimeFromStoreOptions ( runtimeOptions * RuntimeOptions , storeOptions * storage . StoreOptions ) ( * Runtime , error ) {
if storeOptions == nil {
storeOptions = & storage . StoreOptions { }
}
store , err := storage . GetStore ( * storeOptions )
if err != nil {
return nil , err
}
storageTransport . Transport . SetStore ( store )
return RuntimeFromStore ( store , runtimeOptions )
}
// Shutdown attempts to free any kernel resources which are being used by the
// underlying driver. If "force" is true, any mounted (i.e., in use) layers
// are unmounted beforehand. If "force" is not true, then layers being in use
// is considered to be an error condition.
func ( r * Runtime ) Shutdown ( force bool ) error {
_ , err := r . store . Shutdown ( force )
2021-05-18 16:02:48 +08:00
if r . eventChannel != nil {
close ( r . eventChannel )
}
2021-04-11 01:44:51 +08:00
return err
}
// storageToImage transforms a storage.Image to an Image.
func ( r * Runtime ) storageToImage ( storageImage * storage . Image , ref types . ImageReference ) * Image {
2021-04-30 15:16:03 +08:00
return & Image {
2021-04-11 01:44:51 +08:00
runtime : r ,
storageImage : storageImage ,
storageReference : ref ,
}
}
2025-09-17 03:55:13 +08:00
// getImagesAndLayers obtains consistent slices of Image and storage.Layer.
2024-08-30 19:07:24 +08:00
func ( r * Runtime ) getImagesAndLayers ( ) ( [ ] * Image , [ ] storage . Layer , error ) {
snapshot , err := r . store . MultiList (
storage . MultiListOptions {
Images : true ,
Layers : true ,
} )
if err != nil {
return nil , nil , err
}
images := [ ] * Image { }
for i := range snapshot . Images {
images = append ( images , r . storageToImage ( & snapshot . Images [ i ] , nil ) )
}
return images , snapshot . Layers , nil
}
2024-01-26 23:27:18 +08:00
// Exists returns true if the specified image exists in the local containers
2021-06-18 03:24:07 +08:00
// storage. Note that it may return false if an image corrupted.
2021-04-11 01:44:51 +08:00
func ( r * Runtime ) Exists ( name string ) ( bool , error ) {
2021-07-02 20:26:20 +08:00
image , _ , err := r . LookupImage ( name , nil )
2022-07-11 22:11:52 +08:00
if err != nil && ! errors . Is ( err , storage . ErrImageUnknown ) {
2021-04-30 15:16:03 +08:00
return false , err
}
2021-06-18 03:24:07 +08:00
if image == nil {
return false , nil
}
2024-01-26 23:27:18 +08:00
if err := image . isCorrupted ( context . Background ( ) , name ) ; err != nil {
2021-07-02 20:26:20 +08:00
logrus . Error ( err )
2021-06-18 03:24:07 +08:00
return false , nil
}
return true , nil
2021-04-11 01:44:51 +08:00
}
// LookupImageOptions allow for customizing local image lookups.
type LookupImageOptions struct {
2021-06-21 16:29:14 +08:00
// Lookup an image matching the specified architecture.
Architecture string
// Lookup an image matching the specified OS.
OS string
// Lookup an image matching the specified variant.
Variant string
2022-05-12 21:00:29 +08:00
// Controls the behavior when checking the platform of an image.
2023-10-11 23:30:49 +08:00
PlatformPolicy define . PlatformPolicy
2022-05-12 21:00:29 +08:00
2021-05-18 16:02:48 +08:00
// If set, do not look for items/instances in the manifest list that
// match the current platform but return the manifest list as is.
2021-08-27 15:31:51 +08:00
// only check for manifest list, return ErrNotAManifestList if not found.
2021-05-18 16:02:48 +08:00
lookupManifest bool
2021-07-02 20:26:20 +08:00
2021-08-27 15:31:51 +08:00
// If matching images resolves to a manifest list, return manifest list
// instead of resolving to image instance, if manifest list is not found
// try resolving image.
ManifestList bool
2021-07-02 20:26:20 +08:00
// If the image resolves to a manifest list, we usually lookup a
// matching instance and error if none could be found. In this case,
// just return the manifest list. Required for image removal.
returnManifestIfNoInstance bool
2021-04-11 01:44:51 +08:00
}
2022-04-05 02:23:54 +08:00
var errNoHexValue = errors . New ( "invalid format: no 64-byte hexadecimal value" )
2025-09-17 03:55:13 +08:00
// LookupImage looks up `name` in the local container storage. Returns the
2021-07-02 20:26:20 +08:00
// image and the name it has been found with. Note that name may also use the
// `containers-storage:` prefix used to refer to the containers-storage
// transport. Returns storage.ErrImageUnknown if the image could not be found.
//
// Unless specified via the options, the image will be looked up by name only
// without matching the architecture, os or variant. An exception is if the
// image resolves to a manifest list, where an instance of the manifest list
// matching the local or specified platform (via options.{Architecture,OS,Variant})
// is returned.
2021-04-11 01:44:51 +08:00
//
// If the specified name uses the `containers-storage` transport, the resolved
// name is empty.
func ( r * Runtime ) LookupImage ( name string , options * LookupImageOptions ) ( * Image , string , error ) {
logrus . Debugf ( "Looking up image %q in local containers storage" , name )
if options == nil {
options = & LookupImageOptions { }
}
// If needed extract the name sans transport.
storageRef , err := alltransports . ParseImageName ( name )
if err == nil {
if storageRef . Transport ( ) . Name ( ) != storageTransport . Transport . Name ( ) {
2022-07-11 22:11:52 +08:00
return nil , "" , fmt . Errorf ( "unsupported transport %q for looking up local images" , storageRef . Transport ( ) . Name ( ) )
2021-04-11 01:44:51 +08:00
}
2024-01-26 23:27:18 +08:00
_ , img , err := storageTransport . ResolveReference ( storageRef )
2021-04-11 01:44:51 +08:00
if err != nil {
2024-01-26 23:27:18 +08:00
if errors . Is ( err , storageTransport . ErrNoSuchImage ) {
// backward compatibility
return nil , "" , storage . ErrImageUnknown
}
2021-04-11 01:44:51 +08:00
return nil , "" , err
}
logrus . Debugf ( "Found image %q in local containers storage (%s)" , name , storageRef . StringWithinTransport ( ) )
return r . storageToImage ( img , storageRef ) , "" , nil
}
2022-05-12 20:29:35 +08:00
// Docker compat: strip off the tag iff name is tagged and digested
// (e.g., fedora:latest@sha256...). In that case, the tag is stripped
// off and entirely ignored. The digest is the sole source of truth.
2023-07-14 01:57:20 +08:00
normalizedName , possiblyUnqualifiedNamedReference , err := normalizeTaggedDigestedString ( name )
2022-05-12 20:29:35 +08:00
if err != nil {
2024-05-10 05:35:14 +08:00
return nil , "" , fmt . Errorf ( ` parsing reference %q: %w ` , name , err )
2022-05-12 20:29:35 +08:00
}
name = normalizedName
2021-04-11 01:44:51 +08:00
2022-04-05 02:23:54 +08:00
byDigest := false
2021-04-30 15:16:03 +08:00
originalName := name
2021-04-11 01:44:51 +08:00
if strings . HasPrefix ( name , "sha256:" ) {
2022-04-05 02:23:54 +08:00
byDigest = true
2021-04-11 01:44:51 +08:00
name = strings . TrimPrefix ( name , "sha256:" )
}
2022-04-05 02:23:54 +08:00
byFullID := reference . IsFullIdentifier ( name )
if byDigest && ! byFullID {
return nil , "" , fmt . Errorf ( "%s: %v" , originalName , errNoHexValue )
}
// If the name clearly refers to a local image, try to look it up.
if byFullID || byDigest {
2023-07-14 01:57:20 +08:00
img , err := r . lookupImageInLocalStorage ( originalName , name , nil , options )
2022-04-05 02:23:54 +08:00
if err != nil {
return nil , "" , err
}
if img != nil {
return img , originalName , nil
}
2022-07-11 22:11:52 +08:00
return nil , "" , fmt . Errorf ( "%s: %w" , originalName , storage . ErrImageUnknown )
2022-04-05 02:23:54 +08:00
}
2021-04-11 01:44:51 +08:00
2021-07-02 20:26:20 +08:00
// Unless specified, set the platform specified in the system context
// for later platform matching. Builder likes to set these things via
// the system context at runtime creation.
if options . Architecture == "" {
options . Architecture = r . systemContext . ArchitectureChoice
}
if options . OS == "" {
options . OS = r . systemContext . OSChoice
}
if options . Variant == "" {
options . Variant = r . systemContext . VariantChoice
2021-06-21 16:29:14 +08:00
}
2022-01-06 04:36:49 +08:00
// Normalize platform to be OCI compatible (e.g., "aarch64" -> "arm64").
2023-10-11 23:30:49 +08:00
options . OS , options . Architecture , options . Variant = platform . Normalize ( options . OS , options . Architecture , options . Variant )
2021-06-21 16:29:14 +08:00
2021-04-11 01:44:51 +08:00
// Second, try out the candidates as resolved by shortnames. This takes
// "localhost/" prefixed images into account as well.
candidates , err := shortnames . ResolveLocally ( & r . systemContext , name )
if err != nil {
2022-07-11 22:11:52 +08:00
return nil , "" , fmt . Errorf ( "%s: %w" , name , storage . ErrImageUnknown )
2021-04-11 01:44:51 +08:00
}
// Backwards compat: normalize to docker.io as some users may very well
// rely on that.
2021-04-30 15:16:03 +08:00
if dockerNamed , err := reference . ParseDockerRef ( name ) ; err == nil {
candidates = append ( candidates , dockerNamed )
2021-04-11 01:44:51 +08:00
}
for _ , candidate := range candidates {
2023-07-14 01:57:20 +08:00
img , err := r . lookupImageInLocalStorage ( name , candidate . String ( ) , candidate , options )
2021-04-11 01:44:51 +08:00
if err != nil {
return nil , "" , err
}
if img != nil {
return img , candidate . String ( ) , err
}
}
2022-04-05 02:23:54 +08:00
// The specified name may refer to a short ID. Note that this *must*
// happen after the short-name expansion as done above.
2023-07-14 01:57:20 +08:00
img , err := r . lookupImageInLocalStorage ( name , name , nil , options )
2022-04-05 02:23:54 +08:00
if err != nil {
return nil , "" , err
}
if img != nil {
return img , name , err
}
2023-07-14 01:57:20 +08:00
return r . lookupImageInDigestsAndRepoTags ( name , possiblyUnqualifiedNamedReference , options )
2021-04-30 15:16:03 +08:00
}
// lookupImageInLocalStorage looks up the specified candidate for name in the
// storage and checks whether it's matching the system context.
2023-07-14 01:57:20 +08:00
func ( r * Runtime ) lookupImageInLocalStorage ( name , candidate string , namedCandidate reference . Named , options * LookupImageOptions ) ( * Image , error ) {
2021-04-30 15:16:03 +08:00
logrus . Debugf ( "Trying %q ..." , candidate )
2023-07-14 01:57:20 +08:00
var err error
var img * storage . Image
var ref types . ImageReference
// FIXME: the lookup logic for manifest lists needs improvement.
// See https://github.com/containers/common/pull/1505#discussion_r1242677279
// for details.
// For images pulled by tag, Image.Names does not currently contain a
// repo@digest value, so such an input would not match directly in
// c/storage.
if namedCandidate != nil {
namedCandidate = reference . TagNameOnly ( namedCandidate )
ref , err = storageTransport . Transport . NewStoreReference ( r . store , namedCandidate , "" )
if err != nil {
return nil , err
}
2024-01-26 23:27:18 +08:00
_ , img , err = storageTransport . ResolveReference ( ref )
2023-07-14 01:57:20 +08:00
if err != nil {
2024-01-26 23:27:18 +08:00
if errors . Is ( err , storageTransport . ErrNoSuchImage ) {
2023-07-14 01:57:20 +08:00
return nil , nil
}
return nil , err
}
// NOTE: we must reparse the reference another time below since
// an ordinary image may have resolved into a per-platform image
// without any regard to options.{Architecture,OS,Variant}.
} else {
img , err = r . store . Image ( candidate )
if err != nil {
if errors . Is ( err , storage . ErrImageUnknown ) {
return nil , nil
}
return nil , err
}
2021-04-30 15:16:03 +08:00
}
2023-07-14 01:57:20 +08:00
ref , err = storageTransport . Transport . ParseStoreReference ( r . store , img . ID )
2021-04-30 15:16:03 +08:00
if err != nil {
return nil , err
}
image := r . storageToImage ( img , ref )
2021-05-18 16:02:48 +08:00
logrus . Debugf ( "Found image %q as %q in local containers storage" , name , candidate )
2021-04-30 15:16:03 +08:00
// If we referenced a manifest list, we need to check whether we can
// find a matching instance in the local containers storage.
isManifestList , err := image . IsManifestList ( context . Background ( ) )
if err != nil {
2022-07-11 22:11:52 +08:00
if errors . Is ( err , os . ErrNotExist ) {
2021-05-19 22:39:19 +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 if an image is a manifest list: %v, ignoring the error" , err )
2021-05-19 22:39:19 +08:00
return image , nil
}
2021-04-30 15:16:03 +08:00
return nil , err
}
2021-08-27 15:31:51 +08:00
if options . lookupManifest || options . ManifestList {
2021-05-18 16:02:48 +08:00
if isManifestList {
return image , nil
}
2021-08-27 15:31:51 +08:00
// return ErrNotAManifestList if lookupManifest is set otherwise try resolving image.
if options . lookupManifest {
2022-07-11 22:11:52 +08:00
return nil , fmt . Errorf ( "%s: %w" , candidate , ErrNotAManifestList )
2021-08-27 15:31:51 +08:00
}
2021-05-18 16:02:48 +08:00
}
2021-04-30 15:16:03 +08:00
if isManifestList {
2021-05-18 16:02:48 +08:00
logrus . Debugf ( "Candidate %q is a manifest list, looking up matching instance" , candidate )
2021-04-30 15:16:03 +08:00
manifestList , err := image . ToManifestList ( )
if err != nil {
return nil , err
}
2021-06-21 16:29:14 +08:00
instance , err := manifestList . LookupInstance ( context . Background ( ) , options . Architecture , options . OS , options . Variant )
2021-04-30 15:16:03 +08:00
if err != nil {
2021-07-02 20:26:20 +08:00
if options . returnManifestIfNoInstance {
logrus . Debug ( "No matching instance was found: returning manifest list instead" )
2021-05-18 16:02:48 +08:00
return image , nil
}
2022-07-11 22:11:52 +08:00
return nil , fmt . Errorf ( "%v: %w" , err , storage . ErrImageUnknown )
2021-04-30 15:16:03 +08:00
}
2021-05-18 16:02:48 +08:00
ref , err = storageTransport . Transport . ParseStoreReference ( r . store , "@" + instance . ID ( ) )
2021-04-30 15:16:03 +08:00
if err != nil {
return nil , err
}
2021-05-18 16:02:48 +08:00
image = instance
}
2021-04-30 15:16:03 +08:00
// Also print the string within the storage transport. That may aid in
// debugging when using additional stores since we see explicitly where
// the store is and which driver (options) are used.
logrus . Debugf ( "Found image %q as %q in local containers storage (%s)" , name , candidate , ref . StringWithinTransport ( ) )
2022-05-12 21:00:29 +08:00
// Do not perform any further platform checks if the image was
// requested by ID. In that case, we must assume that the user/tool
// know what they're doing.
if strings . HasPrefix ( image . ID ( ) , candidate ) {
return image , nil
}
// Ignore the (fatal) error since the image may be corrupted, which
// will bubble up at other places. During lookup, we just return it as
// is.
2022-07-11 22:11:52 +08:00
if matchError , customPlatform , _ := image . matchesPlatform ( context . Background ( ) , options . OS , options . Architecture , options . Variant ) ; matchError != nil {
2022-05-12 21:00:29 +08:00
if customPlatform {
logrus . Debugf ( "%v" , matchError )
// Return nil if the user clearly requested a custom
// platform and the located image does not match.
return nil , nil
}
switch options . PlatformPolicy {
2023-10-11 23:30:49 +08:00
case define . PlatformPolicyDefault :
2022-05-12 21:00:29 +08:00
logrus . Debugf ( "%v" , matchError )
2023-10-11 23:30:49 +08:00
case define . PlatformPolicyWarn :
2022-05-12 21:00:29 +08:00
logrus . Warnf ( "%v" , matchError )
}
}
2021-04-30 15:16:03 +08:00
return image , nil
}
// lookupImageInDigestsAndRepoTags attempts to match name against any image in
// the local containers storage. If name is digested, it will be compared
// against image digests. Otherwise, it will be looked up in the repo tags.
2023-07-14 01:57:20 +08:00
func ( r * Runtime ) lookupImageInDigestsAndRepoTags ( name string , possiblyUnqualifiedNamedReference reference . Named , options * LookupImageOptions ) ( * Image , string , error ) {
originalName := name // we may change name below
2021-04-30 15:16:03 +08:00
2023-07-14 01:57:20 +08:00
if possiblyUnqualifiedNamedReference == nil {
return nil , "" , fmt . Errorf ( "%s: %w" , originalName , storage . ErrImageUnknown )
2021-12-02 03:08:25 +08:00
}
if ! shortnames . IsShortName ( name ) {
2023-07-14 01:57:20 +08:00
return nil , "" , fmt . Errorf ( "%s: %w" , originalName , storage . ErrImageUnknown )
2021-04-30 15:16:03 +08:00
}
2023-07-22 02:30:14 +08:00
var requiredDigest digest . Digest // or ""
var requiredTag string // or ""
possiblyUnqualifiedNamedReference = reference . TagNameOnly ( possiblyUnqualifiedNamedReference ) // Docker compat: make sure to add the "latest" tag if needed.
if digested , ok := possiblyUnqualifiedNamedReference . ( reference . Digested ) ; ok {
requiredDigest = digested . Digest ( )
name = reference . TrimNamed ( possiblyUnqualifiedNamedReference ) . String ( )
} else if namedTagged , ok := possiblyUnqualifiedNamedReference . ( reference . NamedTagged ) ; ok {
requiredTag = namedTagged . Tag ( )
} else { // This should never happen after the reference.TagNameOnly above.
2023-07-14 01:57:20 +08:00
return nil , "" , fmt . Errorf ( "%s: %w (could not cast to tagged)" , originalName , storage . ErrImageUnknown )
}
2024-08-30 19:07:24 +08:00
allImages , err := r . ListImages ( context . Background ( ) , nil )
2023-07-14 01:57:20 +08:00
if err != nil {
return nil , "" , err
2021-04-30 15:16:03 +08:00
}
2021-12-02 03:08:25 +08:00
2021-04-30 15:16:03 +08:00
for _ , image := range allImages {
2023-07-22 02:30:14 +08:00
named , err := image . referenceFuzzilyMatchingRepoAndTag ( possiblyUnqualifiedNamedReference , requiredTag )
2021-04-30 15:16:03 +08:00
if err != nil {
return nil , "" , err
}
if named == nil {
continue
}
2023-07-14 01:57:20 +08:00
img , err := r . lookupImageInLocalStorage ( name , named . String ( ) , named , options )
2021-04-30 15:16:03 +08:00
if err != nil {
return nil , "" , err
}
if img != nil {
2023-07-22 02:30:14 +08:00
if requiredDigest != "" {
if ! img . hasDigest ( requiredDigest ) {
2023-07-14 01:57:20 +08:00
continue
}
named = reference . TrimNamed ( named )
canonical , err := reference . WithDigest ( named , requiredDigest )
if err != nil {
return nil , "" , fmt . Errorf ( "building canonical reference with digest %q and matched %q: %w" , requiredDigest . String ( ) , named . String ( ) , err )
}
return img , canonical . String ( ) , nil
}
return img , named . String ( ) , nil
2021-04-30 15:16:03 +08:00
}
}
2023-07-14 01:57:20 +08:00
return nil , "" , fmt . Errorf ( "%s: %w" , originalName , storage . ErrImageUnknown )
2021-04-30 15:16:03 +08:00
}
// ResolveName resolves the specified name. If the name resolves to a local
// image, the fully resolved name will be returned. Otherwise, the name will
// be properly normalized.
//
// Note that an empty string is returned as is.
func ( r * Runtime ) ResolveName ( name string ) ( string , error ) {
if name == "" {
return "" , nil
}
2021-07-02 20:26:20 +08:00
image , resolvedName , err := r . LookupImage ( name , nil )
2022-07-11 22:11:52 +08:00
if err != nil && ! errors . Is ( err , storage . ErrImageUnknown ) {
2021-04-30 15:16:03 +08:00
return "" , err
}
if image != nil && ! strings . HasPrefix ( image . ID ( ) , resolvedName ) {
return resolvedName , err
}
normalized , err := NormalizeName ( name )
if err != nil {
return "" , err
}
return normalized . String ( ) , nil
2021-04-11 01:44:51 +08:00
}
2021-12-02 03:08:25 +08:00
// IsExternalContainerFunc allows for checking whether the specified container
// is an external one. The definition of an external container can be set by
// callers.
type IsExternalContainerFunc func ( containerID string ) ( bool , error )
2021-04-11 01:44:51 +08:00
// ListImagesOptions allow for customizing listing images.
type ListImagesOptions struct {
// Filters to filter the listed images. Supported filters are
// * after,before,since=image
2021-12-02 03:08:25 +08:00
// * containers=true,false,external
2021-04-11 01:44:51 +08:00
// * dangling=true,false
// * intermediate=true,false (useful for pruning images)
// * id=id
// * label=key[=value]
// * readonly=true,false
// * reference=name[:tag] (wildcards allowed)
Filters [ ] string
2021-12-02 03:08:25 +08:00
// IsExternalContainerFunc allows for checking whether the specified
// container is an external one (when containers=external filter is
// used). The definition of an external container can be set by
// callers.
IsExternalContainerFunc IsExternalContainerFunc
2023-04-06 21:00:52 +08:00
// SetListData will populate the Image.ListData fields of returned images.
SetListData bool
2021-04-11 01:44:51 +08:00
}
2024-08-30 19:07:24 +08:00
// ListImagesByNames lists the images in the local container storage by specified names
// The name lookups use the LookupImage semantics.
func ( r * Runtime ) ListImagesByNames ( names [ ] string ) ( [ ] * Image , error ) {
images := [ ] * Image { }
for _ , name := range names {
image , _ , err := r . LookupImage ( name , nil )
2021-04-11 01:44:51 +08:00
if err != nil {
return nil , err
}
2024-08-30 19:07:24 +08:00
images = append ( images , image )
}
return images , nil
}
// ListImages lists the images in the local container storage and filter the images by ListImagesOptions
2025-06-30 19:40:22 +08:00
//
// podman images consumes the output of ListImages and produces one line for each tag in each Image.Names value,
// rather than one line for each Image with all Names, so if options.Filters contains one reference filter
// with a fully qualified image name without negation, it is considered a query so it makes more sense for
// the user to see only the corresponding names in the output, not all the names of the deduplicated
// image; therefore, we make the corresponding names available to the caller by overwriting the actual image names
// with the corresponding names when the reference filter matches and the reference is a fully qualified image name
// (i.e., contains a tag or digest, not just a bare repository name).
//
// This overwriting is done only in memory and is not written to storage in any way.
2024-08-30 19:07:24 +08:00
func ( r * Runtime ) ListImages ( ctx context . Context , options * ListImagesOptions ) ( [ ] * Image , error ) {
if options == nil {
options = & ListImagesOptions { }
2021-04-11 01:44:51 +08:00
}
2024-08-30 19:07:24 +08:00
filters , needsLayerTree , err := r . compileImageFilters ( ctx , options )
2023-04-06 21:00:52 +08:00
if err != nil {
return nil , err
}
2024-08-30 19:07:24 +08:00
if options . SetListData {
needsLayerTree = true
}
snapshot , err := r . store . MultiList (
storage . MultiListOptions {
Images : true ,
Layers : needsLayerTree ,
} )
if err != nil {
return nil , err
}
images := [ ] * Image { }
for i := range snapshot . Images {
images = append ( images , r . storageToImage ( & snapshot . Images [ i ] , nil ) )
2023-04-06 21:00:52 +08:00
}
// If explicitly requested by the user, pre-compute and cache the
// dangling and parent information of all filtered images. That will
// considerably speed things up for callers who need this information
// as the layer tree will computed once for all instead of once for
// each individual image (see containers/podman/issues/17828).
2024-08-30 19:07:24 +08:00
var tree * layerTree
if needsLayerTree {
2025-04-17 23:36:04 +08:00
tree , err = r . newLayerTreeFromData ( images , snapshot . Layers , true )
2024-08-30 19:07:24 +08:00
if err != nil {
return nil , err
}
}
filtered , err := r . filterImages ( ctx , images , filters , tree )
2023-04-06 21:00:52 +08:00
if err != nil {
return nil , err
}
2024-08-30 19:07:24 +08:00
if ! options . SetListData {
return filtered , nil
}
2023-04-06 21:00:52 +08:00
for i := range filtered {
isDangling , err := filtered [ i ] . isDangling ( ctx , tree )
if err != nil {
return nil , err
}
filtered [ i ] . ListData . IsDangling = & isDangling
parent , err := filtered [ i ] . parent ( ctx , tree )
if err != nil {
return nil , err
}
filtered [ i ] . ListData . Parent = parent
}
return filtered , nil
2021-04-11 01:44:51 +08:00
}
// RemoveImagesOptions allow for customizing image removal.
type RemoveImagesOptions struct {
2021-04-30 15:16:03 +08:00
// Force will remove all containers from the local storage that are
// using a removed image. Use RemoveContainerFunc for a custom logic.
// If set, all child images will be removed as well.
Force bool
2021-09-02 18:38:39 +08:00
// LookupManifest will expect all specified names to be manifest lists (no instance look up).
// This allows for removing manifest lists.
// By default, RemoveImages will attempt to resolve to a manifest instance matching
// the local platform (i.e., os, architecture, variant).
LookupManifest bool
2021-04-30 15:16:03 +08:00
// RemoveContainerFunc allows for a custom logic for removing
// containers using a specific image. By default, all containers in
// the local containers storage will be removed (if Force is set).
RemoveContainerFunc RemoveContainerFunc
2022-04-05 02:23:54 +08:00
// Ignore if a specified image does not exist and do not throw an error.
Ignore bool
2021-12-02 03:08:25 +08:00
// IsExternalContainerFunc allows for checking whether the specified
// container is an external one (when containers=external filter is
// used). The definition of an external container can be set by
// callers.
IsExternalContainerFunc IsExternalContainerFunc
// Remove external containers even when Force is false. Requires
// IsExternalContainerFunc to be specified.
ExternalContainers bool
2021-04-11 01:44:51 +08:00
// Filters to filter the removed images. Supported filters are
// * after,before,since=image
2021-12-02 03:08:25 +08:00
// * containers=true,false,external
2021-04-11 01:44:51 +08:00
// * dangling=true,false
// * intermediate=true,false (useful for pruning images)
// * id=id
// * label=key[=value]
// * readonly=true,false
// * reference=name[:tag] (wildcards allowed)
Filters [ ] string
2021-04-30 15:16:03 +08:00
// The RemoveImagesReport will include the size of the removed image.
// This information may be useful when pruning images to figure out how
// much space was freed. However, computing the size of an image is
// comparatively expensive, so it is made optional.
WithSize bool
2022-09-20 15:14:36 +08:00
// NoPrune will not remove dangling images
NoPrune bool
2021-04-11 01:44:51 +08:00
}
2021-05-26 16:49:22 +08:00
// RemoveImages removes images specified by names. If no names are specified,
// remove images as specified via the options' filters. All images are
// expected to exist in the local containers storage.
2021-04-11 01:44:51 +08:00
//
// If an image has more names than one name, the image will be untagged with
// the specified name. RemoveImages returns a slice of untagged and removed
// images.
2021-04-30 15:16:03 +08:00
//
// Note that most errors are non-fatal and collected into `rmErrors` return
// value.
func ( r * Runtime ) RemoveImages ( ctx context . Context , names [ ] string , options * RemoveImagesOptions ) ( reports [ ] * RemoveImageReport , rmErrors [ ] error ) {
2021-04-11 01:44:51 +08:00
if options == nil {
options = & RemoveImagesOptions { }
}
2021-12-02 03:08:25 +08:00
if options . ExternalContainers && options . IsExternalContainerFunc == nil {
2024-03-07 00:12:59 +08:00
return nil , [ ] error { errors . New ( "libimage error: cannot remove external containers without callback" ) }
2021-12-02 03:08:25 +08:00
}
2021-04-30 15:16:03 +08:00
// The logic here may require some explanation. Image removal is
// surprisingly complex since it is recursive (intermediate parents are
// removed) and since multiple items in `names` may resolve to the
// *same* image. On top, the data in the containers storage is shared,
// so we need to be careful and the code must be robust. That is why
// users can only remove images via this function; the logic may be
// complex but the execution path is clear.
// Bundle an image with a possible empty slice of names to untag. That
// allows for a decent untagging logic and to bundle multiple
// references to the same *Image (and circumvent consistency issues).
2021-04-11 01:44:51 +08:00
type deleteMe struct {
2021-04-30 15:16:03 +08:00
image * Image
referencedBy [ ] string
2021-04-11 01:44:51 +08:00
}
2021-04-30 15:16:03 +08:00
appendError := func ( err error ) {
rmErrors = append ( rmErrors , err )
}
deleteMap := make ( map [ string ] * deleteMe ) // ID -> deleteMe
2021-07-19 19:49:02 +08:00
toDelete := [ ] string { }
2021-04-30 15:16:03 +08:00
// Look up images in the local containers storage and fill out
2021-07-19 19:49:02 +08:00
// toDelete and the deleteMap.
2021-04-11 01:44:51 +08:00
switch {
case len ( names ) > 0 :
2021-09-02 18:38:39 +08:00
// prepare lookupOptions
var lookupOptions * LookupImageOptions
if options . LookupManifest {
// LookupManifest configured as true make sure we only remove manifests and no referenced images.
lookupOptions = & LookupImageOptions { lookupManifest : true }
} else {
lookupOptions = & LookupImageOptions { returnManifestIfNoInstance : true }
}
2021-05-26 16:49:22 +08:00
// Look up the images one-by-one. That allows for removing
// images that have been looked up successfully while reporting
// lookup errors at the end.
2021-04-11 01:44:51 +08:00
for _ , name := range names {
2021-09-02 18:38:39 +08:00
img , resolvedName , err := r . LookupImage ( name , lookupOptions )
2021-04-11 01:44:51 +08:00
if err != nil {
2022-04-05 02:23:54 +08:00
if options . Ignore && errors . Is ( err , storage . ErrImageUnknown ) {
continue
}
2021-04-30 15:16:03 +08:00
appendError ( err )
continue
2021-04-11 01:44:51 +08:00
}
2021-04-30 15:16:03 +08:00
dm , exists := deleteMap [ img . ID ( ) ]
if ! exists {
2021-07-19 19:49:02 +08:00
toDelete = append ( toDelete , img . ID ( ) )
2021-04-30 15:16:03 +08:00
dm = & deleteMe { image : img }
deleteMap [ img . ID ( ) ] = dm
2021-04-11 01:44:51 +08:00
}
2021-04-30 15:16:03 +08:00
dm . referencedBy = append ( dm . referencedBy , resolvedName )
2021-04-11 01:44:51 +08:00
}
2021-05-18 16:02:48 +08:00
default :
2021-12-02 03:08:25 +08:00
options := & ListImagesOptions {
IsExternalContainerFunc : options . IsExternalContainerFunc ,
Filters : options . Filters ,
}
2024-08-30 19:07:24 +08:00
filteredImages , err := r . ListImages ( ctx , options )
2021-04-11 01:44:51 +08:00
if err != nil {
2021-04-30 15:16:03 +08:00
appendError ( err )
return nil , rmErrors
2021-04-11 01:44:51 +08:00
}
for _ , img := range filteredImages {
2021-07-19 19:49:02 +08:00
toDelete = append ( toDelete , img . ID ( ) )
2021-04-30 15:16:03 +08:00
deleteMap [ img . ID ( ) ] = & deleteMe { image : img }
2021-04-11 01:44:51 +08:00
}
}
2021-07-19 19:49:02 +08:00
// Return early if there's no image to delete.
if len ( deleteMap ) == 0 {
return nil , rmErrors
}
2021-04-30 15:16:03 +08:00
// Now remove the images in the given order.
rmMap := make ( map [ string ] * RemoveImageReport )
2021-07-19 19:49:02 +08:00
orderedIDs := [ ] string { }
visitedIDs := make ( map [ string ] bool )
for _ , id := range toDelete {
2021-04-30 15:16:03 +08:00
del , exists := deleteMap [ id ]
if ! exists {
2022-07-11 22:11:52 +08:00
appendError ( fmt . Errorf ( "internal error: ID %s not in found in image-deletion map" , id ) )
2021-04-30 15:16:03 +08:00
continue
2021-04-11 01:44:51 +08:00
}
2021-04-30 15:16:03 +08:00
if len ( del . referencedBy ) == 0 {
del . referencedBy = [ ] string { "" }
2021-04-11 01:44:51 +08:00
}
2021-04-30 15:16:03 +08:00
for _ , ref := range del . referencedBy {
2021-07-19 19:49:02 +08:00
processedIDs , err := del . image . remove ( ctx , rmMap , ref , options )
if err != nil {
2021-04-30 15:16:03 +08:00
appendError ( err )
2021-07-19 19:49:02 +08:00
}
// NOTE: make sure to add given ID only once to orderedIDs.
for _ , id := range processedIDs {
if visited := visitedIDs [ id ] ; visited {
continue
}
orderedIDs = append ( orderedIDs , id )
visitedIDs [ id ] = true
2021-04-11 01:44:51 +08:00
}
}
2021-04-30 15:16:03 +08:00
}
2021-04-11 01:44:51 +08:00
2021-04-30 15:16:03 +08:00
// Finally, we can assemble the reports slice.
for _ , id := range orderedIDs {
report , exists := rmMap [ id ]
if exists {
reports = append ( reports , report )
2021-04-11 01:44:51 +08:00
}
}
2021-04-30 15:16:03 +08:00
return reports , rmErrors
2021-04-11 01:44:51 +08:00
}