diff options
Diffstat (limited to 'libpod/image')
-rw-r--r-- | libpod/image/image.go | 243 | ||||
-rw-r--r-- | libpod/image/image_test.go | 2 | ||||
-rw-r--r-- | libpod/image/prune.go | 6 |
3 files changed, 208 insertions, 43 deletions
diff --git a/libpod/image/image.go b/libpod/image/image.go index 27773edcf..b965a4640 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -355,8 +355,8 @@ func (i *Image) TopLayer() string { // outside the context of images // TODO: the force param does nothing as of now. Need to move container // handling logic here eventually. -func (i *Image) Remove(force bool) error { - parent, err := i.GetParent() +func (i *Image) Remove(ctx context.Context, force bool) error { + parent, err := i.GetParent(ctx) if err != nil { return err } @@ -365,11 +365,11 @@ func (i *Image) Remove(force bool) error { } i.newImageEvent(events.Remove) for parent != nil { - nextParent, err := parent.GetParent() + nextParent, err := parent.GetParent(ctx) if err != nil { return err } - children, err := parent.GetChildren() + children, err := parent.GetChildren(ctx) if err != nil { return err } @@ -681,7 +681,8 @@ type History struct { Comment string `json:"comment"` } -// History gets the history of an image and information about its layers +// History gets the history of an image and the IDs of images that are part of +// its history func (i *Image) History(ctx context.Context) ([]*History, error) { img, err := i.toImageRef(ctx) if err != nil { @@ -692,31 +693,92 @@ func (i *Image) History(ctx context.Context) ([]*History, error) { return nil, err } - // Get the IDs of the images making up the history layers - // if the images exist locally in the store + // Use our layers list to find images that use one of them as its + // topmost layer. + interestingLayers := make(map[string]bool) + layer, err := i.imageruntime.store.Layer(i.TopLayer()) + if err != nil { + return nil, err + } + for layer != nil { + interestingLayers[layer.ID] = true + if layer.Parent == "" { + break + } + layer, err = i.imageruntime.store.Layer(layer.Parent) + if err != nil { + return nil, err + } + } + + // Get the IDs of the images that share some of our layers. Hopefully + // this step means that we'll be able to avoid reading the + // configuration of every single image in local storage later on. images, err := i.imageruntime.GetImages() if err != nil { return nil, errors.Wrapf(err, "error getting images from store") } - imageIDs := []string{i.ID()} - if err := i.historyLayerIDs(i.TopLayer(), images, &imageIDs); err != nil { - return nil, errors.Wrap(err, "error getting image IDs for layers in history") + interestingImages := make([]*Image, 0, len(images)) + for i := range images { + if interestingLayers[images[i].TopLayer()] { + interestingImages = append(interestingImages, images[i]) + } + } + + // Build a list of image IDs that correspond to our history entries. + historyImages := make([]*Image, len(oci.History)) + if len(oci.History) > 0 { + // The starting image shares its whole history with itself. + historyImages[len(historyImages)-1] = i + for i := range interestingImages { + image, err := images[i].ociv1Image(ctx) + if err != nil { + return nil, errors.Wrapf(err, "error getting image configuration for image %q", images[i].ID()) + } + // If the candidate has a longer history or no history + // at all, then it doesn't share the portion of our + // history that we're interested in matching with other + // images. + if len(image.History) == 0 || len(image.History) > len(historyImages) { + continue + } + // If we don't include all of the layers that the + // candidate image does (i.e., our rootfs didn't look + // like its rootfs at any point), then it can't be part + // of our history. + if len(image.RootFS.DiffIDs) > len(oci.RootFS.DiffIDs) { + continue + } + candidateLayersAreUsed := true + for i := range image.RootFS.DiffIDs { + if image.RootFS.DiffIDs[i] != oci.RootFS.DiffIDs[i] { + candidateLayersAreUsed = false + break + } + } + if !candidateLayersAreUsed { + continue + } + // If the candidate's entire history is an initial + // portion of our history, then we're based on it, + // either directly or indirectly. + sharedHistory := historiesMatch(oci.History, image.History) + if sharedHistory == len(image.History) { + historyImages[sharedHistory-1] = images[i] + } + } } var ( - imageID string - imgIDCount = 0 size int64 sizeCount = 1 allHistory []*History ) for i := len(oci.History) - 1; i >= 0; i-- { - if imgIDCount < len(imageIDs) { - imageID = imageIDs[imgIDCount] - imgIDCount++ - } else { - imageID = "<missing>" + imageID := "<missing>" + if historyImages[i] != nil { + imageID = historyImages[i].ID() } if !oci.History[i].EmptyLayer { size = img.LayerInfos()[len(img.LayerInfos())-sizeCount].Size @@ -1008,26 +1070,110 @@ func splitString(input string) string { // IsParent goes through the layers in the store and checks if i.TopLayer is // the parent of any other layer in store. Double check that image with that // layer exists as well. -func (i *Image) IsParent() (bool, error) { - children, err := i.GetChildren() +func (i *Image) IsParent(ctx context.Context) (bool, error) { + children, err := i.getChildren(ctx, 1) if err != nil { return false, err } return len(children) > 0, nil } +// historiesMatch returns the number of entries in the histories which have the +// same contents +func historiesMatch(a, b []imgspecv1.History) int { + i := 0 + for i < len(a) && i < len(b) { + if a[i].Created != nil && b[i].Created == nil { + return i + } + if a[i].Created == nil && b[i].Created != nil { + return i + } + if a[i].Created != nil && b[i].Created != nil { + if !a[i].Created.Equal(*(b[i].Created)) { + return i + } + } + if a[i].CreatedBy != b[i].CreatedBy { + return i + } + if a[i].Author != b[i].Author { + return i + } + if a[i].Comment != b[i].Comment { + return i + } + if a[i].EmptyLayer != b[i].EmptyLayer { + return i + } + i++ + } + return i +} + +// areParentAndChild checks diff ID and history in the two images and return +// true if the second should be considered to be directly based on the first +func areParentAndChild(parent, child *imgspecv1.Image) bool { + // the child and candidate parent should share all of the + // candidate parent's diff IDs, which together would have + // controlled which layers were used + if len(parent.RootFS.DiffIDs) > len(child.RootFS.DiffIDs) { + return false + } + childUsesCandidateDiffs := true + for i := range parent.RootFS.DiffIDs { + if child.RootFS.DiffIDs[i] != parent.RootFS.DiffIDs[i] { + childUsesCandidateDiffs = false + break + } + } + if !childUsesCandidateDiffs { + return false + } + // the child should have the same history as the parent, plus + // one more entry + if len(parent.History)+1 != len(child.History) { + return false + } + if historiesMatch(parent.History, child.History) != len(parent.History) { + return false + } + return true +} + // GetParent returns the image ID of the parent. Return nil if a parent is not found. -func (i *Image) GetParent() (*Image, error) { +func (i *Image) GetParent(ctx context.Context) (*Image, error) { images, err := i.imageruntime.GetImages() if err != nil { return nil, err } - layer, err := i.imageruntime.store.Layer(i.TopLayer()) + childLayer, err := i.imageruntime.store.Layer(i.TopLayer()) + if err != nil { + return nil, err + } + // fetch the configuration for the child image + child, err := i.ociv1Image(ctx) if err != nil { return nil, err } for _, img := range images { - if img.TopLayer() == layer.Parent { + if img.ID() == i.ID() { + continue + } + candidateLayer := img.TopLayer() + // as a child, our top layer is either the candidate parent's + // layer, or one that's derived from it, so skip over any + // candidate image where we know that isn't the case + if candidateLayer != childLayer.Parent && candidateLayer != childLayer.ID { + continue + } + // fetch the configuration for the candidate image + candidate, err := img.ociv1Image(ctx) + if err != nil { + return nil, err + } + // compare them + if areParentAndChild(candidate, child) { return img, nil } } @@ -1035,36 +1181,53 @@ func (i *Image) GetParent() (*Image, error) { } // GetChildren returns a list of the imageIDs that depend on the image -func (i *Image) GetChildren() ([]string, error) { +func (i *Image) GetChildren(ctx context.Context) ([]string, error) { + return i.getChildren(ctx, 0) +} + +// 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 images, err := i.imageruntime.GetImages() if err != nil { return nil, err } - layers, err := i.imageruntime.store.Layers() + + // fetch the configuration for the parent image + parent, err := i.ociv1Image(ctx) if err != nil { return nil, err } + parentLayer := i.TopLayer() - for _, layer := range layers { - if layer.Parent == i.TopLayer() { - if imageID := getImageOfTopLayer(images, layer.ID); len(imageID) > 0 { - children = append(children, imageID...) - } - } - } - return children, nil -} - -// getImageOfTopLayer returns the image ID where layer is the top layer of the image -func getImageOfTopLayer(images []*Image, layer string) []string { - var matches []string for _, img := range images { - if img.TopLayer() == layer { - matches = append(matches, img.ID()) + if img.ID() == i.ID() { + continue + } + candidateLayer, err := img.Layer() + if err != nil { + return nil, err + } + // if this image's top layer is not our top layer, and is not + // based on our top layer, we can skip it + if candidateLayer.Parent != parentLayer && candidateLayer.ID != parentLayer { + continue + } + // fetch the configuration for the candidate image + candidate, err := img.ociv1Image(ctx) + if err != nil { + return nil, err + } + // compare them + if areParentAndChild(parent, candidate) { + children = append(children, img.ID()) + } + // if we're not building an exhaustive list, maybe we're done? + if max > 0 && len(children) >= max { + break } } - return matches + return children, nil } // InputIsID returns a bool if the user input for an image diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index dfe817c90..e93ebf797 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -142,7 +142,7 @@ func TestImage_New(t *testing.T) { newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, false, nil) assert.NoError(t, err) assert.NotEqual(t, newImage.ID(), "") - err = newImage.Remove(false) + err = newImage.Remove(context.Background(), false) assert.NoError(t, err) } diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 5bd3c2c99..a4f8a0c9f 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -1,6 +1,8 @@ package image import ( + "context" + "github.com/containers/libpod/libpod/events" "github.com/pkg/errors" ) @@ -34,14 +36,14 @@ func (ir *Runtime) GetPruneImages(all bool) ([]*Image, error) { // PruneImages prunes dangling and optionally all unused images from the local // image store -func (ir *Runtime) PruneImages(all bool) ([]string, error) { +func (ir *Runtime) PruneImages(ctx context.Context, all bool) ([]string, error) { var prunedCids []string pruneImages, err := ir.GetPruneImages(all) if err != nil { return nil, errors.Wrap(err, "unable to get images to prune") } for _, p := range pruneImages { - if err := p.Remove(true); err != nil { + if err := p.Remove(ctx, true); err != nil { return nil, errors.Wrap(err, "failed to prune image") } defer p.newImageEvent(events.Prune) |