Move more formatting into base_helpers

This commit is contained in:
Seth Vargo 2017-09-07 22:04:48 -04:00
parent 30cd478c01
commit b4d9d1517b
No known key found for this signature in database
GPG Key ID: C921994F9C27E0FF
3 changed files with 156 additions and 176 deletions

View File

@ -13,7 +13,6 @@ import (
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/command/token"
"github.com/kr/text"
"github.com/mitchellh/cli"
"github.com/pkg/errors"
"github.com/posener/complete"
@ -274,22 +273,6 @@ func (c *BaseCommand) flagSet(bit FlagSetBit) *FlagSets {
return c.flags
}
// wrapAtLengthWithPadding wraps the given text at the maxLineLength, taking
// into account any provided left padding.
func wrapAtLengthWithPadding(s string, pad int) string {
wrapped := text.Wrap(s, maxLineLength-pad)
lines := strings.Split(wrapped, "\n")
for i, line := range lines {
lines[i] = strings.Repeat(" ", pad) + line
}
return strings.Join(lines, "\n")
}
// wrapAtLength wraps the given text to maxLineLength.
func wrapAtLength(s string) string {
return wrapAtLengthWithPadding(s, 0)
}
// FlagSets is a group of flag sets.
type FlagSets struct {
flagSets []*FlagSet

View File

@ -8,16 +8,13 @@ import (
"github.com/hashicorp/vault/api"
kvbuilder "github.com/hashicorp/vault/helper/kv-builder"
"github.com/kr/text"
homedir "github.com/mitchellh/go-homedir"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/ryanuber/columnize"
)
var ErrMissingID = fmt.Errorf("Missing ID!")
var ErrMissingPath = fmt.Errorf("Missing PATH!")
var ErrMissingThing = fmt.Errorf("Missing THING!")
// extractListData reads the secret and returns a typed list of data and a
// boolean indicating whether the extraction was successful.
func extractListData(secret *api.Secret) ([]interface{}, bool) {
@ -34,54 +31,9 @@ func extractListData(secret *api.Secret) ([]interface{}, bool) {
return i, ok
}
// extractPath extracts the path and list of arguments from the args. If there
// are no extra arguments, the remaining args will be nil.
func extractPath(args []string) (string, []string, error) {
str, remaining, err := extractThings(args)
if err == ErrMissingThing {
err = ErrMissingPath
}
return str, remaining, err
}
// extractID extracts the path and list of arguments from the args. If there
// are no extra arguments, the remaining args will be nil.
func extractID(args []string) (string, []string, error) {
str, remaining, err := extractThings(args)
if err == ErrMissingThing {
err = ErrMissingID
}
return str, remaining, err
}
func extractThings(args []string) (string, []string, error) {
if len(args) < 1 {
return "", nil, ErrMissingThing
}
// Path is always the first argument after all flags
thing := args[0]
// Strip leading and trailing slashes
thing = sanitizePath(thing)
// Verify we have a thing
if thing == "" {
return "", nil, ErrMissingThing
}
// Splice remaining args
var remaining []string
if len(args) > 1 {
remaining = args[1:]
}
return thing, remaining, nil
}
// sanitizePath removes any leading or trailing things from a "path".
func sanitizePath(s string) string {
return ensureNoTrailingSlash(ensureNoLeadingSlash(s))
return ensureNoTrailingSlash(ensureNoLeadingSlash(strings.TrimSpace(s)))
}
// ensureTrailingSlash ensures the given string has a trailing slash.
@ -124,33 +76,45 @@ func ensureNoLeadingSlash(s string) string {
}
// columnOuput prints the list of items as a table with no headers.
func columnOutput(list []string) string {
func columnOutput(list []string, c *columnize.Config) string {
if len(list) == 0 {
return ""
}
return columnize.Format(list, &columnize.Config{
Glue: " ",
Empty: "n/a",
})
if c == nil {
c = &columnize.Config{}
}
if c.Glue == "" {
c.Glue = " "
}
if c.Empty == "" {
c.Empty = "n/a"
}
return columnize.Format(list, c)
}
// tableOutput prints the list of items as columns, where the first row is
// the list of headers.
func tableOutput(list []string) string {
func tableOutput(list []string, c *columnize.Config) string {
if len(list) == 0 {
return ""
}
delim := "|"
if c != nil && c.Delim != "" {
delim = c.Delim
}
underline := ""
headers := strings.Split(list[0], "|")
headers := strings.Split(list[0], delim)
for i, h := range headers {
h = strings.TrimSpace(h)
u := strings.Repeat("-", len(h))
underline = underline + u
if i != len(headers)-1 {
underline = underline + " | "
underline = underline + delim
}
}
@ -158,7 +122,7 @@ func tableOutput(list []string) string {
copy(list[2:], list[1:])
list[1] = underline
return columnOutput(list)
return columnOutput(list, c)
}
// parseArgsData parses the given args in the format key=value into a map of
@ -207,7 +171,7 @@ func printKeyStatus(ks *api.KeyStatus) string {
return columnOutput([]string{
fmt.Sprintf("Key Term | %d", ks.Term),
fmt.Sprintf("Install Time | %s", ks.InstallTime.UTC().Format(time.RFC822)),
})
}, nil)
}
// expandPath takes a filepath and returns the full expanded path, accounting
@ -223,3 +187,57 @@ func expandPath(s string) string {
}
return e
}
// wrapAtLengthWithPadding wraps the given text at the maxLineLength, taking
// into account any provided left padding.
func wrapAtLengthWithPadding(s string, pad int) string {
wrapped := text.Wrap(s, maxLineLength-pad)
lines := strings.Split(wrapped, "\n")
for i, line := range lines {
lines[i] = strings.Repeat(" ", pad) + line
}
return strings.Join(lines, "\n")
}
// wrapAtLength wraps the given text to maxLineLength.
func wrapAtLength(s string) string {
return wrapAtLengthWithPadding(s, 0)
}
// ttlToAPI converts a user-supplied ttl into an API-compatible string. If
// the TTL is 0, this returns the empty string. If the TTL is negative, this
// returns "system" to indicate to use the system values. Otherwise, the
// time.Duration ttl is used.
func ttlToAPI(d time.Duration) string {
if d == 0 {
return ""
}
if d < 0 {
return "system"
}
return d.String()
}
// humanDuration prints the time duration without those pesky zeros.
func humanDuration(d time.Duration) string {
if d == 0 {
return "0s"
}
s := d.String()
if strings.HasSuffix(s, "m0s") {
s = s[:len(s)-2]
}
if idx := strings.Index(s, "h0m"); idx > 0 {
s = s[:idx+1] + s[idx+3:]
}
return s
}
// humanDurationInt prints the given int as if it were a time.Duration number
// of seconds.
func humanDurationInt(i int) string {
return humanDuration(time.Duration(i) * time.Second)
}

View File

@ -5,19 +5,19 @@ import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/ghodss/yaml"
"github.com/hashicorp/vault/api"
"github.com/mitchellh/cli"
"github.com/posener/complete"
"github.com/ryanuber/columnize"
)
var predictFormat complete.Predictor = complete.PredictSet("json", "yaml")
const (
// hopeDelim is the delimiter to use when splitting columns. We call it a
// hopeDelim because we hope that it's never contained in a secret.
hopeDelim = "♨"
)
func OutputSecret(ui cli.Ui, format string, secret *api.Secret) int {
return outputWithFormat(ui, format, secret, secret)
@ -89,7 +89,7 @@ type TableFormatter struct {
func (t TableFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{}) error {
// TODO: this should really use reflection like the other formatters do
if s, ok := data.(*api.Secret); ok {
return t.OutputSecret(ui, secret, s)
return t.OutputSecret(ui, s)
}
if s, ok := data.([]interface{}); ok {
return t.OutputList(ui, secret, s)
@ -98,132 +98,111 @@ func (t TableFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{})
}
func (t TableFormatter) OutputList(ui cli.Ui, secret *api.Secret, list []interface{}) error {
config := columnize.DefaultConfig()
config.Delim = "♨"
config.Glue = "\t"
config.Prefix = ""
input := make([]string, 0, 5)
t.printWarnings(ui, secret)
if len(list) > 0 {
input = append(input, "Keys")
input = append(input, "----")
keys := make([]string, 0, len(list))
for _, k := range list {
keys = append(keys, k.(string))
keys := make([]string, len(list))
for i, v := range list {
typed, ok := v.(string)
if !ok {
return fmt.Errorf("Error: %v is not a string", v)
}
keys[i] = typed
}
sort.Strings(keys)
for _, k := range keys {
input = append(input, fmt.Sprintf("%s", k))
}
// Prepend the header
keys = append([]string{"Keys"}, keys...)
ui.Output(tableOutput(keys, &columnize.Config{
Delim: hopeDelim,
}))
}
tableOutputStr := columnize.Format(input, config)
// Print the warning separately because the length of first
// column in the output will be increased by the length of
// the longest warning string making the output look bad.
warningsInput := make([]string, 0, 5)
if len(secret.Warnings) != 0 {
warningsInput = append(warningsInput, "")
warningsInput = append(warningsInput, "The following warnings were returned from the Vault server:")
for _, warning := range secret.Warnings {
warningsInput = append(warningsInput, fmt.Sprintf("* %s", warning))
}
}
warningsOutputStr := columnize.Format(warningsInput, config)
ui.Output(fmt.Sprintf("%s\n%s", tableOutputStr, warningsOutputStr))
return nil
}
func (t TableFormatter) OutputSecret(ui cli.Ui, secret, s *api.Secret) error {
config := columnize.DefaultConfig()
config.Delim = "♨"
config.Glue = "\t"
config.Prefix = ""
// printWarnings prints any warnings in the secret.
func (t TableFormatter) printWarnings(ui cli.Ui, secret *api.Secret) {
if secret != nil && len(secret.Warnings) > 0 {
ui.Warn("WARNING! The following warnings were returned from Vault:\n")
for _, warning := range secret.Warnings {
ui.Warn(wrapAtLengthWithPadding(fmt.Sprintf("* %s", warning), 2))
}
ui.Warn("")
}
}
input := make([]string, 0, 5)
onceHeader := &sync.Once{}
headerFunc := func() {
input = append(input, fmt.Sprintf("Key %s Value", config.Delim))
input = append(input, fmt.Sprintf("--- %s -----", config.Delim))
func (t TableFormatter) OutputSecret(ui cli.Ui, secret *api.Secret) error {
if secret == nil {
return nil
}
if s.LeaseDuration > 0 {
onceHeader.Do(headerFunc)
if s.LeaseID != "" {
input = append(input, fmt.Sprintf("lease_id %s %s", config.Delim, s.LeaseID))
input = append(input, fmt.Sprintf(
"lease_duration %s %s", config.Delim, (time.Second*time.Duration(s.LeaseDuration)).String()))
t.printWarnings(ui, secret)
out := make([]string, 0, 8)
if secret.LeaseDuration > 0 {
if secret.LeaseID != "" {
out = append(out, fmt.Sprintf("lease_id %s %s", hopeDelim, secret.LeaseID))
out = append(out, fmt.Sprintf("lease_duration %s %s", hopeDelim, humanDurationInt(secret.LeaseDuration)))
out = append(out, fmt.Sprintf("lease_renewable %s %t", hopeDelim, secret.Renewable))
} else {
input = append(input, fmt.Sprintf(
"refresh_interval %s %s", config.Delim, (time.Second*time.Duration(s.LeaseDuration)).String()))
}
if s.LeaseID != "" {
input = append(input, fmt.Sprintf(
"lease_renewable %s %s", config.Delim, strconv.FormatBool(s.Renewable)))
// This is probably the generic secret backend which has leases, but we
// print them as refresh_interval to reduce confusion.
out = append(out, fmt.Sprintf("refresh_interval %s %s", hopeDelim, humanDurationInt(secret.LeaseDuration)))
}
}
if s.Auth != nil {
onceHeader.Do(headerFunc)
input = append(input, fmt.Sprintf("token %s %s", config.Delim, s.Auth.ClientToken))
input = append(input, fmt.Sprintf("token_accessor %s %s", config.Delim, s.Auth.Accessor))
input = append(input, fmt.Sprintf("token_duration %s %s", config.Delim, (time.Second*time.Duration(s.Auth.LeaseDuration)).String()))
input = append(input, fmt.Sprintf("token_renewable %s %v", config.Delim, s.Auth.Renewable))
input = append(input, fmt.Sprintf("token_policies %s %v", config.Delim, s.Auth.Policies))
for k, v := range s.Auth.Metadata {
input = append(input, fmt.Sprintf("token_meta_%s %s %#v", k, config.Delim, v))
if secret.Auth != nil {
out = append(out, fmt.Sprintf("token %s %s", hopeDelim, secret.Auth.ClientToken))
out = append(out, fmt.Sprintf("token_accessor %s %s", hopeDelim, secret.Auth.Accessor))
// If the lease duration is 0, it's likely a root token, so output the
// duration as "infinity" to clear things up.
if secret.Auth.LeaseDuration == 0 {
out = append(out, fmt.Sprintf("token_duration %s %s", hopeDelim, "∞"))
} else {
out = append(out, fmt.Sprintf("token_duration %s %s", hopeDelim, humanDurationInt(secret.Auth.LeaseDuration)))
}
out = append(out, fmt.Sprintf("token_renewable %s %t", hopeDelim, secret.Auth.Renewable))
out = append(out, fmt.Sprintf("token_policies %s %v", hopeDelim, secret.Auth.Policies))
for k, v := range secret.Auth.Metadata {
out = append(out, fmt.Sprintf("token_meta_%s %s %v", k, hopeDelim, v))
}
}
if s.WrapInfo != nil {
onceHeader.Do(headerFunc)
input = append(input, fmt.Sprintf("wrapping_token: %s %s", config.Delim, s.WrapInfo.Token))
input = append(input, fmt.Sprintf("wrapping_token_ttl: %s %s", config.Delim, (time.Second*time.Duration(s.WrapInfo.TTL)).String()))
input = append(input, fmt.Sprintf("wrapping_token_creation_time: %s %s", config.Delim, s.WrapInfo.CreationTime.String()))
input = append(input, fmt.Sprintf("wrapping_token_creation_path: %s %s", config.Delim, s.WrapInfo.CreationPath))
if s.WrapInfo.WrappedAccessor != "" {
input = append(input, fmt.Sprintf("wrapped_accessor: %s %s", config.Delim, s.WrapInfo.WrappedAccessor))
if secret.WrapInfo != nil {
out = append(out, fmt.Sprintf("wrapping_token: %s %s", hopeDelim, secret.WrapInfo.Token))
out = append(out, fmt.Sprintf("wrapping_token_ttl: %s %s", hopeDelim, humanDurationInt(secret.WrapInfo.TTL)))
out = append(out, fmt.Sprintf("wrapping_token_creation_time: %s %s", hopeDelim, secret.WrapInfo.CreationTime.String()))
out = append(out, fmt.Sprintf("wrapping_token_creation_path: %s %s", hopeDelim, secret.WrapInfo.CreationPath))
if secret.WrapInfo.WrappedAccessor != "" {
out = append(out, fmt.Sprintf("wrapped_accessor: %s %s", hopeDelim, secret.WrapInfo.WrappedAccessor))
}
}
if s.Data != nil && len(s.Data) > 0 {
onceHeader.Do(headerFunc)
keys := make([]string, 0, len(s.Data))
for k := range s.Data {
if len(secret.Data) > 0 {
keys := make([]string, 0, len(secret.Data))
for k := range secret.Data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
input = append(input, fmt.Sprintf("%s %s %v", k, config.Delim, s.Data[k]))
out = append(out, fmt.Sprintf("%s %s %v", k, hopeDelim, secret.Data[k]))
}
}
tableOutputStr := columnize.Format(input, config)
// Print the warning separately because the length of first
// column in the output will be increased by the length of
// the longest warning string making the output look bad.
warningsInput := make([]string, 0, 5)
if len(s.Warnings) != 0 {
warningsInput = append(warningsInput, "")
warningsInput = append(warningsInput, "The following warnings were returned from the Vault server:")
for _, warning := range s.Warnings {
warningsInput = append(warningsInput, fmt.Sprintf("* %s", warning))
}
// If we got this far and still don't have any data, there's nothing to print,
// sorry.
if len(out) == 0 {
return nil
}
warningsOutputStr := columnize.Format(warningsInput, config)
ui.Output(fmt.Sprintf("%s\n%s", tableOutputStr, warningsOutputStr))
// Prepend the header
out = append([]string{"Key" + hopeDelim + "Value"}, out...)
ui.Output(tableOutput(out, &columnize.Config{
Delim: hopeDelim,
}))
return nil
}