Skip to content
Open
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
5 changes: 3 additions & 2 deletions cmd/uc/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ type deployOptions struct {
func NewDeployCommand() *cobra.Command {
opts := deployOptions{}
cmd := &cobra.Command{
Use: "deploy [FLAGS] [SERVICE...]",
Short: "Deploy services from a Compose file.",
Use: "deploy [FLAGS] [SERVICE...]",
Aliases: []string{"up"},
Short: "Deploy services from a Compose file.",
RunE: func(cmd *cobra.Command, args []string) error {
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")

Expand Down
163 changes: 163 additions & 0 deletions cmd/uc/destroy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package main

import (
"context"
"errors"
"fmt"
"os"

"charm.land/lipgloss/v2"
composecli "github.com/compose-spec/compose-go/v2/cli"
"github.com/docker/compose/v2/pkg/progress"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/completion"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/pkg/api"
"github.com/psviderski/uncloud/pkg/client/compose"
"github.com/spf13/cobra"
)

type destroyOptions struct {
cli.BuildServicesOptions

files []string
profiles []string
services []string
yes bool
}

// NewDestroyCommand creates a new command to tear down services from a Compose file.
func NewDestroyCommand() *cobra.Command {
opts := destroyOptions{}
cmd := &cobra.Command{
Use: "destroy [FLAGS] [SERVICE...]",
Aliases: []string{"down", "undeploy"},
Short: "Destroy services from a Compose file.",
Long: `Destroy services from a Compose file.

Destroy removes all containers of the specified service(s) across all machines in the cluster.

See "uc service remove".`,
RunE: func(cmd *cobra.Command, args []string) error {
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")

uncli := cmd.Context().Value("cli").(*cli.CLI)
opts.services = args

return runDestroy(cmd.Context(), uncli, opts)
},
GroupID: "service",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
return completion.ComposeServices(cmd.Context(), args, toComplete, opts.files, opts.profiles)
},
}

cmd.Flags().StringSliceVarP(&opts.files, "file", "f", nil,
"One or more Compose files to destroy services from. (default compose.yaml)")
cmd.Flags().StringSliceVarP(&opts.profiles, "profile", "p", nil,
"One or more Compose profiles to enable.")
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
"Auto-confirm deployment plan. Should be explicitly set when running non-interactively,\n"+
"e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")

return cmd
}

// runDestroy parses the Compose file(s) and destroys the services.
func runDestroy(ctx context.Context, uncli *cli.CLI, opts destroyOptions) error {
project, err := compose.LoadProject(ctx, opts.files, composecli.WithDefaultProfiles(opts.profiles...))
if err != nil {
return fmt.Errorf("load compose file(s): %w", err)
}

uncli.SetClusterContextIfUnset(compose.ClusterContext(project))

if len(opts.services) > 0 {
project, err = project.WithSelectedServices(opts.services)
if err != nil {
return fmt.Errorf("select services: %w", err)
}
}

composeServices := append(project.ServiceNames(), project.DisabledServiceNames()...)
if len(composeServices) == 0 {
return errors.New("no services found in Compose file(s)")
}

client, err := uncli.ConnectCluster(ctx)
if err != nil {
return fmt.Errorf("connect to cluster: %w", err)
}
defer client.Close()

services := []api.Service{}
for _, service := range composeServices {
svc, err := client.InspectService(ctx, service)
if err != nil {
return fmt.Errorf("inspect service: %w", err)
}
services = append(services, svc)
}

t := tui.NewTable()
headers := []string{"NAME", "MODE", "REPLICAS"}
t.Headers(headers...)

for _, s := range services {
row := []string{s.Name, s.Mode, fmt.Sprintf("%d", len(s.Containers))}
t.Row(row...)
}
fmt.Println(t)

if !opts.yes {
if !tui.IsStdinTerminal() {
return errors.New("cannot ask to confirm destroy in non-interactive mode, " +
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
}

directConn := uncli.DirectConnection()
contextName := uncli.ContextOverrideOrCurrent()
destroyTarget := ""
if directConn != "" {
destroyTarget = directConn
fmt.Println(tui.Faint.Render("connection: ") + tui.NameStyle.Render(directConn))
fmt.Println()
} else if contextName != "" && len(uncli.Config.Contexts) > 1 {
destroyTarget = contextName
fmt.Println(tui.Faint.Render("context: ") + tui.NameStyle.Render(contextName))
fmt.Println()
}

title := "Proceed with destroy?"
// Include the direct connection or context name in the confirmation prompt to avoid accidentally
// destroying in the wrong cluster.
if destroyTarget != "" {
isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
confirmStyle := tui.ThemeConfirm().Theme(isDark).Focused.Title
title = "Proceed with destroy to " + tui.NameStyle.Render(destroyTarget) + confirmStyle.Render("?")
}

fmt.Println()
confirmed, err := tui.Confirm(title)
if err != nil {
return fmt.Errorf("confirm destroy: %w", err)
}
if !confirmed {
return cli.Cancelled("Destroy cancelled. No changes were made.")
}
}

if opts.yes {
fmt.Println() // slightly nicer in the output
}
for _, s := range composeServices {
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
if err = client.RemoveService(ctx, s); err != nil {
return fmt.Errorf("destroying service '%s': %w", s, err)
}
return nil
}, uncli.ProgressOut(), "Destroy service "+s)
}

return err
}
1 change: 1 addition & 0 deletions cmd/uc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func main() {
cmd.AddCommand(
NewBuildCommand(),
NewDeployCommand(),
NewDestroyCommand(),
NewDocsCommand(),
NewImagesCommand(),
NewPsCommand(),
Expand Down
1 change: 1 addition & 0 deletions website/docs/9-cli-reference/uc.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ A CLI tool for managing Uncloud resources such as machines, services, and volume
* [uc caddy](uc_caddy.md) - Manage Caddy reverse proxy service.
* [uc ctx](uc_ctx.md) - Switch between different cluster contexts. Contains subcommands to manage contexts.
* [uc deploy](uc_deploy.md) - Deploy services from a Compose file.
* [uc destroy](uc_destroy.md) - Destroy services from a Compose file.
* [uc dns](uc_dns.md) - Manage cluster domain in Uncloud DNS.
* [uc exec](uc_exec.md) - Execute a command in a running service container.
* [uc image](uc_image.md) - Manage images on machines in the cluster.
Expand Down
39 changes: 39 additions & 0 deletions website/docs/9-cli-reference/uc_destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# uc destroy

Destroy services from a Compose file.

## Synopsis

Destroy services from a Compose file.

Destroy removes all containers of the specified service(s) across all machines in the cluster.

See "uc service remove".

```
uc destroy [FLAGS] [SERVICE...] [flags]
```

## Options

```
-f, --file strings One or more Compose files to destroy services from. (default compose.yaml)
-h, --help help for destroy
-p, --profile strings One or more Compose profiles to enable.
-y, --yes Auto-confirm deployment plan. Should be explicitly set when running non-interactively,
e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]
```

## Options inherited from parent commands

```
--connect string Connect to a remote cluster machine without using the Uncloud configuration file. [$UNCLOUD_CONNECT]
Format: [ssh://]user@host[:port], ssh+go://user@host[:port], tcp://host:port, or unix:///path/to/uncloud.sock
-c, --context string Name of the cluster context to use (default is the current context). [$UNCLOUD_CONTEXT]
--uncloud-config string Path to the Uncloud configuration file. [$UNCLOUD_CONFIG] (default "~/.config/uncloud/config.yaml")
```

## See also

* [uc](uc.md) - A CLI tool for managing Uncloud resources such as machines, services, and volumes.

Loading