Update flags to align with other var commands. (#14550)

This commit is contained in:
Charlie Voiselle 2022-09-12 15:26:12 -04:00 committed by GitHub
parent add8383c77
commit 4c9554f87c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 104 additions and 124 deletions

View File

@ -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 = `

View File

@ -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
}
pList := make([]*pTuple, len(vars))
for i, sv := range vars {
pList[i] = &pTuple{sv.Namespace, sv.Path}
}
return pList
func (c *VarListCommand) validateOutputFlag() error {
if c.outFmt != "go-template" && c.tmpl != "" {
return errors.New(errUnexpectedTemplate)
}
// 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
switch c.outFmt {
case "json", "terse", "table":
return nil
case "go-template":
if c.tmpl == "" {
return errors.New(errMissingTemplate)
}
return nil
default:
return errors.New(errInvalidListOutFormat)
}
return pList
}

View File

@ -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 {