diff --git a/commands/auth.go b/commands/auth.go index 2c9ef2789..b1f251b11 100644 --- a/commands/auth.go +++ b/commands/auth.go @@ -14,6 +14,7 @@ limitations under the License. package commands import ( + "encoding/json" "errors" "fmt" "io" @@ -218,11 +219,16 @@ func RunAuthList(c *CmdConfig) error { } contexts := viper.GetStringMap("auth-contexts") - displayAuthContexts(c.Out, context, contexts) + if viper.GetString("output") == "json" { + displayAuthContextsJSON(c.Out, context, contexts) + } else { + displayAuthContexts(c.Out, context, contexts) + } + return nil } -func displayAuthContexts(out io.Writer, currentContext string, contexts map[string]any) { +func ensureDefaultContextAndKeysOrder(contexts map[string]any) []string { // Because the default context isn't present on the auth-contexts field, // we add it manually so that it's always included in the output, and so // we can check if it's the current context. @@ -236,6 +242,31 @@ func displayAuthContexts(out io.Writer, currentContext string, contexts map[stri } sort.Strings(keys) + return keys +} + +func displayAuthContextsJSON(out io.Writer, currentContext string, contexts map[string]any) { + type contextJSON struct { + Name string `json:"name"` + Current bool `json:"current"` + } + + var contextsJSON []contextJSON + + keys := ensureDefaultContextAndKeysOrder(contexts) + for _, ctx := range keys { + contextsJSON = append(contextsJSON, contextJSON{ + Name: ctx, + Current: ctx == currentContext, + }) + } + + jsonData, _ := json.MarshalIndent(contextsJSON, "", " ") + fmt.Fprintln(out, string(jsonData)) +} + +func displayAuthContexts(out io.Writer, currentContext string, contexts map[string]any) { + keys := ensureDefaultContextAndKeysOrder(contexts) for _, ctx := range keys { if ctx == currentContext { fmt.Fprintln(out, ctx, "(current)") diff --git a/commands/auth_test.go b/commands/auth_test.go index 9d83cd39a..058d5acee 100644 --- a/commands/auth_test.go +++ b/commands/auth_test.go @@ -172,11 +172,12 @@ func TestAuthList(t *testing.T) { func Test_displayAuthContexts(t *testing.T) { testCases := []struct { - Name string - Out *bytes.Buffer - Context string - Contexts map[string]any - Expected string + Name string + Out *bytes.Buffer + Context string + Contexts map[string]any + Expected string + ExpectedJSON string }{ { Name: "default context only", @@ -227,6 +228,62 @@ func Test_displayAuthContexts(t *testing.T) { } } +func Test_displayAuthContextsJSON(t *testing.T) { + testCases := []struct { + Name string + Out *bytes.Buffer + Context string + Contexts map[string]any + ExpectedJSON string + }{ + { + Name: "default context only", + Out: &bytes.Buffer{}, + Context: doctl.ArgDefaultContext, + Contexts: map[string]any{ + doctl.ArgDefaultContext: true, + }, + ExpectedJSON: "[\n {\n \"name\": \"default\",\n \"current\": true\n }\n]\n", + }, + { + Name: "default context and additional context", + Out: &bytes.Buffer{}, + Context: doctl.ArgDefaultContext, + Contexts: map[string]any{ + doctl.ArgDefaultContext: true, + "test": true, + }, + ExpectedJSON: "[\n {\n \"name\": \"default\",\n \"current\": true\n },\n {\n \"name\": \"test\",\n \"current\": false\n }\n]\n", + }, + { + Name: "default context and additional context set to additional context", + Out: &bytes.Buffer{}, + Context: "test", + Contexts: map[string]any{ + doctl.ArgDefaultContext: true, + "test": true, + }, + ExpectedJSON: "[\n {\n \"name\": \"default\",\n \"current\": false\n },\n {\n \"name\": \"test\",\n \"current\": true\n }\n]\n", + }, + { + Name: "unset context", + Out: &bytes.Buffer{}, + Context: "missing", + Contexts: map[string]any{ + doctl.ArgDefaultContext: true, + "test": true, + }, + ExpectedJSON: "[\n {\n \"name\": \"default\",\n \"current\": false\n },\n {\n \"name\": \"test\",\n \"current\": false\n }\n]\n", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + displayAuthContextsJSON(tc.Out, tc.Context, tc.Contexts) + assert.Equal(t, tc.ExpectedJSON, tc.Out.String()) + }) + } +} func TestTokenInputValidator(t *testing.T) { tests := []struct { name string