Skip to content

Commit

Permalink
Merge pull request #456 from upbound/space-detach-without-running-space
Browse files Browse the repository at this point in the history
allow detaching space without a cluster
  • Loading branch information
avalanche123 committed Apr 2, 2024
2 parents eca1be1 + fef4e27 commit 52fe383
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 30 deletions.
48 changes: 34 additions & 14 deletions cmd/up/space/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (c *attachCmd) Run(ctx context.Context, mgr *helm.Installer, kClient *kuber
}
}()
return undo.Do(func(u undo.Undoer) error {
a, err := c.getAccount(ctx, upCtx, ac)
a, err := getAccount(ctx, upCtx, ac)
if err != nil {
return err
}
Expand All @@ -158,7 +158,7 @@ func (c *attachCmd) Run(ctx context.Context, mgr *helm.Installer, kClient *kuber
return err
}

if err := c.prepareSpace(ctx, attachSpinner.InfoPrinter, kClient, a, sc, u, &cc); err != nil {
if err := c.prepareSpace(ctx, attachSpinner, kClient, a, sc, u, &cc); err != nil {
return err
}
attachSpinner.UpdateText(fmt.Sprintf("Connecting Space %q to Upbound Console...", cc.Data[keySpace]))
Expand All @@ -167,7 +167,7 @@ func (c *attachCmd) Run(ctx context.Context, mgr *helm.Installer, kClient *kuber
return err
}

attachSpinner.UpdateText("Installing agent...")
attachSpinner.UpdateText("Installing Upbound agent...")
if err := c.createNamespace(ctx, attachSpinner.InfoPrinter, kClient, agentNs, u); err != nil {
return err
}
Expand Down Expand Up @@ -278,7 +278,7 @@ func (c *attachCmd) prepareToken(ctx context.Context, p pterm.TextPrinter, kClie
return nil
}

func (c *attachCmd) prepareSpace(ctx context.Context, p pterm.TextPrinter, kClient *kubernetes.Clientset, a *accounts.AccountResponse, sc *spaces.Client, u undo.Undoer, cmr **corev1.ConfigMap) error { //nolint:gocyclo
func (c *attachCmd) prepareSpace(ctx context.Context, attachSpinner *pterm.SpinnerPrinter, kClient *kubernetes.Clientset, a *accounts.AccountResponse, sc *spaces.Client, u undo.Undoer, cmr **corev1.ConfigMap) error { //nolint:gocyclo
cm := *cmr
space := &upboundv1alpha1.Space{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -297,14 +297,14 @@ func (c *attachCmd) prepareSpace(ctx context.Context, p pterm.TextPrinter, kClie
}
ns, name := parts[0], parts[1]
if space.Namespace == ns && (space.Name == "" || space.Name == name) {
p.Printfln("Using Space %q", v)
attachSpinner.InfoPrinter.Printfln("Using Space %q", v)
u.Undo(func() error {
return c.deleteSpace(ctx, p, a, sc)
return c.deleteSpace(ctx, attachSpinner.InfoPrinter, a, sc)
})
c.Space = name
return nil
}
p.Printfln("Not using Space %q", v)
attachSpinner.InfoPrinter.Printfln("Not using Space %q", v)
delete(cm.Data, keySpace)
var err error
cm, err = kClient.CoreV1().ConfigMaps(agentNs).Update(ctx, cm, metav1.UpdateOptions{})
Expand All @@ -313,28 +313,46 @@ func (c *attachCmd) prepareSpace(ctx context.Context, p pterm.TextPrinter, kClie
}
*cmr = cm
}
name, err := c.createSpace(ctx, p, kClient, a, space, sc, u, cmr)
name, err := c.createSpace(ctx, attachSpinner, kClient, a, space, sc, u, cmr)
if err != nil {
return err
}
c.Space = name
return nil
}

func (c *attachCmd) createSpace(ctx context.Context, p pterm.TextPrinter, kClient *kubernetes.Clientset, a *accounts.AccountResponse, space *upboundv1alpha1.Space, sc *spaces.Client, u undo.Undoer, cmr **corev1.ConfigMap) (string, error) {
func (c *attachCmd) createSpace(ctx context.Context, attachSpinner *pterm.SpinnerPrinter, kClient *kubernetes.Clientset, a *accounts.AccountResponse, space *upboundv1alpha1.Space, sc *spaces.Client, u undo.Undoer, cmr **corev1.ConfigMap) (string, error) {
cm := *cmr
p.Printfln("Creating a new Space in Upbound Console in organization %q...", a.Organization.Name)
attachSpinner.InfoPrinter.Printfln("Creating a new Space in Upbound Console in organization %q...", a.Organization.Name)
space, err := sc.Create(ctx, a.Organization.Name, space, nil)
if err != nil {
if kerrors.IsAlreadyExists(err) && c.Space != "" {
attachSpinner.UpdateText("Continue? (Y/n)")
if err := warnAndConfirm(
`Space "%s/%s" already exists. Would you like to overwrite it?`+"\n\n"+
" If the other Space cluster still exists, the Upbound agent will be left running and you will need to delete it manually.\n",
a.Organization.Name, c.Space,
); err != nil {
return "", err
}
attachSpinner.UpdateText(fmt.Sprintf("Connecting Space %q to Upbound Console...", c.Space))
if cm.Data[keySpace] == path.Join(a.Organization.Name, c.Space) {
return c.Space, nil
}
cm.Data[keySpace] = path.Join(a.Organization.Name, c.Space)
cm, err = kClient.CoreV1().ConfigMaps(agentNs).Update(ctx, cm, metav1.UpdateOptions{})
if err != nil {
return "", errors.Wrapf(err, `failed to update ConfigMap "%s/%s"`, agentNs, connConfMap)
}
*cmr = cm
return c.Space, nil
}
return "", errors.Wrapf(err, errCreateSpace)
}
u.Undo(func() error {
return c.deleteSpace(ctx, p, a, sc)
return c.deleteSpace(ctx, attachSpinner.InfoPrinter, a, sc)
})
p.Printfln(`Space "%s/%s" created`, a.Organization.Name, space.Name)
attachSpinner.InfoPrinter.Printfln(`Space "%s/%s" created`, a.Organization.Name, space.Name)
cm.Data[keySpace] = path.Join(a.Organization.Name, space.Name)
cm, err = kClient.CoreV1().ConfigMaps(agentNs).Update(ctx, cm, metav1.UpdateOptions{})
if err != nil {
Expand All @@ -352,7 +370,7 @@ func (c *attachCmd) deleteSpace(ctx context.Context, p pterm.TextPrinter, a *acc
return nil
}

func (c *attachCmd) getAccount(ctx context.Context, upCtx *upbound.Context, ac *accounts.Client) (*accounts.AccountResponse, error) {
func getAccount(ctx context.Context, upCtx *upbound.Context, ac *accounts.Client) (*accounts.AccountResponse, error) {
a, err := ac.Get(ctx, upCtx.Account)
if err != nil {
return nil, errors.Wrapf(err, "failed to get Account %q", upCtx.Account)
Expand Down Expand Up @@ -440,6 +458,7 @@ func deleteConnectConfigmap(ctx context.Context, p pterm.TextPrinter, kClient *k
}

func (c *attachCmd) createTokenSecret(ctx context.Context, p pterm.TextPrinter, kClient *kubernetes.Clientset, ns, name string, u undo.Undoer) error {
p.Printfln(`Creating Secret "%s/%s"`, ns, name)
_, err := kClient.CoreV1().Secrets(ns).Create(ctx, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Expand All @@ -459,6 +478,7 @@ func (c *attachCmd) createTokenSecret(ctx context.Context, p pterm.TextPrinter,
if !kerrors.IsAlreadyExists(err) {
return errors.Wrapf(err, `failed to create Secret "%s/%s"`, ns, name)
}
p.Printfln(`Secret "%s/%s" exists, updating...`, ns, name)
// secret already exists, replace it
s, err := kClient.CoreV1().Secrets(ns).Get(ctx, name, metav1.GetOptions{})
if err != nil {
Expand Down Expand Up @@ -505,7 +525,7 @@ func (c *attachCmd) createRobot(ctx context.Context, p pterm.TextPrinter, kClien
if r.Name != c.Space {
continue
}
p.Printfln(`Reusing existing Robot "%s/%s"`, ar.Organization.Name, c.Space)
p.Printfln(`Robot "%s/%s" exists`, ar.Organization.Name, c.Space)
// delete generated robot at clean up
u.Undo(func() error {
return c.deleteRobot(ctx, p, ar, rc, r.ID)
Expand Down
117 changes: 101 additions & 16 deletions cmd/up/space/detach.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (
"k8s.io/client-go/kubernetes"

sdkerrs "github.com/upbound/up-sdk-go/errors"
"github.com/upbound/up-sdk-go/service/accounts"
"github.com/upbound/up-sdk-go/service/organizations"
"github.com/upbound/up-sdk-go/service/robots"
"github.com/upbound/up-sdk-go/service/spaces"
"github.com/upbound/up/internal/install/helm"
Expand All @@ -41,6 +43,8 @@ import (
type detachCmd struct {
Upbound upbound.Flags `embed:""`
Kube upbound.KubeFlags `embed:""`

Space string `arg:"" optional:"" help:"Name of the Upbound Space. If name is not a supplied, it will be determined from the connection info in the space."`
}

func (c *detachCmd) AfterApply(kongCtx *kong.Context) error {
Expand All @@ -49,8 +53,12 @@ func (c *detachCmd) AfterApply(kongCtx *kong.Context) error {
return err
}

needsKube := true
if err := c.Kube.AfterApply(); err != nil {
return err
if c.Space == "" {
return errors.Wrap(err, "failed to get kube config")
}
needsKube = false
}

// NOTE(tnthornton) we currently only have support for stylized output.
Expand All @@ -68,12 +76,20 @@ func (c *detachCmd) AfterApply(kongCtx *kong.Context) error {
kongCtx.Bind(upCtx)
kongCtx.Bind(robots.NewClient(cfg))
kongCtx.Bind(spaces.NewClient(cfg))
kongCtx.Bind(accounts.NewClient(cfg))
kongCtx.Bind(organizations.NewClient(cfg))

// bind nils as k8s client and helm manager
if !needsKube {
kongCtx.Bind((*kubernetes.Clientset)(nil))
kongCtx.Bind((*helm.Installer)(nil))
return nil
}
kubeconfig := c.Kube.GetConfig()

kClient, err := kubernetes.NewForConfig(kubeconfig)
if err != nil {
return err
return errors.Wrap(err, "failed to create kube client")
}
kongCtx.Bind(kClient)

Expand All @@ -96,8 +112,12 @@ func (c *detachCmd) AfterApply(kongCtx *kong.Context) error {
}

// Run executes the detach command.
func (c *detachCmd) Run(ctx context.Context, kClient *kubernetes.Clientset, mgr *helm.Installer, sc *spaces.Client, rc *robots.Client) (rErr error) {
detachSpinner, err := upterm.CheckmarkSuccessSpinner.Start("Disconnecting Space from Upbound Console...")
func (c *detachCmd) Run(ctx context.Context, upCtx *upbound.Context, ac *accounts.Client, oc *organizations.Client, kClient *kubernetes.Clientset, mgr *helm.Installer, sc *spaces.Client, rc *robots.Client) (rErr error) {
msg := "Disconnecting Space from Upbound Console..."
if c.Space != "" {
msg = fmt.Sprintf("Disconnecting Space %q from Upbound Console...", c.Space)
}
detachSpinner, err := upterm.CheckmarkSuccessSpinner.Start(msg)
if err != nil {
return err
}
Expand All @@ -106,21 +126,75 @@ func (c *detachCmd) Run(ctx context.Context, kClient *kubernetes.Clientset, mgr
detachSpinner.Fail(rErr)
}
}()
if err := c.deleteResources(ctx, detachSpinner.InfoPrinter, kClient, rc, sc); err != nil {
if err := c.detachSpace(ctx, detachSpinner, upCtx, ac, oc, kClient, mgr, rc, sc); err != nil {
return err
}
detachSpinner.InfoPrinter.Println("Uninstalling connect agent...")
if err := mgr.Uninstall(); err != nil && !errors.Is(err, driver.ErrReleaseNotFound) {
return errors.Wrapf(err, `failed to uninstall Chart "%s/%s"`, agentNs, agentChart)
msg = "Space has been successfully disconnected from Upbound Console"
if c.Space != "" {
msg = fmt.Sprintf("Space %q has been successfully disconnected from Upbound Console", c.Space)
}
if err := deleteTokenSecret(ctx, detachSpinner.InfoPrinter, kClient, agentNs, agentSecret); err != nil && !kerrors.IsNotFound(err) {
return err
detachSpinner.Success(msg)
return nil
}

func (c *detachCmd) detachSpace(ctx context.Context, detachSpinner *pterm.SpinnerPrinter, upCtx *upbound.Context, ac *accounts.Client, oc *organizations.Client, kClient *kubernetes.Clientset, mgr *helm.Installer, rc *robots.Client, sc *spaces.Client) error {
if kClient == nil {
detachSpinner.UpdateText("Continue? (Y/n)")
if err := warnAndConfirm(
"Not connected to a Space cluster, would you like to only remove the Space from the Upbound Console?\n\n" +
" If the other Space cluster still exists, the Upbound agent will be left running and you will need to delete it manually.\n",
); err != nil {
return err
}
detachSpinner.UpdateText(fmt.Sprintf("Disconnecting Space %q from Upbound Console...", c.Space))
a, err := getAccount(ctx, upCtx, ac)
if err != nil {
return err
}
if err := c.deleteRobot(ctx, detachSpinner.InfoPrinter, oc, rc, a); err != nil {
return err
}
if err := c.deleteSpace(ctx, detachSpinner.InfoPrinter, sc, a); err != nil {
return err
}
return nil
}
return c.deleteResources(ctx, detachSpinner.InfoPrinter, kClient, mgr, rc, sc)
}

func (c *detachCmd) deleteSpace(ctx context.Context, p pterm.TextPrinter, sc *spaces.Client, ar *accounts.AccountResponse) error {
p.Printf(`Deleting Space "%s/%s"`, ar.Organization.Name, c.Space)
if err := sc.Delete(ctx, ar.Organization.Name, c.Space, nil); err != nil && !kerrors.IsNotFound(err) {
return errors.Wrapf(err, `failed to delete Space "%s/%s"`, ar.Organization.Name, c.Space)
}
p.Printfln(`Space "%s/%s" deleted`, ar.Organization.Name, c.Space)
// replace space with full name for display purposes
c.Space = fmt.Sprintf("%s/%s", ar.Organization.Name, c.Space)
return nil
}

func (c *detachCmd) deleteRobot(ctx context.Context, p pterm.TextPrinter, oc *organizations.Client, rc *robots.Client, ar *accounts.AccountResponse) error {
p.Printf("Looking for robot token for Space %q", c.Space)
rr, err := oc.ListRobots(ctx, ar.Organization.ID)
if err != nil {
return errors.Wrap(err, "failed to list Robots")
}
for _, r := range rr {
if r.Name != c.Space {
continue
}
p.Printfln(`Deleting Robot "%s/%s"`, ar.Organization.Name, c.Space)
if err := rc.Delete(ctx, r.ID); err != nil && !sdkerrs.IsNotFound(err) {
return errors.Wrapf(err, `failed to delete Robot "%s/%s"`, ar.Organization.Name, c.Space)
}
p.Printfln(`Robot "%s/%s" deleted`, ar.Organization.Name, c.Space)
return nil
}
detachSpinner.Success("Space has been successfully disconnected from Upbound Console")
p.Printf("No robot token for Space %q, skipping...", c.Space)
return nil
}

func (c *detachCmd) deleteResources(ctx context.Context, p pterm.TextPrinter, kClient *kubernetes.Clientset, rc *robots.Client, sc *spaces.Client) error {
func (c *detachCmd) deleteResources(ctx context.Context, p pterm.TextPrinter, kClient *kubernetes.Clientset, mgr *helm.Installer, rc *robots.Client, sc *spaces.Client) error {
cm, err := getConnectConfigmap(ctx, kClient, agentNs, connConfMap)
if kerrors.IsNotFound(err) {
return nil
Expand All @@ -129,15 +203,22 @@ func (c *detachCmd) deleteResources(ctx context.Context, p pterm.TextPrinter, kC
return errors.Wrapf(err, `failed to get ConfigMap "%s/%s"`, agentNs, agentSecret)
}
p.Printfln(`ConfigMap "%s/%s" found, deleting resources in Upbound Console...`, agentNs, agentSecret)
if err := c.deleteAgentRobot(ctx, p, kClient, rc, &cm); err != nil {
if err := c.deleteGeneratedSpace(ctx, p, kClient, sc, &cm); err != nil {
return err
}
if err := c.deleteGeneratedSpace(ctx, p, kClient, sc, &cm); err != nil {
if err := c.deleteAgentRobot(ctx, p, kClient, rc, &cm); err != nil {
return err
}
if err := deleteConnectConfigmap(ctx, p, kClient, agentNs, connConfMap); err != nil {
return err
}
p.Println("Uninstalling connect agent...")
if err := mgr.Uninstall(); err != nil && !errors.Is(err, driver.ErrReleaseNotFound) {
return errors.Wrapf(err, `failed to uninstall Chart "%s/%s"`, agentNs, agentChart)
}
if err := deleteTokenSecret(ctx, p, kClient, agentNs, agentSecret); err != nil && !kerrors.IsNotFound(err) {
return err
}
return nil
}

Expand Down Expand Up @@ -175,10 +256,14 @@ func (c *detachCmd) deleteGeneratedSpace(ctx context.Context, p pterm.TextPrinte
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return fmt.Errorf("invalid space %q", v)
}
p.Printfln("Deleting Space %q", v)
ns, name := parts[0], parts[1]
if c.Space != "" && c.Space != name {
return fmt.Errorf("connected Space %q does not match specified name %q", name, c.Space)
}
c.Space = name
p.Printfln("Deleting Space %q", name)
if err := sc.Delete(ctx, ns, name, nil); err != nil && !kerrors.IsNotFound(err) {
return errors.Wrapf(err, `failed to delete Space "%s/%s"`, ns, name)
return errors.Wrapf(err, `failed to delete Space %q`, name)
}
delete(cm.Data, keySpace)
cm, err := kClient.CoreV1().ConfigMaps(agentNs).Update(ctx, cm, metav1.UpdateOptions{})
Expand Down

0 comments on commit 52fe383

Please sign in to comment.