Skip to content

Commit

Permalink
Add unique flag to group by images (#21)
Browse files Browse the repository at this point in the history
Co-authored-by: mandochen <[email protected]>
  • Loading branch information
chenjiandongx and mandochen authored Jul 18, 2022
1 parent fe06503 commit 15d61ce
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 31 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -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
[
Expand Down
13 changes: 11 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/spf13/cobra"
)

const version = "0.4.0"
const version = "0.5.0"

var rootCmd *cobra.Command

Expand Down Expand Up @@ -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)
},
}
Expand All @@ -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() {
Expand Down
85 changes: 56 additions & 29 deletions kubectl_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand All @@ -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))
}

Expand All @@ -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))
}

Expand All @@ -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!")
Expand Down

0 comments on commit 15d61ce

Please sign in to comment.