From 4a12217e657d588d394b48a65bd84a5a6cc48e02 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Wed, 29 Apr 2026 04:18:05 +0200 Subject: [PATCH] feat: add uc CLI argument completion for machines, services, volumes (#309) * Allow for argument completion This shows for a single command on how to complete service names in the cluster: `uc inspect ` will complete using ListServices See: #307 Signed-off-by: Miek Gieben * All cluster interactions can be completed Signed-off-by: Miek Gieben * Machines -m flag completion Signed-off-by: Miek Gieben * Add completion for "ctx use" Signed-off-by: Miek Gieben * Complete from compose file too Signed-off-by: Miek Gieben * Allow for argument completion This shows for a single command on how to complete service names in the cluster: `uc inspect ` will complete using ListServices See: #307 Signed-off-by: Miek Gieben * All cluster interactions can be completed Signed-off-by: Miek Gieben * Machines -m flag completion Signed-off-by: Miek Gieben * Add completion for "ctx use" Signed-off-by: Miek Gieben * Complete from compose file too Signed-off-by: Miek Gieben * Move to better place Signed-off-by: Miek Gieben * Adjust imports Signed-off-by: Miek Gieben * prefix match for context too Signed-off-by: Miek Gieben --------- Signed-off-by: Miek Gieben --- cmd/uncloud/build.go | 6 +++++ cmd/uncloud/caddy/config.go | 3 +++ cmd/uncloud/caddy/deploy.go | 3 +++ cmd/uncloud/context/use.go | 8 ++++++ cmd/uncloud/deploy.go | 4 +++ cmd/uncloud/image/ls.go | 3 +++ cmd/uncloud/image/push.go | 3 +++ cmd/uncloud/machine/rm.go | 8 ++++++ cmd/uncloud/machine/update.go | 8 ++++++ cmd/uncloud/service/exec.go | 8 ++++++ cmd/uncloud/service/inspect.go | 8 ++++++ cmd/uncloud/service/logs.go | 9 +++++++ cmd/uncloud/service/rm.go | 5 ++++ cmd/uncloud/service/run.go | 3 +++ cmd/uncloud/service/scale.go | 8 ++++++ cmd/uncloud/service/start.go | 5 ++++ cmd/uncloud/service/stop.go | 5 ++++ cmd/uncloud/volume/create.go | 3 +++ cmd/uncloud/volume/inspect.go | 10 ++++++++ cmd/uncloud/volume/ls.go | 3 +++ cmd/uncloud/volume/rm.go | 7 +++++ cmd/uncloud/wg/wg.go | 4 +++ internal/cli/completion/compose.go | 30 ++++++++++++++++++++++ internal/cli/completion/context.go | 28 ++++++++++++++++++++ internal/cli/completion/machine.go | 41 ++++++++++++++++++++++++++++++ internal/cli/completion/service.go | 34 +++++++++++++++++++++++++ internal/cli/completion/volume.go | 34 +++++++++++++++++++++++++ 27 files changed, 291 insertions(+) create mode 100644 internal/cli/completion/compose.go create mode 100644 internal/cli/completion/context.go create mode 100644 internal/cli/completion/machine.go create mode 100644 internal/cli/completion/service.go create mode 100644 internal/cli/completion/volume.go diff --git a/cmd/uncloud/build.go b/cmd/uncloud/build.go index eeacf056..2cc6aaa8 100644 --- a/cmd/uncloud/build.go +++ b/cmd/uncloud/build.go @@ -6,6 +6,7 @@ import ( composecli "github.com/compose-spec/compose-go/v2/cli" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/pkg/client/compose" "github.com/spf13/cobra" ) @@ -50,6 +51,9 @@ to cluster machines or --push-registry to upload them to external registries.`, return runBuild(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().StringArrayVar(&opts.BuildArgs, "build-arg", nil, @@ -76,6 +80,8 @@ to cluster machines or --push-registry to upload them to external registries.`, cmd.Flags().BoolVar(&opts.PushRegistry, "push-registry", false, "Upload the built images to external registries (e.g., Docker Hub) after building.") + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/caddy/config.go b/cmd/uncloud/caddy/config.go index 28d6733d..c159df72 100644 --- a/cmd/uncloud/caddy/config.go +++ b/cmd/uncloud/caddy/config.go @@ -7,6 +7,7 @@ import ( "github.com/alecthomas/chroma/v2/quick" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/spf13/cobra" ) @@ -33,6 +34,8 @@ func NewConfigCommand() *cobra.Command { cmd.Flags().BoolVar(&opts.noColor, "no-color", false, "Disable syntax highlighting for the output.") + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/caddy/deploy.go b/cmd/uncloud/caddy/deploy.go index 8cdcb12f..c3acc4ef 100644 --- a/cmd/uncloud/caddy/deploy.go +++ b/cmd/uncloud/caddy/deploy.go @@ -13,6 +13,7 @@ import ( "github.com/docker/cli/cli/streams" "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/internal/machine/api/pb" "github.com/psviderski/uncloud/pkg/api" @@ -48,6 +49,8 @@ func NewDeployCommand() *cobra.Command { "Machine names or IDs to deploy to. Can be specified multiple times or as a comma-separated "+ "list. (default is all machines)") + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/context/use.go b/cmd/uncloud/context/use.go index 5e9c39bf..37eb8076 100644 --- a/cmd/uncloud/context/use.go +++ b/cmd/uncloud/context/use.go @@ -7,6 +7,7 @@ import ( "charm.land/huh/v2" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/spf13/cobra" ) @@ -29,6 +30,13 @@ func NewUseCommand() *cobra.Command { return selectContext(uncli) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Contexts(cmd.Context(), uncli, args, toComplete) + }, } return cmd diff --git a/cmd/uncloud/deploy.go b/cmd/uncloud/deploy.go index dabf839b..2a6bec19 100644 --- a/cmd/uncloud/deploy.go +++ b/cmd/uncloud/deploy.go @@ -11,6 +11,7 @@ import ( "github.com/docker/compose/v2/pkg/progress" "github.com/docker/docker/pkg/stringid" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/internal/cli/logs" "github.com/psviderski/uncloud/internal/cli/tui" "github.com/psviderski/uncloud/pkg/api" @@ -48,6 +49,9 @@ func NewDeployCommand() *cobra.Command { return runDeploy(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().StringArrayVar(&opts.BuildServicesOptions.BuildArgs, "build-arg", nil, diff --git a/cmd/uncloud/image/ls.go b/cmd/uncloud/image/ls.go index 6e6e9ce5..324a7e08 100644 --- a/cmd/uncloud/image/ls.go +++ b/cmd/uncloud/image/ls.go @@ -16,6 +16,7 @@ import ( "github.com/docker/go-units" "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/spf13/cobra" @@ -62,6 +63,8 @@ func NewListCommand() *cobra.Command { "Filter images by machine name or ID. Can be specified multiple times or as a comma-separated list. "+ "(default is include all machines)") + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/image/push.go b/cmd/uncloud/image/push.go index fca1bd36..09394f3d 100644 --- a/cmd/uncloud/image/push.go +++ b/cmd/uncloud/image/push.go @@ -7,6 +7,7 @@ import ( "github.com/containerd/platforms" "github.com/docker/compose/v2/pkg/progress" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/pkg/client" "github.com/spf13/cobra" ) @@ -54,6 +55,8 @@ The image is uploaded to all cluster machines (default) or the specified machine "Local Docker must be configured to use containerd image store to support multi-platform images.", ) + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/machine/rm.go b/cmd/uncloud/machine/rm.go index 64633a4d..537079e0 100644 --- a/cmd/uncloud/machine/rm.go +++ b/cmd/uncloud/machine/rm.go @@ -14,6 +14,7 @@ import ( "github.com/docker/compose/v2/pkg/progress" "github.com/docker/docker/api/types/container" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/internal/cli/tui" "github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/pkg/api" @@ -37,6 +38,13 @@ func NewRmCommand() *cobra.Command { uncli := cmd.Context().Value("cli").(*cli.CLI) return remove(cmd.Context(), uncli, args[0], opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Machines(cmd.Context(), uncli, args, toComplete) + }, } cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false, diff --git a/cmd/uncloud/machine/update.go b/cmd/uncloud/machine/update.go index 79e63f94..7fa013b4 100644 --- a/cmd/uncloud/machine/update.go +++ b/cmd/uncloud/machine/update.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/internal/machine/network" "github.com/spf13/cobra" @@ -46,6 +47,13 @@ At least one flag must be specified to perform an update.`, uncli := cmd.Context().Value("cli").(*cli.CLI) return update(cmd.Context(), uncli, cmd, opts, args[0]) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Machines(cmd.Context(), uncli, args, toComplete) + }, } cmd.Flags().StringVar( diff --git a/cmd/uncloud/service/exec.go b/cmd/uncloud/service/exec.go index b05f0051..0bd638f6 100644 --- a/cmd/uncloud/service/exec.go +++ b/cmd/uncloud/service/exec.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli/streams" "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/spf13/cobra" @@ -55,6 +56,13 @@ If the service has multiple replicas and no container ID is specified, the comma return runExec(cmd.Context(), uncli, serviceName, command, opts) }, GroupID: groupID, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Services(cmd.Context(), uncli, args, toComplete) + }, } execCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: run command in the background") diff --git a/cmd/uncloud/service/inspect.go b/cmd/uncloud/service/inspect.go index 0fb7e59d..09ab7156 100644 --- a/cmd/uncloud/service/inspect.go +++ b/cmd/uncloud/service/inspect.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/pkg/stringid" "github.com/docker/go-units" "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/spf13/cobra" @@ -30,6 +31,13 @@ func NewInspectCommand(groupID string) *cobra.Command { return inspect(cmd.Context(), uncli, opts) }, GroupID: groupID, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Services(cmd.Context(), uncli, args, toComplete) + }, } return cmd } diff --git a/cmd/uncloud/service/logs.go b/cmd/uncloud/service/logs.go index 7249b475..218ee424 100644 --- a/cmd/uncloud/service/logs.go +++ b/cmd/uncloud/service/logs.go @@ -8,6 +8,7 @@ import ( mapset "github.com/deckarep/golang-set/v2" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/internal/cli/logs" "github.com/psviderski/uncloud/internal/cli/tui" "github.com/psviderski/uncloud/pkg/api" @@ -61,12 +62,20 @@ If no services are specified, streams logs from all services defined in the Comp return runLogs(cmd.Context(), uncli, args, options) }, GroupID: groupID, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Services(cmd.Context(), uncli, args, toComplete) + }, } cmd.Flags().StringSliceVar(&options.Files, "file", nil, "One or more Compose files to load service names from when no services are specified. (default compose.yaml)") cmd.Flags().AddFlagSet(logs.Flags(&options)) + completion.MachinesFlag(cmd) + + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/service/rm.go b/cmd/uncloud/service/rm.go index 88a4faf7..12ce4237 100644 --- a/cmd/uncloud/service/rm.go +++ b/cmd/uncloud/service/rm.go @@ -6,6 +6,7 @@ import ( "github.com/docker/compose/v2/pkg/progress" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/spf13/cobra" ) @@ -31,6 +32,10 @@ directives in image Dockerfiles) are automatically removed with their containers return rm(cmd.Context(), uncli, opts) }, GroupID: groupID, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Services(cmd.Context(), uncli, args, toComplete) + }, } return cmd } diff --git a/cmd/uncloud/service/run.go b/cmd/uncloud/service/run.go index 6f1d733a..01ff5eca 100644 --- a/cmd/uncloud/service/run.go +++ b/cmd/uncloud/service/run.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/daemon/names" "github.com/docker/go-units" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/internal/secret" "github.com/psviderski/uncloud/pkg/api" "github.com/psviderski/uncloud/pkg/client/deploy" @@ -124,6 +125,8 @@ func NewRunCommand(groupID string) *cobra.Command { " -v /data/uploads:/app/uploads Bind mount /data/uploads host directory to /app/uploads in container\n"+ " -v /host/path:/container/path:ro Bind mount a host directory or file as read-only") + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/service/scale.go b/cmd/uncloud/service/scale.go index 84ea8f67..6c32c0d8 100644 --- a/cmd/uncloud/service/scale.go +++ b/cmd/uncloud/service/scale.go @@ -11,6 +11,7 @@ import ( "charm.land/lipgloss/v2" "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/spf13/cobra" @@ -44,6 +45,13 @@ func NewScaleCommand(groupID string) *cobra.Command { return scale(cmd.Context(), uncli, opts) }, GroupID: groupID, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Services(cmd.Context(), uncli, args, toComplete) + }, } cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false, diff --git a/cmd/uncloud/service/start.go b/cmd/uncloud/service/start.go index ce1f75b3..73e283e2 100644 --- a/cmd/uncloud/service/start.go +++ b/cmd/uncloud/service/start.go @@ -7,6 +7,7 @@ import ( "github.com/docker/compose/v2/pkg/progress" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/spf13/cobra" ) @@ -30,6 +31,10 @@ Services can be specified by name or ID.`, return start(cmd.Context(), uncli, opts) }, GroupID: groupID, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Services(cmd.Context(), uncli, args, toComplete) + }, } return cmd } diff --git a/cmd/uncloud/service/stop.go b/cmd/uncloud/service/stop.go index 6c03fcc5..81469d7c 100644 --- a/cmd/uncloud/service/stop.go +++ b/cmd/uncloud/service/stop.go @@ -8,6 +8,7 @@ import ( "github.com/docker/compose/v2/pkg/progress" "github.com/docker/docker/api/types/container" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/spf13/cobra" ) @@ -33,6 +34,10 @@ Services can be specified by name or ID. Stopped services can be restarted with return stop(cmd.Context(), uncli, opts) }, GroupID: groupID, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Services(cmd.Context(), uncli, args, toComplete) + }, } cmd.Flags().StringVarP(&opts.signal, "signal", "s", "", "Signal to send to each container's main process.\n"+ diff --git a/cmd/uncloud/volume/create.go b/cmd/uncloud/volume/create.go index a74f0771..5e31b803 100644 --- a/cmd/uncloud/volume/create.go +++ b/cmd/uncloud/volume/create.go @@ -9,6 +9,7 @@ import ( "charm.land/huh/v2" "github.com/docker/docker/api/types/volume" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/pkg/api" "github.com/spf13/cobra" @@ -50,6 +51,8 @@ func NewCreateCommand() *cobra.Command { cmd.Flags().StringVarP(&opts.machine, "machine", "m", "", "Name or ID of the machine to create the volume on.") + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/volume/inspect.go b/cmd/uncloud/volume/inspect.go index dd8f66e0..eb00b780 100644 --- a/cmd/uncloud/volume/inspect.go +++ b/cmd/uncloud/volume/inspect.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/pkg/api" "github.com/spf13/cobra" ) @@ -26,12 +27,21 @@ func NewInspectCommand() *cobra.Command { uncli := cmd.Context().Value("cli").(*cli.CLI) return inspect(cmd.Context(), uncli, args[0], opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Volumes(cmd.Context(), uncli, args, toComplete) + }, } cmd.Flags().StringVarP(&opts.machine, "machine", "m", "", "Name or ID of the machine where the volume is located. "+ "If not specified, the volume will be searched across all machines.") + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/volume/ls.go b/cmd/uncloud/volume/ls.go index 732ef495..e6b6d343 100644 --- a/cmd/uncloud/volume/ls.go +++ b/cmd/uncloud/volume/ls.go @@ -7,6 +7,7 @@ import ( "strings" "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/spf13/cobra" @@ -36,6 +37,8 @@ func NewListCommand() *cobra.Command { cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only display volume names.") + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/volume/rm.go b/cmd/uncloud/volume/rm.go index 2e2d2fcd..74e33d9b 100644 --- a/cmd/uncloud/volume/rm.go +++ b/cmd/uncloud/volume/rm.go @@ -6,6 +6,7 @@ import ( "fmt" "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/spf13/cobra" @@ -30,6 +31,10 @@ func NewRemoveCommand() *cobra.Command { uncli := cmd.Context().Value("cli").(*cli.CLI) return remove(cmd.Context(), uncli, args, opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Volumes(cmd.Context(), uncli, args, toComplete) + }, } cmd.Flags().BoolVarP(&opts.force, "force", "f", false, @@ -41,6 +46,8 @@ func NewRemoveCommand() *cobra.Command { cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false, "Do not prompt for confirmation before removing the volume(s).") + completion.MachinesFlag(cmd) + return cmd } diff --git a/cmd/uncloud/wg/wg.go b/cmd/uncloud/wg/wg.go index 0c921584..5a3ea680 100644 --- a/cmd/uncloud/wg/wg.go +++ b/cmd/uncloud/wg/wg.go @@ -8,6 +8,7 @@ import ( "github.com/docker/go-units" "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/completion" "github.com/psviderski/uncloud/internal/cli/tui" "github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/spf13/cobra" @@ -44,6 +45,9 @@ func newShowCommand() *cobra.Command { } cmd.Flags().StringVarP(&opts.machine, "machine", "m", "", "Name or ID of the machine to show the configuration for. (default is connected machine)") + + completion.MachinesFlag(cmd) + return cmd } diff --git a/internal/cli/completion/compose.go b/internal/cli/completion/compose.go new file mode 100644 index 00000000..35a6dd2c --- /dev/null +++ b/internal/cli/completion/compose.go @@ -0,0 +1,30 @@ +package completion + +import ( + "context" + "slices" + "strings" + + composecli "github.com/compose-spec/compose-go/v2/cli" + "github.com/psviderski/uncloud/pkg/client/compose" + "github.com/spf13/cobra" +) + +func ComposeServices(ctx context.Context, args []string, toComplete string, files, profiles []string) ([]cobra.Completion, cobra.ShellCompDirective) { + project, err := compose.LoadProject(ctx, files, composecli.WithDefaultProfiles(profiles...)) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + services := project.ServiceNames() + names := []cobra.Completion{} + for _, service := range services { + if slices.Contains(args, service) { + continue + } + if strings.HasPrefix(service, toComplete) { + names = append(names, service) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/internal/cli/completion/context.go b/internal/cli/completion/context.go new file mode 100644 index 00000000..77fa5b55 --- /dev/null +++ b/internal/cli/completion/context.go @@ -0,0 +1,28 @@ +package completion + +import ( + "context" + "maps" + "slices" + "strings" + + "github.com/psviderski/uncloud/internal/cli" + "github.com/spf13/cobra" +) + +func Contexts(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + contexts := slices.Sorted(maps.Keys(uncli.Config.Contexts)) + + names := []cobra.Completion{} + for _, context := range contexts { + if slices.Contains(args, context) { + continue + } + if strings.HasPrefix(context, toComplete) { + names = append(names, context) + } + names = append(names, context) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/internal/cli/completion/machine.go b/internal/cli/completion/machine.go new file mode 100644 index 00000000..33b2a569 --- /dev/null +++ b/internal/cli/completion/machine.go @@ -0,0 +1,41 @@ +package completion + +import ( + "context" + "slices" + "strings" + + "github.com/psviderski/uncloud/internal/cli" + "github.com/spf13/cobra" +) + +func Machines(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + client, err := uncli.ConnectCluster(ctx) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + defer client.Close() + + machines, err := client.ListMachines(ctx, nil) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + names := []cobra.Completion{} + for _, machine := range machines { + if slices.Contains(args, machine.Machine.Name) { + continue + } + if strings.HasPrefix(machine.Machine.Name, toComplete) { + names = append(names, machine.Machine.Name) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func MachinesFlag(cmd *cobra.Command) { + cmd.RegisterFlagCompletionFunc("machine", + func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + uncli := cmd.Context().Value("cli").(*cli.CLI) + return Machines(cmd.Context(), uncli, args, toComplete) + }) +} diff --git a/internal/cli/completion/service.go b/internal/cli/completion/service.go new file mode 100644 index 00000000..c91f0b1c --- /dev/null +++ b/internal/cli/completion/service.go @@ -0,0 +1,34 @@ +// Package completion implements completion functions for the uc cli. +package completion + +import ( + "context" + "slices" + "strings" + + "github.com/psviderski/uncloud/internal/cli" + "github.com/spf13/cobra" +) + +func Services(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + client, err := uncli.ConnectCluster(ctx) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + defer client.Close() + + services, err := client.ListServices(ctx) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + names := []cobra.Completion{} + for _, service := range services { + if slices.Contains(args, service.Name) { + continue + } + if strings.HasPrefix(service.Name, toComplete) { + names = append(names, service.Name) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/internal/cli/completion/volume.go b/internal/cli/completion/volume.go new file mode 100644 index 00000000..51e482c9 --- /dev/null +++ b/internal/cli/completion/volume.go @@ -0,0 +1,34 @@ +package completion + +import ( + "context" + "slices" + "strings" + + "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/pkg/api" + "github.com/spf13/cobra" +) + +func Volumes(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + client, err := uncli.ConnectCluster(ctx) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + defer client.Close() + + volumes, err := client.ListVolumes(ctx, &api.VolumeFilter{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + names := []cobra.Completion{} + for _, volume := range volumes { + if slices.Contains(args, volume.Volume.Name) { + continue + } + if strings.HasPrefix(volume.Volume.Name, toComplete) { + names = append(names, volume.Volume.Name) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +}