diff --git a/.changelog/16442.txt b/.changelog/16442.txt new file mode 100644 index 000000000..77442c544 --- /dev/null +++ b/.changelog/16442.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Added `-json` and `-t` flag to `namespace status` command +``` diff --git a/command/namespace_status.go b/command/namespace_status.go index d9d59d76d..3c3d3a857 100644 --- a/command/namespace_status.go +++ b/command/namespace_status.go @@ -24,13 +24,26 @@ Usage: nomad namespace status [options] General Options: - ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + ` + +Status Specific Options: + + -json + Output the latest namespace status information in a JSON format. + + -t + Format and display namespace status information using a Go template. +` return strings.TrimSpace(helpText) } func (c *NamespaceStatusCommand) AutocompleteFlags() complete.Flags { - return c.Meta.AutocompleteFlags(FlagSetClient) + return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), + complete.Flags{ + "-json": complete.PredictNothing, + "-t": complete.PredictAnything, + }) } func (c *NamespaceStatusCommand) AutocompleteArgs() complete.Predictor { @@ -44,7 +57,12 @@ func (c *NamespaceStatusCommand) Synopsis() string { func (c *NamespaceStatusCommand) Name() string { return "namespace status" } func (c *NamespaceStatusCommand) Run(args []string) int { + var json bool + var tmpl string + flags := c.Meta.FlagSet(c.Name(), FlagSetClient) + flags.BoolVar(&json, "json", false, "") + flags.StringVar(&tmpl, "t", "", "") flags.Usage = func() { c.Ui.Output(c.Help()) } if err := flags.Parse(args); err != nil { @@ -80,6 +98,17 @@ func (c *NamespaceStatusCommand) Run(args []string) int { return 1 } + if json || len(tmpl) > 0 { + out, err := Format(json, tmpl, ns) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + + c.Ui.Output(out) + return 0 + } + c.Ui.Output(formatNamespaceBasics(ns)) if len(ns.Meta) > 0 { diff --git a/command/namespace_status_test.go b/command/namespace_status_test.go index 084ef3233..e5f248530 100644 --- a/command/namespace_status_test.go +++ b/command/namespace_status_test.go @@ -1,6 +1,7 @@ package command import ( + "encoding/json" "strings" "testing" @@ -8,6 +9,7 @@ import ( "github.com/hashicorp/nomad/ci" "github.com/mitchellh/cli" "github.com/posener/complete" + "github.com/shoenig/test/must" "github.com/stretchr/testify/assert" ) @@ -22,24 +24,21 @@ func TestNamespaceStatusCommand_Fails(t *testing.T) { cmd := &NamespaceStatusCommand{Meta: Meta{Ui: ui}} // Fails on misuse - if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 { - t.Fatalf("expected exit code 1, got: %d", code) - } - if out := ui.ErrorWriter.String(); !strings.Contains(out, commandErrorText(cmd)) { - t.Fatalf("expected help output, got: %s", out) - } + code := cmd.Run([]string{"some", "bad", "args"}) + must.One(t, code) + + must.StrContains(t, ui.ErrorWriter.String(), commandErrorText(cmd)) + ui.ErrorWriter.Reset() - if code := cmd.Run([]string{"-address=nope", "foo"}); code != 1 { - t.Fatalf("expected exit code 1, got: %d", code) - } - if out := ui.ErrorWriter.String(); !strings.Contains(out, "retrieving namespace") { - t.Fatalf("connection error, got: %s", out) - } + code = cmd.Run([]string{"-address=nope", "foo"}) + must.One(t, code) + + must.StrContains(t, ui.ErrorWriter.String(), "retrieving namespace") ui.ErrorWriter.Reset() } -func TestNamespaceStatusCommand_Good(t *testing.T) { +func TestNamespaceStatusCommand_Run(t *testing.T) { ci.Parallel(t) // Create a server @@ -54,21 +53,42 @@ func TestNamespaceStatusCommand_Good(t *testing.T) { Name: "foo", } _, err := client.Namespaces().Register(ns, nil) - assert.Nil(t, err) + must.NoError(t, err) // Check status on namespace - if code := cmd.Run([]string{"-address=" + url, ns.Name}); code != 0 { - t.Fatalf("expected exit 0, got: %d; %v", code, ui.ErrorWriter.String()) - } + code := cmd.Run([]string{"-address=" + url, ns.Name}) + must.Zero(t, code) // Check for basic spec out := ui.OutputWriter.String() if !strings.Contains(out, "= foo") { t.Fatalf("expected quota, got: %s", out) } + + ui.OutputWriter.Reset() + + // List json + code = cmd.Run([]string{"-address=" + url, "-json", ns.Name}) + must.Zero(t, code) + + outJson := api.Namespace{} + err = json.Unmarshal(ui.OutputWriter.Bytes(), &outJson) + must.NoError(t, err) + + ui.OutputWriter.Reset() + + // Go template to format the output + code = cmd.Run([]string{"-address=" + url, "-t", "{{.Name}}", ns.Name}) + must.Zero(t, code) + + out = ui.OutputWriter.String() + must.StrContains(t, out, "foo") + + ui.OutputWriter.Reset() + ui.ErrorWriter.Reset() } -func TestNamespaceStatusCommand_Good_Quota(t *testing.T) { +func TestNamespaceStatusCommand_Run_Quota(t *testing.T) { ci.Parallel(t) // Create a server @@ -96,25 +116,21 @@ func TestNamespaceStatusCommand_Good_Quota(t *testing.T) { assert.Nil(t, err) // Check status on namespace - if code := cmd.Run([]string{"-address=" + url, ns.Name}); code != 0 { - t.Fatalf("expected exit 0, got: %d; %v", code, ui.ErrorWriter.String()) - } + code := cmd.Run([]string{"-address=" + url, ns.Name}) + must.Zero(t, code) + + out := ui.OutputWriter.String() // Check for basic spec - out := ui.OutputWriter.String() - if !strings.Contains(out, "= foo") { - t.Fatalf("expected quota, got: %s", out) - } + must.StrContains(t, out, "= foo") // Check for usage - if !strings.Contains(out, "0 / 100") { - t.Fatalf("expected quota, got: %s", out) - } + must.StrContains(t, out, "0 / 100") + } func TestNamespaceStatusCommand_AutocompleteArgs(t *testing.T) { ci.Parallel(t) - assert := assert.New(t) srv, client, url := testServer(t, true, nil) defer srv.Shutdown() @@ -127,14 +143,14 @@ func TestNamespaceStatusCommand_AutocompleteArgs(t *testing.T) { Name: "foo", } _, err := client.Namespaces().Register(ns, nil) - assert.Nil(err) + must.NoError(t, err) args := complete.Args{Last: "f"} predictor := cmd.AutocompleteArgs() res := predictor.Predict(args) - assert.Equal(1, len(res)) - assert.Equal(ns.Name, res[0]) + must.One(t, len(res)) + must.StrContains(t, ns.Name, res[0]) } // This test should demonstrate the behavior of a namespace @@ -154,27 +170,23 @@ func TestNamespaceStatusCommand_NamespaceMatchesPrefix(t *testing.T) { // Create a namespace that uses foo as a prefix ns := &api.Namespace{Name: "fooBar"} _, err := client.Namespaces().Register(ns, nil) - assert.Nil(t, err) + must.NoError(t, err) // Create a foo namespace ns2 := &api.Namespace{Name: "foo"} _, err = client.Namespaces().Register(ns2, nil) - assert.Nil(t, err) + must.NoError(t, err) // Adding a NS after to prevent sort from creating // false successes ns = &api.Namespace{Name: "fooBaz"} _, err = client.Namespaces().Register(ns, nil) - assert.Nil(t, err) + must.NoError(t, err) // Check status on namespace code := cmd.Run([]string{"-address=" + url, ns2.Name}) - if code != 0 { - t.Fatalf("expected exit 0, got: %d; %v", code, ui.ErrorWriter.String()) - } + must.Zero(t, code) + // Check to ensure we got the proper foo - out := ui.OutputWriter.String() - if !strings.Contains(out, "= foo\n") { - t.Fatalf("expected namespace foo, got: %s", out) - } + must.StrContains(t, ui.OutputWriter.String(), "= foo\n") } diff --git a/website/content/docs/commands/namespace/status.mdx b/website/content/docs/commands/namespace/status.mdx index 5a51e3ac6..2c0fc2cd2 100644 --- a/website/content/docs/commands/namespace/status.mdx +++ b/website/content/docs/commands/namespace/status.mdx @@ -27,6 +27,11 @@ that has a capability associated with the namespace. @include 'general_options_no_namespace.mdx' +## Status Options + +- `-json` : Output the namespace status in its JSON format. +- `-t` : Format and display the namespace status using a Go template. + ## Examples View the status of a namespace: @@ -46,3 +51,24 @@ Quota Limits Region CPU Usage Memory Usage global 500 / 2500 256 / 2000 ``` + +The `-json` flag can be used to get the namespace status in json format: + +```shell-session +$ nomad namespace status -json default +{ + "Capabilities": null, + "CreateIndex": 1, + "Description": "Default shared namespace", + "Meta": null, + "ModifyIndex": 1, + "Name": "default", + "Quota": "" +} + +Or use the `-t` flag to format and display the status using a Go template: + +```shell-session +$ nomad namespace status -t {{.Description}} default +Default shared namespace +``` \ No newline at end of file