mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Complete multiple resource names
This commit teaches the completion function to repeat resource names when supported by the command. The logic checks if a resource name has already been specified by the user and does not include it again when repeating the completion. For example, the get command can receive multiple pods names, therefore with this commit we have: kubectl get pod pod1 [tab] will provide completion of pod names again, but not show 'pod1' since it is already part of the command-line. The improvement affects the following commands: - annotate - apply edit-last-applied - apply view-last-applied - autoscale - delete - describe - edit - expose - get - label - patch - rollout history - rollout pause - rollout restart - rollout resume - rollout undo - scale - taint Note that "rollout status" only accepts a single resource name, unlike the other "rollout ..." commands; this required the creation of a special completion function that did not repeat just for that case. Signed-off-by: Marc Khouzam <marc.khouzam@montreal.ca>
This commit is contained in:
parent
55e1d2f9a7
commit
7aa5cb4031
@ -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
|
||||
},
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user