Merge pull request #105711 from VilledeMontreal/feat/multiComp

Shell completion of multiple resource names
This commit is contained in:
Kubernetes Prow Robot 2021-10-27 10:33:25 -07:00 committed by GitHub
commit aa7c6338c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 127 additions and 65 deletions

View File

@ -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
},

View File

@ -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())

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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)