Skip to content

Commit

Permalink
Merge pull request #388 from tnthornton/expand-ctp
Browse files Browse the repository at this point in the history
Introduce support for `up ctp` (create, get, list, delete) against an Upbound Space
  • Loading branch information
tnthornton committed Oct 8, 2023
2 parents c697d53 + 3bf6d4c commit 97f19fb
Show file tree
Hide file tree
Showing 13 changed files with 1,530 additions and 78 deletions.
58 changes: 51 additions & 7 deletions cmd/up/controlplane/controlplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ import (
"github.com/alecthomas/kong"
"github.com/posener/complete"

"github.com/upbound/up-sdk-go/service/configurations"
cp "github.com/upbound/up-sdk-go/service/controlplanes"
"github.com/upbound/up/cmd/up/controlplane/kubeconfig"
"github.com/upbound/up/cmd/up/controlplane/pkg"
"github.com/upbound/up/cmd/up/controlplane/pullsecret"
"github.com/upbound/up/internal/controlplane"
"github.com/upbound/up/internal/feature"
"github.com/upbound/up/internal/upbound"
"github.com/upbound/up/internal/upterm"
)

var (
cloudfieldNames = []string{"NAME", "ID", "STATUS", "CONFIGURATION", "CONFIGURATION STATUS"}
spacefieldNames = []string{"NAME", "ID", "STATUS", "MESSAGE", "CONNECTION NAME", "CONNECTION NAMESPACE"}
)

// BeforeReset is the first hook to run.
Expand All @@ -41,13 +47,8 @@ func (c *Cmd) AfterApply(kongCtx *kong.Context) error {
if err != nil {
return err
}
cfg, err := upCtx.BuildSDKConfig()
if err != nil {
return err
}
kongCtx.Bind(upCtx)
kongCtx.Bind(cp.NewClient(cfg))
kongCtx.Bind(configurations.NewClient(cfg))

return nil
}

Expand Down Expand Up @@ -103,3 +104,46 @@ type Cmd struct {
// Common Upbound API configuration
Flags upbound.Flags `embed:""`
}

func extractCloudFields(obj any) []string {
id, readyStatus := "unknown", "unknown"

resp, ok := obj.(*controlplane.Response)
if !ok {
return []string{"", id, readyStatus}
}

return []string{
resp.Name,
resp.ID,
resp.Status,
resp.Cfg,
resp.CfgStatus,
}
}

func extractSpaceFields(obj any) []string {
id, readyStatus := "unknown", "unknown"

resp, ok := obj.(*controlplane.Response)
if !ok {
return []string{"", id, readyStatus}
}

return []string{
resp.Name,
resp.ID,
resp.Status,
resp.Message,
resp.ConnName,
resp.ConnNamespace,
}
}

func tabularPrint(obj any, printer upterm.ObjectPrinter, upCtx *upbound.Context) error {
if upCtx.Profile.IsSpace() {
return printer.Print(obj, spacefieldNames, extractSpaceFields)
} else {
return printer.Print(obj, cloudfieldNames, extractCloudFields)
}
}
65 changes: 50 additions & 15 deletions cmd/up/controlplane/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,76 @@ package controlplane

import (
"context"
"fmt"

"github.com/alecthomas/kong"
"github.com/pterm/pterm"
"k8s.io/client-go/dynamic"

"github.com/upbound/up-sdk-go/service/configurations"
cp "github.com/upbound/up-sdk-go/service/controlplanes"

"github.com/upbound/up/internal/controlplane"
"github.com/upbound/up/internal/controlplane/cloud"
"github.com/upbound/up/internal/controlplane/space"
"github.com/upbound/up/internal/upbound"
)

type ctpCreator interface {
Create(ctx context.Context, name string, opts controlplane.Options) (*controlplane.Response, error)
}

// createCmd creates a control plane on Upbound.
type createCmd struct {
Name string `arg:"" required:"" help:"Name of control plane."`

ConfigurationName string `required:"" help:"The name of the Configuration."`
ConfigurationName string `help:"The name of the Configuration. This property is required for cloud control planes."`
Description string `short:"d" help:"Description for control plane."`

SecretName string `help:"The name of the control plane's secret. Defaults to 'kubeconfig-{control plane name}'. Only applicable for Space control planes."`
SecretNamespace string `default:"default" help:"The name of namespace for the control plane's secret. Only applicable for Space control planes."`

client ctpCreator
}

// Run executes the create command.
func (c *createCmd) Run(p pterm.TextPrinter, cc *cp.Client, cfc *configurations.Client, upCtx *upbound.Context) error {
// AfterApply sets default values in command after assignment and validation.
func (c *createCmd) AfterApply(kongCtx *kong.Context, upCtx *upbound.Context) error {

if upCtx.Profile.IsSpace() {
return fmt.Errorf("create is not supported for space profile %q", upCtx.ProfileName)
}
kubeconfig, err := upCtx.Profile.GetKubeClientConfig()
if err != nil {
return err
}
client, err := dynamic.NewForConfig(kubeconfig)
if err != nil {
return err
}
c.client = space.New(client)
} else {
cfg, err := upCtx.BuildSDKConfig()
if err != nil {
return err
}
ctpclient := cp.NewClient(cfg)
cfgclient := configurations.NewClient(cfg)

// Get the UUID from the Configuration name, if it exists.
cfg, err := cfc.Get(context.Background(), upCtx.Account, c.ConfigurationName)
if err != nil {
return err
c.client = cloud.New(ctpclient, cfgclient, upCtx.Account)
}

if _, err := cc.Create(context.Background(), upCtx.Account, &cp.ControlPlaneCreateParameters{
Name: c.Name,
Description: c.Description,
ConfigurationID: cfg.ID,
}); err != nil {
kongCtx.Bind(pterm.DefaultTable.WithWriter(kongCtx.Stdout).WithSeparator(" "))
return nil
}

// Run executes the create command.
func (c *createCmd) Run(p pterm.TextPrinter, upCtx *upbound.Context) error {
_, err := c.client.Create(
context.Background(),
c.Name,
controlplane.Options{
SecretName: c.SecretName,
SecretNamespace: c.SecretNamespace,
},
)
if err != nil {
return err
}

Expand Down
47 changes: 42 additions & 5 deletions cmd/up/controlplane/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,64 @@ package controlplane

import (
"context"
"fmt"

"github.com/alecthomas/kong"
"github.com/pterm/pterm"
"k8s.io/client-go/dynamic"

"github.com/upbound/up-sdk-go/service/configurations"
cp "github.com/upbound/up-sdk-go/service/controlplanes"

"github.com/upbound/up/internal/controlplane"
"github.com/upbound/up/internal/controlplane/cloud"
"github.com/upbound/up/internal/controlplane/space"
"github.com/upbound/up/internal/upbound"
)

type ctpDeleter interface {
Delete(ctx context.Context, name string) error
}

// deleteCmd deletes a control plane on Upbound.
type deleteCmd struct {
Name string `arg:"" help:"Name of control plane." predictor:"ctps"`

client ctpDeleter
}

// Run executes the delete command.
func (c *deleteCmd) Run(p pterm.TextPrinter, cc *cp.Client, upCtx *upbound.Context) error {
// AfterApply sets default values in command after assignment and validation.
func (c *deleteCmd) AfterApply(kongCtx *kong.Context, upCtx *upbound.Context) error {

if upCtx.Profile.IsSpace() {
return fmt.Errorf("delete is not supported for space profile %q", upCtx.ProfileName)
kubeconfig, err := upCtx.Profile.GetKubeClientConfig()
if err != nil {
return err
}
client, err := dynamic.NewForConfig(kubeconfig)
if err != nil {
return err
}
c.client = space.New(client)
} else {
cfg, err := upCtx.BuildSDKConfig()
if err != nil {
return err
}
ctpclient := cp.NewClient(cfg)
cfgclient := configurations.NewClient(cfg)

c.client = cloud.New(ctpclient, cfgclient, upCtx.Account)
}
return nil
}

if err := cc.Delete(context.Background(), upCtx.Account, c.Name); err != nil {
// Run executes the delete command.
func (c *deleteCmd) Run(p pterm.TextPrinter, upCtx *upbound.Context) error {
if err := c.client.Delete(context.Background(), c.Name); err != nil {
if controlplane.IsNotFound(err) {
p.Printfln("Control plane %s not found", c.Name)
return nil
}
return err
}
p.Printfln("%s deleted", c.Name)
Expand Down
50 changes: 38 additions & 12 deletions cmd/up/controlplane/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,73 @@ package controlplane

import (
"context"
"fmt"

"github.com/alecthomas/kong"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/pterm/pterm"
"k8s.io/client-go/dynamic"

"github.com/upbound/up-sdk-go/service/configurations"
cp "github.com/upbound/up-sdk-go/service/controlplanes"

"github.com/upbound/up/internal/controlplane"
"github.com/upbound/up/internal/controlplane/cloud"
"github.com/upbound/up/internal/controlplane/space"
"github.com/upbound/up/internal/upbound"
"github.com/upbound/up/internal/upterm"
)

const errNoConfigurationFound = "no configuration associated to this control plane"
type ctpGetter interface {
Get(ctx context.Context, name string) (*controlplane.Response, error)
}

// AfterApply sets default values in command after assignment and validation.
func (c *getCmd) AfterApply(kongCtx *kong.Context, upCtx *upbound.Context) error {

if upCtx.Profile.IsSpace() {
kubeconfig, err := upCtx.Profile.GetKubeClientConfig()
if err != nil {
return err
}
client, err := dynamic.NewForConfig(kubeconfig)
if err != nil {
return err
}
c.client = space.New(client)
} else {
cfg, err := upCtx.BuildSDKConfig()
if err != nil {
return err
}
ctpclient := cp.NewClient(cfg)
cfgclient := configurations.NewClient(cfg)

c.client = cloud.New(ctpclient, cfgclient, upCtx.Account)
}

kongCtx.Bind(pterm.DefaultTable.WithWriter(kongCtx.Stdout).WithSeparator(" "))
return nil
}

// getCmd gets a single control plane in an account on Upbound.
type getCmd struct {
Name string `arg:"" required:"" help:"Name of control plane." predictor:"ctps"`

client ctpGetter
}

// Run executes the get command.
func (c *getCmd) Run(printer upterm.ObjectPrinter, cc *cp.Client, upCtx *upbound.Context) error {
if upCtx.Profile.IsSpace() {
return fmt.Errorf("get is not supported for space profile %q", upCtx.ProfileName)
func (c *getCmd) Run(printer upterm.ObjectPrinter, p pterm.TextPrinter, upCtx *upbound.Context) error {
ctp, err := c.client.Get(context.Background(), c.Name)
if controlplane.IsNotFound(err) {
p.Printfln("Control plane %s not found", c.Name)
return nil
}

ctp, err := cc.Get(context.Background(), upCtx.Account, c.Name)
if err != nil {
return err
}
// All Upbound managed control planes in an account should be associated to a configuration.
if ctp.ControlPlane.Configuration == EmptyControlPlaneConfiguration() {
return errors.New(errNoConfigurationFound)
}

return printer.Print(*ctp, fieldNames, extractFields)
return tabularPrint(ctp, printer, upCtx)
}

// EmptyControlPlaneConfiguration returns an empty ControlPlaneConfiguration with default values.
Expand Down
Loading

0 comments on commit 97f19fb

Please sign in to comment.