From cc695abb3b196e0b00d028b4816d0e33d41a4aa4 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/graph/nodes/nodes.go | 8 +- pkg/image/prune/helper.go | 59 + pkg/image/prune/prune.go | 354 ++++-- pkg/image/prune/prune_test.go | 1796 +++++++++++++---------------- pkg/image/prune/testutil/util.go | 444 +++++++ pkg/oc/admin/prune/images.go | 135 ++- pkg/oc/admin/prune/images_test.go | 139 +++ pkg/oc/admin/top/graph.go | 5 +- 8 files changed, 1803 insertions(+), 1137 deletions(-) create mode 100644 pkg/image/prune/testutil/util.go diff --git a/pkg/image/graph/nodes/nodes.go b/pkg/image/graph/nodes/nodes.go index bb355336aa21..7a252be4673c 100644 --- a/pkg/image/graph/nodes/nodes.go +++ b/pkg/image/graph/nodes/nodes.go @@ -36,8 +36,12 @@ func EnsureAllImageStreamTagNodes(g osgraph.MutableUniqueGraph, is *imageapi.Ima return ret } -func FindImage(g osgraph.MutableUniqueGraph, imageName string) graph.Node { - return g.Find(ImageNodeName(&imageapi.Image{ObjectMeta: metav1.ObjectMeta{Name: imageName}})) +func FindImage(g osgraph.MutableUniqueGraph, imageName string) *ImageNode { + n := g.Find(ImageNodeName(&imageapi.Image{ObjectMeta: metav1.ObjectMeta{Name: imageName}})) + if imageNode, ok := n.(*ImageNode); ok { + return imageNode + } + return nil } // EnsureDockerRepositoryNode adds the named Docker repository tag reference to the graph if it does diff --git a/pkg/image/prune/helper.go b/pkg/image/prune/helper.go index 57380919bdaf..ed49cdb7ec88 100644 --- a/pkg/image/prune/helper.go +++ b/pkg/image/prune/helper.go @@ -7,6 +7,8 @@ import ( "sort" "strings" + kapi "k8s.io/kubernetes/pkg/api" + "github.com/docker/distribution/registry/api/errcode" "github.com/golang/glog" @@ -206,3 +208,60 @@ func TryProtocolsWithRegistryURL(registry string, allowInsecure bool, action fun type retryPath struct{ err error } func (rp *retryPath) Error() string { return rp.err.Error() } + +// ErrBadReference denotes an invalid reference to image, imagestreamtag or imagestreamimage stored in a +// particular object. The object is identified by kind, namespace and name. +type ErrBadReference struct { + kind string + namespace string + name string + targetKind string + reference string + reason string +} + +func newErrBadReferenceToImage(reference string, obj *kapi.ObjectReference, reason string) error { + kind := "" + namespace := "" + name := "" + if obj != nil { + kind = obj.Kind + namespace = obj.Namespace + name = obj.Name + } + + return &ErrBadReference{ + kind: kind, + namespace: namespace, + name: name, + reference: reference, + reason: reason, + } +} + +func newErrBadReferenceTo(targetKind, reference string, obj *kapi.ObjectReference, reason string) error { + return &ErrBadReference{ + kind: obj.Kind, + namespace: obj.Namespace, + name: obj.Name, + targetKind: targetKind, + reference: reference, + reason: reason, + } +} + +func (e *ErrBadReference) Error() string { + return e.String() +} + +func (e *ErrBadReference) String() string { + name := e.name + if len(e.namespace) > 0 { + name = e.namespace + "/" + name + } + targetKind := "docker image" + if len(e.targetKind) > 0 { + targetKind = e.targetKind + } + return fmt.Sprintf("%s[%s]: invalid %s reference %q: %s", e.kind, name, targetKind, e.reference, e.reason) +} diff --git a/pkg/image/prune/prune.go b/pkg/image/prune/prune.go index 5c7180b1cc81..fb7d93e80662 100644 --- a/pkg/image/prune/prune.go +++ b/pkg/image/prune/prune.go @@ -21,6 +21,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" kapi "k8s.io/kubernetes/pkg/api" + kapiref "k8s.io/kubernetes/pkg/api/ref" kapisext "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/retry" @@ -220,7 +221,7 @@ var _ Pruner = &pruner{} // // Also automatically remove any image layer that is no longer referenced by any // images. -func NewPruner(options PrunerOptions) (Pruner, error) { +func NewPruner(options PrunerOptions) (Pruner, kerrors.Aggregate) { glog.V(1).Infof("Creating image pruner with keepYoungerThan=%v, keepTagRevisions=%s, pruneOverSizeLimit=%s, allImages=%s", options.KeepYoungerThan, getValue(options.KeepTagRevisions), getValue(options.PruneOverSizeLimit), getValue(options.AllImages)) @@ -240,28 +241,37 @@ 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) kerrors.Aggregate { + p.g = graph.New() + + var errs []error + + errs = append(errs, p.addImagesToGraph(options.Images)...) + errs = append(errs, p.addImageStreamsToGraph(options.Streams, options.LimitRanges)...) + errs = append(errs, p.addPodsToGraph(options.Pods)...) + errs = append(errs, p.addReplicationControllersToGraph(options.RCs)...) + errs = append(errs, p.addBuildConfigsToGraph(options.BCs)...) + errs = append(errs, p.addBuildsToGraph(options.Builds)...) + errs = append(errs, p.addDaemonSetsToGraph(options.DSs)...) + errs = append(errs, p.addDeploymentsToGraph(options.Deployments)...) + errs = append(errs, p.addDeploymentConfigsToGraph(options.DCs)...) + errs = append(errs, p.addReplicaSetsToGraph(options.RSs)...) + + return kerrors.NewAggregate(errs) } func getValue(option interface{}) string { @@ -275,26 +285,28 @@ 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) []error { 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) } } + + return nil } // addImageStreamsToGraph adds all the streams to the graph. The most recent n @@ -310,7 +322,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) []error { for i := range streams.Items { stream := &streams.Items[i] @@ -319,50 +331,56 @@ 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) - if n == nil { + imageNode := imagegraph.FindImage(p.g, history.Items[i].Image) + if imageNode == 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) continue } - 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,14 +388,16 @@ 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) } } } } } + + return nil } // exceedsLimits checks if given image exceeds LimitRanges defined in ImageStream's namespace. @@ -416,33 +436,40 @@ 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) []error { + var errs []error + for i := range pods.Items { pod := &pods.Items[i] - glog.V(4).Infof("Examining pod %s", getName(pod)) + desc := fmt.Sprintf("Pod %s", getName(pod)) + glog.V(4).Infof("Examining %s", desc) // A pod is only *excluded* from being added to the graph if its phase is not // 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) { - glog.V(4).Infof("Ignoring pod %s for image reference counting because it's not running/pending and is too old", getName(pod)) + if !pod.CreationTimestamp.Time.After(p.algorithm.keepYoungerThan) { + glog.V(4).Infof("Ignoring %s for image reference counting because it's not running/pending and is too old", desc) continue } } - glog.V(4).Infof("Adding pod %s to graph", getName(pod)) - podNode := kubegraph.EnsurePodNode(g, pod) + glog.V(4).Infof("Adding %s to graph", desc) + podNode := kubegraph.EnsurePodNode(p.g, pod) - addPodSpecToGraph(g, &pod.Spec, podNode) + errs = append(errs, p.addPodSpecToGraph(getRef(pod), &pod.Spec, podNode)...) } + + return errs } // 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(referrer *kapi.ObjectReference, spec *kapi.PodSpec, predecessor gonum.Node) []error { + var errs []error + for j := range spec.Containers { container := spec.Containers[j] @@ -450,24 +477,47 @@ func addPodSpecToGraph(g graph.Graph, spec *kapi.PodSpec, predecessor gonum.Node ref, err := imageapi.ParseDockerImageReference(container.Image) if err != nil { - glog.V(2).Infof("Unable to parse DockerImageReference %q: %v - skipping", container.Image, err) + glog.V(4).Infof("Unable to parse DockerImageReference %q of %s: %v - skipping", container.Image, getKindName(referrer), err) + errs = append(errs, newErrBadReferenceToImage(container.Image, referrer, err.Error())) continue } if len(ref.ID) == 0 { + // Attempt to dereference istag. Since we cannot be sure whether the reference refers to the + // integrated registry or not, we ignore the host part completely. As a consequence, we may keep + // image otherwise sentenced for a removal just because its pull spec accidentally matches one of + // our imagestreamtags. + + // set the tag if empty + ref = ref.DockerClientDefaults() glog.V(4).Infof("%q has no image ID", container.Image) + node := p.g.Find(imagegraph.ImageStreamTagNodeName(makeISTag(ref.Namespace, ref.Name, ref.Tag))) + 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) + glog.V(2).Infof("Unable to find image %q referenced by %s in the graph - skipping", ref.ID, getKindName(referrer)) continue } - glog.V(4).Infof("Adding edge from pod to image") - g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) + glog.V(4).Infof("Adding edge from %s to image %v", getKindName(referrer), imageNode) + p.g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) } + + return errs } // addReplicationControllersToGraph adds replication controllers to the graph. @@ -475,39 +525,54 @@ 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) []error { + var errs []error + 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) + desc := fmt.Sprintf("ReplicationController %s", getName(rc)) + glog.V(4).Infof("Examining %s", desc) + rcNode := kubegraph.EnsureReplicationControllerNode(p.g, rc) + errs = append(errs, p.addPodSpecToGraph(getRef(rc), &rc.Spec.Template.Spec, rcNode)...) } + + return errs } // addDaemonSetsToGraph adds daemon set to the graph. // // 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) []error { + var errs []error + 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) + desc := fmt.Sprintf("DaemonSet %s", getName(ds)) + glog.V(4).Infof("Examining %s", desc) + dsNode := deploygraph.EnsureDaemonSetNode(p.g, ds) + errs = append(errs, p.addPodSpecToGraph(getRef(ds), &ds.Spec.Template.Spec, dsNode)...) } + + return errs } // addDeploymentsToGraph adds kube's deployments to the graph. // // 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) []error { + var errs []error + 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) + ref := getRef(d) + glog.V(4).Infof("Examining %s", getKindName(ref)) + dNode := deploygraph.EnsureDeploymentNode(p.g, d) + errs = append(errs, p.addPodSpecToGraph(ref, &d.Spec.Template.Spec, dNode)...) } + + return errs } // addDeploymentConfigsToGraph adds deployment configs to the graph. @@ -515,56 +580,70 @@ 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) []error { + var errs []error + 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) + ref := getRef(dc) + glog.V(4).Infof("Examining %s", getKindName(ref)) + dcNode := deploygraph.EnsureDeploymentConfigNode(p.g, dc) + errs = append(errs, p.addPodSpecToGraph(getRef(dc), &dc.Spec.Template.Spec, dcNode)...) } + + return errs } // addReplicaSetsToGraph adds replica set to the graph. // // 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) []error { + var errs []error + 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) + ref := getRef(rs) + glog.V(4).Infof("Examining %s", getKindName(ref)) + rsNode := deploygraph.EnsureReplicaSetNode(p.g, rs) + errs = append(errs, p.addPodSpecToGraph(ref, &rs.Spec.Template.Spec, rsNode)...) } + + return errs } // 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 { + var errs []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 { - return fmt.Errorf("unable to add BuildConfig %s to graph: %v", getName(bc), err) - } + ref := getRef(bc) + glog.V(4).Infof("Examining %s", getKindName(ref)) + bcNode := buildgraph.EnsureBuildConfigNode(p.g, bc) + errs = append(errs, p.addBuildStrategyImageReferencesToGraph(ref, bc.Spec.Strategy, bcNode)...) } - return nil + + return errs } // 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 { + var errs []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 { - return fmt.Errorf("unable to add Build %s to graph: %v", getName(build), err) - } + ref := getRef(build) + glog.V(4).Infof("Examining %s", getKindName(ref)) + buildNode := buildgraph.EnsureBuildNode(p.g, build) + errs = append(errs, p.addBuildStrategyImageReferencesToGraph(ref, build.Spec.Strategy, buildNode)...) } - return nil + + return errs } // addBuildStrategyImageReferencesToGraph ads references from the build strategy's parent node to the image @@ -573,7 +652,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(referrer *kapi.ObjectReference, strategy buildapi.BuildStrategy, predecessor gonum.Node) []error { from := buildapi.GetInputReference(strategy) if from == nil { glog.V(4).Infof("Unable to determine 'from' reference - skipping") @@ -585,32 +664,61 @@ func addBuildStrategyImageReferencesToGraph(g graph.Graph, strategy buildapi.Bui var imageID string switch from.Kind { + case "DockerImage": + ref, err := imageapi.ParseDockerImageReference(from.Name) + if err != nil { + msg := fmt.Sprintf("failed to parse DockerImage name %q of %s: %v", from.Name, getKindName(referrer), err) + glog.V(4).Infof(msg) + return []error{newErrBadReferenceToImage(from.Name, referrer, err.Error())} + } + imageID = ref.ID + case "ImageStreamImage": _, id, err := imageapi.ParseImageStreamImageName(from.Name) if err != nil { - return fmt.Errorf("error parsing ImageStreamImage name %q: %v", from.Name, err) + msg := fmt.Sprintf("failed to parse ImageStreamImage name %q of %s: %v", from.Name, getKindName(referrer), err) + glog.V(4).Infof(msg) + return []error{newErrBadReferenceTo("ImageStreamImage", from.Name, referrer, err.Error())} } imageID = id - case "DockerImage": - ref, err := imageapi.ParseDockerImageReference(from.Name) + + case "ImageStreamTag": + istNode, err := resolveISTagName(p.g, referrer, from.Name) if err != nil { - return fmt.Errorf("error parsing DockerImage name %q: %v", from.Name, err) + glog.V(4).Infof(err.Error()) + return []error{err} } - imageID = ref.ID + if istNode == nil { + glog.V(2).Infof("%s referenced by %s could not be found", getKindName(from), getKindName(referrer)) + return nil + } + for _, n := range p.g.From(istNode) { + imgNode, ok := n.(*imagegraph.ImageNode) + if !ok { + continue + } + imageID = imgNode.Image.Name + break + } + if len(imageID) == 0 { + glog.V(4).Infof("No image referenced by %s found", getKindName(from)) + return nil + } + default: - glog.V(4).Infof("Unknown kind for name %q: %v", from.Name, from.Kind) + glog.V(4).Infof("Ignoring unrecognized source location %q in %s", getKindName(from), getKindName(referrer)) return nil } 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) + glog.V(2).Infof("Unable to find image %q in graph referenced by %s - skipping", imageID, getKindName(referrer)) return nil } - glog.V(4).Infof("Adding edge from %v to %v", predecessor, imageNode) - g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) + glog.V(4).Infof("Adding edge from %s to image %s", predecessor, imageNode.Image.Name) + p.g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) return nil } @@ -1164,5 +1272,57 @@ func getName(obj runtime.Object) string { glog.V(4).Infof("Error getting accessor for %#v", obj) return "" } - return fmt.Sprintf("%s/%s", accessor.GetNamespace(), accessor.GetName()) + ns := accessor.GetNamespace() + if len(ns) == 0 { + return accessor.GetName() + } + return fmt.Sprintf("%s/%s", ns, accessor.GetName()) +} + +func getKindName(obj *kapi.ObjectReference) string { + if obj == nil { + return "unknown object" + } + name := obj.Name + if len(obj.Namespace) > 0 { + name = obj.Namespace + "/" + name + } + return fmt.Sprintf("%s[%s]", obj.Kind, name) +} + +func getRef(obj runtime.Object) *kapi.ObjectReference { + ref, err := kapiref.GetReference(kapi.Scheme, obj) + if err != nil { + glog.Errorf("failed to get reference to object %T: %v", obj, err) + return nil + } + return ref +} + +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) +} + +// resolveISTagName parses ImageStreamTag's name and tries to find it in the graph. If the parsing fails, +// an error is returned. If the istag cannot be found, nil is returned. +func resolveISTagName(g graph.Graph, referrer *kapi.ObjectReference, istagName string) (*imagegraph.ImageStreamTagNode, error) { + name, tag, err := imageapi.ParseImageStreamTagName(istagName) + if err != nil { + return nil, newErrBadReferenceTo("ImageStreamTag", istagName, referrer, err.Error()) + } + node := g.Find(imagegraph.ImageStreamTagNodeName(makeISTag(referrer.Namespace, name, tag))) + if istNode, ok := node.(*imagegraph.ImageStreamTagNode); ok { + return istNode, nil + } + + return nil, nil } diff --git a/pkg/image/prune/prune_test.go b/pkg/image/prune/prune_test.go index 761b06433d64..3e5e2125318b 100644 --- a/pkg/image/prune/prune_test.go +++ b/pkg/image/prune/prune_test.go @@ -11,10 +11,6 @@ import ( "testing" "time" - "github.com/docker/distribution/manifest/schema1" - "github.com/docker/distribution/manifest/schema2" - - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" @@ -30,509 +26,27 @@ import ( imageapi "github.com/openshift/origin/pkg/image/apis/image" imageclient "github.com/openshift/origin/pkg/image/generated/internalclientset/fake" imagegraph "github.com/openshift/origin/pkg/image/graph/nodes" -) - -func imageList(images ...imageapi.Image) imageapi.ImageList { - return imageapi.ImageList{ - Items: images, - } -} - -const ( - layer1 = "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - layer2 = "tarsum.dev+sha256:b194de3772ebbcdc8f244f663669799ac1cb141834b7cb8b69100285d357a2b0" - layer3 = "tarsum.dev+sha256:c937c4bb1c1a21cc6d94340812262c6472092028972ae69b551b1a70d4276171" - layer4 = "tarsum.dev+sha256:2aaacc362ac6be2b9e9ae8c6029f6f616bb50aec63746521858e47841b90fabd" - layer5 = "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" -) + "github.com/openshift/origin/pkg/image/prune/testutil" -var ( - config1 = "sha256:2b8fd9751c4c0f5dd266fcae00707e67a2545ef34f9a29354585f93dac906749" - config2 = "sha256:8ddc19f16526912237dd8af81971d5e4dd0587907234be2b83e249518d5b673f" + _ "github.com/openshift/origin/pkg/apps/apis/apps/install" + _ "github.com/openshift/origin/pkg/build/apis/build/install" + _ "github.com/openshift/origin/pkg/image/apis/image/install" + _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/extensions/install" ) -func agedImage(id, ref string, ageInMinutes int64) imageapi.Image { - image := imageWithLayers(id, ref, nil, layer1, layer2, layer3, layer4, layer5) - - if ageInMinutes >= 0 { - image.CreationTimestamp = metav1.NewTime(metav1.Now().Add(time.Duration(-1*ageInMinutes) * time.Minute)) - } - - return image -} - -func sizedImage(id, ref string, size int64, configName *string) imageapi.Image { - image := imageWithLayers(id, ref, configName, layer1, layer2, layer3, layer4, layer5) - image.CreationTimestamp = metav1.NewTime(metav1.Now().Add(time.Duration(-1) * time.Minute)) - image.DockerImageMetadata.Size = size - - return image -} - -func image(id, ref string) imageapi.Image { - return agedImage(id, ref, -1) -} - -func imageWithLayers(id, ref string, configName *string, layers ...string) imageapi.Image { - image := imageapi.Image{ - ObjectMeta: metav1.ObjectMeta{ - Name: id, - Annotations: map[string]string{ - imageapi.ManagedByOpenShiftAnnotation: "true", - }, - }, - DockerImageReference: ref, - DockerImageManifestMediaType: schema1.MediaTypeManifest, - } - - if configName != nil { - image.DockerImageMetadata = imageapi.DockerImage{ - ID: *configName, - } - image.DockerImageConfig = fmt.Sprintf("{Digest: %s}", *configName) - image.DockerImageManifestMediaType = schema2.MediaTypeManifest - } - - image.DockerImageLayers = []imageapi.ImageLayer{} - for _, layer := range layers { - image.DockerImageLayers = append(image.DockerImageLayers, imageapi.ImageLayer{Name: layer}) - } - - return image -} - -func unmanagedImage(id, ref string, hasAnnotations bool, annotation, value string) imageapi.Image { - image := imageWithLayers(id, ref, nil) - if !hasAnnotations { - image.Annotations = nil - } else { - delete(image.Annotations, imageapi.ManagedByOpenShiftAnnotation) - image.Annotations[annotation] = value - } - return image -} - -func podList(pods ...kapi.Pod) kapi.PodList { - return kapi.PodList{ - Items: pods, - } -} - -func pod(namespace, name string, phase kapi.PodPhase, containerImages ...string) kapi.Pod { - return agedPod(namespace, name, phase, -1, containerImages...) -} - -func agedPod(namespace, name string, phase kapi.PodPhase, ageInMinutes int64, containerImages ...string) kapi.Pod { - pod := kapi.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: podSpec(containerImages...), - Status: kapi.PodStatus{ - Phase: phase, - }, - } - - if ageInMinutes >= 0 { - pod.CreationTimestamp = metav1.NewTime(metav1.Now().Add(time.Duration(-1*ageInMinutes) * time.Minute)) - } - - return pod -} - -func podSpec(containerImages ...string) kapi.PodSpec { - spec := kapi.PodSpec{ - Containers: []kapi.Container{}, - } - for _, image := range containerImages { - container := kapi.Container{ - Image: image, - } - spec.Containers = append(spec.Containers, container) - } - return spec -} - -func streamList(streams ...imageapi.ImageStream) imageapi.ImageStreamList { - return imageapi.ImageStreamList{ - Items: streams, - } -} - -func stream(registry, namespace, name string, tags map[string]imageapi.TagEventList) imageapi.ImageStream { - return agedStream(registry, namespace, name, -1, tags) -} - -func agedStream(registry, namespace, name string, ageInMinutes int64, tags map[string]imageapi.TagEventList) imageapi.ImageStream { - stream := imageapi.ImageStream{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Status: imageapi.ImageStreamStatus{ - DockerImageRepository: fmt.Sprintf("%s/%s/%s", registry, namespace, name), - Tags: tags, - }, - } - - if ageInMinutes >= 0 { - stream.CreationTimestamp = metav1.NewTime(metav1.Now().Add(time.Duration(-1*ageInMinutes) * time.Minute)) - } - - return stream -} - -func streamPtr(registry, namespace, name string, tags map[string]imageapi.TagEventList) *imageapi.ImageStream { - s := stream(registry, namespace, name, tags) - return &s -} - -func tags(list ...namedTagEventList) map[string]imageapi.TagEventList { - m := make(map[string]imageapi.TagEventList, len(list)) - for _, tag := range list { - m[tag.name] = tag.events - } - return m -} - -type namedTagEventList struct { - name string - events imageapi.TagEventList -} - -func tag(name string, events ...imageapi.TagEvent) namedTagEventList { - return namedTagEventList{ - name: name, - events: imageapi.TagEventList{ - Items: events, - }, - } -} - -func tagEvent(id, ref string) imageapi.TagEvent { - return imageapi.TagEvent{ - Image: id, - DockerImageReference: ref, - } -} - -func youngTagEvent(id, ref string, created metav1.Time) imageapi.TagEvent { - return imageapi.TagEvent{ - Image: id, - Created: created, - DockerImageReference: ref, - } -} - -func rcList(rcs ...kapi.ReplicationController) kapi.ReplicationControllerList { - return kapi.ReplicationControllerList{ - Items: rcs, - } -} - -func rc(namespace, name string, containerImages ...string) kapi.ReplicationController { - return kapi.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: kapi.ReplicationControllerSpec{ - Template: &kapi.PodTemplateSpec{ - Spec: podSpec(containerImages...), - }, - }, - } -} - -func dsList(dss ...kapisext.DaemonSet) kapisext.DaemonSetList { - return kapisext.DaemonSetList{ - Items: dss, - } -} - -func ds(namespace, name string, containerImages ...string) kapisext.DaemonSet { - return kapisext.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: kapisext.DaemonSetSpec{ - Template: kapi.PodTemplateSpec{ - Spec: podSpec(containerImages...), - }, - }, - } -} - -func deploymentList(deployments ...kapisext.Deployment) kapisext.DeploymentList { - return kapisext.DeploymentList{ - Items: deployments, - } -} - -func deployment(namespace, name string, containerImages ...string) kapisext.Deployment { - return kapisext.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: kapisext.DeploymentSpec{ - Template: kapi.PodTemplateSpec{ - Spec: podSpec(containerImages...), - }, - }, - } -} - -func dcList(dcs ...deployapi.DeploymentConfig) deployapi.DeploymentConfigList { - return deployapi.DeploymentConfigList{ - Items: dcs, - } -} - -func dc(namespace, name string, containerImages ...string) deployapi.DeploymentConfig { - return deployapi.DeploymentConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: deployapi.DeploymentConfigSpec{ - Template: &kapi.PodTemplateSpec{ - Spec: podSpec(containerImages...), - }, - }, - } -} - -func rsList(rss ...kapisext.ReplicaSet) kapisext.ReplicaSetList { - return kapisext.ReplicaSetList{ - Items: rss, - } -} - -func rs(namespace, name string, containerImages ...string) kapisext.ReplicaSet { - return kapisext.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: kapisext.ReplicaSetSpec{ - Template: kapi.PodTemplateSpec{ - Spec: podSpec(containerImages...), - }, - }, - } -} - -func bcList(bcs ...buildapi.BuildConfig) buildapi.BuildConfigList { - return buildapi.BuildConfigList{ - Items: bcs, - } -} - -func bc(namespace, name, strategyType, fromKind, fromNamespace, fromName string) buildapi.BuildConfig { - return buildapi.BuildConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: buildapi.BuildConfigSpec{ - CommonSpec: commonSpec(strategyType, fromKind, fromNamespace, fromName), - }, - } -} - -func buildList(builds ...buildapi.Build) buildapi.BuildList { - return buildapi.BuildList{ - Items: builds, - } -} - -func build(namespace, name, strategyType, fromKind, fromNamespace, fromName string) buildapi.Build { - return buildapi.Build{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: buildapi.BuildSpec{ - CommonSpec: commonSpec(strategyType, fromKind, fromNamespace, fromName), - }, - } -} - -func limitList(limits ...int64) []*kapi.LimitRange { - list := make([]*kapi.LimitRange, len(limits)) - for _, limit := range limits { - quantity := resource.NewQuantity(limit, resource.BinarySI) - list = append(list, &kapi.LimitRange{ - Spec: kapi.LimitRangeSpec{ - Limits: []kapi.LimitRangeItem{ - { - Type: imageapi.LimitTypeImage, - Max: kapi.ResourceList{ - kapi.ResourceStorage: *quantity, - }, - }, - }, - }, - }) - } - return list -} - -func commonSpec(strategyType, fromKind, fromNamespace, fromName string) buildapi.CommonSpec { - spec := buildapi.CommonSpec{ - Strategy: buildapi.BuildStrategy{}, - } - switch strategyType { - case "source": - spec.Strategy.SourceStrategy = &buildapi.SourceBuildStrategy{ - From: kapi.ObjectReference{ - Kind: fromKind, - Namespace: fromNamespace, - Name: fromName, - }, - } - case "docker": - spec.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{ - From: &kapi.ObjectReference{ - Kind: fromKind, - Namespace: fromNamespace, - Name: fromName, - }, - } - case "custom": - spec.Strategy.CustomStrategy = &buildapi.CustomBuildStrategy{ - From: kapi.ObjectReference{ - Kind: fromKind, - Namespace: fromNamespace, - Name: fromName, - }, - } - } - - return spec -} - -type fakeImageDeleter struct { - invocations sets.String - err error -} - -var _ ImageDeleter = &fakeImageDeleter{} - -func (p *fakeImageDeleter) DeleteImage(image *imageapi.Image) error { - p.invocations.Insert(image.Name) - return p.err -} - -type fakeImageStreamDeleter struct { - invocations sets.String - err error - streamImages map[string][]string - streamTags map[string][]string -} - -var _ ImageStreamDeleter = &fakeImageStreamDeleter{} - -func (p *fakeImageStreamDeleter) GetImageStream(stream *imageapi.ImageStream) (*imageapi.ImageStream, error) { - if p.streamImages == nil { - p.streamImages = make(map[string][]string) - } - if p.streamTags == nil { - p.streamTags = make(map[string][]string) - } - for tag, history := range stream.Status.Tags { - streamName := fmt.Sprintf("%s/%s", stream.Namespace, stream.Name) - p.streamTags[streamName] = append(p.streamTags[streamName], tag) - - for _, tagEvent := range history.Items { - p.streamImages[streamName] = append(p.streamImages[streamName], tagEvent.Image) - } - } - return stream, p.err -} - -func (p *fakeImageStreamDeleter) UpdateImageStream(stream *imageapi.ImageStream) (*imageapi.ImageStream, error) { - streamImages := make(map[string]struct{}) - streamTags := make(map[string]struct{}) - - for tag, history := range stream.Status.Tags { - streamTags[tag] = struct{}{} - for _, tagEvent := range history.Items { - streamImages[tagEvent.Image] = struct{}{} - } - } - - streamName := fmt.Sprintf("%s/%s", stream.Namespace, stream.Name) - - for _, tag := range p.streamTags[streamName] { - if _, ok := streamTags[tag]; !ok { - p.invocations.Insert(fmt.Sprintf("%s:%s", streamName, tag)) - } - } - - for _, imageName := range p.streamImages[streamName] { - if _, ok := streamImages[imageName]; !ok { - p.invocations.Insert(fmt.Sprintf("%s|%s", streamName, imageName)) - } - } - - return stream, p.err -} - -func (p *fakeImageStreamDeleter) NotifyImageStreamPrune(stream *imageapi.ImageStream, updatedTags []string, deletedTags []string) { - return -} - -type fakeBlobDeleter struct { - invocations sets.String - err error -} - -var _ BlobDeleter = &fakeBlobDeleter{} - -func (p *fakeBlobDeleter) DeleteBlob(registryClient *http.Client, registryURL *url.URL, blob string) error { - p.invocations.Insert(fmt.Sprintf("%s|%s", registryURL.String(), blob)) - return p.err -} - -type fakeLayerLinkDeleter struct { - invocations sets.String - err error -} - -var _ LayerLinkDeleter = &fakeLayerLinkDeleter{} - -func (p *fakeLayerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL *url.URL, repo, layer string) error { - p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL.String(), repo, layer)) - return p.err -} - -type fakeManifestDeleter struct { - invocations sets.String - err error -} - -var _ ManifestDeleter = &fakeManifestDeleter{} - -func (p *fakeManifestDeleter) DeleteManifest(registryClient *http.Client, registryURL *url.URL, repo, manifest string) error { - p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL.String(), repo, manifest)) - return p.err -} - var logLevel = flag.Int("loglevel", 0, "") -var testCase = flag.String("testcase", "", "") func TestImagePruning(t *testing.T) { flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel)) registryHost := "registry.io" registryURL := "https://" + registryHost - tests := map[string]struct { + tests := []struct { + name string pruneOverSizeLimit *bool allImages *bool + keepTagRevisions *int namespace string images imageapi.ImageList pods kapi.PodList @@ -549,268 +63,299 @@ func TestImagePruning(t *testing.T) { expectedStreamUpdates []string expectedLayerLinkDeletions []string expectedBlobDeletions []string + expectedErrorString string }{ - "1 pod - phase pending - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList(pod("foo", "pod1", kapi.PodPending, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "1 pod - phase pending - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList(testutil.Pod("foo", "pod1", kapi.PodPending, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "3 pods - last phase pending - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList( - pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod2", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod3", kapi.PodPending, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + { + name: "3 pods - last phase pending - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList( + testutil.Pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Pod("foo", "pod2", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Pod("foo", "pod3", kapi.PodPending, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), ), expectedImageDeletions: []string{}, }, - "1 pod - phase running - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList(pod("foo", "pod1", kapi.PodRunning, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "1 pod - phase running - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList(testutil.Pod("foo", "pod1", kapi.PodRunning, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "3 pods - last phase running - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList( - pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod2", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod3", kapi.PodRunning, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + { + name: "3 pods - last phase running - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList( + testutil.Pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Pod("foo", "pod2", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Pod("foo", "pod3", kapi.PodRunning, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), ), expectedImageDeletions: []string{}, }, - "pod phase succeeded - prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList(pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "pod phase succeeded - prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList(testutil.Pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ - registryURL + "|" + layer1, - registryURL + "|" + layer2, - registryURL + "|" + layer3, - registryURL + "|" + layer4, - registryURL + "|" + layer5, + registryURL + "|" + testutil.Layer1, + registryURL + "|" + testutil.Layer2, + registryURL + "|" + testutil.Layer3, + registryURL + "|" + testutil.Layer4, + registryURL + "|" + testutil.Layer5, }, }, - "pod phase succeeded, pod less than min pruning age - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList(agedPod("foo", "pod1", kapi.PodSucceeded, 5, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "pod phase succeeded, pod less than min pruning age - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList(testutil.AgedPod("foo", "pod1", kapi.PodSucceeded, 5, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "pod phase succeeded, image less than min pruning age - don't prune": { - images: imageList(agedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", 5)), - pods: podList(pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "pod phase succeeded, image less than min pruning age - don't prune", + images: testutil.ImageList(testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", 5)), + pods: testutil.PodList(testutil.Pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "pod phase failed - prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList( - pod("foo", "pod1", kapi.PodFailed, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod2", kapi.PodFailed, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod3", kapi.PodFailed, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + { + name: "pod phase failed - prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList( + testutil.Pod("foo", "pod1", kapi.PodFailed, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Pod("foo", "pod2", kapi.PodFailed, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Pod("foo", "pod3", kapi.PodFailed, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ - registryURL + "|" + layer1, - registryURL + "|" + layer2, - registryURL + "|" + layer3, - registryURL + "|" + layer4, - registryURL + "|" + layer5, + registryURL + "|" + testutil.Layer1, + registryURL + "|" + testutil.Layer2, + registryURL + "|" + testutil.Layer3, + registryURL + "|" + testutil.Layer4, + registryURL + "|" + testutil.Layer5, }, }, - "pod phase unknown - prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList( - pod("foo", "pod1", kapi.PodUnknown, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod2", kapi.PodUnknown, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod3", kapi.PodUnknown, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + { + name: "pod phase unknown - prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList( + testutil.Pod("foo", "pod1", kapi.PodUnknown, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Pod("foo", "pod2", kapi.PodUnknown, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Pod("foo", "pod3", kapi.PodUnknown, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ - registryURL + "|" + layer1, - registryURL + "|" + layer2, - registryURL + "|" + layer3, - registryURL + "|" + layer4, - registryURL + "|" + layer5, + registryURL + "|" + testutil.Layer1, + registryURL + "|" + testutil.Layer2, + registryURL + "|" + testutil.Layer3, + registryURL + "|" + testutil.Layer4, + registryURL + "|" + testutil.Layer5, }, }, - "pod container image not parsable": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList( - pod("foo", "pod1", kapi.PodRunning, "a/b/c/d/e"), + { + name: "pod container image not parsable", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList( + testutil.Pod("foo", "pod1", kapi.PodRunning, "a/b/c/d/e"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ - registryURL + "|" + layer1, - registryURL + "|" + layer2, - registryURL + "|" + layer3, - registryURL + "|" + layer4, - registryURL + "|" + layer5, + registryURL + "|" + testutil.Layer1, + registryURL + "|" + testutil.Layer2, + registryURL + "|" + testutil.Layer3, + registryURL + "|" + testutil.Layer4, + registryURL + "|" + testutil.Layer5, }, }, - "pod container image doesn't have an id": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList( - pod("foo", "pod1", kapi.PodRunning, "foo/bar:latest"), + { + name: "pod container image doesn't have an id", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList( + testutil.Pod("foo", "pod1", kapi.PodRunning, "foo/bar:latest"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ - registryURL + "|" + layer1, - registryURL + "|" + layer2, - registryURL + "|" + layer3, - registryURL + "|" + layer4, - registryURL + "|" + layer5, + registryURL + "|" + testutil.Layer1, + registryURL + "|" + testutil.Layer2, + registryURL + "|" + testutil.Layer3, + registryURL + "|" + testutil.Layer4, + registryURL + "|" + testutil.Layer5, }, }, - "pod refers to image not in graph": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList( - pod("foo", "pod1", kapi.PodRunning, registryHost+"/foo/bar@otherid"), + { + name: "pod refers to image not in graph", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: testutil.PodList( + testutil.Pod("foo", "pod1", kapi.PodRunning, registryHost+"/foo/bar@sha256:ABC0000000000000000000000000000000000000000000000000000000000002"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ - registryURL + "|" + layer1, - registryURL + "|" + layer2, - registryURL + "|" + layer3, - registryURL + "|" + layer4, - registryURL + "|" + layer5, + registryURL + "|" + testutil.Layer1, + registryURL + "|" + testutil.Layer2, + registryURL + "|" + testutil.Layer3, + registryURL + "|" + testutil.Layer4, + registryURL + "|" + testutil.Layer5, }, }, - "referenced by rc - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - rcs: rcList(rc("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by rc - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + rcs: testutil.RCList(testutil.RC("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by dc - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - dcs: dcList(dc("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by dc - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + dcs: testutil.DCList(testutil.DC("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by daemonset - don't prune": { - images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + { + name: "referenced by daemonset - don't prune", + images: testutil.ImageList( + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), - dss: dsList(ds("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + dss: testutil.DSList(testutil.DS("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, - "referenced by replicaset - don't prune": { - images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + { + name: "referenced by replicaset - don't prune", + images: testutil.ImageList( + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), - rss: rsList(rs("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + rss: testutil.RSList(testutil.RS("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, - "referenced by upstream deployment - don't prune": { - images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + { + name: "referenced by upstream deployment - don't prune", + images: testutil.ImageList( + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), - deployments: deploymentList(deployment("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + deployments: testutil.DeploymentList(testutil.Deployment("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, - "referenced by bc - sti - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "source", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by bc - sti - ImageStreamImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: testutil.BCList(testutil.BC("foo", "bc1", "source", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by bc - docker - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "docker", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by bc - docker - ImageStreamImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: testutil.BCList(testutil.BC("foo", "bc1", "docker", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by bc - custom - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "custom", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by bc - custom - ImageStreamImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: testutil.BCList(testutil.BC("foo", "bc1", "custom", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by bc - sti - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "source", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by bc - sti - DockerImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: testutil.BCList(testutil.BC("foo", "bc1", "source", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by bc - docker - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "docker", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by bc - docker - DockerImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: testutil.BCList(testutil.BC("foo", "bc1", "docker", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by bc - custom - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "custom", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by bc - custom - DockerImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: testutil.BCList(testutil.BC("foo", "bc1", "custom", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by build - sti - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "source", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by build - sti - ImageStreamImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: testutil.BuildList(testutil.Build("foo", "build1", "source", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by build - docker - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "docker", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by build - docker - ImageStreamImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: testutil.BuildList(testutil.Build("foo", "build1", "docker", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by build - custom - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "custom", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by build - custom - ImageStreamImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: testutil.BuildList(testutil.Build("foo", "build1", "custom", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by build - sti - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "source", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by build - sti - DockerImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: testutil.BuildList(testutil.Build("foo", "build1", "source", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by build - docker - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "docker", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by build - docker - DockerImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: testutil.BuildList(testutil.Build("foo", "build1", "docker", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "referenced by build - custom - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "custom", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + { + name: "referenced by build - custom - DockerImage - don't prune", + images: testutil.ImageList(testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: testutil.BuildList(testutil.Build("foo", "build1", "custom", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, - "image stream - keep most recent n images": { - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), - image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), - ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + { + name: "image stream - keep most recent n images", + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), @@ -818,37 +363,39 @@ func TestImagePruning(t *testing.T) { expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, }, - "image stream - same manifest listed multiple times in tag history": { - images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + { + name: "image stream - same manifest listed multiple times in tag history", + images: testutil.ImageList( + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), )), ), }, - "image stream age less than min pruning age - don't prune": { - images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), - ), - streams: streamList( - agedStream(registryHost, "foo", "bar", 5, tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + { + name: "image stream age less than min pruning age - don't prune", + images: testutil.ImageList( + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + streams: testutil.StreamList( + testutil.AgedStream(registryHost, "foo", "bar", 5, testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), @@ -856,32 +403,34 @@ func TestImagePruning(t *testing.T) { expectedStreamUpdates: []string{}, }, - "image stream - unreference absent image": { - images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + { + name: "image stream - unreference absent image", + images: testutil.ImageList( + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), - 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"), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), )), ), expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000000"}, }, - "image stream with dangling references - delete tags": { - images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", nil, "layer1"), + { + name: "image stream with dangling references - delete tags", + images: testutil.ImageList( + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", nil, "layer1"), ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), ), - tag("tag", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Tag("tag", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), )), ), @@ -895,86 +444,148 @@ func TestImagePruning(t *testing.T) { expectedBlobDeletions: []string{registryURL + "|layer1"}, }, - "image stream - keep reference to a young absent image": { - images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", nil), + { + name: "image stream - keep reference to a young absent image", + images: testutil.ImageList( + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", nil), ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - youngTagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", metav1.Now()), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.YoungTagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", metav1.Now()), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), )), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000002"}, }, - "multiple resources pointing to image - don't prune": { - images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + { + name: "images referenced by istag - keep", + keepTagRevisions: keepTagRevisions(0), + images: testutil.ImageList( + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000006", registryHost+"/foo/baz@sha256:0000000000000000000000000000000000000000000000000000000000000006"), ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005"), + ), + testutil.Tag("dummy", // removed because no object references the image (the nm/dcfoo has mismatched repository name) + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005"), + ), + )), + testutil.Stream(registryHost, "foo", "baz", testutil.Tags( + testutil.Tag("late", // kept because replicaset references the tagged image + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + ), + testutil.Tag("keepme", // kept because a deployment references the tagged image + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000006", registryHost+"/foo/baz@sha256:0000000000000000000000000000000000000000000000000000000000000006"), ), )), ), - rcs: rcList(rc("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002")), - pods: podList(pod("foo", "pod1", kapi.PodRunning, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002")), - dcs: dcList(dc("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "source", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "custom", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + dss: testutil.DSList(testutil.DS("nm", "dsfoo", fmt.Sprintf("%s/%s/%s:%s", registryHost, "foo", "bar", "latest"))), + dcs: testutil.DCList(testutil.DC("nm", "dcfoo", fmt.Sprintf("%s/%s/%s:%s", registryHost, "foo", "repo", "dummy"))), + rss: testutil.RSList(testutil.RS("nm", "rsfoo", fmt.Sprintf("%s/%s/%s:%s", registryHost, "foo", "baz", "late"))), + // ignore different registry hostname + deployments: testutil.DeploymentList(testutil.Deployment("nm", "depfoo", fmt.Sprintf("%s/%s/%s:%s", "external.registry:5000", "foo", "baz", "keepme"))), + 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", + }, + }, + + { + name: "multiple resources pointing to image - don't prune", + images: testutil.ImageList( + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + ), + )), + ), + rcs: testutil.RCList(testutil.RC("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002")), + pods: testutil.PodList(testutil.Pod("foo", "pod1", kapi.PodRunning, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002")), + dcs: testutil.DCList(testutil.DC("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: testutil.BCList(testutil.BC("foo", "bc1", "source", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: testutil.BuildList(testutil.Build("foo", "build1", "custom", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, - "image with nil annotations": { - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + { + name: "image with nil annotations", + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedStreamUpdates: []string{}, }, - "prune all-images=true image with nil annotations": { + { + name: "prune all-images=true image with nil annotations", allImages: newBool(true), - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedStreamUpdates: []string{}, }, - "prune all-images=false image with nil annotations": { + { + name: "prune all-images=false image with nil annotations", allImages: newBool(false), - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), ), expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, - "image missing managed annotation": { - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, "foo", "bar"), + { + name: "image missing managed annotation", + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, "foo", "bar"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedStreamUpdates: []string{}, }, - "image with managed annotation != true": { - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "false"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), + { + name: "image with managed annotation != true", + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "false"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), ), expectedImageDeletions: []string{ "sha256:0000000000000000000000000000000000000000000000000000000000000000", @@ -987,24 +598,26 @@ func TestImagePruning(t *testing.T) { expectedStreamUpdates: []string{}, }, - "prune all-images=true with image missing managed annotation": { + { + name: "prune all-images=true with image missing managed annotation", allImages: newBool(true), - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, "foo", "bar"), + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, "foo", "bar"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedStreamUpdates: []string{}, }, - "prune all-images=true with image with managed annotation != true": { + { + name: "prune all-images=true with image with managed annotation != true", allImages: newBool(true), - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "false"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "false"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), ), expectedImageDeletions: []string{ "sha256:0000000000000000000000000000000000000000000000000000000000000000", @@ -1017,43 +630,46 @@ func TestImagePruning(t *testing.T) { expectedStreamUpdates: []string{}, }, - "prune all-images=false with image missing managed annotation": { + { + name: "prune all-images=false with image missing managed annotation", allImages: newBool(false), - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, "foo", "bar"), + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, "foo", "bar"), ), expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, - "prune all-images=false with image with managed annotation != true": { + { + name: "prune all-images=false with image with managed annotation != true", allImages: newBool(false), - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "false"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "false"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), ), expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, - "image with layers": { - images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config2, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", nil, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", nil, "layer5", "layer6", "layer7", "layer8"), - ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - 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"), + { + name: "image with layers", + images: testutil.ImageList( + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &testutil.Config2, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", nil, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", nil, "layer5", "layer6", "layer7", "layer8"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), @@ -1073,35 +689,36 @@ func TestImagePruning(t *testing.T) { }, }, - "images with duplicate layers and configs": { - images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", &config2, "layer5", "layer6", "layer7", "layer8"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005", &config2, "layer5", "layer6", "layer9", "layerX"), - ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - 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"), + { + name: "images with duplicate layers and configs", + images: testutil.ImageList( + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", &testutil.Config2, "layer5", "layer6", "layer7", "layer8"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005", &testutil.Config2, "layer5", "layer6", "layer9", "layerX"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000004", "sha256:0000000000000000000000000000000000000000000000000000000000000005"}, expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, expectedLayerLinkDeletions: []string{ - registryURL + "|foo/bar|" + config2, + registryURL + "|foo/bar|" + testutil.Config2, registryURL + "|foo/bar|layer5", registryURL + "|foo/bar|layer6", registryURL + "|foo/bar|layer7", registryURL + "|foo/bar|layer8", }, expectedBlobDeletions: []string{ - registryURL + "|" + config2, + registryURL + "|" + testutil.Config2, registryURL + "|layer5", registryURL + "|layer6", registryURL + "|layer7", @@ -1111,176 +728,217 @@ func TestImagePruning(t *testing.T) { }, }, - "layers shared with young images are not pruned": { - images: imageList( - agedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 43200), - agedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 5), + { + name: "layers shared with young images are not pruned", + images: testutil.ImageList( + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 43200), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 5), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, - "image exceeding limits": { + { + name: "image exceeding limits", pruneOverSizeLimit: newBool(true), - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), - ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), ), )), ), limits: map[string][]*kapi.LimitRange{ - "foo": limitList(100, 200), + "foo": testutil.LimitList(100, 200), }, expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000003"}, expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, }, - "multiple images in different namespaces exceeding different limits": { + { + name: "multiple images in different namespaces exceeding different limits", pruneOverSizeLimit: newBool(true), - images: imageList( - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 100, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 200, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000003", 500, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000004", 600, nil), - ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + images: testutil.ImageList( + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 100, nil), + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 200, nil), + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000003", 500, nil), + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000004", 600, nil), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), )), - stream(registryHost, "bar", "foo", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + testutil.Stream(registryHost, "bar", "foo", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), limits: map[string][]*kapi.LimitRange{ - "foo": limitList(150), - "bar": limitList(550), + "foo": testutil.LimitList(150), + "bar": testutil.LimitList(550), }, expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000002", "sha256:0000000000000000000000000000000000000000000000000000000000000004"}, expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000002", "bar/foo|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, }, - "image within allowed limits": { + { + name: "image within allowed limits", pruneOverSizeLimit: newBool(true), - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), - ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), ), )), ), limits: map[string][]*kapi.LimitRange{ - "foo": limitList(300), + "foo": testutil.LimitList(300), }, expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, - "image exceeding limits with namespace specified": { + { + name: "image exceeding limits with namespace specified", pruneOverSizeLimit: newBool(true), namespace: "foo", - images: imageList( - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), - ), - streams: streamList( - stream(registryHost, "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), + testutil.SizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), ), )), ), limits: map[string][]*kapi.LimitRange{ - "foo": limitList(100, 200), + "foo": testutil.LimitList(100, 200), }, expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, }, - } - for name, test := range tests { - tcFilter := flag.Lookup("testcase").Value.String() - if len(tcFilter) > 0 && name != tcFilter { - continue - } + { + name: "build with bad image reference", + builds: testutil.BuildList(testutil.Build("foo", "build1", "source", "DockerImage", "foo", registryHost+"/foo/bar@invalid-digest")), + expectedErrorString: fmt.Sprintf(`Build[foo/build1]: invalid docker image reference "%s/foo/bar@invalid-digest": invalid reference format`, registryHost), + }, - options := PrunerOptions{ - Namespace: test.namespace, - AllImages: test.allImages, - Images: &test.images, - Streams: &test.streams, - Pods: &test.pods, - RCs: &test.rcs, - BCs: &test.bcs, - Builds: &test.builds, - DSs: &test.dss, - Deployments: &test.deployments, - DCs: &test.dcs, - RSs: &test.rss, - LimitRanges: test.limits, - RegistryURL: &url.URL{Scheme: "https", Host: registryHost}, - } - if test.pruneOverSizeLimit != nil { - options.PruneOverSizeLimit = test.pruneOverSizeLimit - } else { - keepYoungerThan := 60 * time.Minute - keepTagRevisions := 3 - options.KeepYoungerThan = &keepYoungerThan - options.KeepTagRevisions = &keepTagRevisions - } - p, err := NewPruner(options) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } + { + name: "buildconfig with bad imagestreamtag", + bcs: testutil.BCList(testutil.BC("foo", "bc1", "source", "ImageStreamTag", "ns", "bad/tag@name")), + expectedErrorString: `BuildConfig[foo/bc1]: invalid ImageStreamTag reference "bad/tag@name":` + + ` "bad/tag@name" is an image stream image, not an image stream tag`, + }, - imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} - streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} - layerLinkDeleter := &fakeLayerLinkDeleter{invocations: sets.NewString()} - blobDeleter := &fakeBlobDeleter{invocations: sets.NewString()} - manifestDeleter := &fakeManifestDeleter{invocations: sets.NewString()} + { + name: "more parsing errors", + bcs: testutil.BCList(testutil.BC("foo", "bc1", "source", "ImageStreamImage", "ns", "bad:isi")), + deployments: testutil.DeploymentList(testutil.Deployment("nm", "dep1", "garbage")), + rss: testutil.RSList(testutil.RS("nm", "rs1", "I am certainly a valid reference")), + expectedErrorString: `[BuildConfig[foo/bc1]: invalid ImageStreamImage reference "bad:isi":` + + ` expected exactly one @ in the isimage name "bad:isi",` + + ` ReplicaSet[nm/rs1]: invalid docker image reference "I am certainly a valid reference":` + + ` invalid reference format]`, + }, + } - p.Prune(imageDeleter, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + options := PrunerOptions{ + Namespace: test.namespace, + AllImages: test.allImages, + Images: &test.images, + Streams: &test.streams, + Pods: &test.pods, + RCs: &test.rcs, + BCs: &test.bcs, + Builds: &test.builds, + DSs: &test.dss, + Deployments: &test.deployments, + DCs: &test.dcs, + RSs: &test.rss, + LimitRanges: test.limits, + RegistryURL: &url.URL{Scheme: "https", Host: registryHost}, + } + if test.pruneOverSizeLimit != nil { + options.PruneOverSizeLimit = test.pruneOverSizeLimit + } else { + youngerThan := time.Hour + tagRevisions := 3 + if test.keepTagRevisions != nil { + tagRevisions = *test.keepTagRevisions + } + options.KeepYoungerThan = &youngerThan + options.KeepTagRevisions = &tagRevisions + } + p, err := NewPruner(options) + if err != nil { + if len(test.expectedErrorString) > 0 { + if a, e := err.Error(), test.expectedErrorString; a != e { + t.Fatalf("got unexpected error: %q != %q", a, e) + } + } else { + t.Fatalf("got unexpected error: %v", test.expectedErrorString) + } + return + } else if len(test.expectedErrorString) > 0 { + t.Fatalf("got no error while expecting: %s", test.expectedErrorString) + return + } - expectedImageDeletions := sets.NewString(test.expectedImageDeletions...) - if a, e := imageDeleter.invocations, expectedImageDeletions; !reflect.DeepEqual(a, e) { - t.Errorf("%s: unexpected image deletions: %s", name, diff.ObjectDiff(a, e)) - } + imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} + streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} + layerLinkDeleter := &fakeLayerLinkDeleter{invocations: sets.NewString()} + blobDeleter := &fakeBlobDeleter{invocations: sets.NewString()} + manifestDeleter := &fakeManifestDeleter{invocations: sets.NewString()} - expectedStreamUpdates := sets.NewString(test.expectedStreamUpdates...) - if a, e := streamDeleter.invocations, expectedStreamUpdates; !reflect.DeepEqual(a, e) { - t.Errorf("%s: unexpected stream updates: %s", name, diff.ObjectDiff(a, e)) - } + if err := p.Prune(imageDeleter, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter); err != nil { + t.Fatalf("unexpected error: %v", err) + } - expectedLayerLinkDeletions := sets.NewString(test.expectedLayerLinkDeletions...) - if a, e := layerLinkDeleter.invocations, expectedLayerLinkDeletions; !reflect.DeepEqual(a, e) { - t.Errorf("%s: unexpected layer link deletions: %s", name, diff.ObjectDiff(a, e)) - } + expectedImageDeletions := sets.NewString(test.expectedImageDeletions...) + if a, e := imageDeleter.invocations, expectedImageDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected image deletions: %s", diff.ObjectDiff(a, e)) + } - expectedBlobDeletions := sets.NewString(test.expectedBlobDeletions...) - if a, e := blobDeleter.invocations, expectedBlobDeletions; !reflect.DeepEqual(a, e) { - t.Errorf("%s: unexpected blob deletions: %s", name, diff.ObjectDiff(a, e)) - } + expectedStreamUpdates := sets.NewString(test.expectedStreamUpdates...) + if a, e := streamDeleter.invocations, expectedStreamUpdates; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected stream updates: %s", diff.ObjectDiff(a, e)) + } + + expectedLayerLinkDeletions := sets.NewString(test.expectedLayerLinkDeletions...) + if a, e := layerLinkDeleter.invocations, expectedLayerLinkDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected layer link deletions: %s", diff.ObjectDiff(a, e)) + } + + expectedBlobDeletions := sets.NewString(test.expectedBlobDeletions...) + if a, e := blobDeleter.invocations, expectedBlobDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected blob deletions: %s", diff.ObjectDiff(a, e)) + } + }) } } @@ -1356,7 +1014,8 @@ func TestNotFoundLayerDeleter(t *testing.T) { func TestRegistryPruning(t *testing.T) { flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel)) - tests := map[string]struct { + tests := []struct { + name string images imageapi.ImageList streams imageapi.ImageStreamList expectedLayerLinkDeletions sets.String @@ -1364,31 +1023,32 @@ func TestRegistryPruning(t *testing.T) { expectedManifestDeletions sets.String pingErr error }{ - "layers unique to id1 pruned": { - images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config2, "layer3", "layer4", "layer5", "layer6"), - ), - streams: streamList( - stream("registry1.io", "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + { + name: "layers unique to id1 pruned", + images: testutil.ImageList( + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &testutil.Config2, "layer3", "layer4", "layer5", "layer6"), + ), + streams: testutil.StreamList( + testutil.Stream("registry1.io", "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), )), - stream("registry1.io", "foo", "other", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/other@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Stream("registry1.io", "foo", "other", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/other@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), )), ), expectedLayerLinkDeletions: sets.NewString( - "https://registry1.io|foo/bar|"+config1, + "https://registry1.io|foo/bar|"+testutil.Config1, "https://registry1.io|foo/bar|layer1", "https://registry1.io|foo/bar|layer2", ), expectedBlobDeletions: sets.NewString( - "https://registry1.io|"+config1, + "https://registry1.io|"+testutil.Config1, "https://registry1.io|layer1", "https://registry1.io|layer2", ), @@ -1396,14 +1056,16 @@ func TestRegistryPruning(t *testing.T) { "https://registry1.io|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000001", ), }, - "no pruning when no images are pruned": { - images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), - ), - streams: streamList( - stream("registry1.io", "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + + { + name: "no pruning when no images are pruned", + images: testutil.ImageList( + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + ), + streams: testutil.StreamList( + testutil.Stream("registry1.io", "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), )), ), @@ -1411,15 +1073,17 @@ func TestRegistryPruning(t *testing.T) { expectedBlobDeletions: sets.NewString(), expectedManifestDeletions: sets.NewString(), }, - "blobs pruned when streams have already been deleted": { - images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config2, "layer3", "layer4", "layer5", "layer6"), + + { + name: "blobs pruned when streams have already been deleted", + images: testutil.ImageList( + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &testutil.Config2, "layer3", "layer4", "layer5", "layer6"), ), expectedLayerLinkDeletions: sets.NewString(), expectedBlobDeletions: sets.NewString( - "https://registry1.io|"+config1, - "https://registry1.io|"+config2, + "https://registry1.io|"+testutil.Config1, + "https://registry1.io|"+testutil.Config2, "https://registry1.io|layer1", "https://registry1.io|layer2", "https://registry1.io|layer3", @@ -1429,23 +1093,25 @@ func TestRegistryPruning(t *testing.T) { ), expectedManifestDeletions: sets.NewString(), }, - "config used as a layer": { - images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", config1), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config2, "layer3", "layer4", "layer5", config1), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/other@sha256:0000000000000000000000000000000000000000000000000000000000000003", nil, "layer3", "layer4", "layer6", config1), - ), - streams: streamList( - stream("registry1.io", "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + + { + name: "config used as a layer", + images: testutil.ImageList( + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &testutil.Config1, "layer1", "layer2", "layer3", testutil.Config1), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &testutil.Config2, "layer3", "layer4", "layer5", testutil.Config1), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/other@sha256:0000000000000000000000000000000000000000000000000000000000000003", nil, "layer3", "layer4", "layer6", testutil.Config1), + ), + streams: testutil.StreamList( + testutil.Stream("registry1.io", "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), )), - stream("registry1.io", "foo", "other", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/other@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Stream("registry1.io", "foo", "other", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/other@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), )), ), @@ -1464,53 +1130,48 @@ func TestRegistryPruning(t *testing.T) { }, } - for name, test := range tests { - tcFilter := flag.Lookup("testcase").Value.String() - if len(tcFilter) > 0 && name != tcFilter { - continue - } - - t.Logf("Running test case %s", name) - - keepYoungerThan := 60 * time.Minute - keepTagRevisions := 1 - options := PrunerOptions{ - KeepYoungerThan: &keepYoungerThan, - KeepTagRevisions: &keepTagRevisions, - Images: &test.images, - Streams: &test.streams, - Pods: &kapi.PodList{}, - RCs: &kapi.ReplicationControllerList{}, - BCs: &buildapi.BuildConfigList{}, - Builds: &buildapi.BuildList{}, - DSs: &kapisext.DaemonSetList{}, - Deployments: &kapisext.DeploymentList{}, - DCs: &deployapi.DeploymentConfigList{}, - RSs: &kapisext.ReplicaSetList{}, - RegistryURL: &url.URL{Scheme: "https", Host: "registry1.io"}, - } - p, err := NewPruner(options) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + keepYoungerThan := 60 * time.Minute + keepTagRevisions := 1 + options := PrunerOptions{ + KeepYoungerThan: &keepYoungerThan, + KeepTagRevisions: &keepTagRevisions, + Images: &test.images, + Streams: &test.streams, + Pods: &kapi.PodList{}, + RCs: &kapi.ReplicationControllerList{}, + BCs: &buildapi.BuildConfigList{}, + Builds: &buildapi.BuildList{}, + DSs: &kapisext.DaemonSetList{}, + Deployments: &kapisext.DeploymentList{}, + DCs: &deployapi.DeploymentConfigList{}, + RSs: &kapisext.ReplicaSetList{}, + RegistryURL: &url.URL{Scheme: "https", Host: "registry1.io"}, + } + p, err := NewPruner(options) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } - imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} - streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} - layerLinkDeleter := &fakeLayerLinkDeleter{invocations: sets.NewString()} - blobDeleter := &fakeBlobDeleter{invocations: sets.NewString()} - manifestDeleter := &fakeManifestDeleter{invocations: sets.NewString()} + imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} + streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} + layerLinkDeleter := &fakeLayerLinkDeleter{invocations: sets.NewString()} + blobDeleter := &fakeBlobDeleter{invocations: sets.NewString()} + manifestDeleter := &fakeManifestDeleter{invocations: sets.NewString()} - p.Prune(imageDeleter, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) + p.Prune(imageDeleter, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) - if a, e := layerLinkDeleter.invocations, test.expectedLayerLinkDeletions; !reflect.DeepEqual(a, e) { - t.Errorf("%s: unexpected layer link deletions: %s", name, diff.ObjectDiff(a, e)) - } - if a, e := blobDeleter.invocations, test.expectedBlobDeletions; !reflect.DeepEqual(a, e) { - t.Errorf("%s: unexpected blob deletions: %s", name, diff.ObjectDiff(a, e)) - } - if a, e := manifestDeleter.invocations, test.expectedManifestDeletions; !reflect.DeepEqual(a, e) { - t.Errorf("%s: unexpected manifest deletions: %s", name, diff.ObjectDiff(a, e)) - } + if a, e := layerLinkDeleter.invocations, test.expectedLayerLinkDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected layer link deletions: %s", diff.ObjectDiff(a, e)) + } + if a, e := blobDeleter.invocations, test.expectedBlobDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected blob deletions: %s", diff.ObjectDiff(a, e)) + } + if a, e := manifestDeleter.invocations, test.expectedManifestDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected manifest deletions: %s", diff.ObjectDiff(a, e)) + } + }) } } @@ -1523,31 +1184,31 @@ func newBool(a bool) *bool { func TestImageWithStrongAndWeakRefsIsNotPruned(t *testing.T) { flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel)) - images := imageList( - agedImage("0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 1540), - agedImage("0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 1540), - agedImage("0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 1540), + images := testutil.ImageList( + testutil.AgedImage("0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 1540), + testutil.AgedImage("0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 1540), + testutil.AgedImage("0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 1540), ) - streams := streamList( - stream("registry1", "foo", "bar", tags( - tag("latest", - tagEvent("0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + streams := testutil.StreamList( + testutil.Stream("registry1", "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), - tag("strong", - tagEvent("0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.Tag("strong", + testutil.TagEvent("0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), ), )), ) - pods := podList() - rcs := rcList() - bcs := bcList() - builds := buildList() - dss := dsList() - deployments := deploymentList() - dcs := dcList() - rss := rsList() + pods := testutil.PodList() + rcs := testutil.RCList() + bcs := testutil.BCList() + builds := testutil.BuildList() + dss := testutil.DSList() + deployments := testutil.DeploymentList() + dcs := testutil.DCList() + rss := testutil.RSList() options := PrunerOptions{ Images: &images, @@ -1608,3 +1269,114 @@ func TestImageIsPrunable(t *testing.T) { t.Fatalf("Image is prunable although it should not") } } + +func keepTagRevisions(n int) *int { + return &n +} + +type fakeImageDeleter struct { + invocations sets.String + err error +} + +var _ ImageDeleter = &fakeImageDeleter{} + +func (p *fakeImageDeleter) DeleteImage(image *imageapi.Image) error { + p.invocations.Insert(image.Name) + return p.err +} + +type fakeImageStreamDeleter struct { + invocations sets.String + err error + streamImages map[string][]string + streamTags map[string][]string +} + +var _ ImageStreamDeleter = &fakeImageStreamDeleter{} + +func (p *fakeImageStreamDeleter) GetImageStream(stream *imageapi.ImageStream) (*imageapi.ImageStream, error) { + if p.streamImages == nil { + p.streamImages = make(map[string][]string) + } + if p.streamTags == nil { + p.streamTags = make(map[string][]string) + } + for tag, history := range stream.Status.Tags { + streamName := fmt.Sprintf("%s/%s", stream.Namespace, stream.Name) + p.streamTags[streamName] = append(p.streamTags[streamName], tag) + + for _, tagEvent := range history.Items { + p.streamImages[streamName] = append(p.streamImages[streamName], tagEvent.Image) + } + } + return stream, p.err +} + +func (p *fakeImageStreamDeleter) UpdateImageStream(stream *imageapi.ImageStream) (*imageapi.ImageStream, error) { + streamImages := make(map[string]struct{}) + streamTags := make(map[string]struct{}) + + for tag, history := range stream.Status.Tags { + streamTags[tag] = struct{}{} + for _, tagEvent := range history.Items { + streamImages[tagEvent.Image] = struct{}{} + } + } + + streamName := fmt.Sprintf("%s/%s", stream.Namespace, stream.Name) + + for _, tag := range p.streamTags[streamName] { + if _, ok := streamTags[tag]; !ok { + p.invocations.Insert(fmt.Sprintf("%s:%s", streamName, tag)) + } + } + + for _, imageName := range p.streamImages[streamName] { + if _, ok := streamImages[imageName]; !ok { + p.invocations.Insert(fmt.Sprintf("%s|%s", streamName, imageName)) + } + } + + return stream, p.err +} + +func (p *fakeImageStreamDeleter) NotifyImageStreamPrune(stream *imageapi.ImageStream, updatedTags []string, deletedTags []string) { + return +} + +type fakeBlobDeleter struct { + invocations sets.String + err error +} + +var _ BlobDeleter = &fakeBlobDeleter{} + +func (p *fakeBlobDeleter) DeleteBlob(registryClient *http.Client, registryURL *url.URL, blob string) error { + p.invocations.Insert(fmt.Sprintf("%s|%s", registryURL.String(), blob)) + return p.err +} + +type fakeLayerLinkDeleter struct { + invocations sets.String + err error +} + +var _ LayerLinkDeleter = &fakeLayerLinkDeleter{} + +func (p *fakeLayerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL *url.URL, repo, layer string) error { + p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL.String(), repo, layer)) + return p.err +} + +type fakeManifestDeleter struct { + invocations sets.String + err error +} + +var _ ManifestDeleter = &fakeManifestDeleter{} + +func (p *fakeManifestDeleter) DeleteManifest(registryClient *http.Client, registryURL *url.URL, repo, manifest string) error { + p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL.String(), repo, manifest)) + return p.err +} diff --git a/pkg/image/prune/testutil/util.go b/pkg/image/prune/testutil/util.go new file mode 100644 index 000000000000..8cd6cfc63d47 --- /dev/null +++ b/pkg/image/prune/testutil/util.go @@ -0,0 +1,444 @@ +package testutil + +import ( + "fmt" + "time" + + "github.com/docker/distribution/manifest/schema1" + "github.com/docker/distribution/manifest/schema2" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kapi "k8s.io/kubernetes/pkg/api" + kapisext "k8s.io/kubernetes/pkg/apis/extensions" + + deployapi "github.com/openshift/origin/pkg/apps/apis/apps" + buildapi "github.com/openshift/origin/pkg/build/apis/build" + imageapi "github.com/openshift/origin/pkg/image/apis/image" +) + +const ( + Layer1 = "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + Layer2 = "tarsum.dev+sha256:b194de3772ebbcdc8f244f663669799ac1cb141834b7cb8b69100285d357a2b0" + Layer3 = "tarsum.dev+sha256:c937c4bb1c1a21cc6d94340812262c6472092028972ae69b551b1a70d4276171" + Layer4 = "tarsum.dev+sha256:2aaacc362ac6be2b9e9ae8c6029f6f616bb50aec63746521858e47841b90fabd" + Layer5 = "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +) + +var ( + Config1 = "sha256:2b8fd9751c4c0f5dd266fcae00707e67a2545ef34f9a29354585f93dac906749" + Config2 = "sha256:8ddc19f16526912237dd8af81971d5e4dd0587907234be2b83e249518d5b673f" +) + +// ImageList turns the given images into ImageList. +func ImageList(images ...imageapi.Image) imageapi.ImageList { + return imageapi.ImageList{ + Items: images, + } +} + +// AgedImage creates a test image with specified age. +func AgedImage(id, ref string, ageInMinutes int64) imageapi.Image { + return CreatedImage(id, ref, time.Now().Add(time.Duration(ageInMinutes)*time.Minute*-1)) +} + +// CreatedImage creates a test image with the CreationTime set to the given timestamp. +func CreatedImage(id, ref string, created time.Time) imageapi.Image { + image := ImageWithLayers(id, ref, nil, Layer1, Layer2, Layer3, Layer4, Layer5) + image.CreationTimestamp = metav1.NewTime(created) + return image +} + +// SizedImage returns a test image of given size. +func SizedImage(id, ref string, size int64, configName *string) imageapi.Image { + image := ImageWithLayers(id, ref, configName, Layer1, Layer2, Layer3, Layer4, Layer5) + image.CreationTimestamp = metav1.NewTime(metav1.Now().Add(time.Duration(-1) * time.Minute)) + image.DockerImageMetadata.Size = size + + return image +} + +// Image returns a default test image object 120 minutes old. +func Image(id, ref string) imageapi.Image { + return AgedImage(id, ref, 120) +} + +// Image returns a default test image referencing the given layers. +func ImageWithLayers(id, ref string, configName *string, layers ...string) imageapi.Image { + image := imageapi.Image{ + ObjectMeta: metav1.ObjectMeta{ + Name: id, + Annotations: map[string]string{ + imageapi.ManagedByOpenShiftAnnotation: "true", + }, + }, + DockerImageReference: ref, + DockerImageManifestMediaType: schema1.MediaTypeManifest, + } + + if configName != nil { + image.DockerImageMetadata = imageapi.DockerImage{ + ID: *configName, + } + image.DockerImageConfig = fmt.Sprintf("{Digest: %s}", *configName) + image.DockerImageManifestMediaType = schema2.MediaTypeManifest + } + + image.DockerImageLayers = []imageapi.ImageLayer{} + for _, layer := range layers { + image.DockerImageLayers = append(image.DockerImageLayers, imageapi.ImageLayer{Name: layer}) + } + + return image +} + +// UnmanagedImage creates a test image object lacking managed by OpenShift annotation. +func UnmanagedImage(id, ref string, hasAnnotations bool, annotation, value string) imageapi.Image { + image := ImageWithLayers(id, ref, nil) + if !hasAnnotations { + image.Annotations = nil + } else { + delete(image.Annotations, imageapi.ManagedByOpenShiftAnnotation) + image.Annotations[annotation] = value + } + return image +} + +// PodList turns the given pods into PodList. +func PodList(pods ...kapi.Pod) kapi.PodList { + return kapi.PodList{ + Items: pods, + } +} + +// Pod creates and returns a pod having the given docker image references. +func Pod(namespace, name string, phase kapi.PodPhase, containerImages ...string) kapi.Pod { + return AgedPod(namespace, name, phase, -1, containerImages...) +} + +// AgedPod creates and returns a pod of particular age. +func AgedPod(namespace, name string, phase kapi.PodPhase, ageInMinutes int64, containerImages ...string) kapi.Pod { + pod := kapi.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + SelfLink: "/pod/" + name, + }, + Spec: PodSpec(containerImages...), + Status: kapi.PodStatus{ + Phase: phase, + }, + } + + if ageInMinutes >= 0 { + pod.CreationTimestamp = metav1.NewTime(metav1.Now().Add(time.Duration(-1*ageInMinutes) * time.Minute)) + } + + return pod +} + +// PodSpec creates a pod specification having the given docker image references. +func PodSpec(containerImages ...string) kapi.PodSpec { + spec := kapi.PodSpec{ + Containers: []kapi.Container{}, + } + for _, image := range containerImages { + container := kapi.Container{ + Image: image, + } + spec.Containers = append(spec.Containers, container) + } + return spec +} + +// StreamList turns the given streams into StreamList. +func StreamList(streams ...imageapi.ImageStream) imageapi.ImageStreamList { + return imageapi.ImageStreamList{ + Items: streams, + } +} + +// Stream creates and returns a test ImageStream object 1 minute old +func Stream(registry, namespace, name string, tags map[string]imageapi.TagEventList) imageapi.ImageStream { + return AgedStream(registry, namespace, name, -1, tags) +} + +// Stream creates and returns a test ImageStream object of given age. +func AgedStream(registry, namespace, name string, ageInMinutes int64, tags map[string]imageapi.TagEventList) imageapi.ImageStream { + stream := imageapi.ImageStream{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + Status: imageapi.ImageStreamStatus{ + DockerImageRepository: fmt.Sprintf("%s/%s/%s", registry, namespace, name), + Tags: tags, + }, + } + + if ageInMinutes >= 0 { + stream.CreationTimestamp = metav1.NewTime(metav1.Now().Add(time.Duration(-1*ageInMinutes) * time.Minute)) + } + + return stream +} + +// Stream creates an ImageStream object and returns a pointer to it. +func StreamPtr(registry, namespace, name string, tags map[string]imageapi.TagEventList) *imageapi.ImageStream { + s := Stream(registry, namespace, name, tags) + return &s +} + +// Tags creates a map of tags for image stream status. +func Tags(list ...namedTagEventList) map[string]imageapi.TagEventList { + m := make(map[string]imageapi.TagEventList, len(list)) + for _, tag := range list { + m[tag.name] = tag.events + } + return m +} + +type namedTagEventList struct { + name string + events imageapi.TagEventList +} + +// Tag creates tag entries for Tags function. +func Tag(name string, events ...imageapi.TagEvent) namedTagEventList { + return namedTagEventList{ + name: name, + events: imageapi.TagEventList{ + Items: events, + }, + } +} + +// TagEvent creates a TagEvent object. +func TagEvent(id, ref string) imageapi.TagEvent { + return imageapi.TagEvent{ + Image: id, + DockerImageReference: ref, + } +} + +// YoungTagEvent creates a TagEvent with the given created timestamp. +func YoungTagEvent(id, ref string, created metav1.Time) imageapi.TagEvent { + return imageapi.TagEvent{ + Image: id, + Created: created, + DockerImageReference: ref, + } +} + +// RCList turns the given replication controllers into RCList. +func RCList(rcs ...kapi.ReplicationController) kapi.ReplicationControllerList { + return kapi.ReplicationControllerList{ + Items: rcs, + } +} + +// RC creates and returns a ReplicationController. +func RC(namespace, name string, containerImages ...string) kapi.ReplicationController { + return kapi.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + SelfLink: "/rc/" + name, + }, + Spec: kapi.ReplicationControllerSpec{ + Template: &kapi.PodTemplateSpec{ + Spec: PodSpec(containerImages...), + }, + }, + } +} + +// DSList turns the given daemon sets into DaemonSetList. +func DSList(dss ...kapisext.DaemonSet) kapisext.DaemonSetList { + return kapisext.DaemonSetList{ + Items: dss, + } +} + +// DS creates and returns a DaemonSet object. +func DS(namespace, name string, containerImages ...string) kapisext.DaemonSet { + return kapisext.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + SelfLink: "/ds/" + name, + }, + Spec: kapisext.DaemonSetSpec{ + Template: kapi.PodTemplateSpec{ + Spec: PodSpec(containerImages...), + }, + }, + } +} + +// DeploymentList turns the given deployments into DeploymentList. +func DeploymentList(deployments ...kapisext.Deployment) kapisext.DeploymentList { + return kapisext.DeploymentList{ + Items: deployments, + } +} + +// Deployment creates and returns aDeployment object. +func Deployment(namespace, name string, containerImages ...string) kapisext.Deployment { + return kapisext.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + SelfLink: "/deployment/" + name, + }, + Spec: kapisext.DeploymentSpec{ + Template: kapi.PodTemplateSpec{ + Spec: PodSpec(containerImages...), + }, + }, + } +} + +// DCList turns the given deployment configs into DeploymentConfigList. +func DCList(dcs ...deployapi.DeploymentConfig) deployapi.DeploymentConfigList { + return deployapi.DeploymentConfigList{ + Items: dcs, + } +} + +// DC creates and returns a DeploymentConfig object. +func DC(namespace, name string, containerImages ...string) deployapi.DeploymentConfig { + return deployapi.DeploymentConfig{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + SelfLink: "/dc/" + name, + }, + Spec: deployapi.DeploymentConfigSpec{ + Template: &kapi.PodTemplateSpec{ + Spec: PodSpec(containerImages...), + }, + }, + } +} + +// RSList turns the given replica set into ReplicaSetList. +func RSList(rss ...kapisext.ReplicaSet) kapisext.ReplicaSetList { + return kapisext.ReplicaSetList{ + Items: rss, + } +} + +// RS creates and returns a ReplicaSet object. +func RS(namespace, name string, containerImages ...string) kapisext.ReplicaSet { + return kapisext.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + SelfLink: "/rs/" + name, + }, + Spec: kapisext.ReplicaSetSpec{ + Template: kapi.PodTemplateSpec{ + Spec: PodSpec(containerImages...), + }, + }, + } +} + +// BCList turns the given build configs into BuildConfigList. +func BCList(bcs ...buildapi.BuildConfig) buildapi.BuildConfigList { + return buildapi.BuildConfigList{ + Items: bcs, + } +} + +// BC creates and returns a BuildConfig object. +func BC(namespace, name, strategyType, fromKind, fromNamespace, fromName string) buildapi.BuildConfig { + return buildapi.BuildConfig{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + SelfLink: "/bc/" + name, + }, + Spec: buildapi.BuildConfigSpec{ + CommonSpec: CommonSpec(strategyType, fromKind, fromNamespace, fromName), + }, + } +} + +// BuildList turns the given builds into BuildList. +func BuildList(builds ...buildapi.Build) buildapi.BuildList { + return buildapi.BuildList{ + Items: builds, + } +} + +// Build creates and returns a Build object. +func Build(namespace, name, strategyType, fromKind, fromNamespace, fromName string) buildapi.Build { + return buildapi.Build{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + SelfLink: "/build/" + name, + }, + Spec: buildapi.BuildSpec{ + CommonSpec: CommonSpec(strategyType, fromKind, fromNamespace, fromName), + }, + } +} + +// LimitList turns the given limits into LimitRanges. +func LimitList(limits ...int64) []*kapi.LimitRange { + list := make([]*kapi.LimitRange, len(limits)) + for _, limit := range limits { + quantity := resource.NewQuantity(limit, resource.BinarySI) + list = append(list, &kapi.LimitRange{ + Spec: kapi.LimitRangeSpec{ + Limits: []kapi.LimitRangeItem{ + { + Type: imageapi.LimitTypeImage, + Max: kapi.ResourceList{ + kapi.ResourceStorage: *quantity, + }, + }, + }, + }, + }) + } + return list +} + +// CommonSpec creates and returns CommonSpec object. +func CommonSpec(strategyType, fromKind, fromNamespace, fromName string) buildapi.CommonSpec { + spec := buildapi.CommonSpec{ + Strategy: buildapi.BuildStrategy{}, + } + switch strategyType { + case "source": + spec.Strategy.SourceStrategy = &buildapi.SourceBuildStrategy{ + From: kapi.ObjectReference{ + Kind: fromKind, + Namespace: fromNamespace, + Name: fromName, + }, + } + case "docker": + spec.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{ + From: &kapi.ObjectReference{ + Kind: fromKind, + Namespace: fromNamespace, + Name: fromName, + }, + } + case "custom": + spec.Strategy.CustomStrategy = &buildapi.CustomBuildStrategy{ + From: kapi.ObjectReference{ + Kind: fromKind, + Namespace: fromNamespace, + Name: fromName, + }, + } + } + + return spec +} diff --git a/pkg/oc/admin/prune/images.go b/pkg/oc/admin/prune/images.go index 52484e4934a1..fed548b7ba12 100644 --- a/pkg/oc/admin/prune/images.go +++ b/pkg/oc/admin/prune/images.go @@ -2,6 +2,7 @@ package prune import ( "crypto/x509" + "encoding/json" "errors" "fmt" "io" @@ -15,14 +16,19 @@ import ( "time" "github.com/spf13/cobra" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kutilerrors "k8s.io/apimachinery/pkg/util/errors" knet "k8s.io/apimachinery/pkg/util/net" + discovery "k8s.io/client-go/discovery" restclient "k8s.io/client-go/rest" + kclientcmd "k8s.io/client-go/tools/clientcmd" kapi "k8s.io/kubernetes/pkg/api" kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + //kubeversion "k8s.io/kubernetes/pkg/version" appsclient "github.com/openshift/origin/pkg/apps/generated/internalclientset/typed/apps/internalversion" buildclient "github.com/openshift/origin/pkg/build/generated/internalclientset/typed/build/internalversion" @@ -33,6 +39,7 @@ import ( "github.com/openshift/origin/pkg/image/prune" oserrors "github.com/openshift/origin/pkg/util/errors" "github.com/openshift/origin/pkg/util/netutils" + "github.com/openshift/origin/pkg/version" ) // PruneImagesRecommendedName is the recommended command name @@ -112,12 +119,14 @@ type PruneImagesOptions struct { Namespace string ForceInsecure bool - ClientConfig *restclient.Config - AppsClient appsclient.AppsInterface - BuildClient buildclient.BuildInterface - ImageClient imageclient.ImageInterface - KubeClient kclientset.Interface - Out io.Writer + ClientConfig *restclient.Config + AppsClient appsclient.AppsInterface + BuildClient buildclient.BuildInterface + ImageClient imageclient.ImageInterface + DiscoveryClient discovery.DiscoveryInterface + KubeClient kclientset.Interface + Out io.Writer + ErrOut io.Writer } // NewCmdPruneImages implements the OpenShift cli prune images command. @@ -184,6 +193,7 @@ func (o *PruneImagesOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, } } o.Out = out + o.ErrOut = os.Stderr clientConfig, err := f.ClientConfig() if err != nil { @@ -199,6 +209,7 @@ func (o *PruneImagesOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, o.BuildClient = buildClient o.ImageClient = imageClient o.KubeClient = kubeClient + o.DiscoveryClient = kubeClient.Discovery() return nil } @@ -268,7 +279,7 @@ func (o PruneImagesOptions) Run() error { if !kerrors.IsForbidden(err) { return err } - fmt.Fprintf(os.Stderr, "Failed to list daemonsets: %v\n - * Make sure to update clusterRoleBindings.\n", err) + fmt.Fprintf(o.ErrOut, "Failed to list daemonsets: %v\n - * Make sure to update clusterRoleBindings.\n", err) } allDeployments, err := o.KubeClient.Extensions().Deployments(o.Namespace).List(metav1.ListOptions{}) @@ -277,7 +288,7 @@ func (o PruneImagesOptions) Run() error { if !kerrors.IsForbidden(err) { return err } - fmt.Fprintf(os.Stderr, "Failed to list deployments: %v\n - * Make sure to update clusterRoleBindings.\n", err) + fmt.Fprintf(o.ErrOut, "Failed to list deployments: %v\n - * Make sure to update clusterRoleBindings.\n", err) } allDCs, err := o.AppsClient.DeploymentConfigs(o.Namespace).List(metav1.ListOptions{}) @@ -291,7 +302,7 @@ func (o PruneImagesOptions) Run() error { if !kerrors.IsForbidden(err) { return err } - fmt.Fprintf(os.Stderr, "Failed to list replicasets: %v\n - * Make sure to update clusterRoleBindings.\n", err) + fmt.Fprintf(o.ErrOut, "Failed to list replicasets: %v\n - * Make sure to update clusterRoleBindings.\n", err) } limitRangesList, err := o.KubeClient.Core().LimitRanges(o.Namespace).List(metav1.ListOptions{}) @@ -372,19 +383,20 @@ func (o PruneImagesOptions) Run() error { if o.Namespace != metav1.NamespaceAll { options.Namespace = o.Namespace } - pruner, err := prune.NewPruner(options) - if err != nil { - return err + pruner, errs := prune.NewPruner(options) + if errs != nil { + printGraphBuildErrors(o.DiscoveryClient, errs, o.ErrOut) + return fmt.Errorf("failed to build graph - no changes made") } w := tabwriter.NewWriter(o.Out, 10, 4, 3, ' ', 0) defer w.Flush() - imageDeleter := &describingImageDeleter{w: w} - imageStreamDeleter := &describingImageStreamDeleter{w: w} - layerLinkDeleter := &describingLayerLinkDeleter{w: w} - blobDeleter := &describingBlobDeleter{w: w} - manifestDeleter := &describingManifestDeleter{w: w} + imageDeleter := &describingImageDeleter{w: w, errOut: o.ErrOut} + imageStreamDeleter := &describingImageStreamDeleter{w: w, errOut: o.ErrOut} + layerLinkDeleter := &describingLayerLinkDeleter{w: w, errOut: o.ErrOut} + blobDeleter := &describingBlobDeleter{w: w, errOut: o.ErrOut} + manifestDeleter := &describingManifestDeleter{w: w, errOut: o.ErrOut} if o.Confirm { imageDeleter.delegate = prune.NewImageDeleter(o.ImageClient) @@ -393,7 +405,7 @@ func (o PruneImagesOptions) Run() error { blobDeleter.delegate = prune.NewBlobDeleter() manifestDeleter.delegate = prune.NewManifestDeleter() } else { - fmt.Fprintln(os.Stderr, "Dry run enabled - no modifications will be made. Add --confirm to remove images") + fmt.Fprintln(o.ErrOut, "Dry run enabled - no modifications will be made. Add --confirm to remove images") } return pruner.Prune(imageDeleter, imageStreamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) @@ -405,6 +417,7 @@ type describingImageStreamDeleter struct { w io.Writer delegate prune.ImageStreamDeleter headerPrinted bool + errOut io.Writer } var _ prune.ImageStreamDeleter = &describingImageStreamDeleter{} @@ -420,7 +433,7 @@ func (p *describingImageStreamDeleter) UpdateImageStream(stream *imageapi.ImageS updatedStream, err := p.delegate.UpdateImageStream(stream) if err != nil { - fmt.Fprintf(os.Stderr, "error updating image stream %s/%s to remove image references: %v\n", stream.Namespace, stream.Name, err) + fmt.Fprintf(p.errOut, "error updating image stream %s/%s to remove image references: %v\n", stream.Namespace, stream.Name, err) } return updatedStream, err @@ -447,6 +460,7 @@ type describingImageDeleter struct { w io.Writer delegate prune.ImageDeleter headerPrinted bool + errOut io.Writer } var _ prune.ImageDeleter = &describingImageDeleter{} @@ -466,7 +480,7 @@ func (p *describingImageDeleter) DeleteImage(image *imageapi.Image) error { err := p.delegate.DeleteImage(image) if err != nil { - fmt.Fprintf(os.Stderr, "error deleting image %s from server: %v\n", image.Name, err) + fmt.Fprintf(p.errOut, "error deleting image %s from server: %v\n", image.Name, err) } return err @@ -478,6 +492,7 @@ type describingLayerLinkDeleter struct { w io.Writer delegate prune.LayerLinkDeleter headerPrinted bool + errOut io.Writer } var _ prune.LayerLinkDeleter = &describingLayerLinkDeleter{} @@ -497,7 +512,7 @@ func (p *describingLayerLinkDeleter) DeleteLayerLink(registryClient *http.Client err := p.delegate.DeleteLayerLink(registryClient, registryURL, repo, name) if err != nil { - fmt.Fprintf(os.Stderr, "error deleting repository %s layer link %s from the registry: %v\n", repo, name, err) + fmt.Fprintf(p.errOut, "error deleting repository %s layer link %s from the registry: %v\n", repo, name, err) } return err @@ -509,6 +524,7 @@ type describingBlobDeleter struct { w io.Writer delegate prune.BlobDeleter headerPrinted bool + errOut io.Writer } var _ prune.BlobDeleter = &describingBlobDeleter{} @@ -528,7 +544,7 @@ func (p *describingBlobDeleter) DeleteBlob(registryClient *http.Client, registry err := p.delegate.DeleteBlob(registryClient, registryURL, layer) if err != nil { - fmt.Fprintf(os.Stderr, "error deleting blob %s from the registry: %v\n", layer, err) + fmt.Fprintf(p.errOut, "error deleting blob %s from the registry: %v\n", layer, err) } return err @@ -541,6 +557,7 @@ type describingManifestDeleter struct { w io.Writer delegate prune.ManifestDeleter headerPrinted bool + errOut io.Writer } var _ prune.ManifestDeleter = &describingManifestDeleter{} @@ -560,7 +577,7 @@ func (p *describingManifestDeleter) DeleteManifest(registryClient *http.Client, err := p.delegate.DeleteManifest(registryClient, registryURL, repo, manifest) if err != nil { - fmt.Fprintf(os.Stderr, "error deleting manifest %s from repository %s: %v\n", manifest, repo, err) + fmt.Fprintf(p.errOut, "error deleting manifest %s from repository %s: %v\n", manifest, repo, err) } return err @@ -674,3 +691,75 @@ func getRegistryClient(clientConfig *restclient.Config, registryCABundle string, Transport: wrappedTransport, }, nil } + +func getClientAndMasterVersions(client discovery.DiscoveryInterface) (clientVersion, masterVersion *version.Info, err error) { + done := make(chan error) + + go func() { + defer close(done) + + ocVersionBody, err := client.RESTClient().Get().AbsPath("/version/openshift").Do().Raw() + switch { + case err == nil: + var ocServerInfo version.Info + err = json.Unmarshal(ocVersionBody, &ocServerInfo) + if err != nil && len(ocVersionBody) > 0 { + done <- err + return + } + masterVersion = &ocServerInfo + + case kerrors.IsNotFound(err) || kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err): + default: + done <- err + return + } + }() + + select { + case err, closed := <-done: + if strings.HasSuffix(fmt.Sprintf("%v", err), "connection refused") || clientcmd.IsConfigurationMissing(err) || kclientcmd.IsConfigurationInvalid(err) { + return nil, nil, err + } + if closed && err != nil { + return nil, nil, err + } + case <-time.After(time.Second): + return nil, nil, fmt.Errorf("error: server took too long to respond with version information.") + } + + v := version.Get() + clientVersion = &v + + return +} + +func printGraphBuildErrors(discoveryClient discovery.DiscoveryInterface, errs kutilerrors.Aggregate, errOut io.Writer) { + refErrors := []error{} + + fmt.Fprintf(errOut, "Failed to build graph!\n") + + for _, err := range errs.Errors() { + if _, ok := err.(*prune.ErrBadReference); ok { + refErrors = append(refErrors, err) + } else { + fmt.Fprintf(errOut, "%v\n", err) + } + } + + if len(refErrors) > 0 { + clientVersion, masterVersion, err := getClientAndMasterVersions(discoveryClient) + if err != nil { + fmt.Fprintf(errOut, "Failed to get master API version: %v\n", err) + } + fmt.Fprintf(errOut, "\nThe following objects have invalid references:\n\n") + for _, err := range refErrors { + fmt.Fprintf(errOut, " %s\n", err) + } + fmt.Fprintf(errOut, "\nEither fix the references or delete the objects to make the pruner proceed.\n") + + if masterVersion != nil && (clientVersion.Major != masterVersion.Major || clientVersion.Minor != masterVersion.Minor) { + fmt.Fprintf(errOut, "Client version (%s) doesn't match master (%s), which may allow for different image references. Try to re-run this binary with the same version.\n", clientVersion, masterVersion) + } + } +} diff --git a/pkg/oc/admin/prune/images_test.go b/pkg/oc/admin/prune/images_test.go index 6fbcb1167b64..577592edb6a7 100644 --- a/pkg/oc/admin/prune/images_test.go +++ b/pkg/oc/admin/prune/images_test.go @@ -1,17 +1,39 @@ package prune import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" "io/ioutil" + "net/http" + "os" + "strings" "testing" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/kubernetes/scheme" + restclient "k8s.io/client-go/rest" + kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/diff" + + restfake "k8s.io/client-go/rest/fake" appsclient "github.com/openshift/origin/pkg/apps/generated/internalclientset/fake" buildclient "github.com/openshift/origin/pkg/build/generated/internalclientset/fake" imageclient "github.com/openshift/origin/pkg/image/generated/internalclientset/fake" + "github.com/openshift/origin/pkg/image/prune/testutil" + "github.com/openshift/origin/pkg/version" ) +var logLevel = flag.Int("loglevel", 0, "") + func TestImagePruneNamespaced(t *testing.T) { + flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel)) kFake := fake.NewSimpleClientset() imageFake := imageclient.NewSimpleClientset() opts := &PruneImagesOptions{ @@ -22,6 +44,7 @@ func TestImagePruneNamespaced(t *testing.T) { ImageClient: imageFake.Image(), KubeClient: kFake, Out: ioutil.Discard, + ErrOut: os.Stderr, } if err := opts.Run(); err != nil { @@ -46,3 +69,119 @@ func TestImagePruneNamespaced(t *testing.T) { } } } + +func TestImagePruneErrOnBadReference(t *testing.T) { + flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel)) + podBad := testutil.Pod("foo", "pod1", kapi.PodRunning, "invalid image reference") + podGood := testutil.Pod("foo", "pod2", kapi.PodRunning, "example.com/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000") + dep := testutil.Deployment("foo", "dep1", "do not blame me") + bcBad := testutil.BC("foo", "bc1", "source", "ImageStreamImage", "foo", "bar:invalid-digest") + + kFake := fake.NewSimpleClientset(&podBad, &podGood, &dep) + imageFake := imageclient.NewSimpleClientset() + fakeDiscovery := &fakeVersionDiscovery{ + masterVersion: version.Get(), + } + + switch d := kFake.Discovery().(type) { + case *fakediscovery.FakeDiscovery: + fakeDiscovery.FakeDiscovery = d + default: + t.Fatalf("unexpected discovery type: %T != %T", d, &fakediscovery.FakeDiscovery{}) + } + + errBuf := bytes.NewBuffer(make([]byte, 0, 4096)) + opts := &PruneImagesOptions{ + AppsClient: appsclient.NewSimpleClientset().Apps(), + BuildClient: buildclient.NewSimpleClientset(&bcBad).Build(), + ImageClient: imageFake.Image(), + KubeClient: kFake, + DiscoveryClient: fakeDiscovery, + Out: ioutil.Discard, + ErrOut: errBuf, + } + + verifyOutput := func(out string, expectClientVersionMismatch bool) { + t.Logf("pruner error output: %s\n", out) + + badRefErrors := sets.NewString() + for _, l := range strings.Split(out, "\n") { + if strings.HasPrefix(l, " ") { + badRefErrors.Insert(l[2:]) + } + } + expBadRefErrors := sets.NewString( + `Pod[foo/pod1]: invalid docker image reference "invalid image reference": invalid reference format`, + `BuildConfig[foo/bc1]: invalid ImageStreamImage reference "bar:invalid-digest": expected exactly one @ in the isimage name "bar:invalid-digest"`, + `Deployment[foo/dep1]: invalid docker image reference "do not blame me": invalid reference format`) + + if a, e := badRefErrors, expBadRefErrors; !a.Equal(e) { + t.Fatalf("got unexpected invalid reference errors: %s", diff.ObjectDiff(a, e)) + } + + if expectClientVersionMismatch { + if msg := "client version"; !strings.Contains(strings.ToLower(out), msg) { + t.Errorf("expected message %q is not contained in the output", msg) + } + } else { + for _, msg := range []string{"failed to get master api version", "client version"} { + if strings.Contains(strings.ToLower(out), msg) { + t.Errorf("got unexpected message %q in the output", msg) + } + } + } + } + + err := opts.Run() + if err == nil { + t.Fatal("Unexpected non-error") + } + + t.Logf("pruner error: %s\n", err) + verifyOutput(errBuf.String(), false) + + t.Logf("bump master version and try again") + fakeDiscovery.masterVersion.Minor += "1" + errBuf.Reset() + err = opts.Run() + if err == nil { + t.Fatal("Unexpected non-error") + } + + t.Logf("pruner error: %s\n", err) + verifyOutput(errBuf.String(), true) +} + +type fakeVersionDiscovery struct { + *fakediscovery.FakeDiscovery + masterVersion version.Info +} + +func (f *fakeVersionDiscovery) RESTClient() restclient.Interface { + return &restfake.RESTClient{ + APIRegistry: kapi.Registry, + NegotiatedSerializer: scheme.Codecs, + Client: restfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + if req.URL.Path != "/version/openshift" { + return &http.Response{ + StatusCode: http.StatusNotFound, + }, nil + } + header := http.Header{} + header.Set("Content-Type", runtime.ContentTypeJSON) + return &http.Response{ + StatusCode: http.StatusOK, + Header: header, + Body: objBody(&f.masterVersion), + }, nil + }), + } +} + +func objBody(object interface{}) io.ReadCloser { + output, err := json.MarshalIndent(object, "", "") + if err != nil { + panic(err) + } + return ioutil.NopCloser(bytes.NewReader([]byte(output))) +} diff --git a/pkg/oc/admin/top/graph.go b/pkg/oc/admin/top/graph.go index a683b533945b..553c37bbd71f 100644 --- a/pkg/oc/admin/top/graph.go +++ b/pkg/oc/admin/top/graph.go @@ -74,13 +74,12 @@ func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList) { for tag, history := range stream.Status.Tags { for i := range history.Items { image := history.Items[i] - n := imagegraph.FindImage(g, image.Image) - if n == nil { + imageNode := imagegraph.FindImage(g, image.Image) + if imageNode == nil { glog.V(2).Infof("Unable to find image %q in graph (from tag=%q, dockerImageReference=%s)", history.Items[i].Image, tag, image.DockerImageReference) continue } - imageNode := n.(*imagegraph.ImageNode) glog.V(4).Infof("Adding edge from %q to %q", imageStreamNode.UniqueName(), imageNode.UniqueName()) edgeKind := ImageStreamImageEdgeKind if i > 1 {