diff options
Diffstat (limited to 'libpod/image')
-rw-r--r-- | libpod/image/docker_registry_options.go | 12 | ||||
-rw-r--r-- | libpod/image/image.go | 284 | ||||
-rw-r--r-- | libpod/image/image_test.go | 13 | ||||
-rw-r--r-- | libpod/image/parts.go | 2 | ||||
-rw-r--r-- | libpod/image/pull.go | 30 | ||||
-rw-r--r-- | libpod/image/pull_test.go | 6 | ||||
-rw-r--r-- | libpod/image/search.go | 4 | ||||
-rw-r--r-- | libpod/image/utils.go | 22 |
8 files changed, 238 insertions, 135 deletions
diff --git a/libpod/image/docker_registry_options.go b/libpod/image/docker_registry_options.go index d205fe4ac..62a4af465 100644 --- a/libpod/image/docker_registry_options.go +++ b/libpod/image/docker_registry_options.go @@ -3,8 +3,8 @@ package image import ( "fmt" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/types" podmanVersion "github.com/containers/libpod/version" ) @@ -26,6 +26,10 @@ type DockerRegistryOptions struct { // certificates and allows connecting to registries without encryption // - or forces it on even if registries.conf has the registry configured as insecure. DockerInsecureSkipTLSVerify types.OptionalBool + // If not "", overrides the use of platform.GOOS when choosing an image or verifying OS match. + OSChoice string + // If not "", overrides the use of platform.GOARCH when choosing an image or verifying architecture match. + ArchitectureChoice string } // GetSystemContext constructs a new system context from a parent context. the values in the DockerRegistryOptions, and other parameters. @@ -35,12 +39,16 @@ func (o DockerRegistryOptions) GetSystemContext(parent *types.SystemContext, add DockerCertPath: o.DockerCertPath, DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify, DockerArchiveAdditionalTags: additionalDockerArchiveTags, + OSChoice: o.OSChoice, + ArchitectureChoice: o.ArchitectureChoice, } if parent != nil { sc.SignaturePolicyPath = parent.SignaturePolicyPath sc.AuthFilePath = parent.AuthFilePath sc.DirForceCompress = parent.DirForceCompress sc.DockerRegistryUserAgent = parent.DockerRegistryUserAgent + sc.OSChoice = parent.OSChoice + sc.ArchitectureChoice = parent.ArchitectureChoice } return sc } diff --git a/libpod/image/image.go b/libpod/image/image.go index 9adefb5c5..c912ac2ca 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -3,26 +3,29 @@ package image import ( "context" "encoding/json" + stderrors "errors" "fmt" "io" "io/ioutil" "os" "path/filepath" + "sort" "strings" "syscall" "time" - cp "github.com/containers/image/v4/copy" - "github.com/containers/image/v4/directory" - dockerarchive "github.com/containers/image/v4/docker/archive" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - ociarchive "github.com/containers/image/v4/oci/archive" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/tarball" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/directory" + dockerarchive "github.com/containers/image/v5/docker/archive" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/manifest" + ociarchive "github.com/containers/image/v5/oci/archive" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/tarball" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod/driver" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/inspect" @@ -42,8 +45,8 @@ import ( type Image struct { // Adding these two structs for now but will cull when we near // completion of this library. - imgRef types.Image - storeRef types.ImageReference + imgRef types.Image + imgSrcRef types.ImageSource inspect.ImageData inspect.ImageResult inspectInfo *types.ImageInspectInfo @@ -72,7 +75,10 @@ type InfoImage struct { } // ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store -var ErrRepoTagNotFound = errors.New("unable to match user input to any specific repotag") +var ErrRepoTagNotFound = stderrors.New("unable to match user input to any specific repotag") + +// ErrImageIsBareList is the error returned when the image is just a list or index +var ErrImageIsBareList = stderrors.New("image contains a manifest list or image index, but no runnable image") // NewImageRuntimeFromStore creates an ImageRuntime based on a provided store func NewImageRuntimeFromStore(store storage.Store) *Runtime { @@ -294,9 +300,23 @@ func (i *Image) Digest() digest.Digest { return i.image.Digest } +// Digests returns the image's digests +func (i *Image) Digests() []digest.Digest { + return i.image.Digests +} + +// GetManifest returns the image's manifest as a byte array +// and manifest type as a string. +func (i *Image) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { + imgSrcRef, err := i.toImageSourceRef(ctx) + if err != nil { + return nil, "", err + } + return imgSrcRef.GetManifest(ctx, instanceDigest) +} + // Manifest returns the image's manifest as a byte array -// and manifest type as a string. The manifest type is -// MediaTypeImageManifest from ociv1. +// and manifest type as a string. func (i *Image) Manifest(ctx context.Context) ([]byte, string, error) { imgRef, err := i.toImageRef(ctx) if err != nil { @@ -305,29 +325,54 @@ func (i *Image) Manifest(ctx context.Context) ([]byte, string, error) { return imgRef.Manifest(ctx) } -// Names returns a string array of names associated with the image +// Names returns a string array of names associated with the image, which may be a mixture of tags and digests func (i *Image) Names() []string { return i.image.Names } -// RepoDigests returns a string array of repodigests associated with the image -func (i *Image) RepoDigests() ([]string, error) { - var repoDigests []string - imageDigest := i.Digest() - +// RepoTags returns a string array of repotags associated with the image +func (i *Image) RepoTags() ([]string, error) { + var repoTags []string for _, name := range i.Names() { 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 tagged, isTagged := named.(reference.NamedTagged); isTagged { + repoTags = append(repoTags, tagged.String()) } + } + return repoTags, nil +} + +// RepoDigests returns a string array of repodigests associated with the image +func (i *Image) RepoDigests() ([]string, error) { + var repoDigests []string + added := make(map[string]struct{}) - repoDigests = append(repoDigests, canonical.String()) + 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 } @@ -379,26 +424,6 @@ func (i *Image) Remove(ctx context.Context, force bool) error { return nil } -// TODO: Rework this method to not require an assembly of the fq name with transport -/* -// GetManifest tries to GET an images manifest, returns nil on success and err on failure -func (i *Image) GetManifest() error { - pullRef, err := alltransports.ParseImageName(i.assembleFqNameTransport()) - if err != nil { - return errors.Errorf("unable to parse '%s'", i.Names()[0]) - } - imageSource, err := pullRef.NewImageSource(nil) - if err != nil { - return errors.Wrapf(err, "unable to create new image source") - } - _, _, err = imageSource.GetManifest(nil) - if err == nil { - return nil - } - return err -} -*/ - // getImage retrieves an image matching the given name or hash from system // storage // If no matching image can be found, an error is returned @@ -612,76 +637,98 @@ func (i *Image) PushImageToReference(ctx context.Context, dest types.ImageRefere // MatchesID returns a bool based on if the input id // matches the image's id +// TODO: This isn't used anywhere, so remove it func (i *Image) MatchesID(id string) bool { return strings.HasPrefix(i.ID(), id) } -// toStorageReference returns a *storageReference from an Image -func (i *Image) toStorageReference() (types.ImageReference, error) { - var lookupName string - if i.storeRef == nil { - if i.image != nil { - lookupName = i.ID() - } else { - lookupName = i.InputName - } - storeRef, err := is.Transport.ParseStoreReference(i.imageruntime.store, lookupName) - if err != nil { - return nil, err - } - i.storeRef = storeRef - } - return i.storeRef, nil -} - // ToImageRef returns an image reference type from an image // TODO: Hopefully we can remove this exported function for mheon func (i *Image) ToImageRef(ctx context.Context) (types.Image, error) { return i.toImageRef(ctx) } -// toImageRef returns an Image Reference type from an image -func (i *Image) toImageRef(ctx context.Context) (types.Image, error) { +// toImageSourceRef returns an ImageSource Reference type from an image +func (i *Image) toImageSourceRef(ctx context.Context) (types.ImageSource, error) { if i == nil { - return nil, errors.Errorf("cannot convert nil image to image reference") + return nil, errors.Errorf("cannot convert nil image to image source reference") } - if i.imgRef == nil { + if i.imgSrcRef == nil { ref, err := is.Transport.ParseStoreReference(i.imageruntime.store, "@"+i.ID()) if err != nil { return nil, errors.Wrapf(err, "error parsing reference to image %q", i.ID()) } - imgRef, err := ref.NewImage(ctx, nil) + imgSrcRef, err := ref.NewImageSource(ctx, nil) if err != nil { - return nil, errors.Wrapf(err, "error reading image %q", i.ID()) + return nil, errors.Wrapf(err, "error reading image %q as image source", i.ID()) } - i.imgRef = imgRef + i.imgSrcRef = imgSrcRef } - return i.imgRef, nil -} - -// sizer knows its size. -type sizer interface { - Size() (int64, error) + return i.imgSrcRef, nil } //Size returns the size of the image func (i *Image) Size(ctx context.Context) (*uint64, error) { - storeRef, err := is.Transport.ParseStoreReference(i.imageruntime.store, i.ID()) - if err != nil { - return nil, err + if i.image == nil { + localImage, err := i.getLocalImage() + if err != nil { + return nil, err + } + i.image = localImage } - systemContext := &types.SystemContext{} - img, err := storeRef.NewImageSource(ctx, systemContext) + if sum, err := i.imageruntime.store.ImageSize(i.ID()); err == nil && sum >= 0 { + usum := uint64(sum) + return &usum, nil + } + return nil, errors.Errorf("unable to determine size") +} + +// toImageRef returns an Image Reference type from an image +func (i *Image) toImageRef(ctx context.Context) (types.Image, error) { + if i == nil { + return nil, errors.Errorf("cannot convert nil image to image reference") + } + imgSrcRef, err := i.toImageSourceRef(ctx) if err != nil { return nil, err } - if s, ok := img.(sizer); ok { - if sum, err := s.Size(); err == nil { - usum := uint64(sum) - return &usum, nil + if i.imgRef == nil { + systemContext := &types.SystemContext{} + unparsedDefaultInstance := image.UnparsedInstance(imgSrcRef, nil) + imgRef, err := image.FromUnparsedImage(ctx, systemContext, unparsedDefaultInstance) + if err != nil { + // check for a "tried-to-treat-a-bare-list-like-a-runnable-image" problem, else + // return info about the not-a-bare-list runnable image part of this storage.Image + if manifestBytes, manifestType, err2 := imgSrcRef.GetManifest(ctx, nil); err2 == nil { + if manifest.MIMETypeIsMultiImage(manifestType) { + if list, err3 := manifest.ListFromBlob(manifestBytes, manifestType); err3 == nil { + switch manifestType { + case ociv1.MediaTypeImageIndex: + err = errors.Wrapf(ErrImageIsBareList, "%q is an image index", i.InputName) + case manifest.DockerV2ListMediaType: + err = errors.Wrapf(ErrImageIsBareList, "%q is a manifest list", i.InputName) + default: + err = errors.Wrapf(ErrImageIsBareList, "%q", i.InputName) + } + for _, instanceDigest := range list.Instances() { + instance := instanceDigest + unparsedInstance := image.UnparsedInstance(imgSrcRef, &instance) + if imgRef2, err4 := image.FromUnparsedImage(ctx, systemContext, unparsedInstance); err4 == nil { + imgRef = imgRef2 + err = nil + break + } + } + } + } + } + if err != nil { + return nil, errors.Wrapf(err, "error reading image %q as image", i.ID()) + } } + i.imgRef = imgRef } - return nil, errors.Errorf("unable to determine size") + return i.imgRef, nil } // DriverData gets the driver data from the store on a layer @@ -708,6 +755,9 @@ type History struct { func (i *Image) History(ctx context.Context) ([]*History, error) { img, err := i.toImageRef(ctx) if err != nil { + if errors.Cause(err) == ErrImageIsBareList { + return nil, nil + } return nil, err } oci, err := img.OCIConfig(ctx) @@ -853,7 +903,10 @@ func (i *Image) GetLabel(ctx context.Context, label string) (string, error) { func (i *Image) Annotations(ctx context.Context) (map[string]string, error) { imageManifest, manifestType, err := i.Manifest(ctx) if err != nil { - return nil, err + imageManifest, manifestType, err = i.GetManifest(ctx, nil) + if err != nil { + return nil, err + } } annotations := make(map[string]string) switch manifestType { @@ -868,24 +921,19 @@ func (i *Image) Annotations(ctx context.Context) (map[string]string, error) { return annotations, nil } -// ociv1Image converts and image to an imgref and then an -// ociv1 image type +// ociv1Image converts an image to an imgref and then returns its config blob +// converted to an ociv1 image type func (i *Image) ociv1Image(ctx context.Context) (*ociv1.Image, error) { imgRef, err := i.toImageRef(ctx) if err != nil { return nil, err } - return imgRef.OCIConfig(ctx) } func (i *Image) imageInspectInfo(ctx context.Context) (*types.ImageInspectInfo, error) { if i.inspectInfo == nil { - sr, err := i.toStorageReference() - if err != nil { - return nil, err - } - ic, err := sr.NewImage(ctx, &types.SystemContext{}) + ic, err := i.toImageRef(ctx) if err != nil { return nil, err } @@ -906,18 +954,23 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { ociv1Img, err := i.ociv1Image(ctx) if err != nil { - return nil, err + ociv1Img = &ociv1.Image{} } info, err := i.imageInspectInfo(ctx) if err != nil { - return nil, err + info = &types.ImageInspectInfo{} } annotations, err := i.Annotations(ctx) if err != nil { return nil, err } - size, err := i.Size(ctx) + size := int64(-1) + if usize, err := i.Size(ctx); err == nil { + size = int64(*usize) + } + + repoTags, err := i.RepoTags() if err != nil { return nil, err } @@ -932,7 +985,7 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { return nil, err } - _, manifestType, err := i.Manifest(ctx) + _, manifestType, err := i.GetManifest(ctx, nil) if err != nil { return nil, errors.Wrapf(err, "unable to determine manifest type") } @@ -943,7 +996,7 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { data := &inspect.ImageData{ ID: i.ID(), - RepoTags: i.Names(), + RepoTags: repoTags, RepoDigests: repoDigests, Comment: comment, Created: ociv1Img.Created, @@ -952,8 +1005,8 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { Os: ociv1Img.OS, Config: &ociv1Img.Config, Version: info.DockerVersion, - Size: int64(*size), - VirtualSize: int64(*size), + Size: size, + VirtualSize: size, Annotations: annotations, Digest: i.Digest(), Labels: info.Labels, @@ -1082,6 +1135,9 @@ func splitString(input string) string { func (i *Image) IsParent(ctx context.Context) (bool, error) { children, err := i.getChildren(ctx, 1) if err != nil { + if errors.Cause(err) == ErrImageIsBareList { + return false, nil + } return false, err } return len(children) > 0, nil @@ -1165,6 +1221,9 @@ func (i *Image) GetParent(ctx context.Context) (*Image, error) { // fetch the configuration for the child image child, err := i.ociv1Image(ctx) if err != nil { + if errors.Cause(err) == ErrImageIsBareList { + return nil, nil + } return nil, err } for _, img := range images { @@ -1205,12 +1264,24 @@ func (i *Image) GetParent(ctx context.Context) (*Image, error) { // GetChildren returns a list of the imageIDs that depend on the image func (i *Image) GetChildren(ctx context.Context) ([]string, error) { - return i.getChildren(ctx, 0) + children, err := i.getChildren(ctx, 0) + if err != nil { + if errors.Cause(err) == ErrImageIsBareList { + return nil, nil + } + return nil, err + } + return children, nil } // getChildren returns a list of at most "max" imageIDs that depend on the image func (i *Image) getChildren(ctx context.Context, max int) ([]string, error) { var children []string + + if _, err := i.toImageRef(ctx); err != nil { + return nil, nil + } + images, err := i.imageruntime.GetImages() if err != nil { return nil, err @@ -1301,6 +1372,9 @@ func (i *Image) Comment(ctx context.Context, manifestType string) (string, error } ociv1Img, err := i.ociv1Image(ctx) if err != nil { + if errors.Cause(err) == ErrImageIsBareList { + return "", nil + } return "", err } if len(ociv1Img.History) > 0 { diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index ef39d09c3..5aff7d860 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -247,6 +247,19 @@ func TestImage_RepoDigests(t *testing.T) { } assert.Equal(t, test.expected, actual) + + image = &Image{ + image: &storage.Image{ + Names: test.names, + Digests: []digest.Digest{dgst}, + }, + } + actual, err = image.RepoDigests() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, test.expected, actual) }) } } diff --git a/libpod/image/parts.go b/libpod/image/parts.go index 69bc44cdd..d4677f935 100644 --- a/libpod/image/parts.go +++ b/libpod/image/parts.go @@ -3,7 +3,7 @@ package image import ( "strings" - "github.com/containers/image/v4/docker/reference" + "github.com/containers/image/v5/docker/reference" "github.com/pkg/errors" ) diff --git a/libpod/image/pull.go b/libpod/image/pull.go index 36950b6f3..7f5dc33b9 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -7,17 +7,17 @@ import ( "path/filepath" "strings" - cp "github.com/containers/image/v4/copy" - "github.com/containers/image/v4/directory" - "github.com/containers/image/v4/docker" - dockerarchive "github.com/containers/image/v4/docker/archive" - "github.com/containers/image/v4/docker/tarfile" - ociarchive "github.com/containers/image/v4/oci/archive" - oci "github.com/containers/image/v4/oci/layout" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/directory" + "github.com/containers/image/v5/docker" + dockerarchive "github.com/containers/image/v5/docker/archive" + "github.com/containers/image/v5/docker/tarfile" + ociarchive "github.com/containers/image/v5/oci/archive" + oci "github.com/containers/image/v5/oci/layout" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/registries" "github.com/hashicorp/go-multierror" @@ -223,6 +223,10 @@ func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName s var goal *pullGoal sc := GetSystemContext(signaturePolicyPath, authfile, false) + if dockerOptions != nil { + sc.OSChoice = dockerOptions.OSChoice + sc.ArchitectureChoice = dockerOptions.ArchitectureChoice + } sc.BlobInfoCacheDir = filepath.Join(ir.store.GraphRoot(), "cache") srcRef, err := alltransports.ParseImageName(inputName) if err != nil { @@ -246,6 +250,10 @@ func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.Imag defer span.Finish() sc := GetSystemContext(signaturePolicyPath, authfile, false) + if dockerOptions != nil { + sc.OSChoice = dockerOptions.OSChoice + sc.ArchitectureChoice = dockerOptions.ArchitectureChoice + } goal, err := ir.pullGoalFromImageReference(ctx, srcRef, transports.ImageName(srcRef), sc) if err != nil { return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef)) diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go index 131b8b1f6..0046cdfef 100644 --- a/libpod/image/pull_test.go +++ b/libpod/image/pull_test.go @@ -9,9 +9,9 @@ import ( "strings" "testing" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/stretchr/testify/assert" diff --git a/libpod/image/search.go b/libpod/image/search.go index 0313c2d6e..fd29dac45 100644 --- a/libpod/image/search.go +++ b/libpod/image/search.go @@ -6,8 +6,8 @@ import ( "strings" "sync" - "github.com/containers/image/v4/docker" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/types" sysreg "github.com/containers/libpod/pkg/registries" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/libpod/image/utils.go b/libpod/image/utils.go index e4ff1cfc4..b7ea63c66 100644 --- a/libpod/image/utils.go +++ b/libpod/image/utils.go @@ -7,10 +7,10 @@ import ( "regexp" "strings" - cp "github.com/containers/image/v4/copy" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/signature" - "github.com/containers/image/v4/types" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/pkg/errors" ) @@ -87,18 +87,18 @@ func hasTransport(image string) bool { } // ReposToMap parses the specified repotags and returns a map with repositories -// as keys and the corresponding arrays of tags as values. -func ReposToMap(repotags []string) (map[string][]string, error) { - // map format is repo -> tag +// as keys and the corresponding arrays of tags or digests-as-strings as values. +func ReposToMap(names []string) (map[string][]string, error) { + // map format is repo -> []tag-or-digest repos := make(map[string][]string) - for _, repo := range repotags { + for _, name := range names { var repository, tag string - if len(repo) > 0 { - named, err := reference.ParseNormalizedNamed(repo) - repository = named.Name() + if len(name) > 0 { + named, err := reference.ParseNormalizedNamed(name) if err != nil { return nil, err } + repository = named.Name() if ref, ok := named.(reference.NamedTagged); ok { tag = ref.Tag() } else if ref, ok := named.(reference.Canonical); ok { |