From 3fc7551671fdd204c0e1f44a08b4f47713dd928e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Min=C3=A1=C5=99?= Date: Thu, 12 Oct 2017 13:51:00 +0200 Subject: [PATCH] image-pruning: derefence imagestreamtags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create strong references to images for each pod/bc/dc/etc that uses /:tag reference. Resolves [bz#1498604](https://bugzilla.redhat.com/show_bug.cgi?id=1498604) Signed-off-by: Michal Minář --- pkg/image/prune/prune.go | 188 ++++++++++++++++++++++------------ pkg/image/prune/prune_test.go | 64 +++++++++++- 2 files changed, 182 insertions(+), 70 deletions(-) diff --git a/pkg/image/prune/prune.go b/pkg/image/prune/prune.go index 8fc1e135f176..355fe0f14d83 100644 --- a/pkg/image/prune/prune.go +++ b/pkg/image/prune/prune.go @@ -240,28 +240,41 @@ func NewPruner(options PrunerOptions) (Pruner, error) { } algorithm.namespace = options.Namespace - g := graph.New() - addImagesToGraph(g, options.Images, algorithm) - addImageStreamsToGraph(g, options.Streams, options.LimitRanges, algorithm) - addPodsToGraph(g, options.Pods, algorithm) - addReplicationControllersToGraph(g, options.RCs) - if err := addBuildConfigsToGraph(g, options.BCs); err != nil { - return nil, err + p := &pruner{ + algorithm: algorithm, + registryClient: options.RegistryClient, + registryURL: options.RegistryURL, } - if err := addBuildsToGraph(g, options.Builds); err != nil { + + if err := p.buildGraph(options); err != nil { return nil, err } - addDaemonSetsToGraph(g, options.DSs) - addDeploymentsToGraph(g, options.Deployments) - addDeploymentConfigsToGraph(g, options.DCs) - addReplicaSetsToGraph(g, options.RSs) - return &pruner{ - g: g, - algorithm: algorithm, - registryClient: options.RegistryClient, - registryURL: options.RegistryURL, - }, nil + return p, nil +} + +// buildGraph builds a graph +func (p *pruner) buildGraph(options PrunerOptions) error { + p.g = graph.New() + + p.addImagesToGraph(options.Images) + p.addImageStreamsToGraph(options.Streams, options.LimitRanges) + p.addPodsToGraph(options.Pods) + p.addReplicationControllersToGraph(options.RCs) + if err := p.addBuildConfigsToGraph(options.BCs); err != nil { + // TODO: either make the other methods error out on image reference parsing or ignore this error + return err + } + if err := p.addBuildsToGraph(options.Builds); err != nil { + // TODO: either make the other methods error out on image reference parsing or ignore this error + return err + } + p.addDaemonSetsToGraph(options.DSs) + p.addDeploymentsToGraph(options.Deployments) + p.addDeploymentConfigsToGraph(options.DCs) + p.addReplicaSetsToGraph(options.RSs) + + return nil } func getValue(option interface{}) string { @@ -275,24 +288,24 @@ func getValue(option interface{}) string { // registries in the algorithm and are at least as old as the minimum age // threshold as specified by the algorithm. It also adds all the images' layers // to the graph. -func addImagesToGraph(g graph.Graph, images *imageapi.ImageList, algorithm pruneAlgorithm) { +func (p *pruner) addImagesToGraph(images *imageapi.ImageList) { for i := range images.Items { image := &images.Items[i] glog.V(4).Infof("Adding image %q to graph", image.Name) - imageNode := imagegraph.EnsureImageNode(g, image) + imageNode := imagegraph.EnsureImageNode(p.g, image) if image.DockerImageManifestMediaType == schema2.MediaTypeManifest && len(image.DockerImageMetadata.ID) > 0 { configName := image.DockerImageMetadata.ID glog.V(4).Infof("Adding image config %q to graph", configName) - configNode := imagegraph.EnsureImageComponentConfigNode(g, configName) - g.AddEdge(imageNode, configNode, ReferencedImageConfigEdgeKind) + configNode := imagegraph.EnsureImageComponentConfigNode(p.g, configName) + p.g.AddEdge(imageNode, configNode, ReferencedImageConfigEdgeKind) } for _, layer := range image.DockerImageLayers { glog.V(4).Infof("Adding image layer %q to graph", layer.Name) - layerNode := imagegraph.EnsureImageComponentLayerNode(g, layer.Name) - g.AddEdge(imageNode, layerNode, ReferencedImageLayerEdgeKind) + layerNode := imagegraph.EnsureImageComponentLayerNode(p.g, layer.Name) + p.g.AddEdge(imageNode, layerNode, ReferencedImageLayerEdgeKind) } } } @@ -310,7 +323,7 @@ func addImagesToGraph(g graph.Graph, images *imageapi.ImageList, algorithm prune // // addImageStreamsToGraph also adds references from each stream to all the // layers it references (via each image a stream references). -func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, limits map[string][]*kapi.LimitRange, algorithm pruneAlgorithm) { +func (p *pruner) addImageStreamsToGraph(streams *imageapi.ImageStreamList, limits map[string][]*kapi.LimitRange) { for i := range streams.Items { stream := &streams.Items[i] @@ -319,18 +332,20 @@ func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, li // use a weak reference for old image revisions by default oldImageRevisionReferenceKind := WeakReferencedImageEdgeKind - if !algorithm.pruneOverSizeLimit && stream.CreationTimestamp.Time.After(algorithm.keepYoungerThan) { + if !p.algorithm.pruneOverSizeLimit && stream.CreationTimestamp.Time.After(p.algorithm.keepYoungerThan) { // stream's age is below threshold - use a strong reference for old image revisions instead oldImageRevisionReferenceKind = ReferencedImageEdgeKind } glog.V(4).Infof("Adding ImageStream %s to graph", getName(stream)) - isNode := imagegraph.EnsureImageStreamNode(g, stream) + isNode := imagegraph.EnsureImageStreamNode(p.g, stream) imageStreamNode := isNode.(*imagegraph.ImageStreamNode) for tag, history := range stream.Status.Tags { + istNode := imagegraph.EnsureImageStreamTagNode(p.g, makeISTagWithStream(stream, tag)) + for i := range history.Items { - n := imagegraph.FindImage(g, history.Items[i].Image) + n := imagegraph.FindImage(p.g, history.Items[i].Image) if n == nil { glog.V(2).Infof("Unable to find image %q in graph (from tag=%q, revision=%d, dockerImageReference=%s) - skipping", history.Items[i].Image, tag, i, history.Items[i].DockerImageReference) @@ -339,30 +354,35 @@ func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, li imageNode := n.(*imagegraph.ImageNode) kind := oldImageRevisionReferenceKind - if algorithm.pruneOverSizeLimit { + if p.algorithm.pruneOverSizeLimit { if exceedsLimits(stream, imageNode.Image, limits) { kind = WeakReferencedImageEdgeKind } else { kind = ReferencedImageEdgeKind } } else { - if i < algorithm.keepTagRevisions { + if i < p.algorithm.keepTagRevisions { kind = ReferencedImageEdgeKind } } + if i == 0 { + glog.V(4).Infof("Adding edge (kind=%s) from %q to %q", kind, istNode.UniqueName(), imageNode.UniqueName()) + p.g.AddEdge(istNode, imageNode, kind) + } + glog.V(4).Infof("Checking for existing strong reference from stream %s to image %s", getName(stream), imageNode.Image.Name) - if edge := g.Edge(imageStreamNode, imageNode); edge != nil && g.EdgeKinds(edge).Has(ReferencedImageEdgeKind) { + if edge := p.g.Edge(imageStreamNode, imageNode); edge != nil && p.g.EdgeKinds(edge).Has(ReferencedImageEdgeKind) { glog.V(4).Infof("Strong reference found") continue } glog.V(4).Infof("Adding edge (kind=%s) from %q to %q", kind, imageStreamNode.UniqueName(), imageNode.UniqueName()) - g.AddEdge(imageStreamNode, imageNode, kind) + p.g.AddEdge(imageStreamNode, imageNode, kind) glog.V(4).Infof("Adding stream->(layer|config) references") // add stream -> layer references so we can prune them later - for _, s := range g.From(imageNode) { + for _, s := range p.g.From(imageNode) { cn, ok := s.(*imagegraph.ImageComponentNode) if !ok { continue @@ -370,9 +390,9 @@ func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, li glog.V(4).Infof("Adding reference from stream %s to %s", getName(stream), cn.Describe()) if cn.Type == imagegraph.ImageComponentTypeConfig { - g.AddEdge(imageStreamNode, s, ReferencedImageConfigEdgeKind) + p.g.AddEdge(imageStreamNode, s, ReferencedImageConfigEdgeKind) } else { - g.AddEdge(imageStreamNode, s, ReferencedImageLayerEdgeKind) + p.g.AddEdge(imageStreamNode, s, ReferencedImageLayerEdgeKind) } } } @@ -416,7 +436,7 @@ func exceedsLimits(is *imageapi.ImageStream, image *imageapi.Image, limits map[s // // Edges are added to the graph from each pod to the images specified by that // pod's list of containers, as long as the image is managed by OpenShift. -func addPodsToGraph(g graph.Graph, pods *kapi.PodList, algorithm pruneAlgorithm) { +func (p *pruner) addPodsToGraph(pods *kapi.PodList) { for i := range pods.Items { pod := &pods.Items[i] @@ -426,23 +446,23 @@ func addPodsToGraph(g graph.Graph, pods *kapi.PodList, algorithm pruneAlgorithm) // pending or running. Additionally, it has to be at least as old as the minimum // age threshold defined by the algorithm. if pod.Status.Phase != kapi.PodRunning && pod.Status.Phase != kapi.PodPending { - if !pod.CreationTimestamp.Time.After(algorithm.keepYoungerThan) { + if !pod.CreationTimestamp.Time.After(p.algorithm.keepYoungerThan) { glog.V(4).Infof("Pod %s is neither running nor pending and is too old", getName(pod)) continue } } glog.V(4).Infof("Adding pod %s to graph", getName(pod)) - podNode := kubegraph.EnsurePodNode(g, pod) + podNode := kubegraph.EnsurePodNode(p.g, pod) - addPodSpecToGraph(g, &pod.Spec, podNode) + p.addPodSpecToGraph(&pod.Spec, podNode) } } // Edges are added to the graph from each predecessor (pod or replication // controller) to the images specified by the pod spec's list of containers, as // long as the image is managed by OpenShift. -func addPodSpecToGraph(g graph.Graph, spec *kapi.PodSpec, predecessor gonum.Node) { +func (p *pruner) addPodSpecToGraph(spec *kapi.PodSpec, predecessor gonum.Node) { for j := range spec.Containers { container := spec.Containers[j] @@ -455,18 +475,36 @@ func addPodSpecToGraph(g graph.Graph, spec *kapi.PodSpec, predecessor gonum.Node } if len(ref.ID) == 0 { + ref = ref.DockerClientDefaults() + if p.registryURL.Host != ref.RegistryURL().Host { + glog.V(4).Infof("%q has third party registry host name (%q != %q) - skipping", container.Image, ref.RegistryURL().Host, p.registryURL.Host) + continue + } glog.V(4).Infof("%q has no image ID", container.Image) + node := p.g.Find(imagegraph.ImageStreamTagNodeName(makeISTagWithReference(&ref))) + if node == nil { + glog.V(4).Infof("No image stream tag found for %q - skipping", container.Image) + continue + } + for _, n := range p.g.From(node) { + imgNode, ok := n.(*imagegraph.ImageNode) + if !ok { + continue + } + glog.V(4).Infof("Adding edge from pod to image %q referenced by %s:%s", imgNode.Image.Name, ref.RepositoryName(), ref.Tag) + p.g.AddEdge(predecessor, imgNode, ReferencedImageEdgeKind) + } continue } - imageNode := imagegraph.FindImage(g, ref.ID) + imageNode := imagegraph.FindImage(p.g, ref.ID) if imageNode == nil { glog.V(2).Infof("Unable to find image %q in the graph - skipping", ref.ID) continue } glog.V(4).Infof("Adding edge from pod to image") - g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) + p.g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) } } @@ -475,12 +513,12 @@ func addPodSpecToGraph(g graph.Graph, spec *kapi.PodSpec, predecessor gonum.Node // Edges are added to the graph from each replication controller to the images // specified by its pod spec's list of containers, as long as the image is // managed by OpenShift. -func addReplicationControllersToGraph(g graph.Graph, rcs *kapi.ReplicationControllerList) { +func (p *pruner) addReplicationControllersToGraph(rcs *kapi.ReplicationControllerList) { for i := range rcs.Items { rc := &rcs.Items[i] glog.V(4).Infof("Examining replication controller %s", getName(rc)) - rcNode := kubegraph.EnsureReplicationControllerNode(g, rc) - addPodSpecToGraph(g, &rc.Spec.Template.Spec, rcNode) + rcNode := kubegraph.EnsureReplicationControllerNode(p.g, rc) + p.addPodSpecToGraph(&rc.Spec.Template.Spec, rcNode) } } @@ -488,12 +526,12 @@ func addReplicationControllersToGraph(g graph.Graph, rcs *kapi.ReplicationContro // // Edges are added to the graph from each daemon set to the images specified by its pod spec's list of // containers, as long as the image is managed by OpenShift. -func addDaemonSetsToGraph(g graph.Graph, dss *kapisext.DaemonSetList) { +func (p *pruner) addDaemonSetsToGraph(dss *kapisext.DaemonSetList) { for i := range dss.Items { ds := &dss.Items[i] glog.V(4).Infof("Examining DaemonSet %s", getName(ds)) - dsNode := deploygraph.EnsureDaemonSetNode(g, ds) - addPodSpecToGraph(g, &ds.Spec.Template.Spec, dsNode) + dsNode := deploygraph.EnsureDaemonSetNode(p.g, ds) + p.addPodSpecToGraph(&ds.Spec.Template.Spec, dsNode) } } @@ -501,12 +539,12 @@ func addDaemonSetsToGraph(g graph.Graph, dss *kapisext.DaemonSetList) { // // Edges are added to the graph from each deployment to the images specified by its pod spec's list of // containers, as long as the image is managed by OpenShift. -func addDeploymentsToGraph(g graph.Graph, dmnts *kapisext.DeploymentList) { +func (p *pruner) addDeploymentsToGraph(dmnts *kapisext.DeploymentList) { for i := range dmnts.Items { d := &dmnts.Items[i] glog.V(4).Infof("Examining Deployment %s", getName(d)) - dNode := deploygraph.EnsureDeploymentNode(g, d) - addPodSpecToGraph(g, &d.Spec.Template.Spec, dNode) + dNode := deploygraph.EnsureDeploymentNode(p.g, d) + p.addPodSpecToGraph(&d.Spec.Template.Spec, dNode) } } @@ -515,12 +553,12 @@ func addDeploymentsToGraph(g graph.Graph, dmnts *kapisext.DeploymentList) { // Edges are added to the graph from each deployment config to the images // specified by its pod spec's list of containers, as long as the image is // managed by OpenShift. -func addDeploymentConfigsToGraph(g graph.Graph, dcs *deployapi.DeploymentConfigList) { +func (p *pruner) addDeploymentConfigsToGraph(dcs *deployapi.DeploymentConfigList) { for i := range dcs.Items { dc := &dcs.Items[i] glog.V(4).Infof("Examining DeploymentConfig %s", getName(dc)) - dcNode := deploygraph.EnsureDeploymentConfigNode(g, dc) - addPodSpecToGraph(g, &dc.Spec.Template.Spec, dcNode) + dcNode := deploygraph.EnsureDeploymentConfigNode(p.g, dc) + p.addPodSpecToGraph(&dc.Spec.Template.Spec, dcNode) } } @@ -528,24 +566,24 @@ func addDeploymentConfigsToGraph(g graph.Graph, dcs *deployapi.DeploymentConfigL // // Edges are added to the graph from each replica set to the images specified by its pod spec's list of // containers, as long as the image is managed by OpenShift. -func addReplicaSetsToGraph(g graph.Graph, rss *kapisext.ReplicaSetList) { +func (p *pruner) addReplicaSetsToGraph(rss *kapisext.ReplicaSetList) { for i := range rss.Items { rs := &rss.Items[i] glog.V(4).Infof("Examining ReplicaSet %s", getName(rs)) - rsNode := deploygraph.EnsureReplicaSetNode(g, rs) - addPodSpecToGraph(g, &rs.Spec.Template.Spec, rsNode) + rsNode := deploygraph.EnsureReplicaSetNode(p.g, rs) + p.addPodSpecToGraph(&rs.Spec.Template.Spec, rsNode) } } // addBuildConfigsToGraph adds build configs to the graph. // // Edges are added to the graph from each build config to the image specified by its strategy.from. -func addBuildConfigsToGraph(g graph.Graph, bcs *buildapi.BuildConfigList) error { +func (p *pruner) addBuildConfigsToGraph(bcs *buildapi.BuildConfigList) error { for i := range bcs.Items { bc := &bcs.Items[i] glog.V(4).Infof("Examining BuildConfig %s", getName(bc)) - bcNode := buildgraph.EnsureBuildConfigNode(g, bc) - if err := addBuildStrategyImageReferencesToGraph(g, bc.Spec.Strategy, bcNode); err != nil { + bcNode := buildgraph.EnsureBuildConfigNode(p.g, bc) + if err := p.addBuildStrategyImageReferencesToGraph(bc.Spec.Strategy, bcNode); err != nil { return fmt.Errorf("unable to add BuildConfig %s to graph: %v", getName(bc), err) } } @@ -555,12 +593,12 @@ func addBuildConfigsToGraph(g graph.Graph, bcs *buildapi.BuildConfigList) error // addBuildsToGraph adds builds to the graph. // // Edges are added to the graph from each build to the image specified by its strategy.from. -func addBuildsToGraph(g graph.Graph, builds *buildapi.BuildList) error { +func (p *pruner) addBuildsToGraph(builds *buildapi.BuildList) error { for i := range builds.Items { build := &builds.Items[i] glog.V(4).Infof("Examining build %s", getName(build)) - buildNode := buildgraph.EnsureBuildNode(g, build) - if err := addBuildStrategyImageReferencesToGraph(g, build.Spec.Strategy, buildNode); err != nil { + buildNode := buildgraph.EnsureBuildNode(p.g, build) + if err := p.addBuildStrategyImageReferencesToGraph(build.Spec.Strategy, buildNode); err != nil { return fmt.Errorf("unable to add Build %s to graph: %v", getName(build), err) } } @@ -573,7 +611,7 @@ func addBuildsToGraph(g graph.Graph, builds *buildapi.BuildList) error { // Edges are added to the graph from each predecessor (build or build config) // to the image specified by strategy.from, as long as the image is managed by // OpenShift. -func addBuildStrategyImageReferencesToGraph(g graph.Graph, strategy buildapi.BuildStrategy, predecessor gonum.Node) error { +func (p *pruner) addBuildStrategyImageReferencesToGraph(strategy buildapi.BuildStrategy, predecessor gonum.Node) error { from := buildapi.GetInputReference(strategy) if from == nil { glog.V(4).Infof("Unable to determine 'from' reference - skipping") @@ -603,14 +641,14 @@ func addBuildStrategyImageReferencesToGraph(g graph.Graph, strategy buildapi.Bui } glog.V(4).Infof("Looking for image %q in graph", imageID) - imageNode := imagegraph.FindImage(g, imageID) + imageNode := imagegraph.FindImage(p.g, imageID) if imageNode == nil { glog.V(4).Infof("Unable to find image %q in graph - skipping", imageID) return nil } glog.V(4).Infof("Adding edge from %v to %v", predecessor, imageNode) - g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) + p.g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) return nil } @@ -1166,3 +1204,21 @@ func getName(obj runtime.Object) string { } return fmt.Sprintf("%s/%s", accessor.GetNamespace(), accessor.GetName()) } + +func makeISTag(namespace, name, tag string) *imageapi.ImageStreamTag { + return &imageapi.ImageStreamTag{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: imageapi.JoinImageStreamTag(name, tag), + }, + } +} + +func makeISTagWithStream(is *imageapi.ImageStream, tag string) *imageapi.ImageStreamTag { + return makeISTag(is.Namespace, is.Name, tag) +} + +func makeISTagWithReference(ref *imageapi.DockerImageReference) *imageapi.ImageStreamTag { + defaultedRef := ref.DockerClientDefaults() + return makeISTag(defaultedRef.Namespace, defaultedRef.Name, defaultedRef.Tag) +} diff --git a/pkg/image/prune/prune_test.go b/pkg/image/prune/prune_test.go index 761b06433d64..6a84b13f50ee 100644 --- a/pkg/image/prune/prune_test.go +++ b/pkg/image/prune/prune_test.go @@ -533,6 +533,7 @@ func TestImagePruning(t *testing.T) { tests := map[string]struct { pruneOverSizeLimit *bool allImages *bool + keepTagRevisions *int namespace string images imageapi.ImageList pods kapi.PodList @@ -911,6 +912,54 @@ func TestImagePruning(t *testing.T) { expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000002"}, }, + "images referenced by istag - keep": { + keepTagRevisions: keepTagRevisions(0), + images: imageList( + image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005"), + ), + streams: streamList( + stream(registryHost, "foo", "bar", tags( + tag("latest", + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005"), + ), + tag("dummy", + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005"), + ), + )), + stream(registryHost, "foo", "baz", tags( + tag("late", + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + ), + )), + ), + dss: dsList(ds("nm", "dsfoo", fmt.Sprintf("%s/%s/%s:%s", registryHost, "foo", "bar", "latest"))), + dcs: dcList(dc("nm", "dcfoo", fmt.Sprintf("%s/%s/%s:%s", "remote.registry:5000", "foo", "bar", "dummy"))), + rss: rsList(rs("nm", "rsfoo", fmt.Sprintf("%s/%s/%s:%s", registryHost, "foo", "baz", "late"))), + expectedImageDeletions: []string{ + "sha256:0000000000000000000000000000000000000000000000000000000000000001", + "sha256:0000000000000000000000000000000000000000000000000000000000000003", + "sha256:0000000000000000000000000000000000000000000000000000000000000004", + "sha256:0000000000000000000000000000000000000000000000000000000000000005", + }, + expectedStreamUpdates: []string{ + "foo/bar:dummy", + "foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000000", + "foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000001", + "foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003", + "foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004", + "foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000005", + }, + }, + "multiple resources pointing to image - don't prune": { images: imageList( image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), @@ -1244,10 +1293,13 @@ func TestImagePruning(t *testing.T) { if test.pruneOverSizeLimit != nil { options.PruneOverSizeLimit = test.pruneOverSizeLimit } else { - keepYoungerThan := 60 * time.Minute - keepTagRevisions := 3 - options.KeepYoungerThan = &keepYoungerThan - options.KeepTagRevisions = &keepTagRevisions + youngerThan := 60 * time.Minute + tagRevisions := 3 + if test.keepTagRevisions != nil { + tagRevisions = *test.keepTagRevisions + } + options.KeepYoungerThan = &youngerThan + options.KeepTagRevisions = &tagRevisions } p, err := NewPruner(options) if err != nil { @@ -1608,3 +1660,7 @@ func TestImageIsPrunable(t *testing.T) { t.Fatalf("Image is prunable although it should not") } } + +func keepTagRevisions(n int) *int { + return &n +}