2019-10-23 17:09:04 +00:00
|
|
|
package main
|
2019-10-24 19:20:49 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-11-07 00:45:00 +00:00
|
|
|
"encoding/json"
|
2019-10-24 19:20:49 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2019-10-28 22:23:40 +00:00
|
|
|
"strconv"
|
2019-10-24 19:20:49 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2019-10-28 22:23:40 +00:00
|
|
|
"github.com/fatih/color"
|
2019-10-24 19:20:49 +00:00
|
|
|
"github.com/influxdata/influxdb"
|
2019-10-25 21:39:38 +00:00
|
|
|
"github.com/influxdata/influxdb/http"
|
2019-10-24 19:20:49 +00:00
|
|
|
"github.com/influxdata/influxdb/pkger"
|
2019-10-28 22:23:40 +00:00
|
|
|
"github.com/olekukonko/tablewriter"
|
2019-10-24 19:20:49 +00:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
input "github.com/tcnksm/go-input"
|
|
|
|
)
|
|
|
|
|
|
|
|
func pkgCmd() *cobra.Command {
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "pkg",
|
|
|
|
Short: "Create a reusable pkg to create resources in a declarative manner",
|
|
|
|
}
|
|
|
|
|
|
|
|
path := cmd.Flags().String("path", "", "path to manifest file")
|
|
|
|
cmd.MarkFlagFilename("path", "yaml", "yml", "json")
|
|
|
|
cmd.MarkFlagRequired("path")
|
|
|
|
|
|
|
|
orgID := cmd.Flags().String("org-id", "", "The ID of the organization that owns the bucket")
|
|
|
|
cmd.MarkFlagRequired("org-id")
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
hasColor := cmd.Flags().Bool("color", true, "Enable color in output, defaults true")
|
|
|
|
hasTableBorders := cmd.Flags().Bool("table-borders", true, "Enable table borders, defaults true")
|
|
|
|
|
|
|
|
cmd.RunE = pkgApply(orgID, path, hasColor, hasTableBorders)
|
2019-10-24 19:20:49 +00:00
|
|
|
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
func pkgApply(orgID, path *string, hasColor, hasTableBorders *bool) func(*cobra.Command, []string) error {
|
2019-10-24 19:20:49 +00:00
|
|
|
return func(cmd *cobra.Command, args []string) (e error) {
|
2019-10-30 17:55:13 +00:00
|
|
|
if !*hasColor {
|
|
|
|
color.NoColor = true
|
|
|
|
}
|
|
|
|
|
2019-10-25 21:39:38 +00:00
|
|
|
influxOrgID, err := influxdb.IDFromString(*orgID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-24 19:20:49 +00:00
|
|
|
svc, err := newPkgerSVC(flags)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
pkg, err := pkgFromFile(*path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-28 22:23:40 +00:00
|
|
|
_, diff, err := svc.DryRun(context.Background(), *influxOrgID, pkg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
printPkgDiff(*hasColor, *hasTableBorders, diff)
|
2019-10-24 19:20:49 +00:00
|
|
|
|
|
|
|
ui := &input.UI{
|
|
|
|
Writer: os.Stdout,
|
|
|
|
Reader: os.Stdin,
|
|
|
|
}
|
|
|
|
|
|
|
|
confirm := getInput(ui, "Confirm application of the above resources (y/n)", "n")
|
|
|
|
if strings.ToLower(confirm) != "y" {
|
2019-10-28 22:23:40 +00:00
|
|
|
fmt.Fprintln(os.Stdout, "aborted application of package")
|
2019-10-24 19:20:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
summary, err := svc.Apply(context.Background(), *influxOrgID, pkg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
printPkgSummary(*hasColor, *hasTableBorders, summary)
|
2019-10-24 19:20:49 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newPkgerSVC(f Flags) (*pkger.Service, error) {
|
|
|
|
bucketSVC, err := newBucketService(f)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-25 21:39:38 +00:00
|
|
|
labelSVC, err := newLabelService(f)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-30 21:13:42 +00:00
|
|
|
dashSVC, err := newDashboardService(f)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
varSVC, err := newVariableService(f)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return pkger.NewService(
|
|
|
|
pkger.WithBucketSVC(bucketSVC),
|
|
|
|
pkger.WithDashboardSVC(dashSVC),
|
|
|
|
pkger.WithLabelSVC(labelSVC),
|
|
|
|
pkger.WithVariableSVC(varSVC),
|
|
|
|
), nil
|
2019-10-30 21:13:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newDashboardService(f Flags) (influxdb.DashboardService, error) {
|
|
|
|
if f.local {
|
|
|
|
return newLocalKVService()
|
|
|
|
}
|
|
|
|
return &http.DashboardService{
|
|
|
|
Addr: f.host,
|
|
|
|
Token: f.token,
|
|
|
|
}, nil
|
2019-10-25 21:39:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newLabelService(f Flags) (influxdb.LabelService, error) {
|
|
|
|
if f.local {
|
|
|
|
return newLocalKVService()
|
|
|
|
}
|
|
|
|
return &http.LabelService{
|
|
|
|
Addr: f.host,
|
|
|
|
Token: f.token,
|
|
|
|
}, nil
|
2019-10-24 19:20:49 +00:00
|
|
|
}
|
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
func newVariableService(f Flags) (influxdb.VariableService, error) {
|
|
|
|
if f.local {
|
|
|
|
return newLocalKVService()
|
|
|
|
}
|
|
|
|
return &http.VariableService{
|
|
|
|
Addr: f.host,
|
|
|
|
Token: f.token,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-10-24 19:20:49 +00:00
|
|
|
func pkgFromFile(path string) (*pkger.Pkg, error) {
|
|
|
|
var enc pkger.Encoding
|
|
|
|
switch ext := filepath.Ext(path); ext {
|
|
|
|
case ".yaml", ".yml":
|
|
|
|
enc = pkger.EncodingYAML
|
|
|
|
case ".json":
|
|
|
|
enc = pkger.EncodingJSON
|
|
|
|
default:
|
|
|
|
return nil, errors.New("file provided must be one of yaml/yml/json extension but got: " + ext)
|
|
|
|
}
|
|
|
|
|
|
|
|
return pkger.Parse(enc, pkger.FromFile(path))
|
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
func printPkgDiff(hasColor, hasTableBorders bool, diff pkger.Diff) {
|
2019-10-28 22:23:40 +00:00
|
|
|
red := color.New(color.FgRed).SprintfFunc()
|
|
|
|
green := color.New(color.FgHiGreen, color.Bold).SprintfFunc()
|
|
|
|
|
|
|
|
strDiff := func(isNew bool, old, new string) string {
|
|
|
|
if isNew {
|
|
|
|
return green(new)
|
2019-10-26 02:11:47 +00:00
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
if old == new {
|
|
|
|
return new
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s\n%s", red("%q", old), green("%q", new))
|
|
|
|
}
|
|
|
|
|
|
|
|
boolDiff := func(b bool) string {
|
|
|
|
bb := strconv.FormatBool(b)
|
|
|
|
if b {
|
|
|
|
return green(bb)
|
|
|
|
}
|
|
|
|
return bb
|
|
|
|
}
|
|
|
|
|
|
|
|
durDiff := func(isNew bool, oldDur, newDur time.Duration) string {
|
|
|
|
o := oldDur.String()
|
|
|
|
if oldDur == 0 {
|
|
|
|
o = "inf"
|
|
|
|
}
|
|
|
|
n := newDur.String()
|
|
|
|
if newDur == 0 {
|
|
|
|
n = "inf"
|
|
|
|
}
|
|
|
|
if isNew {
|
|
|
|
return green(n)
|
|
|
|
}
|
|
|
|
if oldDur == newDur {
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s\n%s", red(o), green(n))
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn := tablePrinterGen(hasColor, hasTableBorders)
|
2019-10-30 21:13:42 +00:00
|
|
|
if labels := diff.Labels; len(labels) > 0 {
|
2019-10-28 22:23:40 +00:00
|
|
|
headers := []string{"New", "ID", "Name", "Color", "Description"}
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn("LABELS", headers, len(labels), func(w *tablewriter.Table) {
|
2019-10-30 21:13:42 +00:00
|
|
|
for _, l := range labels {
|
2019-10-28 22:23:40 +00:00
|
|
|
w.Append([]string{
|
|
|
|
boolDiff(l.IsNew()),
|
|
|
|
l.ID.String(),
|
|
|
|
l.Name,
|
|
|
|
strDiff(l.IsNew(), l.OldColor, l.NewColor),
|
|
|
|
strDiff(l.IsNew(), l.OldDesc, l.NewDesc),
|
|
|
|
})
|
2019-10-26 02:11:47 +00:00
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-10-30 21:13:42 +00:00
|
|
|
if bkts := diff.Buckets; len(bkts) > 0 {
|
2019-10-28 22:23:40 +00:00
|
|
|
headers := []string{"New", "ID", "Name", "Retention Period", "Description"}
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn("BUCKETS", headers, len(bkts), func(w *tablewriter.Table) {
|
2019-10-30 21:13:42 +00:00
|
|
|
for _, b := range bkts {
|
2019-10-28 22:23:40 +00:00
|
|
|
w.Append([]string{
|
|
|
|
boolDiff(b.IsNew()),
|
|
|
|
b.ID.String(),
|
|
|
|
b.Name,
|
|
|
|
durDiff(b.IsNew(), b.OldRetention, b.NewRetention),
|
|
|
|
strDiff(b.IsNew(), b.OldDesc, b.NewDesc),
|
|
|
|
})
|
2019-10-26 02:11:47 +00:00
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
})
|
2019-10-25 21:39:38 +00:00
|
|
|
}
|
|
|
|
|
2019-10-30 21:13:42 +00:00
|
|
|
if dashes := diff.Dashboards; len(dashes) > 0 {
|
2019-11-01 18:11:42 +00:00
|
|
|
headers := []string{"New", "Name", "Description", "Num Charts"}
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn("DASHBOARDS", headers, len(dashes), func(w *tablewriter.Table) {
|
2019-10-30 21:13:42 +00:00
|
|
|
for _, d := range dashes {
|
|
|
|
w.Append([]string{
|
2019-11-01 18:11:42 +00:00
|
|
|
boolDiff(true),
|
2019-10-30 21:13:42 +00:00
|
|
|
d.Name,
|
2019-11-01 18:11:42 +00:00
|
|
|
green(d.Desc),
|
|
|
|
green(strconv.Itoa(len(d.Charts))),
|
2019-10-30 21:13:42 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
if vars := diff.Variables; len(vars) > 0 {
|
|
|
|
headers := []string{"New", "ID", "Name", "Description", "Arg Type", "Arg Values"}
|
|
|
|
tablePrintFn("VARIABLES", headers, len(vars), func(w *tablewriter.Table) {
|
|
|
|
for _, v := range vars {
|
|
|
|
var oldArgType string
|
|
|
|
if v.OldArgs != nil {
|
|
|
|
oldArgType = v.OldArgs.Type
|
|
|
|
}
|
|
|
|
var newArgType string
|
|
|
|
if v.NewArgs != nil {
|
|
|
|
newArgType = v.NewArgs.Type
|
|
|
|
}
|
|
|
|
w.Append([]string{
|
|
|
|
boolDiff(v.IsNew()),
|
|
|
|
v.ID.String(),
|
|
|
|
v.Name,
|
|
|
|
strDiff(v.IsNew(), v.OldDesc, v.NewDesc),
|
|
|
|
strDiff(v.IsNew(), oldArgType, newArgType),
|
|
|
|
strDiff(v.IsNew(), printVarArgs(v.OldArgs), printVarArgs(v.NewArgs)),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-10-28 22:23:40 +00:00
|
|
|
if len(diff.LabelMappings) > 0 {
|
|
|
|
headers := []string{"New", "Resource Type", "Resource Name", "Resource ID", "Label Name", "Label ID"}
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn("LABEL MAPPINGS", headers, len(diff.LabelMappings), func(w *tablewriter.Table) {
|
2019-10-28 22:23:40 +00:00
|
|
|
for _, m := range diff.LabelMappings {
|
|
|
|
w.Append([]string{
|
|
|
|
boolDiff(m.IsNew),
|
|
|
|
string(m.ResType),
|
|
|
|
m.ResName,
|
|
|
|
m.ResID.String(),
|
|
|
|
m.LabelName,
|
|
|
|
m.LabelID.String(),
|
|
|
|
})
|
2019-10-26 02:11:47 +00:00
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-10-26 02:11:47 +00:00
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
func printVarArgs(a *influxdb.VariableArguments) string {
|
|
|
|
if a == nil {
|
|
|
|
return "<nil>"
|
|
|
|
}
|
|
|
|
if a.Type == "map" {
|
|
|
|
b, err := json.Marshal(a.Values)
|
|
|
|
if err != nil {
|
|
|
|
return "{}"
|
|
|
|
}
|
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
if a.Type == "constant" {
|
|
|
|
vals, ok := a.Values.(influxdb.VariableConstantValues)
|
|
|
|
if !ok {
|
|
|
|
return "[]"
|
|
|
|
}
|
|
|
|
var out []string
|
|
|
|
for _, s := range vals {
|
|
|
|
out = append(out, fmt.Sprintf("%q", s))
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("[%s]", strings.Join(out, " "))
|
|
|
|
}
|
|
|
|
if a.Type == "query" {
|
|
|
|
qVal, ok := a.Values.(influxdb.VariableQueryValues)
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("language=%q query=%q", qVal.Language, qVal.Query)
|
|
|
|
}
|
|
|
|
return "unknown variable argument"
|
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
func printPkgSummary(hasColor, hasTableBorders bool, sum pkger.Summary) {
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn := tablePrinterGen(hasColor, hasTableBorders)
|
2019-10-28 22:23:40 +00:00
|
|
|
if labels := sum.Labels; len(labels) > 0 {
|
|
|
|
headers := []string{"ID", "Name", "Description", "Color"}
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn("LABELS", headers, len(labels), func(w *tablewriter.Table) {
|
2019-10-28 22:23:40 +00:00
|
|
|
for _, l := range labels {
|
|
|
|
w.Append([]string{
|
|
|
|
l.ID.String(),
|
|
|
|
l.Name,
|
|
|
|
l.Properties["description"],
|
|
|
|
l.Properties["color"],
|
|
|
|
})
|
2019-10-26 02:11:47 +00:00
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if buckets := sum.Buckets; len(buckets) > 0 {
|
|
|
|
headers := []string{"ID", "Name", "Retention", "Description"}
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn("BUCKETS", headers, len(buckets), func(w *tablewriter.Table) {
|
2019-10-28 22:23:40 +00:00
|
|
|
for _, bucket := range buckets {
|
|
|
|
w.Append([]string{
|
|
|
|
bucket.ID.String(),
|
|
|
|
bucket.Name,
|
|
|
|
formatDuration(bucket.RetentionPeriod),
|
|
|
|
bucket.Description,
|
|
|
|
})
|
2019-10-26 02:11:47 +00:00
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-10-30 21:13:42 +00:00
|
|
|
if dashes := sum.Dashboards; len(dashes) > 0 {
|
|
|
|
headers := []string{"ID", "Name", "Description"}
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn("DASHBOARDS", headers, len(dashes), func(w *tablewriter.Table) {
|
2019-10-30 21:13:42 +00:00
|
|
|
for _, d := range dashes {
|
|
|
|
w.Append([]string{
|
|
|
|
d.ID.String(),
|
|
|
|
d.Name,
|
|
|
|
d.Description,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
if vars := sum.Variables; len(vars) > 0 {
|
|
|
|
headers := []string{"ID", "Name", "Description", "Arg Type", "Arg Values"}
|
|
|
|
tablePrintFn("VARIABLES", headers, len(vars), func(w *tablewriter.Table) {
|
|
|
|
for _, v := range vars {
|
|
|
|
args := v.Arguments
|
|
|
|
w.Append([]string{
|
|
|
|
v.ID.String(),
|
|
|
|
v.Name,
|
|
|
|
v.Description,
|
|
|
|
args.Type,
|
|
|
|
printVarArgs(args),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-10-28 22:23:40 +00:00
|
|
|
if mappings := sum.LabelMappings; len(mappings) > 0 {
|
|
|
|
headers := []string{"Resource Type", "Resource Name", "Resource ID", "Label Name", "Label ID"}
|
2019-11-07 00:45:00 +00:00
|
|
|
tablePrintFn("LABEL MAPPINGS", headers, len(mappings), func(w *tablewriter.Table) {
|
2019-10-28 22:23:40 +00:00
|
|
|
for _, m := range mappings {
|
|
|
|
w.Append([]string{
|
|
|
|
string(m.ResourceType),
|
|
|
|
m.ResourceName,
|
|
|
|
m.ResourceID.String(),
|
|
|
|
m.LabelName,
|
|
|
|
m.LabelID.String(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
func tablePrinterGen(hasColor, hasTableBorder bool) func(table string, headers []string, count int, appendFn func(w *tablewriter.Table)) {
|
|
|
|
return func(table string, headers []string, count int, appendFn func(w *tablewriter.Table)) {
|
|
|
|
tablePrinter(table, headers, count, hasColor, hasTableBorder, appendFn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
func tablePrinter(table string, headers []string, count int, hasColor, hasTableBorders bool, appendFn func(w *tablewriter.Table)) {
|
2019-10-28 22:23:40 +00:00
|
|
|
descrCol := -1
|
|
|
|
for i, h := range headers {
|
|
|
|
if strings.ToLower(h) == "description" {
|
|
|
|
descrCol = i
|
|
|
|
break
|
2019-10-24 19:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
|
|
|
|
w := tablewriter.NewWriter(os.Stdout)
|
2019-10-30 17:55:13 +00:00
|
|
|
w.SetBorder(hasTableBorders)
|
|
|
|
w.SetRowLine(hasTableBorders)
|
2019-11-01 18:11:42 +00:00
|
|
|
|
|
|
|
var alignments []int
|
|
|
|
for range headers {
|
|
|
|
alignments = append(alignments, tablewriter.ALIGN_CENTER)
|
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
if descrCol != -1 {
|
|
|
|
w.SetColMinWidth(descrCol, 30)
|
2019-11-01 18:11:42 +00:00
|
|
|
alignments[descrCol] = tablewriter.ALIGN_LEFT
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
color.New(color.FgYellow, color.Bold).Fprintln(os.Stdout, strings.ToUpper(table))
|
|
|
|
w.SetHeader(headers)
|
2019-11-01 18:11:42 +00:00
|
|
|
w.SetColumnAlignment(alignments)
|
2019-10-28 22:23:40 +00:00
|
|
|
|
|
|
|
appendFn(w)
|
|
|
|
|
|
|
|
footers := make([]string, len(headers))
|
|
|
|
footers[len(footers)-2] = "TOTAL"
|
|
|
|
footers[len(footers)-1] = strconv.Itoa(count)
|
|
|
|
w.SetFooter(footers)
|
2019-10-30 17:55:13 +00:00
|
|
|
if hasColor {
|
|
|
|
var colors []tablewriter.Colors
|
|
|
|
for i := 0; i < len(headers); i++ {
|
|
|
|
colors = append(colors, tablewriter.Color(tablewriter.FgHiCyanColor))
|
|
|
|
}
|
|
|
|
w.SetHeaderColor(colors...)
|
2019-11-01 18:11:42 +00:00
|
|
|
colors[len(colors)-2] = tablewriter.Color(tablewriter.FgHiBlueColor)
|
|
|
|
colors[len(colors)-1] = tablewriter.Color(tablewriter.FgHiBlueColor)
|
2019-10-30 17:55:13 +00:00
|
|
|
w.SetFooterColor(colors...)
|
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
|
|
|
|
w.Render()
|
|
|
|
fmt.Fprintln(os.Stdout)
|
2019-10-24 19:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func formatDuration(d time.Duration) string {
|
|
|
|
if d == 0 {
|
|
|
|
return "inf"
|
|
|
|
}
|
|
|
|
return d.String()
|
|
|
|
}
|