8000 Add JSON/YAML output options to namespace list by codyjlandstrom · Pull Request #4719 · okteto/okteto · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add JSON/YAML output options to namespace list #4719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 90 additions & 11 deletions cmd/namespace/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ package namespace

import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"text/tabwriter"
Expand All @@ -23,12 +25,29 @@ import (
"github.com/okteto/okteto/cmd/utils"
oktetoErrors "github.com/okteto/okteto/pkg/errors"
"github.com/okteto/okteto/pkg/okteto"
"github.com/okteto/okteto/pkg/types"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)

var (
errInvalidOutput = errors.New("output format is not accepted. Value must be one of: ['json', 'yaml']")
)

type listFlags struct {
output string
}

type namespaceOutput struct {
Namespace string `json:"namespace" yaml:"namespace"`
Status string `json:"status" yaml:"status"`
Current bool `json:"current" yaml:"current"`
}

// List all namespace in current context
func List(ctx context.Context) *cobra.Command {
return &cobra.Command{
flags := &listFlags{}
cmd := &cobra.Command{
Use: "list",
Short: "List your Okteto Namespaces",
Aliases: []string{"ls"},
Expand All @@ -46,27 +65,87 @@ func List(ctx context.Context) *cobra.Command {
if err != nil {
return err
}
err = nsCmd.executeListNamespaces(ctx)
err = nsCmd.executeListNamespaces(ctx, flags.output)
return err
},
Args: utils.NoArgsAccepted(""),
}

cmd.Flags().StringVarP(&flags.output, "output", "o", "", "output format. One of: ['json', 'yaml']")
return cmd
}

func (nc *Command) executeListNamespaces(ctx context.Context) error {
func (nc *Command) executeListNamespaces(ctx context.Context, output string) error {
if err := validateNamespaceListOutput(output); err != nil {
return err
}

spaces, err := nc.okClient.Namespaces().List(ctx)
if err != nil {
return fmt.Errorf("failed to get namespaces: %w", err)
}
w := tabwriter.NewWriter(os.Stdout, 1, 1, 2, ' ', 0)
fmt.Fprintf(w, "Namespace\tStatus\n")
for _, space := range spaces {
if space.ID == okteto.GetContext().Namespace {
space.ID += " *"

namespaces := getNamespaceOutput(spaces)
return displayListNamespaces(namespaces, output)
}

func displayListNamespaces(namespaces []namespaceOutput, output string) error {
switch output {
case "json":
if len(namespaces) == 0 {
fmt.Println("[]")
return nil
}
b, err := json.MarshalIndent(namespaces, "", " ")
if err != nil {
return err
}
fmt.Fprintf(w, "%s\t%v\n", space.ID, space.Status)
fmt.Println(string(b))
case "yaml":
b, err := yaml.Marshal(namespaces)
if err != nil {
return err
}
fmt.Print(string(b))
default:
if len(namespaces) == 0 {
fmt.Println("There are no namespaces")
return nil
}
w := tabwriter.NewWriter(os.Stdout, 1, 1, 2, ' ', 0)
fmt.Fprintf(w, "Namespace\tStatus\n")
for _, space := range namespaces {
id := space.Namespace
if id == okteto.GetContext().Namespace {
id += " *"
}
fmt.Fprintf(w, "%s\t%v\n", id, space.Status)
}
w.Flush()
}

w.Flush()
return nil
}

func validateNamespaceListOutput(output string) error {
switch output {
case "", "json", "yaml":
return nil
default:
return errInvalidOutput
}
}

// getNamespaceOutput transforms type.Namespace into namespaceOutput type
func getNamespaceOutput(namespaces []types.Namespace) []namespaceOutput {
var namespaceSlice []namespaceOutput
currentNamespace := okteto.GetContext().Namespace
for _, ns := range namespaces {
previewOutput := namespaceOutput{
Namespace: ns.ID,
Status: ns.Status,
Current: ns.ID == currentNamespace,
}
namespaceSlice = append(namespaceSlice, previewOutput)
}
return namespaceSlice
}
106 changes: 105 additions & 1 deletion cmd/namespace/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package namespace
import (
"context"
"fmt"
"io"
"os"
"testing"

"github.com/okteto/okteto/internal/test/client"
Expand Down Expand Up @@ -73,7 +75,7 @@ func Test_listNamespace(t *testing.T) {
okClient: fakeOktetoClient,
ctxCmd: newFakeContextCommand(fakeOktetoClient, usr),
}
err := nsCmd.executeListNamespaces(ctx)
err := nsCmd.executeListNamespaces(ctx, "")
if tt.err != nil {
assert.Error(t, err)
} else {
Expand All @@ -83,3 +85,105 @@ func Test_listNamespace(t *testing.T) {
})
}
}

func Test_validateNamespaceListOutput(t *testing.T) {
tests := []struct {
name string
output string
expectedErr error
}{
{
name: "yaml output",
output: "yaml",
},
{
name: "json output",
output: "json",
},
{
name: "invalid output",
output: "xml",
expectedErr: errInvalidOutput,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateNamespaceListOutput(tt.output)
assert.Equal(t, tt.expectedErr, err)
})
}
}

func Test_displayListNamespaces(t *testing.T) {
tests := []struct {
name string
format string
input []namespaceOutput
expectedOutput string
}{
{
name: "empty default",
format: "",
expectedOutput: "There are no namespaces\n",
},
{
name: "empty json",
format: "json",
expectedOutput: "[]\n",
},
{
name: "default format",
format: "",
input: []namespaceOutput{
{Namespace: "test", Status: "Active", Current: true},
{Namespace: "test2", Status: "Sleeping", Current: false},
},
expectedOutput: "Namespace Status\ntest * Active\ntest2 Sleeping\n",
},
{
name: "json format",
format: "json",
input: []namespaceOutput{
{Namespace: "test", Status: "Active", Current: true},
{Namespace: "test2", Status: "Sleeping", Current: false},
},
expectedOutput: "[\n {\n \"namespace\": \"test\",\n \"status\": \"Active\",\n \"current\": true\n },\n {\n \"namespace\": \"test2\",\n \"status\": \"Sleeping\",\n \"current\": false\n }\n]\n",
},
{
name: "yaml format",
format: "yaml",
input: []namespaceOutput{
{Namespace: "test", Status: "Active", Current: true},
{Namespace: "test2", Status: "Sleeping", Current: false},
},
expectedOutput: "- namespace: test\n status: Active\n current: true\n- namespace: test2\n status: Sleeping\n current: false\n",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
okteto.CurrentStore = &okteto.ContextStore{
Contexts: map[string]*okteto.Context{
"test": {
Namespace: "test",
IsOkteto: true,
},
},
CurrentContext: "test",
}
r, w, _ := os.Pipe()
initialStdout := os.Stdout
os.Stdout = w

err := displayListNamespaces(tt.input, tt.format)
assert.NoError(t, err)

w.Close()
out, _ := io.ReadAll(r)
os.Stdout = initialStdout

assert.Equal(t, tt.expectedOutput, string(out))
})
}
}
383B
0