8000 Fixes #4714. Added output option to namespace list command to specify json or yaml by ifbyol · Pull Request #4722 · okteto/okteto · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fixes #4714. Added output option to namespace list command to specify json or yaml #4722

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

Merged
merged 4 commits into from
May 28, 2025
Merged
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
6 changes: 3 additions & 3 deletions cmd/build/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func Build(ctx context.Context, ioCtrl *io.Controller, at, insights buildTracker
// The context must be loaded before reading manifest. Otherwise,
// secrets will not be resolved when GetManifest is called and
// the manifest will load empty values.
oktetoContext, err := getOktetoContext(ctx, options)
oktetoContext, err := getOktetoContext(ctx, options, ioCtrl)
if err != nil {
return err
}
Expand Down Expand Up @@ -224,7 +224,7 @@ func validateDockerfile(file string) error {
return err
}

func getOktetoContext(ctx context.Context, options *types.BuildOptions) (*okteto.ContextStateless, error) {
func getOktetoContext(ctx context.Context, options *types.BuildOptions, ioCtrl *io.Controller) (*okteto.ContextStateless, error) {
ctxOpts := &contextCMD.Options{
Context: options.K8sContext,
Namespace: options.Namespace,
Expand All @@ -248,7 +248,7 @@ func getOktetoContext(ctx context.Context, options *types.BuildOptions) (*okteto
return nil, err
}
if create {
if err := namespace.NewCommandStateless(c).Create(ctx, &namespace.CreateOptions{Namespace: ctxOpts.Namespace}); err != nil {
if err := namespace.NewCommandStateless(c, ioCtrl).Create(ctx, &namespace.CreateOptions{Namespace: ctxOpts.Namespace}); err != nil {
return nil, err
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ $ okteto deploy --no-build=true`,
return err
}
if create {
nsCmd, err := namespace.NewCommand()
nsCmd, err := namespace.NewCommand(ioCtrl)
if err != nil {
return err
}
Expand Down
5 changes: 3 additions & 2 deletions cmd/namespace/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/okteto/okteto/pkg/analytics"
oktetoErrors "github.com/okteto/okteto/pkg/errors"
oktetoLog "github.com/okteto/okteto/pkg/log"
"github.com/okteto/okteto/pkg/log/io"
"github.com/okteto/okteto/pkg/okteto"
"github.com/spf13/cobra"
)
Expand All @@ -36,7 +37,7 @@ type CreateOptions struct {
}

// Create creates a namespace
func Create(ctx context.Context) *cobra.Command {
func Create(ctx context.Context, ioCtrl *io.Controller) *cobra.Command {
options := &CreateOptions{
Show: false,
}
Expand All @@ -53,7 +54,7 @@ func Create(ctx context.Context) *cobra.Command {
return oktetoErrors.ErrContextIsNotOktetoCluster
}

nsCmd, err := NewCommand()
nsCmd, err := NewCommand(ioCtrl)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/namespace/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
)

// Delete deletes a namespace
func Delete(ctx context.Context, k8sLogger *io.K8sLogger) *cobra.Command {
func Delete(ctx context.Context, k8sLogger *io.K8sLogger, ioCtrl *io.Controller) *cobra.Command {
cmd := &cobra.Command{
Use: "delete <name>",
Short: "Delete an Okteto Namespace",
Expand All @@ -54,7 +54,7 @@ func Delete(ctx context.Context, k8sLogger *io.K8sLogger) *cobra.Command {
return oktetoErrors.ErrContextIsNotOktetoCluster
}

nsCmd, err := NewCommand()
nsCmd, err := NewCommand(ioCtrl)
if err != nil {
return err
}
Expand Down
106 changes: 93 additions & 13 deletions cmd/namespace/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,40 @@ package namespace

import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"text/tabwriter"

contextCMD "github.com/okteto/okteto/cmd/context"
"github.com/okteto/okteto/cmd/utils"
oktetoErrors "github.com/okteto/okteto/pkg/errors"
"github.com/okteto/okteto/pkg/log/io"
"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{
func List(ctx context.Context, ioCtrl *io.Controller) *cobra.Command {
flags := &listFlags{}
cmd := &cobra.Command{
Use: "list",
Short: "List your Okteto Namespaces",
Aliases: []string{"ls"},
Expand All @@ -42,31 +62,91 @@ func List(ctx context.Context) *cobra.Command {
return oktetoErrors.ErrContextIsNotOktetoCluster
}

nsCmd, err := NewCommand()
nsCmd, err := NewCommand(ioCtrl)
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 nc.displayListNamespaces(namespaces, output)
}

func (nc *Command) displayListNamespaces(namespaces []namespaceOutput, output string) error {
switch output {
case "json":
if len(namespaces) == 0 {
nc.ioCtrl.Out().Println("[]")
return nil
}
b, err := json.MarshalIndent(namespaces, "", " ")
if err != nil {
return err
}
fmt.Fprintf(w, "%s\t%v\n", space.ID, space.Status)
nc.ioCtrl.Out().Println(string(b))
case "yaml":
b, err := yaml.Marshal(namespaces)
if err != nil {
return err
}
nc.ioCtrl.Out().Print(string(b))
default:
if len(namespaces) == 0 {
nc.ioCtrl.Out().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
}
110 changes: 109 additions & 1 deletion cmd/namespace/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ package namespace
import (
"context"
"fmt"
"io"
"os"
"testing"

"github.com/okteto/okteto/internal/test/client"
oktetoio "github.com/okteto/okteto/pkg/log/io"
"github.com/okteto/okteto/pkg/okteto"
"github.com/okteto/okteto/pkg/types"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -73,7 +76,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 +86,108 @@ 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
nsCmd := &Command{
ioCtrl: oktetoio.NewIOController(),
}

err := nsCmd.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))
})
}
}
Loading
0