Repository: digitalocean/doctl
Pull request #1547 (2024-07-11 21:15:37)

doctl auth list: respect json output flag
addresses https://github.com/digitalocean/doctl/issues/1543

Updates the `doctl auth list` command to respect the -o json flag.

Example output:

```
$ go run cmd/doctl/main.go auth list -o json

[
  {
    "name": "default",
    "current": true
  },
  {
    "name": "personal",
    "current": false
  },
  {
    "name": "work",
    "current": false
  }
]
```



Patch:
diff --git a/commands/auth.go b/commands/auth.go
index 2c9ef278..b1f251b1 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)")


Test patch:
diff --git a/commands/auth_test.go b/commands/auth_test.go
index 9d83cd39..058d5ace 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