pkg/cmd/admin/top/images.go
0985958a
 package top
 
 import (
 	"fmt"
 	"io"
 	"strings"
 
 	"github.com/spf13/cobra"
 
 	kapi "k8s.io/kubernetes/pkg/api"
 	kclient "k8s.io/kubernetes/pkg/client/unversioned"
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
 	"k8s.io/kubernetes/pkg/util/sets"
 
 	"github.com/openshift/origin/pkg/api/graph"
 	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
 	buildapi "github.com/openshift/origin/pkg/build/api"
 	"github.com/openshift/origin/pkg/client"
6267dded
 	"github.com/openshift/origin/pkg/cmd/templates"
0985958a
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
 	imageapi "github.com/openshift/origin/pkg/image/api"
6267dded
 
0985958a
 	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
 )
 
 const (
 	TopImagesRecommendedName = "images"
6267dded
 	maxImageIDLength         = 20
 )
0985958a
 
6267dded
 var (
 	topImagesLong = templates.LongDesc(`
 		Show usage statistics for Images
0985958a
 
6267dded
 		This command analyzes all the Images managed by the platform and presents current
 		usage statistics.`)
0985958a
 
6267dded
 	topImagesExample = templates.Examples(`
 		# Show usage statistics for Images
   	%[1]s %[2]s`)
0985958a
 )
 
 // NewCmdTopImages implements the OpenShift cli top images command.
 func NewCmdTopImages(f *clientcmd.Factory, parentName, name string, out io.Writer) *cobra.Command {
 	opts := &TopImagesOptions{}
 	cmd := &cobra.Command{
 		Use:     name,
 		Short:   "Show usage statistics for Images",
 		Long:    topImagesLong,
 		Example: fmt.Sprintf(topImagesExample, parentName, name),
 		Run: func(cmd *cobra.Command, args []string) {
 			kcmdutil.CheckErr(opts.Complete(f, cmd, args, out))
 			kcmdutil.CheckErr(opts.Validate(cmd))
 			kcmdutil.CheckErr(opts.Run())
 		},
 	}
 
 	return cmd
 }
 
 type TopImagesOptions struct {
 	// internal values
 	Images  *imageapi.ImageList
 	Streams *imageapi.ImageStreamList
 	Pods    *kapi.PodList
 
 	// helpers
 	out      io.Writer
 	osClient client.Interface
 	kClient  kclient.Interface
 }
 
 // Complete turns a partially defined TopImagesOptions into a solvent structure
 // which can be validated and used for showing limits usage.
 func (o *TopImagesOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string, out io.Writer) error {
 	osClient, kClient, err := f.Clients()
 	if err != nil {
 		return err
 	}
 	namespace := cmd.Flag("namespace").Value.String()
 	if len(namespace) == 0 {
 		namespace = kapi.NamespaceAll
 	}
 	o.out = out
 
 	allImages, err := osClient.Images().List(kapi.ListOptions{})
 	if err != nil {
 		return err
 	}
 	o.Images = allImages
 
 	allStreams, err := osClient.ImageStreams(namespace).List(kapi.ListOptions{})
 	if err != nil {
 		return err
 	}
 	o.Streams = allStreams
 
 	allPods, err := kClient.Pods(namespace).List(kapi.ListOptions{})
 	if err != nil {
 		return err
 	}
 	o.Pods = allPods
 
 	return nil
 }
 
 // Validate ensures that a TopImagesOptions is valid and can be used to execute command.
 func (o TopImagesOptions) Validate(cmd *cobra.Command) error {
 	return nil
 }
 
 // Run contains all the necessary functionality to show current image references.
 func (o TopImagesOptions) Run() error {
 	infos := o.imagesTop()
 	Print(o.out, ImageColumns, infos)
 	return nil
 }
 
 var ImageColumns = []string{"NAME", "IMAGESTREAMTAG", "PARENTS", "USAGE", "METADATA", "STORAGE"}
 
 // imageInfo contains statistic information about Image usage.
 type imageInfo struct {
 	Image           string
 	ImageStreamTags []string
 	Parents         []string
 	Usage           []string
 	Metadata        bool
 	Storage         int64
 }
 
 var _ Info = &imageInfo{}
 
 func (i imageInfo) PrintLine(out io.Writer) {
 	printValue(out, i.Image)
 	printArray(out, i.ImageStreamTags)
31530fab
 	shortParents := make([]string, len(i.Parents))
 	for i, p := range i.Parents {
 		if len(p) > maxImageIDLength {
 			shortParents[i] = p[:maxImageIDLength-3] + "..."
 		} else {
 			shortParents[i] = p
 		}
 	}
 	printArray(out, shortParents)
0985958a
 	printArray(out, i.Usage)
 	printBool(out, i.Metadata)
 	printSize(out, i.Storage)
 }
 
 // imagesTop generates Image information from a graph and returns this as a list
 // of imageInfo array.
 func (o TopImagesOptions) imagesTop() []Info {
 	g := graph.New()
 	addImagesToGraph(g, o.Images)
 	addImageStreamsToGraph(g, o.Streams)
 	addPodsToGraph(g, o.Pods)
 	markParentsInGraph(g)
 
 	infos := []Info{}
 	imageNodes := getImageNodes(g.Nodes())
 	for _, in := range imageNodes {
 		image := in.Image
 		istags := getImageStreamTags(g, in)
 		parents := getImageParents(g, in)
 		usage := getImageUsage(g, in)
 		metadata := len(image.DockerImageManifest) != 0 && len(image.DockerImageLayers) != 0
 		storage := getStorage(image)
 		infos = append(infos, imageInfo{
 			Image:           image.Name,
 			ImageStreamTags: istags,
 			Parents:         parents,
 			Usage:           usage,
 			Metadata:        metadata,
 			Storage:         storage,
 		})
 	}
 
 	return infos
 }
 
 func getStorage(image *imageapi.Image) int64 {
 	storage := int64(0)
c442bf46
 	blobSet := sets.NewString()
0985958a
 	for _, layer := range image.DockerImageLayers {
c442bf46
 		if blobSet.Has(layer.Name) {
0985958a
 			continue
 		}
c442bf46
 		blobSet.Insert(layer.Name)
0985958a
 		storage += layer.LayerSize
 	}
c442bf46
 	if len(image.DockerImageConfig) > 0 && !blobSet.Has(image.DockerImageMetadata.ID) {
 		blobSet.Insert(image.DockerImageMetadata.ID)
 		storage += int64(len(image.DockerImageConfig))
 	}
0985958a
 	return storage
 }
 
 func getImageStreamTags(g graph.Graph, node *imagegraph.ImageNode) []string {
 	istags := []string{}
 	for _, e := range g.InboundEdges(node, ImageStreamImageEdgeKind) {
 		streamNode, ok := e.From().(*imagegraph.ImageStreamNode)
 		if !ok {
 			continue
 		}
 		stream := streamNode.ImageStream
 		tags := getTags(stream, node.Image)
 		istags = append(istags, fmt.Sprintf("%s/%s (%s)", stream.Namespace, stream.Name, strings.Join(tags, ",")))
 	}
 	return istags
 }
 
 func getTags(stream *imageapi.ImageStream, image *imageapi.Image) []string {
 	tags := []string{}
 	for tag, history := range stream.Status.Tags {
 		if history.Items[0].Image == image.Name {
 			tags = append(tags, tag)
 		}
 	}
 	imageapi.PrioritizeTags(tags)
 	return tags
 }
 
 func getImageParents(g graph.Graph, node *imagegraph.ImageNode) []string {
 	parents := []string{}
 	for _, e := range g.InboundEdges(node, ParentImageEdgeKind) {
 		imageNode, ok := e.From().(*imagegraph.ImageNode)
 		if !ok {
 			continue
 		}
 		parents = append(parents, imageNode.Image.Name)
 	}
 	return parents
 }
 
 func getImageUsage(g graph.Graph, node *imagegraph.ImageNode) []string {
 	usage := []string{}
 	for _, e := range g.InboundEdges(node, PodImageEdgeKind) {
 		podNode, ok := e.From().(*kubegraph.PodNode)
 		if !ok {
 			continue
 		}
 		usage = append(usage, getController(podNode.Pod))
 	}
 	return usage
 }
 
 func getController(pod *kapi.Pod) string {
 	controller := "<unknown>"
 	if pod.Annotations == nil {
 		return controller
 	}
 
 	if bc, ok := pod.Annotations[buildapi.BuildAnnotation]; ok {
 		return fmt.Sprintf("Build: %s/%s", pod.Namespace, bc)
 	}
 	if dc, ok := pod.Annotations[deployapi.DeploymentAnnotation]; ok {
 		return fmt.Sprintf("Deployment: %s/%s", pod.Namespace, dc)
 	}
 	if dc, ok := pod.Annotations[deployapi.DeploymentPodAnnotation]; ok {
 		return fmt.Sprintf("Deployer: %s/%s", pod.Namespace, dc)
 	}
 
 	return controller
 }