Skip to content

Commit

Permalink
rpk: add rpk cloud namespace
Browse files Browse the repository at this point in the history
This first commit introduces:
- Create
- List
- Delete
  • Loading branch information
r-vasquez committed Feb 23, 2024
1 parent 0a13960 commit e777839
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/go/rpk/pkg/cli/cloud/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package cloud
import (
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/cloud/auth"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/cloud/byoc"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/cloud/namespace"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
"github.com/spf13/afero"
"github.com/spf13/cobra"
Expand All @@ -26,6 +27,7 @@ func NewCommand(fs afero.Fs, p *config.Params, execFn func(string, []string) err
cmd.AddCommand(
auth.NewCommand(fs, p),
byoc.NewCommand(fs, p, execFn),
namespace.NewCommand(fs, p),
newLoginCommand(fs, p),
newLogoutCommand(fs, p),
)
Expand Down
81 changes: 81 additions & 0 deletions src/go/rpk/pkg/cli/cloud/namespace/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2024 Redpanda Data, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.md
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0

package namespace

import (
"fmt"
"os"

"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth/providers/auth0"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/out"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/publicapi"
controlplanev1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

type createResponse struct {
Name string `json:"name" yaml:"name"`
ID string `json:"id" yaml:"id"`
Error string `json:"error,omitempty" yaml:"error,omitempty"`
}

func createCommand(fs afero.Fs, p *config.Params) *cobra.Command {
return &cobra.Command{
Use: "create [NAMES...]",
Args: cobra.MinimumNArgs(1),
Short: "Create a Namespace in Redpanda Cloud",
Run: func(cmd *cobra.Command, args []string) {
f := p.Formatter
if h, ok := f.Help([]createResponse{}); ok {
out.Exit(h)
}
cfg, err := p.Load(fs)
out.MaybeDie(err, "unable to load config: %v", err)
authToken, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false)
out.MaybeDie(err, "unable to authenticate with Redpanda Cloud: %v", err)
cl, err := publicapi.NewClientSet(cmd.Context(), cfg.DevOverrides().PublicAPIURL, authToken)
out.MaybeDie(err, "unable to create the public api client: %v", err)

var (
res []createResponse
exit1 bool
)
for _, name := range args {
n, err := cl.Namespace.CreateNamespace(cmd.Context(), &controlplanev1beta1.CreateNamespaceRequest{
Namespace: &controlplanev1beta1.Namespace{
Name: name,
},
})
if err != nil {
res = append(res, createResponse{Name: name, Error: err.Error()})
exit1 = true
} else {
res = append(res, createResponse{Name: n.Name, ID: n.Id})
}
}
if exit1 {
defer os.Exit(1)
}
if isText, _, s, err := f.Format(res); !isText {
out.MaybeDie(err, "unable to print in the required format %q: %v", f.Kind, err)
fmt.Println(s)
return
}
tw := out.NewTable("name", "id", "error")
defer tw.Flush()
for _, r := range res {
tw.PrintStructFields(r)
}
},
}
}
76 changes: 76 additions & 0 deletions src/go/rpk/pkg/cli/cloud/namespace/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package namespace

import (
"fmt"

"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth/providers/auth0"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/out"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/publicapi"
controlplanev1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

type deleteResponse struct {
Name string `json:"name" yaml:"name"`
ID string `json:"id" yaml:"id"`
}

func deleteCommand(fs afero.Fs, p *config.Params) *cobra.Command {
var noConfirm bool
cmd := &cobra.Command{
Use: "delete [NAME]",
Args: cobra.ExactArgs(1),
Short: "Delete Namespaces in Redpanda Cloud",
Run: func(cmd *cobra.Command, args []string) {
f := p.Formatter
if h, ok := f.Help(deleteResponse{}); ok {
out.Exit(h)
}
cfg, err := p.Load(fs)
out.MaybeDie(err, "unable to load config: %v", err)
authToken, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false)
out.MaybeDie(err, "unable to authenticate with Redpanda Cloud: %v", err)
cl, err := publicapi.NewClientSet(cmd.Context(), cfg.DevOverrides().PublicAPIURL, authToken)
out.MaybeDie(err, "unable to create the public api client: %v", err)

name := args[0]
listed, err := cl.Namespace.ListNamespaces(cmd.Context(), &controlplanev1beta1.ListNamespacesRequest{
Filter: &controlplanev1beta1.ListNamespacesRequest_Filter{Name: name},
})
out.MaybeDie(err, "unable to find namespace %q: %v", name, err)
if len(listed.Namespaces) == 0 {
out.Die("unable to find namespace %q", name)
}
if len(listed.Namespaces) > 1 {
// This is currently not possible, the filter is an exact
// filter. This is just being cautious.
out.Die("multiple namespaces were found for %q, please provide an exact match", name)
}
namespace := listed.Namespaces[0]
if !noConfirm {
confirmed, err := out.Confirm("Confirm deletion of namespace %q with ID %q?", namespace.Name, namespace.Id)
out.MaybeDie(err, "unable to confirm deletion: %v", err)
if !confirmed {
out.Exit("Deletion canceled.")
}
}

_, err = cl.Namespace.DeleteNamespace(cmd.Context(), &controlplanev1beta1.DeleteNamespaceRequest{Id: namespace.Id})
out.MaybeDie(err, "unable to delete namespace %q: %v", name, err)
res := deleteResponse{namespace.Name, namespace.Id}
if isText, _, s, err := f.Format(res); !isText {
out.MaybeDie(err, "unable to print in the required format %q: %v", f.Kind, err)
fmt.Println(s)
return
}
tw := out.NewTable("name", "id")
defer tw.Flush()
tw.PrintStructFields(res)
},
}
cmd.Flags().BoolVar(&noConfirm, "no-confirm", false, "Disable confirmation prompt")
return cmd
}
61 changes: 61 additions & 0 deletions src/go/rpk/pkg/cli/cloud/namespace/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package namespace

import (
"fmt"
"sort"
"strings"

"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth/providers/auth0"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/out"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/publicapi"
controlplanev1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

type listResponse struct {
Name string `json:"name" yaml:"name"`
ID string `json:"id" yaml:"id"`
}

func listCommand(fs afero.Fs, p *config.Params) *cobra.Command {
return &cobra.Command{
Use: "list",
Args: cobra.ExactArgs(0),
Short: "List Namespaces in Redpanda Cloud",
Run: func(cmd *cobra.Command, _ []string) {
f := p.Formatter
if h, ok := f.Help([]listResponse{}); ok {
out.Exit(h)
}
cfg, err := p.Load(fs)
out.MaybeDie(err, "unable to load config: %v", err)
authToken, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false)
out.MaybeDie(err, "unable to authenticate with Redpanda Cloud: %v", err)
cl, err := publicapi.NewClientSet(cmd.Context(), cfg.DevOverrides().PublicAPIURL, authToken)
out.MaybeDie(err, "unable to create the public api client: %v", err)

listed, err := cl.Namespace.ListNamespaces(cmd.Context(), &controlplanev1beta1.ListNamespacesRequest{})
out.MaybeDie(err, "unable to list namespaces: %v", err)
var res []listResponse
for _, n := range listed.Namespaces {
if n != nil {
res = append(res, listResponse{n.Name, n.Id})
}
}
sort.Slice(res, func(i, j int) bool { return strings.ToLower(res[i].Name) < strings.ToLower(res[j].Name) })
if isText, _, s, err := f.Format(res); !isText {
out.MaybeDie(err, "unable to print in the required format %q: %v", f.Kind, err)
fmt.Println(s)
return
}
tw := out.NewTable("name", "id")
defer tw.Flush()
for _, r := range res {
tw.PrintStructFields(r)
}
},
}
}
34 changes: 34 additions & 0 deletions src/go/rpk/pkg/cli/cloud/namespace/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 Redpanda Data, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.md
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0

package namespace

import (
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

func NewCommand(fs afero.Fs, p *config.Params) *cobra.Command {
cmd := &cobra.Command{
Use: "namespace",
Aliases: []string{"ns"},
SuggestFor: []string{"namespaces"},
Args: cobra.ExactArgs(0),
Short: "Interact with Namespaces in Redpanda Cloud",
}
cmd.AddCommand(
createCommand(fs, p),
deleteCommand(fs, p),
listCommand(fs, p),
)
p.InstallCloudFlags(cmd)
p.InstallFormatFlag(cmd)
return cmd
}

0 comments on commit e777839

Please sign in to comment.