diff --git a/changelogs/unreleased/9720-Joeavaikath b/changelogs/unreleased/9720-Joeavaikath new file mode 100644 index 000000000..cde7a017f --- /dev/null +++ b/changelogs/unreleased/9720-Joeavaikath @@ -0,0 +1 @@ +Add dynamic resource autocompletion to Velero CLI diff --git a/pkg/cmd/cli/backup/create.go b/pkg/cmd/cli/backup/create.go index 5e18f468f..ae9dd2fec 100644 --- a/pkg/cmd/cli/backup/create.go +++ b/pkg/cmd/cli/backup/create.go @@ -32,6 +32,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" "github.com/vmware-tanzu/velero/pkg/util/collections" @@ -75,6 +76,10 @@ func NewCreateCommand(f client.Factory, use string) *cobra.Command { output.BindFlags(c.Flags()) output.ClearOutputFlagDefault(c) + _ = c.RegisterFlagCompletionFunc("from-schedule", cli.CompleteScheduleNames(f)) + _ = c.RegisterFlagCompletionFunc("storage-location", cli.CompleteBackupStorageLocationNames(f)) + _ = c.RegisterFlagCompletionFunc("volume-snapshot-locations", cli.CompleteVolumeSnapshotLocationNames(f)) + return c } diff --git a/pkg/cmd/cli/backup/delete.go b/pkg/cmd/cli/backup/delete.go index f4eaf1b83..ba5a4954b 100644 --- a/pkg/cmd/cli/backup/delete.go +++ b/pkg/cmd/cli/backup/delete.go @@ -64,6 +64,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteBackupNames(f) o.BindFlags(c.Flags()) return c diff --git a/pkg/cmd/cli/backup/describe.go b/pkg/cmd/cli/backup/describe.go index b0ef4a93e..dd819edd1 100644 --- a/pkg/cmd/cli/backup/describe.go +++ b/pkg/cmd/cli/backup/describe.go @@ -29,6 +29,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" "github.com/vmware-tanzu/velero/pkg/label" ) @@ -112,6 +113,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteBackupNames(f) c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector.") c.Flags().BoolVar(&details, "details", details, "Display additional detail in the command output.") c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.") diff --git a/pkg/cmd/cli/backup/download.go b/pkg/cmd/cli/backup/download.go index e4afd216c..a8d692520 100644 --- a/pkg/cmd/cli/backup/download.go +++ b/pkg/cmd/cli/backup/download.go @@ -31,6 +31,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/cacert" "github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest" ) @@ -55,6 +56,7 @@ func NewDownloadCommand(f client.Factory) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteBackupNames(f) o.BindFlags(c.Flags()) return c diff --git a/pkg/cmd/cli/backup/get.go b/pkg/cmd/cli/backup/get.go index 159fac30d..1af80399b 100644 --- a/pkg/cmd/cli/backup/get.go +++ b/pkg/cmd/cli/backup/get.go @@ -27,6 +27,7 @@ import ( api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" ) @@ -66,6 +67,7 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteBackupNames(f) c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector") output.BindFlags(c.Flags()) diff --git a/pkg/cmd/cli/backup/logs.go b/pkg/cmd/cli/backup/logs.go index a0149acf1..6e60c30f1 100644 --- a/pkg/cmd/cli/backup/logs.go +++ b/pkg/cmd/cli/backup/logs.go @@ -30,6 +30,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/cacert" "github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest" ) @@ -119,6 +120,7 @@ func NewLogsCommand(f client.Factory) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteBackupNames(f) l.BindFlags(c.Flags()) return c diff --git a/pkg/cmd/cli/backuplocation/delete.go b/pkg/cmd/cli/backuplocation/delete.go index 9c1e60507..eabadef97 100644 --- a/pkg/cmd/cli/backuplocation/delete.go +++ b/pkg/cmd/cli/backuplocation/delete.go @@ -62,6 +62,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteBackupStorageLocationNames(f) o.BindFlags(c.Flags()) return c } diff --git a/pkg/cmd/cli/backuplocation/get.go b/pkg/cmd/cli/backuplocation/get.go index fd7c057c2..964ae5a7e 100644 --- a/pkg/cmd/cli/backuplocation/get.go +++ b/pkg/cmd/cli/backuplocation/get.go @@ -27,6 +27,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" ) @@ -89,6 +90,7 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteBackupStorageLocationNames(f) c.Flags().BoolVar(&showDefaultOnly, "default", false, "Displays the current default backup storage location.") c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector.") diff --git a/pkg/cmd/cli/backuplocation/set.go b/pkg/cmd/cli/backuplocation/set.go index c1b52e536..2024f0761 100644 --- a/pkg/cmd/cli/backuplocation/set.go +++ b/pkg/cmd/cli/backuplocation/set.go @@ -33,6 +33,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" "github.com/vmware-tanzu/velero/pkg/util/boolptr" ) @@ -51,6 +52,7 @@ func NewSetCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteBackupStorageLocationNames(f) o.BindFlags(c.Flags()) return c diff --git a/pkg/cmd/cli/completion_functions.go b/pkg/cmd/cli/completion_functions.go new file mode 100644 index 000000000..3a7231484 --- /dev/null +++ b/pkg/cmd/cli/completion_functions.go @@ -0,0 +1,97 @@ +/* +Copyright The Velero Contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "context" + "strings" + "time" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/meta" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/client" +) + +// completionFunc is the function signature for cobra's ValidArgsFunction. +type completionFunc = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) + +// completeNames builds a completion function for any Velero list type. +// It extracts resource names via apimachinery's meta helpers. +func completeNames(f client.Factory, list kbclient.ObjectList) completionFunc { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kbClient, err := f.KubebuilderClient() + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + freshList := list.DeepCopyObject().(kbclient.ObjectList) + if err := kbClient.List(ctx, freshList, &kbclient.ListOptions{Namespace: f.Namespace()}); err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + items, err := meta.ExtractList(freshList) + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + seen := make(map[string]bool, len(args)) + for _, a := range args { + seen[a] = true + } + var filtered []string + for _, item := range items { + accessor, err := meta.Accessor(item) + if err != nil { + continue + } + name := accessor.GetName() + if seen[name] { + continue + } + if strings.HasPrefix(name, toComplete) { + filtered = append(filtered, name) + } + } + return filtered, cobra.ShellCompDirectiveNoFileComp + } +} + +func CompleteBackupNames(f client.Factory) completionFunc { + return completeNames(f, &velerov1api.BackupList{}) +} + +func CompleteRestoreNames(f client.Factory) completionFunc { + return completeNames(f, &velerov1api.RestoreList{}) +} + +func CompleteScheduleNames(f client.Factory) completionFunc { + return completeNames(f, &velerov1api.ScheduleList{}) +} + +func CompleteBackupStorageLocationNames(f client.Factory) completionFunc { + return completeNames(f, &velerov1api.BackupStorageLocationList{}) +} + +func CompleteVolumeSnapshotLocationNames(f client.Factory) completionFunc { + return completeNames(f, &velerov1api.VolumeSnapshotLocationList{}) +} + +func CompleteBackupRepositoryNames(f client.Factory) completionFunc { + return completeNames(f, &velerov1api.BackupRepositoryList{}) +} diff --git a/pkg/cmd/cli/completion_functions_test.go b/pkg/cmd/cli/completion_functions_test.go new file mode 100644 index 000000000..b765ed54a --- /dev/null +++ b/pkg/cmd/cli/completion_functions_test.go @@ -0,0 +1,212 @@ +/* +Copyright The Velero Contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "fmt" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" + velerotest "github.com/vmware-tanzu/velero/pkg/test" +) + +// TestCompleteNames exercises the core completeNames helper with various list +// types, prefix filters, and edge cases (empty cluster, no match). +func TestCompleteNames(t *testing.T) { + tests := []struct { + name string + objects []runtime.Object + list kbclient.ObjectList + args []string + toComplete string + want []string + }{ + { + name: "no resources returns nil", + objects: nil, + list: &velerov1api.BackupList{}, + toComplete: "", + want: nil, + }, + { + name: "returns all matching names", + objects: []runtime.Object{ + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "daily", Namespace: "velero"}}, + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "weekly", Namespace: "velero"}}, + }, + list: &velerov1api.BackupList{}, + toComplete: "", + want: []string{"daily", "weekly"}, + }, + { + name: "filters by prefix", + objects: []runtime.Object{ + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "daily", Namespace: "velero"}}, + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "weekly", Namespace: "velero"}}, + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "daily-full", Namespace: "velero"}}, + }, + list: &velerov1api.BackupList{}, + toComplete: "dai", + want: []string{"daily", "daily-full"}, + }, + { + name: "no prefix match returns nil", + objects: []runtime.Object{ + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "daily", Namespace: "velero"}}, + }, + list: &velerov1api.BackupList{}, + toComplete: "xyz", + want: nil, + }, + { + name: "works with RestoreList", + objects: []runtime.Object{ + &velerov1api.Restore{ObjectMeta: metav1.ObjectMeta{Name: "restore-1", Namespace: "velero"}}, + &velerov1api.Restore{ObjectMeta: metav1.ObjectMeta{Name: "restore-2", Namespace: "velero"}}, + }, + list: &velerov1api.RestoreList{}, + toComplete: "restore-", + want: []string{"restore-1", "restore-2"}, + }, + { + name: "works with ScheduleList", + objects: []runtime.Object{ + &velerov1api.Schedule{ObjectMeta: metav1.ObjectMeta{Name: "nightly", Namespace: "velero"}}, + }, + list: &velerov1api.ScheduleList{}, + toComplete: "", + want: []string{"nightly"}, + }, + { + name: "works with BackupStorageLocationList", + objects: []runtime.Object{ + &velerov1api.BackupStorageLocation{ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "velero"}}, + &velerov1api.BackupStorageLocation{ObjectMeta: metav1.ObjectMeta{Name: "secondary", Namespace: "velero"}}, + }, + list: &velerov1api.BackupStorageLocationList{}, + toComplete: "s", + want: []string{"secondary"}, + }, + { + name: "works with VolumeSnapshotLocationList", + objects: []runtime.Object{ + &velerov1api.VolumeSnapshotLocation{ObjectMeta: metav1.ObjectMeta{Name: "aws-snap", Namespace: "velero"}}, + }, + list: &velerov1api.VolumeSnapshotLocationList{}, + toComplete: "", + want: []string{"aws-snap"}, + }, + { + name: "works with BackupRepositoryList", + objects: []runtime.Object{ + &velerov1api.BackupRepository{ObjectMeta: metav1.ObjectMeta{Name: "repo-1", Namespace: "velero"}}, + }, + list: &velerov1api.BackupRepositoryList{}, + toComplete: "", + want: []string{"repo-1"}, + }, + { + name: "excludes already-typed args", + objects: []runtime.Object{ + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "daily", Namespace: "velero"}}, + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "weekly", Namespace: "velero"}}, + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "monthly", Namespace: "velero"}}, + }, + list: &velerov1api.BackupList{}, + args: []string{"daily", "monthly"}, + toComplete: "", + want: []string{"weekly"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + kbClient := velerotest.NewFakeControllerRuntimeClient(t, tc.objects...) + + f := new(factorymocks.Factory) + f.On("KubebuilderClient").Return(kbClient, nil) + f.On("Namespace").Return("velero") + + completionFn := completeNames(f, tc.list) + got, directive := completionFn(&cobra.Command{}, tc.args, tc.toComplete) + + assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive) + assert.Equal(t, tc.want, got) + }) + } +} + +// TestCompleteNames_KubebuilderClientError verifies that a factory error +// (e.g. no kubeconfig) returns nil completions instead of panicking. +func TestCompleteNames_KubebuilderClientError(t *testing.T) { + f := new(factorymocks.Factory) + f.On("KubebuilderClient").Return(nil, fmt.Errorf("connection refused")) + + completionFn := completeNames(f, &velerov1api.BackupList{}) + got, directive := completionFn(&cobra.Command{}, nil, "") + + assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive) + assert.Nil(t, got) +} + +// TestCompleteWrappers verifies each exported Complete*Names wrapper returns +// only its own resource type. A single fake client holds one object of every +// type, so each wrapper must filter correctly and not leak other kinds. +func TestCompleteWrappers(t *testing.T) { + objects := []runtime.Object{ + &velerov1api.Backup{ObjectMeta: metav1.ObjectMeta{Name: "b1", Namespace: "velero"}}, + &velerov1api.Restore{ObjectMeta: metav1.ObjectMeta{Name: "r1", Namespace: "velero"}}, + &velerov1api.Schedule{ObjectMeta: metav1.ObjectMeta{Name: "s1", Namespace: "velero"}}, + &velerov1api.BackupStorageLocation{ObjectMeta: metav1.ObjectMeta{Name: "bsl1", Namespace: "velero"}}, + &velerov1api.VolumeSnapshotLocation{ObjectMeta: metav1.ObjectMeta{Name: "vsl1", Namespace: "velero"}}, + &velerov1api.BackupRepository{ObjectMeta: metav1.ObjectMeta{Name: "br1", Namespace: "velero"}}, + } + kbClient := velerotest.NewFakeControllerRuntimeClient(t, objects...) + + f := new(factorymocks.Factory) + f.On("KubebuilderClient").Return(kbClient, nil) + f.On("Namespace").Return("velero") + + tests := []struct { + name string + fn completionFunc + expected []string + }{ + {"CompleteBackupNames", CompleteBackupNames(f), []string{"b1"}}, + {"CompleteRestoreNames", CompleteRestoreNames(f), []string{"r1"}}, + {"CompleteScheduleNames", CompleteScheduleNames(f), []string{"s1"}}, + {"CompleteBackupStorageLocationNames", CompleteBackupStorageLocationNames(f), []string{"bsl1"}}, + {"CompleteVolumeSnapshotLocationNames", CompleteVolumeSnapshotLocationNames(f), []string{"vsl1"}}, + {"CompleteBackupRepositoryNames", CompleteBackupRepositoryNames(f), []string{"br1"}}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, directive := tc.fn(&cobra.Command{}, nil, "") + require.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive) + assert.Equal(t, tc.expected, got) + }) + } +} diff --git a/pkg/cmd/cli/debug/debug.go b/pkg/cmd/cli/debug/debug.go index fac49d622..62f1d0823 100644 --- a/pkg/cmd/cli/debug/debug.go +++ b/pkg/cmd/cli/debug/debug.go @@ -38,6 +38,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" ) //go:embed cshd-scripts/velero.cshd @@ -171,6 +172,10 @@ specs of resources created by velero server, and optionally the logs of backup a }, } o.bindFlags(c.Flags()) + + _ = c.RegisterFlagCompletionFunc("backup", cli.CompleteBackupNames(f)) + _ = c.RegisterFlagCompletionFunc("restore", cli.CompleteRestoreNames(f)) + return c } diff --git a/pkg/cmd/cli/repo/get.go b/pkg/cmd/cli/repo/get.go index ec57b9845..b3b914ae3 100644 --- a/pkg/cmd/cli/repo/get.go +++ b/pkg/cmd/cli/repo/get.go @@ -27,6 +27,7 @@ import ( api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" ) @@ -66,6 +67,7 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteBackupRepositoryNames(f) c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector.") output.BindFlags(c.Flags()) diff --git a/pkg/cmd/cli/restore/create.go b/pkg/cmd/cli/restore/create.go index c76097176..ac4284229 100644 --- a/pkg/cmd/cli/restore/create.go +++ b/pkg/cmd/cli/restore/create.go @@ -36,6 +36,7 @@ import ( api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" "github.com/vmware-tanzu/velero/pkg/util/boolptr" @@ -81,6 +82,9 @@ Notes: output.BindFlags(c.Flags()) output.ClearOutputFlagDefault(c) + _ = c.RegisterFlagCompletionFunc("from-backup", cli.CompleteBackupNames(f)) + _ = c.RegisterFlagCompletionFunc("from-schedule", cli.CompleteScheduleNames(f)) + return c } diff --git a/pkg/cmd/cli/restore/delete.go b/pkg/cmd/cli/restore/delete.go index 51c31e1da..b20186fb8 100644 --- a/pkg/cmd/cli/restore/delete.go +++ b/pkg/cmd/cli/restore/delete.go @@ -61,6 +61,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { cmd.CheckError(Run(o)) }, } + c.ValidArgsFunction = cli.CompleteRestoreNames(f) o.BindFlags(c.Flags()) return c } diff --git a/pkg/cmd/cli/restore/describe.go b/pkg/cmd/cli/restore/describe.go index 6404ef21d..7fc58ce22 100644 --- a/pkg/cmd/cli/restore/describe.go +++ b/pkg/cmd/cli/restore/describe.go @@ -29,6 +29,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" "github.com/vmware-tanzu/velero/pkg/label" ) @@ -92,6 +93,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteRestoreNames(f) c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector.") c.Flags().BoolVar(&details, "details", details, "Display additional detail in the command output.") c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.") diff --git a/pkg/cmd/cli/restore/get.go b/pkg/cmd/cli/restore/get.go index 9a4014b25..568e31b8d 100644 --- a/pkg/cmd/cli/restore/get.go +++ b/pkg/cmd/cli/restore/get.go @@ -27,6 +27,7 @@ import ( api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" ) @@ -76,6 +77,7 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteRestoreNames(f) c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector.") output.BindFlags(c.Flags()) diff --git a/pkg/cmd/cli/restore/logs.go b/pkg/cmd/cli/restore/logs.go index f4315c917..26d3123ac 100644 --- a/pkg/cmd/cli/restore/logs.go +++ b/pkg/cmd/cli/restore/logs.go @@ -29,6 +29,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/cacert" "github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest" ) @@ -82,6 +83,7 @@ func NewLogsCommand(f client.Factory) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteRestoreNames(f) c.Flags().DurationVar(&timeout, "timeout", timeout, "How long to wait to receive logs.") c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.") c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "Path to a certificate bundle to use when verifying TLS connections. If not specified, the CA certificate from the BackupStorageLocation will be used if available.") diff --git a/pkg/cmd/cli/schedule/create.go b/pkg/cmd/cli/schedule/create.go index 2e4a1e8e9..03f5626fd 100644 --- a/pkg/cmd/cli/schedule/create.go +++ b/pkg/cmd/cli/schedule/create.go @@ -30,6 +30,7 @@ import ( api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/cli/backup" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" ) @@ -77,6 +78,9 @@ example: "@every 2h30m".`, output.BindFlags(c.Flags()) output.ClearOutputFlagDefault(c) + _ = c.RegisterFlagCompletionFunc("storage-location", cli.CompleteBackupStorageLocationNames(f)) + _ = c.RegisterFlagCompletionFunc("volume-snapshot-locations", cli.CompleteVolumeSnapshotLocationNames(f)) + return c } diff --git a/pkg/cmd/cli/schedule/delete.go b/pkg/cmd/cli/schedule/delete.go index 78e8c9104..28418afbd 100644 --- a/pkg/cmd/cli/schedule/delete.go +++ b/pkg/cmd/cli/schedule/delete.go @@ -62,6 +62,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteScheduleNames(f) o.BindFlags(c.Flags()) return c } diff --git a/pkg/cmd/cli/schedule/describe.go b/pkg/cmd/cli/schedule/describe.go index 82c88dac7..b657245e9 100644 --- a/pkg/cmd/cli/schedule/describe.go +++ b/pkg/cmd/cli/schedule/describe.go @@ -28,6 +28,7 @@ import ( v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" ) @@ -73,6 +74,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteScheduleNames(f) c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector.") return c diff --git a/pkg/cmd/cli/schedule/get.go b/pkg/cmd/cli/schedule/get.go index 88bd49fe0..ba8ddb122 100644 --- a/pkg/cmd/cli/schedule/get.go +++ b/pkg/cmd/cli/schedule/get.go @@ -27,6 +27,7 @@ import ( api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" ) @@ -71,6 +72,7 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteScheduleNames(f) c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector.") output.BindFlags(c.Flags()) diff --git a/pkg/cmd/cli/schedule/pause.go b/pkg/cmd/cli/schedule/pause.go index 41a17f384..06fc43f5c 100644 --- a/pkg/cmd/cli/schedule/pause.go +++ b/pkg/cmd/cli/schedule/pause.go @@ -60,6 +60,7 @@ func NewPauseCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteScheduleNames(f) o.BindFlags(c.Flags()) pauseOpts.BindFlags(c.Flags()) diff --git a/pkg/cmd/cli/schedule/unpause.go b/pkg/cmd/cli/schedule/unpause.go index 72197a934..15107ba38 100644 --- a/pkg/cmd/cli/schedule/unpause.go +++ b/pkg/cmd/cli/schedule/unpause.go @@ -49,6 +49,7 @@ func NewUnpauseCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteScheduleNames(f) o.BindFlags(c.Flags()) pauseOpts.BindFlags(c.Flags()) diff --git a/pkg/cmd/cli/snapshotlocation/get.go b/pkg/cmd/cli/snapshotlocation/get.go index 2acddbf7f..79da478bf 100644 --- a/pkg/cmd/cli/snapshotlocation/get.go +++ b/pkg/cmd/cli/snapshotlocation/get.go @@ -26,6 +26,7 @@ import ( api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" ) @@ -56,6 +57,7 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { cmd.CheckError(err) }, } + c.ValidArgsFunction = cli.CompleteVolumeSnapshotLocationNames(f) c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector") output.BindFlags(c.Flags()) return c diff --git a/pkg/cmd/cli/snapshotlocation/set.go b/pkg/cmd/cli/snapshotlocation/set.go index 0814bdfe7..c67ef4231 100644 --- a/pkg/cmd/cli/snapshotlocation/set.go +++ b/pkg/cmd/cli/snapshotlocation/set.go @@ -30,6 +30,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" ) @@ -48,6 +49,7 @@ func NewSetCommand(f client.Factory, use string) *cobra.Command { }, } + c.ValidArgsFunction = cli.CompleteVolumeSnapshotLocationNames(f) o.BindFlags(c.Flags()) return c } diff --git a/site/content/docs/main/customize-installation.md b/site/content/docs/main/customize-installation.md index e9561eea9..28cc24154 100644 --- a/site/content/docs/main/customize-installation.md +++ b/site/content/docs/main/customize-installation.md @@ -356,7 +356,7 @@ Run `velero install --help` or see the [Helm chart documentation](https://vmware ### Enabling shell autocompletion -**Velero CLI** provides autocompletion support for `Bash` and `Zsh`, which can save you a lot of typing. +**Velero CLI** provides autocompletion support for `Bash`, `Zsh`, and `Fish`, which can save you a lot of typing. In addition to command and flag names, the CLI dynamically completes resource names (backups, restores, schedules, etc.) by querying the cluster. Below are the procedures to set up autocompletion for `Bash` (including the difference between `Linux` and `macOS`) and `Zsh`.