Skip to content

Commit 70769fe

Browse files
committed
feat: add output formats split and split-wide
1 parent a58f4a4 commit 70769fe

File tree

5 files changed

+77
-24
lines changed

5 files changed

+77
-24
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ Apart from the flags defined by the [`genericclioptions`](https://pkg.go.dev/k8s
6262
- `--namespace`, `-n`: If present, the namespace scope for the request
6363
- `--no-colors`: Do not use colors to highlight increase/decrease percentage values
6464
- `--no-headers`: Do not print table headers
65-
- `--output`, `-o`: Output format. Empty string or `wide`
65+
- `--output`, `-o`: Output format. One of: `wide` | `split` | `split-wide`
6666
- `--recommendation-type`: The type of recommendation to use in comparisons. One of: `lower-bound`, `target`, `uncapped-target`, `upper-bound`. Default to `target`
6767
- see [`RecommendedContainerResources`](https://github.com/kubernetes/autoscaler/blob/master/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1/types.go#L245) for more details about the fields represented by each possible value
6868
- `--show-containers`, `-c`: Display containers recommendations for each `VerticalPodAutoscaler` resource
6969
- `--show-kind`, `-k`: Show the resource type for the requested object(s) and their target
7070
- `--show-namespace`: Show resource namespace as the first column
71-
- `--sort-columns`: Comma-separated list of column names for sorting the table. Any of: `cpu-diff`, `cpu-rec`, `cpu-req`, `mem-diff`, `mem-rec`, `mem-request`, `name`, `namespace`, `target`. Default to `namespace,name`
71+
- `--sort-columns`: Comma-separated list of column names for sorting the table. Any of: `cpu-diff` | `cpu-rec` | `cpu-req` | `mem-diff` | `mem-rec` | `mem-req` | `name` | `namespace` | `target`. Default to `namespace,name`
7272
- `--sort-order`: The sort order of the table columns. Either `asc` or `desc`. Default to `asc`
7373

7474
To view the full list of available options, use the following command:

cli/command.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
// Embed command example.
66
_ "embed"
77
"fmt"
8+
"os"
9+
"sort"
810
"strings"
911
"time"
1012

@@ -72,6 +74,9 @@ func NewCmd(streams genericclioptions.IOStreams, name string) *cobra.Command {
7274
comps := get.CompGetResource(f, cmd, vpaPlural, tc)
7375
return comps, cobra.ShellCompDirectiveNoFileComp
7476
},
77+
PersistentPreRun: func(_ *cobra.Command, _ []string) {
78+
opts.Flags.Tidy()
79+
},
7580
}
7681
cmd.SetVersionTemplate("{{printf \"%s\" .Version}}\n")
7782

@@ -174,10 +179,39 @@ func (co *CommandOptions) Execute() error {
174179
}
175180
klog.V(4).Infof("fetched %d VPA(s)", len(vpas))
176181

177-
table := co.bindRecommendationsAndRequests(vpas)
178-
table.SortBy(co.Flags.SortOrder, co.Flags.SortColumns...)
182+
var tables []table
179183

180-
return table.Print(co.Out, co.Flags)
184+
if co.Flags.split {
185+
// Sort the result list by namespace, and create
186+
// a table of VPA objects for each.
187+
sort.SliceStable(vpas, func(i, j int) bool {
188+
return vpas[i].Namespace < vpas[j].Namespace
189+
})
190+
j := 0
191+
for i := 0; i < len(vpas); i++ {
192+
if i != 0 && vpas[i].Namespace != vpas[i-1].Namespace || i == len(vpas)-1 {
193+
table := co.bindRecommendationsAndRequests(vpas[j:i])
194+
tables = append(tables, table)
195+
j = i
196+
}
197+
}
198+
} else {
199+
table := co.bindRecommendationsAndRequests(vpas)
200+
tables = append(tables, table)
201+
}
202+
for i := range tables {
203+
tables[i].SortBy(co.Flags.SortOrder, co.Flags.SortColumns...)
204+
if err := tables[i].Print(co.Out, co.Flags); err != nil {
205+
return err
206+
}
207+
if i != len(tables)-1 {
208+
_, err := os.Stdout.WriteString("\n")
209+
if err != nil {
210+
return err
211+
}
212+
}
213+
}
214+
return nil
181215
}
182216

183217
// bindRecommendationsAndRequests returns a table that bind the

cli/flags.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ const (
3030
flagRecommendationType = "recommendation-type"
3131
)
3232

33+
const (
34+
wideOutput = "wide"
35+
splitOutput = "split"
36+
splitWideOutput = "split-wide"
37+
)
38+
3339
var (
3440
defaultSortOrder = orderAsc
3541
defaultSortColumns = []string{"namespace", "name"}
@@ -50,6 +56,9 @@ type Flags struct {
5056
FieldSelector string
5157
Output string
5258
RecommendationType vpa.RecommendationType
59+
60+
wide bool
61+
split bool
5362
}
5463

5564
// DefaultFlags returns default command flags.
@@ -95,12 +104,25 @@ func (f *Flags) AddFlags(flags *pflag.FlagSet) {
95104
"Selector (field query) to filter on, supports '=', '==', and '!=' (e.g. --field-selector key1=value1,key2=value2)")
96105

97106
flags.StringVarP(&f.Output, flagOutput, flagOutputShorthand, f.Output,
98-
"Output format. Empty string or 'wide'")
107+
"Output format. One of 'wide', 'slit', 'split-wide'")
99108

100109
flags.Var(&f.RecommendationType, flagRecommendationType,
101110
fmt.Sprintf("The type of recommendation to use in comparisons. One of: %s", strings.Join(recommendationTypeFlagValues(), ", ")))
102111
}
103112

113+
// Tidy post-processes the flags.
114+
func (f *Flags) Tidy() {
115+
switch f.Output {
116+
case wideOutput:
117+
f.wide = true
118+
case splitOutput:
119+
f.split = true
120+
case splitWideOutput:
121+
f.wide = true
122+
f.split = true
123+
}
124+
}
125+
104126
func sortColumnsFlagValues() []string {
105127
keys := make([]string, 0, len(columnLessFunc))
106128
for k := range columnLessFunc {
@@ -112,11 +134,15 @@ func sortColumnsFlagValues() []string {
112134

113135
func recommendationTypeFlagValues() []string {
114136
v := []string{
115-
vpa.RecommendationTarget.String(),
116-
vpa.RecommendationLowerBound.String(),
117-
vpa.RecommendationUpperBound.String(),
118-
vpa.RecommendationUncappedTarget.String(),
137+
singleQuoted(vpa.RecommendationTarget.String()),
138+
singleQuoted(vpa.RecommendationLowerBound.String()),
139+
singleQuoted(vpa.RecommendationUpperBound.String()),
140+
singleQuoted(vpa.RecommendationUncappedTarget.String()),
119141
}
120142
sort.Strings(v)
121143
return v
122144
}
145+
146+
func singleQuoted(s string) string {
147+
return fmt.Sprintf("'%s'", s)
148+
}

cli/table.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const (
1919
treeElemPrefix = `├─`
2020
treeLastElemPrefix = `└─`
2121
tableUnsetCell = `-`
22-
wideOutput = "wide"
2322
)
2423

2524
type sortOrder string
@@ -82,19 +81,16 @@ func (tr tableRow) toTableData(flags *Flags, isChild bool) []string {
8281
if flags.ShowNamespace {
8382
rowData = append(rowData, tr.Namespace)
8483
}
85-
rowData = append(rowData,
86-
name,
87-
tr.Mode,
88-
targetName,
89-
)
90-
if flags.Output == wideOutput {
84+
rowData = append(rowData, name, tr.Mode, targetName)
85+
86+
if flags.wide {
9187
rowData = append(rowData,
9288
formatQuantity(tr.Requests.CPU),
9389
formatQuantity(tr.Recommendations.CPU),
9490
)
9591
}
9692
rowData = append(rowData, formatPercentage(tr.CPUDifference, flags.NoColors))
97-
if flags.Output == wideOutput {
93+
if flags.wide {
9894
rowData = append(rowData,
9995
formatQuantity(tr.Requests.Memory),
10096
formatQuantity(tr.Recommendations.Memory),
@@ -154,11 +150,11 @@ func (t table) Print(w io.Writer, flags *Flags) error {
154150
headers = append(headers, hdrNamespace)
155151
}
156152
headers = append(headers, hdrName, hdrMode, hdrTarget)
157-
if flags.Output == wideOutput {
153+
if flags.wide {
158154
headers = append(headers, hdrCPURequest, hdrCPUTarget)
159155
}
160156
headers = append(headers, hdrCPUDifference)
161-
if flags.Output == wideOutput {
157+
if flags.wide {
162158
headers = append(headers, hdrMemRequest, hdrMemTarget)
163159
}
164160
headers = append(headers, hdrMemDifference)

go.sum

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,12 +1137,10 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
11371137
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
11381138
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
11391139
k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA=
1140-
k8s.io/api v0.23.1 h1:ncu/qfBfUoClqwkTGbeRqqOqBCRoUAflMuOaOD7J0c8=
11411140
k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo=
11421141
k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38=
11431142
k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ=
11441143
k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
1145-
k8s.io/apimachinery v0.23.1 h1:sfBjlDFwj2onG0Ijx5C+SrAoeUscPrmghm7wHP+uXlo=
11461144
k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno=
11471145
k8s.io/apimachinery v0.23.3 h1:7IW6jxNzrXTsP0c8yXz2E5Yx/WTzVPTsHIx/2Vm0cIk=
11481146
k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM=
@@ -1151,7 +1149,6 @@ k8s.io/autoscaler/vertical-pod-autoscaler v0.9.2/go.mod h1:PwWTGRRCxefhAezrDbG/t
11511149
k8s.io/cli-runtime v0.23.1 h1:vHUZrq1Oejs0WaJnxs09mLHKScvIIl2hMSthhS8o8Yo=
11521150
k8s.io/cli-runtime v0.23.1/go.mod h1:r9r8H/qfXo9w+69vwUL7LokKlLRKW5D6A8vUKCx+YL0=
11531151
k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw=
1154-
k8s.io/client-go v0.23.1 h1:Ma4Fhf/p07Nmj9yAB1H7UwbFHEBrSPg8lviR24U2GiQ=
11551152
k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0=
11561153
k8s.io/client-go v0.23.3 h1:23QYUmCQ/W6hW78xIwm3XqZrrKZM+LWDqW2zfo+szJs=
11571154
k8s.io/client-go v0.23.3/go.mod h1:47oMd+YvAOqZM7pcQ6neJtBiFH7alOyfunYN48VsmwE=
@@ -1201,8 +1198,8 @@ sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLC
12011198
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
12021199
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
12031200
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
1204-
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
12051201
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
1202+
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
12061203
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
12071204
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
12081205
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=

0 commit comments

Comments
 (0)