Repository: chenjiandongx/kubectl-images Branch: master Commit: 62d58004d658 Files: 10 Total size: 29.8 KB Directory structure: gitextract_8pr76q2y/ ├── .github/ │ └── workflows/ │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── .krew.yaml ├── LICENSE ├── README.md ├── cmd/ │ └── main.go ├── go.mod ├── go.sum └── kubectl_images.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/release.yml ================================================ name: release on: push: tags: - 'v*.*.*' jobs: goreleaser: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: "1.22" - name: GoReleaser uses: goreleaser/goreleaser-action@v6 with: version: latest args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Update new version in krew-index uses: rajatjindal/krew-release-bot@v0.0.46 ================================================ FILE: .gitignore ================================================ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # IDE .idea/ .vscode/ .DS_Store releases/ ================================================ FILE: .goreleaser.yml ================================================ builds: - id: kubectl-images main: ./cmd binary: kubectl-images env: - CGO_ENABLED=0 goos: - darwin - linux - windows goarch: - amd64 - arm64 - arm ignore: - goos: windows goarch: arm - goos: windows goarch: arm64 - goos: darwin goarch: arm archives: - builds: - kubectl-images name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" wrap_in_directory: false format: tar.gz files: - LICENSE ================================================ FILE: .krew.yaml ================================================ apiVersion: krew.googlecontainertools.github.com/v1alpha2 kind: Plugin metadata: name: images spec: version: {{ .TagName }} homepage: https://github.com/chenjiandongx/kubectl-images shortDescription: Show container images used in the cluster. description: | This plugin shows container images used in the Kubernetes cluster in a table view. You can show all images or show images used in a specified namespace. platforms: - selector: matchLabels: os: darwin arch: amd64 files: - from: kubectl-images to: . - from: LICENSE to: . {{ addURIAndSha "https://github.com/chenjiandongx/kubectl-images/releases/download/{{ .TagName }}/kubectl-images_darwin_amd64.tar.gz" .TagName }} bin: kubectl-images - selector: matchLabels: os: darwin arch: arm64 files: - from: kubectl-images to: . - from: LICENSE to: . {{ addURIAndSha "https://github.com/chenjiandongx/kubectl-images/releases/download/{{ .TagName }}/kubectl-images_darwin_arm64.tar.gz" .TagName }} bin: kubectl-images - selector: matchLabels: os: linux arch: amd64 files: - from: kubectl-images to: . - from: LICENSE to: . {{ addURIAndSha "https://github.com/chenjiandongx/kubectl-images/releases/download/{{ .TagName }}/kubectl-images_linux_amd64.tar.gz" .TagName }} bin: kubectl-images - selector: matchLabels: os: linux arch: arm64 files: - from: kubectl-images to: . - from: LICENSE to: . {{ addURIAndSha "https://github.com/chenjiandongx/kubectl-images/releases/download/{{ .TagName }}/kubectl-images_linux_arm64.tar.gz" .TagName }} bin: kubectl-images - selector: matchLabels: os: linux arch: arm files: - from: kubectl-images to: . - from: LICENSE to: . {{ addURIAndSha "https://github.com/chenjiandongx/kubectl-images/releases/download/{{ .TagName }}/kubectl-images_linux_arm.tar.gz" .TagName }} bin: kubectl-images - selector: matchLabels: os: windows arch: amd64 files: - from: kubectl-images.exe to: . - from: LICENSE to: . {{ addURIAndSha "https://github.com/chenjiandongx/kubectl-images/releases/download/{{ .TagName }}/kubectl-images_windows_amd64.tar.gz" .TagName }} bin: kubectl-images.exe ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020~now chenjiandongx Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

kubectl-images

🕸 Show container images used in the cluster.

kubectl-images makes use of the `kubectl` command. It first calls `kubectl get pods` to retrieve pods details and filters out the container image information of each pod, then prints out the final result in a table/json/yaml view. ### 🔰 Installation Krew ```shell $ kubectl krew install images Updated the local copy of plugin index. Installing plugin: images Installed plugin: images \ | Use this plugin: | kubectl images | Documentation: | https://github.com/chenjiandongx/kubectl-images / ``` Build from source code ```shell $ git clone https://github.com/chenjiandongx/kubectl-images.git $ cd kubectl-images && go build -ldflags="-s -w" -o kubectl-images . && mv ./kubectl-images /usr/local/bin $ kubectl images --help ``` Download the binary ```shell # Refer to the link: https://github.com/chenjiandongx/kubectl-images/releases # Download the binary and then... $ chmod +x kubectl-images && mv kubectl-images /usr/local/bin/ $ kubectl images --help ``` ### 📝 Usage ```shell ~ 🐶 kubectl images --help Show container images used in the cluster. Usage: kubectl-images [podname-regex] [flags] Examples: # display a table of all images in current namespace using podName/containerName/containerImage as columns. kubectl images # display images info in yaml format kubectl images -oy # display a table of images that match 'nginx' podname regex in 'dev' namespace using podName/containerImage as columns. kubectl images -n dev nginx -c 1,2 Flags: -A, --all-namespaces if present, list images in all namespaces. -c, --columns string specify the columns to display, separated by comma. [0:Namespace, 1:PodName, 2:ContainerName, 3:ContainerImage, 4:ImagePullPolicy, 5:ImageSize] (default "1,2,3") -C, --context string The name of the kubeconfig context to use. -h, --help help for kubectl-images -k, --kubeconfig string path to the kubeconfig file to use for CLI requests. -n, --namespace string if present, list images in the specified namespace only. Use current namespace as fallback. -o, --output-format string output format. [json(j)|table(t)|yaml(y)] (default "table") -u, --unique Unique images group by namespace/container/images/pullPolicy. --version version for kubectl-images ``` ### 🔖 Glances ```shell ~ 🐶 kubectl images -n kube-system -oy dns - pod: coredns-78fcd69978-9pbjh container: coredns image: k8s.gcr.io/coredns/coredns:v1.8.4 - pod: coredns-78fcd69978-jh7m2 container: coredns image: k8s.gcr.io/coredns/coredns:v1.8.4 ~ 🐶 kubectl images -A -c 0,1,3 [Summary]: 2 namespaces, 11 pods, 11 containers and 9 different images +-------------+----------------------------------------+--------------------------------------------+ | Namespace | Pod | Image | +-------------+----------------------------------------+--------------------------------------------+ | kube-system | coredns-78fcd69978-9pbjh | k8s.gcr.io/coredns/coredns:v1.8.4 | + +----------------------------------------+ + | | coredns-78fcd69978-jh7m2 | | + +----------------------------------------+--------------------------------------------+ | | etcd-docker-desktop | k8s.gcr.io/etcd:3.5.0-0 | + +----------------------------------------+--------------------------------------------+ | | kube-apiserver-docker-desktop | k8s.gcr.io/kube-apiserver:v1.22.5 | + +----------------------------------------+--------------------------------------------+ | | kube-controller-manager-docker-desktop | k8s.gcr.io/kube-controller-manager:v1.22.5 | + +----------------------------------------+--------------------------------------------+ | | kube-proxy-vc7fv | k8s.gcr.io/kube-proxy:v1.22.5 | + +----------------------------------------+--------------------------------------------+ | | kube-scheduler-docker-desktop | k8s.gcr.io/kube-scheduler:v1.22.5 | + +----------------------------------------+--------------------------------------------+ | | storage-provisioner | docker/desktop-storage-provisioner:v2.0 | + +----------------------------------------+--------------------------------------------+ | | vpnkit-controller | docker/desktop-vpnkit-controller:v2.0 | +-------------+----------------------------------------+--------------------------------------------+ | nginx | nginx-deployment-66b6c48dd5-s9wv5 | nginx:1.14.2 | + +----------------------------------------+ + | | nginx-deployment-66b6c48dd5-wmn9x | | +-------------+----------------------------------------+--------------------------------------------+ ~ 🐶 kubectl images -A -c 0,1,3 -u [Summary]: 2 namespaces, 11 pods, 11 containers and 9 different images +-------------+----------------------------------------+--------------------------------------------+ | Namespace | Pod | Image | +-------------+----------------------------------------+--------------------------------------------+ | kube-system | coredns-78fcd69978-9pbjh | k8s.gcr.io/coredns/coredns:v1.8.4 | + + +----------------------------------------+--------------------------------------------+ | | etcd-docker-desktop | k8s.gcr.io/etcd:3.5.0-0 | + +----------------------------------------+--------------------------------------------+ | | kube-apiserver-docker-desktop | k8s.gcr.io/kube-apiserver:v1.22.5 | + +----------------------------------------+--------------------------------------------+ | | kube-controller-manager-docker-desktop | k8s.gcr.io/kube-controller-manager:v1.22.5 | + +----------------------------------------+--------------------------------------------+ | | kube-proxy-vc7fv | k8s.gcr.io/kube-proxy:v1.22.5 | + +----------------------------------------+--------------------------------------------+ | | kube-scheduler-docker-desktop | k8s.gcr.io/kube-scheduler:v1.22.5 | + +----------------------------------------+--------------------------------------------+ | | storage-provisioner | docker/desktop-storage-provisioner:v2.0 | + +----------------------------------------+--------------------------------------------+ | | vpnkit-controller | docker/desktop-vpnkit-controller:v2.0 | +-------------+----------------------------------------+--------------------------------------------+ | nginx | nginx-deployment-66b6c48dd5-s9wv5 | nginx:1.14.2 | +-------------+----------------------------------------+--------------------------------------------+ ~ 🐶 kubectl images -c 0,1,2,3,4 -n nginx -oj [ { "namespace": "nginx", "pod": "nginx-deployment-66b6c48dd5-s9wv5", "container": "nginx", "image": "nginx:latest", "imagePullPolicy": "IfNotPresent" }, { "namespace": "nginx", "pod": "nginx-deployment-66b6c48dd5-wmn9x", "container": "nginx", "image": "nginx:latest", "imagePullPolicy": "IfNotPresent" } ] ``` ### 📃 License MIT [©chenjiandongx](https://github.com/chenjiandongx) ================================================ FILE: cmd/main.go ================================================ package main import ( "fmt" "os" "regexp" kubeimages "github.com/chenjiandongx/kubectl-images" "github.com/spf13/cobra" ) const version = "0.6.5" var rootCmd *cobra.Command func init() { rootCmd = &cobra.Command{ Use: "kubectl-images [podname-regex]", Short: "Show container images used in the cluster.", Example: ` # display a table of all images in current namespace using podName/containerName/containerImage as columns. kubectl images # display images info in yaml format kubectl images -oy # display a table of images that match 'nginx' podname regex in 'dev' namespace using podName/containerImage as columns. kubectl images -n dev nginx -c 1,2`, Version: version, Run: func(cmd *cobra.Command, args []string) { var regx *regexp.Regexp var err error if len(args) > 0 { if regx, err = regexp.Compile(args[0]); err != nil { fmt.Fprintf(os.Stderr, "[Oh...] Invalid regex pattern (%q)", args[0]) return } } namespace, _ := cmd.Flags().GetString("namespace") columns, _ := cmd.Flags().GetString("columns") format, _ := cmd.Flags().GetString("output-format") allNamespace, _ := cmd.Flags().GetBool("all-namespaces") kubeConfig, _ := cmd.Flags().GetString("kubeConfig") context, _ := cmd.Flags().GetString("context") unique, _ := cmd.Flags().GetBool("unique") kubeImage := kubeimages.NewKubeImage(regx, kubeimages.Parameters{ AllNamespace: allNamespace, Namespace: namespace, Columns: columns, KubeConfig: kubeConfig, Context: context, Unique: unique, }) kubeImage.Render(format) }, } rootCmd.Flags().BoolP("all-namespaces", "A", false, "if present, list images in all namespaces.") rootCmd.Flags().StringP("namespace", "n", "", "if present, list images in the specified namespace only. Use current namespace as fallback.") rootCmd.Flags().StringP("columns", "c", "1,2,3", "specify the columns to display, separated by comma. [0:Namespace, 1:PodName, 2:ContainerName, 3:ContainerImage, 4:ImagePullPolicy, 5:ImageSize]") rootCmd.Flags().StringP("kubeconfig", "k", "", "path to the kubeconfig file to use for CLI requests.") rootCmd.Flags().StringP("output-format", "o", "table", "output format. [json(j)|table(t)|yaml(y)]") rootCmd.Flags().StringP("context", "C", "", "The name of the kubeconfig context to use.") rootCmd.Flags().BoolP("unique", "u", false, "Unique images group by namespace/container/images/pullPolicy.") } func main() { if err := rootCmd.Execute(); err != nil { fmt.Fprintf(os.Stderr, "[Oh...] Failed to exec command: %v", err) } } ================================================ FILE: go.mod ================================================ module github.com/chenjiandongx/kubectl-images go 1.18 require ( github.com/dustin/go-humanize v1.0.1 github.com/olekukonko/tablewriter v0.0.4 github.com/spf13/cobra v0.0.5 gopkg.in/yaml.v2 v2.2.8 ) require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/mattn/go-runewidth v0.0.7 // indirect github.com/spf13/pflag v1.0.3 // indirect ) ================================================ FILE: go.sum ================================================ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: kubectl_images.go ================================================ package kubeimage import ( "encoding/json" "fmt" "os" "os/exec" "path" "regexp" "strconv" "strings" "github.com/dustin/go-humanize" "github.com/olekukonko/tablewriter" "gopkg.in/yaml.v2" ) const ( podTemplate = `go-template={{range .items}} {{.metadata.namespace}} {{","}} {{.metadata.name}} {{","}} {{range .spec.containers}} {{.name}} {{","}} {{.image}} {{","}} {{.imagePullPolicy}} {{"\n"}} {{end}} {{range .spec.initContainers}} {{"(init)"}} {{.name}} {{","}} {{.image}} {{","}} {{.imagePullPolicy}} {{"\n"}} {{end}} {{range .spec.ephemeralContainers}} {{"(ephemeral)"}} {{.name}} {{","}} {{.image}} {{","}} {{.imagePullPolicy}} {{"\n"}} {{end}} {{end}}` nodeTemplate = `go-template={{range .items}} {{range .status.images}} {{range .names}} {{.}} {{","}} {{end}} {{.sizeBytes}} {{"\n"}} {{end}} {{end}}` labelNamespace = "Namespace" labelPod = "Pod" labelContainer = "Container" labelImage = "Image" labelImagePullPolicy = "ImagePullPolicy" labelImageSize = "ImageSize" ) type Parameters struct { AllNamespace bool Namespace string Columns string KubeConfig string Context string Unique bool } // KubeImage is the representation of a container image used in the cluster. type KubeImage struct { entities []*ImageEntity columns []string regx *regexp.Regexp params Parameters imageSize map[string]int needNodeInfo bool } // NewKubeImage creates a new KubeImage instance. func NewKubeImage(regx *regexp.Regexp, params Parameters) *KubeImage { var needNodeInfo bool names := make([]string, 0) for _, c := range stringSplit(params.Columns, ",") { switch c { case "0": names = append(names, labelNamespace) case "1": names = append(names, labelPod) case "2": names = append(names, labelContainer) case "3": names = append(names, labelImage) case "4": names = append(names, labelImagePullPolicy) case "5": names = append(names, labelImageSize) needNodeInfo = true } } return &KubeImage{ columns: names, params: params, regx: regx, imageSize: make(map[string]int), needNodeInfo: needNodeInfo, } } // ImageEntity is the representation of an entity to be displayed. type ImageEntity struct { Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` Pod string `json:"pod,omitempty" yaml:"pod,omitempty"` Container string `json:"container,omitempty" yaml:"container,omitempty"` Image string `json:"image,omitempty" yaml:"image,omitempty"` ImagePullPolicy string `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"` ImageSize string `json:"imageSize,omitempty" yaml:"imageSize,omitempty"` } func (ie *ImageEntity) selectBy(columns []string) []string { result := make([]string, 0) for _, c := range columns { switch c { case labelNamespace: result = append(result, ie.Namespace) case labelPod: result = append(result, ie.Pod) case labelContainer: result = append(result, ie.Container) case labelImage: result = append(result, ie.Image) case labelImagePullPolicy: result = append(result, ie.ImagePullPolicy) case labelImageSize: result = append(result, ie.ImageSize) } } return result } func (ie *ImageEntity) filterBy(columns []string) ImageEntity { var entity ImageEntity for _, c := range columns { switch c { case labelNamespace: entity.Namespace = ie.Namespace case labelPod: entity.Pod = ie.Pod case labelContainer: entity.Container = ie.Container case labelImage: entity.Image = ie.Image case labelImagePullPolicy: entity.ImagePullPolicy = ie.ImagePullPolicy case labelImageSize: entity.ImageSize = ie.ImageSize } } return entity } // Counter is a simple counter. type Counter struct { cnt int items map[string]bool } // NewCounter creates a new Counter instance. func NewCounter() *Counter { return &Counter{items: make(map[string]bool)} } func (c *Counter) add(obj string) { if !c.items[obj] { c.cnt += 1 c.items[obj] = true } } // Count returns current counter reading. func (c *Counter) Count() int { return c.cnt } func stringSplit(in, sep string) []string { out := make([]string, 0) for _, s := range strings.Split(in, sep) { out = append(out, strings.TrimSpace(s)) } return out } // podCommands builds the command to be executed based on user input. func (ki *KubeImage) podCommands() []string { kubecfg := make([]string, 0) if ki.params.KubeConfig != "" { kubecfg = append(kubecfg, "--kubeconfig", ki.params.KubeConfig) } if ki.params.Context != "" { kubecfg = append(kubecfg, "--context", ki.params.Context) } if ki.params.AllNamespace { return append([]string{"get", "pods", "--all-namespaces", "-o", podTemplate}, kubecfg...) } else if ki.params.Namespace != "" { return append([]string{"get", "pods", "-n", ki.params.Namespace, "-o", podTemplate}, kubecfg...) } return append([]string{"get", "pods", "-o", podTemplate}, kubecfg...) } func (ki *KubeImage) nodeCommands() []string { kubecfg := make([]string, 0) if ki.params.KubeConfig != "" { kubecfg = append(kubecfg, "--kubeconfig", ki.params.KubeConfig) } if ki.params.Context != "" { kubecfg = append(kubecfg, "--context", ki.params.Context) } return append([]string{"get", "nodes", "-o", nodeTemplate}, kubecfg...) } func (ki *KubeImage) recordImageSize(image string, size int) { ki.imageSize[image] = size ki.imageSize[path.Base(image)] = size } func (ki *KubeImage) execNodeCommand() { process := exec.Command("kubectl", ki.nodeCommands()...) bs, err := process.CombinedOutput() if err != nil { fmt.Fprintf(os.Stderr, "[Oh...] Execute nodes command error: %v, %s", err, string(bs)) os.Exit(1) } for _, line := range stringSplit(string(bs), "\n") { items := stringSplit(line, ",") switch len(items) { case 2: size, err := strconv.Atoi(items[1]) if err != nil { continue } ki.recordImageSize(items[0], size) parts := strings.Split(items[0], ":") if len(parts) == 2 && parts[1] == "latest" { ki.recordImageSize(parts[0], size) } case 3: size, err := strconv.Atoi(items[2]) if err != nil { continue } ki.recordImageSize(items[0], size) ki.recordImageSize(items[1], size) parts := strings.Split(items[1], ":") if len(parts) == 2 && parts[1] == "latest" { ki.recordImageSize(parts[0], size) } } } for _, entity := range ki.entities { size, ok := ki.imageSize[entity.Image] if ok { entity.ImageSize = humanize.IBytes(uint64(size)) } } } func (ki *KubeImage) execPodCommand() { process := exec.Command("kubectl", ki.podCommands()...) bs, err := process.CombinedOutput() if err != nil { fmt.Fprintf(os.Stderr, "[Oh...] Execute pods command error: %v, %s", err, string(bs)) os.Exit(1) } entities := make([]*ImageEntity, 0) for _, line := range stringSplit(string(bs), "\n") { items := stringSplit(line, ",") entity := &ImageEntity{} switch len(items) { case 1: continue case 3: entity.Container = items[0] entity.Image = items[1] entity.ImagePullPolicy = items[2] case 5: entity.Namespace = items[0] entity.Pod = items[1] entity.Container = items[2] entity.Image = items[3] entity.ImagePullPolicy = items[4] } entities = append(entities, entity) } for i := 0; i < len(entities); i++ { if entities[i].Pod == "" && i > 0 { entities[i].Namespace = entities[i-1].Namespace entities[i].Pod = entities[i-1].Pod } } for i := 0; i < len(entities); i++ { if ki.regx == nil { ki.entities = append(ki.entities, entities[i]) continue } if ki.regx.Match([]byte(entities[i].Pod)) { ki.entities = append(ki.entities, entities[i]) } } } func (ki *KubeImage) groupBy() []*ImageEntity { if !ki.params.Unique { return ki.entities } set := make(map[string]struct{}) entities := make([]*ImageEntity, 0) for i, entity := range ki.entities { k := fmt.Sprintf("%s/%s/%s/%s", entity.Namespace, entity.Container, entity.Image, entity.ImagePullPolicy) if _, ok := set[k]; ok { continue } set[k] = struct{}{} entities = append(entities, ki.entities[i]) } return entities } func (ki *KubeImage) summary() { namespaceCnt := NewCounter() podCnt := NewCounter() imageCnt := NewCounter() containerCnt := 0 for i := 0; i < len(ki.entities); i++ { namespaceCnt.add(ki.entities[i].Namespace) podCnt.add(ki.entities[i].Pod) imageCnt.add(ki.entities[i].Image) containerCnt += 1 } fmt.Fprintf(os.Stdout, "[Summary]: %d namespaces, %d pods, %d containers and %d different images\n", namespaceCnt.Count(), podCnt.Count(), containerCnt, imageCnt.Count(), ) } func (ki *KubeImage) tableRender() { table := tablewriter.NewWriter(os.Stdout) table.SetHeader(ki.columns) table.SetAutoFormatHeaders(false) table.SetAutoMergeCells(true) table.SetRowLine(true) entities := ki.getGroupByEntities() for _, entity := range entities { table.Append(entity) } table.Render() } func (ki *KubeImage) getGroupByEntities() [][]string { set := make(map[string]struct{}) dst := make([][]string, 0) for _, entity := range ki.groupBy() { line := strings.Join(entity.selectBy(ki.columns), "||") _, ok := set[line] if !ok { set[line] = struct{}{} dst = append(dst, strings.Split(line, "||")) } } return dst } func (ki *KubeImage) jsonRender() { entities := ki.groupBy() records := make([]ImageEntity, 0, len(entities)) for _, entity := range entities { records = append(records, entity.filterBy(ki.columns)) } output, err := json.MarshalIndent(records, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "[Oh...] Failed to marshal JSON data, error: %v", err) os.Exit(1) } fmt.Fprintln(os.Stdout, string(output)) } func (ki *KubeImage) yamlRender() { entities := ki.groupBy() records := make([]ImageEntity, 0, len(entities)) for _, entity := range entities { records = append(records, entity.filterBy(ki.columns)) } output, err := yaml.Marshal(records) if err != nil { fmt.Fprintf(os.Stderr, "[Oh...] Failed to marshal YAML data, error: %v", err) os.Exit(1) } fmt.Fprintln(os.Stdout, string(output)) } // Render renders and displays the table output. func (ki *KubeImage) Render(format string) { ki.execPodCommand() if ki.needNodeInfo { ki.execNodeCommand() } if len(ki.entities) == 0 { fmt.Fprintln(os.Stdout, "[Oh...] No images matched!") return } switch format { case "json", "j": ki.jsonRender() case "yaml", "y": ki.yamlRender() default: // table ki.summary() ki.tableRender() } }