mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
feat: init 'machine rm' command that outputs service containers that will be removed
This commit is contained in:
@@ -14,18 +14,18 @@ import (
|
||||
)
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
var clusterContext string
|
||||
var contextName string
|
||||
cmd := &cobra.Command{
|
||||
Use: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Short: "List machines in a cluster.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||
return list(cmd.Context(), uncli, clusterContext)
|
||||
return list(cmd.Context(), uncli, contextName)
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(
|
||||
&clusterContext, "context", "c", "",
|
||||
&contextName, "context", "c", "",
|
||||
"Name of the cluster context. (default is the current context)",
|
||||
)
|
||||
return cmd
|
||||
@@ -68,7 +68,8 @@ func list(ctx context.Context, uncli *cli.CLI, clusterName string) error {
|
||||
}
|
||||
|
||||
if _, err = fmt.Fprintf(
|
||||
tw, "%s\t%s\t%s\t%s\t%s\n", m.Name, capitalise(member.State.String()), subnet, publicIP, strings.Join(endpoints, ", "),
|
||||
tw, "%s\t%s\t%s\t%s\t%s\n", m.Name, capitalise(member.State.String()), subnet, publicIP,
|
||||
strings.Join(endpoints, ", "),
|
||||
); err != nil {
|
||||
return fmt.Errorf("write row: %w", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/lipgloss/tree"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type removeOptions struct {
|
||||
force bool
|
||||
yes bool
|
||||
context string
|
||||
}
|
||||
|
||||
func NewRmCommand() *cobra.Command {
|
||||
opts := removeOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm MACHINE",
|
||||
Aliases: []string{"remove", "delete"},
|
||||
Short: "Remove a machine from a cluster.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||
return remove(cmd.Context(), uncli, args[0], opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
|
||||
"Name of the cluster context. (default is the current context)")
|
||||
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
||||
"Do not prompt for confirmation before removing the machine.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func remove(ctx context.Context, uncli *cli.CLI, machineName string, opts removeOptions) error {
|
||||
client, err := uncli.ConnectCluster(ctx, opts.context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to cluster: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Verify the machine exists and list all service containers on it including stopped ones.
|
||||
listCtx, machines, err := api.ProxyMachinesContext(ctx, client, []string{machineName})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(machines) == 0 {
|
||||
return fmt.Errorf("machine '%s' not found in the cluster", machineName)
|
||||
}
|
||||
m := machines[0].Machine
|
||||
|
||||
listOpts := container.ListOptions{All: true}
|
||||
machineContainers, err := client.Docker.ListServiceContainers(listCtx, "", listOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list containers: %w", err)
|
||||
}
|
||||
containers := machineContainers[0].Containers
|
||||
|
||||
if len(containers) > 0 {
|
||||
fmt.Printf("Found %d service containers on machine '%s':\n\n", len(containers), m.Name)
|
||||
fmt.Println(formatContainerTree(containers))
|
||||
fmt.Println()
|
||||
fmt.Println("This will remove all service containers on the machine, reset it to the uninitialised state, " +
|
||||
"and remove it from the cluster.")
|
||||
} else {
|
||||
fmt.Printf("No service containers found on machine '%s'.\n", m.Name)
|
||||
fmt.Println("This will reset the machine to the uninitialised state and remove it from the cluster.")
|
||||
}
|
||||
|
||||
if !opts.yes {
|
||||
confirmed, err := cli.Confirm()
|
||||
if err != nil {
|
||||
return fmt.Errorf("confirm removal: %w", err)
|
||||
}
|
||||
if !confirmed {
|
||||
fmt.Println("Cancelled. Machine was not removed.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 3. Remove all service containers on the machine.
|
||||
// TODO: 4. Implement and call ResetMachine via Machine API to reset the machine state to uninitialised.
|
||||
// TODO: 5. Remove the machine from the cluster store.
|
||||
|
||||
fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// formatContainerTree formats a list of containers grouped by service as a tree structure.
|
||||
func formatContainerTree(containers []api.ServiceContainer) string {
|
||||
if len(containers) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Group containers by service.
|
||||
serviceContainers := make(map[string][]api.ServiceContainer)
|
||||
for _, ctr := range containers {
|
||||
serviceName := ctr.ServiceName()
|
||||
serviceContainers[serviceName] = append(serviceContainers[serviceName], ctr)
|
||||
}
|
||||
|
||||
// Build tree output.
|
||||
var output []string
|
||||
serviceNames := slices.Sorted(maps.Keys(serviceContainers))
|
||||
for _, serviceName := range serviceNames {
|
||||
ctrs := serviceContainers[serviceName]
|
||||
mode := ctrs[0].ServiceMode()
|
||||
|
||||
// Format a tree for the service with its containers.
|
||||
plural := ""
|
||||
if len(ctrs) > 1 {
|
||||
plural = "s"
|
||||
}
|
||||
t := tree.Root(fmt.Sprintf("• %s (%s, %d container%s)", serviceName, mode, len(ctrs), plural)).
|
||||
EnumeratorStyle(lipgloss.NewStyle().MarginLeft(2).MarginRight(1))
|
||||
|
||||
// Add containers as children.
|
||||
for _, ctr := range ctrs {
|
||||
state, _ := ctr.HumanState()
|
||||
info := fmt.Sprintf("%s • %s • %s", ctr.Name, ctr.Config.Image, state)
|
||||
t.Child(info)
|
||||
}
|
||||
|
||||
output = append(output, t.String())
|
||||
}
|
||||
|
||||
return strings.Join(output, "\n")
|
||||
}
|
||||
@@ -14,6 +14,7 @@ func NewRootCommand() *cobra.Command {
|
||||
NewAddCommand(),
|
||||
NewInitCommand(),
|
||||
NewListCommand(),
|
||||
NewRmCommand(),
|
||||
NewTokenCommand(),
|
||||
)
|
||||
return cmd
|
||||
|
||||
Reference in New Issue
Block a user