From 15d61ce978fd20fccf785812ca4fa8f897d8a2ba Mon Sep 17 00:00:00 2001 From: dongdong Date: Mon, 18 Jul 2022 13:10:48 +0800 Subject: [PATCH] Add unique flag to group by images (#21) Co-authored-by: mandochen --- README.md | 25 ++++++++++++++ cmd/main.go | 13 ++++++-- kubectl_images.go | 85 +++++++++++++++++++++++++++++++---------------- 3 files changed, 92 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 99ff930..bf148b8 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Flags: -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 ``` @@ -109,6 +110,30 @@ Flags: | | 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 [Summary]: 1 namespaces, 2 pods, 2 containers and 1 different images [ diff --git a/cmd/main.go b/cmd/main.go index 2c01301..342b815 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/cobra" ) -const version = "0.4.0" +const version = "0.5.0" var rootCmd *cobra.Command @@ -41,7 +41,15 @@ func init() { allNamespace, _ := cmd.Flags().GetBool("all-namespaces") kubeConfig, _ := cmd.Flags().GetString("kubeConfig") context, _ := cmd.Flags().GetString("context") - kubeImage := kubeimages.NewKubeImage(regx, allNamespace, namespace, columns, kubeConfig, 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) }, } @@ -51,6 +59,7 @@ func init() { 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() { diff --git a/kubectl_images.go b/kubectl_images.go index cf297a0..3cd6d9b 100644 --- a/kubectl_images.go +++ b/kubectl_images.go @@ -22,21 +22,27 @@ const ( labelImagePullPolicy = "ImagePullPolicy" ) +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 - allNamespace bool - namespace string - columns []string - kubeconfig string - context string - regx *regexp.Regexp + entities []*ImageEntity + columns []string + regx *regexp.Regexp + params Parameters } // NewKubeImage creates a new KubeImage instance. -func NewKubeImage(regx *regexp.Regexp, allNamespace bool, namespace, columns, kubeconfig, context string) *KubeImage { +func NewKubeImage(regx *regexp.Regexp, params Parameters) *KubeImage { names := make([]string, 0) - for _, c := range stringSplit(columns, ",") { + for _, c := range stringSplit(params.Columns, ",") { switch c { case "0": names = append(names, labelNamespace) @@ -52,12 +58,9 @@ func NewKubeImage(regx *regexp.Regexp, allNamespace bool, namespace, columns, ku } return &KubeImage{ - allNamespace: allNamespace, - columns: names, - namespace: namespace, - kubeconfig: kubeconfig, - context: context, - regx: regx, + columns: names, + params: params, + regx: regx, } } @@ -142,23 +145,23 @@ func stringSplit(in, sep string) []string { // Commands builds the command to be executed based on user input. func (ki *KubeImage) Commands() []string { kubecfg := make([]string, 0) - if ki.kubeconfig != "" { - kubecfg = append(kubecfg, "--kubeconfig", ki.kubeconfig) + if ki.params.KubeConfig != "" { + kubecfg = append(kubecfg, "--kubeconfig", ki.params.KubeConfig) } - if ki.context != "" { - kubecfg = append(kubecfg, "--context", ki.context) + if ki.params.Context != "" { + kubecfg = append(kubecfg, "--context", ki.params.Context) } - if ki.allNamespace { + if ki.params.AllNamespace { return append([]string{"get", "pods", "--all-namespaces", "-o", gotemplate}, kubecfg...) - } else if ki.namespace != "" { - return append([]string{"get", "pods", "-n", ki.namespace, "-o", gotemplate}, kubecfg...) + } else if ki.params.Namespace != "" { + return append([]string{"get", "pods", "-n", ki.params.Namespace, "-o", gotemplate}, kubecfg...) } return append([]string{"get", "pods", "-o", gotemplate}, kubecfg...) } -func (ki *KubeImage) run() { +func (ki *KubeImage) exec() { process := exec.Command("kubectl", ki.Commands()...) bs, err := process.CombinedOutput() if err != nil { @@ -206,6 +209,26 @@ func (ki *KubeImage) run() { } } +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() @@ -230,15 +253,18 @@ func (ki *KubeImage) tableRender() { table.SetAutoFormatHeaders(false) table.SetAutoMergeCells(true) table.SetRowLine(true) - for _, entity := range ki.entities { + + entities := ki.groupBy() + for _, entity := range entities { table.Append(entity.selectBy(ki.columns)) } table.Render() } func (ki *KubeImage) jsonRender() { - records := make([]ImageEntity, 0, len(ki.entities)) - for _, entity := range ki.entities { + entities := ki.groupBy() + records := make([]ImageEntity, 0, len(entities)) + for _, entity := range entities { records = append(records, entity.filterBy(ki.columns)) } @@ -251,8 +277,9 @@ func (ki *KubeImage) jsonRender() { } func (ki *KubeImage) yamlRender() { - records := make([]ImageEntity, 0, len(ki.entities)) - for _, entity := range ki.entities { + entities := ki.groupBy() + records := make([]ImageEntity, 0, len(entities)) + for _, entity := range entities { records = append(records, entity.filterBy(ki.columns)) } @@ -266,7 +293,7 @@ func (ki *KubeImage) yamlRender() { // Render renders and displays the table output. func (ki *KubeImage) Render(format string) { - ki.run() + ki.exec() if len(ki.entities) == 0 { fmt.Fprintln(os.Stdout, "[Oh...] No images matched!")