Skip to content

Commit

Permalink
feature: display the size of images (#25)
Browse files Browse the repository at this point in the history
* feature: display the size of images

* docs update

* minor: upgrade gomod version

---------

Co-authored-by: mandochen <[email protected]>
  • Loading branch information
chenjiandongx and mandochen authored May 31, 2023
1 parent 4ba9f51 commit 1a3163e
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Examples:

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] (default "1,2,3")
-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.
Expand Down
4 changes: 2 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.5.2"
const version = "0.6.0"

var rootCmd *cobra.Command

Expand Down Expand Up @@ -55,7 +55,7 @@ func init() {
}
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]")
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.")
Expand Down
9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
module github.com/chenjiandongx/kubectl-images

go 1.13
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
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz
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=
Expand Down
96 changes: 79 additions & 17 deletions kubectl_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@ import (
"os"
"os/exec"
"regexp"
"strconv"
"strings"

"github.com/dustin/go-humanize"
"github.com/olekukonko/tablewriter"
"gopkg.in/yaml.v2"
)

const (
gotemplate = `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}} {{end}}`
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}} {{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 {
Expand All @@ -33,14 +37,17 @@ type Parameters struct {

// KubeImage is the representation of a container image used in the cluster.
type KubeImage struct {
entities []*ImageEntity
columns []string
regx *regexp.Regexp
params Parameters
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 {
Expand All @@ -54,13 +61,18 @@ func NewKubeImage(regx *regexp.Regexp, params Parameters) *KubeImage {
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,
columns: names,
params: params,
regx: regx,
imageSize: make(map[string]int),
needNodeInfo: needNodeInfo,
}
}

Expand All @@ -71,6 +83,7 @@ type ImageEntity struct {
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 {
Expand All @@ -87,6 +100,8 @@ func (ie *ImageEntity) selectBy(columns []string) []string {
result = append(result, ie.Image)
case labelImagePullPolicy:
result = append(result, ie.ImagePullPolicy)
case labelImageSize:
result = append(result, ie.ImageSize)
}
}
return result
Expand All @@ -106,6 +121,8 @@ func (ie *ImageEntity) filterBy(columns []string) ImageEntity {
entity.Image = ie.Image
case labelImagePullPolicy:
entity.ImagePullPolicy = ie.ImagePullPolicy
case labelImageSize:
entity.ImageSize = ie.ImageSize
}
}
return entity
Expand Down Expand Up @@ -142,8 +159,8 @@ func stringSplit(in, sep string) []string {
return out
}

// Commands builds the command to be executed based on user input.
func (ki *KubeImage) Commands() []string {
// 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)
Expand All @@ -154,18 +171,60 @@ func (ki *KubeImage) Commands() []string {
}

if ki.params.AllNamespace {
return append([]string{"get", "pods", "--all-namespaces", "-o", gotemplate}, kubecfg...)
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", gotemplate}, kubecfg...)
return append([]string{"get", "pods", "-n", ki.params.Namespace, "-o", podTemplate}, kubecfg...)
}
return append([]string{"get", "pods", "-o", gotemplate}, kubecfg...)
return append([]string{"get", "pods", "-o", podTemplate}, kubecfg...)
}

func (ki *KubeImage) exec() {
process := exec.Command("kubectl", ki.Commands()...)
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) 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 3:
size, err := strconv.Atoi(items[2])
if err != nil {
continue
}
ki.imageSize[items[0]] = size
ki.imageSize[items[1]] = 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 command error: %v, %s", err, string(bs))
fmt.Fprintf(os.Stderr, "[Oh...] Execute pods command error: %v, %s", err, string(bs))
os.Exit(1)
}

Expand Down Expand Up @@ -293,7 +352,10 @@ func (ki *KubeImage) yamlRender() {

// Render renders and displays the table output.
func (ki *KubeImage) Render(format string) {
ki.exec()
ki.execPodCommand()
if ki.needNodeInfo {
ki.execNodeCommand()
}

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

0 comments on commit 1a3163e

Please sign in to comment.