diff --git a/go.mod b/go.mod index 03865db4..5dc44368 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( k8s.io/cli-runtime v0.29.2 k8s.io/client-go v0.29.2 k8s.io/code-generator v0.29.2 + k8s.io/utils v0.0.0-20240102154912-e7106e64919e knative.dev/eventing v0.40.1-0.20240325205050-ff32fbeefd03 knative.dev/hack v0.0.0-20240318013248-424e75ed769a knative.dev/pkg v0.0.0-20240325103648-fd7cc2153e6a @@ -139,7 +140,6 @@ require ( k8s.io/gengo v0.0.0-20240129211411-f967bbeff4b4 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect knative.dev/networking v0.0.0-20240318132715-69566347ab2a // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect diff --git a/hack/tools.go b/hack/tools.go index 44721d11..a16edcf5 100644 --- a/hack/tools.go +++ b/hack/tools.go @@ -20,6 +20,6 @@ package tools import ( _ "k8s.io/code-generator" _ "k8s.io/code-generator/cmd/deepcopy-gen" - _ "knative.dev/hack" + _ "knative.dev/hack/cmd/script" _ "knative.dev/pkg/hack" ) diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 1c16b60e..2ec54b9b 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -18,7 +18,8 @@ set -o errexit set -o nounset set -o pipefail -source $(dirname $0)/../vendor/knative.dev/hack/codegen-library.sh +# shellcheck disable=SC1090 +source "$(go run knative.dev/hack/cmd/script codegen-library.sh)" # If we run with -mod=vendor here, then generate-groups.sh looks for vendor files in the wrong place. export GOFLAGS=-mod= diff --git a/hack/update-deps.sh b/hack/update-deps.sh index f7e76718..75cc07d9 100755 --- a/hack/update-deps.sh +++ b/hack/update-deps.sh @@ -18,6 +18,7 @@ set -o errexit set -o nounset set -o pipefail -source $(dirname $0)/../vendor/knative.dev/hack/library.sh +# shellcheck disable=SC1090 +source "$(go run knative.dev/hack/cmd/script library.sh)" go_update_deps "$@" diff --git a/hack/update-k8s-deps.sh b/hack/update-k8s-deps.sh index d97170f1..e35c57e9 100755 --- a/hack/update-k8s-deps.sh +++ b/hack/update-k8s-deps.sh @@ -18,7 +18,8 @@ set -o errexit set -o nounset set -o pipefail -source $(dirname "$0")/../vendor/knative.dev/hack/library.sh +# shellcheck disable=SC1090 +source "$(go run knative.dev/hack/cmd/script library.sh)" run_go_tool knative.dev/test-infra/buoy \ buoy float ${REPO_ROOT_DIR}/go.mod \ diff --git a/pkg/apis/client/register.go b/pkg/apis/client/register.go index cf294021..c5eb97b7 100644 --- a/pkg/apis/client/register.go +++ b/pkg/apis/client/register.go @@ -18,5 +18,5 @@ package client // GroupName is the group name used in this package const ( - GroupName = "client-pkg.knative.dev" + GroupName = "client.knative.dev" ) diff --git a/pkg/apis/client/v1alpha1/doc.go b/pkg/apis/client/v1alpha1/doc.go index ae231727..f81c13db 100644 --- a/pkg/apis/client/v1alpha1/doc.go +++ b/pkg/apis/client/v1alpha1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:deepcopy-gen=package -// +groupName=client-pkg.knative.dev +// +groupName=client.knative.dev // Package v1alpha1 is the v1alpha1 version of the API. package v1alpha1 // import "knative.dev/client-pkg/pkg/apis/client/v1alpha1" diff --git a/pkg/commands/completion_helper.go b/pkg/commands/completion_helper.go new file mode 100644 index 00000000..398ff242 --- /dev/null +++ b/pkg/commands/completion_helper.go @@ -0,0 +1,458 @@ +// Copyright © 2021 The Knative Authors +// +// 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 commands + +import ( + "strings" + + "github.com/spf13/cobra" +) + +type completionConfig struct { + params *KnParams + command *cobra.Command + args []string + toComplete string +} + +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, + "eventtype": completeEventtype, + } +) + +// ResourceNameCompletionFunc will return a function that will autocomplete the name of +// the resource based on the subcommand +func ResourceNameCompletionFunc(p *KnParams) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + + var use string + if cmd.Parent() != nil { + use = cmd.Parent().Name() + } + config := completionConfig{ + p, + cmd, + args, + toComplete, + } + return config.getCompletion(use), cobra.ShellCompDirectiveNoFileComp + } +} + +func (config *completionConfig) getCompletion(parent string) []string { + completionFunc := resourceToFuncMap[parent] + if completionFunc == nil { + return []string{} + } + return completionFunc(config) +} + +func getTargetFlagValue(cmd *cobra.Command) string { + flag := cmd.Flag("target") + if flag == nil { + return "" + } + return flag.Value.String() +} + +func completeGitOps(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.NewGitopsServingClient(namespace, getTargetFlagValue(config.command)) + if err != nil { + return + } + serviceList, err := client.ListServices(config.command.Context()) + if err != nil { + return + } + for _, sug := range serviceList.Items { + if !strings.HasPrefix(sug.Name, config.toComplete) { + continue + } + suggestions = append(suggestions, sug.Name) + } + return +} + +func completeService(config *completionConfig) (suggestions []string) { + if getTargetFlagValue(config.command) != "" { + return completeGitOps(config) + } + + 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.NewServingClient(namespace) + if err != nil { + return + } + serviceList, err := client.ListServices(config.command.Context()) + if err != nil { + return + } + for _, sug := range serviceList.Items { + if !strings.HasPrefix(sug.Name, config.toComplete) { + continue + } + suggestions = append(suggestions, sug.Name) + } + return +} + +func completeBroker(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 + } + brokerList, err := client.ListBrokers(config.command.Context()) + if err != nil { + return + } + for _, sug := range brokerList.Items { + if !strings.HasPrefix(sug.Name, config.toComplete) { + continue + } + suggestions = append(suggestions, sug.Name) + } + return +} + +func completeRevision(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.NewServingClient(namespace) + if err != nil { + return + } + revisionList, err := client.ListRevisions(config.command.Context()) + if err != nil { + return + } + for _, sug := range revisionList.Items { + if !strings.HasPrefix(sug.Name, config.toComplete) { + continue + } + suggestions = append(suggestions, sug.Name) + } + return +} + +func completeRoute(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.NewServingClient(namespace) + if err != nil { + return + } + routeList, err := client.ListRoutes(config.command.Context()) + if err != nil { + return + } + for _, sug := range routeList.Items { + if !strings.HasPrefix(sug.Name, config.toComplete) { + continue + } + suggestions = append(suggestions, sug.Name) + } + return +} + +func completeDomain(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.NewServingV1beta1Client(namespace) + if err != nil { + return + } + domainMappingList, err := client.ListDomainMappings(config.command.Context()) + if err != nil { + return + } + for _, sug := range domainMappingList.Items { + if !strings.HasPrefix(sug.Name, config.toComplete) { + continue + } + suggestions = append(suggestions, sug.Name) + } + 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 +} + +func completeEventtype(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.NewEventingV1beta2Client(namespace) + if err != nil { + return + } + + eventTypeList, err := client.ListEventtypes(config.command.Context()) + if err != nil { + return + } + for _, sug := range eventTypeList.Items { + if !strings.HasPrefix(sug.Name, config.toComplete) { + continue + } + suggestions = append(suggestions, sug.Name) + } + return +} diff --git a/pkg/commands/completion_helper_test.go b/pkg/commands/completion_helper_test.go new file mode 100644 index 00000000..5d7bbe3d --- /dev/null +++ b/pkg/commands/completion_helper_test.go @@ -0,0 +1,1617 @@ +// Copyright © 2021 The Knative Authors +// +// 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 commands + +import ( + "fmt" + "os" + "path" + "testing" + + "github.com/spf13/cobra" + "gotest.tools/v3/assert" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" + clienteventingv1beta2 "knative.dev/client-pkg/pkg/eventing/v1beta2" + v1beta1 "knative.dev/client-pkg/pkg/messaging/v1" + clientv1beta1 "knative.dev/client-pkg/pkg/serving/v1beta1" + clientsourcesv1 "knative.dev/client-pkg/pkg/sources/v1" + "knative.dev/client-pkg/pkg/sources/v1beta2" + eventingv1beta2 "knative.dev/eventing/pkg/apis/eventing/v1beta2" + messagingv1 "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" + clienttesting "k8s.io/client-go/testing" + clienteventingv1 "knative.dev/client-pkg/pkg/eventing/v1" + v1 "knative.dev/client-pkg/pkg/serving/v1" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1/fake" + beta2fake "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" + servingv1beta1 "knative.dev/serving/pkg/apis/serving/v1beta1" + servingv1fake "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1/fake" + servingv1beta1fake "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1beta1/fake" +) + +type testType struct { + name string + namespace string + p *KnParams + args []string + toComplete string + 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" +) + +var ( + testSvc1 = servingv1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-svc-1", Namespace: testNs}, + } + testSvc2 = servingv1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-svc-2", Namespace: testNs}, + } + testSvc3 = servingv1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-svc-3", Namespace: testNs}, + } + testNsServices = []servingv1.Service{testSvc1, testSvc2, testSvc3} + + fakeServing = &servingv1fake.FakeServingV1{Fake: &clienttesting.Fake{}} + fakeServingAlpha = &servingv1beta1fake.FakeServingV1beta1{Fake: &clienttesting.Fake{}} +) + +var ( + testBroker1 = eventingv1.Broker{ + TypeMeta: metav1.TypeMeta{ + Kind: "Broker", + APIVersion: "eventing.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-broker-1", Namespace: testNs}, + } + testBroker2 = eventingv1.Broker{ + TypeMeta: metav1.TypeMeta{ + Kind: "Broker", + APIVersion: "eventing.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-broker-2", Namespace: testNs}, + } + testBroker3 = eventingv1.Broker{ + TypeMeta: metav1.TypeMeta{ + Kind: "Broker", + APIVersion: "eventing.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-broker-3", Namespace: testNs}, + } + testNsBrokers = []eventingv1.Broker{testBroker1, testBroker2, testBroker3} + + fakeEventing = &fake.FakeEventingV1{Fake: &clienttesting.Fake{}} +) + +var ( + testRev1 = servingv1.Revision{ + TypeMeta: metav1.TypeMeta{ + Kind: "Revision", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-rev-1", Namespace: testNs}, + } + testRev2 = servingv1.Revision{ + TypeMeta: metav1.TypeMeta{ + Kind: "Revision", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-rev-2", Namespace: testNs}, + } + testRev3 = servingv1.Revision{ + TypeMeta: metav1.TypeMeta{ + Kind: "Revision", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-rev-3", Namespace: testNs}, + } + testNsRevs = []servingv1.Revision{testRev1, testRev2, testRev3} +) + +var ( + testRoute1 = servingv1.Route{ + TypeMeta: metav1.TypeMeta{ + Kind: "Route", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-route-1", Namespace: testNs}, + } + testRoute2 = servingv1.Route{ + TypeMeta: metav1.TypeMeta{ + Kind: "Route", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-route-2", Namespace: testNs}, + } + testRoute3 = servingv1.Route{ + TypeMeta: metav1.TypeMeta{ + Kind: "Route", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-route-3", Namespace: testNs}, + } + testNsRoutes = []servingv1.Route{testRoute1, testRoute2, testRoute3} +) + +var ( + testDomain1 = servingv1beta1.DomainMapping{ + TypeMeta: metav1.TypeMeta{ + Kind: "DomainMapping", + APIVersion: "serving.knative.dev/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-domain-1", Namespace: testNs}, + } + testDomain2 = servingv1beta1.DomainMapping{ + TypeMeta: metav1.TypeMeta{ + Kind: "DomainMapping", + APIVersion: "serving.knative.dev/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-domain-2", Namespace: testNs}, + } + testDomain3 = servingv1beta1.DomainMapping{ + TypeMeta: metav1.TypeMeta{ + Kind: "DomainMapping", + APIVersion: "serving.knative.dev/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-domain-3", Namespace: testNs}, + } + testNsDomains = []servingv1beta1.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 = messagingv1.Channel{ + TypeMeta: metav1.TypeMeta{ + Kind: "Channel", + APIVersion: "messaging.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-channel-1", Namespace: testNs}, + } + testChannel2 = messagingv1.Channel{ + TypeMeta: metav1.TypeMeta{ + Kind: "Channel", + APIVersion: "messaging.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-channel-2", Namespace: testNs}, + } + testChannel3 = messagingv1.Channel{ + TypeMeta: metav1.TypeMeta{ + Kind: "Channel", + APIVersion: "messaging.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-channel-3", Namespace: testNs}, + } + testNsChannels = []messagingv1.Channel{testChannel1, testChannel2, testChannel3} +) + +var ( + testSubscription1 = messagingv1.Subscription{ + TypeMeta: metav1.TypeMeta{ + Kind: "Subscription", + APIVersion: "messaging.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-subscription-1", Namespace: testNs}, + } + testSubscription2 = messagingv1.Subscription{ + TypeMeta: metav1.TypeMeta{ + Kind: "Subscription", + APIVersion: "messaging.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-subscription-2", Namespace: testNs}, + } + testSubscription3 = messagingv1.Subscription{ + TypeMeta: metav1.TypeMeta{ + Kind: "Subscription", + APIVersion: "messaging.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-subscription-3", Namespace: testNs}, + } + testNsSubscriptions = []messagingv1.Subscription{testSubscription1, testSubscription2, testSubscription3} +) + +var ( + testEventtype1 = eventingv1beta2.EventType{ + TypeMeta: metav1.TypeMeta{ + Kind: "EventType", + APIVersion: "eventing.knative.dev/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-eventtype-1", Namespace: testNs}, + } + testEventtype2 = eventingv1beta2.EventType{ + TypeMeta: metav1.TypeMeta{ + Kind: "EventType", + APIVersion: "eventing.knative.dev/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-eventtype-2", Namespace: testNs}, + } + testEventtype3 = eventingv1beta2.EventType{ + TypeMeta: metav1.TypeMeta{ + Kind: "EventType", + APIVersion: "eventing.knative.dev/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-eventtype-3", Namespace: testNs}, + } + testEventtypes = []eventingv1beta2.EventType{testEventtype1, testEventtype2, testEventtype3} + fakeEventingBeta2Client = &beta2fake.FakeEventingV1beta2{Fake: &clienttesting.Fake{}} +) + +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 + }, + NewGitopsServingClient: func(namespace string, dir string) (v1.KnServingClient, error) { + return v1.NewKnServingGitOpsClient(namespace, dir), nil + }, + NewEventingClient: func(namespace string) (clienteventingv1.KnEventingClient, error) { + return clienteventingv1.NewKnEventingClient(fakeEventing, namespace), nil + }, + NewServingV1beta1Client: func(namespace string) (clientv1beta1.KnServingClient, error) { + return clientv1beta1.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 + }, + NewEventingV1beta2Client: func(namespace string) (clienteventingv1beta2.KnEventingV1Beta2Client, error) { + return clienteventingv1beta2.NewKnEventingV1Beta2Client(fakeEventingBeta2Client, namespace), nil + }, + ClientConfig: blankConfig, + } +} + +func TestResourceNameCompletionFuncService(t *testing.T) { + completionFunc := ResourceNameCompletionFunc(knParams) + + fakeServing.AddReactor("list", "services", + 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, &servingv1.ServiceList{Items: testNsServices}, nil + }) + + tests := []testType{ + { + "Empty suggestions when no parent command found", + testNs, + knParams, + nil, + "", + "no-parent", + }, + { + "Empty suggestions when non-zero args", + testNs, + knParams, + []string{"xyz"}, + "", + "service", + }, + { + "Empty suggestions when no namespace flag", + "", + knParams, + nil, + "", + "service", + }, + { + "Suggestions when test-ns namespace set", + testNs, + knParams, + nil, + "", + "service", + }, + { + "Empty suggestions when toComplete is not a prefix", + testNs, + knParams, + nil, + "xyz", + "service", + }, + { + "Empty suggestions when error during list operation", + errorNs, + knParams, + nil, + "", + "service", + }, + } + + 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 TestResourceNameCompletionFuncBroker(t *testing.T) { + completionFunc := ResourceNameCompletionFunc(knParams) + + 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 brokers")) + } + return true, &eventingv1.BrokerList{Items: testNsBrokers}, nil + }) + tests := []testType{ + { + "Empty suggestions when non-zero args", + testNs, + knParams, + []string{"xyz"}, + "", + "broker", + }, + { + "Empty suggestions when no namespace flag", + "", + knParams, + nil, + "", + "broker", + }, + { + "Suggestions when test-ns namespace set", + testNs, + knParams, + nil, + "", + "broker", + }, + { + "Empty suggestions when toComplete is not a prefix", + testNs, + knParams, + nil, + "xyz", + "broker", + }, + { + "Empty suggestions when error during list operation", + errorNs, + knParams, + nil, + "", + "broker", + }, + } + 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 TestResourceNameCompletionFuncRevision(t *testing.T) { + completionFunc := ResourceNameCompletionFunc(knParams) + + fakeServing.AddReactor("list", "revisions", + func(a clienttesting.Action) (bool, runtime.Object, error) { + if a.GetNamespace() == errorNs { + return true, nil, errors.NewInternalError(fmt.Errorf("unable to list revisions")) + } + return true, &servingv1.RevisionList{Items: testNsRevs}, nil + }) + + tests := []testType{ + { + "Empty suggestions when non-zero args", + testNs, + knParams, + []string{"xyz"}, + "", + "revision", + }, + { + "Empty suggestions when no namespace flag", + "", + knParams, + nil, + "", + "revision", + }, + { + "Suggestions when test-ns namespace set", + testNs, + knParams, + nil, + "", + "revision", + }, + { + "Empty suggestions when toComplete is not a prefix", + testNs, + knParams, + nil, + "xyz", + "revision", + }, + { + "Empty suggestions when error during list operation", + errorNs, + knParams, + nil, + "", + "revision", + }, + } + 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 TestResourceNameCompletionFuncGitOps(t *testing.T) { + tempDir := setupTempDir(t) + + completionFunc := ResourceNameCompletionFunc(knParams) + + tests := []testType{ + { + "Empty suggestions when no parent command found", + testNs, + knParams, + nil, + "", + "service", + }, + { + "Empty suggestions when non-zero args", + testNs, + knParams, + []string{"xyz"}, + "", + "service", + }, + { + "Empty suggestions when no namespace flag", + "", + knParams, + nil, + "", + "service", + }, + { + "Suggestions when test-ns namespace set", + testNs, + knParams, + nil, + "", + "service", + }, + { + "Empty suggestions when toComplete is not a prefix", + testNs, + knParams, + nil, + "xyz", + "service", + }, + { + "Empty suggestions when error during list operation", + errorNs, + knParams, + nil, + "", + "service", + }, + } + + 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] + cmd.Flags().String("target", tempDir, "target directory") + cmd.Flags().Set("namespace", tt.namespace) + + expectedSuggestions := expectedFunc(config) + expectedDirective := cobra.ShellCompDirectiveNoFileComp + actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete) + assert.DeepEqual(t, actualSuggestions, expectedSuggestions) + assert.Equal(t, actualDirective, expectedDirective) + }) + } +} + +func TestResourceNameCompletionFuncRoute(t *testing.T) { + completionFunc := ResourceNameCompletionFunc(knParams) + + 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 routes")) + } + return true, &servingv1.RouteList{Items: testNsRoutes}, nil + }) + + tests := []testType{ + { + "Empty suggestions when non-zero args", + testNs, + knParams, + []string{"xyz"}, + "", + "route", + }, + { + "Empty suggestions when no namespace flag", + "", + knParams, + nil, + "", + "route", + }, + { + "Suggestions when test-ns namespace set", + testNs, + knParams, + nil, + "", + "route", + }, + { + "Empty suggestions when toComplete is not a prefix", + testNs, + knParams, + nil, + "xyz", + "route", + }, + { + "Empty suggestions when error during list operation", + errorNs, + knParams, + nil, + "", + "route", + }, + } + 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 TestResourceNameCompletionFuncDomain(t *testing.T) { + completionFunc := ResourceNameCompletionFunc(knParams) + + 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 domains")) + } + return true, &servingv1beta1.DomainMappingList{Items: testNsDomains}, nil + }) + + tests := []testType{ + { + "Empty suggestions when non-zero args", + testNs, + knParams, + []string{"xyz"}, + "", + "domain", + }, + { + "Empty suggestions when no namespace flag", + "", + knParams, + nil, + "", + "domain", + }, + { + "Suggestions when test-ns namespace set", + testNs, + knParams, + nil, + "", + "domain", + }, + { + "Empty suggestions when toComplete is not a prefix", + testNs, + knParams, + nil, + "xyz", + "domain", + }, + { + "Empty suggestions when error during list operation", + errorNs, + knParams, + nil, + "", + "domain", + }, + } + 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 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(&messagingv1.ChannelList{Items: testNsChannels}, nil) + channelClient.Recorder().ListChannel(&messagingv1.ChannelList{Items: testNsChannels}, nil) + + channelClient.Recorder().ListChannel(&messagingv1.ChannelList{Items: testNsChannels}, nil) + channelClient.Recorder().ListChannel(&messagingv1.ChannelList{Items: testNsChannels}, nil) + + channelClient.Recorder().ListChannel(&messagingv1.ChannelList{}, fmt.Errorf("error listing channels")) + channelClient.Recorder().ListChannel(&messagingv1.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(&messagingv1.SubscriptionList{Items: testNsSubscriptions}, nil) + subscriptionsClient.Recorder().ListSubscription(&messagingv1.SubscriptionList{Items: testNsSubscriptions}, nil) + + subscriptionsClient.Recorder().ListSubscription(&messagingv1.SubscriptionList{Items: testNsSubscriptions}, nil) + subscriptionsClient.Recorder().ListSubscription(&messagingv1.SubscriptionList{Items: testNsSubscriptions}, nil) + + subscriptionsClient.Recorder().ListSubscription(&messagingv1.SubscriptionList{}, fmt.Errorf("error listing channels")) + subscriptionsClient.Recorder().ListSubscription(&messagingv1.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 TestResourceNameCompletionFuncEventtype(t *testing.T) { + completionFunc := ResourceNameCompletionFunc(knParams) + + fakeEventingBeta2Client.AddReactor("list", "eventtypes", func(a clienttesting.Action) (bool, runtime.Object, error) { + if a.GetNamespace() == errorNs { + return true, nil, errors.NewInternalError(fmt.Errorf("unable to list eventtypes")) + } + return true, &eventingv1beta2.EventTypeList{Items: testEventtypes}, nil + }) + + tests := []testType{ + { + "Empty suggestions when non-zero args", + testNs, + knParams, + []string{"xyz"}, + "", + "eventtype", + }, + { + "Empty suggestions when no namespace flag", + "", + knParams, + nil, + "", + "eventtype", + }, + { + "Suggestions when test-ns namespace set", + testNs, + knParams, + nil, + "", + "eventtype", + }, + { + "Empty suggestions when toComplete is not a prefix", + testNs, + knParams, + nil, + "xyz", + "eventtype", + }, + { + "Empty suggestions when error during list operation", + errorNs, + knParams, + nil, + "", + "eventtype", + }, + } + 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 getResourceCommandWithTestSubcommand(resource string, addNamespace, addSubcommand bool) *cobra.Command { + testCommand := &cobra.Command{ + Use: resource, + } + testSubCommand := &cobra.Command{ + Use: "test", + } + if addSubcommand { + testCommand.AddCommand(testSubCommand) + } + if addNamespace { + AddNamespaceFlags(testCommand.Flags(), true) + AddNamespaceFlags(testSubCommand.Flags(), true) + } + return testSubCommand +} + +func setupTempDir(t *testing.T) string { + tempDir := t.TempDir() + + svcPath := path.Join(tempDir, "test-ns", "ksvc") + err := os.MkdirAll(svcPath, 0700) + assert.NilError(t, err) + + for i, testSvc := range []servingv1.Service{testSvc1, testSvc2, testSvc3} { + tempFile, err := os.Create(path.Join(svcPath, fmt.Sprintf("test-svc-%d.yaml", i+1))) + assert.NilError(t, err) + writeToFile(t, testSvc, tempFile) + } + + return tempDir +} + +func writeToFile(t *testing.T, testSvc servingv1.Service, tempFile *os.File) { + yamlPrinter, err := genericclioptions.NewJSONYamlPrintFlags().ToPrinter("yaml") + assert.NilError(t, err) + + err = yamlPrinter.PrintObj(&testSvc, tempFile) + assert.NilError(t, err) + + defer tempFile.Close() +} diff --git a/pkg/commands/flags/listprint.go b/pkg/commands/flags/list/print.go similarity index 60% rename from pkg/commands/flags/listprint.go rename to pkg/commands/flags/list/print.go index 098674f9..6f59b26a 100644 --- a/pkg/commands/flags/listprint.go +++ b/pkg/commands/flags/list/print.go @@ -1,18 +1,20 @@ -// Copyright © 2019 The Knative Authors -// -// 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 flags +/* + Copyright 2024 The Knative Authors + + 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 list import ( "io" @@ -26,16 +28,15 @@ import ( "knative.dev/client-pkg/pkg/util" ) -// ListFlags composes common printer flag structs -// used in the list command. -type ListPrintFlags struct { +// PrintFlags composes common printer flag structs used in the list command. +type PrintFlags struct { GenericPrintFlags *genericclioptions.PrintFlags HumanReadableFlags *commands.HumanPrintFlags PrinterHandler func(h hprinters.PrintHandler) } // AllowedFormats is the list of formats in which data can be displayed -func (f *ListPrintFlags) AllowedFormats() []string { +func (f *PrintFlags) AllowedFormats() []string { formats := f.GenericPrintFlags.AllowedFormats() formats = append(formats, f.HumanReadableFlags.AllowedFormats()...) return formats @@ -43,7 +44,7 @@ func (f *ListPrintFlags) AllowedFormats() []string { // ToPrinter attempts to find a composed set of ListTypesFlags suitable for // returning a printer based on current flag values. -func (f *ListPrintFlags) ToPrinter() (hprinters.ResourcePrinter, error) { +func (f *PrintFlags) ToPrinter() (hprinters.ResourcePrinter, error) { // if there are flags specified for generic printing if f.GenericPrintFlags.OutputFlagSpecified() { p, err := f.GenericPrintFlags.ToPrinter() @@ -61,7 +62,7 @@ func (f *ListPrintFlags) ToPrinter() (hprinters.ResourcePrinter, error) { } // Print is to print an Object to a Writer -func (f *ListPrintFlags) Print(obj runtime.Object, w io.Writer) error { +func (f *PrintFlags) Print(obj runtime.Object, w io.Writer) error { printer, err := f.ToPrinter() if err != nil { return err @@ -80,15 +81,15 @@ func (f *ListPrintFlags) Print(obj runtime.Object, w io.Writer) error { // AddFlags receives a *cobra.Command reference and binds // flags related to humanreadable and template printing. -func (f *ListPrintFlags) AddFlags(cmd *cobra.Command) { +func (f *PrintFlags) AddFlags(cmd *cobra.Command) { f.GenericPrintFlags.AddFlags(cmd) f.HumanReadableFlags.AddFlags(cmd) } -// NewListFlags returns flags associated with humanreadable, -// template, and "name" printing, with default values set. -func NewListPrintFlags(printer func(h hprinters.PrintHandler)) *ListPrintFlags { - return &ListPrintFlags{ +// NewPrintFlags returns flags associated with humanreadable, template, and +// "name" printing, with default values set. +func NewPrintFlags(printer func(h hprinters.PrintHandler)) *PrintFlags { + return &PrintFlags{ GenericPrintFlags: genericclioptions.NewPrintFlags(""), HumanReadableFlags: commands.NewHumanPrintFlags(), PrinterHandler: printer, @@ -97,6 +98,6 @@ func NewListPrintFlags(printer func(h hprinters.PrintHandler)) *ListPrintFlags { // EnsureWithNamespace ensures that humanreadable flags return // a printer capable of printing with a "namespace" column. -func (f *ListPrintFlags) EnsureWithNamespace() { +func (f *PrintFlags) EnsureWithNamespace() { f.HumanReadableFlags.EnsureWithNamespace() } diff --git a/pkg/commands/flags/listprint_test.go b/pkg/commands/flags/list/print_test.go similarity index 76% rename from pkg/commands/flags/listprint_test.go rename to pkg/commands/flags/list/print_test.go index 972f15f4..30e1ba54 100644 --- a/pkg/commands/flags/listprint_test.go +++ b/pkg/commands/flags/list/print_test.go @@ -1,18 +1,20 @@ -// Copyright © 2019 The Knative Authors -// -// 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 flags +/* + Copyright 2024 The Knative Authors + + 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 list_test import ( "bytes" @@ -25,6 +27,7 @@ import ( metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/duration" + "knative.dev/client-pkg/pkg/commands/flags/list" "k8s.io/cli-runtime/pkg/genericclioptions" "knative.dev/client-pkg/pkg/printers" @@ -56,7 +59,7 @@ var ( ) func TestListPrintFlagsFormats(t *testing.T) { - flags := NewListPrintFlags(nil) + flags := list.NewPrintFlags(nil) formats := flags.AllowedFormats() expected := []string{"json", "yaml", "name", "go-template", "go-template-file", "template", "templatefile", "jsonpath", "jsonpath-as-json", "jsonpath-file", "no-headers"} assert.DeepEqual(t, formats, expected) @@ -64,7 +67,7 @@ func TestListPrintFlagsFormats(t *testing.T) { func TestListPrintFlags(t *testing.T) { var cmd *cobra.Command - flags := NewListPrintFlags(func(h hprinters.PrintHandler) {}) + flags := list.NewPrintFlags(func(h hprinters.PrintHandler) {}) cmd = &cobra.Command{} flags.AddFlags(cmd) @@ -82,8 +85,8 @@ func TestListPrintFlags(t *testing.T) { func TestListPrintFlagsPrint(t *testing.T) { var cmd *cobra.Command - flags := NewListPrintFlags(func(h hprinters.PrintHandler) { - h.TableHandler(columnDefs, validPrintFunc) + flags := list.NewPrintFlags(func(h hprinters.PrintHandler) { + assert.NilError(t, h.TableHandler(columnDefs, validPrintFunc)) }) cmd = &cobra.Command{} @@ -108,8 +111,8 @@ func TestListPrintFlagsPrint(t *testing.T) { func TestEnsureNamespaces(t *testing.T) { var cmd *cobra.Command - flags := NewListPrintFlags(func(h hprinters.PrintHandler) { - h.TableHandler(columnDefs, validPrintFunc) + flags := list.NewPrintFlags(func(h hprinters.PrintHandler) { + assert.NilError(t, h.TableHandler(columnDefs, validPrintFunc)) }) cmd = &cobra.Command{} diff --git a/pkg/commands/flags/sink.go b/pkg/commands/flags/sink/flag.go similarity index 55% rename from pkg/commands/flags/sink.go rename to pkg/commands/flags/sink/flag.go index 5a1c0281..ba03e586 100644 --- a/pkg/commands/flags/sink.go +++ b/pkg/commands/flags/sink/flag.go @@ -1,18 +1,20 @@ -// Copyright © 2019 The Knative Authors -// -// 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 flags +/* + Copyright 2024 The Knative Authors + + 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 sink import ( "context" @@ -29,32 +31,44 @@ import ( clientdynamic "knative.dev/client-pkg/pkg/dynamic" ) -type SinkFlags struct { - sink string +// Flag holds the sink flag and its mappings +type Flag struct { + Sink string + SinkMappings map[string]schema.GroupVersionResource +} + +// NewFlag is a constructor function to create Flag from provided map +func NewFlag(mapping map[string]schema.GroupVersionResource) *Flag { + return &Flag{ + SinkMappings: mapping, + } } -// AddWithFlagName configures sink flag with given flag name and a short flag name -// pass empty short flag name if you dont want to set one -func (i *SinkFlags) AddWithFlagName(cmd *cobra.Command, fname, short string) { +// AddWithFlagName configures Sink flag with given flag name and a short flag name +// pass empty short flag name if you don't want to set one +func (i *Flag) AddWithFlagName(cmd *cobra.Command, fname, short string) { flag := "--" + fname if short == "" { - cmd.Flags().StringVar(&i.sink, fname, "", "") + cmd.Flags().StringVar(&i.Sink, fname, "", "") } else { - cmd.Flags().StringVarP(&i.sink, fname, short, "", "") + cmd.Flags().StringVarP(&i.Sink, fname, short, "", "") } cmd.Flag(fname).Usage = "Addressable sink for events. " + "You can specify a broker, channel, Knative service or URI. " + "Examples: '" + flag + " broker:nest' for a broker 'nest', " + "'" + flag + " channel:pipe' for a channel 'pipe', " + "'" + flag + " ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', " + - "'" + flag + " https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, " + + "'" + flag + " https://event.receiver.uri' for an HTTP URI, " + "'" + flag + " ksvc:receiver' or simply '" + flag + " receiver' for a Knative service 'receiver' in the current namespace. " + - "If a prefix is not provided, it is considered as a Knative service in the current namespace. " + - "If referring to a Knative service in another namespace, 'ksvc:name:namespace' combination must be provided explicitly." - + "'" + flag + " special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. " + + "If a prefix is not provided, it is considered as a Knative service in the current namespace." + // Use default mapping if empty + if i.SinkMappings == nil { + i.SinkMappings = defaultSinkMappings + } for _, p := range config.GlobalConfig.SinkMappings() { //user configuration might override the default configuration - sinkMappings[p.Prefix] = schema.GroupVersionResource{ + i.SinkMappings[p.Prefix] = schema.GroupVersionResource{ Resource: p.Resource, Group: p.Group, Version: p.Version, @@ -62,13 +76,13 @@ func (i *SinkFlags) AddWithFlagName(cmd *cobra.Command, fname, short string) { } } -// Add configures sink flag with name 'sink' amd short name 's' -func (i *SinkFlags) Add(cmd *cobra.Command) { +// Add configures Sink flag with name 'Sink' amd short name 's' +func (i *Flag) Add(cmd *cobra.Command) { i.AddWithFlagName(cmd, "sink", "s") } -// sinkPrefixes maps prefixes used for sinks to their GroupVersionResources. -var sinkMappings = map[string]schema.GroupVersionResource{ +// SinkPrefixes maps prefixes used for sinks to their GroupVersionResources. +var defaultSinkMappings = map[string]schema.GroupVersionResource{ "broker": { Resource: "brokers", Group: "eventing.knative.dev", @@ -89,12 +103,16 @@ var sinkMappings = map[string]schema.GroupVersionResource{ // ResolveSink returns the Destination referred to by the flags in the acceptor. // It validates that any object the user is referring to exists. -func (i *SinkFlags) ResolveSink(ctx context.Context, knclient clientdynamic.KnDynamicClient, namespace string) (*duckv1.Destination, error) { +func (i *Flag) ResolveSink(ctx context.Context, knclient clientdynamic.KnDynamicClient, namespace string) (*duckv1.Destination, error) { client := knclient.RawClient() - if i.sink == "" { + if i.Sink == "" { return nil, nil } - prefix, name, ns := parseSink(i.sink) + // Use default mapping if empty + if i.SinkMappings == nil { + i.SinkMappings = defaultSinkMappings + } + prefix, name, ns := parseSink(i.Sink) if prefix == "" { // URI target uri, err := apis.ParseURL(name) @@ -103,10 +121,10 @@ func (i *SinkFlags) ResolveSink(ctx context.Context, knclient clientdynamic.KnDy } return &duckv1.Destination{URI: uri}, nil } - typ, ok := sinkMappings[prefix] + gvr, ok := i.SinkMappings[prefix] if !ok { if prefix == "svc" || prefix == "service" { - return nil, fmt.Errorf("unsupported sink prefix: '%s', please use prefix 'ksvc' for knative service", prefix) + return nil, fmt.Errorf("unsupported Sink prefix: '%s', please use prefix 'ksvc' for knative service", prefix) } idx := strings.LastIndex(prefix, "/") var groupVersion string @@ -116,21 +134,24 @@ func (i *SinkFlags) ResolveSink(ctx context.Context, knclient clientdynamic.KnDy } else { kind = prefix } - parsedVersion, _ := schema.ParseGroupVersion(groupVersion) + parsedVersion, err := schema.ParseGroupVersion(groupVersion) + if err != nil { + return nil, err + } + // For the RAWclient the resource name must be in lower case plural form. + // This is the best effort to sanitize the inputs, but the safest way is to provide + // the appropriate form in user's input. if !strings.HasSuffix(kind, "s") { kind = kind + "s" } - typ = schema.GroupVersionResource{ - Group: parsedVersion.Group, - Version: parsedVersion.Version, - Resource: kind, - } + kind = strings.ToLower(kind) + gvr = parsedVersion.WithResource(kind) } if ns != "" { namespace = ns } - obj, err := client.Resource(typ).Namespace(namespace).Get(ctx, name, metav1.GetOptions{}) + obj, err := client.Resource(gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil, err } @@ -163,10 +184,10 @@ func parseSink(sink string) (string, string, string) { } } -// SinkToString prepares a sink for list output +// SinkToString prepares a Sink for list output func SinkToString(sink duckv1.Destination) string { if sink.Ref != nil { - if sink.Ref.Kind == "Service" && strings.HasPrefix(sink.Ref.APIVersion, sinkMappings["ksvc"].Group) { + if sink.Ref.Kind == "Service" && strings.HasPrefix(sink.Ref.APIVersion, defaultSinkMappings["ksvc"].Group) { return fmt.Sprintf("ksvc:%s", sink.Ref.Name) } else { return fmt.Sprintf("%s:%s", strings.ToLower(sink.Ref.Kind), sink.Ref.Name) diff --git a/pkg/commands/flags/sink_test.go b/pkg/commands/flags/sink/flag_test.go similarity index 79% rename from pkg/commands/flags/sink_test.go rename to pkg/commands/flags/sink/flag_test.go index 622bd0c2..66026746 100644 --- a/pkg/commands/flags/sink_test.go +++ b/pkg/commands/flags/sink/flag_test.go @@ -1,18 +1,20 @@ -// Copyright © 2019 The Knative Authors -// -// 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 flags +/* + Copyright 2024 The Knative Authors + + 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 sink_test import ( "context" @@ -21,6 +23,7 @@ import ( "github.com/spf13/cobra" "gotest.tools/v3/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/client-pkg/pkg/commands/flags/sink" eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" "knative.dev/eventing/pkg/apis/sources/v1beta2" @@ -58,7 +61,7 @@ func TestSinkFlagAdd(t *testing.T) { } for _, tc := range cases { c := &cobra.Command{Use: "sinktest"} - sinkFlags := new(SinkFlags) + sinkFlags := sink.Flag{} if tc.flagName == "" { sinkFlags.Add(c) assert.Equal(t, tc.expectedFlagName, c.Flag("sink").Name) @@ -72,7 +75,7 @@ func TestSinkFlagAdd(t *testing.T) { } func TestResolve(t *testing.T) { - targetExampleCom, err := apis.ParseURL("http://target.example.com") + targetExampleCom, err := apis.ParseURL("https://target.example.com") assert.NilError(t, err) mysvc := &servingv1.Service{ @@ -130,7 +133,19 @@ func TestResolve(t *testing.T) { Namespace: "default", Name: "foo", }}, ""}, - {"http://target.example.com", &duckv1.Destination{ + {"sources.knative.dev/v1/Pingsource:foo", &duckv1.Destination{Ref: &duckv1.KReference{ + APIVersion: "sources.knative.dev/v1", + Kind: "PingSource", + Namespace: "default", + Name: "foo", + }}, ""}, + {"sources.knative.dev/v1/PingSources:foo", &duckv1.Destination{Ref: &duckv1.KReference{ + APIVersion: "sources.knative.dev/v1", + Kind: "PingSource", + Namespace: "default", + Name: "foo", + }}, ""}, + {"https://target.example.com", &duckv1.Destination{ URI: targetExampleCom, }, ""}, {"k8ssvc:foo", nil, "k8ssvcs \"foo\" not found"}, @@ -141,7 +156,7 @@ func TestResolve(t *testing.T) { dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", mysvc, defaultBroker, pipeChannel, pingSource) for _, c := range cases { - i := &SinkFlags{c.sink} + i := &sink.Flag{Sink: c.sink} result, err := i.ResolveSink(context.Background(), dynamicClient, "default") if c.destination != nil { assert.DeepEqual(t, result, c.destination) @@ -185,7 +200,7 @@ func TestResolveWithNamespace(t *testing.T) { } dynamicClient := dynamicfake.CreateFakeKnDynamicClient("my-namespace", mysvc, defaultBroker, pipeChannel) for _, c := range cases { - i := &SinkFlags{c.sink} + i := &sink.Flag{Sink: c.sink} result, err := i.ResolveSink(context.Background(), dynamicClient, "default") if c.destination != nil { assert.DeepEqual(t, result, c.destination) @@ -198,34 +213,34 @@ func TestResolveWithNamespace(t *testing.T) { } func TestSinkToString(t *testing.T) { - sink := duckv1.Destination{ + dest := duckv1.Destination{ Ref: &duckv1.KReference{Kind: "Service", APIVersion: "serving.knative.dev/v1", Namespace: "my-namespace", Name: "mysvc"}} expected := "ksvc:mysvc" - assert.Equal(t, expected, SinkToString(sink)) - sink = duckv1.Destination{ + assert.Equal(t, expected, sink.SinkToString(dest)) + dest = duckv1.Destination{ Ref: &duckv1.KReference{Kind: "Broker", APIVersion: "eventing.knative.dev/v1", Namespace: "my-namespace", Name: "default"}} expected = "broker:default" - assert.Equal(t, expected, SinkToString(sink)) - sink = duckv1.Destination{ + assert.Equal(t, expected, sink.SinkToString(dest)) + dest = duckv1.Destination{ Ref: &duckv1.KReference{Kind: "Service", APIVersion: "v1", Namespace: "my-namespace", Name: "mysvc"}} expected = "service:mysvc" - assert.Equal(t, expected, SinkToString(sink)) + assert.Equal(t, expected, sink.SinkToString(dest)) - uri := "http://target.example.com" + uri := "https://target.example.com" targetExampleCom, err := apis.ParseURL(uri) assert.NilError(t, err) - sink = duckv1.Destination{ + dest = duckv1.Destination{ URI: targetExampleCom, } - assert.Equal(t, uri, SinkToString(sink)) - assert.Equal(t, "", SinkToString(duckv1.Destination{})) + assert.Equal(t, uri, sink.SinkToString(dest)) + assert.Equal(t, "", sink.SinkToString(duckv1.Destination{})) } diff --git a/pkg/commands/testing_helper_test.go b/pkg/commands/testing_helper_test.go new file mode 100644 index 00000000..f78ae967 --- /dev/null +++ b/pkg/commands/testing_helper_test.go @@ -0,0 +1,66 @@ +// Copyright © 2018 The Knative Authors +// +// 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 commands + +import ( + "fmt" + "testing" + + "github.com/spf13/cobra" + "gotest.tools/v3/assert" + + "knative.dev/client-pkg/pkg/util/test" +) + +func TestCreateTestKnCommand(t *testing.T) { + knParams := &KnParams{} + knCmd, serving, buffer := CreateTestKnCommand(&cobra.Command{Use: "fake"}, knParams) + assert.Assert(t, knCmd != nil) + assert.Assert(t, serving != nil) + assert.Assert(t, buffer != nil) + assert.Assert(t, len(knCmd.Commands()) == 1) + assert.Assert(t, knCmd.Commands()[0].Use == "fake") +} + +func TestCreateSourcesTestKnCommand(t *testing.T) { + knParams := &KnParams{} + knCmd, sources, buffer := CreateSourcesTestKnCommand(&cobra.Command{Use: "fake"}, knParams) + assert.Assert(t, knCmd != nil) + assert.Assert(t, sources != nil) + assert.Assert(t, buffer != nil) + assert.Assert(t, len(knCmd.Commands()) == 1) + assert.Assert(t, knCmd.Commands()[0].Use == "fake") +} + +func TestCreateDynamicTestKnCommand(t *testing.T) { + knParams := &KnParams{} + knCmd, dynamic, buffer := CreateDynamicTestKnCommand(&cobra.Command{Use: "fake"}, knParams) + assert.Assert(t, knCmd != nil) + assert.Assert(t, dynamic != nil) + assert.Assert(t, buffer != nil) + assert.Assert(t, len(knCmd.Commands()) == 1) + assert.Assert(t, knCmd.Commands()[0].Use == "fake") + client, err := knParams.NewDynamicClient("foo") + assert.NilError(t, err) + assert.Assert(t, client != nil) +} + +func TestCaptureStdout(t *testing.T) { + c := test.CaptureOutput(t) + fmt.Print("Hello World !") + stdOut, stdErr := c.Close() + assert.Equal(t, stdErr, "") + assert.Equal(t, stdOut, "Hello World !") +} diff --git a/pkg/commands/types.go b/pkg/commands/types.go index f1004e4b..c792b8b6 100644 --- a/pkg/commands/types.go +++ b/pkg/commands/types.go @@ -20,11 +20,13 @@ import ( "os" "path/filepath" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" eventingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1" - eventingv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1" + eventingv1beta2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2" messagingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1" sourcesv1client "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1" sourcesv1beta2client "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2" @@ -36,7 +38,7 @@ import ( clientdynamic "knative.dev/client-pkg/pkg/dynamic" knerrors "knative.dev/client-pkg/pkg/errors" clienteventingv1 "knative.dev/client-pkg/pkg/eventing/v1" - clienteventingv1beta1 "knative.dev/client-pkg/pkg/eventing/v1beta1" + clienteventingv1beta2 "knative.dev/client-pkg/pkg/eventing/v1beta2" clientmessagingv1 "knative.dev/client-pkg/pkg/messaging/v1" clientservingv1 "knative.dev/client-pkg/pkg/serving/v1" clientservingv1beta1 "knative.dev/client-pkg/pkg/serving/v1beta1" @@ -54,6 +56,7 @@ type KnParams struct { KubeAsUID string KubeAsGroup []string ClientConfig clientcmd.ClientConfig + NewKubeClient func() (kubernetes.Interface, error) NewServingClient func(namespace string) (clientservingv1.KnServingClient, error) NewServingV1beta1Client func(namespace string) (clientservingv1beta1.KnServingClient, error) NewGitopsServingClient func(namespace string, dir string) (clientservingv1.KnServingClient, error) @@ -62,7 +65,7 @@ type KnParams struct { NewEventingClient func(namespace string) (clienteventingv1.KnEventingClient, error) NewMessagingClient func(namespace string) (clientmessagingv1.KnMessagingClient, error) NewDynamicClient func(namespace string) (clientdynamic.KnDynamicClient, error) - NewEventingV1beta1Client func(namespace string) (clienteventingv1beta1.KnEventingV1Beta1Client, error) + NewEventingV1beta2Client func(namespace string) (clienteventingv1beta2.KnEventingV1Beta2Client, error) // General global options LogHTTP bool @@ -72,6 +75,10 @@ type KnParams struct { } func (params *KnParams) Initialize() { + if params.NewKubeClient == nil { + params.NewKubeClient = params.newKubeClient + } + if params.NewServingClient == nil { params.NewServingClient = params.newServingClient } @@ -104,9 +111,23 @@ func (params *KnParams) Initialize() { params.NewSourcesV1beta2Client = params.newSourcesClientV1beta2 } - if params.NewEventingV1beta1Client == nil { - params.NewEventingV1beta1Client = params.newEventingV1Beta1Client + if params.NewEventingV1beta2Client == nil { + params.NewEventingV1beta2Client = params.newEventingV1Beta2Client + } +} + +func (params *KnParams) newKubeClient() (kubernetes.Interface, error) { + restConfig, err := params.RestConfig() + if err != nil { + return nil, err + } + + client, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return nil, err } + + return client, nil } func (params *KnParams) newServingClient(namespace string) (clientservingv1.KnServingClient, error) { @@ -169,14 +190,14 @@ func (params *KnParams) newEventingClient(namespace string) (clienteventingv1.Kn return clienteventingv1.NewKnEventingClient(client, namespace), nil } -func (params *KnParams) newEventingV1Beta1Client(namespace string) (clienteventingv1beta1.KnEventingV1Beta1Client, error) { +func (params *KnParams) newEventingV1Beta2Client(namespace string) (clienteventingv1beta2.KnEventingV1Beta2Client, error) { restConfig, err := params.RestConfig() if err != nil { return nil, err } - client, _ := eventingv1beta1.NewForConfig(restConfig) - return clienteventingv1beta1.NewKnEventingV1Beta1Client(client, namespace), nil + client, _ := eventingv1beta2.NewForConfig(restConfig) + return clienteventingv1beta2.NewKnEventingV1Beta2Client(client, namespace), nil } func (params *KnParams) newMessagingClient(namespace string) (clientmessagingv1.KnMessagingClient, error) { diff --git a/pkg/commands/types_test.go b/pkg/commands/types_test.go index 3ef621f0..e76aedf4 100644 --- a/pkg/commands/types_test.go +++ b/pkg/commands/types_test.go @@ -353,7 +353,7 @@ func TestNewSourcesV1beta2Client(t *testing.T) { } } -func TestNewServingV1alpha1Clients(t *testing.T) { +func TestNewServingV1beta1Clients(t *testing.T) { basic, err := clientcmd.NewClientConfigFromBytes([]byte(BASIC_KUBECONFIG)) namespace := "test" if err != nil { @@ -381,7 +381,7 @@ func TestNewServingV1alpha1Clients(t *testing.T) { LogHTTP: tc.logHttp, } - servingV1alpha1Client, err := p.newServingClientV1beta1(namespace) + servingV1beta1Client, err := p.newServingClientV1beta1(namespace) switch len(tc.expectedErrString) { case 0: @@ -397,8 +397,8 @@ func TestNewServingV1alpha1Clients(t *testing.T) { } } - if servingV1alpha1Client != nil { - assert.Assert(t, servingV1alpha1Client.Namespace() == namespace) + if servingV1beta1Client != nil { + assert.Assert(t, servingV1beta1Client.Namespace() == namespace) } } } @@ -513,7 +513,7 @@ func TestInitialize(t *testing.T) { assert.Assert(t, params.NewEventingClient != nil) assert.Assert(t, params.NewMessagingClient != nil) assert.Assert(t, params.NewDynamicClient != nil) - assert.Assert(t, params.NewEventingV1beta1Client != nil) + assert.Assert(t, params.NewEventingV1beta2Client != nil) basic, err := clientcmd.NewClientConfigFromBytes([]byte(BASIC_KUBECONFIG)) if err != nil { @@ -542,7 +542,7 @@ func TestInitialize(t *testing.T) { assert.NilError(t, err) assert.Assert(t, sourcesClient != nil) - eventingBeta1Client, err := params.NewEventingV1beta1Client("mockNamespace") + eventingBeta1Client, err := params.NewEventingV1beta2Client("mockNamespace") assert.NilError(t, err) assert.Assert(t, eventingBeta1Client != nil) } diff --git a/pkg/commands/wait_flags.go b/pkg/commands/wait_flags.go new file mode 100644 index 00000000..3ae3e4de --- /dev/null +++ b/pkg/commands/wait_flags.go @@ -0,0 +1,59 @@ +// Copyright © 2019 The Knative Authors +// +// 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 commands + +import ( + "fmt" + + "github.com/spf13/cobra" + + knflags "knative.dev/client-pkg/pkg/flags" +) + +// Default time out to use when waiting for reconciliation. It is deliberately very long as it is expected that +// the service doesn't stay in `Unknown` status very long and eventually ends up as `False` or `True` in a timely +// manner +const WaitDefaultTimeout = 600 + +// Flags for tuning wait behaviour +type WaitFlags struct { + // Timeout in seconds for how long to wait for a command to return + TimeoutInSeconds int + // If set then apply resources and wait for completion + Wait bool + // Duration in seconds for waiting between intermediate false ready conditions + ErrorWindowInSeconds int +} + +// Add flags which influence the wait/no-wait behaviour when creating, updating, waiting for +// resources. If the action is not `wait`, set `waitDefault` argument if the default behaviour is synchronous. +// Use `what` for describing what is waited for. +func (p *WaitFlags) AddConditionWaitFlags(command *cobra.Command, waitTimeoutDefault int, action, what, until string) { + if action != "wait" { + waitUsage := fmt.Sprintf("Wait for '%s %s' operation to be completed.", what, action) + waitDefault := true + // Special-case 'delete' command so it comes back to the user immediately + if action == "delete" { + waitDefault = false + } + + knflags.AddBothBoolFlagsUnhidden(command.Flags(), &p.Wait, "wait", "", waitDefault, waitUsage) + } + timeoutUsage := fmt.Sprintf("Seconds to wait before giving up on waiting for %s to be %s.", what, until) + command.Flags().IntVar(&p.TimeoutInSeconds, "wait-timeout", waitTimeoutDefault, timeoutUsage) + + windowUsage := fmt.Sprintf("Seconds to wait for %s to be %s after a false ready condition is returned", what, until) + command.Flags().IntVar(&p.ErrorWindowInSeconds, "wait-window", 2, windowUsage) +} diff --git a/pkg/commands/wait_flags_test.go b/pkg/commands/wait_flags_test.go new file mode 100644 index 00000000..14c7a7b1 --- /dev/null +++ b/pkg/commands/wait_flags_test.go @@ -0,0 +1,152 @@ +// Copyright © 2019 The Knative Authors +// +// 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 commands + +import ( + "fmt" + "strings" + "testing" + + knflags "knative.dev/client-pkg/pkg/flags" + + "github.com/spf13/cobra" + "gotest.tools/v3/assert" +) + +type waitTestCase struct { + args []string + timeoutExpected int + isWaitExpected bool + isParseErrorExpected bool +} + +func TestAddWaitForReadyDeprecatedFlags(t *testing.T) { + for i, tc := range []waitTestCase{ + {[]string{"--no-wait"}, 60, false, false}, + {[]string{}, 60, true, false}, + {[]string{"--wait-timeout=120"}, 120, true, false}, + // Can't be easily prevented, the timeout is just ignored in this case: + {[]string{"--no-wait", "--wait-timeout=120"}, 120, false, false}, + {[]string{"--wait-timeout=bla"}, 0, true, true}, + } { + + flags := &WaitFlags{} + cmd := cobra.Command{} + flags.AddConditionWaitFlags(&cmd, 60, "create", "service", "ready") + + err := cmd.ParseFlags(tc.args) + if err != nil && !tc.isParseErrorExpected { + t.Errorf("%d: parse flags: %v", i, err) + } + if err == nil && tc.isParseErrorExpected { + t.Errorf("%d: parse error expected, but got none: %v", i, err) + } + if tc.isParseErrorExpected { + continue + } + + // reconcile to ensure wait, no-wait behave as expected + err = knflags.ReconcileBoolFlags(cmd.Flags()) + assert.NilError(t, err) + + if flags.Wait != tc.isWaitExpected { + t.Errorf("%d: wrong wait mode detected: %t (expected) != %t (actual)", i, tc.isWaitExpected, flags.Wait) + } + if flags.TimeoutInSeconds != tc.timeoutExpected { + t.Errorf("%d: Invalid timeout set. %d (expected) != %d (actual)", i, tc.timeoutExpected, flags.TimeoutInSeconds) + } + } +} + +func TestAddWaitForReadyFlags(t *testing.T) { + for i, tc := range []waitTestCase{ + {[]string{}, 60, true, false}, + {[]string{"--wait-timeout=120"}, 120, true, false}, + // Can't be easily prevented, the timeout is just ignored in this case: + {[]string{"--no-wait", "--wait-timeout=120"}, 120, false, false}, + {[]string{"--wait-timeout=bla"}, 0, true, true}, + } { + + flags := &WaitFlags{} + cmd := cobra.Command{} + flags.AddConditionWaitFlags(&cmd, 60, "create", "service", "ready") + + err := cmd.ParseFlags(tc.args) + if err != nil && !tc.isParseErrorExpected { + t.Errorf("%d: parse flags: %v", i, err) + } + if err == nil && tc.isParseErrorExpected { + t.Errorf("%d: parse error expected, but got none: %v", i, err) + } + if tc.isParseErrorExpected { + continue + } + + // reconcile to ensure wait, no-wait behave as expected + err = knflags.ReconcileBoolFlags(cmd.Flags()) + assert.NilError(t, err) + fmt.Println("wait value") + fmt.Println(flags.Wait) + if flags.Wait != tc.isWaitExpected { + t.Errorf("%d: wrong wait mode detected: %t (expected) != %t (actual)", i, tc.isWaitExpected, flags.Wait) + } + if flags.TimeoutInSeconds != tc.timeoutExpected { + t.Errorf("%d: Invalid timeout set. %d (expected) != %d (actual)", i, tc.timeoutExpected, flags.TimeoutInSeconds) + } + } +} + +func TestAddWaitUsageMessage(t *testing.T) { + + flags := &WaitFlags{} + cmd := cobra.Command{} + flags.AddConditionWaitFlags(&cmd, 60, "bla", "blub", "deleted") + if !strings.Contains(cmd.UsageString(), "blub") { + t.Error("no type returned in usage") + } + if !strings.Contains(cmd.UsageString(), "Do not wait") { + t.Error("wrong usage message") + } + if !strings.Contains(cmd.UsageString(), "60") { + t.Error("default timeout not contained") + } + if !strings.Contains(cmd.UsageString(), "deleted") { + t.Error("wrong until message") + } +} + +func TestAddWaitUsageDelete(t *testing.T) { + flags := &WaitFlags{} + cmd := cobra.Command{} + flags.AddConditionWaitFlags(&cmd, 60, "delete", "blub", "deleted") + if !strings.Contains(cmd.UsageString(), "completed. (default true)") { + t.Error("Delete has wrong default value for --no-wait") + } +} + +func TestAddWaitUsageWait(t *testing.T) { + flags := &WaitFlags{} + cmd := cobra.Command{} + flags.AddConditionWaitFlags(&cmd, 60, "wait", "blub", "ready") + if !strings.Contains(cmd.UsageString(), "blub") { + t.Error("no type returned in usage") + } + if !strings.Contains(cmd.UsageString(), "60") { + t.Error("default timeout not contained") + } + if !strings.Contains(cmd.UsageString(), "ready") { + t.Error("wrong until message") + } +} diff --git a/pkg/errors/factory_test.go b/pkg/errors/factory_test.go index 1a5e6956..03f08b7c 100644 --- a/pkg/errors/factory_test.go +++ b/pkg/errors/factory_test.go @@ -167,9 +167,10 @@ func TestIsForbiddenError(t *testing.T) { }, } for _, tc := range cases { + itc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() - assert.Equal(t, IsForbiddenError(GetError(tc.Error)), tc.Forbidden) + assert.Equal(t, IsForbiddenError(GetError(itc.Error)), itc.Forbidden) }) } } @@ -207,9 +208,10 @@ func TestIsInternalError(t *testing.T) { } for _, tc := range cases { + itc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() - assert.Equal(t, api_errors.IsInternalError(GetError(tc.Error)), tc.Internal) + assert.Equal(t, api_errors.IsInternalError(GetError(itc.Error)), itc.Internal) }) } } @@ -240,9 +242,10 @@ func TestStatusError(t *testing.T) { }, } for _, tc := range cases { + itc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() - assert.Assert(t, tc.ErrorType(GetError(tc.Error))) + assert.Assert(t, itc.ErrorType(GetError(itc.Error))) }) } } diff --git a/pkg/eventing/v1/client_mock.go b/pkg/eventing/v1/client_mock.go new file mode 100644 index 00000000..b95d2561 --- /dev/null +++ b/pkg/eventing/v1/client_mock.go @@ -0,0 +1,183 @@ +// Copyright © 2019 The Knative Authors +// +// 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 v1 + +import ( + "context" + "testing" + "time" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + + "knative.dev/client-pkg/pkg/util/mock" +) + +// MockKnEventingClient is a combine of test object and recorder +type MockKnEventingClient struct { + t *testing.T + recorder *EventingRecorder +} + +// NewMockKnEventingClient returns a new mock instance which you need to record for +func NewMockKnEventingClient(t *testing.T, ns ...string) *MockKnEventingClient { + namespace := "default" + if len(ns) > 0 { + namespace = ns[0] + } + return &MockKnEventingClient{ + t: t, + recorder: &EventingRecorder{mock.NewRecorder(t, namespace)}, + } +} + +// Ensure that the interface is implemented +var _ KnEventingClient = &MockKnEventingClient{} + +// EventingRecorder is recorder for eventing objects +type EventingRecorder struct { + r *mock.Recorder +} + +// Recorder returns the recorder for registering API calls +func (c *MockKnEventingClient) Recorder() *EventingRecorder { + return c.recorder +} + +// Namespace of this client +func (c *MockKnEventingClient) Namespace() string { + return c.recorder.r.Namespace() +} + +// CreateTrigger records a call for CreatePingSource with the expected error +func (sr *EventingRecorder) CreateTrigger(trigger interface{}, err error) { + sr.r.Add("CreateTrigger", []interface{}{trigger}, []interface{}{err}) +} + +// CreateTrigger performs a previously recorded action +func (c *MockKnEventingClient) CreateTrigger(ctx context.Context, trigger *eventingv1.Trigger) error { + call := c.recorder.r.VerifyCall("CreateTrigger", trigger) + return mock.ErrorOrNil(call.Result[0]) +} + +// GetTrigger records a call for GetTrigger with the expected object or error. Either trigger or err should be nil +func (sr *EventingRecorder) GetTrigger(name interface{}, trigger *eventingv1.Trigger, err error) { + sr.r.Add("GetTrigger", []interface{}{name}, []interface{}{trigger, err}) +} + +// GetTrigger performs a previously recorded action +func (c *MockKnEventingClient) GetTrigger(ctx context.Context, name string) (*eventingv1.Trigger, error) { + call := c.recorder.r.VerifyCall("GetTrigger", name) + return call.Result[0].(*eventingv1.Trigger), mock.ErrorOrNil(call.Result[1]) +} + +// DeleteTrigger records a call for DeleteTrigger with the expected error (nil if none) +func (sr *EventingRecorder) DeleteTrigger(name interface{}, err error) { + sr.r.Add("DeleteTrigger", []interface{}{name}, []interface{}{err}) +} + +// DeleteTrigger performs a previously recorded action, failing if non has been registered +func (c *MockKnEventingClient) DeleteTrigger(ctx context.Context, name string) error { + call := c.recorder.r.VerifyCall("DeleteTrigger", name) + return mock.ErrorOrNil(call.Result[0]) +} + +// ListTriggers records a call for ListTriggers with the expected result and error (nil if none) +func (sr *EventingRecorder) ListTriggers(triggerList *eventingv1.TriggerList, err error) { + sr.r.Add("ListTriggers", nil, []interface{}{triggerList, err}) +} + +// ListTriggers performs a previously recorded action +func (c *MockKnEventingClient) ListTriggers(context.Context) (*eventingv1.TriggerList, error) { + call := c.recorder.r.VerifyCall("ListTriggers") + return call.Result[0].(*eventingv1.TriggerList), mock.ErrorOrNil(call.Result[1]) +} + +// UpdateTrigger records a call for UpdateTrigger with the expected result and error (nil if none) +func (sr *EventingRecorder) UpdateTrigger(trigger interface{}, err error) { + sr.r.Add("UpdateTrigger", []interface{}{trigger}, []interface{}{err}) +} + +// UpdateTrigger performs a previously recorded action +func (c *MockKnEventingClient) UpdateTrigger(ctx context.Context, trigger *eventingv1.Trigger) error { + call := c.recorder.r.VerifyCall("UpdateTrigger") + return mock.ErrorOrNil(call.Result[0]) +} + +func (c *MockKnEventingClient) UpdateTriggerWithRetry(ctx context.Context, name string, updateFunc TriggerUpdateFunc, nrRetries int) error { + return updateTriggerWithRetry(ctx, c, name, updateFunc, nrRetries) +} + +// CreateBroker records a call for CreateBroker with the expected error +func (sr *EventingRecorder) CreateBroker(broker interface{}, err error) { + sr.r.Add("CreateBroker", []interface{}{broker}, []interface{}{err}) +} + +// CreateBroker performs a previously recorded action +func (c *MockKnEventingClient) CreateBroker(ctx context.Context, broker *eventingv1.Broker) error { + call := c.recorder.r.VerifyCall("CreateBroker", broker) + return mock.ErrorOrNil(call.Result[0]) +} + +// GetBroker records a call for GetBroker with the expected object or error. Either trigger or err should be nil +func (sr *EventingRecorder) GetBroker(name interface{}, broker *eventingv1.Broker, err error) { + sr.r.Add("GetBroker", []interface{}{name}, []interface{}{broker, err}) +} + +// GetBroker performs a previously recorded action +func (c *MockKnEventingClient) GetBroker(ctx context.Context, name string) (*eventingv1.Broker, error) { + call := c.recorder.r.VerifyCall("GetBroker", name) + return call.Result[0].(*eventingv1.Broker), mock.ErrorOrNil(call.Result[1]) +} + +// DeleteBroker records a call for DeleteBroker with the expected error (nil if none) +func (sr *EventingRecorder) DeleteBroker(name, timeout interface{}, err error) { + sr.r.Add("DeleteBroker", []interface{}{name, timeout}, []interface{}{err}) +} + +// DeleteBroker performs a previously recorded action, failing if non has been registered +func (c *MockKnEventingClient) DeleteBroker(ctx context.Context, name string, timeout time.Duration) error { + call := c.recorder.r.VerifyCall("DeleteBroker", name, timeout) + return mock.ErrorOrNil(call.Result[0]) +} + +// ListBrokers records a call for ListBrokers with the expected result and error (nil if none) +func (sr *EventingRecorder) ListBrokers(brokerList *eventingv1.BrokerList, err error) { + sr.r.Add("ListBrokers", nil, []interface{}{brokerList, err}) +} + +// ListBrokers performs a previously recorded action +func (c *MockKnEventingClient) ListBrokers(context.Context) (*eventingv1.BrokerList, error) { + call := c.recorder.r.VerifyCall("ListBrokers") + return call.Result[0].(*eventingv1.BrokerList), mock.ErrorOrNil(call.Result[1]) +} + +// UpdateBroker records a call for UpdateBroker with the expected result and error (nil if none) +func (sr *EventingRecorder) UpdateBroker(broker *eventingv1.Broker, err error) { + sr.r.Add("UpdateBroker", []interface{}{broker}, []interface{}{err}) +} + +func (c *MockKnEventingClient) UpdateBroker(ctx context.Context, broker *eventingv1.Broker) error { + call := c.recorder.r.VerifyCall("UpdateBroker") + return mock.ErrorOrNil(call.Result[0]) +} + +func (c *MockKnEventingClient) UpdateBrokerWithRetry(ctx context.Context, name string, updateFunc BrokerUpdateFunc, nrRetries int) error { + return updateBrokerWithRetry(ctx, c, name, updateFunc, nrRetries) +} + +// Validate validates whether every recorded action has been called +func (sr *EventingRecorder) Validate() { + sr.r.CheckThatAllRecordedMethodsHaveBeenCalled() +} diff --git a/pkg/eventing/v1/client_mock_test.go b/pkg/eventing/v1/client_mock_test.go new file mode 100644 index 00000000..7a84792d --- /dev/null +++ b/pkg/eventing/v1/client_mock_test.go @@ -0,0 +1,70 @@ +// Copyright © 2019 The Knative Authors +// +// 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 v1 + +import ( + "context" + "testing" + "time" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" +) + +func TestMockKnClient(t *testing.T) { + + client := NewMockKnEventingClient(t) + + recorder := client.Recorder() + + // Record all services + recorder.GetTrigger("hello", nil, nil) + recorder.CreateTrigger(&eventingv1.Trigger{}, nil) + recorder.DeleteTrigger("hello", nil) + recorder.ListTriggers(nil, nil) + recorder.UpdateTrigger(&eventingv1.Trigger{}, nil) + recorder.GetTrigger("hello", &eventingv1.Trigger{}, nil) + recorder.UpdateTrigger(&eventingv1.Trigger{}, nil) + + recorder.CreateBroker(&eventingv1.Broker{}, nil) + recorder.GetBroker("foo", nil, nil) + recorder.DeleteBroker("foo", time.Duration(10)*time.Second, nil) + recorder.ListBrokers(nil, nil) + recorder.GetBroker("foo", &eventingv1.Broker{}, nil) + recorder.UpdateBroker(&eventingv1.Broker{}, nil) + recorder.UpdateBroker(&eventingv1.Broker{}, nil) + + // Call all service + ctx := context.Background() + client.GetTrigger(ctx, "hello") + client.CreateTrigger(ctx, &eventingv1.Trigger{}) + client.DeleteTrigger(ctx, "hello") + client.ListTriggers(ctx) + client.UpdateTrigger(ctx, &eventingv1.Trigger{}) + client.UpdateTriggerWithRetry(ctx, "hello", func(origTrigger *eventingv1.Trigger) (*eventingv1.Trigger, error) { + return origTrigger, nil + }, 10) + + client.CreateBroker(ctx, &eventingv1.Broker{}) + client.GetBroker(ctx, "foo") + client.DeleteBroker(ctx, "foo", time.Duration(10)*time.Second) + client.ListBrokers(ctx) + client.UpdateBroker(ctx, &eventingv1.Broker{}) + client.UpdateBrokerWithRetry(ctx, "foo", func(origBroker *eventingv1.Broker) (*eventingv1.Broker, error) { + return origBroker, nil + }, 10) + + // Validate + recorder.Validate() +} diff --git a/pkg/eventing/v1beta1/client.go b/pkg/eventing/v1beta2/client.go similarity index 74% rename from pkg/eventing/v1beta1/client.go rename to pkg/eventing/v1beta2/client.go index 8572bee1..b1b713c2 100644 --- a/pkg/eventing/v1beta1/client.go +++ b/pkg/eventing/v1beta2/client.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v1beta1 +package v1beta2 import ( "context" @@ -21,35 +21,37 @@ import ( "k8s.io/apimachinery/pkg/runtime" kn_errors "knative.dev/client-pkg/pkg/errors" "knative.dev/client-pkg/pkg/util" - eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + eventingv1beta2 "knative.dev/eventing/pkg/apis/eventing/v1beta2" "knative.dev/eventing/pkg/client/clientset/versioned/scheme" - beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1" + beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2" "knative.dev/pkg/apis" + v1 "knative.dev/pkg/apis/duck/v1" ) -// KnEventingV1Beta1Client to Eventing Sources. All methods are relative to the +// KnEventingV1Beta2Client to Eventing Sources. All methods are relative to the // namespace specified during construction -type KnEventingV1Beta1Client interface { +type KnEventingV1Beta2Client interface { // Namespace in which this client is operating for Namespace() string // ListEventtypes is used to list eventtypes - ListEventtypes(ctx context.Context) (*eventingv1beta1.EventTypeList, error) + ListEventtypes(ctx context.Context) (*eventingv1beta2.EventTypeList, error) // GetEventtype is used to describe an eventtype - GetEventtype(ctx context.Context, name string) (*eventingv1beta1.EventType, error) + GetEventtype(ctx context.Context, name string) (*eventingv1beta2.EventType, error) // CreateEventtype is used to create an eventtype - CreateEventtype(ctx context.Context, eventtype *eventingv1beta1.EventType) error + CreateEventtype(ctx context.Context, eventtype *eventingv1beta2.EventType) error // DeleteEventtype is used to delete an eventtype DeleteEventtype(ctx context.Context, name string) error } -// KnEventingV1Beta1Client is a client for eventing v1beta1 resources +// KnEventingV1Beta2Client is a client for eventing v1beta2 resources type knEventingV1Beta1Client struct { - client beta1.EventingV1beta1Interface + client beta1.EventingV1beta2Interface namespace string } -// NewKnEventingV1Beta1Client is to invoke Eventing Types Client API to create object -func NewKnEventingV1Beta1Client(client beta1.EventingV1beta1Interface, namespace string) KnEventingV1Beta1Client { +// NewKnEventingV1Beta2Client is to invoke Eventing Types Client API to create object +func NewKnEventingV1Beta2Client(client beta1.EventingV1beta2Interface, namespace string) KnEventingV1Beta2Client { return &knEventingV1Beta1Client{ client: client, namespace: namespace, @@ -57,14 +59,14 @@ func NewKnEventingV1Beta1Client(client beta1.EventingV1beta1Interface, namespace } func updateEventingBeta1GVK(obj runtime.Object) error { - return util.UpdateGroupVersionKindWithScheme(obj, eventingv1beta1.SchemeGroupVersion, scheme.Scheme) + return util.UpdateGroupVersionKindWithScheme(obj, eventingv1beta2.SchemeGroupVersion, scheme.Scheme) } func (c *knEventingV1Beta1Client) Namespace() string { return c.namespace } -func (c *knEventingV1Beta1Client) ListEventtypes(ctx context.Context) (*eventingv1beta1.EventTypeList, error) { +func (c *knEventingV1Beta1Client) ListEventtypes(ctx context.Context) (*eventingv1beta2.EventTypeList, error) { eventTypeList, err := c.client.EventTypes(c.namespace).List(ctx, apis_v1.ListOptions{}) if err != nil { return nil, kn_errors.GetError(err) @@ -75,7 +77,7 @@ func (c *knEventingV1Beta1Client) ListEventtypes(ctx context.Context) (*eventing return nil, err } - listNew.Items = make([]eventingv1beta1.EventType, len(eventTypeList.Items)) + listNew.Items = make([]eventingv1beta2.EventType, len(eventTypeList.Items)) for idx, eventType := range eventTypeList.Items { clone := eventType.DeepCopy() err := updateEventingBeta1GVK(clone) @@ -87,7 +89,7 @@ func (c *knEventingV1Beta1Client) ListEventtypes(ctx context.Context) (*eventing return listNew, nil } -func (c *knEventingV1Beta1Client) GetEventtype(ctx context.Context, name string) (*eventingv1beta1.EventType, error) { +func (c *knEventingV1Beta1Client) GetEventtype(ctx context.Context, name string) (*eventingv1beta2.EventType, error) { eventType, err := c.client.EventTypes(c.namespace).Get(ctx, name, apis_v1.GetOptions{}) if err != nil { return nil, kn_errors.GetError(err) @@ -107,7 +109,7 @@ func (c *knEventingV1Beta1Client) DeleteEventtype(ctx context.Context, name stri return nil } -func (c *knEventingV1Beta1Client) CreateEventtype(ctx context.Context, eventtype *eventingv1beta1.EventType) error { +func (c *knEventingV1Beta1Client) CreateEventtype(ctx context.Context, eventtype *eventingv1beta2.EventType) error { _, err := c.client.EventTypes(c.namespace).Create(ctx, eventtype, apis_v1.CreateOptions{}) if err != nil { return kn_errors.GetError(err) @@ -117,12 +119,12 @@ func (c *knEventingV1Beta1Client) CreateEventtype(ctx context.Context, eventtype // EventtypeBuilder is for building the eventtype type EventtypeBuilder struct { - eventtype *eventingv1beta1.EventType + eventtype *eventingv1beta2.EventType } // NewEventtypeBuilder for building eventtype object func NewEventtypeBuilder(name string) *EventtypeBuilder { - return &EventtypeBuilder{eventtype: &eventingv1beta1.EventType{ + return &EventtypeBuilder{eventtype: &eventingv1beta2.EventType{ ObjectMeta: apis_v1.ObjectMeta{ Name: name, }, @@ -155,11 +157,21 @@ func (e *EventtypeBuilder) Source(source *apis.URL) *EventtypeBuilder { // Broker for eventtype builder func (e *EventtypeBuilder) Broker(broker string) *EventtypeBuilder { - e.eventtype.Spec.Broker = broker + e.eventtype.Spec.Reference = &v1.KReference{ + APIVersion: eventingv1.SchemeGroupVersion.String(), + Kind: "Broker", + Name: broker, + } + return e +} + +// Reference for eventtype builder +func (e *EventtypeBuilder) Reference(ref *v1.KReference) *EventtypeBuilder { + e.eventtype.Spec.Reference = ref return e } // Build to return an instance of eventtype object -func (e *EventtypeBuilder) Build() *eventingv1beta1.EventType { +func (e *EventtypeBuilder) Build() *eventingv1beta2.EventType { return e.eventtype } diff --git a/pkg/eventing/v1beta2/client_mock.go b/pkg/eventing/v1beta2/client_mock.go new file mode 100644 index 00000000..b026f1ef --- /dev/null +++ b/pkg/eventing/v1beta2/client_mock.go @@ -0,0 +1,106 @@ +// Copyright © 2022 The Knative Authors +// +// 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 v1beta2 + +import ( + "context" + "testing" + + eventingv1beta2 "knative.dev/eventing/pkg/apis/eventing/v1beta2" + + "knative.dev/client-pkg/pkg/util/mock" +) + +// MockKnEventingV1beta2Client is a combine of test object and recorder +type MockKnEventingV1beta2Client struct { + t *testing.T + recorder *EventingV1beta2Recorder +} + +// NewMockKnEventingV1beta2Client returns a new mock instance which you need to record for +func NewMockKnEventingV1beta2Client(t *testing.T, ns ...string) *MockKnEventingV1beta2Client { + namespace := "default" + if len(ns) > 0 { + namespace = ns[0] + } + return &MockKnEventingV1beta2Client{ + t: t, + recorder: &EventingV1beta2Recorder{mock.NewRecorder(t, namespace)}, + } +} + +// Ensure that the interface is implemented +var _ KnEventingV1Beta2Client = &MockKnEventingV1beta2Client{} + +// EventingV1beta2Recorder is recorder for eventingv1beta2 objects +type EventingV1beta2Recorder struct { + r *mock.Recorder +} + +// Recorder returns the recorder for registering API calls +func (c *MockKnEventingV1beta2Client) Recorder() *EventingV1beta2Recorder { + return c.recorder +} + +// Namespace of this client +func (c *MockKnEventingV1beta2Client) Namespace() string { + return c.recorder.r.Namespace() +} + +// ListEventtypes records a call for ListEventtypes with the expected result and error (nil if none) +func (sr *EventingV1beta2Recorder) ListEventtypes(eventtypeList *eventingv1beta2.EventTypeList, err error) { + sr.r.Add("ListEventtypes", nil, []interface{}{eventtypeList, err}) +} + +func (c *MockKnEventingV1beta2Client) ListEventtypes(ctx context.Context) (*eventingv1beta2.EventTypeList, error) { + call := c.recorder.r.VerifyCall("ListEventtypes") + return call.Result[0].(*eventingv1beta2.EventTypeList), mock.ErrorOrNil(call.Result[1]) +} + +// GetEventtype records a call for GetEventtype with the expected result and error (nil if none) +func (sr *EventingV1beta2Recorder) GetEventtype(name string, eventtype *eventingv1beta2.EventType, err error) { + sr.r.Add("GetEventtype", []interface{}{name}, []interface{}{eventtype, err}) +} + +// GetEventtypes records a call for GetEventtype with the expected object or error. Either eventtype or err should be nil +func (c *MockKnEventingV1beta2Client) GetEventtype(ctx context.Context, name string) (*eventingv1beta2.EventType, error) { + call := c.recorder.r.VerifyCall("GetEventtype", name) + return call.Result[0].(*eventingv1beta2.EventType), mock.ErrorOrNil(call.Result[1]) +} + +// CreateEventtype records a call for CreateEventtype with the expected error +func (sr *EventingV1beta2Recorder) CreateEventtype(eventtype interface{}, err error) { + sr.r.Add("CreateEventtype", []interface{}{eventtype}, []interface{}{err}) +} + +func (c *MockKnEventingV1beta2Client) CreateEventtype(ctx context.Context, eventtype *eventingv1beta2.EventType) error { + call := c.recorder.r.VerifyCall("CreateEventtype", eventtype) + return mock.ErrorOrNil(call.Result[0]) +} + +// DeleteEventtype records a call for DeleteEventtype with the expected error +func (sr *EventingV1beta2Recorder) DeleteEventtype(name interface{}, err error) { + sr.r.Add("DeleteEventtype", []interface{}{name}, []interface{}{err}) +} + +func (c *MockKnEventingV1beta2Client) DeleteEventtype(ctx context.Context, name string) error { + call := c.recorder.r.VerifyCall("DeleteEventtype", name) + return mock.ErrorOrNil(call.Result[0]) +} + +// Validate validates whether every recorded action has been called +func (sr *EventingV1beta2Recorder) Validate() { + sr.r.CheckThatAllRecordedMethodsHaveBeenCalled() +} diff --git a/pkg/eventing/v1beta2/client_mock_test.go b/pkg/eventing/v1beta2/client_mock_test.go new file mode 100644 index 00000000..b4160fea --- /dev/null +++ b/pkg/eventing/v1beta2/client_mock_test.go @@ -0,0 +1,41 @@ +// Copyright © 2022 The Knative Authors +// +// 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 v1beta2 + +import ( + "context" + "testing" + + "knative.dev/eventing/pkg/apis/eventing/v1beta2" +) + +func TestMockKnClient(t *testing.T) { + client := NewMockKnEventingV1beta2Client(t, "test-ns") + + recorder := client.Recorder() + + recorder.CreateEventtype(&v1beta2.EventType{}, nil) + recorder.GetEventtype("eventtype-name", &v1beta2.EventType{}, nil) + recorder.DeleteEventtype("eventtype-name", nil) + recorder.ListEventtypes(&v1beta2.EventTypeList{}, nil) + + ctx := context.Background() + client.CreateEventtype(ctx, &v1beta2.EventType{}) + client.GetEventtype(ctx, "eventtype-name") + client.DeleteEventtype(ctx, "eventtype-name") + client.ListEventtypes(ctx) + + recorder.Validate() +} diff --git a/pkg/eventing/v1beta1/client_test.go b/pkg/eventing/v1beta2/client_test.go similarity index 80% rename from pkg/eventing/v1beta1/client_test.go rename to pkg/eventing/v1beta2/client_test.go index 85715462..ebe0c076 100644 --- a/pkg/eventing/v1beta1/client_test.go +++ b/pkg/eventing/v1beta2/client_test.go @@ -12,19 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v1beta1 +package v1beta2 import ( "context" "fmt" "testing" + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + v1 "knative.dev/pkg/apis/duck/v1" + "gotest.tools/v3/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" client_testing "k8s.io/client-go/testing" - "knative.dev/eventing/pkg/apis/eventing/v1beta1" - "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake" + "knative.dev/eventing/pkg/apis/eventing/v1beta2" + "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake" "knative.dev/pkg/apis" ) @@ -37,9 +40,9 @@ const ( errName = "error-eventtype" ) -func setup(ns string) (fakeSvr fake.FakeEventingV1beta1, client KnEventingV1Beta1Client) { - fakeE := fake.FakeEventingV1beta1{Fake: &client_testing.Fake{}} - cli := NewKnEventingV1Beta1Client(&fakeE, ns) +func setup(ns string) (fakeSvr fake.FakeEventingV1beta2, client KnEventingV1Beta2Client) { + fakeE := fake.FakeEventingV1beta2{Fake: &client_testing.Fake{}} + cli := NewKnEventingV1Beta2Client(&fakeE, ns) return fakeE, cli } @@ -51,7 +54,21 @@ func TestNamespace(t *testing.T) { func TestBuilder(t *testing.T) { et := newEventtypeWithSourceBroker(testName, testSource, testBroker) assert.Equal(t, et.Name, testName) - assert.Equal(t, et.Spec.Broker, testBroker) + assert.Equal(t, et.Spec.Reference.Name, testBroker) + source := et.Spec.Source + assert.Assert(t, source != nil) + assert.Equal(t, source.String(), testSource) +} + +func TestBuilderWithRefence(t *testing.T) { + ref := &v1.KReference{ + APIVersion: eventingv1.SchemeGroupVersion.String(), + Kind: "Broker", + Name: testBroker, + } + et := newEventtypeWithSourceRef(testName, testSource, ref) + assert.Equal(t, et.Name, testName) + assert.Equal(t, et.Spec.Reference, ref) source := et.Spec.Source assert.Assert(t, source != nil) assert.Equal(t, source.String(), testSource) @@ -147,7 +164,7 @@ func TestKnEventingV1Beta1Client_ListEventtypes(t *testing.T) { func(a client_testing.Action) (bool, runtime.Object, error) { assert.Equal(t, testNamespace, a.GetNamespace()) - return true, &v1beta1.EventTypeList{Items: []v1beta1.EventType{ + return true, &v1beta2.EventTypeList{Items: []v1beta2.EventType{ *newEventtype("eventtype-1"), *newEventtype("eventtype-2")}}, nil }) @@ -161,7 +178,7 @@ func TestKnEventingV1Beta1Client_ListEventtypes(t *testing.T) { assert.Equal(t, list.Items[1].Name, "eventtype-2") } -func newEventtypeWithSourceBroker(name string, source string, broker string) *v1beta1.EventType { +func newEventtypeWithSourceBroker(name string, source string, broker string) *v1beta2.EventType { url, _ := apis.ParseURL(source) return NewEventtypeBuilder(name). Namespace(testNamespace). @@ -172,7 +189,18 @@ func newEventtypeWithSourceBroker(name string, source string, broker string) *v1 Build() } -func newEventtype(name string) *v1beta1.EventType { +func newEventtypeWithSourceRef(name string, source string, ref *v1.KReference) *v1beta2.EventType { + url, _ := apis.ParseURL(source) + return NewEventtypeBuilder(name). + Namespace(testNamespace). + WithGvk(). + Type(testType). + Source(url). + Reference(ref). + Build() +} + +func newEventtype(name string) *v1beta2.EventType { return NewEventtypeBuilder(name). Namespace(testNamespace). Type(testType). diff --git a/pkg/flags/podspec_helper.go b/pkg/flags/podspec_helper.go index 4a2dfd12..d1d9f1b4 100644 --- a/pkg/flags/podspec_helper.go +++ b/pkg/flags/podspec_helper.go @@ -20,6 +20,8 @@ import ( "strconv" "strings" + "k8s.io/utils/pointer" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/api/resource" @@ -378,6 +380,37 @@ func UpdateImagePullPolicy(spec *corev1.PodSpec, imagePullPolicy string) error { return nil } +// UpdateSecurityContext update the Security Context +func UpdateSecurityContext(spec *corev1.PodSpec, securityContext string) error { + container := containerOfPodSpec(spec) + switch strings.ToLower(securityContext) { + case "none": + // Blank any Security Context defined + container.SecurityContext = nil + case "strict": + // Add or update Security Context to default strict + container.SecurityContext = DefaultStrictSecCon() + //TODO(dsimansk): add parsing of SC options from the flag value + default: + return fmt.Errorf("invalid --security-context %s. Valid arguments: strict | none", securityContext) + } + return nil +} + +// DefaultStrictSecCon helper function to get default strict Security Context +func DefaultStrictSecCon() *corev1.SecurityContext { + return &corev1.SecurityContext{ + AllowPrivilegeEscalation: pointer.Bool(false), + RunAsNonRoot: pointer.Bool(true), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + } +} + func getPolicy(policy string) v1.PullPolicy { var ret v1.PullPolicy switch strings.ToLower(policy) { diff --git a/pkg/flags/podspec_helper_test.go b/pkg/flags/podspec_helper_test.go index 04eafd8f..e8e615ac 100644 --- a/pkg/flags/podspec_helper_test.go +++ b/pkg/flags/podspec_helper_test.go @@ -24,13 +24,12 @@ import ( "strings" "testing" - "k8s.io/apimachinery/pkg/util/intstr" - "knative.dev/client-pkg/pkg/util/test" - "gotest.tools/v3/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/intstr" "knative.dev/client-pkg/pkg/util" + "knative.dev/client-pkg/pkg/util/test" "knative.dev/pkg/ptr" ) diff --git a/pkg/kn-source-pkg/pkg/factories/kn_source_factory.go b/pkg/kn-source-pkg/pkg/factories/kn_source_factory.go index a8e11e56..7762fa59 100644 --- a/pkg/kn-source-pkg/pkg/factories/kn_source_factory.go +++ b/pkg/kn-source-pkg/pkg/factories/kn_source_factory.go @@ -16,7 +16,7 @@ package factories import ( "k8s.io/client-go/rest" - "knative.dev/client-pkg/pkg/commands/flags" + "knative.dev/client-pkg/pkg/commands/flags/sink" "knative.dev/client-pkg/pkg/kn-source-pkg/pkg/client" "knative.dev/client-pkg/pkg/kn-source-pkg/pkg/types" ) @@ -37,7 +37,7 @@ func NewDefaultKnSourceFactory() types.KnSourceFactory { func (f *DefautKnSourceFactory) CreateKnSourceParams() *types.KnSourceParams { f.knSourceParams = &types.KnSourceParams{ - SinkFlag: flags.SinkFlags{}, + SinkFlag: sink.Flag{}, } f.knSourceParams.Initialize() return f.knSourceParams diff --git a/pkg/kn-source-pkg/pkg/factories/kn_source_factory_test.go b/pkg/kn-source-pkg/pkg/factories/kn_source_factory_test.go index aa3053a9..821d6341 100644 --- a/pkg/kn-source-pkg/pkg/factories/kn_source_factory_test.go +++ b/pkg/kn-source-pkg/pkg/factories/kn_source_factory_test.go @@ -19,7 +19,7 @@ import ( "gotest.tools/v3/assert" "k8s.io/client-go/rest" - "knative.dev/client-pkg/pkg/commands/flags" + "knative.dev/client-pkg/pkg/commands/flags/sink" "knative.dev/client-pkg/pkg/kn-source-pkg/pkg/types" "knative.dev/client-pkg/pkg/kn-source-pkg/pkg/types/typesfakes" ) @@ -49,7 +49,7 @@ func TestCreateKnSourceClient(t *testing.T) { func newDefaultKnSourceFactory() types.KnSourceFactory { return &DefautKnSourceFactory{ - knSourceParams: &types.KnSourceParams{SinkFlag: flags.SinkFlags{}}, + knSourceParams: &types.KnSourceParams{SinkFlag: sink.Flag{}}, knSourceClientFunc: fakeKnSourceClientFunc, } } diff --git a/pkg/kn-source-pkg/pkg/types/structs.go b/pkg/kn-source-pkg/pkg/types/structs.go index 82ad787f..bf965ea6 100644 --- a/pkg/kn-source-pkg/pkg/types/structs.go +++ b/pkg/kn-source-pkg/pkg/types/structs.go @@ -19,13 +19,13 @@ package types import ( "github.com/spf13/cobra" "knative.dev/client-pkg/pkg/commands" - "knative.dev/client-pkg/pkg/commands/flags" + "knative.dev/client-pkg/pkg/commands/flags/sink" ) type KnSourceParams struct { commands.KnParams - SinkFlag flags.SinkFlags + SinkFlag sink.Flag } func (p *KnSourceParams) AddCommonFlags(cmd *cobra.Command) { diff --git a/pkg/kn-source-pkg/pkg/types/structs_test.go b/pkg/kn-source-pkg/pkg/types/structs_test.go index 78bf1e2b..9b8bcd5f 100644 --- a/pkg/kn-source-pkg/pkg/types/structs_test.go +++ b/pkg/kn-source-pkg/pkg/types/structs_test.go @@ -19,7 +19,7 @@ import ( "github.com/spf13/cobra" "gotest.tools/v3/assert" - "knative.dev/client-pkg/pkg/commands/flags" + "knative.dev/client-pkg/pkg/commands/flags/sink" ) func TestAddCommonFlags(t *testing.T) { @@ -52,6 +52,6 @@ func TestAddCreateUpdateFlags(t *testing.T) { func newFakeKnSourceParams() *KnSourceParams { return &KnSourceParams{ - SinkFlag: flags.SinkFlags{}, + SinkFlag: sink.Flag{}, } } diff --git a/pkg/printers/tablegenerator.go b/pkg/printers/tablegenerator.go index 27fd94be..95fd6d8a 100644 --- a/pkg/printers/tablegenerator.go +++ b/pkg/printers/tablegenerator.go @@ -74,9 +74,8 @@ func (h *HumanReadablePrinter) GenerateTable(obj runtime.Object, options PrintOp } columns := make([]metav1beta1.TableColumnDefinition, 0, len(handler.columnDefinitions)) - for i := range handler.columnDefinitions { - columns = append(columns, handler.columnDefinitions[i]) - } + + columns = append(columns, handler.columnDefinitions...) table := &metav1beta1.Table{ ListMeta: metav1.ListMeta{ diff --git a/pkg/serving/service.go b/pkg/serving/service.go new file mode 100644 index 00000000..6e37a158 --- /dev/null +++ b/pkg/serving/service.go @@ -0,0 +1,74 @@ +// Copyright © 2019 The Knative Authors +// +// 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 serving + +import ( + "bytes" + "math/rand" + "strings" + "text/template" + "time" + + servingv1 "knative.dev/serving/pkg/apis/serving/v1" +) + +var revisionNameRand = rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec // Weak crypto is fine here, we use it for generating unique keys. + +var charChoices = []string{ + "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", + "y", "z", +} + +type revisionTemplContext struct { + Service string + Generation int64 +} + +func (c *revisionTemplContext) Random(l int) string { + chars := make([]string, 0, l) + for i := 0; i < l; i++ { + //nolint:gosec // Weak crypto is fine here, we use it for generating unique keys. + chars = append(chars, charChoices[revisionNameRand.Int()%len(charChoices)]) + } + return strings.Join(chars, "") +} + +// GenerateRevisionName returns an automatically-generated name suitable for the +// next revision of the given service. +func GenerateRevisionName(nameTempl string, service *servingv1.Service) (string, error) { + templ, err := template.New("revisionName").Parse(nameTempl) + if err != nil { + return "", err + } + context := &revisionTemplContext{ + Service: service.Name, + Generation: service.Generation + 1, + } + buf := new(bytes.Buffer) + err = templ.Execute(buf, context) + if err != nil { + return "", err + } + res := buf.String() + // Empty is ok. + if res == "" { + return res, nil + } + prefix := service.Name + "-" + if !strings.HasPrefix(res, prefix) { + res = prefix + res + } + return res, nil +} diff --git a/pkg/serving/service_test.go b/pkg/serving/service_test.go new file mode 100644 index 00000000..6c942f01 --- /dev/null +++ b/pkg/serving/service_test.go @@ -0,0 +1,55 @@ +// Copyright © 2018 The Knative Authors +// +// 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 serving + +import ( + "testing" + + "gotest.tools/v3/assert" + + servingv1 "knative.dev/serving/pkg/apis/serving/v1" +) + +type generateNameTest struct { + templ string + result string + err string +} + +func TestGenerateName(t *testing.T) { + revisionNameRand.Seed(1) + someRandomChars := (&revisionTemplContext{}).Random(20) + service := &servingv1.Service{} + service.Name = "foo" + service.Generation = 3 + cases := []generateNameTest{ + {"{{.Service}}-v-{{.Generation}}", "foo-v-4", ""}, + {"foo-asdf", "foo-asdf", ""}, + {"{{.Bad}}", "", "can't evaluate field Bad"}, + {"{{.Service}}-{{.Random 5}}", "foo-" + someRandomChars[0:5], ""}, + {"", "", ""}, + {"andrew", "foo-andrew", ""}, + {"{{.Random 5}}", "foo-" + someRandomChars[0:5], ""}, + } + for _, c := range cases { + revisionNameRand.Seed(1) + name, err := GenerateRevisionName(c.templ, service) + if c.err != "" { + assert.ErrorContains(t, err, c.err) + } else { + assert.Equal(t, name, c.result) + } + } +} diff --git a/pkg/serving/v1/client_mock.go b/pkg/serving/v1/client_mock.go new file mode 100644 index 00000000..f446b8d7 --- /dev/null +++ b/pkg/serving/v1/client_mock.go @@ -0,0 +1,282 @@ +// Copyright © 2019 The Knative Authors +// +// 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 v1 + +import ( + "context" + "testing" + "time" + + "gotest.tools/v3/assert" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" + + "knative.dev/client-pkg/pkg/util/mock" + "knative.dev/client-pkg/pkg/wait" +) + +type MockKnServingClient struct { + t *testing.T + recorder *ServingRecorder +} + +// NewMockKnServiceClient returns a new mock instance which you need to record for +func NewMockKnServiceClient(t *testing.T, ns ...string) *MockKnServingClient { + namespace := "default" + if len(ns) > 0 { + namespace = ns[0] + } + return &MockKnServingClient{ + t: t, + recorder: &ServingRecorder{mock.NewRecorder(t, namespace)}, + } +} + +// recorder for service +type ServingRecorder struct { + r *mock.Recorder +} + +// Get the record to start for the recorder +func (c *MockKnServingClient) Recorder() *ServingRecorder { + return c.recorder +} + +// Namespace of this client +func (c *MockKnServingClient) Namespace() string { + return c.recorder.r.Namespace() +} + +// Get Service +func (sr *ServingRecorder) GetService(name interface{}, service *servingv1.Service, err error) { + sr.r.Add("GetService", []interface{}{name}, []interface{}{service, err}) +} + +func (c *MockKnServingClient) GetService(ctx context.Context, name string) (*servingv1.Service, error) { + call := c.recorder.r.VerifyCall("GetService", name) + return call.Result[0].(*servingv1.Service), mock.ErrorOrNil(call.Result[1]) +} + +// List services +func (sr *ServingRecorder) ListServices(opts interface{}, serviceList *servingv1.ServiceList, err error) { + sr.r.Add("ListServices", []interface{}{opts}, []interface{}{serviceList, err}) +} + +func (c *MockKnServingClient) ListServices(ctx context.Context, opts ...ListConfig) (*servingv1.ServiceList, error) { + call := c.recorder.r.VerifyCall("ListServices", opts) + return call.Result[0].(*servingv1.ServiceList), mock.ErrorOrNil(call.Result[1]) +} + +// Create a new service +func (sr *ServingRecorder) CreateService(service interface{}, err error) { + sr.r.Add("CreateService", []interface{}{service}, []interface{}{err}) +} + +func (c *MockKnServingClient) CreateService(ctx context.Context, service *servingv1.Service) error { + call := c.recorder.r.VerifyCall("CreateService", service) + return mock.ErrorOrNil(call.Result[0]) +} + +// Update the given service +func (sr *ServingRecorder) UpdateService(service interface{}, hasChanged bool, err error) { + sr.r.Add("UpdateService", []interface{}{service}, []interface{}{hasChanged, err}) +} + +func (c *MockKnServingClient) UpdateService(ctx context.Context, service *servingv1.Service) (bool, error) { + call := c.recorder.r.VerifyCall("UpdateService", service) + return call.Result[0].(bool), mock.ErrorOrNil(call.Result[1]) +} + +// Delegate to shared retry method +func (c *MockKnServingClient) UpdateServiceWithRetry(ctx context.Context, name string, updateFunc ServiceUpdateFunc, maxRetry int) (bool, error) { + return updateServiceWithRetry(ctx, c, name, updateFunc, maxRetry) +} + +// Update the given service +func (sr *ServingRecorder) ApplyService(service interface{}, hasChanged bool, err error) { + sr.r.Add("ApplyService", []interface{}{service}, []interface{}{hasChanged, err}) +} + +func (c *MockKnServingClient) ApplyService(ctx context.Context, service *servingv1.Service) (bool, error) { + call := c.recorder.r.VerifyCall("ApplyService", service) + return call.Result[0].(bool), mock.ErrorOrNil(call.Result[1]) +} + +// Delete a service by name +func (sr *ServingRecorder) DeleteService(name, timeout interface{}, err error) { + sr.r.Add("DeleteService", []interface{}{name, timeout}, []interface{}{err}) +} + +func (c *MockKnServingClient) DeleteService(ctx context.Context, name string, timeout time.Duration) error { + call := c.recorder.r.VerifyCall("DeleteService", name, timeout) + return mock.ErrorOrNil(call.Result[0]) +} + +// Wait for a service to become ready, but not longer than provided timeout +func (sr *ServingRecorder) WaitForService(name interface{}, wconfig interface{}, callback interface{}, err error, duration time.Duration) { + sr.r.Add("WaitForService", []interface{}{name, wconfig, callback}, []interface{}{err, duration}) +} + +func (c *MockKnServingClient) WaitForService(ctx context.Context, name string, wconfig WaitConfig, msgCallback wait.MessageCallback) (error, time.Duration) { + call := c.recorder.r.VerifyCall("WaitForService", name, wconfig, msgCallback) + return mock.ErrorOrNil(call.Result[0]), call.Result[1].(time.Duration) +} + +// Get a revision by name +func (sr *ServingRecorder) GetRevision(name interface{}, revision *servingv1.Revision, err error) { + sr.r.Add("GetRevision", []interface{}{name}, []interface{}{revision, err}) +} + +func (c *MockKnServingClient) GetRevision(ctx context.Context, name string) (*servingv1.Revision, error) { + call := c.recorder.r.VerifyCall("GetRevision", name) + return call.Result[0].(*servingv1.Revision), mock.ErrorOrNil(call.Result[1]) +} + +// List revisions +func (sr *ServingRecorder) ListRevisions(opts interface{}, revisionList *servingv1.RevisionList, err error) { + sr.r.Add("ListRevisions", []interface{}{opts}, []interface{}{revisionList, err}) +} + +func (c *MockKnServingClient) ListRevisions(ctx context.Context, opts ...ListConfig) (*servingv1.RevisionList, error) { + call := c.recorder.r.VerifyCall("ListRevisions", opts) + return call.Result[0].(*servingv1.RevisionList), mock.ErrorOrNil(call.Result[1]) +} + +// Delete a revision +func (sr *ServingRecorder) DeleteRevision(name, timeout interface{}, err error) { + sr.r.Add("DeleteRevision", []interface{}{name, timeout}, []interface{}{err}) +} + +func (c *MockKnServingClient) DeleteRevision(ctx context.Context, name string, timeout time.Duration) error { + call := c.recorder.r.VerifyCall("DeleteRevision", name, timeout) + return mock.ErrorOrNil(call.Result[0]) +} + +// Wait for a revision to become ready, but not longer than provided timeout +func (sr *ServingRecorder) WaitForRevision(name interface{}, timeout interface{}, callback interface{}, err error, duration time.Duration) { + sr.r.Add("WaitForRevision", []interface{}{name, timeout, callback}, []interface{}{err, duration}) +} + +func (c *MockKnServingClient) WaitForRevision(ctx context.Context, name string, timeout time.Duration, msgCallback wait.MessageCallback) (error, time.Duration) { + call := c.recorder.r.VerifyCall("WaitForRevision", name, timeout, msgCallback) + return mock.ErrorOrNil(call.Result[0]), call.Result[1].(time.Duration) +} + +// Get a route by its unique name +func (sr *ServingRecorder) GetRoute(name interface{}, route *servingv1.Route, err error) { + sr.r.Add("GetRoute", []interface{}{name}, []interface{}{route, err}) +} + +func (c *MockKnServingClient) GetRoute(ctx context.Context, name string) (*servingv1.Route, error) { + call := c.recorder.r.VerifyCall("GetRoute", name) + return call.Result[0].(*servingv1.Route), mock.ErrorOrNil(call.Result[1]) + +} + +// List routes +func (sr *ServingRecorder) ListRoutes(opts interface{}, routeList *servingv1.RouteList, err error) { + sr.r.Add("ListRoutes", []interface{}{opts}, []interface{}{routeList, err}) +} + +func (c *MockKnServingClient) ListRoutes(ctx context.Context, opts ...ListConfig) (*servingv1.RouteList, error) { + call := c.recorder.r.VerifyCall("ListRoutes", opts) + return call.Result[0].(*servingv1.RouteList), mock.ErrorOrNil(call.Result[1]) +} + +// GetConfiguration records a call to GetConfiguration with possible return values +func (sr *ServingRecorder) GetConfiguration(name string, config *servingv1.Configuration, err error) { + sr.r.Add("GetConfiguration", []interface{}{name}, []interface{}{config, err}) + +} + +// GetBaseRevision returns the base revision +func (c *MockKnServingClient) GetBaseRevision(ctx context.Context, service *servingv1.Service) (*servingv1.Revision, error) { + return getBaseRevision(ctx, c, service) +} + +// GetConfiguration returns a configuration looked up by name +func (c *MockKnServingClient) GetConfiguration(ctx context.Context, name string) (*servingv1.Configuration, error) { + call := c.recorder.r.VerifyCall("GetConfiguration", name) + return call.Result[0].(*servingv1.Configuration), mock.ErrorOrNil(call.Result[1]) +} + +// CreateRevision records a call CreateRevision +func (sr *ServingRecorder) CreateRevision(revision interface{}, err error) { + sr.r.Add("CreateRevision", []interface{}{revision}, []interface{}{err}) +} + +// CreateRevision creates a new revision +func (c *MockKnServingClient) CreateRevision(ctx context.Context, revision *servingv1.Revision) error { + call := c.recorder.r.VerifyCall("CreateRevision", revision) + return mock.ErrorOrNil(call.Result[0]) +} + +// UpdateRevision records a call UpdateRevision +func (sr *ServingRecorder) UpdateRevision(revision interface{}, err error) { + sr.r.Add("UpdateRevision", []interface{}{revision}, []interface{}{err}) +} + +// UpdateRevision updates given revision +func (c *MockKnServingClient) UpdateRevision(ctx context.Context, revision *servingv1.Revision) error { + call := c.recorder.r.VerifyCall("UpdateRevision", revision) + return mock.ErrorOrNil(call.Result[0]) +} + +// Check that every recorded method has been called +func (sr *ServingRecorder) Validate() { + sr.r.CheckThatAllRecordedMethodsHaveBeenCalled() +} + +// HasLabelSelector returns a comparable which can be used for asserting that list methods are called +// with the appropriate label selector +func HasLabelSelector(keyAndValues ...string) func(t *testing.T, a interface{}) { + return func(t *testing.T, a interface{}) { + lc := a.([]ListConfig) + listConfigCollector := listConfigCollector{ + Labels: make(labels.Set), + Fields: make(fields.Set), + } + lc[0](&listConfigCollector) + for i := 0; i < len(keyAndValues); i += 2 { + assert.Equal(t, listConfigCollector.Labels[keyAndValues[i]], keyAndValues[i+1]) + } + } +} + +// HasFieldSelector returns a comparable which can be used for asserting that list methods are called +// with the appropriate field selectors +func HasFieldSelector(keyAndValues ...string) func(t *testing.T, a interface{}) { + return func(t *testing.T, a interface{}) { + lc := a.([]ListConfig) + listConfigCollector := listConfigCollector{ + Labels: make(labels.Set), + Fields: make(fields.Set), + } + lc[0](&listConfigCollector) + for i := 0; i < len(keyAndValues); i += 2 { + assert.Equal(t, listConfigCollector.Fields[keyAndValues[i]], keyAndValues[i+1]) + } + } +} + +// HasSelector returns a comparable which can be used for asserting that list methods are called +// with the appropriate label and field selectors +func HasSelector(labelKeysAndValues []string, fieldKeysAndValue []string) func(t *testing.T, a interface{}) { + return func(t *testing.T, a interface{}) { + HasLabelSelector(labelKeysAndValues...)(t, a) + HasFieldSelector(fieldKeysAndValue...)(t, a) + } +} diff --git a/pkg/serving/v1/client_mock_test.go b/pkg/serving/v1/client_mock_test.go new file mode 100644 index 00000000..34c04000 --- /dev/null +++ b/pkg/serving/v1/client_mock_test.go @@ -0,0 +1,111 @@ +// Copyright © 2019 The Knative Authors +// +// 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 v1 + +import ( + "context" + "testing" + "time" + + "knative.dev/serving/pkg/apis/serving" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" + + "knative.dev/client-pkg/pkg/util/mock" + "knative.dev/client-pkg/pkg/wait" +) + +func TestMockKnClient(t *testing.T) { + + client := NewMockKnServiceClient(t) + + recorder := client.Recorder() + + // Record all services + recorder.GetService("hello", nil, nil) + recorder.ListServices(mock.Any(), nil, nil) + recorder.ListServices(mock.Any(), nil, nil) + recorder.CreateService(&servingv1.Service{}, nil) + recorder.UpdateService(&servingv1.Service{}, false, nil) + recorder.ApplyService(&servingv1.Service{}, true, nil) + recorder.DeleteService("hello", time.Duration(10)*time.Second, nil) + recorder.WaitForService("hello", WaitConfig{ + Timeout: time.Duration(10) * time.Second, + ErrorWindow: time.Duration(2) * time.Second, + }, wait.NoopMessageCallback(), nil, 10*time.Second) + recorder.GetRevision("hello", nil, nil) + recorder.ListRevisions(mock.Any(), nil, nil) + recorder.CreateRevision(&servingv1.Revision{}, nil) + recorder.UpdateRevision(&servingv1.Revision{}, nil) + recorder.DeleteRevision("hello", time.Duration(10)*time.Second, nil) + recorder.WaitForRevision("hello", time.Duration(10)*time.Second, wait.NoopMessageCallback(), nil, 10*time.Second) + recorder.GetRoute("hello", nil, nil) + recorder.ListRoutes(mock.Any(), nil, nil) + recorder.GetConfiguration("hello", nil, nil) + + // Call all services + ctx := context.Background() + client.GetService(ctx, "hello") + client.ListServices(ctx, WithName("blub")) + client.ListServices(ctx, WithLabel("foo", "bar")) + client.CreateService(ctx, &servingv1.Service{}) + client.UpdateService(ctx, &servingv1.Service{}) + client.ApplyService(ctx, &servingv1.Service{}) + client.DeleteService(ctx, "hello", time.Duration(10)*time.Second) + client.WaitForService(ctx, "hello", WaitConfig{ + time.Duration(10) * time.Second, + time.Duration(2) * time.Second, + }, wait.NoopMessageCallback()) + client.GetRevision(ctx, "hello") + client.ListRevisions(ctx, WithName("blub")) + client.CreateRevision(ctx, &servingv1.Revision{}) + client.UpdateRevision(ctx, &servingv1.Revision{}) + client.DeleteRevision(ctx, "hello", time.Duration(10)*time.Second) + client.WaitForRevision(ctx, "hello", time.Duration(10)*time.Second, wait.NoopMessageCallback()) + client.GetRoute(ctx, "hello") + client.ListRoutes(ctx, WithName("blub")) + client.GetConfiguration(ctx, "hello") + + // Validate + recorder.Validate() +} + +func TestHasLabelSelector(t *testing.T) { + assertFunction := HasLabelSelector(serving.ServiceLabelKey, "myservice") + listConfig := []ListConfig{ + WithService("myservice"), + } + assertFunction(t, listConfig) +} + +func TestHasFieldSelector(t *testing.T) { + assertFunction := HasFieldSelector("metadata.name", "myname") + listConfig := []ListConfig{ + WithName("myname"), + } + assertFunction(t, listConfig) +} + +func TestHasSelector(t *testing.T) { + assertFunction := HasSelector( + []string{serving.ServiceLabelKey, "myservice"}, + []string{"metadata.name", "myname"}) + listConfig := []ListConfig{ + func(lo *listConfigCollector) { + lo.Labels[serving.ServiceLabelKey] = "myservice" + lo.Fields["metadata.name"] = "myname" + }, + } + assertFunction(t, listConfig) +} diff --git a/pkg/serving/v1/gitops_test.go b/pkg/serving/v1/gitops_test.go index 390f60d1..4632fcc3 100644 --- a/pkg/serving/v1/gitops_test.go +++ b/pkg/serving/v1/gitops_test.go @@ -21,13 +21,13 @@ import ( "time" "gotest.tools/v3/assert" - libtest "knative.dev/client-pkg/pkg/util/test" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" servingtest "knative.dev/serving/pkg/testing/v1" + libtest "knative.dev/client-pkg/pkg/util/test" "knative.dev/pkg/ptr" servingv1 "knative.dev/serving/pkg/apis/serving/v1" ) diff --git a/pkg/sources/common.go b/pkg/sources/common.go new file mode 100644 index 00000000..5aa9b7f0 --- /dev/null +++ b/pkg/sources/common.go @@ -0,0 +1,31 @@ +// Copyright © 2021 The Knative Authors +// +// 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 sources + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/eventing/pkg/apis/sources/v1beta2" +) + +// BuiltInSourcesGVKs returns the GVKs for built in sources +func BuiltInSourcesGVKs() []schema.GroupVersionKind { + return []schema.GroupVersionKind{ + v1.SchemeGroupVersion.WithKind("ApiServerSource"), + v1.SchemeGroupVersion.WithKind("ContainerSource"), + v1.SchemeGroupVersion.WithKind("SinkBinding"), + v1beta2.SchemeGroupVersion.WithKind("PingSource"), + } +} diff --git a/pkg/sources/common_test.go b/pkg/sources/common_test.go new file mode 100644 index 00000000..5cb1c0e1 --- /dev/null +++ b/pkg/sources/common_test.go @@ -0,0 +1,36 @@ +// Copyright © 2020 The Knative Authors +// +// 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 sources + +import ( + "testing" + + "gotest.tools/v3/assert" + + sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" +) + +func TestBuiltInSourcesGVks(t *testing.T) { + gvks := BuiltInSourcesGVKs() + for _, each := range gvks { + if each.Kind != "PingSource" { + assert.DeepEqual(t, each.GroupVersion(), sourcesv1.SchemeGroupVersion) + } else { + assert.DeepEqual(t, each.GroupVersion(), sourcesv1beta2.SchemeGroupVersion) + } + } + assert.Equal(t, len(gvks), 4) +} diff --git a/pkg/util/test/broker.go b/pkg/util/test/broker.go new file mode 100644 index 00000000..dc057f8e --- /dev/null +++ b/pkg/util/test/broker.go @@ -0,0 +1,79 @@ +// Copyright 2020 The Knative Authors + +// 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 im +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "strings" + "time" + + "gotest.tools/v3/assert" + "knative.dev/client-pkg/pkg/util" + + "k8s.io/apimachinery/pkg/util/wait" + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" +) + +// BrokerCreate creates a broker with the given name. +func BrokerCreate(r *KnRunResultCollector, name string) { + out := r.KnTest().Kn().Run("broker", "create", name) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "Broker", name, "created", "namespace", r.KnTest().Kn().Namespace())) +} + +// BrokerCreateWithClass creates a broker with the given name and class. +func BrokerCreateWithClass(r *KnRunResultCollector, name, class string) { + out := r.KnTest().Kn().Run("broker", "create", name, "--class", class) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "Broker", name, "created", "namespace", r.KnTest().Kn().Namespace())) +} + +// BrokerDelete deletes a broker with the given name. +func BrokerDelete(r *KnRunResultCollector, name string, wait bool) { + args := []string{"broker", "delete", name} + if wait { + args = append(args, "--wait") + } + out := r.KnTest().Kn().Run(args...) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "Broker", name, "deleted", "namespace", r.KnTest().Kn().Namespace())) +} + +// LabelNamespaceForDefaultBroker adds label 'knative-eventing-injection=enabled' to the configured namespace +func LabelNamespaceForDefaultBroker(r *KnRunResultCollector) error { + cmd := []string{"label", "namespace", r.KnTest().Kn().Namespace(), eventingv1.InjectionAnnotation + "=enabled"} + _, err := Kubectl{}.Run(cmd...) + + if err != nil { + r.T().Fatalf("error executing '%s': %s", strings.Join(cmd, " "), err.Error()) + } + + return wait.PollImmediate(10*time.Second, 5*time.Minute, func() (bool, error) { + out, err := NewKubectl(r.KnTest().Kn().Namespace()).Run("get", "broker", "-o=jsonpath='{.items[0].status.conditions[?(@.type==\"Ready\")].status}'") + if err != nil { + return false, nil + } + + return strings.Contains(out, "True"), nil + }) +} + +// UnlabelNamespaceForDefaultBroker removes label 'knative-eventing-injection=enabled' from the configured namespace +func UnlabelNamespaceForDefaultBroker(r *KnRunResultCollector) { + cmd := []string{"label", "namespace", r.KnTest().Kn().Namespace(), eventingv1.InjectionAnnotation + "-"} + _, err := Kubectl{}.Run(cmd...) + if err != nil { + r.T().Fatalf("error executing '%s': %s", strings.Join(cmd, " "), err.Error()) + } +} diff --git a/pkg/util/test/channel.go b/pkg/util/test/channel.go new file mode 100644 index 00000000..6f6ca538 --- /dev/null +++ b/pkg/util/test/channel.go @@ -0,0 +1,64 @@ +// Copyright 2020 The Knative Authors + +// 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 im +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "time" + + "gotest.tools/v3/assert" + + "knative.dev/client-pkg/pkg/util" +) + +func ChannelCreate(r *KnRunResultCollector, cname string, args ...string) { + cmd := []string{"channel", "create", cname} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "channel", cname, "created")) + // let channel reconcile TODO: fix the wait for channel to become ready + time.Sleep(5 * time.Second) +} + +func ChannelList(r *KnRunResultCollector, args ...string) string { + cmd := []string{"channel", "list"} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + return out.Stdout +} + +func ChannelDescribe(r *KnRunResultCollector, cname string, args ...string) string { + cmd := []string{"channel", "describe", cname} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + return out.Stdout +} + +func ChannelDelete(r *KnRunResultCollector, cname string) { + out := r.KnTest().Kn().Run("channel", "delete", cname) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "channel", cname, "deleted")) +} + +// ChannelListTypes return available channel types +func ChannelListTypes(r *KnRunResultCollector, args ...string) string { + cmd := []string{"channel", "list-types"} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + return out.Stdout +} diff --git a/pkg/util/test/eventtype.go b/pkg/util/test/eventtype.go new file mode 100644 index 00000000..ac3cc090 --- /dev/null +++ b/pkg/util/test/eventtype.go @@ -0,0 +1,60 @@ +// Copyright 2021 The Knative Authors + +// 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 test + +import ( + "gotest.tools/v3/assert" + "knative.dev/client-pkg/pkg/util" +) + +// EventtypeCreate creates an eventtype with the given name. +func EventtypeCreate(r *KnRunResultCollector, name, cetype string) { + out := r.KnTest().Kn().Run("eventtype", "create", name, "--type", cetype) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "Eventtype", name, "created", "namespace", r.KnTest().Kn().Namespace())) +} + +// EventtypeDelete deletes an eventtype with the given name. +func EventtypeDelete(r *KnRunResultCollector, name string) { + out := r.KnTest().Kn().Run("eventtype", "delete", name) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "Eventtype", name, "deleted", "namespace", r.KnTest().Kn().Namespace())) +} + +// EventtypeList verifies listing eventtypes in the given namespace +func EventtypeList(r *KnRunResultCollector, eventtypes ...string) { + out := r.KnTest().Kn().Run("eventtype", "list") + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, eventtypes...)) +} + +// EventtypeDescribe describes an eventtype with the given name. +func EventtypeDescribe(r *KnRunResultCollector, name string) { + out := r.KnTest().Kn().Run("eventtype", "describe", name) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAll(out.Stdout, name, r.KnTest().Kn().Namespace(), "Conditions")) +} + +func EventtypeCreateWithBrokerSource(r *KnRunResultCollector, name, cetype, broker, source string) { + out := r.KnTest().Kn().Run("eventtype", "create", name, "--type", cetype, "--broker", broker, "--source", source) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "Eventtype", name, "created", "namespace", r.KnTest().Kn().Namespace())) +} + +func EventtypeCreateWithSourceError(r *KnRunResultCollector, name, cetype, source string) { + out := r.KnTest().Kn().Run("eventtype", "create", name, "--type", cetype, "--source", source) + r.AssertError(out) + assert.Check(r.T(), util.ContainsAll(out.Stderr, name, "invalid", "control character")) +} diff --git a/pkg/util/test/init.go b/pkg/util/test/init.go new file mode 100644 index 00000000..5e406659 --- /dev/null +++ b/pkg/util/test/init.go @@ -0,0 +1,25 @@ +/* + Copyright 2024 The Knative Authors + + 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 test + +import "testing" + +func init() { + if !testing.Testing() { + panic("knative.dev/client-pkg/pkg/util/test: can only be called from test files") + } +} diff --git a/pkg/util/test/revision.go b/pkg/util/test/revision.go new file mode 100644 index 00000000..46521e7d --- /dev/null +++ b/pkg/util/test/revision.go @@ -0,0 +1,155 @@ +// Copyright 2020 The Knative Authors + +// 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 test + +import ( + "fmt" + "strconv" + "strings" + + "gotest.tools/v3/assert" + "knative.dev/client-pkg/pkg/util" +) + +// RevisionListForService list revisions of given service and verifies if their status is True +func RevisionListForService(r *KnRunResultCollector, serviceName string) { + out := r.KnTest().Kn().Run("revision", "list", "-s", serviceName) + r.AssertNoError(out) + outputLines := strings.Split(out.Stdout, "\n") + // Ignore the last line because it is an empty string caused by splitting a line break + // at the end of the output string + for _, line := range outputLines[1 : len(outputLines)-1] { + // The last item is the revision status, which should be ready + assert.Check(r.T(), util.ContainsAll(line, " "+serviceName+" ", "True")) + } +} + +// RevisionDescribe verifies revision describe output for given service's revision +func RevisionDescribe(r *KnRunResultCollector, serviceName string) { + revName := FindRevision(r, serviceName) + + out := r.KnTest().Kn().Run("revision", "describe", revName) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAll(out.Stdout, revName, r.KnTest().Kn().Namespace(), serviceName, "++ Ready", "TARGET=kn")) +} + +// RevisionDelete verifies deleting given revision in sync mode +func RevisionDelete(r *KnRunResultCollector, revName string) { + out := r.KnTest().Kn().Run("revision", "delete", "--wait", revName) + assert.Check(r.T(), util.ContainsAll(out.Stdout, "Revision", revName, "deleted", "namespace", r.KnTest().Kn().Namespace())) + r.AssertNoError(out) +} + +// RevisionMultipleDelete verifies deleting multiple revisions +func RevisionMultipleDelete(r *KnRunResultCollector, existRevision1, existRevision2, nonexistRevision string) { + out := r.KnTest().Kn().Run("revision", "list") + r.AssertNoError(out) + assert.Check(r.T(), strings.Contains(out.Stdout, existRevision1), "Required revision1 does not exist") + assert.Check(r.T(), strings.Contains(out.Stdout, existRevision2), "Required revision2 does not exist") + + out = r.KnTest().Kn().Run("revision", "delete", existRevision1, existRevision2, nonexistRevision) + r.AssertError(out) + + assert.Check(r.T(), util.ContainsAll(out.Stdout, "Revision", existRevision1, "deleted", "namespace", r.KnTest().Kn().Namespace()), "Failed to get 'deleted' first revision message") + assert.Check(r.T(), util.ContainsAll(out.Stdout, "Revision", existRevision2, "deleted", "namespace", r.KnTest().Kn().Namespace()), "Failed to get 'deleted' second revision message") + assert.Check(r.T(), util.ContainsAll(out.Stderr, "revisions.serving.knative.dev", nonexistRevision, "not found"), "Failed to get 'not found' error") +} + +// RevisionDescribeWithPrintFlags verifies describing given revision using print flag '--output=name' +func RevisionDescribeWithPrintFlags(r *KnRunResultCollector, revName string) { + out := r.KnTest().Kn().Run("revision", "describe", revName, "-o=name") + r.AssertNoError(out) + expectedName := fmt.Sprintf("revision.serving.knative.dev/%s", revName) + assert.Equal(r.T(), strings.TrimSpace(out.Stdout), expectedName) +} + +// FindRevision returns a revision name (at index 0) for given service +func FindRevision(r *KnRunResultCollector, serviceName string) string { + out := r.KnTest().Kn().Run("revision", "list", "-s", serviceName, "-o=jsonpath={.items[0].metadata.name}") + r.AssertNoError(out) + if strings.Contains(out.Stdout, "No resources") { + r.T().Errorf("Could not find revision name.") + } + return out.Stdout +} + +// FindRevisionByGeneration returns a revision name for given revision at given generation number +func FindRevisionByGeneration(r *KnRunResultCollector, serviceName string, generation int) string { + maxGen := FindConfigurationGeneration(r, serviceName) + out := r.KnTest().Kn().Run("revision", "list", "-s", serviceName, + fmt.Sprintf("-o=jsonpath={.items[%d].metadata.name}", maxGen-generation)) + r.AssertNoError(out) + if strings.Contains(out.Stdout, "No resources found.") { + r.T().Errorf("Could not find revision name.") + } + return out.Stdout +} + +// FindConfigurationGeneration returns the configuration generation number of given service +func FindConfigurationGeneration(r *KnRunResultCollector, serviceName string) int { + out := r.KnTest().Kn().Run("revision", "list", "-s", serviceName, "-o=jsonpath={.items[0].metadata.labels.serving\\.knative\\.dev/configurationGeneration}") + r.AssertNoError(out) + if out.Stdout == "" { + r.T().Errorf("Could not find configuration generation.") + } + confGen, err := strconv.Atoi(out.Stdout) + if err != nil { + r.T().Errorf("Invalid type of configuration generation: %s", err) + } + + return confGen +} + +// RevisionListOutputName verifies listing given revision using print flag '--output name' +func RevisionListOutputName(r *KnRunResultCollector, revisionName string) { + out := r.KnTest().Kn().Run("revision", "list", "--output", "name") + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAll(out.Stdout, revisionName, "revision.serving.knative.dev")) +} + +// RevisionListWithService verifies listing revisions per service from each given service names +func RevisionListWithService(r *KnRunResultCollector, serviceNames ...string) { + for _, svcName := range serviceNames { + confGen := FindConfigurationGeneration(r, svcName) + out := r.KnTest().Kn().Run("revision", "list", "-s", svcName) + r.AssertNoError(out) + + outputLines := strings.Split(out.Stdout, "\n") + // Ignore the last line because it is an empty string caused by splitting a line break + // at the end of the output string + for _, line := range outputLines[1 : len(outputLines)-1] { + revName := FindRevisionByGeneration(r, svcName, confGen) + assert.Check(r.T(), util.ContainsAll(line, revName, svcName, strconv.Itoa(confGen))) + confGen-- + } + if r.T().Failed() { + r.AddDump("service", svcName, r.KnTest().Kn().Namespace()) + } + } +} + +// RevisionDeleteWithPruneOption verifies removeing all unreferenced revisions for a given service in sync mode +func RevisionDeleteWithPruneOption(r *KnRunResultCollector, serviceName, revName string) { + out := r.KnTest().Kn().Run("revision", "delete", "--prune", serviceName) + assert.Check(r.T(), util.ContainsAll(out.Stdout, "Revision", "deleted", revName, "namespace", r.KnTest().Kn().Namespace())) + r.AssertNoError(out) +} + +// RevisionDeleteWithPruneAllOption verifies removeing all unreferenced revision in sync mode +func RevisionDeleteWithPruneAllOption(r *KnRunResultCollector, revName1, revName2 string) { + out := r.KnTest().Kn().Run("revision", "delete", "--prune-all") + assert.Check(r.T(), util.ContainsAll(out.Stdout, "Revision", "deleted", revName1, revName1, "namespace", r.KnTest().Kn().Namespace())) + r.AssertNoError(out) +} diff --git a/pkg/util/test/subscription.go b/pkg/util/test/subscription.go new file mode 100644 index 00000000..f61f5ef1 --- /dev/null +++ b/pkg/util/test/subscription.go @@ -0,0 +1,67 @@ +/* +Copyright 2020 The Knative Authors + +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 test + +import ( + "time" + + "gotest.tools/v3/assert" + + "knative.dev/client-pkg/pkg/util" +) + +func SubscriptionCreate(r *KnRunResultCollector, sname string, args ...string) { + cmd := []string{"subscription", "create", sname} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "subscription", sname, "created")) + // let the subscription and related resource reconcile + time.Sleep(time.Second * 5) +} + +func SubscriptionList(r *KnRunResultCollector, args ...string) string { + cmd := []string{"subscription", "list"} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + return out.Stdout +} + +func SubscriptionDescribe(r *KnRunResultCollector, sname string, args ...string) string { + cmd := []string{"subscription", "describe", sname} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + return out.Stdout +} + +func SubscriptionDelete(r *KnRunResultCollector, sname string) { + out := r.KnTest().Kn().Run("subscription", "delete", sname) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "subscription", sname, "deleted")) +} + +func SubscriptionUpdate(r *KnRunResultCollector, sname string, args ...string) { + cmd := []string{"subscription", "update", sname} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "subscription", sname, "updated")) + // let the subscription and related resource reconcile + time.Sleep(time.Second * 5) +} diff --git a/test/presubmit-tests.sh b/test/presubmit-tests.sh index 2ebea84f..715a5a06 100755 --- a/test/presubmit-tests.sh +++ b/test/presubmit-tests.sh @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -source $(dirname $0)/../vendor/knative.dev/hack/presubmit-tests.sh +# shellcheck disable=SC1090 +source "$(go run knative.dev/hack/cmd/script presubmit-tests.sh)" -main $@ +main "$@" diff --git a/vendor/k8s.io/code-generator/generate-groups.sh b/vendor/k8s.io/code-generator/generate-groups.sh old mode 100644 new mode 100755 diff --git a/vendor/k8s.io/code-generator/generate-internal-groups.sh b/vendor/k8s.io/code-generator/generate-internal-groups.sh old mode 100644 new mode 100755 diff --git a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/doc.go b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/doc.go similarity index 97% rename from vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/doc.go rename to vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/doc.go index 68b15a55..07add715 100644 --- a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/doc.go +++ b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/doc.go @@ -17,4 +17,4 @@ limitations under the License. // Code generated by client-gen. DO NOT EDIT. // This package has the automatically generated typed clients. -package v1beta1 +package v1beta2 diff --git a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventing_client.go b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/eventing_client.go similarity index 71% rename from vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventing_client.go rename to vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/eventing_client.go index ab8a0f85..ced74363 100644 --- a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventing_client.go +++ b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/eventing_client.go @@ -16,34 +16,34 @@ limitations under the License. // Code generated by client-gen. DO NOT EDIT. -package v1beta1 +package v1beta2 import ( "net/http" rest "k8s.io/client-go/rest" - v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + v1beta2 "knative.dev/eventing/pkg/apis/eventing/v1beta2" "knative.dev/eventing/pkg/client/clientset/versioned/scheme" ) -type EventingV1beta1Interface interface { +type EventingV1beta2Interface interface { RESTClient() rest.Interface EventTypesGetter } -// EventingV1beta1Client is used to interact with features provided by the eventing.knative.dev group. -type EventingV1beta1Client struct { +// EventingV1beta2Client is used to interact with features provided by the eventing.knative.dev group. +type EventingV1beta2Client struct { restClient rest.Interface } -func (c *EventingV1beta1Client) EventTypes(namespace string) EventTypeInterface { +func (c *EventingV1beta2Client) EventTypes(namespace string) EventTypeInterface { return newEventTypes(c, namespace) } -// NewForConfig creates a new EventingV1beta1Client for the given config. +// NewForConfig creates a new EventingV1beta2Client for the given config. // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), // where httpClient was generated with rest.HTTPClientFor(c). -func NewForConfig(c *rest.Config) (*EventingV1beta1Client, error) { +func NewForConfig(c *rest.Config) (*EventingV1beta2Client, error) { config := *c if err := setConfigDefaults(&config); err != nil { return nil, err @@ -55,9 +55,9 @@ func NewForConfig(c *rest.Config) (*EventingV1beta1Client, error) { return NewForConfigAndClient(&config, httpClient) } -// NewForConfigAndClient creates a new EventingV1beta1Client for the given config and http client. +// NewForConfigAndClient creates a new EventingV1beta2Client for the given config and http client. // Note the http client provided takes precedence over the configured transport values. -func NewForConfigAndClient(c *rest.Config, h *http.Client) (*EventingV1beta1Client, error) { +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*EventingV1beta2Client, error) { config := *c if err := setConfigDefaults(&config); err != nil { return nil, err @@ -66,12 +66,12 @@ func NewForConfigAndClient(c *rest.Config, h *http.Client) (*EventingV1beta1Clie if err != nil { return nil, err } - return &EventingV1beta1Client{client}, nil + return &EventingV1beta2Client{client}, nil } -// NewForConfigOrDie creates a new EventingV1beta1Client for the given config and +// NewForConfigOrDie creates a new EventingV1beta2Client for the given config and // panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *EventingV1beta1Client { +func NewForConfigOrDie(c *rest.Config) *EventingV1beta2Client { client, err := NewForConfig(c) if err != nil { panic(err) @@ -79,13 +79,13 @@ func NewForConfigOrDie(c *rest.Config) *EventingV1beta1Client { return client } -// New creates a new EventingV1beta1Client for the given RESTClient. -func New(c rest.Interface) *EventingV1beta1Client { - return &EventingV1beta1Client{c} +// New creates a new EventingV1beta2Client for the given RESTClient. +func New(c rest.Interface) *EventingV1beta2Client { + return &EventingV1beta2Client{c} } func setConfigDefaults(config *rest.Config) error { - gv := v1beta1.SchemeGroupVersion + gv := v1beta2.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() @@ -99,7 +99,7 @@ func setConfigDefaults(config *rest.Config) error { // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. -func (c *EventingV1beta1Client) RESTClient() rest.Interface { +func (c *EventingV1beta2Client) RESTClient() rest.Interface { if c == nil { return nil } diff --git a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventtype.go b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/eventtype.go similarity index 83% rename from vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventtype.go rename to vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/eventtype.go index 7005e330..95ba7873 100644 --- a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventtype.go +++ b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/eventtype.go @@ -16,7 +16,7 @@ limitations under the License. // Code generated by client-gen. DO NOT EDIT. -package v1beta1 +package v1beta2 import ( "context" @@ -26,7 +26,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + v1beta2 "knative.dev/eventing/pkg/apis/eventing/v1beta2" scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" ) @@ -38,15 +38,15 @@ type EventTypesGetter interface { // EventTypeInterface has methods to work with EventType resources. type EventTypeInterface interface { - Create(ctx context.Context, eventType *v1beta1.EventType, opts v1.CreateOptions) (*v1beta1.EventType, error) - Update(ctx context.Context, eventType *v1beta1.EventType, opts v1.UpdateOptions) (*v1beta1.EventType, error) - UpdateStatus(ctx context.Context, eventType *v1beta1.EventType, opts v1.UpdateOptions) (*v1beta1.EventType, error) + Create(ctx context.Context, eventType *v1beta2.EventType, opts v1.CreateOptions) (*v1beta2.EventType, error) + Update(ctx context.Context, eventType *v1beta2.EventType, opts v1.UpdateOptions) (*v1beta2.EventType, error) + UpdateStatus(ctx context.Context, eventType *v1beta2.EventType, opts v1.UpdateOptions) (*v1beta2.EventType, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.EventType, error) - List(ctx context.Context, opts v1.ListOptions) (*v1beta1.EventTypeList, error) + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta2.EventType, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta2.EventTypeList, error) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.EventType, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.EventType, err error) EventTypeExpansion } @@ -57,7 +57,7 @@ type eventTypes struct { } // newEventTypes returns a EventTypes -func newEventTypes(c *EventingV1beta1Client, namespace string) *eventTypes { +func newEventTypes(c *EventingV1beta2Client, namespace string) *eventTypes { return &eventTypes{ client: c.RESTClient(), ns: namespace, @@ -65,8 +65,8 @@ func newEventTypes(c *EventingV1beta1Client, namespace string) *eventTypes { } // Get takes name of the eventType, and returns the corresponding eventType object, and an error if there is any. -func (c *eventTypes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.EventType, err error) { - result = &v1beta1.EventType{} +func (c *eventTypes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.EventType, err error) { + result = &v1beta2.EventType{} err = c.client.Get(). Namespace(c.ns). Resource("eventtypes"). @@ -78,12 +78,12 @@ func (c *eventTypes) Get(ctx context.Context, name string, options v1.GetOptions } // List takes label and field selectors, and returns the list of EventTypes that match those selectors. -func (c *eventTypes) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.EventTypeList, err error) { +func (c *eventTypes) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.EventTypeList, err error) { var timeout time.Duration if opts.TimeoutSeconds != nil { timeout = time.Duration(*opts.TimeoutSeconds) * time.Second } - result = &v1beta1.EventTypeList{} + result = &v1beta2.EventTypeList{} err = c.client.Get(). Namespace(c.ns). Resource("eventtypes"). @@ -110,8 +110,8 @@ func (c *eventTypes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Inte } // Create takes the representation of a eventType and creates it. Returns the server's representation of the eventType, and an error, if there is any. -func (c *eventTypes) Create(ctx context.Context, eventType *v1beta1.EventType, opts v1.CreateOptions) (result *v1beta1.EventType, err error) { - result = &v1beta1.EventType{} +func (c *eventTypes) Create(ctx context.Context, eventType *v1beta2.EventType, opts v1.CreateOptions) (result *v1beta2.EventType, err error) { + result = &v1beta2.EventType{} err = c.client.Post(). Namespace(c.ns). Resource("eventtypes"). @@ -123,8 +123,8 @@ func (c *eventTypes) Create(ctx context.Context, eventType *v1beta1.EventType, o } // Update takes the representation of a eventType and updates it. Returns the server's representation of the eventType, and an error, if there is any. -func (c *eventTypes) Update(ctx context.Context, eventType *v1beta1.EventType, opts v1.UpdateOptions) (result *v1beta1.EventType, err error) { - result = &v1beta1.EventType{} +func (c *eventTypes) Update(ctx context.Context, eventType *v1beta2.EventType, opts v1.UpdateOptions) (result *v1beta2.EventType, err error) { + result = &v1beta2.EventType{} err = c.client.Put(). Namespace(c.ns). Resource("eventtypes"). @@ -138,8 +138,8 @@ func (c *eventTypes) Update(ctx context.Context, eventType *v1beta1.EventType, o // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *eventTypes) UpdateStatus(ctx context.Context, eventType *v1beta1.EventType, opts v1.UpdateOptions) (result *v1beta1.EventType, err error) { - result = &v1beta1.EventType{} +func (c *eventTypes) UpdateStatus(ctx context.Context, eventType *v1beta2.EventType, opts v1.UpdateOptions) (result *v1beta2.EventType, err error) { + result = &v1beta2.EventType{} err = c.client.Put(). Namespace(c.ns). Resource("eventtypes"). @@ -180,8 +180,8 @@ func (c *eventTypes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions } // Patch applies the patch and returns the patched eventType. -func (c *eventTypes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.EventType, err error) { - result = &v1beta1.EventType{} +func (c *eventTypes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.EventType, err error) { + result = &v1beta2.EventType{} err = c.client.Patch(pt). Namespace(c.ns). Resource("eventtypes"). diff --git a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/doc.go b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake/doc.go similarity index 100% rename from vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/doc.go rename to vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake/doc.go diff --git a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventing_client.go b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake/fake_eventing_client.go similarity index 77% rename from vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventing_client.go rename to vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake/fake_eventing_client.go index aa6fcf6b..784f0a65 100644 --- a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventing_client.go +++ b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake/fake_eventing_client.go @@ -21,20 +21,20 @@ package fake import ( rest "k8s.io/client-go/rest" testing "k8s.io/client-go/testing" - v1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1" + v1beta2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2" ) -type FakeEventingV1beta1 struct { +type FakeEventingV1beta2 struct { *testing.Fake } -func (c *FakeEventingV1beta1) EventTypes(namespace string) v1beta1.EventTypeInterface { +func (c *FakeEventingV1beta2) EventTypes(namespace string) v1beta2.EventTypeInterface { return &FakeEventTypes{c, namespace} } // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. -func (c *FakeEventingV1beta1) RESTClient() rest.Interface { +func (c *FakeEventingV1beta2) RESTClient() rest.Interface { var ret *rest.RESTClient return ret } diff --git a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventtype.go b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake/fake_eventtype.go similarity index 76% rename from vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventtype.go rename to vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake/fake_eventtype.go index 7de907f2..5dd5fa7f 100644 --- a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventtype.go +++ b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake/fake_eventtype.go @@ -26,34 +26,34 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + v1beta2 "knative.dev/eventing/pkg/apis/eventing/v1beta2" ) // FakeEventTypes implements EventTypeInterface type FakeEventTypes struct { - Fake *FakeEventingV1beta1 + Fake *FakeEventingV1beta2 ns string } -var eventtypesResource = v1beta1.SchemeGroupVersion.WithResource("eventtypes") +var eventtypesResource = v1beta2.SchemeGroupVersion.WithResource("eventtypes") -var eventtypesKind = v1beta1.SchemeGroupVersion.WithKind("EventType") +var eventtypesKind = v1beta2.SchemeGroupVersion.WithKind("EventType") // Get takes name of the eventType, and returns the corresponding eventType object, and an error if there is any. -func (c *FakeEventTypes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.EventType, err error) { +func (c *FakeEventTypes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.EventType, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(eventtypesResource, c.ns, name), &v1beta1.EventType{}) + Invokes(testing.NewGetAction(eventtypesResource, c.ns, name), &v1beta2.EventType{}) if obj == nil { return nil, err } - return obj.(*v1beta1.EventType), err + return obj.(*v1beta2.EventType), err } // List takes label and field selectors, and returns the list of EventTypes that match those selectors. -func (c *FakeEventTypes) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.EventTypeList, err error) { +func (c *FakeEventTypes) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.EventTypeList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(eventtypesResource, eventtypesKind, c.ns, opts), &v1beta1.EventTypeList{}) + Invokes(testing.NewListAction(eventtypesResource, eventtypesKind, c.ns, opts), &v1beta2.EventTypeList{}) if obj == nil { return nil, err @@ -63,8 +63,8 @@ func (c *FakeEventTypes) List(ctx context.Context, opts v1.ListOptions) (result if label == nil { label = labels.Everything() } - list := &v1beta1.EventTypeList{ListMeta: obj.(*v1beta1.EventTypeList).ListMeta} - for _, item := range obj.(*v1beta1.EventTypeList).Items { + list := &v1beta2.EventTypeList{ListMeta: obj.(*v1beta2.EventTypeList).ListMeta} + for _, item := range obj.(*v1beta2.EventTypeList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -80,43 +80,43 @@ func (c *FakeEventTypes) Watch(ctx context.Context, opts v1.ListOptions) (watch. } // Create takes the representation of a eventType and creates it. Returns the server's representation of the eventType, and an error, if there is any. -func (c *FakeEventTypes) Create(ctx context.Context, eventType *v1beta1.EventType, opts v1.CreateOptions) (result *v1beta1.EventType, err error) { +func (c *FakeEventTypes) Create(ctx context.Context, eventType *v1beta2.EventType, opts v1.CreateOptions) (result *v1beta2.EventType, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(eventtypesResource, c.ns, eventType), &v1beta1.EventType{}) + Invokes(testing.NewCreateAction(eventtypesResource, c.ns, eventType), &v1beta2.EventType{}) if obj == nil { return nil, err } - return obj.(*v1beta1.EventType), err + return obj.(*v1beta2.EventType), err } // Update takes the representation of a eventType and updates it. Returns the server's representation of the eventType, and an error, if there is any. -func (c *FakeEventTypes) Update(ctx context.Context, eventType *v1beta1.EventType, opts v1.UpdateOptions) (result *v1beta1.EventType, err error) { +func (c *FakeEventTypes) Update(ctx context.Context, eventType *v1beta2.EventType, opts v1.UpdateOptions) (result *v1beta2.EventType, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(eventtypesResource, c.ns, eventType), &v1beta1.EventType{}) + Invokes(testing.NewUpdateAction(eventtypesResource, c.ns, eventType), &v1beta2.EventType{}) if obj == nil { return nil, err } - return obj.(*v1beta1.EventType), err + return obj.(*v1beta2.EventType), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeEventTypes) UpdateStatus(ctx context.Context, eventType *v1beta1.EventType, opts v1.UpdateOptions) (*v1beta1.EventType, error) { +func (c *FakeEventTypes) UpdateStatus(ctx context.Context, eventType *v1beta2.EventType, opts v1.UpdateOptions) (*v1beta2.EventType, error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(eventtypesResource, "status", c.ns, eventType), &v1beta1.EventType{}) + Invokes(testing.NewUpdateSubresourceAction(eventtypesResource, "status", c.ns, eventType), &v1beta2.EventType{}) if obj == nil { return nil, err } - return obj.(*v1beta1.EventType), err + return obj.(*v1beta2.EventType), err } // Delete takes name of the eventType and deletes it. Returns an error if one occurs. func (c *FakeEventTypes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(eventtypesResource, c.ns, name, opts), &v1beta1.EventType{}) + Invokes(testing.NewDeleteActionWithOptions(eventtypesResource, c.ns, name, opts), &v1beta2.EventType{}) return err } @@ -125,17 +125,17 @@ func (c *FakeEventTypes) Delete(ctx context.Context, name string, opts v1.Delete func (c *FakeEventTypes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { action := testing.NewDeleteCollectionAction(eventtypesResource, c.ns, listOpts) - _, err := c.Fake.Invokes(action, &v1beta1.EventTypeList{}) + _, err := c.Fake.Invokes(action, &v1beta2.EventTypeList{}) return err } // Patch applies the patch and returns the patched eventType. -func (c *FakeEventTypes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.EventType, err error) { +func (c *FakeEventTypes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.EventType, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(eventtypesResource, c.ns, name, pt, data, subresources...), &v1beta1.EventType{}) + Invokes(testing.NewPatchSubresourceAction(eventtypesResource, c.ns, name, pt, data, subresources...), &v1beta2.EventType{}) if obj == nil { return nil, err } - return obj.(*v1beta1.EventType), err + return obj.(*v1beta2.EventType), err } diff --git a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/generated_expansion.go b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/generated_expansion.go similarity index 97% rename from vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/generated_expansion.go rename to vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/generated_expansion.go index cd07c63e..18a7ab38 100644 --- a/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/generated_expansion.go +++ b/vendor/knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/generated_expansion.go @@ -16,6 +16,6 @@ limitations under the License. // Code generated by client-gen. DO NOT EDIT. -package v1beta1 +package v1beta2 type EventTypeExpansion interface{} diff --git a/vendor/knative.dev/hack/cmd/script/README.md b/vendor/knative.dev/hack/cmd/script/README.md new file mode 100644 index 00000000..66579b24 --- /dev/null +++ b/vendor/knative.dev/hack/cmd/script/README.md @@ -0,0 +1,49 @@ +# Vendorless Knative + +The Knative projects can be built without a vendor directory. This is a +convenience for developers, and brings a number of benefits: + +* It is easier to see the changes to the code and review them. +* It is easier to maintain the build and CI scripts, as they don't need to + filter out the vendor directory. +* The project doesn't look dated (the proper dependency management tools + are available for Go since 1.13+). +* No vendor directory means less possibility of accidentally traversing + into it by symlinks or scripts. + +For more details and reasons for avoiding the vendor directory, see +[knative/infra#134](https://github.com/knative/infra/issues/134). + +## Status + +The [knative/infra#134](https://github.com/knative/infra/issues/134) is +ongoing effort. Currently, it is possible to use make projects vendorless, +only if they don't use Knative nor Kubernetes code-generation tools. See the +epic issue for current status. + +## Migration to a vendorless project + +The following steps are required to migrate a project to be vendorless: + +1. Update the `knative.dev/hack` dependency to the latest version. +1. Update the project scripts to use the scripts inflation: + ```patch + -source $(dirname $0)/../vendor/knative.dev/hack/release.sh + +source "$(go run knative.dev/hack/cmd/script release.sh)" + ``` +1. Update the `hack/tools.go` file to refer to the `knative.dev/hack/cmd/script` + tool: + ```go + package hack + + import ( + _ "knative.dev/hack/cmd/script" + ) + ``` +1. Remove the `vendor` directory. +1. Run `hack/update-deps.sh` to update the `go.mod` file(s). + +### Examples of migrated projects + +* [knative/func#1966](https://github.com/knative/func/pull/1966) +* [knative-extensions/kn-plugin-event#307](https://github.com/knative-extensions/kn-plugin-event/pull/307) diff --git a/vendor/knative.dev/hack/cmd/script/main.go b/vendor/knative.dev/hack/cmd/script/main.go new file mode 100644 index 00000000..288e1071 --- /dev/null +++ b/vendor/knative.dev/hack/cmd/script/main.go @@ -0,0 +1,28 @@ +/* +Copyright 2022 The Knative Authors + +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 + + https://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 main + +import "knative.dev/hack/pkg/inflator/cli" + +func main() { + cli.ExecuteOrDie(cli.Options...) +} + +// RunMain is used by tests to run the main function. +func RunMain() { // nolint:deadcode + main() +} diff --git a/vendor/knative.dev/hack/pkg/inflator/cli/app.go b/vendor/knative.dev/hack/pkg/inflator/cli/app.go new file mode 100644 index 00000000..445adca2 --- /dev/null +++ b/vendor/knative.dev/hack/pkg/inflator/cli/app.go @@ -0,0 +1,40 @@ +package cli + +import ( + "fmt" + + "knative.dev/hack/pkg/inflator/extract" + "knative.dev/hack/pkg/retcode" +) + +// Execute will execute the application. +func Execute(opts []Option) Result { + ex := Execution{}.Default().Configure(opts) + fl, err := parseArgs(&ex) + if err != nil { + return Result{ + Execution: ex, + Err: err, + } + } + op := createOperation(fl, ex.Args) + return Result{ + Execution: ex, + Err: op.Extract(ex), + } +} + +// ExecuteOrDie will execute the application or perform os.Exit in case of error. +func ExecuteOrDie(opts ...Option) { + if r := Execute(opts); r.Err != nil { + r.PrintErrln(fmt.Sprintf("%v", r.Err)) + r.Exit(retcode.Calc(r.Err)) + } +} + +func createOperation(fl *flags, argv []string) extract.Operation { + return extract.Operation{ + ScriptName: argv[0], + Verbose: fl.verbose, + } +} diff --git a/vendor/knative.dev/hack/pkg/inflator/cli/exec.go b/vendor/knative.dev/hack/pkg/inflator/cli/exec.go new file mode 100644 index 00000000..3f3617b2 --- /dev/null +++ b/vendor/knative.dev/hack/pkg/inflator/cli/exec.go @@ -0,0 +1,51 @@ +package cli + +import ( + "io" + "os" +) + +// Execution is used to execute a command. +type Execution struct { + Args []string + Stdout io.Writer + Stderr io.Writer + Exit func(code int) +} + +// Default will set default values for the execution. +func (e Execution) Default() Execution { + if e.Stdout == nil { + e.Stdout = os.Stdout + } + if e.Stderr == nil { + e.Stderr = os.Stderr + } + if e.Exit == nil { + e.Exit = os.Exit + } + if len(e.Args) == 0 { + e.Args = os.Args[1:] + } + return e +} + +// Configure will configure the execution. +func (e Execution) Configure(opts []Option) Execution { + for _, opt := range opts { + opt(&e) + } + return e +} + +// Option is used to configure an App. +type Option func(*Execution) + +// Options to override the commandline for testing purposes. +var Options []Option //nolint:gochecknoglobals + +// Result is a result of execution. +type Result struct { + Execution + Err error +} diff --git a/vendor/knative.dev/hack/pkg/inflator/cli/flags.go b/vendor/knative.dev/hack/pkg/inflator/cli/flags.go new file mode 100644 index 00000000..3e933181 --- /dev/null +++ b/vendor/knative.dev/hack/pkg/inflator/cli/flags.go @@ -0,0 +1,46 @@ +package cli + +import ( + "os" + "strings" +) + +const ( + // ManualVerboseEnvVar is the environment variable that can be set to disable + // automatic verbose mode on CI servers. + ManualVerboseEnvVar = "KNATIVE_HACK_SCRIPT_MANUAL_VERBOSE" +) + +type flags struct { + verbose bool +} + +func parseArgs(ex *Execution) (*flags, error) { + f := flags{ + verbose: isCiServer(), + } + if len(ex.Args) == 0 { + return nil, usageErr{} + } + for i := 0; i < len(ex.Args); i++ { + if ex.Args[i] == "-v" || ex.Args[i] == "--verbose" { + f.verbose = true + ex.Args = append(ex.Args[:i], ex.Args[i+1:]...) + i-- + } + + if ex.Args[i] == "-h" || ex.Args[i] == "--help" { + return nil, usageErr{} + } + } + return &f, nil +} + +func isCiServer() bool { + if strings.HasPrefix(strings.ToLower(os.Getenv(ManualVerboseEnvVar)), "t") { + return false + } + return os.Getenv("CI") != "" || + os.Getenv("BUILD_ID") != "" || + os.Getenv("PROW_JOB_ID") != "" +} diff --git a/vendor/knative.dev/hack/pkg/inflator/cli/print.go b/vendor/knative.dev/hack/pkg/inflator/cli/print.go new file mode 100644 index 00000000..ca734572 --- /dev/null +++ b/vendor/knative.dev/hack/pkg/inflator/cli/print.go @@ -0,0 +1,33 @@ +package cli + +import "fmt" + +// Print is a convenience method to Print to the defined output, fallback to Stderr if not set. +func (e Execution) Print(i ...interface{}) { + fmt.Fprint(e.Stdout, i...) +} + +// Println is a convenience method to Println to the defined output, fallback to Stderr if not set. +func (e Execution) Println(i ...interface{}) { + e.Print(fmt.Sprintln(i...)) +} + +// Printf is a convenience method to Printf to the defined output, fallback to Stderr if not set. +func (e Execution) Printf(format string, i ...interface{}) { + e.Print(fmt.Sprintf(format, i...)) +} + +// PrintErr is a convenience method to Print to the defined Err output, fallback to Stderr if not set. +func (e Execution) PrintErr(i ...interface{}) { + fmt.Fprint(e.Stderr, i...) +} + +// PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set. +func (e Execution) PrintErrln(i ...interface{}) { + e.PrintErr(fmt.Sprintln(i...)) +} + +// PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set. +func (e Execution) PrintErrf(format string, i ...interface{}) { + e.PrintErr(fmt.Sprintf(format, i...)) +} diff --git a/vendor/knative.dev/hack/pkg/inflator/cli/usage.go b/vendor/knative.dev/hack/pkg/inflator/cli/usage.go new file mode 100644 index 00000000..e338e536 --- /dev/null +++ b/vendor/knative.dev/hack/pkg/inflator/cli/usage.go @@ -0,0 +1,25 @@ +package cli + +type usageErr struct{} + +func (u usageErr) Retcode() int { + return 0 +} + +func (u usageErr) Error() string { + return `Hacks as Go self-extracting binary + +Will extract Hack scripts to a temporary directory, and provide a source +file path to requested shell script. + +# In Bash script +source "$(go run knative.dev/hack/cmd/script@latest library.sh)" + +Usage: + script [flags] library.sh + +Flags: + -h, --help help + -v, --verbose verbose output +` +} diff --git a/vendor/knative.dev/hack/pkg/inflator/extract/errors.go b/vendor/knative.dev/hack/pkg/inflator/extract/errors.go new file mode 100644 index 00000000..f6583458 --- /dev/null +++ b/vendor/knative.dev/hack/pkg/inflator/extract/errors.go @@ -0,0 +1,24 @@ +package extract + +import ( + "errors" + "fmt" +) + +var ( + // ErrBug is an error that indicates a bug in the code. + ErrBug = errors.New("probably a bug in the code") + + // ErrUnexpected is an error that indicates an unexpected situation. + ErrUnexpected = errors.New("unexpected situation") +) + +func wrapErr(err error, target error) error { + if err == nil { + return nil + } + if errors.Is(err, target) { + return err + } + return fmt.Errorf("%w: %v", target, err) +} diff --git a/vendor/knative.dev/hack/pkg/inflator/extract/extract.go b/vendor/knative.dev/hack/pkg/inflator/extract/extract.go new file mode 100644 index 00000000..b48ac568 --- /dev/null +++ b/vendor/knative.dev/hack/pkg/inflator/extract/extract.go @@ -0,0 +1,136 @@ +package extract + +import ( + "io/fs" + "math" + "os" + "path" + "strings" + + "knative.dev/hack" +) + +const ( + // HackScriptsDirEnvVar is the name of the environment variable that points + // to directory where knative-hack scripts will be extracted. + HackScriptsDirEnvVar = "KNATIVE_HACK_SCRIPTS_DIR" + // PermOwnerWrite is the permission bits for owner write. + PermOwnerWrite = 0o200 + // PermAllExecutable is the permission bits for executable. + PermAllExecutable = 0o111 +) + +// Printer is an interface for printing messages. +type Printer interface { + Print(i ...interface{}) + Println(i ...interface{}) + Printf(format string, i ...interface{}) + PrintErr(i ...interface{}) + PrintErrln(i ...interface{}) + PrintErrf(format string, i ...interface{}) +} + +// Operation is the main extract object that can extract scripts. +type Operation struct { + // ScriptName is the name of the script to extract. + ScriptName string + // Verbose will print more information. + Verbose bool +} + +// Extract will extract a script from the library to a temporary directory and +// provide the file path to it. +func (o Operation) Extract(prtr Printer) error { + l := logger{o.Verbose, prtr} + hackRootDir := os.Getenv(HackScriptsDirEnvVar) + if f, err := hack.Scripts.Open(o.ScriptName); err != nil { + return wrapErr(err, ErrUnexpected) + } else if err = f.Close(); err != nil { + return wrapErr(err, ErrUnexpected) + } + if hackRootDir == "" { + hackRootDir = path.Join(os.TempDir(), "knative", "hack", "scripts") + } + l.debugf("Extracting hack scripts to directory: %s", hackRootDir) + if err := copyDir(l, hack.Scripts, hackRootDir, "."); err != nil { + return err + } + scriptPath := path.Join(hackRootDir, o.ScriptName) + l.Println(scriptPath) + return nil +} + +func copyDir(l logger, inputFS fs.ReadDirFS, destRootDir, dir string) error { + return wrapErr(fs.WalkDir(inputFS, dir, func(filePath string, dirEntry fs.DirEntry, err error) error { + if err != nil { + return wrapErr(err, ErrBug) + } + return copyFile(l, inputFS, destRootDir, filePath, dirEntry) + }), ErrUnexpected) +} + +func copyFile( + l logger, + inputFS fs.ReadDirFS, + destRootDir, filePath string, + dirEntry fs.DirEntry, +) error { + inputFI, err := dirEntry.Info() + if err != nil { + return wrapErr(err, ErrBug) + } + + destPath := path.Join(destRootDir, filePath) + perm := inputFI.Mode().Perm() + + perm |= PermOwnerWrite + if dirEntry.IsDir() { + perm |= PermAllExecutable + if err = os.MkdirAll(destPath, perm); err != nil { + return wrapErr(err, ErrUnexpected) + } + return nil + } + + var ( + inputCS checksum + destCS checksum + destFI fs.FileInfo + bytes []byte + ) + inputCS = asChecksum(inputFI) + if destFI, err = os.Stat(destPath); err != nil { + if !os.IsNotExist(err) { + return wrapErr(err, ErrUnexpected) + } + } else { + destCS = asChecksum(destFI) + } + if inputCS == destCS { + l.debugf("%-30s up-to-date", filePath) + return nil + } + if bytes, err = fs.ReadFile(inputFS, filePath); err != nil { + return wrapErr(err, ErrBug) + } + if err = os.WriteFile(destPath, bytes, perm); err != nil { + return wrapErr(err, ErrUnexpected) + } + + sizeKB := int(inputFI.Size() / 1024) + size5k := int(math.Ceil(float64(sizeKB) / 5)) + l.debugf("%-30s %3d KiB %s", filePath, sizeKB, strings.Repeat("+", size5k)) + return nil +} + +func asChecksum(f fs.FileInfo) checksum { + return checksum{ + exists: true, + size: f.Size(), + } +} + +type checksum struct { + exists bool + size int64 +} diff --git a/vendor/knative.dev/hack/pkg/inflator/extract/logger.go b/vendor/knative.dev/hack/pkg/inflator/extract/logger.go new file mode 100644 index 00000000..5f7e25b4 --- /dev/null +++ b/vendor/knative.dev/hack/pkg/inflator/extract/logger.go @@ -0,0 +1,12 @@ +package extract + +type logger struct { + verbose bool + Printer +} + +func (l logger) debugf(format string, i ...interface{}) { + if l.verbose { + l.PrintErrf("[hack] "+format+"\n", i...) + } +} diff --git a/vendor/knative.dev/hack/pkg/retcode/retcode.go b/vendor/knative.dev/hack/pkg/retcode/retcode.go new file mode 100644 index 00000000..886eb0e6 --- /dev/null +++ b/vendor/knative.dev/hack/pkg/retcode/retcode.go @@ -0,0 +1,28 @@ +package retcode + +import "hash/crc32" + +var ( + // LowerBound is the lower bound of the POSIX retcode range. Use this to + // configure the package. + LowerBound = 1 + // UpperBound is the upper bound of the POSIX retcode range. Use this to + // configure the package. + UpperBound = 255 +) + +// Calc will calculate an POSIX retcode from an error. +func Calc(err error) int { + if err == nil { + return 0 + } + if r, ok := err.(retcodeErr); ok { + return r.Retcode() + } + upper := UpperBound - LowerBound + return int(crc32.ChecksumIEEE([]byte(err.Error())))%upper + LowerBound +} + +type retcodeErr interface { + Retcode() int +} diff --git a/vendor/knative.dev/pkg/hack/generate-knative.sh b/vendor/knative.dev/pkg/hack/generate-knative.sh old mode 100644 new mode 100755 diff --git a/vendor/modules.txt b/vendor/modules.txt index 023ad083..f7b02427 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1002,8 +1002,8 @@ knative.dev/eventing/pkg/apis/sources/v1beta2 knative.dev/eventing/pkg/client/clientset/versioned/scheme knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1 knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1/fake -knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1 -knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake +knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2 +knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta2/fake knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1 knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1 knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1/fake @@ -1013,6 +1013,10 @@ knative.dev/eventing/pkg/eventingtls # knative.dev/hack v0.0.0-20240318013248-424e75ed769a ## explicit; go 1.18 knative.dev/hack +knative.dev/hack/cmd/script +knative.dev/hack/pkg/inflator/cli +knative.dev/hack/pkg/inflator/extract +knative.dev/hack/pkg/retcode # knative.dev/networking v0.0.0-20240318132715-69566347ab2a ## explicit; go 1.21 knative.dev/networking/pkg