mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-07 07:07:05 +02:00
Move more formatting into base_helpers
This commit is contained in:
parent
30cd478c01
commit
b4d9d1517b
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user