diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2019-10-29 20:58:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-29 20:58:51 +0100 |
commit | e7540d0406c49b22de245246d16ebc6e1778df37 (patch) | |
tree | d25a5f8d259d19f2c0017d9987e93d065e577f89 /libpod/image/image.go | |
parent | 5918f3a5f1d11862fbaaca94ff25f1d9cc1309e2 (diff) | |
parent | 66c126d6dee178f96f8a120f13372802d46ea9b5 (diff) | |
download | podman-e7540d0406c49b22de245246d16ebc6e1778df37.tar.gz podman-e7540d0406c49b22de245246d16ebc6e1778df37.tar.bz2 podman-e7540d0406c49b22de245246d16ebc6e1778df37.zip |
Merge pull request #4310 from nalind/manifest-lists
Move to containers/image v5, support manifest lists
Diffstat (limited to 'libpod/image/image.go')
-rw-r--r-- | libpod/image/image.go | 284 |
1 files changed, 179 insertions, 105 deletions
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 { |