From ab7cda2ba615f0f05d161dfd12ec798c76022406 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 8 May 2026 17:09:16 +0200 Subject: [PATCH 1/2] feat: image completion this adds completion for both local and remote images. The current commands that deal with images are using this. Fixes: #361 Signed-off-by: Miek Gieben --- cmd/uncloud/image/ls.go | 4 ++ cmd/uncloud/image/push.go | 3 ++ cmd/uncloud/service/run.go | 6 +++ internal/cli/completion/image.go | 71 ++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 internal/cli/completion/image.go diff --git a/cmd/uncloud/image/ls.go b/cmd/uncloud/image/ls.go index 324a7e08..891352f5 100644 --- a/cmd/uncloud/image/ls.go +++ b/cmd/uncloud/image/ls.go @@ -57,6 +57,10 @@ func NewListCommand() *cobra.Command { uncli := cmd.Context().Value("cli").(*cli.CLI) return list(cmd.Context(), uncli, opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + uncli := cmd.Context().Value("cli").(*cli.CLI) + return completion.Images(cmd.Context(), uncli, args, toComplete) + }, } cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil, diff --git a/cmd/uncloud/image/push.go b/cmd/uncloud/image/push.go index 09394f3d..2c8ec82b 100644 --- a/cmd/uncloud/image/push.go +++ b/cmd/uncloud/image/push.go @@ -44,6 +44,9 @@ The image is uploaded to all cluster machines (default) or the specified machine opts.image = args[0] return push(cmd.Context(), uncli, opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + return completion.LocalImages(cmd.Context(), args, toComplete) + }, } cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil, diff --git a/cmd/uncloud/service/run.go b/cmd/uncloud/service/run.go index 01ff5eca..3570fafb 100644 --- a/cmd/uncloud/service/run.go +++ b/cmd/uncloud/service/run.go @@ -58,6 +58,12 @@ func NewRunCommand(groupID string) *cobra.Command { return run(cmd.Context(), uncli, opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return completion.LocalImages(cmd.Context(), args, toComplete) + }, GroupID: groupID, } diff --git a/internal/cli/completion/image.go b/internal/cli/completion/image.go new file mode 100644 index 00000000..52185138 --- /dev/null +++ b/internal/cli/completion/image.go @@ -0,0 +1,71 @@ +package completion + +import ( + "context" + "slices" + "strings" + + "github.com/docker/docker/api/types/image" + dockerclient "github.com/docker/docker/client" + "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/docker" + "github.com/psviderski/uncloud/pkg/api" + "github.com/spf13/cobra" +) + +// Images allows for completion of remote images. +func Images(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() + + machineImages, err := client.ListImages(ctx, api.ImageFilter{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + names := []cobra.Completion{} + for _, machineImage := range machineImages { + for _, image := range machineImage.Images { + if len(image.RepoTags) > 0 { + if slices.Contains(args, image.RepoTags[0]) { + continue + } + if strings.HasPrefix(image.RepoTags[0], toComplete) { + names = append(names, image.RepoTags[0]) + } + } + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +// LocalImages allows for local image completion. +func LocalImages(ctx context.Context, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { + dockerCliWrapped, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation()) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + dockerCli := &docker.Client{Client: dockerCliWrapped} + defer dockerCli.Close() + + images, err := dockerCli.ImageList(context.Background(), image.ListOptions{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + names := []cobra.Completion{} + for _, image := range images { + if len(image.RepoTags) > 0 { + if slices.Contains(args, image.RepoTags[0]) { + continue + } + if strings.HasPrefix(image.RepoTags[0], toComplete) { + names = append(names, image.RepoTags[0]) + } + } + } + return names, cobra.ShellCompDirectiveNoFileComp + +} From 55c8f589ea94e8503e3fcdee8f7d6779273ecdbc Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 8 May 2026 17:14:11 +0200 Subject: [PATCH 2/2] lint Signed-off-by: Miek Gieben --- internal/cli/completion/image.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/cli/completion/image.go b/internal/cli/completion/image.go index 52185138..88957ebd 100644 --- a/internal/cli/completion/image.go +++ b/internal/cli/completion/image.go @@ -67,5 +67,4 @@ func LocalImages(ctx context.Context, args []string, toComplete string) ([]cobra } } return names, cobra.ShellCompDirectiveNoFileComp - }