diff options
Diffstat (limited to 'libpod/image')
-rw-r--r-- | libpod/image/config.go | 6 | ||||
-rw-r--r-- | libpod/image/filters.go | 13 | ||||
-rw-r--r-- | libpod/image/image.go | 154 | ||||
-rw-r--r-- | libpod/image/image_test.go | 2 | ||||
-rw-r--r-- | libpod/image/manifests.go | 154 | ||||
-rw-r--r-- | libpod/image/parts.go | 2 | ||||
-rw-r--r-- | libpod/image/pull.go | 1 | ||||
-rw-r--r-- | libpod/image/tree.go | 138 |
8 files changed, 390 insertions, 80 deletions
diff --git a/libpod/image/config.go b/libpod/image/config.go index bb84175a3..efd83d343 100644 --- a/libpod/image/config.go +++ b/libpod/image/config.go @@ -1,5 +1,11 @@ package image +const ( + // LatestTag describes the tag used to refer to the latest version + // of an image + LatestTag = "latest" +) + // ImageDeleteResponse is the response for removing an image from storage and containers // what was untagged vs actually removed type ImageDeleteResponse struct { //nolint diff --git a/libpod/image/filters.go b/libpod/image/filters.go index d545f1bfc..8ca3526a0 100644 --- a/libpod/image/filters.go +++ b/libpod/image/filters.go @@ -3,13 +3,13 @@ package image import ( "context" "fmt" - "github.com/pkg/errors" "path/filepath" "strconv" "strings" "time" "github.com/containers/libpod/pkg/inspect" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -102,6 +102,13 @@ func ReferenceFilter(ctx context.Context, referenceFilter string) ResultFilter { } } +// IdFilter allows you to filter by image Id +func IdFilter(idFilter string) ResultFilter { + return func(i *Image) bool { + return i.ID() == idFilter + } +} + // OutputImageFilter allows you to filter by an a specific image name func OutputImageFilter(userImage *Image) ResultFilter { return func(i *Image) bool { @@ -141,7 +148,7 @@ func (ir *Runtime) createFilterFuncs(filters []string, img *Image) ([]ResultFilt return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, CreatedBeforeFilter(before.Created())) - case "after": + case "since", "after": after, err := ir.NewFromLocal(splitFilter[1]) if err != nil { return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) @@ -165,6 +172,8 @@ func (ir *Runtime) createFilterFuncs(filters []string, img *Image) ([]ResultFilt case "reference": referenceFilter := strings.Join(splitFilter[1:], "=") filterFuncs = append(filterFuncs, ReferenceFilter(ctx, referenceFilter)) + case "id": + filterFuncs = append(filterFuncs, IdFilter(splitFilter[1])) default: return nil, errors.Errorf("invalid filter %s ", splitFilter[0]) } diff --git a/libpod/image/image.go b/libpod/image/image.go index 6ea49e2a9..5f914ed79 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -99,10 +99,7 @@ func NewImageRuntimeFromOptions(options storage.StoreOptions) (*Runtime, error) if err != nil { return nil, err } - - return &Runtime{ - store: store, - }, nil + return NewImageRuntimeFromStore(store), nil } func setStore(options storage.StoreOptions) (storage.Store, error) { @@ -114,30 +111,29 @@ func setStore(options storage.StoreOptions) (storage.Store, error) { return store, nil } -// newFromStorage creates a new image object from a storage.Image -func (ir *Runtime) newFromStorage(img *storage.Image) *Image { - image := Image{ - InputName: img.ID, +// newImage creates a new image object given an "input name" and a storage.Image +func (ir *Runtime) newImage(inputName string, img *storage.Image) *Image { + return &Image{ + InputName: inputName, imageruntime: ir, image: img, } - return &image +} + +// newFromStorage creates a new image object from a storage.Image. Its "input name" will be its ID. +func (ir *Runtime) newFromStorage(img *storage.Image) *Image { + return ir.newImage(img.ID, img) } // NewFromLocal creates a new image object that is intended // to only deal with local images already in the store (or // its aliases) func (ir *Runtime) NewFromLocal(name string) (*Image, error) { - image := Image{ - InputName: name, - imageruntime: ir, - } - localImage, err := image.getLocalImage() + updatedInputName, localImage, err := ir.getLocalImage(name) if err != nil { return nil, err } - image.image = localImage - return &image, nil + return ir.newImage(updatedInputName, localImage), nil } // New creates a new image object where the image could be local @@ -148,15 +144,10 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile defer span.Finish() // We don't know if the image is local or not ... check local first - newImage := Image{ - InputName: name, - imageruntime: ir, - } if pullType != util.PullImageAlways { - localImage, err := newImage.getLocalImage() + newImage, err := ir.NewFromLocal(name) if err == nil { - newImage.image = localImage - return &newImage, nil + return newImage, nil } else if pullType == util.PullImageNever { return nil, err } @@ -171,13 +162,11 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile return nil, errors.Wrapf(err, "unable to pull %s", name) } - newImage.InputName = imageName[0] - img, err := newImage.getLocalImage() + newImage, err := ir.NewFromLocal(imageName[0]) if err != nil { return nil, errors.Wrapf(err, "error retrieving local image after pulling %s", name) } - newImage.image = img - return &newImage, nil + return newImage, nil } // LoadFromArchiveReference creates a new image object for images pulled from a tar archive and the like (podman load) @@ -194,16 +183,11 @@ func (ir *Runtime) LoadFromArchiveReference(ctx context.Context, srcRef types.Im } for _, name := range imageNames { - newImage := Image{ - InputName: name, - imageruntime: ir, - } - img, err := newImage.getLocalImage() + newImage, err := ir.NewFromLocal(name) if err != nil { return nil, errors.Wrapf(err, "error retrieving local image after pulling %s", name) } - newImage.image = img - newImages = append(newImages, &newImage) + newImages = append(newImages, newImage) } ir.newImageEvent(events.LoadFromArchive, "") return newImages, nil @@ -234,7 +218,7 @@ func (i *Image) reloadImage() error { if err != nil { return errors.Wrapf(err, "unable to reload image") } - i.image = newImage.image + i.image = newImage return nil } @@ -247,60 +231,60 @@ func stripSha256(name string) string { } // getLocalImage resolves an unknown input describing an image and -// returns a storage.Image or an error. It is used by NewFromLocal. -func (i *Image) getLocalImage() (*storage.Image, error) { - imageError := fmt.Sprintf("unable to find '%s' in local storage", i.InputName) - if i.InputName == "" { - return nil, errors.Errorf("input name is blank") +// returns an updated input name, and a storage.Image, or an error. It is used by NewFromLocal. +func (ir *Runtime) getLocalImage(inputName string) (string, *storage.Image, error) { + imageError := fmt.Sprintf("unable to find '%s' in local storage", inputName) + if inputName == "" { + return "", nil, errors.Errorf("input name is blank") } // Check if the input name has a transport and if so strip it - dest, err := alltransports.ParseImageName(i.InputName) + dest, err := alltransports.ParseImageName(inputName) if err == nil && dest.DockerReference() != nil { - i.InputName = dest.DockerReference().String() + inputName = dest.DockerReference().String() } - img, err := i.imageruntime.getImage(stripSha256(i.InputName)) + img, err := ir.getImage(stripSha256(inputName)) if err == nil { - return img.image, err + return inputName, img, err } // container-storage wasn't able to find it in its current form // check if the input name has a tag, and if not, run it through // again - decomposedImage, err := decompose(i.InputName) + decomposedImage, err := decompose(inputName) if err != nil { - return nil, err + return "", nil, err } // The image has a registry name in it and we made sure we looked for it locally // with a tag. It cannot be local. if decomposedImage.hasRegistry { - return nil, errors.Wrapf(ErrNoSuchImage, imageError) + return "", nil, errors.Wrapf(ErrNoSuchImage, imageError) } // if the image is saved with the repository localhost, searching with localhost prepended is necessary // We don't need to strip the sha because we have already determined it is not an ID ref, err := decomposedImage.referenceWithRegistry(DefaultLocalRegistry) if err != nil { - return nil, err + return "", nil, err } - img, err = i.imageruntime.getImage(ref.String()) + img, err = ir.getImage(ref.String()) if err == nil { - return img.image, err + return inputName, img, err } // grab all the local images - images, err := i.imageruntime.GetImages() + images, err := ir.GetImages() if err != nil { - return nil, err + return "", nil, err } // check the repotags of all images for a match repoImage, err := findImageInRepotags(decomposedImage, images) if err == nil { - return repoImage, nil + return inputName, repoImage, nil } - return nil, errors.Wrapf(ErrNoSuchImage, err.Error()) + return "", nil, errors.Wrapf(ErrNoSuchImage, err.Error()) } // ID returns the image ID as a string @@ -460,7 +444,7 @@ func (i *Image) Remove(ctx context.Context, force bool) error { // getImage retrieves an image matching the given name or hash from system // storage // If no matching image can be found, an error is returned -func (ir *Runtime) getImage(image string) (*Image, error) { +func (ir *Runtime) getImage(image string) (*storage.Image, error) { var img *storage.Image ref, err := is.Transport.ParseStoreReference(ir.store, image) if err == nil { @@ -476,8 +460,7 @@ func (ir *Runtime) getImage(image string) (*Image, error) { } img = img2 } - newImage := ir.newFromStorage(img) - return newImage, nil + return img, nil } // GetImages retrieves all images present in storage @@ -702,18 +685,12 @@ func (i *Image) toImageSourceRef(ctx context.Context) (types.ImageSource, error) //Size returns the size of the image func (i *Image) Size(ctx context.Context) (*uint64, error) { - if i.image == nil { - localImage, err := i.getLocalImage() - if err != nil { - return nil, err - } - i.image = localImage - } - if sum, err := i.imageruntime.store.ImageSize(i.ID()); err == nil && sum >= 0 { + sum, err := i.imageruntime.store.ImageSize(i.ID()) + if err == nil && sum >= 0 { usum := uint64(sum) return &usum, nil } - return nil, errors.Errorf("unable to determine size") + return nil, errors.Wrap(err, "unable to determine size") } // toImageRef returns an Image Reference type from an image @@ -831,7 +808,8 @@ func (i *Image) History(ctx context.Context) ([]*History, error) { id := "<missing>" if x == numHistories { id = i.ID() - } else if layer != nil { + } + if layer != nil { if !oci.History[x].EmptyLayer { size = layer.UncompressedSize } @@ -938,12 +916,7 @@ func (i *Image) imageInspectInfo(ctx context.Context) (*types.ImageInspectInfo, return i.inspectInfo, nil } -// Inspect returns an image's inspect data -func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { - span, _ := opentracing.StartSpanFromContext(ctx, "imageInspect") - span.SetTag("type", "image") - defer span.Finish() - +func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.ImageData, error) { ociv1Img, err := i.ociv1Image(ctx) if err != nil { ociv1Img = &ociv1.Image{} @@ -958,8 +931,10 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { } size := int64(-1) - if usize, err := i.Size(ctx); err == nil { - size = int64(*usize) + if calculateSize { + if usize, err := i.Size(ctx); err == nil { + size = int64(*usize) + } } repoTags, err := i.RepoTags() @@ -1012,9 +987,38 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { History: ociv1Img.History, NamesHistory: i.NamesHistory(), } + if manifestType == manifest.DockerV2Schema2MediaType { + hc, err := i.GetHealthCheck(ctx) + if err != nil { + return nil, err + } + if hc != nil { + data.HealthCheck = hc + } + } return data, nil } +// Inspect returns an image's inspect data +func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { + span, _ := opentracing.StartSpanFromContext(ctx, "imageInspect") + + span.SetTag("type", "image") + defer span.Finish() + + return i.inspect(ctx, true) +} + +// InspectNoSize returns an image's inspect data without calculating the size for the image +func (i *Image) InspectNoSize(ctx context.Context) (*inspect.ImageData, error) { + span, _ := opentracing.StartSpanFromContext(ctx, "imageInspectNoSize") + + span.SetTag("type", "image") + defer span.Finish() + + return i.inspect(ctx, false) +} + // Import imports and image into the store and returns an image func (ir *Runtime) Import(ctx context.Context, path, reference string, writer io.Writer, signingOptions SigningOptions, imageConfig ociv1.Image) (*Image, error) { src, err := tarball.Transport.ParseReference(path) diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index 3ff6210d9..19f7eee1e 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -18,7 +18,6 @@ import ( var ( bbNames = []string{"docker.io/library/busybox:latest", "docker.io/library/busybox", "docker.io/busybox:latest", "docker.io/busybox", "busybox:latest", "busybox"} bbGlibcNames = []string{"docker.io/library/busybox:glibc", "docker.io/busybox:glibc", "busybox:glibc"} - fedoraNames = []string{"registry.fedoraproject.org/fedora-minimal:latest", "registry.fedoraproject.org/fedora-minimal", "fedora-minimal:latest", "fedora-minimal"} ) type localImageTest struct { @@ -139,7 +138,6 @@ func TestImage_New(t *testing.T) { ir.Eventer = events.NewNullEventer() // Build the list of pull names names = append(names, bbNames...) - names = append(names, fedoraNames...) writer := os.Stdout // Iterate over the names and delete the image diff --git a/libpod/image/manifests.go b/libpod/image/manifests.go new file mode 100644 index 000000000..9dbeb4cc5 --- /dev/null +++ b/libpod/image/manifests.go @@ -0,0 +1,154 @@ +package image + +import ( + "context" + + "github.com/containers/buildah/manifests" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" +) + +// Options for adding a manifest +// swagger:model ManifestAddOpts +type ManifestAddOpts struct { + All bool `json:"all"` + Annotation map[string]string `json:"annotation"` + Arch string `json:"arch"` + Features []string `json:"features"` + Images []string `json:"images"` + OSVersion string `json:"os_version"` + Variant string `json:"variant"` +} + +// InspectManifest returns a dockerized version of the manifest list +func (i *Image) InspectManifest() (*manifest.Schema2List, error) { + list, err := i.getManifestList() + if err != nil { + return nil, err + } + return list.Docker(), nil +} + +// RemoveManifest removes the given digest from the manifest list. +func (i *Image) RemoveManifest(d digest.Digest) (string, error) { + list, err := i.getManifestList() + if err != nil { + return "", err + } + if err := list.Remove(d); err != nil { + return "", err + } + return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "") +} + +// getManifestList is a helper to obtain a manifest list +func (i *Image) getManifestList() (manifests.List, error) { + _, list, err := manifests.LoadFromImage(i.imageruntime.store, i.ID()) + return list, err +} + +// CreateManifestList creates a new manifest list and can optionally add given images +// to the list +func CreateManifestList(rt *Runtime, systemContext types.SystemContext, names []string, imgs []string, all bool) (string, error) { + list := manifests.Create() + opts := ManifestAddOpts{Images: names, All: all} + for _, img := range imgs { + var ref types.ImageReference + newImage, err := rt.NewFromLocal(img) + if err == nil { + ir, err := newImage.toImageRef(context.Background()) + if err != nil { + return "", err + } + if ir == nil { + return "", errors.New("unable to convert image to ImageReference") + } + ref = ir.Reference() + } else { + ref, err = alltransports.ParseImageName(img) + if err != nil { + return "", err + } + } + list, err = addManifestToList(ref, list, systemContext, opts) + if err != nil { + return "", err + } + } + return list.SaveToImage(rt.store, "", names, manifest.DockerV2ListMediaType) +} + +func addManifestToList(ref types.ImageReference, list manifests.List, systemContext types.SystemContext, opts ManifestAddOpts) (manifests.List, error) { + d, err := list.Add(context.Background(), &systemContext, ref, opts.All) + if err != nil { + return nil, err + } + if len(opts.OSVersion) > 0 { + if err := list.SetOSVersion(d, opts.OSVersion); err != nil { + return nil, err + } + } + if len(opts.Features) > 0 { + if err := list.SetFeatures(d, opts.Features); err != nil { + return nil, err + } + } + if len(opts.Arch) > 0 { + if err := list.SetArchitecture(d, opts.Arch); err != nil { + return nil, err + } + } + if len(opts.Variant) > 0 { + if err := list.SetVariant(d, opts.Variant); err != nil { + return nil, err + } + } + if len(opts.Annotation) > 0 { + if err := list.SetAnnotations(&d, opts.Annotation); err != nil { + return nil, err + } + } + return list, err +} + +// AddManifest adds a manifest to a given manifest list. +func (i *Image) AddManifest(systemContext types.SystemContext, opts ManifestAddOpts) (string, error) { + var ( + ref types.ImageReference + ) + newImage, err := i.imageruntime.NewFromLocal(opts.Images[0]) + if err == nil { + ir, err := newImage.toImageRef(context.Background()) + if err != nil { + return "", err + } + ref = ir.Reference() + } else { + ref, err = alltransports.ParseImageName(opts.Images[0]) + if err != nil { + return "", err + } + } + list, err := i.getManifestList() + if err != nil { + return "", err + } + list, err = addManifestToList(ref, list, systemContext, opts) + if err != nil { + return "", err + } + return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "") +} + +// PushManifest pushes a manifest to a destination +func (i *Image) PushManifest(dest types.ImageReference, opts manifests.PushOptions) (digest.Digest, error) { + list, err := i.getManifestList() + if err != nil { + return "", err + } + _, d, err := list.Push(context.Background(), dest, opts) + return d, err +} diff --git a/libpod/image/parts.go b/libpod/image/parts.go index d4677f935..d6c98783b 100644 --- a/libpod/image/parts.go +++ b/libpod/image/parts.go @@ -67,7 +67,7 @@ func (ip *imageParts) suspiciousRefNameTagValuesForSearch() (string, string, str } else if _, hasDigest := ip.unnormalizedRef.(reference.Digested); hasDigest { tag = "none" } else { - tag = "latest" + tag = LatestTag } return registry, imageName, tag } diff --git a/libpod/image/pull.go b/libpod/image/pull.go index 76294ba06..fd359d593 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -126,6 +126,7 @@ func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types. if err != nil { return nil, err } + defer tarSource.Close() manifest, err := tarSource.LoadTarManifest() if err != nil { diff --git a/libpod/image/tree.go b/libpod/image/tree.go new file mode 100644 index 000000000..c7c69462f --- /dev/null +++ b/libpod/image/tree.go @@ -0,0 +1,138 @@ +package image + +import ( + "context" + "fmt" + "strings" + + "github.com/docker/go-units" + "github.com/pkg/errors" +) + +const ( + middleItem = "├── " + continueItem = "│ " + lastItem = "└── " +) + +type tree struct { + img *Image + imageInfo *InfoImage + layerInfo map[string]*LayerInfo + sb *strings.Builder +} + +// GenerateTree creates an image tree string representation for displaying it +// to the user. +func (i *Image) GenerateTree(whatRequires bool) (string, error) { + // Fetch map of image-layers, which is used for printing output. + layerInfo, err := GetLayersMapWithImageInfo(i.imageruntime) + if err != nil { + return "", errors.Wrapf(err, "error while retrieving layers of image %q", i.InputName) + } + + // Create an imageInfo and fill the image and layer info + imageInfo := &InfoImage{ + ID: i.ID(), + Tags: i.Names(), + } + + if err := BuildImageHierarchyMap(imageInfo, layerInfo, i.TopLayer()); err != nil { + return "", err + } + sb := &strings.Builder{} + tree := &tree{i, imageInfo, layerInfo, sb} + if err := tree.print(whatRequires); err != nil { + return "", err + } + return tree.string(), nil +} + +func (t *tree) string() string { + return t.sb.String() +} + +func (t *tree) print(whatRequires bool) error { + size, err := t.img.Size(context.Background()) + if err != nil { + return err + } + + fmt.Fprintf(t.sb, "Image ID: %s\n", t.imageInfo.ID[:12]) + fmt.Fprintf(t.sb, "Tags: %s\n", t.imageInfo.Tags) + fmt.Fprintf(t.sb, "Size: %v\n", units.HumanSizeWithPrecision(float64(*size), 4)) + if t.img.TopLayer() != "" { + fmt.Fprintf(t.sb, "Image Layers\n") + } else { + fmt.Fprintf(t.sb, "No Image Layers\n") + } + + if !whatRequires { + // fill imageInfo with layers associated with image. + // the layers will be filled such that + // (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) + // Build output from imageInfo into buffer + t.printImageHierarchy(t.imageInfo) + } else { + // fill imageInfo with layers associated with image. + // the layers will be filled such that + // (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End) + // (Forks)... intermediate Child Layer(s) -> Child Top Layer(End) + return t.printImageChildren(t.layerInfo, t.img.TopLayer(), "", true) + } + return nil +} + +// Stores all children layers which are created using given Image. +// Layers are stored as follows +// (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End) +// (Forks)... intermediate Child Layer(s) -> Child Top Layer(End) +func (t *tree) printImageChildren(layerMap map[string]*LayerInfo, layerID string, prefix string, last bool) error { + if layerID == "" { + return nil + } + ll, ok := layerMap[layerID] + if !ok { + return fmt.Errorf("lookup error: layerid %s, not found", layerID) + } + fmt.Fprint(t.sb, prefix) + + //initialize intend with middleItem to reduce middleItem checks. + intend := middleItem + if !last { + // add continueItem i.e. '|' for next iteration prefix + prefix += continueItem + } else if len(ll.ChildID) > 1 || len(ll.ChildID) == 0 { + // The above condition ensure, alignment happens for node, which has more then 1 children. + // If node is last in printing hierarchy, it should not be printed as middleItem i.e. ├── + intend = lastItem + prefix += " " + } + + var tags string + if len(ll.RepoTags) > 0 { + tags = fmt.Sprintf(" Top Layer of: %s", ll.RepoTags) + } + fmt.Fprintf(t.sb, "%sID: %s Size: %7v%s\n", intend, ll.ID[:12], units.HumanSizeWithPrecision(float64(ll.Size), 4), tags) + for count, childID := range ll.ChildID { + if err := t.printImageChildren(layerMap, childID, prefix, count == len(ll.ChildID)-1); err != nil { + return err + } + } + return nil +} + +// prints the layers info of image +func (t *tree) printImageHierarchy(imageInfo *InfoImage) { + for count, l := range imageInfo.Layers { + var tags string + intend := middleItem + if len(l.RepoTags) > 0 { + tags = fmt.Sprintf(" Top Layer of: %s", l.RepoTags) + } + if count == len(imageInfo.Layers)-1 { + intend = lastItem + } + fmt.Fprintf(t.sb, "%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags) + } +} |