From bc894e56b2a7292b5ce27aaeb635f7277f74ba10 Mon Sep 17 00:00:00 2001 From: Rae Krantz <8461333+krantzinator@users.noreply.github.com> Date: Wed, 9 Dec 2020 00:40:50 +0000 Subject: [PATCH 1/7] backport of commit f465c574c8fc1f0b1654e777d48b100ee0d0e668 --- internal/cli/main.go | 5 + internal/cli/uninstall.go | 143 ++++++++++++++++++++++++ internal/serverinstall/docker.go | 23 ++++ internal/serverinstall/k8s.go | 22 ++++ internal/serverinstall/nomad.go | 14 +++ internal/serverinstall/serverinstall.go | 2 + 6 files changed, 209 insertions(+) create mode 100644 internal/cli/uninstall.go diff --git a/internal/cli/main.go b/internal/cli/main.go index 9ace04b271d..add137dfb39 100644 --- a/internal/cli/main.go +++ b/internal/cli/main.go @@ -269,6 +269,11 @@ func Commands( baseCommand: baseCommand, }, nil }, + "server uninstall": func() (cli.Command, error) { + return &UninstallCommand{ + baseCommand: baseCommand, + }, nil + }, "server run": func() (cli.Command, error) { return &ServerRunCommand{ baseCommand: baseCommand, diff --git a/internal/cli/uninstall.go b/internal/cli/uninstall.go new file mode 100644 index 00000000000..dd725ece8c1 --- /dev/null +++ b/internal/cli/uninstall.go @@ -0,0 +1,143 @@ +package cli + +import ( + "strings" + + "github.com/posener/complete" + + "github.com/hashicorp/waypoint-plugin-sdk/terminal" + "github.com/hashicorp/waypoint/internal/clierrors" + "github.com/hashicorp/waypoint/internal/pkg/flag" + "github.com/hashicorp/waypoint/internal/serverinstall" +) + +type UninstallCommand struct { + *baseCommand + + platform string + contextName string + snapshotName string + skipSnapshot bool + flagConfirm bool +} + +func (c *UninstallCommand) Run(args []string) int { + ctx := c.Ctx + log := c.Log.Named("install") + defer c.Close() + + // Initialize. If we fail, we just exit since Init handles the UI. + if err := c.Init( + WithArgs(args), + WithFlags(c.Flags()), + WithNoConfig(), + WithClient(false), + ); err != nil { + return 1 + } + + if !c.flagConfirm { + c.ui.Output(strings.TrimSpace(confirmReqMsg), terminal.WithErrorStyle()) + return 1 + } + + var err error + + // Generate a snapshot + if !c.skipSnapshot { + // sn := fmt.Sprintf("%s-%d", c.snapshotName, time.Now().Unix()) + // generate snapshot + } + + p, ok := serverinstall.Platforms[strings.ToLower(c.platform)] + if !ok { + c.ui.Output( + "Error uninstalling server from %s: invalid platform", + c.platform, + terminal.WithErrorStyle(), + ) + + return 1 + } + + err = p.Uninstall(ctx, c.ui, log) + if err != nil { + // point to current docs on manual server cleanup + c.ui.Output( + "Error uninstalling server from %s: %s", c.platform, clierrors.Humanize(err), + terminal.WithErrorStyle(), + ) + + return 1 + } + + // Verify clean state; remove old context + + return 0 +} + +func (c *UninstallCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *UninstallCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *UninstallCommand) Synopsis() string { + return "Uninstall the Waypoint server" +} + +func (c *UninstallCommand) Help() string { + return formatHelp(` +Usage: waypoint server uninstall [options] + Uninstall the Waypoint server. + +` + c.Flags().Help()) +} + +func (c *UninstallCommand) Flags() *flag.Sets { + return c.flagSet(0, func(set *flag.Sets) { + f := set.NewSet("Command Options") + f.StringVar(&flag.StringVar{ + Name: "context-name", + Target: &c.contextName, + Usage: "Context of the Waypoint server to uninstall.", + }) + + f.BoolVar(&flag.BoolVar{ + Name: "confirm", + Target: &c.flagConfirm, + Default: false, + Usage: "Confirm server uninstallation.", + }) + + f.StringVar(&flag.StringVar{ + Name: "platform", + Target: &c.platform, + Default: "", + Usage: "Platform to uninstall the Waypoint server from.", + }) + + f.StringVar(&flag.StringVar{ + Name: "snapshot-name", + Target: &c.snapshotName, + Default: "", + Usage: "Platform to uninstall the Waypoint server from.", + }) + + f.BoolVar(&flag.BoolVar{ + Name: "skip-snapshot", + Target: &c.skipSnapshot, + Default: false, + Usage: "Skip creating a snapshot of the Waypoint server.", + }) + }) +} + +var ( + confirmReqMsg = strings.TrimSpace(` +Uninstalling Waypoint server requires confirmation. +Rerun the command with ‘-confirm’ to continue with the uninstall. +`) +) diff --git a/internal/serverinstall/docker.go b/internal/serverinstall/docker.go index 7fdb82840e0..3482ceb2d83 100644 --- a/internal/serverinstall/docker.go +++ b/internal/serverinstall/docker.go @@ -240,3 +240,26 @@ func (i *DockerInstaller) InstallFlags(set *flag.Set) { Default: "hashicorp/waypoint:latest", }) } + +func (i *DockerInstaller) Uninstall(ctx context.Context, ui terminal.UI, log hclog.Logger) error { + // sg := ui.StepGroup() + // dockerCli, err := client.NewClientWithOpts(client.FromEnv) + // if err != nil { + // return err + // } + + // stop server + // rm server + // prune volumes + + return nil +} + +func (i *DockerInstaller) UninstallFlags(set *flag.Set) { + set.StringVar(&flag.StringVar{ + Name: "docker-server-image", + Target: &i.config.serverImage, + Usage: "Docker image for the Waypoint server.", + Default: "hashicorp/waypoint:latest", + }) +} diff --git a/internal/serverinstall/k8s.go b/internal/serverinstall/k8s.go index 2f3e2297cbd..c9f77713869 100644 --- a/internal/serverinstall/k8s.go +++ b/internal/serverinstall/k8s.go @@ -623,6 +623,28 @@ func (i *K8sInstaller) InstallFlags(set *flag.Set) { }) } +func (i *K8sInstaller) Uninstall(ctx context.Context, ui terminal.UI, log hclog.Logger) error { + + // deleteStatefulSet() + // delete pvc + // delete svc + + return nil +} + +func deleteStatefulSet() (*metav1.DeleteOptions, error) { + return &metav1.DeleteOptions{}, nil +} + +func (i *K8sInstaller) UninstallFlags(set *flag.Set) { + set.StringVar(&flag.StringVar{ + Name: "k8s-server-image", + Target: &i.config.serverImage, + Usage: "Docker image for the Waypoint server.", + Default: "hashicorp/waypoint:latest", + }) +} + func int32Ptr(i int32) *int32 { return &i } diff --git a/internal/serverinstall/nomad.go b/internal/serverinstall/nomad.go index fa94e19d98b..af83ba7a27a 100644 --- a/internal/serverinstall/nomad.go +++ b/internal/serverinstall/nomad.go @@ -311,3 +311,17 @@ func (i *NomadInstaller) InstallFlags(set *flag.Set) { Default: "hashicorp/waypoint:latest", }) } + +func (i *NomadInstaller) Uninstall(ctx context.Context, ui terminal.UI, log hclog.Logger) error { + return nil +} + +func (i *NomadInstaller) UninstallFlags(set *flag.Set) { + // don't need this; we know what the name is if it's already installed + set.StringVar(&flag.StringVar{ + Name: "nomad-server-image", + Target: &i.config.serverImage, + Usage: "Docker image for the Waypoint server.", + Default: "hashicorp/waypoint:latest", + }) +} diff --git a/internal/serverinstall/serverinstall.go b/internal/serverinstall/serverinstall.go index 5eacf6a1854..dbdec0b6479 100644 --- a/internal/serverinstall/serverinstall.go +++ b/internal/serverinstall/serverinstall.go @@ -15,6 +15,8 @@ import ( type Installer interface { Install(ctx context.Context, ui terminal.UI, log hclog.Logger) (*clicontext.Config, *pb.ServerConfig_AdvertiseAddr, string, error) InstallFlags(*flag.Set) + Uninstall(context.Context, terminal.UI, hclog.Logger) error + UninstallFlags(*flag.Set) } var Platforms = map[string]Installer{ From 4b5fba8ff1451a48707badf1ac5e80fb9d58f0df Mon Sep 17 00:00:00 2001 From: Rae Krantz <8461333+krantzinator@users.noreply.github.com> Date: Wed, 9 Dec 2020 18:13:44 +0000 Subject: [PATCH 2/7] backport of commit 2cad3801f28e5d048e12406a62594dae3f9ed533 --- internal/cli/uninstall.go | 45 ++++++++--- internal/serverinstall/docker.go | 124 ++++++++++++++++++++++++++----- 2 files changed, 143 insertions(+), 26 deletions(-) diff --git a/internal/cli/uninstall.go b/internal/cli/uninstall.go index dd725ece8c1..6fa6d78de21 100644 --- a/internal/cli/uninstall.go +++ b/internal/cli/uninstall.go @@ -15,7 +15,6 @@ type UninstallCommand struct { *baseCommand platform string - contextName string snapshotName string skipSnapshot bool flagConfirm bool @@ -43,12 +42,40 @@ func (c *UninstallCommand) Run(args []string) int { var err error + sg := c.ui.StepGroup() + defer sg.Wait() + + // Pre-install work + // - name the context we'll be uninstalling + // - generate a snapshot of the current install + s := sg.Add("") + contextDefault, err := c.contextStorage.Default() + if err != nil { + c.ui.Output(clierrors.Humanize(err), terminal.WithErrorStyle()) + return 1 + } + s.Update("Default Waypoint server detected as context %q", contextDefault) + s.Status(terminal.StatusWarn) + s.Done() + s = sg.Add("") + s.Update("Uninstalling Waypoint server using context %q...", contextDefault) + s.Done() + + s = sg.Add("") // Generate a snapshot if !c.skipSnapshot { + s.Update("Generating server snapshot...") + defer s.Abort() // sn := fmt.Sprintf("%s-%d", c.snapshotName, time.Now().Unix()) // generate snapshot + // s.Update("Snapshot %q generated", sn") + } else { + s.Update("skip-snapshot set; not generating server snapshot") + s.Status(terminal.StatusWarn) } + s.Done() + // Uninstall p, ok := serverinstall.Platforms[strings.ToLower(c.platform)] if !ok { c.ui.Output( @@ -71,7 +98,13 @@ func (c *UninstallCommand) Run(args []string) int { return 1 } - // Verify clean state; remove old context + // Post-uninstall cleanup of context + if err := c.contextStorage.Delete(contextDefault); err != nil { + c.ui.Output(clierrors.Humanize(err), terminal.WithErrorStyle()) + return 1 + } + + c.ui.Output("Waypoint server successfully uninstalled for %s platform", c.platform, terminal.WithSuccessStyle()) return 0 } @@ -99,12 +132,6 @@ Usage: waypoint server uninstall [options] func (c *UninstallCommand) Flags() *flag.Sets { return c.flagSet(0, func(set *flag.Sets) { f := set.NewSet("Command Options") - f.StringVar(&flag.StringVar{ - Name: "context-name", - Target: &c.contextName, - Usage: "Context of the Waypoint server to uninstall.", - }) - f.BoolVar(&flag.BoolVar{ Name: "confirm", Target: &c.flagConfirm, @@ -123,7 +150,7 @@ func (c *UninstallCommand) Flags() *flag.Sets { Name: "snapshot-name", Target: &c.snapshotName, Default: "", - Usage: "Platform to uninstall the Waypoint server from.", + Usage: "Name to use for the created.", }) f.BoolVar(&flag.BoolVar{ diff --git a/internal/serverinstall/docker.go b/internal/serverinstall/docker.go index 3482ceb2d83..2132a40254a 100644 --- a/internal/serverinstall/docker.go +++ b/internal/serverinstall/docker.go @@ -31,6 +31,15 @@ type dockerConfig struct { serverImage string `hcl:"server_image,optional"` } +var ( + grpcPort = "9701" + httpPort = "9702" + containerLabel = "waypoint-type=server" + containerKey = "waypoint-type" + containerValue = "server" + containerName = "waypoint-server" +) + // Install is a method of DockerInstaller and implements the Installer interface to // create a waypoint-server as a Docker container func (i *DockerInstaller) Install( @@ -54,16 +63,13 @@ func (i *DockerInstaller) Install( containers, err := cli.ContainerList(ctx, types.ContainerListOptions{ Filters: filters.NewArgs(filters.KeyValuePair{ Key: "label", - Value: "waypoint-type=server", + Value: containerLabel, }), }) if err != nil { return nil, nil, "", err } - grpcPort := "9701" - httpPort := "9702" - var ( clicfg clicontext.Config addr pb.ServerConfig_AdvertiseAddr @@ -76,7 +82,7 @@ func (i *DockerInstaller) Install( TlsSkipVerify: true, } - addr.Addr = "waypoint-server:" + grpcPort + addr.Addr = containerName + ":" + grpcPort addr.Tls = true addr.TlsSkipVerify = true @@ -197,7 +203,7 @@ func (i *DockerInstaller) Install( }, } hostconfig := container.HostConfig{ - Binds: []string{"waypoint-server:/data"}, + Binds: []string{containerName + ":/data"}, PortBindings: bindings, } @@ -208,10 +214,10 @@ func (i *DockerInstaller) Install( } cfg.Labels = map[string]string{ - "waypoint-type": "server", + containerKey: containerValue, } - cr, err := cli.ContainerCreate(ctx, &cfg, &hostconfig, &netconfig, "waypoint-server") + cr, err := cli.ContainerCreate(ctx, &cfg, &hostconfig, &netconfig, containerName) if err != nil { return nil, nil, "", err } @@ -241,16 +247,84 @@ func (i *DockerInstaller) InstallFlags(set *flag.Set) { }) } -func (i *DockerInstaller) Uninstall(ctx context.Context, ui terminal.UI, log hclog.Logger) error { - // sg := ui.StepGroup() - // dockerCli, err := client.NewClientWithOpts(client.FromEnv) - // if err != nil { - // return err - // } +func (i *DockerInstaller) Uninstall( + ctx context.Context, ui terminal.UI, log hclog.Logger, +) error { + sg := ui.StepGroup() + defer sg.Wait() + + // bulk of this copied from PR#660 + s := sg.Add("Initializing Docker client...") + defer s.Abort() + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return err + } + + // TODO do we need this? + // defer func() { + // _ = cli.Close() + // }() + + cli.NegotiateAPIVersion(ctx) + + containers, err := cli.ContainerList(ctx, types.ContainerListOptions{ + Filters: filters.NewArgs(filters.KeyValuePair{ + Key: "label", + Value: containerLabel, + }), + }) + + if err != nil { + return err + } + + if len(containers) < 1 { + return fmt.Errorf("cannot find a Waypoint Docker container") + } + + // Pick the first container, as there should be only one. + containerId := containers[0].ID + + s.Update("Stopping Waypoint Docker container...") - // stop server - // rm server - // prune volumes + // Stop the container gracefully, respecting the Engine's default timeout. + if err := cli.ContainerStop(ctx, containerId, nil); err != nil { + return err + } + + removeOptions := types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + } + + if err := cli.ContainerRemove(ctx, containerId, removeOptions); err != nil { + return err + } + s.Update("Docker container %q removed", containerName) + s.Done() + s = sg.Add("") + + volumeExists, err := volumeExists(ctx, cli) + if err != nil { + return err + } + + s.Update("Removing Waypoint Docker volume...") + // If the Waypoint Docker volume does not exist, return + if !volumeExists { + s.Update("Couldn't find Waypoint Docker volume %q; not removing", containerName) + s.Status(terminal.StatusWarn) + s.Done() + return nil + } + + if err := cli.VolumeRemove(ctx, containerName, true); err != nil { + return err + } + s.Update("Docker volume %q removed", containerName) + s.Done() return nil } @@ -263,3 +337,19 @@ func (i *DockerInstaller) UninstallFlags(set *flag.Set) { Default: "hashicorp/waypoint:latest", }) } + +// volumeExists determines whether the Waypoint Docker volume exists. +func volumeExists(ctx context.Context, cli *client.Client) (bool, error) { + listBody, err := cli.VolumeList(ctx, filters.NewArgs(filters.KeyValuePair{ + Key: "name", + Value: containerName, + })) + + if err != nil { + return false, err + } + + exists := len(listBody.Volumes) > 0 + + return exists, nil +} From e63e5f3d993d2475e31971bef4c63deb8f1ab7ff Mon Sep 17 00:00:00 2001 From: Rae Krantz <8461333+krantzinator@users.noreply.github.com> Date: Wed, 9 Dec 2020 18:45:26 +0000 Subject: [PATCH 3/7] backport of commit 7d1e0747fb1ce917fb6eb86e6983f106549cb7fc --- internal/cli/uninstall.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/cli/uninstall.go b/internal/cli/uninstall.go index 6fa6d78de21..caba07ddbf9 100644 --- a/internal/cli/uninstall.go +++ b/internal/cli/uninstall.go @@ -60,7 +60,7 @@ func (c *UninstallCommand) Run(args []string) int { s = sg.Add("") s.Update("Uninstalling Waypoint server using context %q...", contextDefault) s.Done() - + s = sg.Add("") // Generate a snapshot if !c.skipSnapshot { @@ -103,7 +103,7 @@ func (c *UninstallCommand) Run(args []string) int { c.ui.Output(clierrors.Humanize(err), terminal.WithErrorStyle()) return 1 } - + c.ui.Output("Waypoint server successfully uninstalled for %s platform", c.platform, terminal.WithSuccessStyle()) return 0 From acc62234713f0981615da287276bfa5f33ee6f7f Mon Sep 17 00:00:00 2001 From: Rae Krantz <8461333+krantzinator@users.noreply.github.com> Date: Tue, 15 Dec 2020 02:00:42 +0000 Subject: [PATCH 4/7] backport of commit b1bfa2d1781f4b01377fbac47366499d8584e720 --- internal/cli/uninstall.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/internal/cli/uninstall.go b/internal/cli/uninstall.go index caba07ddbf9..7bb3c8758e1 100644 --- a/internal/cli/uninstall.go +++ b/internal/cli/uninstall.go @@ -14,10 +14,11 @@ import ( type UninstallCommand struct { *baseCommand - platform string - snapshotName string - skipSnapshot bool - flagConfirm bool + platform string + snapshotPath string + skipSnapshot bool + flagConfirm bool + deleteContext bool } func (c *UninstallCommand) Run(args []string) int { @@ -66,7 +67,7 @@ func (c *UninstallCommand) Run(args []string) int { if !c.skipSnapshot { s.Update("Generating server snapshot...") defer s.Abort() - // sn := fmt.Sprintf("%s-%d", c.snapshotName, time.Now().Unix()) + // sn := fmt.Sprintf("%s-%d", c.snapshotPath, time.Now().Unix()) // generate snapshot // s.Update("Snapshot %q generated", sn") } else { @@ -99,9 +100,11 @@ func (c *UninstallCommand) Run(args []string) int { } // Post-uninstall cleanup of context - if err := c.contextStorage.Delete(contextDefault); err != nil { - c.ui.Output(clierrors.Humanize(err), terminal.WithErrorStyle()) - return 1 + if c.deleteContext { + if err := c.contextStorage.Delete(contextDefault); err != nil { + c.ui.Output(clierrors.Humanize(err), terminal.WithErrorStyle()) + return 1 + } } c.ui.Output("Waypoint server successfully uninstalled for %s platform", c.platform, terminal.WithSuccessStyle()) @@ -139,6 +142,13 @@ func (c *UninstallCommand) Flags() *flag.Sets { Usage: "Confirm server uninstallation.", }) + f.BoolVar(&flag.BoolVar{ + Name: "delete-context", + Target: &c.deleteContext, + Default: false, + Usage: "Delete the context for the server once it's uninstalled.", + }) + f.StringVar(&flag.StringVar{ Name: "platform", Target: &c.platform, @@ -147,10 +157,10 @@ func (c *UninstallCommand) Flags() *flag.Sets { }) f.StringVar(&flag.StringVar{ - Name: "snapshot-name", - Target: &c.snapshotName, + Name: "snapshot-path", + Target: &c.snapshotPath, Default: "", - Usage: "Name to use for the created.", + Usage: "Path of the file to write the snapshot to.", }) f.BoolVar(&flag.BoolVar{ From d1663eff81b658a8149d9b06306ee534f09afb74 Mon Sep 17 00:00:00 2001 From: Rae Krantz <8461333+krantzinator@users.noreply.github.com> Date: Tue, 15 Dec 2020 16:55:07 +0000 Subject: [PATCH 5/7] backport of commit 7f99eb4d1fd74e79cb8397c123661df9a9ca3f91 --- internal/cli/uninstall.go | 38 ++++++++++++++++--------- internal/serverinstall/docker.go | 5 ++-- internal/serverinstall/k8s.go | 4 +-- internal/serverinstall/nomad.go | 3 +- internal/serverinstall/serverinstall.go | 8 +++++- 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/internal/cli/uninstall.go b/internal/cli/uninstall.go index 7bb3c8758e1..8c0617dd721 100644 --- a/internal/cli/uninstall.go +++ b/internal/cli/uninstall.go @@ -1,7 +1,9 @@ package cli import ( + "fmt" "strings" + "time" "github.com/posener/complete" @@ -14,16 +16,16 @@ import ( type UninstallCommand struct { *baseCommand - platform string - snapshotPath string - skipSnapshot bool - flagConfirm bool - deleteContext bool + platform string + snapshotFilename string + skipSnapshot bool + flagConfirm bool + deleteContext bool } func (c *UninstallCommand) Run(args []string) int { ctx := c.Ctx - log := c.Log.Named("install") + log := c.Log.Named("uninstall") defer c.Close() // Initialize. If we fail, we just exit since Init handles the UI. @@ -67,9 +69,16 @@ func (c *UninstallCommand) Run(args []string) int { if !c.skipSnapshot { s.Update("Generating server snapshot...") defer s.Abort() - // sn := fmt.Sprintf("%s-%d", c.snapshotPath, time.Now().Unix()) // generate snapshot - // s.Update("Snapshot %q generated", sn") + // config := snapshot.Config{ + // Client: c.project.Client(), + // } + // w, err := os.Create(c.snapshotFilename) + // if err = config.WriteSnapshot(ctx, w); err != nil { + // fmt.Fprintf(os.Stderr, "Error generating snapshot: %s", err) + // return 1 + // } + // s.Update("Snapshot %q generated", c.snapshotFilename) } else { s.Update("skip-snapshot set; not generating server snapshot") s.Status(terminal.StatusWarn) @@ -88,7 +97,10 @@ func (c *UninstallCommand) Run(args []string) int { return 1 } - err = p.Uninstall(ctx, c.ui, log) + err = p.Uninstall(ctx, &serverinstall.InstallOpts{ + Log: log, + UI: c.ui, + }) if err != nil { // point to current docs on manual server cleanup c.ui.Output( @@ -157,10 +169,10 @@ func (c *UninstallCommand) Flags() *flag.Sets { }) f.StringVar(&flag.StringVar{ - Name: "snapshot-path", - Target: &c.snapshotPath, - Default: "", - Usage: "Path of the file to write the snapshot to.", + Name: "snapshot-filename", + Target: &c.snapshotFilename, + Default: fmt.Sprintf("sever-snapshot-%d", time.Now().Unix()), + Usage: "Filename to write the snapshot to.", }) f.BoolVar(&flag.BoolVar{ diff --git a/internal/serverinstall/docker.go b/internal/serverinstall/docker.go index 2132a40254a..e449c4fb51c 100644 --- a/internal/serverinstall/docker.go +++ b/internal/serverinstall/docker.go @@ -248,9 +248,10 @@ func (i *DockerInstaller) InstallFlags(set *flag.Set) { } func (i *DockerInstaller) Uninstall( - ctx context.Context, ui terminal.UI, log hclog.Logger, + ctx context.Context, + opts *InstallOpts, ) error { - sg := ui.StepGroup() + sg := opts.UI.StepGroup() defer sg.Wait() // bulk of this copied from PR#660 diff --git a/internal/serverinstall/k8s.go b/internal/serverinstall/k8s.go index c9f77713869..29daa50c23e 100644 --- a/internal/serverinstall/k8s.go +++ b/internal/serverinstall/k8s.go @@ -623,8 +623,8 @@ func (i *K8sInstaller) InstallFlags(set *flag.Set) { }) } -func (i *K8sInstaller) Uninstall(ctx context.Context, ui terminal.UI, log hclog.Logger) error { - +func (i *K8sInstaller) Uninstall(ctx context.Context, opts *InstallOpts) error { + // TODO // deleteStatefulSet() // delete pvc // delete svc diff --git a/internal/serverinstall/nomad.go b/internal/serverinstall/nomad.go index af83ba7a27a..b080dcf76cf 100644 --- a/internal/serverinstall/nomad.go +++ b/internal/serverinstall/nomad.go @@ -312,7 +312,8 @@ func (i *NomadInstaller) InstallFlags(set *flag.Set) { }) } -func (i *NomadInstaller) Uninstall(ctx context.Context, ui terminal.UI, log hclog.Logger) error { +func (i *NomadInstaller) Uninstall(ctx context.Context, opts *InstallOpts) error { + // TODO return nil } diff --git a/internal/serverinstall/serverinstall.go b/internal/serverinstall/serverinstall.go index dbdec0b6479..e618942f771 100644 --- a/internal/serverinstall/serverinstall.go +++ b/internal/serverinstall/serverinstall.go @@ -15,7 +15,13 @@ import ( type Installer interface { Install(ctx context.Context, ui terminal.UI, log hclog.Logger) (*clicontext.Config, *pb.ServerConfig_AdvertiseAddr, string, error) InstallFlags(*flag.Set) - Uninstall(context.Context, terminal.UI, hclog.Logger) error + + // Uninstall expects the Waypoint server to be uninstalled. + Uninstall(context.Context, *InstallOpts) error + + // UninstallFlags is called prior to Uninstall and allows the Uninstaller to + // specify flags for the uninstall CLI. The flags should be prefixed with the + // platform name to avoid conflicts with other flags. UninstallFlags(*flag.Set) } From 296c5c6938d215e0479a0d69d36e37515f1c4892 Mon Sep 17 00:00:00 2001 From: Rae Krantz <8461333+krantzinator@users.noreply.github.com> Date: Wed, 16 Dec 2020 14:24:16 +0000 Subject: [PATCH 6/7] backport of commit 6a257f0e8b26b7e13097c157efd43257fdc79136 --- internal/cli/uninstall.go | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/internal/cli/uninstall.go b/internal/cli/uninstall.go index 8c0617dd721..6603b7f2014 100644 --- a/internal/cli/uninstall.go +++ b/internal/cli/uninstall.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "os" "strings" "time" @@ -9,6 +10,7 @@ import ( "github.com/hashicorp/waypoint-plugin-sdk/terminal" "github.com/hashicorp/waypoint/internal/clierrors" + "github.com/hashicorp/waypoint/internal/clisnapshot" "github.com/hashicorp/waypoint/internal/pkg/flag" "github.com/hashicorp/waypoint/internal/serverinstall" ) @@ -19,7 +21,7 @@ type UninstallCommand struct { platform string snapshotFilename string skipSnapshot bool - flagConfirm bool + autoApprove bool deleteContext bool } @@ -38,8 +40,8 @@ func (c *UninstallCommand) Run(args []string) int { return 1 } - if !c.flagConfirm { - c.ui.Output(strings.TrimSpace(confirmReqMsg), terminal.WithErrorStyle()) + if !c.autoApprove { + c.ui.Output(strings.TrimSpace(autoApproveMsg), terminal.WithErrorStyle()) return 1 } @@ -70,15 +72,15 @@ func (c *UninstallCommand) Run(args []string) int { s.Update("Generating server snapshot...") defer s.Abort() // generate snapshot - // config := snapshot.Config{ - // Client: c.project.Client(), - // } - // w, err := os.Create(c.snapshotFilename) - // if err = config.WriteSnapshot(ctx, w); err != nil { - // fmt.Fprintf(os.Stderr, "Error generating snapshot: %s", err) - // return 1 - // } - // s.Update("Snapshot %q generated", c.snapshotFilename) + config := clisnapshot.Config{ + Client: c.project.Client(), + } + w, err := os.Create(c.snapshotFilename) + if err = config.WriteSnapshot(ctx, w); err != nil { + fmt.Fprintf(os.Stderr, "Error generating snapshot: %s", err) + return 1 + } + s.Update("Snapshot %q generated", c.snapshotFilename) } else { s.Update("skip-snapshot set; not generating server snapshot") s.Status(terminal.StatusWarn) @@ -148,10 +150,10 @@ func (c *UninstallCommand) Flags() *flag.Sets { return c.flagSet(0, func(set *flag.Sets) { f := set.NewSet("Command Options") f.BoolVar(&flag.BoolVar{ - Name: "confirm", - Target: &c.flagConfirm, + Name: "auto-approve", + Target: &c.autoApprove, Default: false, - Usage: "Confirm server uninstallation.", + Usage: "Auto-approve server uninstallation.", }) f.BoolVar(&flag.BoolVar{ @@ -185,8 +187,8 @@ func (c *UninstallCommand) Flags() *flag.Sets { } var ( - confirmReqMsg = strings.TrimSpace(` -Uninstalling Waypoint server requires confirmation. -Rerun the command with ‘-confirm’ to continue with the uninstall. + autoApproveMsg = strings.TrimSpace(` +Uninstalling Waypoint server requires approval. +Rerun the command with -auto-approve to continue with the uninstall. `) ) From 1251289704c14a14896dc7f702e8e81914c3c21d Mon Sep 17 00:00:00 2001 From: Rae Krantz <8461333+krantzinator@users.noreply.github.com> Date: Fri, 18 Dec 2020 22:48:46 +0000 Subject: [PATCH 7/7] backport of commit ad65ea5173598040842845554d8f04845c873556 --- internal/cli/uninstall.go | 18 ++- internal/serverinstall/docker.go | 36 +++++ internal/serverinstall/k8s.go | 221 ++++++++++++++++++++++++++++++- 3 files changed, 264 insertions(+), 11 deletions(-) diff --git a/internal/cli/uninstall.go b/internal/cli/uninstall.go index 6603b7f2014..ca55cc9e121 100644 --- a/internal/cli/uninstall.go +++ b/internal/cli/uninstall.go @@ -35,7 +35,6 @@ func (c *UninstallCommand) Run(args []string) int { WithArgs(args), WithFlags(c.Flags()), WithNoConfig(), - WithClient(false), ); err != nil { return 1 } @@ -54,6 +53,8 @@ func (c *UninstallCommand) Run(args []string) int { // - name the context we'll be uninstalling // - generate a snapshot of the current install s := sg.Add("") + defer func() { s.Abort() }() + contextDefault, err := c.contextStorage.Default() if err != nil { c.ui.Output(clierrors.Humanize(err), terminal.WithErrorStyle()) @@ -72,11 +73,8 @@ func (c *UninstallCommand) Run(args []string) int { s.Update("Generating server snapshot...") defer s.Abort() // generate snapshot - config := clisnapshot.Config{ - Client: c.project.Client(), - } w, err := os.Create(c.snapshotFilename) - if err = config.WriteSnapshot(ctx, w); err != nil { + if err = clisnapshot.WriteSnapshot(ctx, c.project.Client(), w); err != nil { fmt.Fprintf(os.Stderr, "Error generating snapshot: %s", err) return 1 } @@ -87,6 +85,9 @@ func (c *UninstallCommand) Run(args []string) int { } s.Done() + // TODO: should we check if any deployments are running, and exit with + // a warning to run `waypoint destroy` before proceeding? + // Uninstall p, ok := serverinstall.Platforms[strings.ToLower(c.platform)] if !ok { @@ -173,7 +174,7 @@ func (c *UninstallCommand) Flags() *flag.Sets { f.StringVar(&flag.StringVar{ Name: "snapshot-filename", Target: &c.snapshotFilename, - Default: fmt.Sprintf("sever-snapshot-%d", time.Now().Unix()), + Default: fmt.Sprintf("waypoint-sever-snapshot-%d", time.Now().Unix()), Usage: "Filename to write the snapshot to.", }) @@ -183,6 +184,11 @@ func (c *UninstallCommand) Flags() *flag.Sets { Default: false, Usage: "Skip creating a snapshot of the Waypoint server.", }) + + for name, platform := range serverinstall.Platforms { + platformSet := set.NewSet(name + " Options") + platform.UninstallFlags(platformSet) + } }) } diff --git a/internal/serverinstall/docker.go b/internal/serverinstall/docker.go index e449c4fb51c..a099f571bd9 100644 --- a/internal/serverinstall/docker.go +++ b/internal/serverinstall/docker.go @@ -327,6 +327,42 @@ func (i *DockerInstaller) Uninstall( s.Update("Docker volume %q removed", containerName) s.Done() + s = sg.Add("") + + imageRef, err := reference.ParseNormalizedNamed(i.config.serverImage) + if err != nil { + return fmt.Errorf("Error parsing Docker image: %s", err) + } + + imageList, err := cli.ImageList(ctx, types.ImageListOptions{ + Filters: filters.NewArgs(filters.KeyValuePair{ + Key: "reference", + Value: reference.FamiliarString(imageRef), + }), + }) + if err != nil { + return err + } + if len(imageList) < 1 { + s.Update("Could not find image %q, not removing", imageRef.Name()) + s.Status(terminal.StatusWarn) + s.Done() + return nil + } + + // Pick the first image, as there should be only one. + imageId := imageList[0].ID + _, err = cli.ImageRemove(ctx, imageId, types.ImageRemoveOptions{}) + if err != nil { + s.Update("Could not find image %q, not removing", imageRef.Name()) + s.Status(terminal.StatusWarn) + s.Done() + return nil + } + + s.Update("Docker image %q removed", imageRef.Name()) + s.Done() + return nil } diff --git a/internal/serverinstall/k8s.go b/internal/serverinstall/k8s.go index 29daa50c23e..8e51e7b533a 100644 --- a/internal/serverinstall/k8s.go +++ b/internal/serverinstall/k8s.go @@ -111,6 +111,7 @@ func (i *K8sInstaller) Install( s.Status(terminal.StatusWarn) s.Done() s = sg.Add("") + defer s.Abort() } if i.config.secretFile != "" { @@ -168,6 +169,7 @@ func (i *K8sInstaller) Install( s.Done() s = sg.Add("") + defer s.Abort() } // Do some probing to see if this is OpenShift. If so, we'll switch the config for the user. @@ -205,6 +207,7 @@ func (i *K8sInstaller) Install( return nil, nil, "", err } + defer s.Abort() s.Update("Creating Kubernetes resources...") serviceClient := clientset.CoreV1().Services(i.config.namespace) @@ -227,10 +230,11 @@ func (i *K8sInstaller) Install( s.Done() s = sg.Add("Waiting for Kubernetes StatefulSet to be ready...") + defer s.Abort() log.Info("waiting for server statefulset to become ready") err = wait.PollImmediate(2*time.Second, 10*time.Minute, func() (bool, error) { ss, err := clientset.AppsV1().StatefulSets(i.config.namespace).Get( - ctx, "waypoint-server", metav1.GetOptions{}) + ctx, i.config.serverName, metav1.GetOptions{}) if err != nil { return false, err } @@ -253,6 +257,7 @@ func (i *K8sInstaller) Install( s.Done() s = sg.Add("Waiting for Kubernetes service to become ready..") + defer s.Abort() // Wait for our service to be ready log.Info("waiting for server service to become ready") @@ -624,10 +629,199 @@ func (i *K8sInstaller) InstallFlags(set *flag.Set) { } func (i *K8sInstaller) Uninstall(ctx context.Context, opts *InstallOpts) error { - // TODO - // deleteStatefulSet() - // delete pvc - // delete svc + ui := opts.UI + log := opts.Log + + sg := ui.StepGroup() + defer sg.Wait() + + s := sg.Add("Inspecting Kubernetes cluster...") + defer func() { s.Abort() }() + + // Build our k8s client + newCmdConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientcmd.NewDefaultClientConfigLoadingRules(), + &clientcmd.ConfigOverrides{}, + ) + + // Discover the current target namespace in the user's config so that we target + // the current active kubectl target for the waypoint uninstall. Since we + // target the default naemspace for Install, we shouldn't target it for + // Uninstall either + if i.config.namespace == "" { + namespace, _, err := newCmdConfig.Namespace() + if err != nil { + ui.Output( + "Error getting namespace from client config: %s", clierrors.Humanize(err), + terminal.WithErrorStyle(), + ) + } + i.config.namespace = namespace + } + + // initialize the k8s client + clientconfig, err := newCmdConfig.ClientConfig() + if err != nil { + ui.Output( + "Error initializing kubernetes client: %s", clierrors.Humanize(err), + terminal.WithErrorStyle(), + ) + return err + } + + // init new clientset + clientset, err := kubernetes.NewForConfig(clientconfig) + if err != nil { + ui.Output( + "Error initializing kubernetes client: %s", clierrors.Humanize(err), + terminal.WithErrorStyle(), + ) + return err + } + + // delete statefulset and pods + // TOOD - DeleteCollection or no? + s.Update("Deleting statefulset and pods...") + + // create our wait channel to later poll for statefulset+pod deletion + w, err := clientset.AppsV1().StatefulSets(i.config.namespace).Watch( + ctx, + metav1.ListOptions{ + LabelSelector: "app=" + i.config.serverName, + }, + ) + + // send DELETE to statefulset and pods, including any waypoint deployments + if err = clientset.AppsV1().StatefulSets(i.config.namespace).DeleteCollection( + ctx, + metav1.DeleteOptions{}, + metav1.ListOptions{ + LabelSelector: "app=" + i.config.serverName, + }, + ); err != nil { + ui.Output( + "Error deleting Waypoint statefulset: %s", clierrors.Humanize(err), + terminal.WithErrorStyle(), + ) + return err + } + + // wait for deletion to complete + err = wait.PollImmediate(2*time.Second, 10*time.Minute, func() (bool, error) { + select { + case wCh := <-w.ResultChan(): + if wCh.Type == "DELETED" { + w.Stop() + return true, nil + } + log.Trace("statefulset collection not fully removed, waiting") + return false, nil + default: + log.Trace("no message received on watch.ResultChan(), waiting for Event") + return false, nil + } + }) + if err != nil { + return err + } + s.Update("Statefulset and pods deleted") + s.Done() + + s = sg.Add("") + defer func() { s.Abort() }() + + s.Update("Deleting persistent volume claims...") + + // create our wait channel to later poll for pvc deletion + w, err = clientset.CoreV1().PersistentVolumeClaims(i.config.namespace).Watch( + ctx, + metav1.ListOptions{ + LabelSelector: "app=" + i.config.serverName, + }, + ) + + // delete persistent volume claims + if err = clientset.CoreV1().PersistentVolumeClaims(i.config.namespace).DeleteCollection( + ctx, + metav1.DeleteOptions{}, + metav1.ListOptions{ + LabelSelector: "app=" + i.config.serverName, + }, + ); err != nil { + ui.Output( + "Error deleting Waypoint pvc: %s", clierrors.Humanize(err), + terminal.WithErrorStyle(), + ) + return err + } + // wait for deletion to complete + err = wait.PollImmediate(2*time.Second, 10*time.Minute, func() (bool, error) { + select { + case wCh := <-w.ResultChan(): + if wCh.Type == "DELETED" { + w.Stop() + return true, nil + } + log.Trace("persistent volume claims collection not fully removed, waiting") + return false, nil + default: + log.Trace("no message received on watch.ResultChan(), waiting for Event") + return false, nil + } + }) + if err != nil { + return err + } + + s.Update("Persistent Volume Claim deleted") + s.Done() + + s = sg.Add("") + defer func() { s.Abort() }() + + s.Update("Deleting service...") + + // create our wait channel to later poll for service deletion + w, err = clientset.CoreV1().Services(i.config.namespace).Watch( + ctx, + metav1.ListOptions{ + LabelSelector: "app=" + i.config.serverName, + }, + ) + + // delete waypoint service + if err = clientset.CoreV1().Services(i.config.namespace).Delete( + ctx, + i.config.serviceName, + metav1.DeleteOptions{}, + ); err != nil { + ui.Output( + "Error deleting Waypoint service: %s", clierrors.Humanize(err), + terminal.WithErrorStyle(), + ) + return err + } + // wait for deletion to complete + err = wait.PollImmediate(2*time.Second, 10*time.Minute, func() (bool, error) { + select { + case wCh := <-w.ResultChan(): + if wCh.Type == "DELETED" { + w.Stop() + return true, nil + } + log.Trace("no message received on watch.ResultChan(), waiting for Event") + return false, nil + default: + log.Trace("persistent volume claims not fully removed, waiting") + return false, nil + } + }) + if err != nil { + return err + } + + s.Update("Service deleted") + s.Done() return nil } @@ -643,6 +837,23 @@ func (i *K8sInstaller) UninstallFlags(set *flag.Set) { Usage: "Docker image for the Waypoint server.", Default: "hashicorp/waypoint:latest", }) + + // TODO: save serverName and serviceName in serverconfig as part + // of Install so we don't need to ask for it again + + set.StringVar(&flag.StringVar{ + Name: "k8s-server-name", + Target: &i.config.serverName, + Usage: "Name of the Waypoint server for Kubernetes.", + Default: "waypoint-server", + }) + + set.StringVar(&flag.StringVar{ + Name: "k8s-service-name", + Target: &i.config.serviceName, + Usage: "Name of the Kubernetes service for the Waypoint server.", + Default: "waypoint", + }) } func int32Ptr(i int32) *int32 {