// 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) { for j := range spec.Containers { container := spec.Containers[j] glog.V(4).Infof("Examining container image %q", container.Image) ref, err := imageapi.ParseDockerImageReference(container.Image) if err != nil { util.HandleError(fmt.Errorf("unable to parse DockerImageReference %q: %v", container.Image, err)) continue } if len(ref.ID) == 0 { glog.V(4).Infof("%q has no image ID", container.Image) continue } imageNode := imagegraph.FindImage(g, ref.ID) if imageNode == nil { glog.Infof("Unable to find image %q in the graph", ref.ID) continue } glog.V(4).Infof("Adding edge from pod to image") g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) } }
func AddHPAScaleRefEdges(g osgraph.Graph) { for _, node := range g.NodesByKind(kubegraph.HorizontalPodAutoscalerNodeKind) { hpaNode := node.(*kubegraph.HorizontalPodAutoscalerNode) syntheticMeta := kapi.ObjectMeta{ Name: hpaNode.HorizontalPodAutoscaler.Spec.ScaleRef.Name, Namespace: hpaNode.HorizontalPodAutoscaler.Namespace, } var groupVersionResource unversioned.GroupVersionResource resource := strings.ToLower(hpaNode.HorizontalPodAutoscaler.Spec.ScaleRef.Kind) if groupVersion, err := unversioned.ParseGroupVersion(hpaNode.HorizontalPodAutoscaler.Spec.ScaleRef.APIVersion); err == nil { groupVersionResource = groupVersion.WithResource(resource) } else { groupVersionResource = unversioned.GroupVersionResource{Resource: resource} } groupVersionResource, err := registered.RESTMapper().ResourceFor(groupVersionResource) if err != nil { continue } var syntheticNode graph.Node switch groupVersionResource.GroupResource() { case kapi.Resource("replicationcontrollers"): syntheticNode = kubegraph.FindOrCreateSyntheticReplicationControllerNode(g, &kapi.ReplicationController{ObjectMeta: syntheticMeta}) case deployapi.Resource("deploymentconfigs"): syntheticNode = deploygraph.FindOrCreateSyntheticDeploymentConfigNode(g, &deployapi.DeploymentConfig{ObjectMeta: syntheticMeta}) default: continue } g.AddEdge(hpaNode, syntheticNode, ScalingEdgeKind) } }
func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList) { for i := range streams.Items { stream := &streams.Items[i] glog.V(4).Infof("Adding ImageStream %s/%s to graph", stream.Namespace, stream.Name) isNode := imagegraph.EnsureImageStreamNode(g, stream) imageStreamNode := isNode.(*imagegraph.ImageStreamNode) // connect IS with underlying images 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 { 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 { edgeKind = HistoricImageStreamImageEdgeKind } g.AddEdge(imageStreamNode, imageNode, edgeKind) } } } }
func AddMountedSecretEdges(g osgraph.Graph, podSpec *kubegraph.PodSpecNode) { //pod specs are always contained. We'll get the toplevel container so that we can pull a namespace from it containerNode := osgraph.GetTopLevelContainerNode(g, podSpec) containerObj := g.GraphDescriber.Object(containerNode) meta, err := kapi.ObjectMetaFor(containerObj.(runtime.Object)) if err != nil { // this should never happen. it means that a podSpec is owned by a top level container that is not a runtime.Object panic(err) } for _, volume := range podSpec.Volumes { source := volume.VolumeSource if source.Secret == nil { continue } // pod secrets must be in the same namespace syntheticSecret := &kapi.Secret{} syntheticSecret.Namespace = meta.Namespace syntheticSecret.Name = source.Secret.SecretName secretNode := kubegraph.FindOrCreateSyntheticSecretNode(g, syntheticSecret) g.AddEdge(podSpec, secretNode, MountedSecretEdgeKind) } }
func addPodSpecToGraph(g graph.Graph, spec *kapi.PodSpec, predecessor gonum.Node) { for j := range spec.Containers { container := spec.Containers[j] glog.V(4).Infof("Examining container image %q", container.Image) ref, err := imageapi.ParseDockerImageReference(container.Image) if err != nil { glog.V(2).Infof("Unable to parse DockerImageReference %q: %v - skipping", container.Image, err) continue } if len(ref.ID) == 0 { // ignore not managed images continue } imageNode := imagegraph.FindImage(g, ref.ID) if imageNode == nil { glog.V(1).Infof("Unable to find image %q in the graph", ref.ID) continue } glog.V(4).Infof("Adding edge from %v to %v", predecessor, imageNode) g.AddEdge(predecessor, imageNode, PodImageEdgeKind) } }
func addImagesToGraph(g graph.Graph, images *imageapi.ImageList) { for i := range images.Items { image := &images.Items[i] glog.V(4).Infof("Adding image %q to graph", image.Name) imageNode := imagegraph.EnsureImageNode(g, image) topLayerAdded := false // We're looking through layers in reversed order since we need to // find first layer (from top) which is not an empty layer, we're omitting // empty layers because every image has those and they're giving us // false positives about parents. This applies only to schema v1 images // schema v2 does not have that problem. for i := len(image.DockerImageLayers) - 1; i >= 0; i-- { layer := image.DockerImageLayers[i] layerNode := imagegraph.EnsureImageLayerNode(g, layer.Name) edgeKind := ImageLayerEdgeKind if !topLayerAdded && layer.Name != digest.DigestSha256EmptyTar { edgeKind = ImageTopLayerEdgeKind topLayerAdded = true } g.AddEdge(imageNode, layerNode, edgeKind) glog.V(4).Infof("Adding image layer %q to graph (%q)", layer.Name, edgeKind) } } }
// addImagesToGraph adds all images to the graph that belong to one of the // 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) { for i := range images.Items { image := &images.Items[i] glog.V(4).Infof("Examining image %q", image.Name) if image.Annotations == nil { glog.V(4).Infof("Image %q with DockerImageReference %q belongs to an external registry - skipping", image.Name, image.DockerImageReference) continue } if value, ok := image.Annotations[imageapi.ManagedByOpenShiftAnnotation]; !ok || value != "true" { glog.V(4).Infof("Image %q with DockerImageReference %q belongs to an external registry - skipping", image.Name, image.DockerImageReference) continue } age := unversioned.Now().Sub(image.CreationTimestamp.Time) if !algorithm.pruneOverSizeLimit && age < algorithm.keepYoungerThan { glog.V(4).Infof("Image %q is younger than minimum pruning age, skipping (age=%v)", image.Name, age) continue } glog.V(4).Infof("Adding image %q to graph", image.Name) imageNode := imagegraph.EnsureImageNode(g, image) for _, layer := range image.DockerImageLayers { glog.V(4).Infof("Adding image layer %q to graph", layer.Name) layerNode := imagegraph.EnsureImageLayerNode(g, layer.Name) g.AddEdge(imageNode, layerNode, ReferencedImageLayerEdgeKind) } } }
// addImageStreamsToGraph adds all the streams to the graph. The most recent n // image revisions for a tag will be preserved, where n is specified by the // algorithm's keepTagRevisions. Image revisions older than n are candidates // for pruning. if the image stream's age is at least as old as the minimum // threshold in algorithm. Otherwise, if the image stream is younger than the // threshold, all image revisions for that stream are ineligible for pruning. // // 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, algorithm pruneAlgorithm) { for i := range streams.Items { stream := &streams.Items[i] glog.V(4).Infof("Examining ImageStream %s/%s", stream.Namespace, stream.Name) // use a weak reference for old image revisions by default oldImageRevisionReferenceKind := WeakReferencedImageEdgeKind age := util.Now().Sub(stream.CreationTimestamp.Time) if age < algorithm.keepYoungerThan { // stream's age is below threshold - use a strong reference for old image revisions instead glog.V(4).Infof("Stream %s/%s is below age threshold - none of its images are eligible for pruning", stream.Namespace, stream.Name) oldImageRevisionReferenceKind = ReferencedImageEdgeKind } glog.V(4).Infof("Adding ImageStream %s/%s to graph", stream.Namespace, stream.Name) isNode := imagegraph.EnsureImageStreamNode(g, stream) imageStreamNode := isNode.(*imagegraph.ImageStreamNode) for tag, history := range stream.Status.Tags { for i := range history.Items { n := imagegraph.FindImage(g, history.Items[i].Image) if n == nil { glog.V(2).Infof("Unable to find image %q in graph (from tag=%q, revision=%d, dockerImageReference=%s)", history.Items[i].Image, tag, i, history.Items[i].DockerImageReference) continue } imageNode := n.(*imagegraph.ImageNode) var kind string switch { case i < algorithm.keepTagRevisions: kind = ReferencedImageEdgeKind default: kind = oldImageRevisionReferenceKind } glog.V(4).Infof("Checking for existing strong reference from stream %s/%s to image %s", stream.Namespace, stream.Name, imageNode.Image.Name) if edge := g.Edge(imageStreamNode, imageNode); edge != nil && g.EdgeKinds(edge).Has(ReferencedImageEdgeKind) { glog.V(4).Infof("Strong reference found") continue } glog.V(4).Infof("Adding edge (kind=%d) from %q to %q", kind, imageStreamNode.UniqueName.UniqueName(), imageNode.UniqueName.UniqueName()) g.AddEdge(imageStreamNode, imageNode, kind) glog.V(4).Infof("Adding stream->layer references") // add stream -> layer references so we can prune them later for _, s := range g.From(imageNode) { if g.Kind(s) != imagegraph.ImageLayerNodeKind { continue } glog.V(4).Infof("Adding reference from stream %q to layer %q", stream.Name, s.(*imagegraph.ImageLayerNode).Layer) g.AddEdge(imageStreamNode, s, ReferencedImageLayerEdgeKind) } } } } }
func AddMountableSecretEdges(g osgraph.Graph, saNode *kubegraph.ServiceAccountNode) { for _, mountableSecret := range saNode.ServiceAccount.Secrets { syntheticSecret := &kapi.Secret{} syntheticSecret.Namespace = saNode.ServiceAccount.Namespace syntheticSecret.Name = mountableSecret.Name secretNode := kubegraph.FindOrCreateSyntheticSecretNode(g, syntheticSecret) g.AddEdge(saNode, secretNode, MountableSecretEdgeKind) } }
// addImagesToGraph adds all images to the graph that belong to one of the // 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) { for i := range images.Items { image := &images.Items[i] glog.V(4).Infof("Examining image %q", image.Name) if image.Annotations == nil { glog.V(4).Infof("Image %q with DockerImageReference %q belongs to an external registry - skipping", image.Name, image.DockerImageReference) continue } if value, ok := image.Annotations[imageapi.ManagedByOpenShiftAnnotation]; !ok || value != "true" { glog.V(4).Infof("Image %q with DockerImageReference %q belongs to an external registry - skipping", image.Name, image.DockerImageReference) continue } age := unversioned.Now().Sub(image.CreationTimestamp.Time) if !algorithm.pruneOverSizeLimit && age < algorithm.keepYoungerThan { glog.V(4).Infof("Image %q is younger than minimum pruning age, skipping (age=%v)", image.Name, age) continue } glog.V(4).Infof("Adding image %q to graph", image.Name) imageNode := imagegraph.EnsureImageNode(g, image) manifest := imageapi.DockerImageManifest{} if err := json.Unmarshal([]byte(image.DockerImageManifest), &manifest); err != nil { utilruntime.HandleError(fmt.Errorf("unable to extract manifest from image: %v. This image's layers won't be pruned if the image is pruned now.", err)) continue } // schema1 layers for _, layer := range manifest.FSLayers { glog.V(4).Infof("Adding image layer v1 %q to graph", layer.DockerBlobSum) layerNode := imagegraph.EnsureImageLayerNode(g, layer.DockerBlobSum) g.AddEdge(imageNode, layerNode, ReferencedImageLayerEdgeKind) } // schema2 layers for _, layer := range manifest.Layers { glog.V(4).Infof("Adding image layer v2 %q to graph", layer.Digest) layerNode := imagegraph.EnsureImageLayerNode(g, layer.Digest) g.AddEdge(imageNode, layerNode, ReferencedImageLayerEdgeKind) } } }
func AddVolumeClaimEdges(g osgraph.Graph, dcNode *deploygraph.DeploymentConfigNode) { for _, volume := range dcNode.DeploymentConfig.Spec.Template.Spec.Volumes { source := volume.VolumeSource if source.PersistentVolumeClaim == nil { continue } syntheticClaim := &kapi.PersistentVolumeClaim{ ObjectMeta: kapi.ObjectMeta{ Name: source.PersistentVolumeClaim.ClaimName, Namespace: dcNode.DeploymentConfig.Namespace, }, } pvcNode := kubegraph.FindOrCreateSyntheticPVCNode(g, syntheticClaim) // TODO: Consider direction g.AddEdge(dcNode, pvcNode, VolumeClaimEdgeKind) } }
// addBuildStrategyImageReferencesToGraph ads references from the build strategy's parent node to the image // the build strategy references. // // 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) { glog.V(4).Infof("Examining build strategy with type %q", strategy.Type) from := buildutil.GetImageStreamForStrategy(strategy) if from == nil { glog.V(4).Infof("Unable to determine 'from' reference - skipping") return } glog.V(4).Infof("Examining build strategy with from: %#v", from) var imageID string switch from.Kind { case "ImageStreamImage": _, id, err := imagestreamimage.ParseNameAndID(from.Name) if err != nil { glog.V(2).Infof("Error parsing ImageStreamImage name %q: %v - skipping", from.Name, err) return } imageID = id case "DockerImage": ref, err := imageapi.ParseDockerImageReference(from.Name) if err != nil { glog.V(2).Infof("Error parsing DockerImage name %q: %v - skipping", from.Name, err) return } imageID = ref.ID default: return } glog.V(4).Infof("Looking for image %q in graph", imageID) imageNode := imagegraph.FindImage(g, imageID) if imageNode == nil { glog.V(4).Infof("Unable to find image %q in graph - skipping", imageID) return } glog.V(4).Infof("Adding edge from %v to %v", predecessor, imageNode) g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) }
func AddRequestedServiceAccountEdges(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) { //pod specs are always contained. We'll get the toplevel container so that we can pull a namespace from it containerNode := osgraph.GetTopLevelContainerNode(g, podSpecNode) containerObj := g.GraphDescriber.Object(containerNode) meta, err := kapi.ObjectMetaFor(containerObj.(runtime.Object)) if err != nil { panic(err) } // if no SA name is present, admission will set 'default' name := "default" if len(podSpecNode.ServiceAccountName) > 0 { name = podSpecNode.ServiceAccountName } syntheticSA := &kapi.ServiceAccount{} syntheticSA.Namespace = meta.Namespace syntheticSA.Name = name saNode := kubegraph.FindOrCreateSyntheticServiceAccountNode(g, syntheticSA) g.AddEdge(podSpecNode, saNode, ReferencedServiceAccountEdgeKind) }
func markParentsInGraph(g graph.Graph) { imageNodes := getImageNodes(g.Nodes()) for _, in := range imageNodes { // find image's top layer, should be just one for _, e := range g.OutboundEdges(in, ImageTopLayerEdgeKind) { layerNode, _ := e.To().(*imagegraph.ImageLayerNode) // find image's containing this layer but not being their top layer for _, ed := range g.InboundEdges(layerNode, ImageLayerEdgeKind) { childNode, _ := ed.From().(*imagegraph.ImageNode) if in.ID() == childNode.ID() { // don't add self edge, otherwise gonum/graph will panic continue } g.AddEdge(in, childNode, ParentImageEdgeKind) } // TODO: Find image's containing THIS layer being their top layer, // this happens when image contents is not being changed. // TODO: If two layers have exactly the same contents the current // mechanism might trip over that as well. We should check for // a series of layers when checking for parents. } } }
// addImageStreamsToGraph adds all the streams to the graph. The most recent n // image revisions for a tag will be preserved, where n is specified by the // algorithm's keepTagRevisions. Image revisions older than n are candidates // for pruning if the image stream's age is at least as old as the minimum // threshold in algorithm. Otherwise, if the image stream is younger than the // threshold, all image revisions for that stream are ineligible for pruning. // If pruneOverSizeLimit flag is set to true, above does not matter, instead // all images size is checked against LimitRanges defined in that same namespace, // and whenever its size exceeds the smallest limit in that namespace, it will be // considered a candidate for pruning. // // 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) { for i := range streams.Items { stream := &streams.Items[i] glog.V(4).Infof("Examining ImageStream %s/%s", stream.Namespace, stream.Name) // use a weak reference for old image revisions by default oldImageRevisionReferenceKind := WeakReferencedImageEdgeKind age := unversioned.Now().Sub(stream.CreationTimestamp.Time) if !algorithm.pruneOverSizeLimit && age < 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/%s to graph", stream.Namespace, stream.Name) isNode := imagegraph.EnsureImageStreamNode(g, stream) imageStreamNode := isNode.(*imagegraph.ImageStreamNode) for tag, history := range stream.Status.Tags { for i := range history.Items { n := imagegraph.FindImage(g, history.Items[i].Image) if n == nil { glog.V(2).Infof("Unable to find image %q in graph (from tag=%q, revision=%d, dockerImageReference=%s) - skipping", history.Items[i].Image, tag, i, history.Items[i].DockerImageReference) continue } imageNode := n.(*imagegraph.ImageNode) kind := oldImageRevisionReferenceKind if algorithm.pruneOverSizeLimit { if exceedsLimits(stream, imageNode.Image, limits) { kind = WeakReferencedImageEdgeKind } else { kind = ReferencedImageEdgeKind } } else { if i < algorithm.keepTagRevisions { kind = ReferencedImageEdgeKind } } glog.V(4).Infof("Checking for existing strong reference from stream %s/%s to image %s", stream.Namespace, stream.Name, imageNode.Image.Name) if edge := g.Edge(imageStreamNode, imageNode); edge != nil && 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) 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) { cn, ok := s.(*imagegraph.ImageComponentNode) if !ok { continue } glog.V(4).Infof("Adding reference from stream %q to %s", stream.Name, cn.Describe()) if cn.Type == imagegraph.ImageComponentTypeConfig { g.AddEdge(imageStreamNode, s, ReferencedImageConfigEdgeKind) } else { g.AddEdge(imageStreamNode, s, ReferencedImageLayerEdgeKind) } } } } } }