Skip to content

Commit 44c505a

Browse files
committed
When checking IsDangling make sure image is not in manifest list
Currently when we run podman image prune or podman images --filter dangling It is pruning images that are in a local manifest. These images are not dangling because they are currently in use by a named manifest list. You can create this situation simply by doing echo "from scratch" > /tmp/Containerfile id=$(podman build /tmp) podman manifest create test $id podman image prune --force podman image exists $id Will return an error since the image was pruned. Now the local manifest test is broken. Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
1 parent dbeb17e commit 44c505a

File tree

5 files changed

+46
-15
lines changed

5 files changed

+46
-15
lines changed

libimage/history.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func (i *Image) History(ctx context.Context) ([]ImageHistory, error) {
2525
return nil, err
2626
}
2727

28-
layerTree, err := i.runtime.newFreshLayerTree()
28+
layerTree, err := i.runtime.newFreshLayerTree(ctx)
2929
if err != nil {
3030
return nil, err
3131
}

libimage/image.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,21 @@ func (i *Image) IsReadOnly() bool {
191191
}
192192

193193
// IsDangling returns true if the image is dangling, that is an untagged image
194-
// without children.
194+
// without children and not used in a manifest list.
195195
func (i *Image) IsDangling(ctx context.Context) (bool, error) {
196-
return i.isDangling(ctx, nil)
196+
images, layers, err := i.runtime.getImagesAndLayers()
197+
if err != nil {
198+
return false, err
199+
}
200+
tree, err := i.runtime.newLayerTreeFromData(ctx, images, layers, true)
201+
if err != nil {
202+
return false, err
203+
}
204+
return i.isDangling(ctx, tree)
197205
}
198206

199207
// isDangling returns true if the image is dangling, that is an untagged image
200-
// without children. If tree is nil, it will created for this invocation only.
208+
// without children and not used in a manifest list. If tree is nil, it will created for this invocation only.
201209
func (i *Image) isDangling(ctx context.Context, tree *layerTree) (bool, error) {
202210
if len(i.Names()) > 0 {
203211
return false, nil
@@ -206,7 +214,8 @@ func (i *Image) isDangling(ctx context.Context, tree *layerTree) (bool, error) {
206214
if err != nil {
207215
return false, err
208216
}
209-
return len(children) == 0, nil
217+
_, usedInManfiestList := tree.manifestListDigests[i.Digest()]
218+
return (len(children) == 0 && !usedInManfiestList), nil
210219
}
211220

212221
// IsIntermediate returns true if the image is an intermediate image, that is
@@ -258,7 +267,7 @@ func (i *Image) TopLayer() string {
258267

259268
// Parent returns the parent image or nil if there is none
260269
func (i *Image) Parent(ctx context.Context) (*Image, error) {
261-
tree, err := i.runtime.newFreshLayerTree()
270+
tree, err := i.runtime.newFreshLayerTree(ctx)
262271
if err != nil {
263272
return nil, err
264273
}
@@ -292,7 +301,7 @@ func (i *Image) Children(ctx context.Context) ([]*Image, error) {
292301
// created for this invocation only.
293302
func (i *Image) getChildren(ctx context.Context, all bool, tree *layerTree) ([]*Image, error) {
294303
if tree == nil {
295-
t, err := i.runtime.newFreshLayerTree()
304+
t, err := i.runtime.newFreshLayerTree(ctx)
296305
if err != nil {
297306
return nil, err
298307
}

libimage/image_tree.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package libimage
44

55
import (
6+
"context"
67
"fmt"
78
"strings"
89

@@ -13,7 +14,7 @@ import (
1314
// Tree generates a tree for the specified image and its layers. Use
1415
// `traverseChildren` to traverse the layers of all children. By default, only
1516
// layers of the image are printed.
16-
func (i *Image) Tree(traverseChildren bool) (string, error) {
17+
func (i *Image) Tree(ctx context.Context, traverseChildren bool) (string, error) {
1718
// NOTE: a string builder prevents us from copying to much data around
1819
// and compile the string when and where needed.
1920
sb := &strings.Builder{}
@@ -37,7 +38,7 @@ func (i *Image) Tree(traverseChildren bool) (string, error) {
3738
fmt.Fprintf(sb, "No Image Layers")
3839
}
3940

40-
layerTree, err := i.runtime.newFreshLayerTree()
41+
layerTree, err := i.runtime.newFreshLayerTree(ctx)
4142
if err != nil {
4243
return "", err
4344
}

libimage/layer_tree.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/containers/storage"
1010
storageTypes "github.com/containers/storage/types"
11+
digest "github.com/opencontainers/go-digest"
1112
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
1213
"github.com/sirupsen/logrus"
1314
)
@@ -22,6 +23,10 @@ type layerTree struct {
2223
// emptyImages do not have any top-layer so we cannot create a
2324
// *layerNode for them.
2425
emptyImages []*Image
26+
// manifestList keep track of images based on their digest.
27+
// Library will use this map when checking if a image is dangling.
28+
// If an image is used in a manifestList it is NOT dangling
29+
manifestListDigests map[digest.Digest]struct{}
2530
}
2631

2732
// node returns a layerNode for the specified layerID.
@@ -90,20 +95,21 @@ func (l *layerNode) repoTags() ([]string, error) {
9095
}
9196

9297
// newFreshLayerTree extracts a layerTree from consistent layers and images in the local storage.
93-
func (r *Runtime) newFreshLayerTree() (*layerTree, error) {
98+
func (r *Runtime) newFreshLayerTree(ctx context.Context) (*layerTree, error) {
9499
images, layers, err := r.getImagesAndLayers()
95100
if err != nil {
96101
return nil, err
97102
}
98-
return r.newLayerTreeFromData(images, layers)
103+
return r.newLayerTreeFromData(ctx, images, layers, false)
99104
}
100105

101106
// newLayerTreeFromData extracts a layerTree from the given the layers and images.
102107
// The caller is responsible for (layers, images) being consistent.
103-
func (r *Runtime) newLayerTreeFromData(images []*Image, layers []storage.Layer) (*layerTree, error) {
108+
func (r *Runtime) newLayerTreeFromData(ctx context.Context, images []*Image, layers []storage.Layer, generateManifestDigestList bool) (*layerTree, error) {
104109
tree := layerTree{
105-
nodes: make(map[string]*layerNode),
106-
ociCache: make(map[string]*ociv1.Image),
110+
nodes: make(map[string]*layerNode),
111+
ociCache: make(map[string]*ociv1.Image),
112+
manifestListDigests: make(map[digest.Digest]struct{}),
107113
}
108114

109115
// First build a tree purely based on layer information.
@@ -124,6 +130,21 @@ func (r *Runtime) newLayerTreeFromData(images []*Image, layers []storage.Layer)
124130
topLayer := img.TopLayer()
125131
if topLayer == "" {
126132
tree.emptyImages = append(tree.emptyImages, img)
133+
// When img is a manifest list, cache the lists of
134+
// digests refereenced in manifest list. Digests can
135+
// be used to check for dangling images.
136+
if !generateManifestDigestList {
137+
continue
138+
}
139+
if manifestList, _ := img.IsManifestList(ctx); manifestList {
140+
mlist, err := img.ToManifestList()
141+
if err != nil {
142+
return nil, err
143+
}
144+
for _, digest := range mlist.list.Instances() {
145+
tree.manifestListDigests[digest] = struct{}{}
146+
}
147+
}
127148
continue
128149
}
129150
node, exists := tree.nodes[topLayer]

libimage/runtime.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ func (r *Runtime) ListImages(ctx context.Context, options *ListImagesOptions) ([
634634

635635
var tree *layerTree
636636
if needsLayerTree {
637-
tree, err = r.newLayerTreeFromData(images, snapshot.Layers)
637+
tree, err = r.newLayerTreeFromData(ctx, images, snapshot.Layers, true)
638638
if err != nil {
639639
return nil, err
640640
}

0 commit comments

Comments
 (0)