mirror of https://github.com/status-im/consul.git
troubleshoot: make output have tables and colors (#16235)
Adds tables and colors using libraries used in consul-k8s. It doesn't add the full `terminal` UI package that consul-k8s uses since there is an existing UI in Consul that I didn't want to affect too much. So instead this adds to the existing UI.
This commit is contained in:
parent
318ba215ab
commit
80fb18aa35
|
@ -9,7 +9,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/hashicorp/consul/command/cli"
|
||||
mcli "github.com/mitchellh/cli"
|
||||
|
||||
"github.com/hashicorp/consul/agent"
|
||||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
|
@ -215,7 +216,7 @@ func TestBadDataDirPermissions(t *testing.T) {
|
|||
}
|
||||
|
||||
type captureUI struct {
|
||||
*cli.MockUi
|
||||
*mcli.MockUi
|
||||
}
|
||||
|
||||
func (c *captureUI) Stdout() io.Writer {
|
||||
|
@ -226,6 +227,24 @@ func (c *captureUI) Stderr() io.Writer {
|
|||
return c.MockUi.ErrorWriter
|
||||
}
|
||||
|
||||
func newCaptureUI() *captureUI {
|
||||
return &captureUI{MockUi: cli.NewMockUi()}
|
||||
func (c *captureUI) HeaderOutput(s string) {
|
||||
}
|
||||
|
||||
func (c *captureUI) ErrorOutput(s string) {
|
||||
}
|
||||
|
||||
func (c *captureUI) WarnOutput(s string) {
|
||||
}
|
||||
|
||||
func (c *captureUI) SuccessOutput(s string) {
|
||||
}
|
||||
|
||||
func (c *captureUI) UnchangedOutput(s string) {
|
||||
}
|
||||
|
||||
func (c *captureUI) Table(tbl *cli.Table) {
|
||||
}
|
||||
|
||||
func newCaptureUI() *captureUI {
|
||||
return &captureUI{MockUi: mcli.NewMockUi()}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
|
||||
mcli "github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
|
@ -13,6 +16,12 @@ type Ui interface {
|
|||
mcli.Ui
|
||||
Stdout() io.Writer
|
||||
Stderr() io.Writer
|
||||
ErrorOutput(string)
|
||||
HeaderOutput(string)
|
||||
WarnOutput(string)
|
||||
SuccessOutput(string)
|
||||
UnchangedOutput(string)
|
||||
Table(tbl *Table)
|
||||
}
|
||||
|
||||
// BasicUI augments mitchellh/cli.BasicUi by exposing the underlying io.Writer.
|
||||
|
@ -28,5 +37,63 @@ func (b *BasicUI) Stderr() io.Writer {
|
|||
return b.BasicUi.ErrorWriter
|
||||
}
|
||||
|
||||
func (b *BasicUI) HeaderOutput(s string) {
|
||||
b.Output(colorize(fmt.Sprintf("==> %s", s), UiColorNone))
|
||||
}
|
||||
|
||||
func (b *BasicUI) ErrorOutput(s string) {
|
||||
b.Output(colorize(fmt.Sprintf(" ! %s", s), UiColorRed))
|
||||
}
|
||||
|
||||
func (b *BasicUI) WarnOutput(s string) {
|
||||
b.Output(colorize(fmt.Sprintf(" * %s", s), UiColorYellow))
|
||||
}
|
||||
|
||||
func (b *BasicUI) SuccessOutput(s string) {
|
||||
b.Output(colorize(fmt.Sprintf(" ✓ %s", s), UiColorGreen))
|
||||
}
|
||||
|
||||
func (b *BasicUI) UnchangedOutput(s string) {
|
||||
b.Output(colorize(fmt.Sprintf(" %s", s), UiColorNone))
|
||||
}
|
||||
|
||||
// Command is an alias to reduce the diff. It can be removed at any time.
|
||||
type Command mcli.Command
|
||||
|
||||
// Table implements UI.
|
||||
func (u *BasicUI) Table(tbl *Table) {
|
||||
// Build our config and set our options
|
||||
|
||||
table := tablewriter.NewWriter(u.Stdout())
|
||||
|
||||
table.SetHeader(tbl.Headers)
|
||||
table.SetBorder(false)
|
||||
table.SetAutoWrapText(false)
|
||||
table.SetAutoFormatHeaders(false)
|
||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetCenterSeparator("")
|
||||
table.SetColumnSeparator("")
|
||||
table.SetRowSeparator("")
|
||||
table.SetHeaderLine(false)
|
||||
table.SetTablePadding("\t") // pad with tabs
|
||||
table.SetNoWhiteSpace(true)
|
||||
|
||||
for _, row := range tbl.Rows {
|
||||
colors := make([]tablewriter.Colors, len(row))
|
||||
entries := make([]string, len(row))
|
||||
|
||||
for i, ent := range row {
|
||||
entries[i] = ent.Value
|
||||
|
||||
color, ok := colorMapping[ent.Color]
|
||||
if ok {
|
||||
colors[i] = tablewriter.Colors{color}
|
||||
}
|
||||
}
|
||||
|
||||
table.Rich(entries, colors)
|
||||
}
|
||||
|
||||
table.Render()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
// Functions to set up colorized output.
|
||||
const (
|
||||
noColor = -1
|
||||
)
|
||||
|
||||
func colorize(message string, uc UiColor) string {
|
||||
if uc.Code == noColor {
|
||||
return message
|
||||
}
|
||||
|
||||
attr := []color.Attribute{color.Attribute(uc.Code)}
|
||||
if uc.Bold {
|
||||
attr = append(attr, color.Bold)
|
||||
}
|
||||
|
||||
return color.New(attr...).SprintFunc()(message)
|
||||
}
|
||||
|
||||
// UiColor is a posix shell color code to use.
|
||||
type UiColor struct {
|
||||
Code int
|
||||
Bold bool
|
||||
}
|
||||
|
||||
// A list of colors that are useful. These are all non-bolded by default.
|
||||
var (
|
||||
UiColorNone UiColor = UiColor{noColor, false}
|
||||
UiColorRed = UiColor{int(color.FgHiRed), false}
|
||||
UiColorGreen = UiColor{int(color.FgHiGreen), false}
|
||||
UiColorYellow = UiColor{int(color.FgHiYellow), false}
|
||||
UiColorBlue = UiColor{int(color.FgHiBlue), false}
|
||||
UiColorMagenta = UiColor{int(color.FgHiMagenta), false}
|
||||
UiColorCyan = UiColor{int(color.FgHiCyan), false}
|
||||
)
|
||||
|
||||
// Functions that are useful for table output.
|
||||
const (
|
||||
Yellow = "yellow"
|
||||
Green = "green"
|
||||
Red = "red"
|
||||
)
|
||||
|
||||
var colorMapping = map[string]int{
|
||||
Green: tablewriter.FgGreenColor,
|
||||
Yellow: tablewriter.FgYellowColor,
|
||||
Red: tablewriter.FgRedColor,
|
||||
}
|
||||
|
||||
// Table is passed to UI.Table to provide a nicely formatted table.
|
||||
type Table struct {
|
||||
Headers []string
|
||||
Rows [][]Cell
|
||||
}
|
||||
|
||||
// Cell is a single entry for a table.
|
||||
type Cell struct {
|
||||
Value string
|
||||
Color string
|
||||
}
|
||||
|
||||
// NewTable creates a new Table structure that can be used with UI.Table.
|
||||
func NewTable(headers ...string) *Table {
|
||||
return &Table{
|
||||
Headers: headers,
|
||||
}
|
||||
}
|
||||
|
||||
// AddRow adds a row to the table.
|
||||
func (t *Table) AddRow(cols []string, colors []string) {
|
||||
var row []Cell
|
||||
|
||||
for i, col := range cols {
|
||||
if i < len(colors) {
|
||||
row = append(row, Cell{Value: col, Color: colors[i]})
|
||||
} else {
|
||||
row = append(row, Cell{Value: col})
|
||||
}
|
||||
}
|
||||
|
||||
t.Rows = append(t.Rows, row)
|
||||
}
|
|
@ -6,9 +6,9 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/consul/command/cli"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
troubleshoot "github.com/hashicorp/consul/troubleshoot/proxy"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func New(ui cli.Ui) *cmd {
|
||||
|
@ -86,13 +86,14 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
c.UI.HeaderOutput("Validation")
|
||||
for _, o := range messages {
|
||||
if o.Success {
|
||||
c.UI.Output(o.Message)
|
||||
c.UI.SuccessOutput(o.Message)
|
||||
} else {
|
||||
c.UI.Error(o.Message)
|
||||
c.UI.ErrorOutput(o.Message)
|
||||
if o.PossibleActions != "" {
|
||||
c.UI.Output(o.PossibleActions)
|
||||
c.UI.UnchangedOutput(o.PossibleActions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestTroubleshootProxyCommand_noTabs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') {
|
||||
t.Fatal("help has tabs")
|
||||
}
|
||||
}
|
|
@ -5,10 +5,12 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/cli"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
troubleshoot "github.com/hashicorp/consul/troubleshoot/proxy"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func New(ui cli.Ui) *cmd {
|
||||
|
@ -81,9 +83,11 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
|
||||
c.UI.Output(fmt.Sprintf("\n==> Upstream IPs (transparent proxy only) (%v)", len(upstreamIPs)))
|
||||
tbl := cli.NewTable("IPs ", "Virtual ", "Cluster Names")
|
||||
for _, u := range upstreamIPs {
|
||||
c.UI.Output(fmt.Sprintf("%+v %v %+v", u.IPs, u.IsVirtual, u.ClusterNames))
|
||||
tbl.AddRow([]string{formatIPs(u.IPs), strconv.FormatBool(u.IsVirtual), formatClusterNames(u.ClusterNames)}, []string{})
|
||||
}
|
||||
c.UI.Table(tbl)
|
||||
|
||||
c.UI.Output("\nIf you don't see your upstream address or cluster for a transparent proxy upstream:")
|
||||
c.UI.Output("- Check intentions: Tproxy upstreams are configured based on intentions, make sure you " +
|
||||
|
@ -114,3 +118,15 @@ Usage: consul troubleshoot upstreams [options]
|
|||
$ consul troubleshoot upstreams
|
||||
`
|
||||
)
|
||||
|
||||
func formatIPs(ips []string) string {
|
||||
return strings.Join(ips, ", ")
|
||||
}
|
||||
|
||||
func formatClusterNames(names map[string]struct{}) string {
|
||||
var out []string
|
||||
for k := range names {
|
||||
out = append(out, k)
|
||||
}
|
||||
return strings.Join(out, ", ")
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package upstreams
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestTroubleshootUpstreamsCommand_noTabs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') {
|
||||
t.Fatal("help has tabs")
|
||||
}
|
||||
}
|
4
go.mod
4
go.mod
|
@ -29,6 +29,7 @@ require (
|
|||
github.com/coreos/go-oidc v2.1.0+incompatible
|
||||
github.com/docker/go-connections v0.3.0
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/fsnotify/fsnotify v1.5.1
|
||||
github.com/go-openapi/runtime v0.24.1
|
||||
github.com/go-openapi/strfmt v0.21.3
|
||||
|
@ -86,6 +87,7 @@ require (
|
|||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/mitchellh/pointerstructure v1.2.1
|
||||
github.com/mitchellh/reflectwalk v1.0.2
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
|
@ -147,7 +149,6 @@ require (
|
|||
github.com/digitalocean/godo v1.10.0 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/analysis v0.21.2 // indirect
|
||||
|
@ -189,6 +190,7 @@ require (
|
|||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -743,6 +743,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
|||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
|
@ -815,6 +817,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
|
|||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
|
Loading…
Reference in New Issue