CLI Enhancements (#3897)

* Use Colored UI if stdout is a tty

* Add format options to operator unseal

* Add format test on operator unseal

* Add -no-color output flag, and use BasicUi if no-color flag is provided

* Move seal status formatting logic to OutputSealStatus

* Apply no-color to warnings from DeprecatedCommands as well

* Add OutputWithFormat to support arbitrary data, add format option to auth list

* Add ability to output arbitrary list data on TableFormatter

* Clear up switch logic on format

* Add format option for list-related commands

* Add format option to rest of commands that returns a client API response

* Remove initOutputYAML and initOutputJSON, and use OutputWithFormat instead

* Remove outputAsYAML and outputAsJSON, and use OutputWithFormat instead

* Remove -no-color flag, use env var exclusively to toggle colored output

* Fix compile

* Remove -no-color flag in main.go

* Add missing FlagSetOutputFormat

* Fix generate-root/decode test

* Migrate init functions to main.go

* Add no-color flag back as hidden

* Handle non-supported data types for TableFormatter.OutputList

* Pull formatting much further up to remove the need to use c.flagFormat (#3950)

* Pull formatting much further up to remove the need to use c.flagFormat

Also remove OutputWithFormat as the logic can cause issues.

* Use const for env var

* Minor updates

* Remove unnecessary check

* Fix SSH output and some tests

* Fix tests

* Make race detector not run on generate root since it kills Travis these days

* Update docs

* Update docs

* Address review feedback

* Handle --format as well as -format
This commit is contained in:
Calvin Leung Huang 2018-02-12 18:12:16 -05:00 committed by GitHub
parent e708fd5da9
commit 60732577f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 578 additions and 287 deletions

View File

@ -99,11 +99,11 @@ func (c *Sys) generateRootUpdateCommon(path, shard, nonce string) (*GenerateRoot
}
type GenerateRootStatusResponse struct {
Nonce string
Started bool
Progress int
Required int
Complete bool
Nonce string `json:"nonce"`
Started bool `json:"started"`
Progress int `json:"progress"`
Required int `json:"required"`
Complete bool `json:"complete"`
EncodedToken string `json:"encoded_token"`
EncodedRootToken string `json:"encoded_root_token"`
PGPFingerprint string `json:"pgp_fingerprint"`

View File

@ -177,27 +177,27 @@ type RekeyInitRequest struct {
}
type RekeyStatusResponse struct {
Nonce string
Started bool
T int
N int
Progress int
Required int
Nonce string `json:"nonce"`
Started bool `json:"started"`
T int `json:"t"`
N int `json:"n"`
Progress int `json:"progress"`
Required int `json:"required"`
PGPFingerprints []string `json:"pgp_fingerprints"`
Backup bool
Backup bool `json:"backup"`
}
type RekeyUpdateResponse struct {
Nonce string
Complete bool
Keys []string
Nonce string `json:"nonce"`
Complete bool `json:"complete"`
Keys []string `json:"keys"`
KeysB64 []string `json:"keys_base64"`
PGPFingerprints []string `json:"pgp_fingerprints"`
Backup bool
Backup bool `json:"backup"`
}
type RekeyRetrieveResponse struct {
Nonce string
Keys map[string][]string
Nonce string `json:"nonce"`
Keys map[string][]string `json:"keys"`
KeysB64 map[string][]string `json:"keys_base64"`
}

View File

@ -44,7 +44,7 @@ Usage: vault audit list [options]
}
func (c *AuditListCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
@ -99,13 +99,17 @@ func (c *AuditListCommand) Run(args []string) int {
return 0
}
if c.flagDetailed {
c.UI.Output(tableOutput(c.detailedAudits(audits), nil))
switch Format(c.UI) {
case "table":
if c.flagDetailed {
c.UI.Output(tableOutput(c.detailedAudits(audits), nil))
return 0
}
c.UI.Output(tableOutput(c.simpleAudits(audits), nil))
return 0
default:
return OutputData(c.UI, audits)
}
c.UI.Output(tableOutput(c.simpleAudits(audits), nil))
return 0
}
func (c *AuditListCommand) simpleAudits(audits map[string]*api.Audit) []string {

View File

@ -46,7 +46,7 @@ Usage: vault auth list [options]
}
func (c *AuthListCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
@ -55,7 +55,8 @@ func (c *AuthListCommand) Flags() *FlagSets {
Target: &c.flagDetailed,
Default: false,
Usage: "Print detailed information such as configuration and replication " +
"status about each auth method.",
"status about each auth method. This option is only applicable to " +
"table-formatted output.",
})
return set
@ -95,13 +96,17 @@ func (c *AuthListCommand) Run(args []string) int {
return 2
}
if c.flagDetailed {
c.UI.Output(tableOutput(c.detailedMounts(auths), nil))
switch Format(c.UI) {
case "table":
if c.flagDetailed {
c.UI.Output(tableOutput(c.detailedMounts(auths), nil))
return 0
}
c.UI.Output(tableOutput(c.simpleMounts(auths), nil))
return 0
default:
return OutputData(c.UI, auths)
}
c.UI.Output(tableOutput(c.simpleMounts(auths), nil))
return 0
}
func (c *AuthListCommand) simpleMounts(auths map[string]*api.AuthMount) []string {

View File

@ -152,6 +152,11 @@ func (c *BaseCommand) flagSet(bit FlagSetBit) *FlagSets {
c.flagsOnce.Do(func() {
set := NewFlagSets(c.UI)
// These flag sets will apply to all leaf subcommands.
// TODO: Optional, but FlagSetHTTP can be safely removed from the individual
// Flags() subcommands.
bit = bit | FlagSetHTTP
if bit&FlagSetHTTP != 0 {
f := set.NewFlagSet("HTTP Options")
@ -260,7 +265,7 @@ func (c *BaseCommand) flagSet(bit FlagSetBit) *FlagSets {
Name: "format",
Target: &c.flagFormat,
Default: "table",
EnvVar: "VAULT_FORMAT",
EnvVar: EnvVaultFormat,
Completion: complete.PredictSet("table", "json", "yaml"),
Usage: "Print the output in the given format. Valid formats " +
"are \"table\", \"json\", or \"yaml\".",
@ -317,6 +322,11 @@ func (f *FlagSets) Parse(args []string) error {
return f.mainSet.Parse(args)
}
// Parsed reports whether the command-line flags have been parsed.
func (f *FlagSets) Parsed() bool {
return f.mainSet.Parsed()
}
// Args returns the remaining args after parsing.
func (f *FlagSets) Args() []string {
return f.mainSet.Args()

View File

@ -64,6 +64,13 @@ import (
physZooKeeper "github.com/hashicorp/vault/physical/zookeeper"
)
const (
// EnvVaultCLINoColor is an env var that toggles colored UI output.
EnvVaultCLINoColor = `VAULT_CLI_NO_COLOR`
// EnvVaultFormat is the output format
EnvVaultFormat = `VAULT_FORMAT`
)
var (
auditBackends = map[string]audit.Factory{
"file": auditFile.Factory,
@ -149,7 +156,9 @@ func (c *DeprecatedCommand) Help() string {
// Run wraps the embedded Run command and prints a warning about deprecation.
func (c *DeprecatedCommand) Run(args []string) int {
c.warn()
if Format(c.UI) == "table" {
c.warn()
}
return c.Command.Run(args)
}
@ -166,24 +175,7 @@ func (c *DeprecatedCommand) warn() {
var Commands map[string]cli.CommandFactory
var DeprecatedCommands map[string]cli.CommandFactory
func init() {
ui := &cli.ColoredUi{
ErrorColor: cli.UiColorRed,
WarnColor: cli.UiColorYellow,
Ui: &cli.BasicUi{
Writer: os.Stdout,
ErrorWriter: os.Stderr,
},
}
serverCmdUi := &cli.ColoredUi{
ErrorColor: cli.UiColorRed,
WarnColor: cli.UiColorYellow,
Ui: &cli.BasicUi{
Writer: os.Stdout,
},
}
func initCommands(ui, serverCmdUi cli.Ui) {
loginHandlers := map[string]LoginHandler{
"aws": &credAws.CLIHandler{},
"centrify": &credCentrify.CLIHandler{},
@ -572,7 +564,7 @@ func init() {
// Deprecated commands
//
// TODO: Remove in 0.9.0
// TODO: Remove not before 0.11.0
DeprecatedCommands = map[string]cli.CommandFactory{
"audit-disable": func() (cli.Command, error) {
return &DeprecatedCommand{

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"sort"
"strings"
@ -19,29 +20,38 @@ const (
hopeDelim = "♨"
)
func OutputSecret(ui cli.Ui, format string, secret *api.Secret) int {
return outputWithFormat(ui, format, secret, secret)
type FormatOptions struct {
Format string
}
func OutputList(ui cli.Ui, format string, secret *api.Secret) int {
return outputWithFormat(ui, format, secret, secret.Data["keys"])
func OutputSecret(ui cli.Ui, secret *api.Secret) int {
return outputWithFormat(ui, secret, secret)
}
func outputWithFormat(ui cli.Ui, format string, secret *api.Secret, data interface{}) int {
// If we had a colored UI, pull out the nested ui so we don't add escape
// sequences for outputting json, etc.
colorUI, ok := ui.(*cli.ColoredUi)
if ok {
ui = colorUI.Ui
func OutputList(ui cli.Ui, data interface{}) int {
switch data.(type) {
case *api.Secret:
secret := data.(*api.Secret)
return outputWithFormat(ui, secret, secret.Data["keys"])
default:
return outputWithFormat(ui, nil, data)
}
}
formatter, ok := Formatters[strings.ToLower(format)]
func OutputData(ui cli.Ui, data interface{}) int {
return outputWithFormat(ui, nil, data)
}
func outputWithFormat(ui cli.Ui, secret *api.Secret, data interface{}) int {
format := Format(ui)
formatter, ok := Formatters[format]
if !ok {
ui.Error(fmt.Sprintf("Invalid output format: %s", format))
return 1
}
if err := formatter.Output(ui, secret, data); err != nil {
ui.Error(fmt.Sprintf("Could not output secret: %s", err.Error()))
ui.Error(fmt.Sprintf("Could not parse output: %s", err.Error()))
return 1
}
return 0
@ -58,6 +68,22 @@ var Formatters = map[string]Formatter{
"yml": YamlFormatter{},
}
func format() string {
format := os.Getenv(EnvVaultFormat)
if format == "" {
format = "table"
}
return format
}
func Format(ui cli.Ui) string {
switch ui.(type) {
case *VaultUI:
return ui.(*VaultUI).format
}
return format()
}
// An output formatter for json output of an object
type JsonFormatter struct{}
@ -87,19 +113,32 @@ 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, s)
switch data.(type) {
case *api.Secret:
return t.OutputSecret(ui, secret)
case []interface{}:
return t.OutputList(ui, secret, data)
case []string:
return t.OutputList(ui, nil, data)
default:
return errors.New("Cannot use the table formatter for this type")
}
if s, ok := data.([]interface{}); ok {
return t.OutputList(ui, secret, s)
}
return errors.New("Cannot use the table formatter for this type")
}
func (t TableFormatter) OutputList(ui cli.Ui, secret *api.Secret, list []interface{}) error {
func (t TableFormatter) OutputList(ui cli.Ui, secret *api.Secret, data interface{}) error {
t.printWarnings(ui, secret)
switch data.(type) {
case []interface{}:
case []string:
ui.Output(tableOutput(data.([]string), nil))
return nil
default:
return errors.New("Error: table formatter cannot output list for this data type")
}
list := data.([]interface{})
if len(list) > 0 {
keys := make([]string, len(list))
for i, v := range list {
@ -208,7 +247,14 @@ func (t TableFormatter) OutputSecret(ui cli.Ui, secret *api.Secret) error {
return nil
}
// OutputSealStatus will print *api.SealStatusResponse in the CLI according to the format provided
func OutputSealStatus(ui cli.Ui, client *api.Client, status *api.SealStatusResponse) int {
switch Format(ui) {
case "table":
default:
return OutputData(ui, status)
}
var sealPrefix string
if status.RecoverySeal {
sealPrefix = "Recovery "

View File

@ -1,6 +1,7 @@
package command
import (
"os"
"strings"
"testing"
@ -30,8 +31,9 @@ func (m mockUi) Error(s string) { m.t.Log(s) }
func (m mockUi) Warn(s string) { m.t.Log(s) }
func TestJsonFormatter(t *testing.T) {
os.Setenv(EnvVaultFormat, "json")
ui := mockUi{t: t, SampleData: "something"}
if err := outputWithFormat(ui, "json", nil, ui); err != 0 {
if err := outputWithFormat(ui, nil, ui); err != 0 {
t.Fatal(err)
}
var newUi mockUi
@ -46,8 +48,9 @@ func TestJsonFormatter(t *testing.T) {
}
func TestYamlFormatter(t *testing.T) {
os.Setenv(EnvVaultFormat, "yaml")
ui := mockUi{t: t, SampleData: "something"}
if err := outputWithFormat(ui, "yaml", nil, ui); err != 0 {
if err := outputWithFormat(ui, nil, ui); err != 0 {
t.Fatal(err)
}
var newUi mockUi
@ -63,12 +66,68 @@ func TestYamlFormatter(t *testing.T) {
}
func TestTableFormatter(t *testing.T) {
os.Setenv(EnvVaultFormat, "table")
ui := mockUi{t: t}
s := api.Secret{Data: map[string]interface{}{"k": "something"}}
if err := outputWithFormat(ui, "table", &s, &s); err != 0 {
if err := outputWithFormat(ui, &s, &s); err != 0 {
t.Fatal(err)
}
if !strings.Contains(output, "something") {
t.Fatal("did not find 'something'")
}
}
func Test_Format_Parsing(t *testing.T) {
defer func() {
os.Setenv(EnvVaultCLINoColor, "")
os.Setenv(EnvVaultFormat, "")
}()
cases := []struct {
name string
args []string
out string
code int
}{
{
"format",
[]string{"-format", "json"},
"{",
0,
},
{
"format_bad",
[]string{"-format", "nope-not-real"},
"Invalid output format",
1,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
// Login with the token so we can renew-self.
token, _ := testTokenAndAccessor(t, client)
client.SetToken(token)
ui, cmd := testTokenRenewCommand(t)
cmd.client = client
tc.args = setupEnv(tc.args)
code := cmd.Run(tc.args)
if code != tc.code {
t.Errorf("expected %d to be %d", code, tc.code)
}
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, tc.out) {
t.Errorf("expected %q to contain %q", combined, tc.out)
}
})
}
}

View File

@ -123,5 +123,5 @@ func (c *LeaseRenewCommand) Run(args []string) int {
return 2
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}

View File

@ -74,18 +74,6 @@ func TestLeaseRenewCommand_Run(t *testing.T) {
"foo",
0,
},
{
"format",
[]string{"-format", "json"},
"{",
0,
},
{
"format_bad",
[]string{"-format", "nope-not-real"},
"Invalid output format",
1,
},
}
t.Run("group", func(t *testing.T) {

View File

@ -89,7 +89,7 @@ func (c *ListCommand) Run(args []string) int {
// If the secret is wrapped, return the wrapped response.
if secret.WrapInfo != nil && secret.WrapInfo.TTL != 0 {
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}
if _, ok := extractListData(secret); !ok {
@ -97,5 +97,5 @@ func (c *ListCommand) Run(args []string) int {
return 2
}
return OutputList(c.UI, c.flagFormat, secret)
return OutputList(c.UI, secret)
}

View File

@ -57,24 +57,6 @@ func TestListCommand_Run(t *testing.T) {
"bar\nbaz\nfoo",
0,
},
{
"format",
[]string{
"-format", "json",
"secret/list/",
},
"[",
0,
},
{
"format_bad",
[]string{
"-format", "nope-not-real",
"secret/list/",
},
"Invalid output format",
1,
},
}
t.Run("validations", func(t *testing.T) {

View File

@ -274,7 +274,7 @@ func (c *LoginCommand) Run(args []string) int {
return PrintRawField(c.UI, secret, "wrapping_token")
}
if c.flagNoStore {
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}
}
@ -308,7 +308,7 @@ func (c *LoginCommand) Run(args []string) int {
c.UI.Error(wrapAtLength(
"Authentication was successful, but the token was not persisted. The "+
"resulting token is shown below for your records.") + "\n")
OutputSecret(c.UI, c.flagFormat, secret)
OutputSecret(c.UI, secret)
return 2
}
@ -335,7 +335,7 @@ func (c *LoginCommand) Run(args []string) int {
}
// Print some yay! text, but only in table mode.
if c.flagFormat == "table" {
if Format(c.UI) == "table" {
c.UI.Output(wrapAtLength(
"Success! You are now authenticated. The token information displayed "+
"below is already stored in the token helper. You do NOT need to run "+
@ -343,7 +343,7 @@ func (c *LoginCommand) Run(args []string) int {
"this token.") + "\n")
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}
// extractToken extracts the token from the given secret, automatically

View File

@ -10,11 +10,36 @@ import (
"text/tabwriter"
"github.com/mitchellh/cli"
"golang.org/x/crypto/ssh/terminal"
)
func Run(args []string) int {
// Handle -v shorthand
type VaultUI struct {
cli.Ui
isTerminal bool
format string
}
func (u *VaultUI) Output(m string) {
if u.isTerminal {
u.Ui.Output(m)
} else {
getWriterFromUI(u.Ui).Write([]byte(m))
}
}
// setupEnv parses args and may replace them and sets some env vars to known
// values based on format options
func setupEnv(args []string) []string {
var format string
var nextArgFormat bool
for _, arg := range args {
if nextArgFormat {
nextArgFormat = false
format = arg
continue
}
if arg == "--" {
break
}
@ -23,8 +48,87 @@ func Run(args []string) int {
args = []string{"version"}
break
}
// Parse a given flag here, which overrides the env var
if strings.HasPrefix(arg, "--format=") {
format = strings.TrimPrefix(arg, "--format=")
}
if strings.HasPrefix(arg, "-format=") {
format = strings.TrimPrefix(arg, "-format=")
}
// For backwards compat, it could be specified without an equal sign
if arg == "-format" || arg == "--format" {
nextArgFormat = true
}
}
envVaultFormat := os.Getenv(EnvVaultFormat)
// If we did not parse a value, fetch the env var
if format == "" && envVaultFormat != "" {
format = envVaultFormat
}
// Lowercase for consistency
format = strings.ToLower(format)
if format == "" {
format = "table"
}
// Put back into the env for later
os.Setenv(EnvVaultFormat, format)
return args
}
func Run(args []string) int {
args = setupEnv(args)
// Don't use color if disabled
color := true
if os.Getenv(EnvVaultCLINoColor) != "" {
color = false
}
format := format()
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
ui := &VaultUI{
Ui: &cli.BasicUi{
Writer: os.Stdout,
ErrorWriter: os.Stderr,
},
isTerminal: isTerminal,
format: format,
}
serverCmdUi := &VaultUI{
Ui: &cli.BasicUi{
Writer: os.Stdout,
},
isTerminal: isTerminal,
format: format,
}
if _, ok := Formatters[format]; !ok {
ui.Error(fmt.Sprintf("Invalid output format: %s", format))
return 1
}
// Only use colored UI if stdoout is a tty, and not disabled
if isTerminal && color && format == "table" {
ui.Ui = &cli.ColoredUi{
ErrorColor: cli.UiColorRed,
WarnColor: cli.UiColorYellow,
Ui: ui.Ui,
}
serverCmdUi.Ui = &cli.ColoredUi{
ErrorColor: cli.UiColorRed,
WarnColor: cli.UiColorYellow,
Ui: serverCmdUi.Ui,
}
}
initCommands(ui, serverCmdUi)
// Calculate hidden commands from the deprecated ones
hiddenCommands := make([]string, 0, len(DeprecatedCommands)+1)
for k := range DeprecatedCommands {

View File

@ -82,7 +82,7 @@ Usage: vault operator generate-root [options] [KEY]
}
func (c *OperatorGenerateRootCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
@ -337,7 +337,13 @@ func (c *OperatorGenerateRootCommand) init(client *api.Client, otp, pgpKey strin
c.UI.Error(fmt.Sprintf("Error initializing root generation: %s", err))
return 2
}
return c.printStatus(status)
switch Format(c.UI) {
case "table":
return c.printStatus(status)
default:
return OutputData(c.UI, status)
}
}
// provide prompts the user for the seal key and posts it to the update root
@ -428,7 +434,12 @@ func (c *OperatorGenerateRootCommand) provide(client *api.Client, key string, dr
c.UI.Error(fmt.Sprintf("Error posting unseal key: %s", err))
return 2
}
return c.printStatus(status)
switch Format(c.UI) {
case "table":
return c.printStatus(status)
default:
return OutputData(c.UI, status)
}
}
// cancel cancels the root token generation
@ -456,7 +467,12 @@ func (c *OperatorGenerateRootCommand) status(client *api.Client, drToken bool) i
c.UI.Error(fmt.Sprintf("Error getting root generation status: %s", err))
return 2
}
return c.printStatus(status)
switch Format(c.UI) {
case "table":
return c.printStatus(status)
default:
return OutputData(c.UI, status)
}
}
// printStatus dumps the status to output

View File

@ -1,7 +1,10 @@
// +build !race
package command
import (
"io"
"os"
"regexp"
"strings"
"testing"
@ -129,6 +132,14 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
ui, cmd := testOperatorGenerateRootCommand(t)
// Simulate piped output to print raw output
old := os.Stdout
_, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
os.Stdout = w
code := cmd.Run([]string{
"-decode", encoded,
"-otp", otp,
@ -137,6 +148,9 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
t.Errorf("expected %d to be %d", code, exp)
}
w.Close()
os.Stdout = old
expected := "5b54841c-c705-e59c-c6e4-a22b48e4b2cf"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if combined != expected {

View File

@ -1,13 +1,11 @@
package command
import (
"encoding/json"
"fmt"
"net/url"
"runtime"
"strings"
"github.com/ghodss/yaml"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/mitchellh/cli"
@ -434,15 +432,10 @@ func (c *OperatorInitCommand) init(client *api.Client, req *api.InitRequest) int
return 2
}
switch c.flagFormat {
case "yaml", "yml":
return c.initOutputYAML(req, resp)
case "json":
return c.initOutputJSON(req, resp)
switch Format(c.UI) {
case "table":
default:
c.UI.Error(fmt.Sprintf("Unknown format: %s", c.flagFormat))
return 1
return OutputData(c.UI, newMachineInit(req, resp))
}
for i, key := range resp.Keys {
@ -503,26 +496,6 @@ func (c *OperatorInitCommand) init(client *api.Client, req *api.InitRequest) int
return 0
}
// initOutputYAML outputs the init output as YAML.
func (c *OperatorInitCommand) initOutputYAML(req *api.InitRequest, resp *api.InitResponse) int {
b, err := yaml.Marshal(newMachineInit(req, resp))
if err != nil {
c.UI.Error(fmt.Sprintf("Error marshaling YAML: %s", err))
return 2
}
return PrintRaw(c.UI, strings.TrimSpace(string(b)))
}
// initOutputJSON outputs the init output as JSON.
func (c *OperatorInitCommand) initOutputJSON(req *api.InitRequest, resp *api.InitResponse) int {
b, err := json.Marshal(newMachineInit(req, resp))
if err != nil {
c.UI.Error(fmt.Sprintf("Error marshaling JSON: %s", err))
return 2
}
return PrintRaw(c.UI, strings.TrimSpace(string(b)))
}
// status inspects the init status of vault and returns an appropriate error
// code and message.
func (c *OperatorInitCommand) status(client *api.Client) int {

View File

@ -32,7 +32,7 @@ Usage: vault operator key-status [options]
}
func (c *OperatorKeyStatusCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
return c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
}
func (c *OperatorKeyStatusCommand) AutocompleteArgs() complete.Predictor {
@ -69,6 +69,11 @@ func (c *OperatorKeyStatusCommand) Run(args []string) int {
return 2
}
c.UI.Output(printKeyStatus(status))
return 0
switch Format(c.UI) {
case "table":
c.UI.Output(printKeyStatus(status))
return 0
default:
return OutputData(c.UI, status)
}
}

View File

@ -96,7 +96,7 @@ Usage: vault rekey [options] [KEY]
}
func (c *OperatorRekeyCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
f := set.NewFlagSet("Common Options")
@ -534,7 +534,7 @@ func (c *OperatorRekeyCommand) backupRetrieve(client *api.Client) int {
Data: structs.New(storedKeys).Map(),
}
return OutputSecret(c.UI, "table", secret)
return OutputSecret(c.UI, secret)
}
// backupDelete deletes the stored backup keys.
@ -579,11 +579,22 @@ func (c *OperatorRekeyCommand) printStatus(status *api.RekeyStatusResponse) int
out = append(out, fmt.Sprintf("Backup | %t", status.Backup))
}
c.UI.Output(tableOutput(out, nil))
return 0
switch Format(c.UI) {
case "table":
c.UI.Output(tableOutput(out, nil))
return 0
default:
return OutputData(c.UI, status)
}
}
func (c *OperatorRekeyCommand) printUnsealKeys(status *api.RekeyStatusResponse, resp *api.RekeyUpdateResponse) int {
switch Format(c.UI) {
case "table":
default:
return OutputData(c.UI, resp)
}
// Space between the key prompt, if any, and the output
c.UI.Output("")

View File

@ -50,7 +50,7 @@ Usage: vault operator unseal [options] [KEY]
}
func (c *OperatorUnsealCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")

View File

@ -1,7 +1,9 @@
package command
import (
"encoding/json"
"io/ioutil"
"os"
"strings"
"testing"
@ -138,3 +140,36 @@ func TestOperatorUnsealCommand_Run(t *testing.T) {
assertNoTabs(t, cmd)
})
}
func TestOperatorUnsealCommand_Format(t *testing.T) {
defer func() {
os.Setenv(EnvVaultFormat, "")
os.Setenv(EnvVaultCLINoColor, "")
}()
client, keys, closer := testVaultServerUnseal(t)
defer closer()
// Seal so we can unseal
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
ui, cmd := testOperatorUnsealCommand(t)
cmd.client = client
cmd.testOutput = ioutil.Discard
args := setupEnv([]string{"-format", "json"})
// Unseal with one key
code := cmd.Run(append(args, []string{
keys[0],
}...))
if exp := 0; code != exp {
t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String())
}
if !json.Valid(ui.OutputWriter.Bytes()) {
t.Error("expected output to be valid JSON")
}
}

View File

@ -29,6 +29,7 @@ func (c *PoliciesDeprecatedCommand) Run(args []string) int {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
// Got an arg, this is trying to read a policy

View File

@ -31,7 +31,7 @@ Usage: vault policy list [options]
}
func (c *PolicyListCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
return c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
}
func (c *PolicyListCommand) AutocompleteArgs() complete.Predictor {
@ -68,9 +68,14 @@ func (c *PolicyListCommand) Run(args []string) int {
c.UI.Error(fmt.Sprintf("Error listing policies: %s", err))
return 2
}
for _, p := range policies {
c.UI.Output(p)
}
return 0
switch Format(c.UI) {
case "table":
for _, p := range policies {
c.UI.Output(p)
}
return 0
default:
return OutputData(c.UI, policies)
}
}

View File

@ -36,7 +36,7 @@ Usage: vault policy read [options] [NAME]
}
func (c *PolicyReadCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
return c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
}
func (c *PolicyReadCommand) AutocompleteArgs() complete.Predictor {
@ -81,7 +81,15 @@ func (c *PolicyReadCommand) Run(args []string) int {
c.UI.Error(fmt.Sprintf("No policy named: %s", name))
return 2
}
c.UI.Output(strings.TrimSpace(rules))
return 0
switch Format(c.UI) {
case "table":
c.UI.Output(strings.TrimSpace(rules))
return 0
default:
resp := map[string]string{
"policy": rules,
}
return OutputData(c.UI, &resp)
}
}

View File

@ -90,5 +90,5 @@ func (c *ReadCommand) Run(args []string) int {
return PrintRawField(c.UI, secret, c.flagField)
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}

View File

@ -69,24 +69,6 @@ func TestReadCommand_Run(t *testing.T) {
"not present in secret",
1,
},
{
"format",
[]string{
"-format", "json",
"secret/read/foo",
},
"{",
0,
},
{
"format_bad",
[]string{
"-format", "nope-not-real",
"secret/read/foo",
},
"Invalid output format",
1,
},
}
t.Run("validations", func(t *testing.T) {

View File

@ -44,7 +44,7 @@ Usage: vault rotate [options]
}
func (c *OperatorRotateCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
return c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
}
func (c *OperatorRotateCommand) AutocompleteArgs() complete.Predictor {
@ -89,8 +89,13 @@ func (c *OperatorRotateCommand) Run(args []string) int {
return 2
}
c.UI.Output("Success! Rotated key")
c.UI.Output("")
c.UI.Output(printKeyStatus(status))
return 0
switch Format(c.UI) {
case "table":
c.UI.Output("Success! Rotated key")
c.UI.Output("")
c.UI.Output(printKeyStatus(status))
return 0
default:
return OutputData(c.UI, status)
}
}

View File

@ -47,7 +47,7 @@ Usage: vault secrets list [options]
}
func (c *SecretsListCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
@ -96,13 +96,17 @@ func (c *SecretsListCommand) Run(args []string) int {
return 2
}
if c.flagDetailed {
c.UI.Output(tableOutput(c.detailedMounts(mounts), nil))
switch Format(c.UI) {
case "table":
if c.flagDetailed {
c.UI.Output(tableOutput(c.detailedMounts(mounts), nil))
return 0
}
c.UI.Output(tableOutput(c.simpleMounts(mounts), nil))
return 0
default:
return OutputData(c.UI, mounts)
}
c.UI.Output(tableOutput(c.simpleMounts(mounts), nil))
return 0
}
func (c *SecretsListCommand) simpleMounts(mounts map[string]*api.MountOutput) []string {

View File

@ -386,10 +386,10 @@ func (c *SSHCommand) handleTypeCA(username, hostname, ip string, sshArgs []strin
// Handle no-exec
if c.flagNoExec {
if c.flagFormat != "" {
if c.flagField != "" {
return PrintRawField(c.UI, secret, c.flagField)
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}
// Extract public key
@ -490,10 +490,10 @@ func (c *SSHCommand) handleTypeOTP(username, ip string, sshArgs []string) int {
// Handle no-exec
if c.flagNoExec {
if c.flagFormat != "" {
if c.flagField != "" {
return PrintRawField(c.UI, secret, c.flagField)
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}
var cmd *exec.Cmd
@ -572,10 +572,10 @@ func (c *SSHCommand) handleTypeDynamic(username, ip string, sshArgs []string) in
// Handle no-exec
if c.flagNoExec {
if c.flagFormat != "" {
if c.flagField != "" {
return PrintRawField(c.UI, secret, c.flagField)
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}
// Write the dynamic key to disk

View File

@ -39,7 +39,7 @@ Usage: vault status [options]
}
func (c *StatusCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
return c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
}
func (c *StatusCommand) AutocompleteArgs() complete.Predictor {
@ -77,13 +77,13 @@ func (c *StatusCommand) Run(args []string) int {
return 1
}
// Do not return the int here, since we want to return a custom error code
// depending on the seal status.
OutputSealStatus(c.UI, client, status)
// Do not return the int here yet, since we may want to return a custom error
// code depending on the seal status.
code := OutputSealStatus(c.UI, client, status)
if status.Sealed {
return 2
}
return 0
return code
}

View File

@ -45,7 +45,7 @@ Usage: vault token capabilities [options] [TOKEN] PATH
}
func (c *TokenCapabilitiesCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
return c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
}
func (c *TokenCapabilitiesCommand) AutocompleteArgs() complete.Predictor {
@ -94,7 +94,12 @@ func (c *TokenCapabilitiesCommand) Run(args []string) int {
return 2
}
sort.Strings(capabilities)
c.UI.Output(strings.Join(capabilities, ", "))
return 0
switch Format(c.UI) {
case "table":
sort.Strings(capabilities)
c.UI.Output(strings.Join(capabilities, ", "))
return 0
default:
return OutputData(c.UI, capabilities)
}
}

View File

@ -246,5 +246,5 @@ func (c *TokenCreateCommand) Run(args []string) int {
return PrintRawField(c.UI, secret, c.flagField)
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}

View File

@ -68,22 +68,6 @@ func TestTokenCreateCommand_Run(t *testing.T) {
"not present in secret",
1,
},
{
"format",
[]string{
"-format", "json",
},
"{",
0,
},
{
"format_bad",
[]string{
"-format", "nope-not-real",
},
"Invalid output format",
1,
},
}
t.Run("validations", func(t *testing.T) {

View File

@ -124,5 +124,5 @@ func (c *TokenLookupCommand) Run(args []string) int {
return 2
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}

View File

@ -45,18 +45,6 @@ func TestTokenLookupCommand_Run(t *testing.T) {
"Too many arguments",
1,
},
{
"format",
[]string{"-format", "json"},
"{",
0,
},
{
"format_bad",
[]string{"-format", "nope-not-real"},
"Invalid output format",
1,
},
}
t.Run("validations", func(t *testing.T) {

View File

@ -132,5 +132,5 @@ func (c *TokenRenewCommand) Run(args []string) int {
return 2
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}

View File

@ -53,18 +53,6 @@ func TestTokenRenewCommand_Run(t *testing.T) {
"",
0,
},
{
"format",
[]string{"-format", "json"},
"{",
0,
},
{
"format_bad",
[]string{"-format", "nope-not-real"},
"Invalid output format",
1,
},
}
t.Run("validations", func(t *testing.T) {

View File

@ -100,7 +100,7 @@ func (c *UnwrapCommand) Run(args []string) int {
// Check if the original was a list response and format as a list
if _, ok := extractListData(secret); ok {
return OutputList(c.UI, c.flagFormat, secret)
return OutputList(c.UI, secret)
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}

View File

@ -65,18 +65,6 @@ func TestUnwrapCommand_Run(t *testing.T) {
"not present in secret",
1,
},
{
"format",
[]string{"-format", "json"},
"{",
0,
},
{
"format_bad",
[]string{"-format", "nope-not-real"},
"Invalid output format",
1,
},
}
t.Run("validations", func(t *testing.T) {

View File

@ -115,6 +115,8 @@ func PrintRaw(ui cli.Ui, str string) int {
// type, this falls back to os.Stdout.
func getWriterFromUI(ui cli.Ui) io.Writer {
switch t := ui.(type) {
case *VaultUI:
return getWriterFromUI(t.Ui)
case *cli.BasicUi:
return t.Writer
case *cli.ColoredUi:

View File

@ -135,7 +135,7 @@ func (c *WriteCommand) Run(args []string) int {
}
if secret == nil {
// Don't output anything unless using the "table" format
if c.flagFormat == "table" {
if Format(c.UI) == "table" {
c.UI.Info(fmt.Sprintf("Success! Data written to: %s", path))
}
return 0
@ -146,5 +146,5 @@ func (c *WriteCommand) Run(args []string) int {
return PrintRawField(c.UI, secret, c.flagField)
}
return OutputSecret(c.UI, c.flagFormat, secret)
return OutputSecret(c.UI, secret)
}

View File

@ -37,5 +37,13 @@ file/ file n/a replicated file_path=/var/log/audit.log
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.
### Command Options
- `-detailed` `(bool: false)` - Print detailed information such as options and
replication status about each auth device.

View File

@ -39,5 +39,13 @@ userpass/ userpass auth_userpass_eea6507e n/a system syst
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.
### Command Options
- `-detailed` `(bool: false)` - Print detailed information such as configuration
and replication status about each auth method.

View File

@ -212,6 +212,10 @@ model](/docs/internals/security.html).
Name to use as the SNI host when connecting via TLS.
### `VAULT_CLI_NO_COLOR`
If provided, Vault output will not include ANSI color escape sequence characters.
### `VAULT_MFA`
**ENTERPRISE ONLY**

View File

@ -89,13 +89,13 @@ flags](/docs/commands/index.html) included on all commands.
- `-field` `(string: "")` - Print only the field with the given name. Specifying
this option will take precedence over other formatting directives. The result
will not have a trailing newline making it idea for piping to other processes.
will not have a trailing newline making it ideal for piping to other processes.
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.
## Command Options
### Command Options
- `-method` `(string "token")` - Type of authentication to use such as
"userpass" or "ldap". Note this corresponds to the TYPE, not the enabled path.

View File

@ -55,6 +55,14 @@ $ vault operator generate-root -otp="..."
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.
### Command Options
- `-cancel` `(bool: false)` - Reset the root token generation progress. This
will discard any submitted unseal keys or configuration.

View File

@ -24,5 +24,11 @@ Install Time 01 Jan 17 12:30 UTC
## Usage
There are no flags beyond the [standard set of flags](/docs/commands/index.html)
included on all commands.
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.

View File

@ -72,7 +72,13 @@ $ vault operator rekey -backup-delete
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
## Common Options
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.
### Command Options
- `-cancel` `(bool: false)` - Reset the rekeying progress. This will discard any submitted unseal keys
or configuration. The default is false.

View File

@ -32,5 +32,11 @@ Install Time 01 May 17 10:30 UTC
## Usage
There are no flags beyond the [standard set of flags](/docs/commands/index.html)
included on all commands.
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.

View File

@ -50,5 +50,13 @@ Unseal Progress: 0
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.
### Command Options
- `-reset` `(bool: false)` - Discard any previously entered keys to the unseal
process.

View File

@ -24,5 +24,12 @@ root
## Usage
There are no flags beyond the [standard set of flags](/docs/commands/index.html)
included on all commands.
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.

View File

@ -22,5 +22,11 @@ $ vault policy read my-policy
## Usage
There are no flags beyond the [standard set of flags](/docs/commands/index.html)
included on all commands.
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.

View File

@ -37,7 +37,7 @@ flags](/docs/commands/index.html) included on all commands.
- `-field` `(string: "")` - Print only the field with the given name. Specifying
this option will take precedence over other formatting directives. The result
will not have a trailing newline making it idea for piping to other processes.
will not have a trailing newline making it ideal for piping to other processes.
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the

View File

@ -45,5 +45,13 @@ sys/ system system_a9fd745d n/a n/a n/a
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.
### Command Options
- `-detailed` `(bool: false)` - Print detailed information such as configuration
and replication status about each secrets engine.

View File

@ -57,7 +57,7 @@ flags](/docs/commands/index.html) included on all commands.
- `-field` `(string: "")` - Print only the field with the given name. Specifying
this option will take precedence over other formatting directives. The result
will not have a trailing newline making it idea for piping to other processes.
will not have a trailing newline making it ideal for piping to other processes.
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the

View File

@ -40,5 +40,11 @@ High-Availability Enabled: false
## Usage
There are no flags beyond the [standard set of flags](/docs/commands/index.html)
included on all commands.
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.

View File

@ -35,5 +35,11 @@ deny
## Usage
There are no flags beyond the [standard set of flags](/docs/commands/index.html)
included on all commands.
The following flags are available in addition to the [standard set of
flags](/docs/commands/index.html) included on all commands.
### Output Options
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable.

View File

@ -64,7 +64,7 @@ flags](/docs/commands/index.html) included on all commands.
- `-field` `(string: "")` - Print only the field with the given name. Specifying
this option will take precedence over other formatting directives. The result
will not have a trailing newline making it idea for piping to other processes.
will not have a trailing newline making it ideal for piping to other processes.
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the

View File

@ -39,7 +39,7 @@ flags](/docs/commands/index.html) included on all commands.
- `-field` `(string: "")` - Print only the field with the given name. Specifying
this option will take precedence over other formatting directives. The result
will not have a trailing newline making it idea for piping to other processes.
will not have a trailing newline making it ideal for piping to other processes.
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the

View File

@ -56,7 +56,7 @@ flags](/docs/commands/index.html) included on all commands.
- `-field` `(string: "")` - Print only the field with the given name. Specifying
this option will take precedence over other formatting directives. The result
will not have a trailing newline making it idea for piping to other processes.
will not have a trailing newline making it ideal for piping to other processes.
- `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the