Adding completion for remaining Eventing resources (#1567)

* Added completion for trigger

* Added tests for trigger

* Added container sources completion

* Added tests for container source completion

* Add completion helper for apiserver sources

* Added tests for apiserver sources

* Added completion for binding sources

* Added tests for sink binding completion

* Added completion for ping source

* Added tests for ping source completion

* Added completion for channel

* Added completion for subscription

* Added tests for channels

* Added tests for subscription completion

* Kn params initialization fix and resource name fix in tests
This commit is contained in:
Gunjan Vyas 2022-01-18 14:38:32 +05:30 committed by GitHub
parent a96ecf55f6
commit a1079df154
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1028 additions and 25 deletions

View File

@ -30,6 +30,7 @@ func NewChannelDeleteCommand(p *commands.KnParams) *cobra.Command {
Example: `
# Delete a channel 'pipe'
kn channel delete pipe`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn channel delete' requires the channel name as single argument")

View File

@ -46,6 +46,7 @@ func NewChannelDescribeCommand(p *commands.KnParams) *cobra.Command {
Use: "describe NAME",
Short: "Show details of a channel",
Example: describeExample,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn channel describe' requires the channel name given as single argument")

View File

@ -29,11 +29,18 @@ type completionConfig struct {
var (
resourceToFuncMap = map[string]func(config *completionConfig) []string{
"apiserver": completeApiserverSource,
"binding": completeBindingSource,
"broker": completeBroker,
"channel": completeChannel,
"container": completeContainerSource,
"domain": completeDomain,
"ping": completePingSource,
"revision": completeRevision,
"route": completeRoute,
"service": completeService,
"subscription": completeSubscription,
"trigger": completeTrigger,
}
)
@ -231,3 +238,192 @@ func completeDomain(config *completionConfig) (suggestions []string) {
}
return
}
func completeTrigger(config *completionConfig) (suggestions []string) {
suggestions = make([]string, 0)
if len(config.args) != 0 {
return
}
namespace, err := config.params.GetNamespace(config.command)
if err != nil {
return
}
client, err := config.params.NewEventingClient(namespace)
if err != nil {
return
}
triggerList, err := client.ListTriggers(config.command.Context())
if err != nil {
return
}
for _, sug := range triggerList.Items {
if !strings.HasPrefix(sug.Name, config.toComplete) {
continue
}
suggestions = append(suggestions, sug.Name)
}
return
}
func completeContainerSource(config *completionConfig) (suggestions []string) {
suggestions = make([]string, 0)
if len(config.args) != 0 {
return
}
namespace, err := config.params.GetNamespace(config.command)
if err != nil {
return
}
client, err := config.params.NewSourcesClient(namespace)
if err != nil {
return
}
containerSourceList, err := client.ContainerSourcesClient().ListContainerSources(config.command.Context())
if err != nil {
return
}
for _, sug := range containerSourceList.Items {
if !strings.HasPrefix(sug.Name, config.toComplete) {
continue
}
suggestions = append(suggestions, sug.Name)
}
return
}
func completeApiserverSource(config *completionConfig) (suggestions []string) {
suggestions = make([]string, 0)
if len(config.args) != 0 {
return
}
namespace, err := config.params.GetNamespace(config.command)
if err != nil {
return
}
client, err := config.params.NewSourcesClient(namespace)
if err != nil {
return
}
apiServerSourceList, err := client.APIServerSourcesClient().ListAPIServerSource(config.command.Context())
if err != nil {
return
}
for _, sug := range apiServerSourceList.Items {
if !strings.HasPrefix(sug.Name, config.toComplete) {
continue
}
suggestions = append(suggestions, sug.Name)
}
return
}
func completeBindingSource(config *completionConfig) (suggestions []string) {
suggestions = make([]string, 0)
if len(config.args) != 0 {
return
}
namespace, err := config.params.GetNamespace(config.command)
if err != nil {
return
}
client, err := config.params.NewSourcesClient(namespace)
if err != nil {
return
}
bindingList, err := client.SinkBindingClient().ListSinkBindings(config.command.Context())
if err != nil {
return
}
for _, sug := range bindingList.Items {
if !strings.HasPrefix(sug.Name, config.toComplete) {
continue
}
suggestions = append(suggestions, sug.Name)
}
return
}
func completePingSource(config *completionConfig) (suggestions []string) {
suggestions = make([]string, 0)
if len(config.args) != 0 {
return
}
namespace, err := config.params.GetNamespace(config.command)
if err != nil {
return
}
client, err := config.params.NewSourcesV1beta2Client(namespace)
if err != nil {
return
}
pingSourcesClient := client.PingSourcesClient()
pingSourceList, err := pingSourcesClient.ListPingSource(config.command.Context())
if err != nil {
return
}
for _, sug := range pingSourceList.Items {
if !strings.HasPrefix(sug.Name, config.toComplete) {
continue
}
suggestions = append(suggestions, sug.Name)
}
return
}
func completeChannel(config *completionConfig) (suggestions []string) {
suggestions = make([]string, 0)
if len(config.args) != 0 {
return
}
namespace, err := config.params.GetNamespace(config.command)
if err != nil {
return
}
client, err := config.params.NewMessagingClient(namespace)
if err != nil {
return
}
channelList, err := client.ChannelsClient().ListChannel(config.command.Context())
if err != nil {
return
}
for _, sug := range channelList.Items {
if !strings.HasPrefix(sug.Name, config.toComplete) {
continue
}
suggestions = append(suggestions, sug.Name)
}
return
}
func completeSubscription(config *completionConfig) (suggestions []string) {
suggestions = make([]string, 0)
if len(config.args) != 0 {
return
}
namespace, err := config.params.GetNamespace(config.command)
if err != nil {
return
}
client, err := config.params.NewMessagingClient(namespace)
if err != nil {
return
}
subscriptionList, err := client.SubscriptionsClient().ListSubscription(config.command.Context())
if err != nil {
return
}
for _, sug := range subscriptionList.Items {
if !strings.HasPrefix(sug.Name, config.toComplete) {
continue
}
suggestions = append(suggestions, sug.Name)
}
return
}

View File

@ -25,7 +25,16 @@ import (
"gotest.tools/v3/assert"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
v1beta1 "knative.dev/client/pkg/messaging/v1"
clientv1alpha1 "knative.dev/client/pkg/serving/v1alpha1"
clientsourcesv1 "knative.dev/client/pkg/sources/v1"
"knative.dev/client/pkg/sources/v1beta2"
v12 "knative.dev/eventing/pkg/apis/messaging/v1"
sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1"
sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2"
sourcesv1fake "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1/fake"
sourcesv1beta2fake "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2/fake"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -50,6 +59,19 @@ type testType struct {
resource string
}
type mockMessagingClient struct {
channelsClient v1beta1.KnChannelsClient
subscriptionsClient v1beta1.KnSubscriptionsClient
}
func (m *mockMessagingClient) ChannelsClient() v1beta1.KnChannelsClient {
return m.channelsClient
}
func (m *mockMessagingClient) SubscriptionsClient() v1beta1.KnSubscriptionsClient {
return m.subscriptionsClient
}
const (
testNs = "test-ns"
errorNs = "error-ns"
@ -185,9 +207,204 @@ var (
testNsDomains = []v1alpha1.DomainMapping{testDomain1, testDomain2, testDomain3}
)
var (
testTrigger1 = eventingv1.Trigger{
TypeMeta: metav1.TypeMeta{
Kind: "Trigger",
APIVersion: "eventing.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-trigger-1", Namespace: testNs},
}
testTrigger2 = eventingv1.Trigger{
TypeMeta: metav1.TypeMeta{
Kind: "Trigger",
APIVersion: "eventing.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-trigger-2", Namespace: testNs},
}
testTrigger3 = eventingv1.Trigger{
TypeMeta: metav1.TypeMeta{
Kind: "Trigger",
APIVersion: "eventing.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-trigger-3", Namespace: testNs},
}
testNsTriggers = []eventingv1.Trigger{testTrigger1, testTrigger2, testTrigger3}
)
var (
testContainerSource1 = sourcesv1.ContainerSource{
TypeMeta: metav1.TypeMeta{
Kind: "ContainerSource",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-container-source-1", Namespace: testNs},
}
testContainerSource2 = sourcesv1.ContainerSource{
TypeMeta: metav1.TypeMeta{
Kind: "ContainerSource",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-container-source-2", Namespace: testNs},
}
testContainerSource3 = sourcesv1.ContainerSource{
TypeMeta: metav1.TypeMeta{
Kind: "ContainerSource",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-container-source-3", Namespace: testNs},
}
testNsContainerSources = []sourcesv1.ContainerSource{testContainerSource1, testContainerSource2, testContainerSource3}
fakeSources = &sourcesv1fake.FakeSourcesV1{Fake: &clienttesting.Fake{}}
)
var (
testApiServerSource1 = sourcesv1.ApiServerSource{
TypeMeta: metav1.TypeMeta{
Kind: "ApiServerSource",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-ApiServer-source-1", Namespace: testNs},
}
testApiServerSource2 = sourcesv1.ApiServerSource{
TypeMeta: metav1.TypeMeta{
Kind: "ApiServerSource",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-ApiServer-source-2", Namespace: testNs},
}
testApiServerSource3 = sourcesv1.ApiServerSource{
TypeMeta: metav1.TypeMeta{
Kind: "ApiServerSource",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-ApiServer-source-3", Namespace: testNs},
}
testNsApiServerSources = []sourcesv1.ApiServerSource{testApiServerSource1, testApiServerSource2, testApiServerSource3}
)
var (
testSinkBinding1 = sourcesv1.SinkBinding{
TypeMeta: metav1.TypeMeta{
Kind: "SinkBinding",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-sink-binding-1", Namespace: testNs},
}
testSinkBinding2 = sourcesv1.SinkBinding{
TypeMeta: metav1.TypeMeta{
Kind: "SinkBinding",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-sink-binding-2", Namespace: testNs},
}
testSinkBinding3 = sourcesv1.SinkBinding{
TypeMeta: metav1.TypeMeta{
Kind: "SinkBinding",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-sink-binding-3", Namespace: testNs},
}
testNsSinkBindings = []sourcesv1.SinkBinding{testSinkBinding1, testSinkBinding2, testSinkBinding3}
)
var (
testPingSource1 = sourcesv1beta2.PingSource{
TypeMeta: metav1.TypeMeta{
Kind: "PingSource",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-ping-source-1", Namespace: testNs},
}
testPingSource2 = sourcesv1beta2.PingSource{
TypeMeta: metav1.TypeMeta{
Kind: "PingSource",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-ping-source-2", Namespace: testNs},
}
testPingSource3 = sourcesv1beta2.PingSource{
TypeMeta: metav1.TypeMeta{
Kind: "PingSource",
APIVersion: "sources.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-ping-source-3", Namespace: testNs},
}
testNsPingSources = []sourcesv1beta2.PingSource{testPingSource1, testPingSource2, testPingSource3}
fakeSourcesV1Beta2 = &sourcesv1beta2fake.FakeSourcesV1beta2{Fake: &clienttesting.Fake{}}
)
var (
testChannel1 = v12.Channel{
TypeMeta: metav1.TypeMeta{
Kind: "Channel",
APIVersion: "messaging.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-channel-1", Namespace: testNs},
}
testChannel2 = v12.Channel{
TypeMeta: metav1.TypeMeta{
Kind: "Channel",
APIVersion: "messaging.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-channel-2", Namespace: testNs},
}
testChannel3 = v12.Channel{
TypeMeta: metav1.TypeMeta{
Kind: "Channel",
APIVersion: "messaging.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-channel-3", Namespace: testNs},
}
testNsChannels = []v12.Channel{testChannel1, testChannel2, testChannel3}
)
var (
testSubscription1 = v12.Subscription{
TypeMeta: metav1.TypeMeta{
Kind: "Subscription",
APIVersion: "messaging.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-subscription-1", Namespace: testNs},
}
testSubscription2 = v12.Subscription{
TypeMeta: metav1.TypeMeta{
Kind: "Subscription",
APIVersion: "messaging.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-subscription-2", Namespace: testNs},
}
testSubscription3 = v12.Subscription{
TypeMeta: metav1.TypeMeta{
Kind: "Subscription",
APIVersion: "messaging.knative.dev/v1",
},
ObjectMeta: metav1.ObjectMeta{Name: "test-subscription-3", Namespace: testNs},
}
testNsSubscriptions = []v12.Subscription{testSubscription1, testSubscription2, testSubscription3}
)
var knParams = initialiseKnParams()
func initialiseKnParams() *KnParams {
blankConfig, err := clientcmd.NewClientConfigFromBytes([]byte(`kind: Config
version: v1beta2
users:
- name: u
clusters:
- name: c
cluster:
server: example.com
contexts:
- name: x
context:
user: u
cluster: c
current-context: x
`))
if err != nil {
panic(err)
}
return &KnParams{
NewServingClient: func(namespace string) (v1.KnServingClient, error) {
return v1.NewKnServingClient(fakeServing, namespace), nil
@ -201,6 +418,13 @@ func initialiseKnParams() *KnParams {
NewServingV1alpha1Client: func(namespace string) (clientv1alpha1.KnServingClient, error) {
return clientv1alpha1.NewKnServingClient(fakeServingAlpha, namespace), nil
},
NewSourcesClient: func(namespace string) (clientsourcesv1.KnSourcesClient, error) {
return clientsourcesv1.NewKnSourcesClient(fakeSources, namespace), nil
},
NewSourcesV1beta2Client: func(namespace string) (v1beta2.KnSourcesClient, error) {
return v1beta2.NewKnSourcesClient(fakeSourcesV1Beta2, namespace), nil
},
ClientConfig: blankConfig,
}
}
@ -296,7 +520,7 @@ func TestResourceNameCompletionFuncBroker(t *testing.T) {
fakeEventing.AddReactor("list", "brokers", func(action clienttesting.Action) (bool, runtime.Object, error) {
if action.GetNamespace() == errorNs {
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list services"))
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list brokers"))
}
return true, &eventingv1.BrokerList{Items: testNsBrokers}, nil
})
@ -531,7 +755,7 @@ func TestResourceNameCompletionFuncRoute(t *testing.T) {
fakeServing.AddReactor("list", "routes",
func(a clienttesting.Action) (bool, runtime.Object, error) {
if a.GetNamespace() == errorNs {
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list services"))
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list routes"))
}
return true, &servingv1.RouteList{Items: testNsRoutes}, nil
})
@ -609,7 +833,7 @@ func TestResourceNameCompletionFuncDomain(t *testing.T) {
fakeServingAlpha.AddReactor("list", "domainmappings",
func(a clienttesting.Action) (bool, runtime.Object, error) {
if a.GetNamespace() == errorNs {
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list services"))
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list domains"))
}
return true, &v1alpha1.DomainMappingList{Items: testNsDomains}, nil
})
@ -681,6 +905,568 @@ func TestResourceNameCompletionFuncDomain(t *testing.T) {
}
}
func TestResourceNameCompletionFuncTrigger(t *testing.T) {
completionFunc := ResourceNameCompletionFunc(knParams)
fakeServing.AddReactor("list", "triggers",
func(a clienttesting.Action) (bool, runtime.Object, error) {
if a.GetNamespace() == errorNs {
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list triggers"))
}
return true, &eventingv1.TriggerList{Items: testNsTriggers}, nil
})
tests := []testType{
{
"Empty suggestions when non-zero args",
testNs,
knParams,
[]string{"xyz"},
"",
"trigger",
},
{
"Empty suggestions when no namespace flag",
"",
knParams,
nil,
"",
"trigger",
},
{
"Suggestions when test-ns namespace set",
testNs,
knParams,
nil,
"",
"trigger",
},
{
"Empty suggestions when toComplete is not a prefix",
testNs,
knParams,
nil,
"xyz",
"trigger",
},
{
"Empty suggestions when error during list operation",
errorNs,
knParams,
nil,
"",
"trigger",
},
}
for _, tt := range tests {
cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
t.Run(tt.name, func(t *testing.T) {
config := &completionConfig{
params: tt.p,
command: cmd,
args: tt.args,
toComplete: tt.toComplete,
}
expectedFunc := resourceToFuncMap[tt.resource]
if expectedFunc == nil {
expectedFunc = func(config *completionConfig) []string {
return []string{}
}
}
cmd.Flags().Set("namespace", tt.namespace)
actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
expectedSuggestions := expectedFunc(config)
expectedDirective := cobra.ShellCompDirectiveNoFileComp
assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
assert.Equal(t, actualDirective, expectedDirective)
})
}
}
func TestResourceNameCompletionFuncContainerSource(t *testing.T) {
completionFunc := ResourceNameCompletionFunc(knParams)
fakeSources.AddReactor("list", "containersources",
func(a clienttesting.Action) (bool, runtime.Object, error) {
if a.GetNamespace() == errorNs {
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list container sources"))
}
return true, &sourcesv1.ContainerSourceList{Items: testNsContainerSources}, nil
})
tests := []testType{
{
"Empty suggestions when non-zero args",
testNs,
knParams,
[]string{"xyz"},
"",
"container",
},
{
"Empty suggestions when no namespace flag",
"",
knParams,
nil,
"",
"container",
},
{
"Suggestions when test-ns namespace set",
testNs,
knParams,
nil,
"",
"container",
},
{
"Empty suggestions when toComplete is not a prefix",
testNs,
knParams,
nil,
"xyz",
"container",
},
{
"Empty suggestions when error during list operation",
errorNs,
knParams,
nil,
"",
"container",
},
}
for _, tt := range tests {
cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
t.Run(tt.name, func(t *testing.T) {
config := &completionConfig{
params: tt.p,
command: cmd,
args: tt.args,
toComplete: tt.toComplete,
}
expectedFunc := resourceToFuncMap[tt.resource]
if expectedFunc == nil {
expectedFunc = func(config *completionConfig) []string {
return []string{}
}
}
cmd.Flags().Set("namespace", tt.namespace)
actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
expectedSuggestions := expectedFunc(config)
expectedDirective := cobra.ShellCompDirectiveNoFileComp
assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
assert.Equal(t, actualDirective, expectedDirective)
})
}
}
func TestResourceNameCompletionFuncApiserverSource(t *testing.T) {
completionFunc := ResourceNameCompletionFunc(knParams)
fakeSources.AddReactor("list", "apiserversources",
func(a clienttesting.Action) (bool, runtime.Object, error) {
if a.GetNamespace() == errorNs {
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list apiserver sources"))
}
return true, &sourcesv1.ApiServerSourceList{Items: testNsApiServerSources}, nil
})
tests := []testType{
{
"Empty suggestions when non-zero args",
testNs,
knParams,
[]string{"xyz"},
"",
"apiserver",
},
{
"Empty suggestions when no namespace flag",
"",
knParams,
nil,
"",
"apiserver",
},
{
"Suggestions when test-ns namespace set",
testNs,
knParams,
nil,
"",
"apiserver",
},
{
"Empty suggestions when toComplete is not a prefix",
testNs,
knParams,
nil,
"xyz",
"apiserver",
},
{
"Empty suggestions when error during list operation",
errorNs,
knParams,
nil,
"",
"apiserver",
},
}
for _, tt := range tests {
cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
t.Run(tt.name, func(t *testing.T) {
config := &completionConfig{
params: tt.p,
command: cmd,
args: tt.args,
toComplete: tt.toComplete,
}
expectedFunc := resourceToFuncMap[tt.resource]
if expectedFunc == nil {
expectedFunc = func(config *completionConfig) []string {
return []string{}
}
}
cmd.Flags().Set("namespace", tt.namespace)
actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
expectedSuggestions := expectedFunc(config)
expectedDirective := cobra.ShellCompDirectiveNoFileComp
assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
assert.Equal(t, actualDirective, expectedDirective)
})
}
}
func TestResourceNameCompletionFuncBindingSource(t *testing.T) {
completionFunc := ResourceNameCompletionFunc(knParams)
fakeSources.AddReactor("list", "sinkbindings",
func(a clienttesting.Action) (bool, runtime.Object, error) {
if a.GetNamespace() == errorNs {
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list binding sources"))
}
return true, &sourcesv1.SinkBindingList{Items: testNsSinkBindings}, nil
})
tests := []testType{
{
"Empty suggestions when non-zero args",
testNs,
knParams,
[]string{"xyz"},
"",
"binding",
},
{
"Empty suggestions when no namespace flag",
"",
knParams,
nil,
"",
"binding",
},
{
"Suggestions when test-ns namespace set",
testNs,
knParams,
nil,
"",
"binding",
},
{
"Empty suggestions when toComplete is not a prefix",
testNs,
knParams,
nil,
"xyz",
"binding",
},
{
"Empty suggestions when error during list operation",
errorNs,
knParams,
nil,
"",
"binding",
},
}
for _, tt := range tests {
cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
t.Run(tt.name, func(t *testing.T) {
config := &completionConfig{
params: tt.p,
command: cmd,
args: tt.args,
toComplete: tt.toComplete,
}
expectedFunc := resourceToFuncMap[tt.resource]
if expectedFunc == nil {
expectedFunc = func(config *completionConfig) []string {
return []string{}
}
}
cmd.Flags().Set("namespace", tt.namespace)
actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
expectedSuggestions := expectedFunc(config)
expectedDirective := cobra.ShellCompDirectiveNoFileComp
assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
assert.Equal(t, actualDirective, expectedDirective)
})
}
}
func TestResourceNameCompletionFuncPingSource(t *testing.T) {
completionFunc := ResourceNameCompletionFunc(knParams)
fakeSourcesV1Beta2.AddReactor("list", "pingsources",
func(a clienttesting.Action) (bool, runtime.Object, error) {
if a.GetNamespace() == errorNs {
return true, nil, errors.NewInternalError(fmt.Errorf("unable to list ping sources"))
}
return true, &sourcesv1beta2.PingSourceList{Items: testNsPingSources}, nil
})
tests := []testType{
{
"Empty suggestions when non-zero args",
testNs,
knParams,
[]string{"xyz"},
"",
"ping",
},
{
"Empty suggestions when no namespace flag",
"",
knParams,
nil,
"",
"ping",
},
{
"Suggestions when test-ns namespace set",
testNs,
knParams,
nil,
"",
"ping",
},
{
"Empty suggestions when toComplete is not a prefix",
testNs,
knParams,
nil,
"xyz",
"ping",
},
{
"Empty suggestions when error during list operation",
errorNs,
knParams,
nil,
"",
"ping",
},
}
for _, tt := range tests {
cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
t.Run(tt.name, func(t *testing.T) {
config := &completionConfig{
params: tt.p,
command: cmd,
args: tt.args,
toComplete: tt.toComplete,
}
expectedFunc := resourceToFuncMap[tt.resource]
if expectedFunc == nil {
expectedFunc = func(config *completionConfig) []string {
return []string{}
}
}
cmd.Flags().Set("namespace", tt.namespace)
actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
expectedSuggestions := expectedFunc(config)
expectedDirective := cobra.ShellCompDirectiveNoFileComp
assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
assert.Equal(t, actualDirective, expectedDirective)
})
}
}
func TestResourceNameCompletionFuncChannel(t *testing.T) {
completionFunc := ResourceNameCompletionFunc(knParams)
channelClient := v1beta1.NewMockKnChannelsClient(t)
channelClient.Recorder().ListChannel(&v12.ChannelList{Items: testNsChannels}, nil)
channelClient.Recorder().ListChannel(&v12.ChannelList{Items: testNsChannels}, nil)
channelClient.Recorder().ListChannel(&v12.ChannelList{Items: testNsChannels}, nil)
channelClient.Recorder().ListChannel(&v12.ChannelList{Items: testNsChannels}, nil)
channelClient.Recorder().ListChannel(&v12.ChannelList{}, fmt.Errorf("error listing channels"))
channelClient.Recorder().ListChannel(&v12.ChannelList{}, fmt.Errorf("error listing channels"))
messagingClient := &mockMessagingClient{channelClient, nil}
knParams.NewMessagingClient = func(namespace string) (v1beta1.KnMessagingClient, error) {
return messagingClient, nil
}
tests := []testType{
{
"Empty suggestions when non-zero args",
testNs,
knParams,
[]string{"xyz"},
"",
"channel",
},
{
"Empty suggestions when no namespace flag",
"",
knParams,
nil,
"",
"channel",
},
{
"Suggestions when test-ns namespace set",
testNs,
knParams,
nil,
"",
"channel",
},
{
"Empty suggestions when toComplete is not a prefix",
testNs,
knParams,
nil,
"xyz",
"channel",
},
{
"Empty suggestions when error during list operation",
errorNs,
knParams,
nil,
"",
"channel",
},
}
for _, tt := range tests {
cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
t.Run(tt.name, func(t *testing.T) {
config := &completionConfig{
params: tt.p,
command: cmd,
args: tt.args,
toComplete: tt.toComplete,
}
expectedFunc := resourceToFuncMap[tt.resource]
if expectedFunc == nil {
expectedFunc = func(config *completionConfig) []string {
return []string{}
}
}
cmd.Flags().Set("namespace", tt.namespace)
actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
expectedSuggestions := expectedFunc(config)
expectedDirective := cobra.ShellCompDirectiveNoFileComp
assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
assert.Equal(t, actualDirective, expectedDirective)
})
}
channelClient.Recorder().Validate()
}
func TestResourceNameCompletionFuncSubscription(t *testing.T) {
completionFunc := ResourceNameCompletionFunc(knParams)
subscriptionsClient := v1beta1.NewMockKnSubscriptionsClient(t)
subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{Items: testNsSubscriptions}, nil)
subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{Items: testNsSubscriptions}, nil)
subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{Items: testNsSubscriptions}, nil)
subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{Items: testNsSubscriptions}, nil)
subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{}, fmt.Errorf("error listing channels"))
subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{}, fmt.Errorf("error listing channels"))
messagingClient := &mockMessagingClient{nil, subscriptionsClient}
knParams.NewMessagingClient = func(namespace string) (v1beta1.KnMessagingClient, error) {
return messagingClient, nil
}
tests := []testType{
{
"Empty suggestions when non-zero args",
testNs,
knParams,
[]string{"xyz"},
"",
"subscription",
},
{
"Empty suggestions when no namespace flag",
"",
knParams,
nil,
"",
"subscription",
},
{
"Suggestions when test-ns namespace set",
testNs,
knParams,
nil,
"",
"subscription",
},
{
"Empty suggestions when toComplete is not a prefix",
testNs,
knParams,
nil,
"xyz",
"subscription",
},
{
"Empty suggestions when error during list operation",
errorNs,
knParams,
nil,
"",
"subscription",
},
}
for _, tt := range tests {
cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
t.Run(tt.name, func(t *testing.T) {
config := &completionConfig{
params: tt.p,
command: cmd,
args: tt.args,
toComplete: tt.toComplete,
}
expectedFunc := resourceToFuncMap[tt.resource]
if expectedFunc == nil {
expectedFunc = func(config *completionConfig) []string {
return []string{}
}
}
cmd.Flags().Set("namespace", tt.namespace)
actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
expectedSuggestions := expectedFunc(config)
expectedDirective := cobra.ShellCompDirectiveNoFileComp
assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
assert.Equal(t, actualDirective, expectedDirective)
})
}
subscriptionsClient.Recorder().Validate()
}
func getResourceCommandWithTestSubcommand(resource string, addNamespace, addSubcommand bool) *cobra.Command {
testCommand := &cobra.Command{
Use: resource,

View File

@ -31,6 +31,7 @@ func NewAPIServerDeleteCommand(p *commands.KnParams) *cobra.Command {
Example: `
# Delete an ApiServerSource 'k8sevents' in default namespace
kn source apiserver delete k8sevents`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires the name of the source as single argument")

View File

@ -45,6 +45,7 @@ func NewAPIServerDescribeCommand(p *commands.KnParams) *cobra.Command {
Use: "describe NAME",
Short: "Show details of an api-server source",
Example: describeExample,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn source apiserver describe' requires name of the source as single argument")

View File

@ -38,6 +38,7 @@ func NewAPIServerUpdateCommand(p *commands.KnParams) *cobra.Command {
# Update an ApiServerSource 'k8sevents' with different service account and sink service
kn source apiserver update k8sevents --service-account newsa --sink ksvc:newsvc`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) != 1 {
return errors.New("requires the name of the source as single argument")

View File

@ -30,6 +30,7 @@ func NewBindingDeleteCommand(p *commands.KnParams) *cobra.Command {
Example: `
# Delete a sink binding with name 'my-binding'
kn source binding delete my-binding`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires the name of the sink binding to delete as single argument")

View File

@ -46,6 +46,7 @@ func NewBindingDescribeCommand(p *commands.KnParams) *cobra.Command {
Use: "describe NAME",
Short: "Show details of a sink binding",
Example: describeExample,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn source binding describe' requires name of the sink binding as single argument")

View File

@ -38,6 +38,7 @@ func NewBindingUpdateCommand(p *commands.KnParams) *cobra.Command {
# Update the subject of a sink binding 'my-binding' to a new cronjob with label selector 'app=ping'
kn source binding update my-binding --subject cronjob:batch/v1beta1:app=ping"`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) != 1 {
return errors.New("requires the name of the sink binding to update as single argument")

View File

@ -33,6 +33,7 @@ func NewContainerDeleteCommand(p *commands.KnParams) *cobra.Command {
Example: `
# Delete a ContainerSource 'containersrc' in default namespace
kn source container delete containersrc`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires the name of the source as single argument")

View File

@ -36,6 +36,7 @@ func NewContainerDescribeCommand(p *commands.KnParams) *cobra.Command {
Example: `
# Describe a container source with name 'k8sevents'
kn source container describe k8sevents`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn source container describe' requires name of the source as single argument")

View File

@ -30,6 +30,7 @@ func NewPingDeleteCommand(p *commands.KnParams) *cobra.Command {
Example: `
# Delete a Ping source 'my-ping'
kn source ping delete my-ping`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'requires the name of the Ping source to delete as single argument")

View File

@ -44,6 +44,7 @@ func NewPingDescribeCommand(p *commands.KnParams) *cobra.Command {
Use: "describe NAME",
Short: "Show details of a ping source",
Example: describeExample,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn source ping describe' requires name of the source as single argument")

View File

@ -42,6 +42,7 @@ func NewPingUpdateCommand(p *commands.KnParams) *cobra.Command {
# Update the schedule of a Ping source 'my-ping' to fire every minute
kn source ping update my-ping --schedule "* * * * *"`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) != 1 {
return errors.New("name of Ping source required")

View File

@ -32,6 +32,7 @@ func NewSubscriptionDeleteCommand(p *commands.KnParams) *cobra.Command {
Example: `
# Delete a subscription 'sub0'
kn subscription delete sub0`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn subscription delete' requires the subscription name as single argument")

View File

@ -43,6 +43,7 @@ func NewSubscriptionDescribeCommand(p *commands.KnParams) *cobra.Command {
Example: `
# Describe a subscription 'pipe'
kn subscription describe pipe`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn subscription describe' requires the subscription name given as single argument")

View File

@ -43,7 +43,7 @@ func NewSubscriptionUpdateCommand(p *commands.KnParams) *cobra.Command {
# Update a subscription 'sub1' with subscriber ksvc 'mirror', reply to a broker 'nest' and DeadLetterSink to a ksvc 'bucket'
kn subscription update sub1 --sink mirror --sink-reply broker:nest --sink-dead-letter bucket`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) != 1 {
return errors.New("'kn subscription update' requires the subscription name given as single argument")

View File

@ -30,6 +30,7 @@ func NewTriggerDeleteCommand(p *commands.KnParams) *cobra.Command {
Example: `
# Delete a trigger 'mytrigger' in default namespace
kn trigger delete mytrigger`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'trigger delete' requires the name of the trigger as single argument")

View File

@ -43,6 +43,7 @@ func NewTriggerDescribeCommand(p *commands.KnParams) *cobra.Command {
Use: "describe NAME",
Short: "Show details of a trigger",
Example: describeExample,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn trigger describe' requires name of the trigger as single argument")

View File

@ -48,7 +48,7 @@ func NewTriggerUpdateCommand(p *commands.KnParams) *cobra.Command {
# Update the sink of a trigger 'mytrigger' to 'ksvc:new-service'
kn trigger update mytrigger --sink ksvc:new-service
`,
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
RunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) != 1 {
return errors.New("name of trigger required")

View File

@ -94,6 +94,10 @@ func (params *KnParams) Initialize() {
if params.NewDynamicClient == nil {
params.NewDynamicClient = params.newDynamicClient
}
if params.NewSourcesV1beta2Client == nil {
params.NewSourcesV1beta2Client = params.newSourcesClientV1beta2
}
}
func (params *KnParams) newServingClient(namespace string) (clientservingv1.KnServingClient, error) {