diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go b/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go index b04489b7348..9cfd37da044 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go @@ -167,8 +167,11 @@ func NewCmdGet(parent string, f cmdutil.Factory, streams genericclioptions.IOStr var comps []string if len(args) == 0 { comps = apiresources.CompGetResourceList(f, cmd, toComplete) - } else if len(args) == 1 { + } else { comps = CompGetResource(f, cmd, args[0], toComplete) + if len(args) > 1 { + comps = cmdutil.Difference(comps, args[1:]) + } } return comps, cobra.ShellCompDirectiveNoFileComp }, diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go index 11704af03a9..39e012a8d27 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go @@ -102,7 +102,7 @@ func NewCmdRolloutStatus(f cmdutil.Factory, streams genericclioptions.IOStreams) Short: i18n.T("Show the status of the rollout"), Long: statusLong, Example: statusExample, - ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), + ValidArgsFunction: util.SpecifiedResourceTypeAndNameNoRepeatCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, args)) cmdutil.CheckErr(o.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers.go b/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers.go index 7b03deb29c0..bb24f61e65f 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers.go @@ -733,3 +733,18 @@ func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) { oldGeneratorName, ) } + +// Difference removes any elements of subArray from fullArray and returns the result +func Difference(fullArray []string, subArray []string) []string { + exclude := make(map[string]bool, len(subArray)) + for _, elem := range subArray { + exclude[elem] = true + } + var result []string + for _, elem := range fullArray { + if _, found := exclude[elem]; !found { + result = append(result, elem) + } + } + return result +} diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers_test.go index 55785335e6d..f268cdfa3ab 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers_test.go @@ -25,6 +25,9 @@ import ( "syscall" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + corev1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" @@ -321,3 +324,40 @@ func TestDumpReaderToFile(t *testing.T) { t.Fatalf("Wrong file content %s != %s", testString, stringData) } } + +func TestDifferenceFunc(t *testing.T) { + tests := []struct { + name string + fullArray []string + subArray []string + expected []string + }{ + { + name: "remove some", + fullArray: []string{"a", "b", "c", "d"}, + subArray: []string{"c", "b"}, + expected: []string{"a", "d"}, + }, + { + name: "remove all", + fullArray: []string{"a", "b", "c", "d"}, + subArray: []string{"b", "d", "a", "c"}, + expected: nil, + }, + { + name: "remove none", + fullArray: []string{"a", "b", "c", "d"}, + subArray: nil, + expected: []string{"a", "b", "c", "d"}, + }, + } + + for _, tc := range tests { + result := Difference(tc.fullArray, tc.subArray) + if !cmp.Equal(tc.expected, result, cmpopts.SortSlices(func(x, y string) bool { + return x < y + })) { + t.Errorf("%s -> Expected: %v, but got: %v", tc.name, tc.expected, result) + } + } +} diff --git a/staging/src/k8s.io/kubectl/pkg/util/completion.go b/staging/src/k8s.io/kubectl/pkg/util/completion.go index 3253b75764c..3fe8f8cd95f 100644 --- a/staging/src/k8s.io/kubectl/pkg/util/completion.go +++ b/staging/src/k8s.io/kubectl/pkg/util/completion.go @@ -34,15 +34,18 @@ func SetFactoryForCompletion(f cmdutil.Factory) { } // ResourceTypeAndNameCompletionFunc Returns a completion function that completes as a first argument -// the resource types that match the toComplete prefix, and as a second argument the resource names that match +// the resource types that match the toComplete prefix, and all following arguments as resource names that match // the toComplete prefix. func ResourceTypeAndNameCompletionFunc(f cmdutil.Factory) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var comps []string if len(args) == 0 { comps = apiresources.CompGetResourceList(f, cmd, toComplete) - } else if len(args) == 1 { + } else { comps = get.CompGetResource(f, cmd, args[0], toComplete) + if len(args) > 1 { + comps = cmdutil.Difference(comps, args[1:]) + } } return comps, cobra.ShellCompDirectiveNoFileComp } @@ -50,8 +53,19 @@ func ResourceTypeAndNameCompletionFunc(f cmdutil.Factory) func(*cobra.Command, [ // SpecifiedResourceTypeAndNameCompletionFunc Returns a completion function that completes as a first // argument the resource types that match the toComplete prefix and are limited to the allowedTypes, -// and as a second argument the specified resource names that match the toComplete prefix. +// and all following arguments as resource names that match the toComplete prefix. func SpecifiedResourceTypeAndNameCompletionFunc(f cmdutil.Factory, allowedTypes []string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return doSpecifiedResourceTypeAndNameComp(f, allowedTypes, true) +} + +// SpecifiedResourceTypeAndNameNoRepeatCompletionFunc Returns a completion function that completes as a first +// argument the resource types that match the toComplete prefix and are limited to the allowedTypes, and as +// a second argument a resource name that match the toComplete prefix. +func SpecifiedResourceTypeAndNameNoRepeatCompletionFunc(f cmdutil.Factory, allowedTypes []string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return doSpecifiedResourceTypeAndNameComp(f, allowedTypes, false) +} + +func doSpecifiedResourceTypeAndNameComp(f cmdutil.Factory, allowedTypes []string, repeat bool) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var comps []string if len(args) == 0 { @@ -60,8 +74,13 @@ func SpecifiedResourceTypeAndNameCompletionFunc(f cmdutil.Factory, allowedTypes comps = append(comps, comp) } } - } else if len(args) == 1 { - comps = get.CompGetResource(f, cmd, args[0], toComplete) + } else { + if repeat || len(args) == 1 { + comps = get.CompGetResource(f, cmd, args[0], toComplete) + if repeat && len(args) > 1 { + comps = cmdutil.Difference(comps, args[1:]) + } + } } return comps, cobra.ShellCompDirectiveNoFileComp } diff --git a/staging/src/k8s.io/kubectl/pkg/util/completion_test.go b/staging/src/k8s.io/kubectl/pkg/util/completion_test.go index 663b306720e..da94faa99ae 100644 --- a/staging/src/k8s.io/kubectl/pkg/util/completion_test.go +++ b/staging/src/k8s.io/kubectl/pkg/util/completion_test.go @@ -35,115 +35,100 @@ import ( func TestResourceTypeAndNameCompletionFuncOneArg(t *testing.T) { tf, cmd := prepareCompletionTest() addPodsToFactory(tf) + compFunc := ResourceTypeAndNameCompletionFunc(tf) comps, directive := compFunc(cmd, []string{"pod"}, "b") checkCompletion(t, comps, []string{"bar"}, directive, cobra.ShellCompDirectiveNoFileComp) } -func TestResourceTypeAndNameCompletionFuncTooManyArgs(t *testing.T) { - tf := cmdtesting.NewTestFactory() - defer tf.Cleanup() +func TestResourceTypeAndNameCompletionFuncRepeating(t *testing.T) { + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) - streams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := get.NewCmdGet("kubectl", tf, streams) compFunc := ResourceTypeAndNameCompletionFunc(tf) - comps, directive := compFunc(cmd, []string{"pod", "pod-name"}, "") - checkCompletion(t, comps, []string{}, directive, cobra.ShellCompDirectiveNoFileComp) + comps, directive := compFunc(cmd, []string{"pod", "bar"}, "") + // The other pods should be completed, but not the already specified ones + checkCompletion(t, comps, []string{"foo"}, directive, cobra.ShellCompDirectiveNoFileComp) } func TestSpecifiedResourceTypeAndNameCompletionFuncNoArgs(t *testing.T) { - tf := cmdtesting.NewTestFactory() - defer tf.Cleanup() + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) - streams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := get.NewCmdGet("kubectl", tf, streams) compFunc := SpecifiedResourceTypeAndNameCompletionFunc(tf, []string{"pod", "service", "statefulset"}) comps, directive := compFunc(cmd, []string{}, "s") checkCompletion(t, comps, []string{"service", "statefulset"}, directive, cobra.ShellCompDirectiveNoFileComp) } func TestSpecifiedResourceTypeAndNameCompletionFuncOneArg(t *testing.T) { - tf := cmdtesting.NewTestFactory().WithNamespace("test") - defer tf.Cleanup() + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) - pods, _, _ := cmdtesting.TestData() - codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) - tf.UnstructuredClient = &fake.RESTClient{ - NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, - Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, - } - - streams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := get.NewCmdGet("kubectl", tf, streams) compFunc := SpecifiedResourceTypeAndNameCompletionFunc(tf, []string{"pod"}) comps, directive := compFunc(cmd, []string{"pod"}, "b") checkCompletion(t, comps, []string{"bar"}, directive, cobra.ShellCompDirectiveNoFileComp) } -func TestSpecifiedResourceTypeAndNameCompletionFuncTooManyArgs(t *testing.T) { - tf := cmdtesting.NewTestFactory() - defer tf.Cleanup() +func TestSpecifiedResourceTypeAndNameCompletionFuncRepeating(t *testing.T) { + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) - streams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := get.NewCmdGet("kubectl", tf, streams) compFunc := SpecifiedResourceTypeAndNameCompletionFunc(tf, []string{"pod"}) - comps, directive := compFunc(cmd, []string{"pod", "pod-name"}, "") + comps, directive := compFunc(cmd, []string{"pod", "bar"}, "") + // The other pods should be completed, but not the already specified ones + checkCompletion(t, comps, []string{"foo"}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestSpecifiedResourceTypeAndNameCompletionNoRepeatFuncOneArg(t *testing.T) { + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) + + compFunc := SpecifiedResourceTypeAndNameNoRepeatCompletionFunc(tf, []string{"pod"}) + comps, directive := compFunc(cmd, []string{"pod"}, "b") + checkCompletion(t, comps, []string{"bar"}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestSpecifiedResourceTypeAndNameCompletionNoRepeatFuncMultiArg(t *testing.T) { + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) + + compFunc := SpecifiedResourceTypeAndNameNoRepeatCompletionFunc(tf, []string{"pod"}) + comps, directive := compFunc(cmd, []string{"pod", "bar"}, "") + // There should not be any more pods shown as this function should not repeat the completion checkCompletion(t, comps, []string{}, directive, cobra.ShellCompDirectiveNoFileComp) } func TestResourceNameCompletionFuncNoArgs(t *testing.T) { - tf := cmdtesting.NewTestFactory().WithNamespace("test") - defer tf.Cleanup() + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) - pods, _, _ := cmdtesting.TestData() - codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) - tf.UnstructuredClient = &fake.RESTClient{ - NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, - Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, - } - - streams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := get.NewCmdGet("kubectl", tf, streams) compFunc := ResourceNameCompletionFunc(tf, "pod") comps, directive := compFunc(cmd, []string{}, "b") checkCompletion(t, comps, []string{"bar"}, directive, cobra.ShellCompDirectiveNoFileComp) } func TestResourceNameCompletionFuncTooManyArgs(t *testing.T) { - tf := cmdtesting.NewTestFactory() - defer tf.Cleanup() + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) - streams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := get.NewCmdGet("kubectl", tf, streams) compFunc := ResourceNameCompletionFunc(tf, "pod") comps, directive := compFunc(cmd, []string{"pod-name"}, "") checkCompletion(t, comps, []string{}, directive, cobra.ShellCompDirectiveNoFileComp) } func TestPodResourceNameAndContainerCompletionFuncNoArgs(t *testing.T) { - tf := cmdtesting.NewTestFactory().WithNamespace("test") - defer tf.Cleanup() + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) - pods, _, _ := cmdtesting.TestData() - codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) - tf.UnstructuredClient = &fake.RESTClient{ - NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, - Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, - } - - streams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := get.NewCmdGet("kubectl", tf, streams) compFunc := PodResourceNameAndContainerCompletionFunc(tf) comps, directive := compFunc(cmd, []string{}, "b") checkCompletion(t, comps, []string{"bar"}, directive, cobra.ShellCompDirectiveNoFileComp) } func TestPodResourceNameAndContainerCompletionFuncTooManyArgs(t *testing.T) { - tf := cmdtesting.NewTestFactory().WithNamespace("test") - defer tf.Cleanup() + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) - streams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := get.NewCmdGet("kubectl", tf, streams) compFunc := PodResourceNameAndContainerCompletionFunc(tf) comps, directive := compFunc(cmd, []string{"pod-name", "container-name"}, "") checkCompletion(t, comps, []string{}, directive, cobra.ShellCompDirectiveNoFileComp)