Update flags to align with other var commands. (#14550)
This commit is contained in:
parent
add8383c77
commit
4c9554f87c
|
@ -322,8 +322,10 @@ const (
|
|||
errMissingTemplate = `A template must be supplied using '-template' when using go-template formatting`
|
||||
errUnexpectedTemplate = `The '-template' flag is only valid when using 'go-template' formatting`
|
||||
errVariableNotFound = `Variable not found`
|
||||
errNoMatchingVariables = `No matching variables found`
|
||||
errInvalidInFormat = `Invalid value for "-in"; valid values are [hcl, json]`
|
||||
errInvalidOutFormat = `Invalid value for "-out"; valid values are [go-template, hcl, json, none, table]`
|
||||
errInvalidListOutFormat = `Invalid value for "-out"; valid values are [go-template, json, table, terse]`
|
||||
errWildcardNamespaceNotAllowed = `The wildcard namespace ("*") is not valid for this command.`
|
||||
|
||||
msgfmtCASMismatch = `
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
@ -10,12 +12,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
msgVariableNotFound = "No matching variables found"
|
||||
msgWarnFilterPerformance = "Filter queries require a full scan of the data; use prefix searching where possible"
|
||||
)
|
||||
|
||||
type VarListCommand struct {
|
||||
Prefix string
|
||||
prefix string
|
||||
outFmt string
|
||||
tmpl string
|
||||
Meta
|
||||
}
|
||||
|
||||
|
@ -25,6 +28,9 @@ Usage: nomad var list [options] <prefix>
|
|||
|
||||
List is used to list available variables. Supplying an optional prefix,
|
||||
filters the list to variables having a path starting with the prefix.
|
||||
When using pagination, the next page token is provided in the JSON output
|
||||
or as a message to standard error to leave standard output for the listed
|
||||
variables from that page.
|
||||
|
||||
If ACLs are enabled, this command will only return variables stored in
|
||||
namespaces where the token has the 'variables:list' capability.
|
||||
|
@ -46,15 +52,16 @@ List Options:
|
|||
option are less efficient than using the prefix parameter; therefore,
|
||||
the prefix parameter should be used whenever possible.
|
||||
|
||||
-json
|
||||
Output the variables in JSON format.
|
||||
-out (go-template | json | table | terse )
|
||||
Format to render created or updated variable. Defaults to "none" when
|
||||
stdout is a terminal and "json" when the output is redirected. The "terse"
|
||||
format outputs as little information as possible to uniquely identify a
|
||||
variable depending on whether or not the wildcard namespace was passed.
|
||||
|
||||
-t
|
||||
Format and display the variables using a Go template.
|
||||
-template
|
||||
Template to render output with. Required when format is "go-template",
|
||||
invalid for other formats.
|
||||
|
||||
-q
|
||||
Output matching variable paths with no additional information.
|
||||
This option overrides the '-t' option.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
@ -62,8 +69,8 @@ List Options:
|
|||
func (c *VarListCommand) AutocompleteFlags() complete.Flags {
|
||||
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
||||
complete.Flags{
|
||||
"-json": complete.PredictNothing,
|
||||
"-t": complete.PredictAnything,
|
||||
"-out": complete.PredictSet("go-template", "json", "terse", "table"),
|
||||
"-template": complete.PredictAnything,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -78,19 +85,23 @@ func (c *VarListCommand) Synopsis() string {
|
|||
|
||||
func (c *VarListCommand) Name() string { return "var list" }
|
||||
func (c *VarListCommand) Run(args []string) int {
|
||||
var json, quiet bool
|
||||
var perPage int
|
||||
var tmpl, pageToken, filter, prefix string
|
||||
var pageToken, filter, prefix string
|
||||
|
||||
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||
flags.BoolVar(&quiet, "q", false, "")
|
||||
flags.BoolVar(&json, "json", false, "")
|
||||
flags.StringVar(&tmpl, "t", "", "")
|
||||
flags.StringVar(&c.tmpl, "template", "", "")
|
||||
|
||||
flags.IntVar(&perPage, "per-page", 0, "")
|
||||
flags.StringVar(&pageToken, "page-token", "", "")
|
||||
flags.StringVar(&filter, "filter", "", "")
|
||||
|
||||
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 {
|
||||
flags.StringVar(&c.outFmt, "out", "table", "")
|
||||
} else {
|
||||
flags.StringVar(&c.outFmt, "out", "json", "")
|
||||
}
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
@ -107,6 +118,12 @@ func (c *VarListCommand) Run(args []string) int {
|
|||
prefix = args[0]
|
||||
}
|
||||
|
||||
if err := c.validateOutputFlag(); err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
c.Ui.Error(commandErrorText(c))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Get the HTTP client
|
||||
client, err := c.Meta.Client()
|
||||
if err != nil {
|
||||
|
@ -131,26 +148,19 @@ func (c *VarListCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
switch {
|
||||
case json:
|
||||
|
||||
switch c.outFmt {
|
||||
case "json":
|
||||
// obj and items enable us to rework the output before sending it
|
||||
// to the Format method for transformation into JSON.
|
||||
var obj, items interface{}
|
||||
obj = vars
|
||||
items = vars
|
||||
|
||||
if quiet {
|
||||
items = dataToQuietJSONReadySlice(vars, c.Meta.namespace)
|
||||
obj = items
|
||||
}
|
||||
|
||||
// If the response is paginated, we need to provide a means for the
|
||||
// caller to get to the pagination information. Wrapping the list
|
||||
// in a struct for the special case allows this extra data without
|
||||
// adding unnecessary structure in the non-paginated case.
|
||||
if perPage > 0 {
|
||||
|
||||
obj = struct {
|
||||
Data interface{}
|
||||
QueryMeta *api.QueryMeta
|
||||
|
@ -162,7 +172,7 @@ func (c *VarListCommand) Run(args []string) int {
|
|||
|
||||
// By this point, the output is ready to be transformed to JSON via
|
||||
// the Format func.
|
||||
out, err := Format(json, tmpl, obj)
|
||||
out, err := Format(true, "", obj)
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
|
@ -174,18 +184,17 @@ func (c *VarListCommand) Run(args []string) int {
|
|||
// itself, exit the command here so that it doesn't double print.
|
||||
return 0
|
||||
|
||||
case quiet:
|
||||
case "terse":
|
||||
c.Ui.Output(
|
||||
formatList(
|
||||
dataToQuietStringSlice(vars, c.Meta.namespace)))
|
||||
|
||||
case len(tmpl) > 0:
|
||||
out, err := Format(json, tmpl, vars)
|
||||
case "go-template":
|
||||
out, err := Format(false, c.tmpl, vars)
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output(out)
|
||||
|
||||
default:
|
||||
|
@ -204,7 +213,7 @@ func (c *VarListCommand) Run(args []string) int {
|
|||
|
||||
func formatVarStubs(vars []*api.VariableMetadata) string {
|
||||
if len(vars) == 0 {
|
||||
return msgVariableNotFound
|
||||
return errNoMatchingVariables
|
||||
}
|
||||
|
||||
// Sort the output by variable namespace, path
|
||||
|
@ -248,28 +257,19 @@ func dataToQuietStringSlice(vars []*api.VariableMetadata, ns string) []string {
|
|||
return pList
|
||||
}
|
||||
|
||||
func dataToQuietJSONReadySlice(vars []*api.VariableMetadata, ns string) interface{} {
|
||||
// If ns is the wildcard namespace, we have to provide namespace
|
||||
// as part of the quiet output, otherwise it can be a simple list
|
||||
// of paths.
|
||||
if ns == "*" {
|
||||
type pTuple struct {
|
||||
Namespace string
|
||||
Path string
|
||||
func (c *VarListCommand) validateOutputFlag() error {
|
||||
if c.outFmt != "go-template" && c.tmpl != "" {
|
||||
return errors.New(errUnexpectedTemplate)
|
||||
}
|
||||
pList := make([]*pTuple, len(vars))
|
||||
for i, sv := range vars {
|
||||
pList[i] = &pTuple{sv.Namespace, sv.Path}
|
||||
switch c.outFmt {
|
||||
case "json", "terse", "table":
|
||||
return nil
|
||||
case "go-template":
|
||||
if c.tmpl == "" {
|
||||
return errors.New(errMissingTemplate)
|
||||
}
|
||||
return pList
|
||||
return nil
|
||||
default:
|
||||
return errors.New(errInvalidListOutFormat)
|
||||
}
|
||||
|
||||
// Reduce the items slice to a string slice containing only the
|
||||
// variable paths.
|
||||
pList := make([]string, len(vars))
|
||||
for i, sv := range vars {
|
||||
pList[i] = sv.Path
|
||||
}
|
||||
|
||||
return pList
|
||||
}
|
||||
|
|
|
@ -50,6 +50,24 @@ func TestVarListCommand_Offline(t *testing.T) {
|
|||
exitCode: 1,
|
||||
expectStdErrPrefix: "Error initializing client: invalid address",
|
||||
},
|
||||
{
|
||||
name: "missing template",
|
||||
args: []string{`-out=go-template`, "foo"},
|
||||
exitCode: 1,
|
||||
expectStdErrPrefix: errMissingTemplate,
|
||||
},
|
||||
{
|
||||
name: "unexpected_template",
|
||||
args: []string{`-out=json`, `-template="bad"`, "foo"},
|
||||
exitCode: 1,
|
||||
expectStdErrPrefix: errUnexpectedTemplate,
|
||||
},
|
||||
{
|
||||
name: "bad out",
|
||||
args: []string{`-out=bad`, "foo"},
|
||||
exitCode: 1,
|
||||
expectStdErrPrefix: errInvalidListOutFormat,
|
||||
},
|
||||
}
|
||||
for _, tC := range testCases {
|
||||
t.Run(tC.name, func(t *testing.T) {
|
||||
|
@ -109,13 +127,6 @@ func TestVarListCommand_Online(t *testing.T) {
|
|||
|
||||
nsList := []string{api.DefaultNamespace, "ns1"}
|
||||
pathList := []string{"a/b/c", "a/b/c/d", "z/y", "z/y/x"}
|
||||
toJSON := func(in interface{}) string {
|
||||
b, err := json.MarshalIndent(in, "", " ")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(b))
|
||||
}
|
||||
variables := setupTestVariables(client, nsList, pathList)
|
||||
|
||||
testTmpl := `{{ range $i, $e := . }}{{if ne $i 0}}{{print "•"}}{{end}}{{printf "%v\t%v" .Namespace .Path}}{{end}}`
|
||||
|
@ -150,12 +161,12 @@ func TestVarListCommand_Online(t *testing.T) {
|
|||
testCases := []testVarListTestCase{
|
||||
{
|
||||
name: "plaintext/not found",
|
||||
args: []string{"does/not/exist"},
|
||||
expectStdOut: msgVariableNotFound,
|
||||
args: []string{"-out=table", "does/not/exist"},
|
||||
expectStdOut: errNoMatchingVariables,
|
||||
},
|
||||
{
|
||||
name: "plaintext/single variable",
|
||||
args: []string{"a/b/c/d"},
|
||||
args: []string{"-out=table", "a/b/c/d"},
|
||||
expectStdOut: formatList([]string{
|
||||
"Namespace|Path|Last Updated",
|
||||
fmt.Sprintf(
|
||||
|
@ -166,41 +177,41 @@ func TestVarListCommand_Online(t *testing.T) {
|
|||
),
|
||||
},
|
||||
{
|
||||
name: "plaintext/quiet",
|
||||
args: []string{"-q"},
|
||||
name: "plaintext/terse",
|
||||
args: []string{"-out=terse"},
|
||||
expectStdOut: strings.Join(variables.HavingNamespace(api.DefaultNamespace).Strings(), "\n"),
|
||||
},
|
||||
{
|
||||
name: "plaintext/quiet/prefix",
|
||||
args: []string{"-q", "a/b/c"},
|
||||
name: "plaintext/terse/prefix",
|
||||
args: []string{"-out=terse", "a/b/c"},
|
||||
expectStdOut: strings.Join(variables.HavingNSPrefix(api.DefaultNamespace, "a/b/c").Strings(), "\n"),
|
||||
},
|
||||
{
|
||||
name: "plaintext/quiet/filter",
|
||||
args: []string{"-q", "-filter", "VariableMetadata.Path == \"a/b/c\""},
|
||||
name: "plaintext/terse/filter",
|
||||
args: []string{"-out=terse", "-filter", "VariableMetadata.Path == \"a/b/c\""},
|
||||
expectStdOut: "a/b/c",
|
||||
expectStdErrPrefix: msgWarnFilterPerformance,
|
||||
},
|
||||
{
|
||||
name: "plaintext/quiet/paginated",
|
||||
args: []string{"-q", "-per-page=1"},
|
||||
name: "plaintext/terse/paginated",
|
||||
args: []string{"-out=terse", "-per-page=1"},
|
||||
expectStdOut: "a/b/c",
|
||||
expectStdErrPrefix: "Next page token",
|
||||
},
|
||||
{
|
||||
name: "plaintext/quiet/prefix/wildcard ns",
|
||||
args: []string{"-q", "-namespace", "*", "a/b/c/d"},
|
||||
name: "plaintext/terse/prefix/wildcard ns",
|
||||
args: []string{"-out=terse", "-namespace", "*", "a/b/c/d"},
|
||||
expectStdOut: strings.Join(variables.HavingPrefix("a/b/c/d").Strings(), "\n"),
|
||||
},
|
||||
{
|
||||
name: "plaintext/quiet/paginated/prefix/wildcard ns",
|
||||
args: []string{"-q", "-per-page=1", "-namespace", "*", "a/b/c/d"},
|
||||
name: "plaintext/terse/paginated/prefix/wildcard ns",
|
||||
args: []string{"-out=terse", "-per-page=1", "-namespace", "*", "a/b/c/d"},
|
||||
expectStdOut: variables.HavingPrefix("a/b/c/d").Strings()[0],
|
||||
expectStdErrPrefix: "Next page token",
|
||||
},
|
||||
{
|
||||
name: "json/not found",
|
||||
args: []string{"-json", "does/not/exist"},
|
||||
args: []string{"-out=json", "does/not/exist"},
|
||||
jsonTest: &testVarListJSONTest{
|
||||
jsonDest: &SVMSlice{},
|
||||
expectFns: []testVarListJSONTestExpectFn{
|
||||
|
@ -210,7 +221,7 @@ func TestVarListCommand_Online(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "json/prefix",
|
||||
args: []string{"-json", "a"},
|
||||
args: []string{"-out=json", "a"},
|
||||
jsonTest: &testVarListJSONTest{
|
||||
jsonDest: &SVMSlice{},
|
||||
expectFns: []testVarListJSONTestExpectFn{
|
||||
|
@ -220,7 +231,7 @@ func TestVarListCommand_Online(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "json/paginated",
|
||||
args: []string{"-json", "-per-page", "1"},
|
||||
args: []string{"-out=json", "-per-page", "1"},
|
||||
jsonTest: &testVarListJSONTest{
|
||||
jsonDest: &PaginatedSVMSlice{},
|
||||
expectFns: []testVarListJSONTestExpectFn{
|
||||
|
@ -228,68 +239,32 @@ func TestVarListCommand_Online(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "json/quiet",
|
||||
args: []string{"-q", "-json"},
|
||||
expectStdOut: toJSON(variables.HavingNamespace(api.DefaultNamespace).Strings()),
|
||||
},
|
||||
{
|
||||
name: "json/quiet/paginated",
|
||||
args: []string{"-q", "-json", "-per-page", "1"},
|
||||
jsonTest: &testVarListJSONTest{
|
||||
jsonDest: &PaginatedSVQuietSlice{},
|
||||
expectFns: []testVarListJSONTestExpectFn{
|
||||
hasLength(t, 1),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "json/quiet/wildcard-ns",
|
||||
args: []string{"-q", "-json", "-namespace", "*"},
|
||||
jsonTest: &testVarListJSONTest{
|
||||
jsonDest: &SVMSlice{},
|
||||
expectFns: []testVarListJSONTestExpectFn{
|
||||
hasLength(t, variables.Len()),
|
||||
pathsEqual(t, variables),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "json/quiet/paginated/wildcard-ns",
|
||||
args: []string{"-q", "-json", "-per-page=1", "-namespace", "*"},
|
||||
jsonTest: &testVarListJSONTest{
|
||||
jsonDest: &PaginatedSVMSlice{},
|
||||
expectFns: []testVarListJSONTestExpectFn{
|
||||
hasLength(t, 1),
|
||||
pathsEqual(t, SVMSlice{variables[0]}),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "template/not found",
|
||||
args: []string{"-t", testTmpl, "does/not/exist"},
|
||||
args: []string{"-out=go-template", "-template", testTmpl, "does/not/exist"},
|
||||
expectStdOut: "",
|
||||
},
|
||||
{
|
||||
name: "template/prefix",
|
||||
args: []string{"-t", testTmpl, "a/b/c/d"},
|
||||
args: []string{"-out=go-template", "-template", testTmpl, "a/b/c/d"},
|
||||
expectStdOut: "default\ta/b/c/d",
|
||||
},
|
||||
{
|
||||
name: "template/filter",
|
||||
args: []string{"-t", testTmpl, "-filter", "VariableMetadata.Path == \"a/b/c\""},
|
||||
args: []string{"-out=go-template", "-template", testTmpl, "-filter", "VariableMetadata.Path == \"a/b/c\""},
|
||||
expectStdOut: "default\ta/b/c",
|
||||
expectStdErrPrefix: msgWarnFilterPerformance,
|
||||
},
|
||||
{
|
||||
name: "template/paginated",
|
||||
args: []string{"-t", testTmpl, "-per-page=1"},
|
||||
args: []string{"-out=go-template", "-template", testTmpl, "-per-page=1"},
|
||||
expectStdOut: "default\ta/b/c",
|
||||
expectStdErrPrefix: "Next page token",
|
||||
},
|
||||
{
|
||||
name: "template/prefix/wildcard namespace",
|
||||
args: []string{"-namespace", "*", "-t", testTmpl, "a/b/c/d"},
|
||||
args: []string{"-namespace", "*", "-out=go-template", "-template", testTmpl, "a/b/c/d"},
|
||||
expectStdOut: "default\ta/b/c/d•ns1\ta/b/c/d",
|
||||
},
|
||||
}
|
||||
|
@ -385,11 +360,14 @@ func setupTestVariables(c *api.Client, nsList, pathList []string) SVMSlice {
|
|||
return out
|
||||
}
|
||||
|
||||
func setupTestVariable(c *api.Client, ns, p string, out *SVMSlice) {
|
||||
testVar := &api.Variable{Items: map[string]string{"k": "v"}}
|
||||
c.Raw().Write("/v1/var/"+p, testVar, nil, &api.WriteOptions{Namespace: ns})
|
||||
v, _, _ := c.Variables().Read(p, &api.QueryOptions{Namespace: ns})
|
||||
func setupTestVariable(c *api.Client, ns, p string, out *SVMSlice) error {
|
||||
testVar := &api.Variable{
|
||||
Namespace: ns,
|
||||
Path: p,
|
||||
Items: map[string]string{"k": "v"}}
|
||||
v, _, err := c.Variables().Create(testVar, &api.WriteOptions{Namespace: ns})
|
||||
*out = append(*out, *v.Metadata())
|
||||
return err
|
||||
}
|
||||
|
||||
type NSPather interface {
|
||||
|
|
Loading…
Reference in New Issue