pkg/find/images.go (139 lines of code) (raw):

// Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package find import ( "encoding/json" "fmt" "strings" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" bundle "github.com/GoogleCloudPlatform/k8s-cluster-bundle/pkg/apis/bundle/v1alpha1" "github.com/GoogleCloudPlatform/k8s-cluster-bundle/pkg/core" ) // ImageFinder finds container and OS Images in components. type ImageFinder struct { components []*bundle.Component } // NewImageFinder creates a new ImageFinder. func NewImageFinder(c []*bundle.Component) *ImageFinder { return &ImageFinder{c} } // ContainerImage is a helper struct for returning found container images for cluster objects. type ContainerImage struct { // Key represents the key for representing the specific cluster object that // this is from. Key core.ClusterObjectKey // Image are the images used by the cluster object. Usually having the form // `<registry>/<repository>/<image>:<tag>`. For example: // `gcr.io/google_containers/etcd:3.1.11` Image string } // Filter is a function type which filters images from being emitted by the finder. // A filter should return true if the image should be emitted, false otherwise. type Filter func(fieldName string, parentFieldName string, img string) bool // String converts the ContainerImage into a human-readable string. func (c *ContainerImage) String() string { return fmt.Sprintf("{Key:%v, Image:%q}", c.Key, c.Image) } // AllContainerImages returns all the images from the cluster components in a list // of components. func (b *ImageFinder) AllContainerImages() []*ContainerImage { return b.AllFilteredContainerImages(nil) } // AllFilteredContainerImages returns all the images from the cluster components in a list // of components. func (b *ImageFinder) AllFilteredContainerImages(filter Filter) []*ContainerImage { var images []*ContainerImage b.WalkAllContainerImages(filter, func(key core.ClusterObjectKey, img string) string { images = append(images, &ContainerImage{key, img}) return img }) return images } // ContainerImages returns all the images from a single Kubernetes object. func (b *ImageFinder) ContainerImages(key bundle.ComponentReference, st *unstructured.Unstructured) []*ContainerImage { return b.FilteredContainerImages(nil, key, st) } // FilteredContainerImages returns all the images from a single Kubernetes object, // filtered by the specified function. func (b *ImageFinder) FilteredContainerImages(filter Filter, key bundle.ComponentReference, st *unstructured.Unstructured) []*ContainerImage { objkey := core.ClusterObjectKey{ Component: key, Object: core.ObjectRefFromUnstructured(st), } var images []*ContainerImage b.WalkContainerImages(st, filter, func(img string) string { images = append(images, &ContainerImage{objkey, img}) return img }) return images } // WalkContainerImages provides a method for traversing through Container // images in a single kubernetes object, with a function for processing each // container value. // // If an image value is returned from the function that is not equal to the input // value, the value is replaced with the new value. // // This changes the components object in-place, so if changes are intended, it is // recommend that the components be cloned. func (b *ImageFinder) WalkContainerImages(st *unstructured.Unstructured, filter Filter, emit func(img string) string) { // It would be more robust to just be aware of Pods, Deployments, and the // various K8S types that have container images rather then recursing through // everything. It's possible, for example, that we that we might encouncer // an 'image' field in some options custom resource that's unintended. containerImageRecurser("", "", st.Object, filter, emit) } // WalkAllContainerImages works the same as WalkContainerImages, except all // images are traversed. Additionally, the cluster object context is also // provided. Note that objects must be inlined to be walked. // // This changes the components object in-place, so if changes are intended, it is // recommend that the components be cloned. func (b *ImageFinder) WalkAllContainerImages(filter Filter, emit func(key core.ClusterObjectKey, img string) string) { for _, ca := range b.components { key := ca.ComponentReference() for _, obj := range ca.Spec.Objects { ref := core.ObjectRefFromUnstructured(obj) key := core.ClusterObjectKey{ Component: key, Object: ref, } b.WalkContainerImages(obj, filter, func(img string) string { return emit(key, img) }) } } } // imageMod represents a modification to an image. type imageMod struct { key string img string } // ContainerImageRecurser is a function that looks through a struct pb for // fields named "Image" and calls a function on the resulting value. // // If an image value is returned from the function and the value is not equal // to the input value. // func containerImageRecurser(fieldName string, parentFieldName string, elem interface{}, filter Filter, emit func(img string) string) *imageMod { switch elem := elem.(type) { case map[string]interface{}: if elem == nil { return nil } var changes []*imageMod for key, val := range elem { if o := containerImageRecurser(key, fieldName, val, filter, emit); o != nil { changes = append(changes, o) } } // replace the image-keys for _, c := range changes { elem[c.key] = c.img } return nil case string: // It looks like it's frequently true that the parent name for the // container object is 'container', 'containers' or // 'somethingContainer[s]'. if fieldName == "image" && (strings.Contains(parentFieldName, "container") || strings.Contains(parentFieldName, "Container")) || fieldName == "url" && parentFieldName == "osImage" { // hack to make finding images work with NodeConfigs if filter == nil || filter(fieldName, parentFieldName, elem) { ret := emit(elem) if ret != elem { return &imageMod{fieldName, ret} } } } return nil case []interface{}: if elem == nil { return nil } for _, val := range elem { // Ignore any result here. image-fields should be singletons in maps. containerImageRecurser(fieldName, parentFieldName, val, filter, emit) } return nil case int64, bool, float64, nil, json.Number: return nil default: panic(fmt.Errorf("unexpected type; cannot tranverse %T", elem)) } } // WalkAllImages walks all node and container images. Only one of // nodeConfigName or key will be filled out, based on whether the image is from // a node config or from a cluster object. // // This changes the component objects in-place, so if changes are intended, it is // recommend that the components be cloned. func (b *ImageFinder) WalkAllImages(fn func(key core.ClusterObjectKey, img string) string) { b.WalkAllContainerImages(nil, fn) } // AllImages returns all images found -- both container images and OS images for nodes. type AllImages struct { ContainerImages []*ContainerImage } // AllImages finds all container images. func (b *ImageFinder) AllImages() *AllImages { return &AllImages{ ContainerImages: b.AllContainerImages(), } } // Flattened turns an AllImages struct with image information into a struct // containing lists of strings. All duplicates are removed. func (a *AllImages) Flattened() *AllImagesFlattened { seen := make(map[string]bool) var containerImages []string for _, val := range a.ContainerImages { if !seen[val.Image] { containerImages = append(containerImages, val.Image) } seen[val.Image] = true } return &AllImagesFlattened{ ContainerImages: containerImages, } } // AllImagesFlattened contains images found, but flattened into lists of // strings. type AllImagesFlattened struct { ContainerImages []string `json:"containerImages"` }