mirror of
https://github.com/containers/podman.git
synced 2025-06-21 01:19:15 +08:00
Merge pull request #2990 from nalind/image-parent-child
image: rework parent/child/history matching
This commit is contained in:
@ -243,7 +243,7 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma
|
|||||||
// If all is false and the image doesn't have a name, check to see if the top layer of the image is a parent
|
// If all is false and the image doesn't have a name, check to see if the top layer of the image is a parent
|
||||||
// to another image's top layer. If it is, then it is an intermediate image so don't print out if the --all flag
|
// to another image's top layer. If it is, then it is an intermediate image so don't print out if the --all flag
|
||||||
// is not set.
|
// is not set.
|
||||||
isParent, err := img.IsParent()
|
isParent, err := img.IsParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("error checking if image is a parent %q: %v", img.ID(), err)
|
logrus.Errorf("error checking if image is a parent %q: %v", img.ID(), err)
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ func pruneImagesCmd(c *cliconfig.PruneImagesValues) error {
|
|||||||
|
|
||||||
// Call prune; if any cids are returned, print them and then
|
// Call prune; if any cids are returned, print them and then
|
||||||
// return err in case an error also came up
|
// return err in case an error also came up
|
||||||
pruneCids, err := runtime.PruneImages(c.All)
|
pruneCids, err := runtime.PruneImages(getContext(), c.All)
|
||||||
if len(pruneCids) > 0 {
|
if len(pruneCids) > 0 {
|
||||||
for _, cid := range pruneCids {
|
for _, cid := range pruneCids {
|
||||||
fmt.Println(cid)
|
fmt.Println(cid)
|
||||||
|
@ -97,7 +97,7 @@ func rmiCmd(c *cliconfig.RmiValues) error {
|
|||||||
return errors.New("unable to delete all images; re-run the rmi command again.")
|
return errors.New("unable to delete all images; re-run the rmi command again.")
|
||||||
}
|
}
|
||||||
for _, i := range imagesToDelete {
|
for _, i := range imagesToDelete {
|
||||||
isParent, err := i.IsParent()
|
isParent, err := i.IsParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ func imageUniqueSize(ctx context.Context, images []*image.Image) (map[string]uin
|
|||||||
for _, img := range images {
|
for _, img := range images {
|
||||||
parentImg := img
|
parentImg := img
|
||||||
for {
|
for {
|
||||||
next, err := parentImg.GetParent()
|
next, err := parentImg.GetParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error getting parent of image %s", parentImg.ID())
|
return nil, errors.Wrapf(err, "error getting parent of image %s", parentImg.ID())
|
||||||
}
|
}
|
||||||
@ -246,11 +246,11 @@ func getImageDiskUsage(ctx context.Context, images []*image.Image, imageUsedbyCi
|
|||||||
|
|
||||||
unreclaimableSize += imageUsedSize(img, imgUniqueSizeMap, imageUsedbyCintainerMap, imageUsedbyActiveContainerMap)
|
unreclaimableSize += imageUsedSize(img, imgUniqueSizeMap, imageUsedbyCintainerMap, imageUsedbyActiveContainerMap)
|
||||||
|
|
||||||
isParent, err := img.IsParent()
|
isParent, err := img.IsParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return imageDiskUsage, err
|
return imageDiskUsage, err
|
||||||
}
|
}
|
||||||
parent, err := img.GetParent()
|
parent, err := img.GetParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return imageDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID())
|
return imageDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID())
|
||||||
}
|
}
|
||||||
@ -437,11 +437,11 @@ func getImageVerboseDiskUsage(ctx context.Context, images []*image.Image, images
|
|||||||
return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting unique size of images")
|
return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting unique size of images")
|
||||||
}
|
}
|
||||||
for _, img := range images {
|
for _, img := range images {
|
||||||
isParent, err := img.IsParent()
|
isParent, err := img.IsParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return imagesVerboseDiskUsage, errors.Wrapf(err, "error checking if %s is a parent images", img.ID())
|
return imagesVerboseDiskUsage, errors.Wrapf(err, "error checking if %s is a parent images", img.ID())
|
||||||
}
|
}
|
||||||
parent, err := img.GetParent()
|
parent, err := img.GetParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID())
|
return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID())
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ Are you sure you want to continue? [y/N] `, volumeString)
|
|||||||
|
|
||||||
// Call prune; if any cids are returned, print them and then
|
// Call prune; if any cids are returned, print them and then
|
||||||
// return err in case an error also came up
|
// return err in case an error also came up
|
||||||
pruneCids, err := runtime.PruneImages(c.All)
|
pruneCids, err := runtime.PruneImages(ctx, c.All)
|
||||||
if len(pruneCids) > 0 {
|
if len(pruneCids) > 0 {
|
||||||
fmt.Println("Deleted Images")
|
fmt.Println("Deleted Images")
|
||||||
for _, cid := range pruneCids {
|
for _, cid := range pruneCids {
|
||||||
|
@ -353,8 +353,8 @@ func (i *Image) TopLayer() string {
|
|||||||
// outside the context of images
|
// outside the context of images
|
||||||
// TODO: the force param does nothing as of now. Need to move container
|
// TODO: the force param does nothing as of now. Need to move container
|
||||||
// handling logic here eventually.
|
// handling logic here eventually.
|
||||||
func (i *Image) Remove(force bool) error {
|
func (i *Image) Remove(ctx context.Context, force bool) error {
|
||||||
parent, err := i.GetParent()
|
parent, err := i.GetParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -363,11 +363,11 @@ func (i *Image) Remove(force bool) error {
|
|||||||
}
|
}
|
||||||
i.newImageEvent(events.Remove)
|
i.newImageEvent(events.Remove)
|
||||||
for parent != nil {
|
for parent != nil {
|
||||||
nextParent, err := parent.GetParent()
|
nextParent, err := parent.GetParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
children, err := parent.GetChildren()
|
children, err := parent.GetChildren(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -679,7 +679,8 @@ type History struct {
|
|||||||
Comment string `json:"comment"`
|
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) {
|
func (i *Image) History(ctx context.Context) ([]*History, error) {
|
||||||
img, err := i.toImageRef(ctx)
|
img, err := i.toImageRef(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -690,31 +691,92 @@ func (i *Image) History(ctx context.Context) ([]*History, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the IDs of the images making up the history layers
|
// Use our layers list to find images that use one of them as its
|
||||||
// if the images exist locally in the store
|
// 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()
|
images, err := i.imageruntime.GetImages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error getting images from store")
|
return nil, errors.Wrapf(err, "error getting images from store")
|
||||||
}
|
}
|
||||||
imageIDs := []string{i.ID()}
|
interestingImages := make([]*Image, 0, len(images))
|
||||||
if err := i.historyLayerIDs(i.TopLayer(), images, &imageIDs); err != nil {
|
for i := range images {
|
||||||
return nil, errors.Wrap(err, "error getting image IDs for layers in history")
|
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 (
|
var (
|
||||||
imageID string
|
|
||||||
imgIDCount = 0
|
|
||||||
size int64
|
size int64
|
||||||
sizeCount = 1
|
sizeCount = 1
|
||||||
allHistory []*History
|
allHistory []*History
|
||||||
)
|
)
|
||||||
|
|
||||||
for i := len(oci.History) - 1; i >= 0; i-- {
|
for i := len(oci.History) - 1; i >= 0; i-- {
|
||||||
if imgIDCount < len(imageIDs) {
|
imageID := "<missing>"
|
||||||
imageID = imageIDs[imgIDCount]
|
if historyImages[i] != nil {
|
||||||
imgIDCount++
|
imageID = historyImages[i].ID()
|
||||||
} else {
|
|
||||||
imageID = "<missing>"
|
|
||||||
}
|
}
|
||||||
if !oci.History[i].EmptyLayer {
|
if !oci.History[i].EmptyLayer {
|
||||||
size = img.LayerInfos()[len(img.LayerInfos())-sizeCount].Size
|
size = img.LayerInfos()[len(img.LayerInfos())-sizeCount].Size
|
||||||
@ -1006,26 +1068,110 @@ func splitString(input string) string {
|
|||||||
// IsParent goes through the layers in the store and checks if i.TopLayer is
|
// 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
|
// the parent of any other layer in store. Double check that image with that
|
||||||
// layer exists as well.
|
// layer exists as well.
|
||||||
func (i *Image) IsParent() (bool, error) {
|
func (i *Image) IsParent(ctx context.Context) (bool, error) {
|
||||||
children, err := i.GetChildren()
|
children, err := i.getChildren(ctx, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return len(children) > 0, nil
|
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.
|
// 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()
|
images, err := i.imageruntime.GetImages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, img := range images {
|
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
|
return img, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1033,38 +1179,55 @@ func (i *Image) GetParent() (*Image, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetChildren returns a list of the imageIDs that depend on the image
|
// 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
|
var children []string
|
||||||
images, err := i.imageruntime.GetImages()
|
images, err := i.imageruntime.GetImages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
layers, err := i.imageruntime.store.Layers()
|
|
||||||
|
// fetch the configuration for the parent image
|
||||||
|
parent, err := i.ociv1Image(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
parentLayer := i.TopLayer()
|
||||||
|
|
||||||
for _, layer := range layers {
|
for _, img := range images {
|
||||||
if layer.Parent == i.TopLayer() {
|
if img.ID() == i.ID() {
|
||||||
if imageID := getImageOfTopLayer(images, layer.ID); len(imageID) > 0 {
|
continue
|
||||||
children = append(children, imageID...)
|
|
||||||
}
|
}
|
||||||
|
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 children, nil
|
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matches
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputIsID returns a bool if the user input for an image
|
// InputIsID returns a bool if the user input for an image
|
||||||
// is the image's partial or full id
|
// is the image's partial or full id
|
||||||
func (i *Image) InputIsID() bool {
|
func (i *Image) InputIsID() bool {
|
||||||
|
@ -139,7 +139,7 @@ func TestImage_New(t *testing.T) {
|
|||||||
newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, false, nil)
|
newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, false, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEqual(t, newImage.ID(), "")
|
assert.NotEqual(t, newImage.ID(), "")
|
||||||
err = newImage.Remove(false)
|
err = newImage.Remove(context.Background(), false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package image
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod/events"
|
"github.com/containers/libpod/libpod/events"
|
||||||
"github.com/pkg/errors"
|
"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
|
// PruneImages prunes dangling and optionally all unused images from the local
|
||||||
// image store
|
// image store
|
||||||
func (ir *Runtime) PruneImages(all bool) ([]string, error) {
|
func (ir *Runtime) PruneImages(ctx context.Context, all bool) ([]string, error) {
|
||||||
var prunedCids []string
|
var prunedCids []string
|
||||||
pruneImages, err := ir.GetPruneImages(all)
|
pruneImages, err := ir.GetPruneImages(all)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "unable to get images to prune")
|
return nil, errors.Wrap(err, "unable to get images to prune")
|
||||||
}
|
}
|
||||||
for _, p := range pruneImages {
|
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")
|
return nil, errors.Wrap(err, "failed to prune image")
|
||||||
}
|
}
|
||||||
defer p.newImageEvent(events.Prune)
|
defer p.newImageEvent(events.Prune)
|
||||||
|
@ -57,7 +57,7 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChildren, err := img.IsParent()
|
hasChildren, err := img.IsParent(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -82,12 +82,12 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
|
|||||||
// reponames and no force is applied, we error out.
|
// reponames and no force is applied, we error out.
|
||||||
return "", fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", img.ID())
|
return "", fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", img.ID())
|
||||||
}
|
}
|
||||||
err = img.Remove(force)
|
err = img.Remove(ctx, force)
|
||||||
if err != nil && errors.Cause(err) == storage.ErrImageUsedByContainer {
|
if err != nil && errors.Cause(err) == storage.ErrImageUsedByContainer {
|
||||||
if errStorage := r.rmStorageContainers(force, img); errStorage == nil {
|
if errStorage := r.rmStorageContainers(force, img); errStorage == nil {
|
||||||
// Containers associated with the image should be deleted now,
|
// Containers associated with the image should be deleted now,
|
||||||
// let's try removing the image again.
|
// let's try removing the image again.
|
||||||
err = img.Remove(force)
|
err = img.Remove(ctx, force)
|
||||||
} else {
|
} else {
|
||||||
err = errStorage
|
err = errStorage
|
||||||
}
|
}
|
||||||
|
@ -119,8 +119,8 @@ func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, for
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PruneImages is wrapper into PruneImages within the image pkg
|
// PruneImages is wrapper into PruneImages within the image pkg
|
||||||
func (r *LocalRuntime) PruneImages(all bool) ([]string, error) {
|
func (r *LocalRuntime) PruneImages(ctx context.Context, all bool) ([]string, error) {
|
||||||
return r.ImageRuntime().PruneImages(all)
|
return r.ImageRuntime().PruneImages(ctx, all)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export is a wrapper to container export to a tarfile
|
// Export is a wrapper to container export to a tarfile
|
||||||
|
@ -256,7 +256,7 @@ func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authf
|
|||||||
// IsParent goes through the layers in the store and checks if i.TopLayer is
|
// 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
|
// the parent of any other layer in store. Double check that image with that
|
||||||
// layer exists as well.
|
// layer exists as well.
|
||||||
func (ci *ContainerImage) IsParent() (bool, error) {
|
func (ci *ContainerImage) IsParent(context.Context) (bool, error) {
|
||||||
return ci.remoteImage.isParent, nil
|
return ci.remoteImage.isParent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,7 +338,7 @@ func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PruneImages is the wrapper call for a remote-client to prune images
|
// PruneImages is the wrapper call for a remote-client to prune images
|
||||||
func (r *LocalRuntime) PruneImages(all bool) ([]string, error) {
|
func (r *LocalRuntime) PruneImages(ctx context.Context, all bool) ([]string, error) {
|
||||||
return iopodman.ImagesPrune().Call(r.Conn, all)
|
return iopodman.ImagesPrune().Call(r.Conn, all)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ package varlinkapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -49,7 +50,7 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size, _ := image.Size(getContext())
|
size, _ := image.Size(getContext())
|
||||||
isParent, err := image.IsParent()
|
isParent, err := image.IsParent(context.TODO())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return call.ReplyErrorOccurred(err.Error())
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
}
|
}
|
||||||
@ -503,7 +504,7 @@ func (i *LibpodAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error {
|
|||||||
return call.ReplyErrorOccurred(err.Error())
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
}
|
}
|
||||||
if len(containers) == 0 {
|
if len(containers) == 0 {
|
||||||
if err := img.Remove(false); err != nil {
|
if err := img.Remove(context.TODO(), false); err != nil {
|
||||||
return call.ReplyErrorOccurred(err.Error())
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
}
|
}
|
||||||
deletedImages = append(deletedImages, img.ID())
|
deletedImages = append(deletedImages, img.ID())
|
||||||
@ -739,7 +740,7 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.
|
|||||||
|
|
||||||
// ImagesPrune ....
|
// ImagesPrune ....
|
||||||
func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool) error {
|
func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool) error {
|
||||||
prunedImages, err := i.Runtime.ImageRuntime().PruneImages(all)
|
prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return call.ReplyErrorOccurred(err.Error())
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user