Skip to content

Commit 15d61ce

Browse files
chenjiandongxmandochen
andauthored
Add unique flag to group by images (#21)
Co-authored-by: mandochen <mandochen@tencent.com>
1 parent fe06503 commit 15d61ce

File tree

3 files changed

+92
-31
lines changed

3 files changed

+92
-31
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Flags:
6767
-k, --kubeconfig string path to the kubeconfig file to use for CLI requests.
6868
-n, --namespace string if present, list images in the specified namespace only. Use current namespace as fallback.
6969
-o, --output-format string output format. [json(j)|table(t)|yaml(y)] (default "table")
70+
-u, --unique Unique images group by namespace/container/images/pullPolicy.
7071
--version version for kubectl-images
7172
```
7273
@@ -109,6 +110,30 @@ Flags:
109110
| | nginx-deployment-66b6c48dd5-wmn9x | |
110111
+-------------+----------------------------------------+--------------------------------------------+
111112

113+
~ 🐶 kubectl images -A -c 0,1,3 -u
114+
[Summary]: 2 namespaces, 11 pods, 11 containers and 9 different images
115+
+-------------+----------------------------------------+--------------------------------------------+
116+
| Namespace | Pod | Image |
117+
+-------------+----------------------------------------+--------------------------------------------+
118+
| kube-system | coredns-78fcd69978-9pbjh | k8s.gcr.io/coredns/coredns:v1.8.4 | +
119+
+ +----------------------------------------+--------------------------------------------+
120+
| | etcd-docker-desktop | k8s.gcr.io/etcd:3.5.0-0 |
121+
+ +----------------------------------------+--------------------------------------------+
122+
| | kube-apiserver-docker-desktop | k8s.gcr.io/kube-apiserver:v1.22.5 |
123+
+ +----------------------------------------+--------------------------------------------+
124+
| | kube-controller-manager-docker-desktop | k8s.gcr.io/kube-controller-manager:v1.22.5 |
125+
+ +----------------------------------------+--------------------------------------------+
126+
| | kube-proxy-vc7fv | k8s.gcr.io/kube-proxy:v1.22.5 |
127+
+ +----------------------------------------+--------------------------------------------+
128+
| | kube-scheduler-docker-desktop | k8s.gcr.io/kube-scheduler:v1.22.5 |
129+
+ +----------------------------------------+--------------------------------------------+
130+
| | storage-provisioner | docker/desktop-storage-provisioner:v2.0 |
131+
+ +----------------------------------------+--------------------------------------------+
132+
| | vpnkit-controller | docker/desktop-vpnkit-controller:v2.0 |
133+
+-------------+----------------------------------------+--------------------------------------------+
134+
| nginx | nginx-deployment-66b6c48dd5-s9wv5 | nginx:1.14.2 |
135+
+-------------+----------------------------------------+--------------------------------------------+
136+
112137
~ 🐶 kubectl images -c 0,1,2,3,4 -n nginx -oj
113138
[Summary]: 1 namespaces, 2 pods, 2 containers and 1 different images
114139
[

cmd/main.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/spf13/cobra"
1010
)
1111

12-
const version = "0.4.0"
12+
const version = "0.5.0"
1313

1414
var rootCmd *cobra.Command
1515

@@ -41,7 +41,15 @@ func init() {
4141
allNamespace, _ := cmd.Flags().GetBool("all-namespaces")
4242
kubeConfig, _ := cmd.Flags().GetString("kubeConfig")
4343
context, _ := cmd.Flags().GetString("context")
44-
kubeImage := kubeimages.NewKubeImage(regx, allNamespace, namespace, columns, kubeConfig, context)
44+
unique, _ := cmd.Flags().GetBool("unique")
45+
kubeImage := kubeimages.NewKubeImage(regx, kubeimages.Parameters{
46+
AllNamespace: allNamespace,
47+
Namespace: namespace,
48+
Columns: columns,
49+
KubeConfig: kubeConfig,
50+
Context: context,
51+
Unique: unique,
52+
})
4553
kubeImage.Render(format)
4654
},
4755
}
@@ -51,6 +59,7 @@ func init() {
5159
rootCmd.Flags().StringP("kubeconfig", "k", "", "path to the kubeconfig file to use for CLI requests.")
5260
rootCmd.Flags().StringP("output-format", "o", "table", "output format. [json(j)|table(t)|yaml(y)]")
5361
rootCmd.Flags().StringP("context", "C", "", "The name of the kubeconfig context to use.")
62+
rootCmd.Flags().BoolP("unique", "u", false, "Unique images group by namespace/container/images/pullPolicy.")
5463
}
5564

5665
func main() {

kubectl_images.go

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,27 @@ const (
2222
labelImagePullPolicy = "ImagePullPolicy"
2323
)
2424

25+
type Parameters struct {
26+
AllNamespace bool
27+
Namespace string
28+
Columns string
29+
KubeConfig string
30+
Context string
31+
Unique bool
32+
}
33+
2534
// KubeImage is the representation of a container image used in the cluster.
2635
type KubeImage struct {
27-
entities []*ImageEntity
28-
allNamespace bool
29-
namespace string
30-
columns []string
31-
kubeconfig string
32-
context string
33-
regx *regexp.Regexp
36+
entities []*ImageEntity
37+
columns []string
38+
regx *regexp.Regexp
39+
params Parameters
3440
}
3541

3642
// NewKubeImage creates a new KubeImage instance.
37-
func NewKubeImage(regx *regexp.Regexp, allNamespace bool, namespace, columns, kubeconfig, context string) *KubeImage {
43+
func NewKubeImage(regx *regexp.Regexp, params Parameters) *KubeImage {
3844
names := make([]string, 0)
39-
for _, c := range stringSplit(columns, ",") {
45+
for _, c := range stringSplit(params.Columns, ",") {
4046
switch c {
4147
case "0":
4248
names = append(names, labelNamespace)
@@ -52,12 +58,9 @@ func NewKubeImage(regx *regexp.Regexp, allNamespace bool, namespace, columns, ku
5258
}
5359

5460
return &KubeImage{
55-
allNamespace: allNamespace,
56-
columns: names,
57-
namespace: namespace,
58-
kubeconfig: kubeconfig,
59-
context: context,
60-
regx: regx,
61+
columns: names,
62+
params: params,
63+
regx: regx,
6164
}
6265
}
6366

@@ -142,23 +145,23 @@ func stringSplit(in, sep string) []string {
142145
// Commands builds the command to be executed based on user input.
143146
func (ki *KubeImage) Commands() []string {
144147
kubecfg := make([]string, 0)
145-
if ki.kubeconfig != "" {
146-
kubecfg = append(kubecfg, "--kubeconfig", ki.kubeconfig)
148+
if ki.params.KubeConfig != "" {
149+
kubecfg = append(kubecfg, "--kubeconfig", ki.params.KubeConfig)
147150
}
148151

149-
if ki.context != "" {
150-
kubecfg = append(kubecfg, "--context", ki.context)
152+
if ki.params.Context != "" {
153+
kubecfg = append(kubecfg, "--context", ki.params.Context)
151154
}
152155

153-
if ki.allNamespace {
156+
if ki.params.AllNamespace {
154157
return append([]string{"get", "pods", "--all-namespaces", "-o", gotemplate}, kubecfg...)
155-
} else if ki.namespace != "" {
156-
return append([]string{"get", "pods", "-n", ki.namespace, "-o", gotemplate}, kubecfg...)
158+
} else if ki.params.Namespace != "" {
159+
return append([]string{"get", "pods", "-n", ki.params.Namespace, "-o", gotemplate}, kubecfg...)
157160
}
158161
return append([]string{"get", "pods", "-o", gotemplate}, kubecfg...)
159162
}
160163

161-
func (ki *KubeImage) run() {
164+
func (ki *KubeImage) exec() {
162165
process := exec.Command("kubectl", ki.Commands()...)
163166
bs, err := process.CombinedOutput()
164167
if err != nil {
@@ -206,6 +209,26 @@ func (ki *KubeImage) run() {
206209
}
207210
}
208211

212+
func (ki *KubeImage) groupBy() []*ImageEntity {
213+
if !ki.params.Unique {
214+
return ki.entities
215+
}
216+
217+
set := make(map[string]struct{})
218+
entities := make([]*ImageEntity, 0)
219+
220+
for i, entity := range ki.entities {
221+
k := fmt.Sprintf("%s/%s/%s/%s", entity.Namespace, entity.Container, entity.Image, entity.ImagePullPolicy)
222+
if _, ok := set[k]; ok {
223+
continue
224+
}
225+
226+
set[k] = struct{}{}
227+
entities = append(entities, ki.entities[i])
228+
}
229+
return entities
230+
}
231+
209232
func (ki *KubeImage) summary() {
210233
namespaceCnt := NewCounter()
211234
podCnt := NewCounter()
@@ -230,15 +253,18 @@ func (ki *KubeImage) tableRender() {
230253
table.SetAutoFormatHeaders(false)
231254
table.SetAutoMergeCells(true)
232255
table.SetRowLine(true)
233-
for _, entity := range ki.entities {
256+
257+
entities := ki.groupBy()
258+
for _, entity := range entities {
234259
table.Append(entity.selectBy(ki.columns))
235260
}
236261
table.Render()
237262
}
238263

239264
func (ki *KubeImage) jsonRender() {
240-
records := make([]ImageEntity, 0, len(ki.entities))
241-
for _, entity := range ki.entities {
265+
entities := ki.groupBy()
266+
records := make([]ImageEntity, 0, len(entities))
267+
for _, entity := range entities {
242268
records = append(records, entity.filterBy(ki.columns))
243269
}
244270

@@ -251,8 +277,9 @@ func (ki *KubeImage) jsonRender() {
251277
}
252278

253279
func (ki *KubeImage) yamlRender() {
254-
records := make([]ImageEntity, 0, len(ki.entities))
255-
for _, entity := range ki.entities {
280+
entities := ki.groupBy()
281+
records := make([]ImageEntity, 0, len(entities))
282+
for _, entity := range entities {
256283
records = append(records, entity.filterBy(ki.columns))
257284
}
258285

@@ -266,7 +293,7 @@ func (ki *KubeImage) yamlRender() {
266293

267294
// Render renders and displays the table output.
268295
func (ki *KubeImage) Render(format string) {
269-
ki.run()
296+
ki.exec()
270297

271298
if len(ki.entities) == 0 {
272299
fmt.Fprintln(os.Stdout, "[Oh...] No images matched!")

0 commit comments

Comments
 (0)