Skip to content

Commit

Permalink
Merge pull request #25 from kubecost/mmd-tui
Browse files Browse the repository at this point in the history
Add a TUI that supports monthly rate display and most aggregations
  • Loading branch information
michaelmdresser authored Mar 8, 2021
2 parents 33a8393 + b1ceb49 commit fd0abd6
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 49 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ As long as the binary is still named `kubectl-cost` and is somewhere in your `PA

## Usage

There are three supported subcommands: `namespace`, `deployment`, `controller`, and `label`, which display cost information aggregated by the name of the subcommand (see Examples). Each subcommand has two primary modes, rate and non-rate. Rate (the default) displays the projected monthly cost based on the activity during the window. Non-rate (`--historical`) displays the total cost for the duration of the window.
There are five supported subcommands: `namespace`, `deployment`, `controller`, `label`, and `tui`, which display cost information aggregated by the name of the subcommand (see Examples). Each subcommand has two primary modes, rate and non-rate. Rate (the default) displays the projected monthly cost based on the activity during the window. Non-rate (`--historical`) displays the total cost for the duration of the window.

The exception to these descriptions is `kubectl cost tui`, which displays a TUI and is currently limited to only monthly rate projections. It currently supports all of the previously mentioned aggregations except label. These limitations are because the TUI is an experimental feature - if you like it, let us know! We'd be happy to dedicate time to expanding its functionality.


#### Examples
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ go 1.15

require (
github.com/davecgh/go-spew v1.1.1
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591
github.com/imdario/mergo v0.3.11 // indirect
github.com/jedib0t/go-pretty/v6 v6.1.0
github.com/kubecost/cost-model v1.53.1-0.20210203002707-90986f4155cd
github.com/rivo/tview v0.0.0-20210216210747-c3311ba972c1
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
Expand Down
19 changes: 19 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 h1:0WWUDZ1oxq7NxVyGo8M3KI5jbkiwNAdZFFzAdC68up4=
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
github.com/getsentry/sentry-go v0.6.1/go.mod h1:0yZBuzSvbZwBnvaF9VwZIMen3kXscY8/uasKtAX1qG8=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
Expand Down Expand Up @@ -347,6 +351,8 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
Expand All @@ -362,8 +368,11 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
Expand Down Expand Up @@ -434,6 +443,11 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/tview v0.0.0-20210216210747-c3311ba972c1 h1:O32tN8j+pHozyFzLdQyZAZfqtVpF5V9O4dGISV89TEs=
github.com/rivo/tview v0.0.0-20210216210747-c3311ba972c1/go.mod h1:n2q/ydglZJ1kqxiNrnYO+FaX1H14vA0wKyIo953QakU=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
Expand Down Expand Up @@ -633,6 +647,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -659,6 +674,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand All @@ -671,6 +688,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down
8 changes: 3 additions & 5 deletions pkg/cmd/controller.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"context"
"fmt"

"k8s.io/cli-runtime/pkg/genericclioptions"
Expand Down Expand Up @@ -53,7 +54,7 @@ func newCmdCostController(streams genericclioptions.IOStreams) *cobra.Command {
func runCostController(ko *KubeOptions, no *CostOptionsController) error {

if !no.isHistorical {
aggs, err := query.QueryAggCostModel(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "controller", "")
aggs, err := query.QueryAggCostModel(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "controller", "", context.Background())
if err != nil {
return fmt.Errorf("failed to query agg cost model: %s", err)
}
Expand All @@ -63,16 +64,13 @@ func runCostController(ko *KubeOptions, no *CostOptionsController) error {

applyNamespaceFilter(aggs, no.filterNamespace)

err = writeAggregationRateTable(
writeAggregationRateTable(
ko.Out,
aggs,
[]string{"namespace", "controller"},
controllerTitleExtractor,
no.displayOptions,
)
if err != nil {
return fmt.Errorf("failed to write table output: %s", err)
}
} else {
// Not supported because the allocation API does not return the namespace
// of controllers.
Expand Down
19 changes: 16 additions & 3 deletions pkg/cmd/cost.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,23 @@ var (
# Show the projected monthly rate for each namespace based on the last 5 days of activity.
%[1]s cost namespace --window 5d
# Show how much each namespace cost over the past 5 days with additional CPU and memory cost and efficiency breakdown.
%[1]s cost namespace --historical --window 5d --show-cpu --show-memory --show-efficiency
# Show how much each namespace cost over the past 5 days with additional CPU and memory cost and without efficiency.
%[1]s cost namespace --historical --window 5d --show-cpu --show-memory --show-efficiency=false
# Show the projected monthly rate for each controller based on the last 5 days of activity with PV (persistent volume) cost breakdown.
%[1]s cost controller --window 5d --show-pv
# Show costs over the past 5 days broken down by the value of the "app" label:
%[1]s cost label --historical -l app
# Show the projected monthly rate for each deployment based on the last month of activity with CPU, memory, GPU, PV, and network cost breakdown.
%[1]s cost deployment --window month --show-cpu --show-memory --show-gpu --show-pv --show-network
%[1]s cost deployment --window month -A
# Show the projected monthly rate for each deployment in the "kubecost" namespace based on the last 3 days of activity with CPU cost breakdown.
%[1]s cost deployment --window 3d --show-cpu -N kubecost
# The same, but with a non-standard Kubecost deployment in the namespace "kubecost-staging" with the cost analyzer service called "kubecost-staging-cost-analyzer".
%[1]s cost deployment --window 3d --show-cpu -N kubecost -n kubecost-staging --service-name kubecost-staging-cost-analyzer
`

errNoContext = fmt.Errorf("no context is currently set, use %q to select a new one", "kubectl config use-context <context>")
Expand Down Expand Up @@ -85,6 +97,7 @@ func NewCmdCost(streams genericclioptions.IOStreams) *cobra.Command {
cmd.AddCommand(newCmdCostDeployment(streams))
cmd.AddCommand(newCmdCostController(streams))
cmd.AddCommand(newCmdCostLabel(streams))
cmd.AddCommand(newCmdTUI(streams))

return cmd
}
Expand Down
8 changes: 3 additions & 5 deletions pkg/cmd/deployment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"context"
"fmt"

"k8s.io/cli-runtime/pkg/genericclioptions"
Expand Down Expand Up @@ -55,7 +56,7 @@ func newCmdCostDeployment(streams genericclioptions.IOStreams) *cobra.Command {
func runCostDeployment(ko *KubeOptions, no *CostOptionsDeployment) error {

if !no.isHistorical {
aggs, err := query.QueryAggCostModel(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "deployment", "")
aggs, err := query.QueryAggCostModel(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "deployment", "", context.Background())
if err != nil {
return fmt.Errorf("failed to query agg cost model: %s", err)
}
Expand All @@ -65,16 +66,13 @@ func runCostDeployment(ko *KubeOptions, no *CostOptionsDeployment) error {

applyNamespaceFilter(aggs, no.filterNamespace)

err = writeAggregationRateTable(
writeAggregationRateTable(
ko.Out,
aggs,
[]string{"namespace", "deployment"},
deploymentTitleExtractor,
no.displayOptions,
)
if err != nil {
return fmt.Errorf("failed to write table output: %s", err)
}
} else {
// Not supported because the allocation API does not return deployment names.
return fmt.Errorf("kubectl cost deployment does not yet support historical queries")
Expand Down
15 changes: 5 additions & 10 deletions pkg/cmd/label.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"context"
"fmt"

"k8s.io/cli-runtime/pkg/genericclioptions"
Expand Down Expand Up @@ -59,35 +60,29 @@ func newCmdCostLabel(streams genericclioptions.IOStreams) *cobra.Command {
func runCostLabel(ko *KubeOptions, no *CostOptionsLabel) error {

if !no.isHistorical {
aggs, err := query.QueryAggCostModel(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "label", no.queryLabel)
aggs, err := query.QueryAggCostModel(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "label", no.queryLabel, context.Background())
if err != nil {
return fmt.Errorf("failed to query agg cost model: %s", err)
}

// don't show unallocated controller data
delete(aggs, "__unallocated__")

err = writeAggregationRateTable(
writeAggregationRateTable(
ko.Out,
aggs,
[]string{"label"},
noopTitleExtractor,
no.displayOptions,
)
if err != nil {
return fmt.Errorf("failed to write table output: %s", err)
}
} else {
allocations, err := query.QueryAllocation(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, fmt.Sprintf("label:%s", no.queryLabel))
allocations, err := query.QueryAllocation(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, fmt.Sprintf("label:%s", no.queryLabel), context.Background())
if err != nil {
return fmt.Errorf("failed to query allocation API: %s", err)
}

// Use allocations[0] because the query accumulates to a single result
err = writeAllocationTable(ko.Out, "Label", allocations[0], no.displayOptions)
if err != nil {
return fmt.Errorf("failed to write table output: %s", err)
}
writeAllocationTable(ko.Out, "Label", allocations[0], no.displayOptions)
}

return nil
Expand Down
15 changes: 5 additions & 10 deletions pkg/cmd/namespace.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"context"
"fmt"

"k8s.io/cli-runtime/pkg/genericclioptions"
Expand Down Expand Up @@ -50,32 +51,26 @@ func newCmdCostNamespace(streams genericclioptions.IOStreams) *cobra.Command {
func runCostNamespace(ko *KubeOptions, no *CostOptionsNamespace) error {

if !no.isHistorical {
aggs, err := query.QueryAggCostModel(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "namespace", "")
aggs, err := query.QueryAggCostModel(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "namespace", "", context.Background())
if err != nil {
return fmt.Errorf("failed to query agg cost model: %s", err)
}

err = writeAggregationRateTable(
writeAggregationRateTable(
ko.Out,
aggs,
[]string{"namespace"},
noopTitleExtractor,
no.displayOptions,
)
if err != nil {
return fmt.Errorf("failed to write table output: %s", err)
}
} else {
allocations, err := query.QueryAllocation(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "namespace")
allocations, err := query.QueryAllocation(ko.clientset, *ko.configFlags.Namespace, no.serviceName, no.window, "namespace", context.Background())
if err != nil {
return fmt.Errorf("failed to query allocation API: %s", err)
}

// Use allocations[0] because the query accumulates to a single result
err = writeAllocationTable(ko.Out, "Namespace", allocations[0], no.displayOptions)
if err != nil {
return fmt.Errorf("failed to write table output: %s", err)
}
writeAllocationTable(ko.Out, "Namespace", allocations[0], no.displayOptions)
}

return nil
Expand Down
31 changes: 20 additions & 11 deletions pkg/cmd/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ func formatFloat(f float64) string {
return fmt.Sprintf("%.6f", f)
}

func writeAllocationTable(out io.Writer, allocationType string, allocations map[string]kubecost.Allocation, opts displayOptions) error {
t := table.NewWriter()
func writeAllocationTable(out io.Writer, allocationType string, allocations map[string]kubecost.Allocation, opts displayOptions) {
t := makeAllocationTable(allocationType, allocations, opts)

t.SetOutputMirror(out)
t.Render()
}

func makeAllocationTable(allocationType string, allocations map[string]kubecost.Allocation, opts displayOptions) table.Writer {
t := table.NewWriter()

columnConfigs := []table.ColumnConfig{}

Expand Down Expand Up @@ -137,7 +143,7 @@ func writeAllocationTable(out io.Writer, allocationType string, allocations map[
t.SortBy([]table.SortBy{
{
Name: "Total Cost (All)",
Mode: table.Dsc,
Mode: table.DscNumeric,
},
})

Expand Down Expand Up @@ -239,9 +245,8 @@ func writeAllocationTable(out io.Writer, allocationType string, allocations map[
footerRow = append(footerRow, formatFloat(summedCost))

t.AppendFooter(footerRow)
t.Render()

return nil
return t
}

func deploymentTitleExtractor(aggregationName string) ([]string, error) {
Expand Down Expand Up @@ -270,9 +275,15 @@ func noopTitleExtractor(aggregationName string) ([]string, error) {
return []string{aggregationName}, nil
}

func writeAggregationRateTable(out io.Writer, aggs map[string]query.Aggregation, rowTitles []string, rowTitleExtractor func(string) ([]string, error), opts displayOptions) error {
t := table.NewWriter()
func writeAggregationRateTable(out io.Writer, aggs map[string]query.Aggregation, rowTitles []string, rowTitleExtractor func(string) ([]string, error), opts displayOptions) {
t := makeAggregationRateTable(aggs, rowTitles, rowTitleExtractor, opts)

t.SetOutputMirror(out)
t.Render()
}

func makeAggregationRateTable(aggs map[string]query.Aggregation, rowTitles []string, rowTitleExtractor func(string) ([]string, error), opts displayOptions) table.Writer {
t := table.NewWriter()

columnConfigs := []table.ColumnConfig{}

Expand Down Expand Up @@ -397,10 +408,9 @@ func writeAggregationRateTable(out io.Writer, aggs map[string]query.Aggregation,
t.AppendHeader(headerRow)

sortByConfig := []table.SortBy{}

sortByConfig = append(sortByConfig, table.SortBy{
Name: "Monthly Rate (All)",
Mode: table.Dsc,
Mode: table.DscNumeric,
})

t.SortBy(sortByConfig)
Expand Down Expand Up @@ -512,7 +522,6 @@ func writeAggregationRateTable(out io.Writer, aggs map[string]query.Aggregation,
footerRow = append(footerRow, formatFloat(summedCost))

t.AppendFooter(footerRow)
t.Render()

return nil
return t
}
Loading

0 comments on commit fd0abd6

Please sign in to comment.