From 2d0dbe86fbae6080baed7138e4394d97850f19c5 Mon Sep 17 00:00:00 2001 From: David Simansky Date: Sat, 20 Feb 2021 16:39:38 +0100 Subject: [PATCH 1/6] Add DomainMapping CRUD commands --- CHANGELOG.adoc | 5 + docs/cmd/kn.md | 1 + docs/cmd/kn_domain.md | 33 +++ docs/cmd/kn_domain_create.md | 38 ++++ docs/cmd/kn_domain_delete.md | 37 +++ docs/cmd/kn_domain_describe.md | 33 +++ docs/cmd/kn_domain_list.md | 45 ++++ docs/cmd/kn_domain_update.md | 38 ++++ pkg/kn/commands/domain/create.go | 79 +++++++ pkg/kn/commands/domain/create_test.go | 60 +++++ pkg/kn/commands/domain/delete.go | 61 +++++ pkg/kn/commands/domain/delete_test.go | 51 +++++ pkg/kn/commands/domain/describe.go | 95 ++++++++ pkg/kn/commands/domain/describe_test.go | 115 ++++++++++ pkg/kn/commands/domain/domain.go | 36 +++ pkg/kn/commands/domain/domain_test.go | 99 ++++++++ .../commands/domain/human_readable_flags.go | 72 ++++++ pkg/kn/commands/domain/list.go | 65 ++++++ pkg/kn/commands/domain/list_test.go | 58 +++++ pkg/kn/commands/domain/update.go | 82 +++++++ pkg/kn/commands/domain/update_test.go | 73 ++++++ pkg/kn/commands/types.go | 42 +++- pkg/kn/commands/types_test.go | 50 +++++ pkg/kn/root/root.go | 3 + pkg/serving/v1alpha1/client.go | 160 +++++++++++++ pkg/serving/v1alpha1/client_mock.go | 115 ++++++++++ pkg/serving/v1alpha1/client_mock_test.go | 45 ++++ pkg/serving/v1alpha1/client_test.go | 211 ++++++++++++++++++ test/common.sh | 10 + test/e2e/domain_mapping_test.go | 95 ++++++++ .../versioned/typed/serving/v1alpha1/doc.go | 20 ++ .../typed/serving/v1alpha1/domainmapping.go | 195 ++++++++++++++++ .../typed/serving/v1alpha1/fake/doc.go | 20 ++ .../v1alpha1/fake/fake_domainmapping.go | 142 ++++++++++++ .../v1alpha1/fake/fake_serving_client.go | 40 ++++ .../serving/v1alpha1/generated_expansion.go | 21 ++ .../typed/serving/v1alpha1/serving_client.go | 89 ++++++++ vendor/modules.txt | 2 + 38 files changed, 2425 insertions(+), 11 deletions(-) create mode 100644 docs/cmd/kn_domain.md create mode 100644 docs/cmd/kn_domain_create.md create mode 100644 docs/cmd/kn_domain_delete.md create mode 100644 docs/cmd/kn_domain_describe.md create mode 100644 docs/cmd/kn_domain_list.md create mode 100644 docs/cmd/kn_domain_update.md create mode 100644 pkg/kn/commands/domain/create.go create mode 100644 pkg/kn/commands/domain/create_test.go create mode 100644 pkg/kn/commands/domain/delete.go create mode 100644 pkg/kn/commands/domain/delete_test.go create mode 100644 pkg/kn/commands/domain/describe.go create mode 100644 pkg/kn/commands/domain/describe_test.go create mode 100644 pkg/kn/commands/domain/domain.go create mode 100644 pkg/kn/commands/domain/domain_test.go create mode 100644 pkg/kn/commands/domain/human_readable_flags.go create mode 100644 pkg/kn/commands/domain/list.go create mode 100644 pkg/kn/commands/domain/list_test.go create mode 100644 pkg/kn/commands/domain/update.go create mode 100644 pkg/kn/commands/domain/update_test.go create mode 100644 pkg/serving/v1alpha1/client.go create mode 100644 pkg/serving/v1alpha1/client_mock.go create mode 100644 pkg/serving/v1alpha1/client_mock_test.go create mode 100644 pkg/serving/v1alpha1/client_test.go create mode 100644 test/e2e/domain_mapping_test.go create mode 100644 vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/doc.go create mode 100644 vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/domainmapping.go create mode 100644 vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/doc.go create mode 100644 vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/fake_domainmapping.go create mode 100644 vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/fake_serving_client.go create mode 100644 vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/generated_expansion.go create mode 100644 vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/serving_client.go diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index d5838c7572..3e016e5877 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -40,6 +40,11 @@ | 🎁 | Add context.Context parameter to API function | https://github.com/knative/client/pull/1274[#1274] + +| 🎁 +| Add `kn domain` commands to manage DomainMapping +| https://github.com/knative/client/pull/1267[#1267] + |=== ## v0.21.0 (2021-02-23) diff --git a/docs/cmd/kn.md b/docs/cmd/kn.md index ba6759edf0..6c57cee891 100644 --- a/docs/cmd/kn.md +++ b/docs/cmd/kn.md @@ -24,6 +24,7 @@ kn is the command line interface for managing Knative Serving and Eventing resou * [kn broker](kn_broker.md) - Manage message brokers * [kn channel](kn_channel.md) - Manage event channels * [kn completion](kn_completion.md) - Output shell completion code +* [kn domain](kn_domain.md) - Manage domain mappings * [kn options](kn_options.md) - Print the list of flags inherited by all commands * [kn plugin](kn_plugin.md) - Manage kn plugins * [kn revision](kn_revision.md) - Manage service revisions diff --git a/docs/cmd/kn_domain.md b/docs/cmd/kn_domain.md new file mode 100644 index 0000000000..d04e58487b --- /dev/null +++ b/docs/cmd/kn_domain.md @@ -0,0 +1,33 @@ +## kn domain + +Manage domain mappings + +``` +kn domain COMMAND +``` + +### Options + +``` + -h, --help help for domain +``` + +### Options inherited from parent commands + +``` + --cluster string name of the kubeconfig cluster to use + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --context string name of the kubeconfig context to use + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn](kn.md) - kn manages Knative Serving and Eventing resources +* [kn domain create](kn_domain_create.md) - Create a domain mapping +* [kn domain delete](kn_domain_delete.md) - Delete a domain mapping +* [kn domain describe](kn_domain_describe.md) - Show details of a domain mapping +* [kn domain list](kn_domain_list.md) - List domain mappings +* [kn domain update](kn_domain_update.md) - Update a domain mapping + diff --git a/docs/cmd/kn_domain_create.md b/docs/cmd/kn_domain_create.md new file mode 100644 index 0000000000..c37085ab6e --- /dev/null +++ b/docs/cmd/kn_domain_create.md @@ -0,0 +1,38 @@ +## kn domain create + +Create a domain mapping + +``` +kn domain create FQDN +``` + +### Examples + +``` + + # Create a domain mappings 'hello.example.com' for Knative service 'hello' + kn domain create hello.example.com --ref hello +``` + +### Options + +``` + -h, --help help for create + -n, --namespace string Specify the namespace to operate in. + --ref string Addressable target reference for Domain Mapping. You can specify a Knative Service name. +``` + +### Options inherited from parent commands + +``` + --cluster string name of the kubeconfig cluster to use + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --context string name of the kubeconfig context to use + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn domain](kn_domain.md) - Manage domain mappings + diff --git a/docs/cmd/kn_domain_delete.md b/docs/cmd/kn_domain_delete.md new file mode 100644 index 0000000000..d96faa0dff --- /dev/null +++ b/docs/cmd/kn_domain_delete.md @@ -0,0 +1,37 @@ +## kn domain delete + +Delete a domain mapping + +``` +kn domain delete FQDN +``` + +### Examples + +``` + + # Delete domain mappings 'hello.example.com' + kn domain delete hello.example.com +``` + +### Options + +``` + -h, --help help for delete + -n, --namespace string Specify the namespace to operate in. +``` + +### Options inherited from parent commands + +``` + --cluster string name of the kubeconfig cluster to use + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --context string name of the kubeconfig context to use + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn domain](kn_domain.md) - Manage domain mappings + diff --git a/docs/cmd/kn_domain_describe.md b/docs/cmd/kn_domain_describe.md new file mode 100644 index 0000000000..365c7b8785 --- /dev/null +++ b/docs/cmd/kn_domain_describe.md @@ -0,0 +1,33 @@ +## kn domain describe + +Show details of a domain mapping + +``` +kn domain describe FQDN +``` + +### Options + +``` + --allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true) + -h, --help help for describe + -n, --namespace string Specify the namespace to operate in. + -o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-as-json|jsonpath-file|url. + --template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. + -v, --verbose More output. +``` + +### Options inherited from parent commands + +``` + --cluster string name of the kubeconfig cluster to use + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --context string name of the kubeconfig context to use + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn domain](kn_domain.md) - Manage domain mappings + diff --git a/docs/cmd/kn_domain_list.md b/docs/cmd/kn_domain_list.md new file mode 100644 index 0000000000..b256a562d7 --- /dev/null +++ b/docs/cmd/kn_domain_list.md @@ -0,0 +1,45 @@ +## kn domain list + +List domain mappings + +``` +kn domain list +``` + +### Examples + +``` + + # List all domain mappings + kn domain list + + # List all domain mappings in JSON output format + kn revision list -o json +``` + +### Options + +``` + -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. + --allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true) + -h, --help help for list + -n, --namespace string Specify the namespace to operate in. + --no-headers When using the default output format, don't print headers (default: print headers). + -o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-as-json|jsonpath-file. + --template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. +``` + +### Options inherited from parent commands + +``` + --cluster string name of the kubeconfig cluster to use + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --context string name of the kubeconfig context to use + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn domain](kn_domain.md) - Manage domain mappings + diff --git a/docs/cmd/kn_domain_update.md b/docs/cmd/kn_domain_update.md new file mode 100644 index 0000000000..945a0d31f0 --- /dev/null +++ b/docs/cmd/kn_domain_update.md @@ -0,0 +1,38 @@ +## kn domain update + +Update a domain mapping + +``` +kn domain update FQDN +``` + +### Examples + +``` + + # Update a domain mappings 'hello.example.com' for Knative service 'hello' + kn domain create hello.example.com --ref hello +``` + +### Options + +``` + -h, --help help for update + -n, --namespace string Specify the namespace to operate in. + --ref string Addressable target reference for Domain Mapping. You can specify a Knative Service name. +``` + +### Options inherited from parent commands + +``` + --cluster string name of the kubeconfig cluster to use + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --context string name of the kubeconfig context to use + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn domain](kn_domain.md) - Manage domain mappings + diff --git a/pkg/kn/commands/domain/create.go b/pkg/kn/commands/domain/create.go new file mode 100644 index 0000000000..0ad43abc18 --- /dev/null +++ b/pkg/kn/commands/domain/create.go @@ -0,0 +1,79 @@ +// 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 domain + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + knerrors "knative.dev/client/pkg/errors" + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/kn/commands/flags" + clientv1alpha1 "knative.dev/client/pkg/serving/v1alpha1" +) + +// NewDomainMappingCreateCommand to create event channels +func NewDomainMappingCreateCommand(p *commands.KnParams) *cobra.Command { + var refFlags flags.SinkFlags + cmd := &cobra.Command{ + Use: "create FQDN", + Short: "Create a domain mapping", + Example: ` + # Create a domain mappings 'hello.example.com' for Knative service 'hello' + kn domain create hello.example.com --ref hello`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return errors.New("'kn domain create' requires the domain name given as single argument") + } + name := args[0] + namespace, err := p.GetNamespace(cmd) + if err != nil { + return err + } + + dynamicClient, err := p.NewDynamicClient(namespace) + if err != nil { + return err + } + destination, err := refFlags.ResolveSink(dynamicClient, namespace) + if err != nil { + return err + } + + builder := clientv1alpha1.NewDomainMappingBuilder(name). + Namespace(namespace). + Reference(*destination.Ref) + + client, err := p.NewServingV1alpha1Client(namespace) + if err != nil { + return err + } + err = client.CreateDomainMapping(builder.Build()) + if err != nil { + return knerrors.GetError(err) + } + + fmt.Fprintf(cmd.OutOrStdout(), "Domain mapping '%s' created in namespace '%s'.\n", name, namespace) + return nil + }, + } + commands.AddNamespaceFlags(cmd.Flags(), false) + refFlags.AddWithFlagName(cmd, "ref", "") + cmd.Flag("ref").Usage = "Addressable target reference for Domain Mapping. You can specify a Knative Service name." + cmd.MarkFlagRequired("ref") + return cmd +} diff --git a/pkg/kn/commands/domain/create_test.go b/pkg/kn/commands/domain/create_test.go new file mode 100644 index 0000000000..95e13dc010 --- /dev/null +++ b/pkg/kn/commands/domain/create_test.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 domain + +import ( + "testing" + + "gotest.tools/v3/assert" + + dynamicfake "knative.dev/client/pkg/dynamic/fake" + "knative.dev/client/pkg/serving/v1alpha1" + "knative.dev/client/pkg/util" +) + +func TestDomainMappingCreate(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient(client.Namespace(), createService("foo")) + + servingRecorder := client.Recorder() + servingRecorder.CreateDomainMapping(createDomainMapping("foo.bar", createServiceRef("foo", "default")), nil) + + out, err := executeDomainCommand(client, dynamicClient, "create", "foo.bar", "--ref", "foo") + assert.NilError(t, err, "Domain mapping should be created") + assert.Assert(t, util.ContainsAll(out, "Domain", "mapping", "foo.bar", "created", "namespace", "default")) + + servingRecorder.Validate() +} +func TestDomainMappingCreateWithError(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient(client.Namespace(), createService("foo")) + + // No call should be recorded + servingRecorder := client.Recorder() + + _, err := executeDomainCommand(client, dynamicClient, "create", "--ref", "foo") + assert.ErrorContains(t, err, "domain create") + assert.Assert(t, util.ContainsAll(err.Error(), "domain create", "requires", "name", "argument")) + + _, err = executeDomainCommand(client, dynamicClient, "create", "bar") + assert.ErrorContains(t, err, "required flag") + assert.Assert(t, util.ContainsAll(err.Error(), "required", "flag", "not", "set")) + + _, err = executeDomainCommand(client, dynamicClient, "create", "foo.bar", "--ref", "bar") + assert.ErrorContains(t, err, "not found") + assert.Assert(t, util.ContainsAll(err.Error(), "services", "\"bar\"", "not", "found")) + + servingRecorder.Validate() +} diff --git a/pkg/kn/commands/domain/delete.go b/pkg/kn/commands/domain/delete.go new file mode 100644 index 0000000000..6a83c10138 --- /dev/null +++ b/pkg/kn/commands/domain/delete.go @@ -0,0 +1,61 @@ +// 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 domain + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + knerrors "knative.dev/client/pkg/errors" + "knative.dev/client/pkg/kn/commands" +) + +// NewDomainMappingDeleteCommand to create event channels +func NewDomainMappingDeleteCommand(p *commands.KnParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "delete FQDN", + Short: "Delete a domain mapping", + Example: ` + # Delete domain mappings 'hello.example.com' + kn domain delete hello.example.com`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return errors.New("'kn domain delete' requires the domain name given as single argument") + } + name := args[0] + namespace, err := p.GetNamespace(cmd) + if err != nil { + return err + } + + client, err := p.NewServingV1alpha1Client(namespace) + if err != nil { + return err + } + + err = client.DeleteDomainMapping(name) + if err != nil { + return knerrors.GetError(err) + } + + fmt.Fprintf(cmd.OutOrStdout(), "Domain mapping '%s' deleted in namespace '%s'.\n", name, namespace) + return nil + }, + } + commands.AddNamespaceFlags(cmd.Flags(), false) + return cmd +} diff --git a/pkg/kn/commands/domain/delete_test.go b/pkg/kn/commands/domain/delete_test.go new file mode 100644 index 0000000000..345c6ef5f8 --- /dev/null +++ b/pkg/kn/commands/domain/delete_test.go @@ -0,0 +1,51 @@ +// 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 domain + +import ( + "testing" + + "gotest.tools/v3/assert" + + dynamicfake "knative.dev/client/pkg/dynamic/fake" + "knative.dev/client/pkg/serving/v1alpha1" + "knative.dev/client/pkg/util" +) + +func TestDomainMappingDelete(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + servingRecorder := client.Recorder() + servingRecorder.DeleteDomainMapping("foo.bar", nil) + + out, err := executeDomainCommand(client, nil, "delete", "foo.bar") + assert.NilError(t, err, "Domain mapping should be deleted") + assert.Assert(t, util.ContainsAll(out, "Domain", "mapping", "foo.bar", "deleted", "namespace", "default")) + + servingRecorder.Validate() +} +func TestDomainMappingDeleteWithError(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient(client.Namespace(), createService("foo")) + + // No call should be recorded + servingRecorder := client.Recorder() + + _, err := executeDomainCommand(client, dynamicClient, "delete") + assert.ErrorContains(t, err, "domain delete") + assert.Assert(t, util.ContainsAll(err.Error(), "domain delete", "requires", "name", "argument")) + + servingRecorder.Validate() +} diff --git a/pkg/kn/commands/domain/describe.go b/pkg/kn/commands/domain/describe.go new file mode 100644 index 0000000000..42bac45da5 --- /dev/null +++ b/pkg/kn/commands/domain/describe.go @@ -0,0 +1,95 @@ +// 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 domain + +import ( + "errors" + "fmt" + "io" + "strings" + + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" + + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/printers" + "knative.dev/serving/pkg/apis/serving/v1alpha1" +) + +// NewDomainMappingDescribe represents 'kn route describe' command +func NewDomainMappingDescribe(p *commands.KnParams) *cobra.Command { + // For machine readable output + machineReadablePrintFlags := genericclioptions.NewPrintFlags("") + cmd := &cobra.Command{ + Use: "describe FQDN", + Short: "Show details of a domain mapping", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("'kn domain describe' requires name of the domain mapping as single argument") + } + namespace, err := p.GetNamespace(cmd) + if err != nil { + return err + } + + client, err := p.NewServingV1alpha1Client(namespace) + if err != nil { + return err + } + + domainMapping, err := client.GetDomainMapping(args[0]) + if err != nil { + return err + } + + if machineReadablePrintFlags.OutputFlagSpecified() { + if strings.ToLower(*machineReadablePrintFlags.OutputFormat) == "url" { + fmt.Fprintf(cmd.OutOrStdout(), "%s\n", domainMapping.Status.URL) + return nil + } + printer, err := machineReadablePrintFlags.ToPrinter() + if err != nil { + return err + } + return printer.PrintObj(domainMapping, cmd.OutOrStdout()) + } + printDetails, err := cmd.Flags().GetBool("verbose") + if err != nil { + return err + } + return describe(cmd.OutOrStdout(), domainMapping, printDetails) + }, + } + flags := cmd.Flags() + commands.AddNamespaceFlags(flags, false) + machineReadablePrintFlags.AddFlags(cmd) + cmd.Flag("output").Usage = fmt.Sprintf("Output format. One of: %s.", strings.Join(append(machineReadablePrintFlags.AllowedFormats(), "url"), "|")) + flags.BoolP("verbose", "v", false, "More output.") + return cmd +} + +func describe(w io.Writer, domainMapping *v1alpha1.DomainMapping, printDetails bool) error { + dw := printers.NewPrefixWriter(w) + commands.WriteMetadata(dw, &domainMapping.ObjectMeta, printDetails) + dw.WriteLine() + dw.WriteAttribute("URL", domainMapping.Status.URL.String()) + dw.WriteAttribute("Service", domainMapping.Spec.Ref.Name) + dw.WriteLine() + commands.WriteConditions(dw, domainMapping.Status.Conditions, printDetails) + if err := dw.Flush(); err != nil { + return err + } + return nil +} diff --git a/pkg/kn/commands/domain/describe_test.go b/pkg/kn/commands/domain/describe_test.go new file mode 100644 index 0000000000..6bce321aa9 --- /dev/null +++ b/pkg/kn/commands/domain/describe_test.go @@ -0,0 +1,115 @@ +// 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 domain + +import ( + "errors" + "strings" + "testing" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "gotest.tools/v3/assert" + "gotest.tools/v3/assert/cmp" + + "knative.dev/client/pkg/serving/v1alpha1" + "knative.dev/client/pkg/util" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + servingv1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" +) + +func TestDomainMappingDescribe(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + servingRecorder := client.Recorder() + servingRecorder.GetDomainMapping("foo.bar", getDomainMapping(), nil) + + out, err := executeDomainCommand(client, nil, "describe", "foo.bar") + assert.NilError(t, err) + assert.Assert(t, cmp.Regexp("Name:\\s+foo.bar", out)) + assert.Assert(t, cmp.Regexp("Namespace:\\s+default", out)) + assert.Assert(t, util.ContainsAll(out, "URL:", "http://foo.bar")) + assert.Assert(t, util.ContainsAll(out, "Conditions:", "Ready")) + + // There're 2 empty lines used in the "describe" formatting + lineCounter := 0 + for _, line := range strings.Split(strings.TrimSpace(out), "\n") { + if line == "" { + lineCounter++ + } + } + assert.Equal(t, lineCounter, 2) + + servingRecorder.Validate() +} + +func TestDomainMappingDescribeError(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + servingRecorder := client.Recorder() + servingRecorder.GetDomainMapping("foo.bar", getDomainMapping(), errors.New("domainmappings.serving.knative.dev 'foo.bar' not found")) + + _, err := executeDomainCommand(client, nil, "describe", "foo.bar") + assert.ErrorContains(t, err, "foo", "not found") + + servingRecorder.Validate() +} + +func TestDomainMappingDescribeURL(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + servingRecorder := client.Recorder() + servingRecorder.GetDomainMapping("foo.bar", getDomainMapping(), nil) + + out, err := executeDomainCommand(client, nil, "describe", "foo.bar", "-o", "url") + assert.NilError(t, err) + assert.Assert(t, util.ContainsAll(out, "http://foo.bar")) + + servingRecorder.Validate() +} + +func TestDomainMappingDescribeYAML(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + servingRecorder := client.Recorder() + servingRecorder.GetDomainMapping("foo.bar", getDomainMapping(), nil) + + out, err := executeDomainCommand(client, nil, "describe", "foo.bar", "-o", "yaml") + assert.NilError(t, err) + assert.Assert(t, util.ContainsAll(out, "kind: DomainMapping", "spec:", "status:", "metadata:")) + + servingRecorder.Validate() +} + +func getDomainMapping() *servingv1alpha1.DomainMapping { + dm := createDomainMapping("foo.bar", createServiceRef("foo", "default")) + dm.TypeMeta = v1.TypeMeta{ + Kind: "DomainMapping", + APIVersion: "serving.knative.dev/v1alpha1", + } + dm.Status = servingv1alpha1.DomainMappingStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + apis.Condition{ + Type: "Ready", + Status: "True", + }, + }, + }, + URL: &apis.URL{Scheme: "http", Host: "foo.bar"}, + } + return dm +} diff --git a/pkg/kn/commands/domain/domain.go b/pkg/kn/commands/domain/domain.go new file mode 100644 index 0000000000..81acbde627 --- /dev/null +++ b/pkg/kn/commands/domain/domain.go @@ -0,0 +1,36 @@ +// 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 domain + +import ( + "github.com/spf13/cobra" + + "knative.dev/client/pkg/kn/commands" +) + +// NewDomainCommand to manage domain mappings +func NewDomainCommand(p *commands.KnParams) *cobra.Command { + domainCmd := &cobra.Command{ + Use: "domain COMMAND", + Short: "Manage domain mappings", + Aliases: []string{"domains"}, + } + domainCmd.AddCommand(NewDomainMappingCreateCommand(p)) + domainCmd.AddCommand(NewDomainMappingDescribe(p)) + domainCmd.AddCommand(NewDomainMappingUpdateCommand(p)) + domainCmd.AddCommand(NewDomainMappingDeleteCommand(p)) + domainCmd.AddCommand(NewDomainMappingListCommand(p)) + return domainCmd +} diff --git a/pkg/kn/commands/domain/domain_test.go b/pkg/kn/commands/domain/domain_test.go new file mode 100644 index 0000000000..2505692538 --- /dev/null +++ b/pkg/kn/commands/domain/domain_test.go @@ -0,0 +1,99 @@ +// 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 domain + +import ( + "bytes" + + "github.com/spf13/cobra" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" + + kndynamic "knative.dev/client/pkg/dynamic" + "knative.dev/client/pkg/kn/commands" + knflags "knative.dev/client/pkg/kn/flags" + clientservingv1alpha1 "knative.dev/client/pkg/serving/v1alpha1" + v1 "knative.dev/pkg/apis/duck/v1" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" + servingv1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" +) + +// Helper methods +var blankConfig clientcmd.ClientConfig + +func init() { + var err error + blankConfig, err = clientcmd.NewClientConfigFromBytes([]byte(`kind: Config +version: v1 +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) + } +} + +func executeDomainCommand(client clientservingv1alpha1.KnServingClient, dynamicClient kndynamic.KnDynamicClient, args ...string) (string, error) { + knParams := &commands.KnParams{} + knParams.ClientConfig = blankConfig + + output := new(bytes.Buffer) + knParams.Output = output + knParams.NewServingV1alpha1Client = func(namespace string) (clientservingv1alpha1.KnServingClient, error) { + return client, nil + } + knParams.NewDynamicClient = func(namespace string) (kndynamic.KnDynamicClient, error) { + return dynamicClient, nil + } + + cmd := NewDomainCommand(knParams) + cmd.SetArgs(args) + cmd.SetOut(output) + + cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + return knflags.ReconcileBoolFlags(cmd.Flags()) + } + err := cmd.Execute() + return output.String(), err +} + +func createService(name string) *servingv1.Service { + return &servingv1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default"}, + } +} + +func createDomainMapping(name string, ref v1.KReference) *servingv1alpha1.DomainMapping { + return clientservingv1alpha1.NewDomainMappingBuilder(name).Namespace("default").Reference(ref).Build() +} + +func createServiceRef(service, namespace string) v1.KReference { + return v1.KReference{Name: service, + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + Namespace: namespace, + } +} diff --git a/pkg/kn/commands/domain/human_readable_flags.go b/pkg/kn/commands/domain/human_readable_flags.go new file mode 100644 index 0000000000..3747043ad5 --- /dev/null +++ b/pkg/kn/commands/domain/human_readable_flags.go @@ -0,0 +1,72 @@ +// 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 domain + +import ( + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/serving/pkg/apis/serving/v1alpha1" + + "knative.dev/client/pkg/kn/commands" + hprinters "knative.dev/client/pkg/printers" +) + +// DomainMappingListHandlers adds print handlers for route list command +func DomainMappingListHandlers(h hprinters.PrintHandler) { + dmColumnDefinitions := []metav1beta1.TableColumnDefinition{ + {Name: "Namespace", Type: "string", Description: "Namespace of the Knative service", Priority: 0}, + {Name: "Name", Type: "string", Description: "Name of the Knative domain mapping.", Priority: 1}, + {Name: "URL", Type: "string", Description: "URL of the Knative domain mapping.", Priority: 1}, + {Name: "Ready", Type: "string", Description: "Ready condition status of the Knative domain mapping.", Priority: 1}, + {Name: "Ksvc", Type: "string", Description: "Name of the referenced Knative service", Priority: 1}, + } + h.TableHandler(dmColumnDefinitions, printDomainMapping) + h.TableHandler(dmColumnDefinitions, printDomainMappingList) +} + +// printDomainMappingList populates the Knative domain mapping list table rows +func printDomainMappingList(domainMappingList *v1alpha1.DomainMappingList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) { + rows := make([]metav1beta1.TableRow, 0, len(domainMappingList.Items)) + for i := range domainMappingList.Items { + dm := &domainMappingList.Items[i] + r, err := printDomainMapping(dm, options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + } + return rows, nil +} + +// printDomainMapping populates the Knative domain mapping table rows +func printDomainMapping(domainMapping *v1alpha1.DomainMapping, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) { + name := domainMapping.Name + url := domainMapping.Status.URL + ready := commands.ReadyCondition(domainMapping.Status.Conditions) + ksvc := domainMapping.Spec.Ref.Name + row := metav1beta1.TableRow{ + Object: runtime.RawExtension{Object: domainMapping}, + } + if options.AllNamespaces { + row.Cells = append(row.Cells, domainMapping.Namespace) + } + + row.Cells = append(row.Cells, + name, + url, + ready, + ksvc) + return []metav1beta1.TableRow{row}, nil +} diff --git a/pkg/kn/commands/domain/list.go b/pkg/kn/commands/domain/list.go new file mode 100644 index 0000000000..204b42c896 --- /dev/null +++ b/pkg/kn/commands/domain/list.go @@ -0,0 +1,65 @@ +// 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 domain + +import ( + "fmt" + + "github.com/spf13/cobra" + + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/kn/commands/flags" +) + +// NewDomainMappingListCommand represents 'kn revision list' command +func NewDomainMappingListCommand(p *commands.KnParams) *cobra.Command { + listFlags := flags.NewListPrintFlags(DomainMappingListHandlers) + cmd := &cobra.Command{ + Use: "list", + Short: "List domain mappings", + Aliases: []string{"ls"}, + Example: ` + # List all domain mappings + kn domain list + + # List all domain mappings in JSON output format + kn revision list -o json`, + RunE: func(cmd *cobra.Command, args []string) error { + namespace, err := p.GetNamespace(cmd) + if err != nil { + return err + } + client, err := p.NewServingV1alpha1Client(namespace) + if err != nil { + return err + } + domainMappingList, err := client.ListDomainMappings() + if err != nil { + return err + } + if len(domainMappingList.Items) == 0 { + fmt.Fprintf(cmd.OutOrStdout(), "No domain mapping found.\n") + return nil + } + if namespace == "" { + listFlags.EnsureWithNamespace() + } + return listFlags.Print(domainMappingList, cmd.OutOrStdout()) + }, + } + commands.AddNamespaceFlags(cmd.Flags(), true) + listFlags.AddFlags(cmd) + return cmd +} diff --git a/pkg/kn/commands/domain/list_test.go b/pkg/kn/commands/domain/list_test.go new file mode 100644 index 0000000000..574b693ba8 --- /dev/null +++ b/pkg/kn/commands/domain/list_test.go @@ -0,0 +1,58 @@ +// 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 domain + +import ( + "strings" + "testing" + + "gotest.tools/v3/assert" + + "knative.dev/client/pkg/serving/v1alpha1" + "knative.dev/client/pkg/util" + servingv1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" +) + +func TestDomainMappingList(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + dm1 := createDomainMapping("foo1", createServiceRef("foo1", "default")) + dm2 := createDomainMapping("foo2", createServiceRef("foo2", "default")) + servingRecorder := client.Recorder() + servingRecorder.ListDomainMappings(&servingv1alpha1.DomainMappingList{Items: []servingv1alpha1.DomainMapping{*dm1, *dm2}}, nil) + + out, err := executeDomainCommand(client, nil, "list") + assert.NilError(t, err, "Domain mapping should be listed") + + outputLines := strings.Split(out, "\n") + assert.Check(t, util.ContainsAll(outputLines[0], "NAME", "URL", "READY", "KSVC")) + assert.Check(t, util.ContainsAll(outputLines[1], "foo1")) + assert.Check(t, util.ContainsAll(outputLines[2], "foo2")) + + servingRecorder.Validate() +} + +func TestDomainMappingListEmpty(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + servingRecorder := client.Recorder() + servingRecorder.ListDomainMappings(&servingv1alpha1.DomainMappingList{}, nil) + + out, err := executeDomainCommand(client, nil, "list") + assert.NilError(t, err) + assert.Assert(t, util.ContainsAll(out, "No", "domain", "mapping", "found")) + + servingRecorder.Validate() +} diff --git a/pkg/kn/commands/domain/update.go b/pkg/kn/commands/domain/update.go new file mode 100644 index 0000000000..bbbbd23b3d --- /dev/null +++ b/pkg/kn/commands/domain/update.go @@ -0,0 +1,82 @@ +// 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 domain + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + knerrors "knative.dev/client/pkg/errors" + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/kn/commands/flags" +) + +// NewDomainMappingUpdateCommand to create event channels +func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command { + var refFlags flags.SinkFlags + cmd := &cobra.Command{ + Use: "update FQDN", + Short: "Update a domain mapping", + Example: ` + # Update a domain mappings 'hello.example.com' for Knative service 'hello' + kn domain create hello.example.com --ref hello`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return errors.New("'kn domain create' requires the domain name given as single argument") + } + name := args[0] + namespace, err := p.GetNamespace(cmd) + if err != nil { + return err + } + client, err := p.NewServingV1alpha1Client(namespace) + if err != nil { + return err + } + toUpdate, err := client.GetDomainMapping(name) + if err != nil { + return err + } + if toUpdate.GetDeletionTimestamp() != nil { + return fmt.Errorf("can't update domain mapping '%s' because it has been marked for deletion", name) + } + + dynamicClient, err := p.NewDynamicClient(namespace) + if err != nil { + return err + } + destination, err := refFlags.ResolveSink(dynamicClient, namespace) + if err != nil { + return err + } + toUpdate.Spec.Ref = *destination.Ref + + err = client.UpdateDomainMapping(toUpdate) + if err != nil { + return knerrors.GetError(err) + } + + fmt.Fprintf(cmd.OutOrStdout(), "Domain mapping '%s' updated in namespace '%s'.\n", name, namespace) + return nil + }, + } + commands.AddNamespaceFlags(cmd.Flags(), false) + refFlags.AddWithFlagName(cmd, "ref", "") + cmd.Flag("ref").Usage = "Addressable target reference for Domain Mapping. You can specify a Knative Service name." + cmd.MarkFlagRequired("ref") + return cmd +} diff --git a/pkg/kn/commands/domain/update_test.go b/pkg/kn/commands/domain/update_test.go new file mode 100644 index 0000000000..f54749a3fe --- /dev/null +++ b/pkg/kn/commands/domain/update_test.go @@ -0,0 +1,73 @@ +// 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 domain + +import ( + "errors" + "testing" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "gotest.tools/v3/assert" + + dynamicfake "knative.dev/client/pkg/dynamic/fake" + "knative.dev/client/pkg/serving/v1alpha1" + "knative.dev/client/pkg/util" +) + +func TestDomainMappingUpdate(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient(client.Namespace(), createService("foo"), createService("bar")) + + servingRecorder := client.Recorder() + servingRecorder.GetDomainMapping("foo.bar", createDomainMapping("foo.bar", createServiceRef("foo", "default")), nil) + servingRecorder.UpdateDomainMapping(createDomainMapping("foo.bar", createServiceRef("bar", "default")), nil) + + out, err := executeDomainCommand(client, dynamicClient, "update", "foo.bar", "--ref", "bar") + assert.NilError(t, err, "Domain mapping should be updated") + assert.Assert(t, util.ContainsAll(out, "Domain", "mapping", "foo.bar", "updated", "namespace", "default")) + + servingRecorder.Validate() +} + +func TestDomainMappingUpdateNotFound(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + servingRecorder := client.Recorder() + servingRecorder.GetDomainMapping("foo.bar", nil, errors.New("domainmappings.serving.knative.dev \"foo.bar\" not found")) + + _, err := executeDomainCommand(client, nil, "update", "foo.bar", "--ref", "bar") + assert.ErrorContains(t, err, "not found") + assert.Assert(t, util.ContainsAll(err.Error(), "domainmappings.serving.knative.dev", "\"foo.bar\"", "not", "found")) + + servingRecorder.Validate() +} + +func TestDomainMappingUpdateDeletingError(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + deletingDM := createDomainMapping("foo.bar", createServiceRef("foo", "default")) + deletingDM.DeletionTimestamp = &v1.Time{Time: time.Now()} + + servingRecorder := client.Recorder() + servingRecorder.GetDomainMapping("foo.bar", deletingDM, nil) + + _, err := executeDomainCommand(client, nil, "update", "foo.bar", "--ref", "bar") + assert.ErrorContains(t, err, "deletion") + assert.Assert(t, util.ContainsAll(err.Error(), "can't", "update", "domain", "mapping", "foo.bar", "marked", "deletion")) + + servingRecorder.Validate() +} diff --git a/pkg/kn/commands/types.go b/pkg/kn/commands/types.go index 524c521efc..9af0a19c35 100644 --- a/pkg/kn/commands/types.go +++ b/pkg/kn/commands/types.go @@ -27,6 +27,7 @@ import ( messagingv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1" sourcesv1alpha2client "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2" servingv1client "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1" + servingv1alpha1client "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" "knative.dev/client/pkg/sources/v1alpha2" "knative.dev/client/pkg/util" @@ -36,21 +37,23 @@ import ( clienteventingv1beta1 "knative.dev/client/pkg/eventing/v1beta1" clientmessagingv1beta1 "knative.dev/client/pkg/messaging/v1beta1" clientservingv1 "knative.dev/client/pkg/serving/v1" + clientservingv1alpha1 "knative.dev/client/pkg/serving/v1alpha1" ) // KnParams for creating commands. Useful for inserting mocks for testing. type KnParams struct { - Output io.Writer - KubeCfgPath string - KubeContext string - KubeCluster string - ClientConfig clientcmd.ClientConfig - NewServingClient func(namespace string) (clientservingv1.KnServingClient, error) - NewGitopsServingClient func(namespace string, dir string) (clientservingv1.KnServingClient, error) - NewSourcesClient func(namespace string) (v1alpha2.KnSourcesClient, error) - NewEventingClient func(namespace string) (clienteventingv1beta1.KnEventingClient, error) - NewMessagingClient func(namespace string) (clientmessagingv1beta1.KnMessagingClient, error) - NewDynamicClient func(namespace string) (clientdynamic.KnDynamicClient, error) + Output io.Writer + KubeCfgPath string + KubeContext string + KubeCluster string + ClientConfig clientcmd.ClientConfig + NewServingClient func(namespace string) (clientservingv1.KnServingClient, error) + NewServingV1alpha1Client func(namespace string) (clientservingv1alpha1.KnServingClient, error) + NewGitopsServingClient func(namespace string, dir string) (clientservingv1.KnServingClient, error) + NewSourcesClient func(namespace string) (v1alpha2.KnSourcesClient, error) + NewEventingClient func(namespace string) (clienteventingv1beta1.KnEventingClient, error) + NewMessagingClient func(namespace string) (clientmessagingv1beta1.KnMessagingClient, error) + NewDynamicClient func(namespace string) (clientdynamic.KnDynamicClient, error) // General global options LogHTTP bool @@ -64,6 +67,10 @@ func (params *KnParams) Initialize() { params.NewServingClient = params.newServingClient } + if params.NewServingV1alpha1Client == nil { + params.NewServingV1alpha1Client = params.newServingClientV1alpha1 + } + if params.NewGitopsServingClient == nil { params.NewGitopsServingClient = params.newGitopsServingClient } @@ -98,6 +105,19 @@ func (params *KnParams) newServingClient(namespace string) (clientservingv1.KnSe return clientservingv1.NewKnServingClient(client, namespace), nil } +func (params *KnParams) newServingClientV1alpha1(namespace string) (clientservingv1alpha1.KnServingClient, error) { + restConfig, err := params.RestConfig() + if err != nil { + return nil, err + } + + client, err := servingv1alpha1client.NewForConfig(restConfig) + if err != nil { + return nil, err + } + return clientservingv1alpha1.NewKnServingClient(client, namespace), nil +} + func (params *KnParams) newGitopsServingClient(namespace string, dir string) (clientservingv1.KnServingClient, error) { return clientservingv1.NewKnServingGitOpsClient(namespace, dir), nil } diff --git a/pkg/kn/commands/types_test.go b/pkg/kn/commands/types_test.go index 2c725e16bf..90ff507caa 100644 --- a/pkg/kn/commands/types_test.go +++ b/pkg/kn/commands/types_test.go @@ -208,6 +208,56 @@ func TestNewSourcesClient(t *testing.T) { } } +func TestNewServingV1alpha1Clients(t *testing.T) { + basic, err := clientcmd.NewClientConfigFromBytes([]byte(BASIC_KUBECONFIG)) + namespace := "test" + if err != nil { + t.Error(err) + } + for i, tc := range []configTestCase{ + { + clientcmd.NewDefaultClientConfig(clientcmdapi.Config{}, &clientcmd.ConfigOverrides{}), + "no kubeconfig has been provided, please use a valid configuration to connect to the cluster", + false, + }, + { + basic, + "", + false, + }, + { // Test that the cast to wrap the http client in a logger works + basic, + "", + true, + }, + } { + p := &KnParams{ + ClientConfig: tc.clientConfig, + LogHTTP: tc.logHttp, + } + + servingV1alpha1Client, err := p.newServingClientV1alpha1(namespace) + + switch len(tc.expectedErrString) { + case 0: + if err != nil { + t.Errorf("%d: unexpected error: %s", i, err.Error()) + } + default: + if err == nil { + t.Errorf("%d: wrong error detected: %s (expected) != %s (actual)", i, tc.expectedErrString, err) + } + if !strings.Contains(err.Error(), tc.expectedErrString) { + t.Errorf("%d: wrong error detected: %s (expected) != %s (actual)", i, tc.expectedErrString, err.Error()) + } + } + + if servingV1alpha1Client != nil { + assert.Assert(t, servingV1alpha1Client.Namespace() == namespace) + } + } +} + func TestNewDynamicClient(t *testing.T) { basic, err := clientcmd.NewClientConfigFromBytes([]byte(BASIC_KUBECONFIG)) namespace := "test" diff --git a/pkg/kn/root/root.go b/pkg/kn/root/root.go index 4d8a59f68b..65780c4349 100644 --- a/pkg/kn/root/root.go +++ b/pkg/kn/root/root.go @@ -20,6 +20,8 @@ import ( "strings" "text/template" + "knative.dev/client/pkg/kn/commands/domain" + "github.com/spf13/cobra" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" @@ -88,6 +90,7 @@ func NewRootCommand(helpFuncs *template.FuncMap) (*cobra.Command, error) { service.NewServiceCommand(p), revision.NewRevisionCommand(p), route.NewRouteCommand(p), + domain.NewDomainCommand(p), }, }, { diff --git a/pkg/serving/v1alpha1/client.go b/pkg/serving/v1alpha1/client.go new file mode 100644 index 0000000000..c6b17377aa --- /dev/null +++ b/pkg/serving/v1alpha1/client.go @@ -0,0 +1,160 @@ +// 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 v1alpha1 + +import ( + "context" + + duckv1 "knative.dev/pkg/apis/duck/v1" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + knerrors "knative.dev/client/pkg/errors" + "knative.dev/client/pkg/util" + servingv1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" + "knative.dev/serving/pkg/client/clientset/versioned/scheme" + clientv1alpha1 "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" +) + +// KnServingClient to work with Serving v1alpha1 resources +type KnServingClient interface { + // Namespace in which this client is operating for + Namespace() string + + // GetDomainMapping + GetDomainMapping(name string) (*servingv1alpha1.DomainMapping, error) + + // CreateDomainMapping + CreateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error + + // UpdateDomainMapping + UpdateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error + + // DeleteDomainMapping + DeleteDomainMapping(name string) error + + // ListDomainMappings + ListDomainMappings() (*servingv1alpha1.DomainMappingList, error) +} + +type knServingClient struct { + client clientv1alpha1.ServingV1alpha1Interface + namespace string +} + +// NewKnServingClient create a new client facade for the provided namespace +func NewKnServingClient(client clientv1alpha1.ServingV1alpha1Interface, namespace string) KnServingClient { + return &knServingClient{ + client: client, + namespace: namespace, + } +} + +func (cl *knServingClient) Namespace() string { + return cl.namespace +} + +func (cl *knServingClient) GetDomainMapping(name string) (*servingv1alpha1.DomainMapping, error) { + dm, err := cl.client.DomainMappings(cl.namespace).Get(context.TODO(), name, v1.GetOptions{}) + if err != nil { + return nil, knerrors.GetError(err) + } + err = updateServingGvk(dm) + if err != nil { + return nil, err + } + return dm, nil +} + +func (cl *knServingClient) CreateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { + _, err := cl.client.DomainMappings(cl.namespace).Create(context.TODO(), domainMapping, v1.CreateOptions{}) + if err != nil { + return knerrors.GetError(err) + } + return updateServingGvk(domainMapping) +} + +func (cl *knServingClient) UpdateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { + _, err := cl.client.DomainMappings(cl.namespace).Update(context.TODO(), domainMapping, v1.UpdateOptions{}) + if err != nil { + return knerrors.GetError(err) + } + return updateServingGvk(domainMapping) +} + +func (cl *knServingClient) DeleteDomainMapping(name string) error { + err := cl.client.DomainMappings(cl.namespace).Delete(context.TODO(), name, v1.DeleteOptions{}) + if err != nil { + return knerrors.GetError(err) + } + return nil +} + +func (cl *knServingClient) ListDomainMappings() (*servingv1alpha1.DomainMappingList, error) { + domainMappingList, err := cl.client.DomainMappings(cl.namespace).List(context.TODO(), v1.ListOptions{}) + if err != nil { + return nil, knerrors.GetError(err) + } + dmListNew := domainMappingList.DeepCopy() + err = updateServingGvk(dmListNew) + if err != nil { + return nil, err + } + dmListNew.Items = make([]servingv1alpha1.DomainMapping, len(domainMappingList.Items)) + for idx, domainMapping := range domainMappingList.Items { + domainMappingClone := domainMapping.DeepCopy() + err := updateServingGvk(domainMappingClone) + if err != nil { + return nil, err + } + dmListNew.Items[idx] = *domainMappingClone + } + return dmListNew, nil +} + +func updateServingGvk(obj runtime.Object) error { + return util.UpdateGroupVersionKindWithScheme(obj, servingv1alpha1.SchemeGroupVersion, scheme.Scheme) +} + +// DomainMappingBuilder is for building the domainMapping +type DomainMappingBuilder struct { + domainMapping *servingv1alpha1.DomainMapping +} + +// NewDomainMappingBuilder for building domainMapping object +func NewDomainMappingBuilder(name string) *DomainMappingBuilder { + return &DomainMappingBuilder{domainMapping: &servingv1alpha1.DomainMapping{ + ObjectMeta: v1.ObjectMeta{ + Name: name, + }, + }} +} + +// Namespace for domainMapping builder +func (b *DomainMappingBuilder) Namespace(ns string) *DomainMappingBuilder { + b.domainMapping.Namespace = ns + return b +} + +// Reference for domainMapping builder +func (b *DomainMappingBuilder) Reference(reference duckv1.KReference) *DomainMappingBuilder { + b.domainMapping.Spec.Ref = reference + return b +} + +// Build to return an instance of domainMapping object +func (b *DomainMappingBuilder) Build() *servingv1alpha1.DomainMapping { + return b.domainMapping +} diff --git a/pkg/serving/v1alpha1/client_mock.go b/pkg/serving/v1alpha1/client_mock.go new file mode 100644 index 0000000000..0ae90d5aa5 --- /dev/null +++ b/pkg/serving/v1alpha1/client_mock.go @@ -0,0 +1,115 @@ +// 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 v1alpha1 + +import ( + "testing" + + "knative.dev/client/pkg/util/mock" + servingv1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" +) + +// MockKnServingClient client mock +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)}, + } +} + +// ServingRecorder recorder for service +type ServingRecorder struct { + r *mock.Recorder +} + +// Recorder returns the record instance +func (c *MockKnServingClient) Recorder() *ServingRecorder { + return c.recorder +} + +// Validate checks that every recorded method has been called +func (sr *ServingRecorder) Validate() { + sr.r.CheckThatAllRecordedMethodsHaveBeenCalled() +} + +// Namespace of this client +func (c *MockKnServingClient) Namespace() string { + return c.recorder.r.Namespace() +} + +// GetDomainMapping mock function recorder +func (sr *ServingRecorder) GetDomainMapping(name interface{}, domainMapping *servingv1alpha1.DomainMapping, err error) { + sr.r.Add("GetDomainMapping", []interface{}{name}, []interface{}{domainMapping, err}) +} + +// GetDomainMapping mock function +func (c *MockKnServingClient) GetDomainMapping(name string) (*servingv1alpha1.DomainMapping, error) { + call := c.recorder.r.VerifyCall("GetDomainMapping", name) + return call.Result[0].(*servingv1alpha1.DomainMapping), mock.ErrorOrNil(call.Result[1]) +} + +// CreateDomainMapping recorder function +func (sr *ServingRecorder) CreateDomainMapping(domainMapping interface{}, err error) { + sr.r.Add("CreateDomainMapping", []interface{}{domainMapping}, []interface{}{err}) +} + +// CreateDomainMapping mock function +func (c *MockKnServingClient) CreateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { + call := c.recorder.r.VerifyCall("CreateDomainMapping", domainMapping) + return mock.ErrorOrNil(call.Result[0]) +} + +// UpdateDomainMapping recorder function +func (sr *ServingRecorder) UpdateDomainMapping(domainMapping interface{}, err error) { + sr.r.Add("UpdateDomainMapping", []interface{}{domainMapping}, []interface{}{err}) +} + +// UpdateDomainMapping mock function +func (c *MockKnServingClient) UpdateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { + call := c.recorder.r.VerifyCall("UpdateDomainMapping", domainMapping) + return mock.ErrorOrNil(call.Result[0]) +} + +// DeleteDomainMapping recorder function +func (sr *ServingRecorder) DeleteDomainMapping(name string, err error) { + sr.r.Add("DeleteDomainMapping", []interface{}{name}, []interface{}{err}) +} + +// DeleteDomainMapping mock function +func (c *MockKnServingClient) DeleteDomainMapping(name string) error { + call := c.recorder.r.VerifyCall("DeleteDomainMapping", name) + return mock.ErrorOrNil(call.Result[0]) +} + +// ListDomainMappings recorder function +func (sr *ServingRecorder) ListDomainMappings(domainMappingList *servingv1alpha1.DomainMappingList, err error) { + sr.r.Add("ListDomainMappings", nil, []interface{}{domainMappingList, err}) +} + +// ListDomainMappings mock function +func (c *MockKnServingClient) ListDomainMappings() (*servingv1alpha1.DomainMappingList, error) { + call := c.recorder.r.VerifyCall("ListDomainMappings") + return call.Result[0].(*servingv1alpha1.DomainMappingList), mock.ErrorOrNil(call.Result[1]) +} diff --git a/pkg/serving/v1alpha1/client_mock_test.go b/pkg/serving/v1alpha1/client_mock_test.go new file mode 100644 index 0000000000..2b008f44bb --- /dev/null +++ b/pkg/serving/v1alpha1/client_mock_test.go @@ -0,0 +1,45 @@ +// 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 v1alpha1 + +import ( + "testing" + + "knative.dev/serving/pkg/apis/serving/v1alpha1" +) + +func TestMockKnClient(t *testing.T) { + + client := NewMockKnServiceClient(t) + + recorder := client.Recorder() + + // Record all services + recorder.GetDomainMapping("hello.foo.bar", &v1alpha1.DomainMapping{}, nil) + recorder.CreateDomainMapping(&v1alpha1.DomainMapping{}, nil) + recorder.DeleteDomainMapping("hello.foo.bar", nil) + recorder.UpdateDomainMapping(&v1alpha1.DomainMapping{}, nil) + recorder.ListDomainMappings(&v1alpha1.DomainMappingList{}, nil) + + // Call all services + client.GetDomainMapping("hello.foo.bar") + client.CreateDomainMapping(&v1alpha1.DomainMapping{}) + client.DeleteDomainMapping("hello.foo.bar") + client.UpdateDomainMapping(&v1alpha1.DomainMapping{}) + client.ListDomainMappings() + + // Validate + recorder.Validate() +} diff --git a/pkg/serving/v1alpha1/client_test.go b/pkg/serving/v1alpha1/client_test.go new file mode 100644 index 0000000000..58fa151d9c --- /dev/null +++ b/pkg/serving/v1alpha1/client_test.go @@ -0,0 +1,211 @@ +// 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 v1alpha1 + +import ( + "fmt" + "testing" + + "gotest.tools/v3/assert" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clienttesting "k8s.io/client-go/testing" + + "knative.dev/client/pkg/util" + duckv1 "knative.dev/pkg/apis/duck/v1" + servingv1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" + "knative.dev/serving/pkg/client/clientset/versioned/scheme" + servingv1alpha1fake "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake" +) + +const ( + testNamespace = "test-ns" + domainMappingResource = "domainmappings" +) + +func setup() (serving servingv1alpha1fake.FakeServingV1alpha1, client KnServingClient) { + serving = servingv1alpha1fake.FakeServingV1alpha1{Fake: &clienttesting.Fake{}} + client = NewKnServingClient(&serving, testNamespace) + return +} + +func TestGetDomainMapping(t *testing.T) { + serving, client := setup() + serviceName := "foo" + domainName := "foo.bar" + + serving.AddReactor("get", domainMappingResource, + func(a clienttesting.Action) (bool, runtime.Object, error) { + dm := createDomainMapping(domainName, createServiceRef(serviceName, testNamespace)) + name := a.(clienttesting.GetAction).GetName() + + assert.Assert(t, name != "") + assert.Equal(t, testNamespace, a.GetNamespace()) + if name == domainName { + return true, dm, nil + } + return true, nil, errors.NewNotFound(servingv1alpha1.Resource("dm"), name) + }) + + t.Run("get domain mapping by name returns object", func(t *testing.T) { + domainMapping, err := client.GetDomainMapping(domainName) + assert.NilError(t, err) + assert.Equal(t, domainName, domainMapping.Name, "domain mapping name should be equal") + validateGroupVersionKind(t, domainMapping) + }) + + t.Run("get non-existing domain mapping by name returns error", func(t *testing.T) { + nonExistingName := "does-not-exist" + service, err := client.GetDomainMapping(nonExistingName) + assert.Assert(t, service == nil, "no domain mapping should be returned") + assert.ErrorContains(t, err, "not found") + assert.ErrorContains(t, err, nonExistingName) + }) +} + +func TestCreateDomainMapping(t *testing.T) { + serving, client := setup() + serviceName := "foo" + domainName := "foo.bar" + domainMapping := createDomainMapping(domainName, createServiceRef(serviceName, testNamespace)) + serving.AddReactor("create", domainMappingResource, + func(a clienttesting.Action) (bool, runtime.Object, error) { + assert.Equal(t, testNamespace, a.GetNamespace()) + name := a.(clienttesting.CreateAction).GetObject().(metav1.Object).GetName() + if name == domainMapping.Name { + domainMapping.Generation = 2 + return true, domainMapping, nil + } + return true, nil, fmt.Errorf("error while creating service %s", name) + }) + + t.Run("create domain mapping without error creates a new object", func(t *testing.T) { + err := client.CreateDomainMapping(domainMapping) + assert.NilError(t, err) + assert.Equal(t, domainMapping.Generation, int64(2)) + validateGroupVersionKind(t, domainMapping) + }) + + t.Run("create domain mapping with an error returns an error object", func(t *testing.T) { + err := client.CreateDomainMapping(createDomainMapping("unknown", createServiceRef(serviceName, testNamespace))) + assert.ErrorContains(t, err, "unknown") + }) +} + +func TestUpdateDomainMapping(t *testing.T) { + serving, client := setup() + serviceName := "foo" + domainName := "foo.bar" + domainMappingUpdate := createDomainMapping(domainName, createServiceRef(serviceName, testNamespace)) + domainMappingUpdate.ObjectMeta.Generation = 2 + + serving.AddReactor("update", domainMappingResource, + func(a clienttesting.Action) (bool, runtime.Object, error) { + assert.Equal(t, testNamespace, a.GetNamespace()) + name := a.(clienttesting.UpdateAction).GetObject().(metav1.Object).GetName() + if name == domainMappingUpdate.Name { + dmResult := createDomainMapping(domainName, createServiceRef(serviceName, testNamespace)) + dmResult.Generation = 3 + return true, dmResult, nil + } + return true, nil, fmt.Errorf("error while updating service %s", name) + }) + + t.Run("update domain mapping without error", func(t *testing.T) { + err := client.UpdateDomainMapping(domainMappingUpdate) + assert.NilError(t, err) + validateGroupVersionKind(t, domainMappingUpdate) + }) + + t.Run("update domain mapping with error", func(t *testing.T) { + err := client.UpdateDomainMapping(createDomainMapping("unknown", createServiceRef(serviceName, testNamespace))) + assert.ErrorContains(t, err, "unknown") + }) +} + +func TestDeleteDomainMapping(t *testing.T) { + serving, client := setup() + domainName := "foo.bar" + + serving.AddReactor("delete", domainMappingResource, + func(a clienttesting.Action) (bool, runtime.Object, error) { + name := a.(clienttesting.DeleteAction).GetName() + assert.Assert(t, name != "") + assert.Equal(t, testNamespace, a.GetNamespace()) + if name == domainName { + return true, nil, nil + } + return true, nil, errors.NewNotFound(servingv1alpha1.Resource(domainMappingResource), name) + }) + + t.Run("delete domain mapping returns no error", func(t *testing.T) { + err := client.DeleteDomainMapping(domainName) + assert.NilError(t, err) + }) + + t.Run("delete non-existing domain mapping returns error", func(t *testing.T) { + nonExistingName := "does-not-exist" + err := client.DeleteDomainMapping(nonExistingName) + assert.ErrorContains(t, err, "not found") + assert.ErrorContains(t, err, nonExistingName) + assert.ErrorType(t, err, &errors.StatusError{}) + }) +} + +func TestListDomainMappings(t *testing.T) { + serving, client := setup() + t.Run("list domain mappings returns a list of objects", func(t *testing.T) { + dm1 := createDomainMapping("dm-1", createServiceRef("svc1", testNamespace)) + dm2 := createDomainMapping("dm-2", createServiceRef("svc2", testNamespace)) + dm3 := createDomainMapping("dm-3", createServiceRef("svc3", testNamespace)) + serving.AddReactor("list", domainMappingResource, + func(a clienttesting.Action) (bool, runtime.Object, error) { + assert.Equal(t, testNamespace, a.GetNamespace()) + return true, &servingv1alpha1.DomainMappingList{Items: []servingv1alpha1.DomainMapping{*dm1, *dm2, *dm3}}, nil + }) + listServices, err := client.ListDomainMappings() + assert.NilError(t, err) + assert.Assert(t, len(listServices.Items) == 3) + assert.Equal(t, listServices.Items[0].Name, "dm-1") + assert.Equal(t, listServices.Items[1].Name, "dm-2") + assert.Equal(t, listServices.Items[2].Name, "dm-3") + validateGroupVersionKind(t, listServices) + validateGroupVersionKind(t, &listServices.Items[0]) + validateGroupVersionKind(t, &listServices.Items[1]) + validateGroupVersionKind(t, &listServices.Items[2]) + }) +} + +func validateGroupVersionKind(t *testing.T, obj runtime.Object) { + gvkExpected, err := util.GetGroupVersionKind(obj, servingv1alpha1.SchemeGroupVersion, scheme.Scheme) + assert.NilError(t, err) + gvkGiven := obj.GetObjectKind().GroupVersionKind() + fmt.Println(gvkGiven.String()) + assert.Equal(t, *gvkExpected, gvkGiven, "GVK should be the same") +} + +func createDomainMapping(name string, ref duckv1.KReference) *servingv1alpha1.DomainMapping { + return NewDomainMappingBuilder(name).Namespace("default").Reference(ref).Build() +} + +func createServiceRef(service, namespace string) duckv1.KReference { + return duckv1.KReference{Name: service, + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + Namespace: namespace, + } +} diff --git a/test/common.sh b/test/common.sh index 4a80600dba..4d70616834 100755 --- a/test/common.sh +++ b/test/common.sh @@ -66,8 +66,18 @@ function knative_setup() { if [ "${serving_version}" = "latest" ]; then start_latest_knative_serving + + subheader "Installing Serving extension: DomainMapping (${serving_version})" + kubectl apply --filename https://storage.googleapis.com/knative-nightly/serving/latest/serving-domainmapping-crds.yaml + kubectl apply --filename https://storage.googleapis.com/knative-nightly/serving/latest/serving-domainmapping.yaml + wait_until_pods_running knative-serving || return 1 else start_release_knative_serving "${serving_version}" + + subheader "Installing Serving extension: DomainMapping (${serving_version})" + kubectl apply --filename https://storage.googleapis.com/knative-releases/serving/previous/v${serving_version}/serving-domainmapping-crds.yaml + kubectl apply --filename https://storage.googleapis.com/knative-releases/serving/previous/v${serving_version}/serving-domainmapping.yaml + wait_until_pods_running knative-serving || return 1 fi local eventing_version=${KNATIVE_EVENTING_VERSION:-latest} diff --git a/test/e2e/domain_mapping_test.go b/test/e2e/domain_mapping_test.go new file mode 100644 index 0000000000..abd7295283 --- /dev/null +++ b/test/e2e/domain_mapping_test.go @@ -0,0 +1,95 @@ +// 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. + +// +build e2e +// +build !eventing + +package e2e + +import ( + "testing" + + "knative.dev/client/pkg/util" + + "gotest.tools/v3/assert" + + "knative.dev/client/lib/test" +) + +func TestDomain(t *testing.T) { + t.Parallel() + it, err := test.NewKnTest() + assert.NilError(t, err) + defer func() { + assert.NilError(t, it.Teardown()) + }() + + r := test.NewKnRunResultCollector(t, it) + defer r.DumpIfFailed() + + domainName := "hello.example.com" + + t.Log("create domain mapping to hello ksvc") + test.ServiceCreate(r, "hello") + domainCreate(r, domainName, "hello") + + t.Log("list domain mappings") + domainList(r, domainName) + + t.Log("update domain mapping Knative service reference") + test.ServiceCreate(r, "foo") + domainUpdate(r, domainName, "foo") + + t.Log("describe domain mappings") + domainDescribe(r, domainName) + + t.Log("delete domain") + domainDelete(r, domainName) +} + +func domainCreate(r *test.KnRunResultCollector, domainName, serviceName string, options ...string) { + command := []string{"domain", "create", domainName, "--ref", serviceName} + command = append(command, options...) + out := r.KnTest().Kn().Run(command...) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "domain", "mapping", serviceName, domainName, "created", "namespace", r.KnTest().Kn().Namespace())) +} + +func domainUpdate(r *test.KnRunResultCollector, domainName, serviceName string, options ...string) { + command := []string{"domain", "update", domainName, "--ref", serviceName} + command = append(command, options...) + out := r.KnTest().Kn().Run(command...) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "domain", "mapping", domainName, "updated", "namespace", r.KnTest().Kn().Namespace())) +} + +func domainDelete(r *test.KnRunResultCollector, domainName string, options ...string) { + command := []string{"domain", "delete", domainName} + command = append(command, options...) + out := r.KnTest().Kn().Run(command...) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "domain", "mapping", domainName, "deleted", "namespace", r.KnTest().Kn().Namespace())) +} + +func domainList(r *test.KnRunResultCollector, domainName string) { + out := r.KnTest().Kn().Run("domain", "list") + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAll(out.Stdout, domainName)) +} + +func domainDescribe(r *test.KnRunResultCollector, domainName string) { + out := r.KnTest().Kn().Run("domain", "describe", domainName) + r.AssertNoError(out) + assert.Assert(r.T(), util.ContainsAll(out.Stdout, "Name", "Namespace", "URL", "Service")) +} diff --git a/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/doc.go b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/doc.go new file mode 100644 index 0000000000..41e872fe9a --- /dev/null +++ b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/domainmapping.go b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/domainmapping.go new file mode 100644 index 0000000000..606935b693 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/domainmapping.go @@ -0,0 +1,195 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + v1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" + scheme "knative.dev/serving/pkg/client/clientset/versioned/scheme" +) + +// DomainMappingsGetter has a method to return a DomainMappingInterface. +// A group's client should implement this interface. +type DomainMappingsGetter interface { + DomainMappings(namespace string) DomainMappingInterface +} + +// DomainMappingInterface has methods to work with DomainMapping resources. +type DomainMappingInterface interface { + Create(ctx context.Context, domainMapping *v1alpha1.DomainMapping, opts v1.CreateOptions) (*v1alpha1.DomainMapping, error) + Update(ctx context.Context, domainMapping *v1alpha1.DomainMapping, opts v1.UpdateOptions) (*v1alpha1.DomainMapping, error) + UpdateStatus(ctx context.Context, domainMapping *v1alpha1.DomainMapping, opts v1.UpdateOptions) (*v1alpha1.DomainMapping, 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) (*v1alpha1.DomainMapping, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.DomainMappingList, 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 *v1alpha1.DomainMapping, err error) + DomainMappingExpansion +} + +// domainMappings implements DomainMappingInterface +type domainMappings struct { + client rest.Interface + ns string +} + +// newDomainMappings returns a DomainMappings +func newDomainMappings(c *ServingV1alpha1Client, namespace string) *domainMappings { + return &domainMappings{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the domainMapping, and returns the corresponding domainMapping object, and an error if there is any. +func (c *domainMappings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.DomainMapping, err error) { + result = &v1alpha1.DomainMapping{} + err = c.client.Get(). + Namespace(c.ns). + Resource("domainmappings"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of DomainMappings that match those selectors. +func (c *domainMappings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.DomainMappingList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.DomainMappingList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("domainmappings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested domainMappings. +func (c *domainMappings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("domainmappings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a domainMapping and creates it. Returns the server's representation of the domainMapping, and an error, if there is any. +func (c *domainMappings) Create(ctx context.Context, domainMapping *v1alpha1.DomainMapping, opts v1.CreateOptions) (result *v1alpha1.DomainMapping, err error) { + result = &v1alpha1.DomainMapping{} + err = c.client.Post(). + Namespace(c.ns). + Resource("domainmappings"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(domainMapping). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a domainMapping and updates it. Returns the server's representation of the domainMapping, and an error, if there is any. +func (c *domainMappings) Update(ctx context.Context, domainMapping *v1alpha1.DomainMapping, opts v1.UpdateOptions) (result *v1alpha1.DomainMapping, err error) { + result = &v1alpha1.DomainMapping{} + err = c.client.Put(). + Namespace(c.ns). + Resource("domainmappings"). + Name(domainMapping.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(domainMapping). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *domainMappings) UpdateStatus(ctx context.Context, domainMapping *v1alpha1.DomainMapping, opts v1.UpdateOptions) (result *v1alpha1.DomainMapping, err error) { + result = &v1alpha1.DomainMapping{} + err = c.client.Put(). + Namespace(c.ns). + Resource("domainmappings"). + Name(domainMapping.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(domainMapping). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the domainMapping and deletes it. Returns an error if one occurs. +func (c *domainMappings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("domainmappings"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *domainMappings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("domainmappings"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched domainMapping. +func (c *domainMappings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.DomainMapping, err error) { + result = &v1alpha1.DomainMapping{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("domainmappings"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/doc.go b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/doc.go new file mode 100644 index 0000000000..c7f6e65cab --- /dev/null +++ b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/fake_domainmapping.go b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/fake_domainmapping.go new file mode 100644 index 0000000000..f224fe8366 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/fake_domainmapping.go @@ -0,0 +1,142 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" +) + +// FakeDomainMappings implements DomainMappingInterface +type FakeDomainMappings struct { + Fake *FakeServingV1alpha1 + ns string +} + +var domainmappingsResource = schema.GroupVersionResource{Group: "serving.knative.dev", Version: "v1alpha1", Resource: "domainmappings"} + +var domainmappingsKind = schema.GroupVersionKind{Group: "serving.knative.dev", Version: "v1alpha1", Kind: "DomainMapping"} + +// Get takes name of the domainMapping, and returns the corresponding domainMapping object, and an error if there is any. +func (c *FakeDomainMappings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.DomainMapping, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(domainmappingsResource, c.ns, name), &v1alpha1.DomainMapping{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.DomainMapping), err +} + +// List takes label and field selectors, and returns the list of DomainMappings that match those selectors. +func (c *FakeDomainMappings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.DomainMappingList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(domainmappingsResource, domainmappingsKind, c.ns, opts), &v1alpha1.DomainMappingList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.DomainMappingList{ListMeta: obj.(*v1alpha1.DomainMappingList).ListMeta} + for _, item := range obj.(*v1alpha1.DomainMappingList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested domainMappings. +func (c *FakeDomainMappings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(domainmappingsResource, c.ns, opts)) + +} + +// Create takes the representation of a domainMapping and creates it. Returns the server's representation of the domainMapping, and an error, if there is any. +func (c *FakeDomainMappings) Create(ctx context.Context, domainMapping *v1alpha1.DomainMapping, opts v1.CreateOptions) (result *v1alpha1.DomainMapping, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(domainmappingsResource, c.ns, domainMapping), &v1alpha1.DomainMapping{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.DomainMapping), err +} + +// Update takes the representation of a domainMapping and updates it. Returns the server's representation of the domainMapping, and an error, if there is any. +func (c *FakeDomainMappings) Update(ctx context.Context, domainMapping *v1alpha1.DomainMapping, opts v1.UpdateOptions) (result *v1alpha1.DomainMapping, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(domainmappingsResource, c.ns, domainMapping), &v1alpha1.DomainMapping{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.DomainMapping), 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 *FakeDomainMappings) UpdateStatus(ctx context.Context, domainMapping *v1alpha1.DomainMapping, opts v1.UpdateOptions) (*v1alpha1.DomainMapping, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(domainmappingsResource, "status", c.ns, domainMapping), &v1alpha1.DomainMapping{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.DomainMapping), err +} + +// Delete takes name of the domainMapping and deletes it. Returns an error if one occurs. +func (c *FakeDomainMappings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(domainmappingsResource, c.ns, name), &v1alpha1.DomainMapping{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeDomainMappings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(domainmappingsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.DomainMappingList{}) + return err +} + +// Patch applies the patch and returns the patched domainMapping. +func (c *FakeDomainMappings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.DomainMapping, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(domainmappingsResource, c.ns, name, pt, data, subresources...), &v1alpha1.DomainMapping{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.DomainMapping), err +} diff --git a/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/fake_serving_client.go b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/fake_serving_client.go new file mode 100644 index 0000000000..3dde006e65 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake/fake_serving_client.go @@ -0,0 +1,40 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1alpha1 "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" +) + +type FakeServingV1alpha1 struct { + *testing.Fake +} + +func (c *FakeServingV1alpha1) DomainMappings(namespace string) v1alpha1.DomainMappingInterface { + return &FakeDomainMappings{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeServingV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/generated_expansion.go b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/generated_expansion.go new file mode 100644 index 0000000000..ce84dcec66 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +type DomainMappingExpansion interface{} diff --git a/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/serving_client.go b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/serving_client.go new file mode 100644 index 0000000000..1a4aa0ff9d --- /dev/null +++ b/vendor/knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/serving_client.go @@ -0,0 +1,89 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + rest "k8s.io/client-go/rest" + v1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" + "knative.dev/serving/pkg/client/clientset/versioned/scheme" +) + +type ServingV1alpha1Interface interface { + RESTClient() rest.Interface + DomainMappingsGetter +} + +// ServingV1alpha1Client is used to interact with features provided by the serving.knative.dev group. +type ServingV1alpha1Client struct { + restClient rest.Interface +} + +func (c *ServingV1alpha1Client) DomainMappings(namespace string) DomainMappingInterface { + return newDomainMappings(c, namespace) +} + +// NewForConfig creates a new ServingV1alpha1Client for the given config. +func NewForConfig(c *rest.Config) (*ServingV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &ServingV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new ServingV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *ServingV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new ServingV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *ServingV1alpha1Client { + return &ServingV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *ServingV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 5f3d7ce3fe..9ae5e776a9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -865,6 +865,8 @@ knative.dev/serving/pkg/autoscaler/config/autoscalerconfig knative.dev/serving/pkg/client/clientset/versioned/scheme knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1 knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1/fake +knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1 +knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake knative.dev/serving/pkg/gc knative.dev/serving/pkg/networking knative.dev/serving/pkg/reconciler/route/config From 16d43829c2b3e04947fa5679c6ce4106a25852c9 Mon Sep 17 00:00:00 2001 From: David Simansky Date: Mon, 22 Mar 2021 13:41:56 +0100 Subject: [PATCH 2/6] fix: Reflect review feedback --- docs/cmd/kn_domain_create.md | 2 +- docs/cmd/kn_domain_delete.md | 2 +- docs/cmd/kn_domain_describe.md | 2 +- docs/cmd/kn_domain_update.md | 2 +- pkg/kn/commands/domain/create.go | 2 +- pkg/kn/commands/domain/delete.go | 3 ++- pkg/kn/commands/domain/delete_test.go | 15 +++++++++++++++ pkg/kn/commands/domain/describe.go | 6 +++--- pkg/kn/commands/domain/domain.go | 2 +- pkg/kn/commands/domain/domain_test.go | 27 ++++++++++++++++++++++----- pkg/kn/commands/domain/list.go | 10 +++++++--- pkg/kn/commands/domain/update.go | 6 +++++- pkg/serving/v1alpha1/client.go | 6 ++++++ 13 files changed, 66 insertions(+), 19 deletions(-) diff --git a/docs/cmd/kn_domain_create.md b/docs/cmd/kn_domain_create.md index c37085ab6e..332aac1c77 100644 --- a/docs/cmd/kn_domain_create.md +++ b/docs/cmd/kn_domain_create.md @@ -3,7 +3,7 @@ Create a domain mapping ``` -kn domain create FQDN +kn domain create NAME ``` ### Examples diff --git a/docs/cmd/kn_domain_delete.md b/docs/cmd/kn_domain_delete.md index d96faa0dff..a7d4d781a0 100644 --- a/docs/cmd/kn_domain_delete.md +++ b/docs/cmd/kn_domain_delete.md @@ -3,7 +3,7 @@ Delete a domain mapping ``` -kn domain delete FQDN +kn domain delete NAME ``` ### Examples diff --git a/docs/cmd/kn_domain_describe.md b/docs/cmd/kn_domain_describe.md index 365c7b8785..cd51a969a3 100644 --- a/docs/cmd/kn_domain_describe.md +++ b/docs/cmd/kn_domain_describe.md @@ -3,7 +3,7 @@ Show details of a domain mapping ``` -kn domain describe FQDN +kn domain describe NAME ``` ### Options diff --git a/docs/cmd/kn_domain_update.md b/docs/cmd/kn_domain_update.md index 945a0d31f0..3864d94558 100644 --- a/docs/cmd/kn_domain_update.md +++ b/docs/cmd/kn_domain_update.md @@ -3,7 +3,7 @@ Update a domain mapping ``` -kn domain update FQDN +kn domain update NAME ``` ### Examples diff --git a/pkg/kn/commands/domain/create.go b/pkg/kn/commands/domain/create.go index 0ad43abc18..8001b89595 100644 --- a/pkg/kn/commands/domain/create.go +++ b/pkg/kn/commands/domain/create.go @@ -30,7 +30,7 @@ import ( func NewDomainMappingCreateCommand(p *commands.KnParams) *cobra.Command { var refFlags flags.SinkFlags cmd := &cobra.Command{ - Use: "create FQDN", + Use: "create NAME", Short: "Create a domain mapping", Example: ` # Create a domain mappings 'hello.example.com' for Knative service 'hello' diff --git a/pkg/kn/commands/domain/delete.go b/pkg/kn/commands/domain/delete.go index 6a83c10138..0ce6f031f2 100644 --- a/pkg/kn/commands/domain/delete.go +++ b/pkg/kn/commands/domain/delete.go @@ -27,7 +27,7 @@ import ( // NewDomainMappingDeleteCommand to create event channels func NewDomainMappingDeleteCommand(p *commands.KnParams) *cobra.Command { cmd := &cobra.Command{ - Use: "delete FQDN", + Use: "delete NAME", Short: "Delete a domain mapping", Example: ` # Delete domain mappings 'hello.example.com' @@ -37,6 +37,7 @@ func NewDomainMappingDeleteCommand(p *commands.KnParams) *cobra.Command { return errors.New("'kn domain delete' requires the domain name given as single argument") } name := args[0] + namespace, err := p.GetNamespace(cmd) if err != nil { return err diff --git a/pkg/kn/commands/domain/delete_test.go b/pkg/kn/commands/domain/delete_test.go index 345c6ef5f8..2e7418fc39 100644 --- a/pkg/kn/commands/domain/delete_test.go +++ b/pkg/kn/commands/domain/delete_test.go @@ -15,6 +15,7 @@ package domain import ( + "errors" "testing" "gotest.tools/v3/assert" @@ -36,6 +37,20 @@ func TestDomainMappingDelete(t *testing.T) { servingRecorder.Validate() } + +func TestDomainMappingDeleteNotFound(t *testing.T) { + client := v1alpha1.NewMockKnServiceClient(t) + + servingRecorder := client.Recorder() + servingRecorder.DeleteDomainMapping("foo.bar", errors.New("domainmappings.serving.knative.dev \"foo.bar\" not found")) + + _, err := executeDomainCommand(client, nil, "delete", "foo.bar") + assert.ErrorContains(t, err, "not found") + assert.Assert(t, util.ContainsAll(err.Error(), "domainmappings.serving.knative.dev", "\"foo.bar\"", "not", "found")) + + servingRecorder.Validate() +} + func TestDomainMappingDeleteWithError(t *testing.T) { client := v1alpha1.NewMockKnServiceClient(t) dynamicClient := dynamicfake.CreateFakeKnDynamicClient(client.Namespace(), createService("foo")) diff --git a/pkg/kn/commands/domain/describe.go b/pkg/kn/commands/domain/describe.go index 42bac45da5..424829bff8 100644 --- a/pkg/kn/commands/domain/describe.go +++ b/pkg/kn/commands/domain/describe.go @@ -28,12 +28,12 @@ import ( "knative.dev/serving/pkg/apis/serving/v1alpha1" ) -// NewDomainMappingDescribe represents 'kn route describe' command -func NewDomainMappingDescribe(p *commands.KnParams) *cobra.Command { +// NewDomainMappingDescribeCommand represents 'kn route describe' command +func NewDomainMappingDescribeCommand(p *commands.KnParams) *cobra.Command { // For machine readable output machineReadablePrintFlags := genericclioptions.NewPrintFlags("") cmd := &cobra.Command{ - Use: "describe FQDN", + Use: "describe NAME", Short: "Show details of a domain mapping", RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { diff --git a/pkg/kn/commands/domain/domain.go b/pkg/kn/commands/domain/domain.go index 81acbde627..81905e8b1f 100644 --- a/pkg/kn/commands/domain/domain.go +++ b/pkg/kn/commands/domain/domain.go @@ -28,7 +28,7 @@ func NewDomainCommand(p *commands.KnParams) *cobra.Command { Aliases: []string{"domains"}, } domainCmd.AddCommand(NewDomainMappingCreateCommand(p)) - domainCmd.AddCommand(NewDomainMappingDescribe(p)) + domainCmd.AddCommand(NewDomainMappingDescribeCommand(p)) domainCmd.AddCommand(NewDomainMappingUpdateCommand(p)) domainCmd.AddCommand(NewDomainMappingDeleteCommand(p)) domainCmd.AddCommand(NewDomainMappingListCommand(p)) diff --git a/pkg/kn/commands/domain/domain_test.go b/pkg/kn/commands/domain/domain_test.go index 2505692538..7e31bf2d45 100644 --- a/pkg/kn/commands/domain/domain_test.go +++ b/pkg/kn/commands/domain/domain_test.go @@ -16,6 +16,9 @@ package domain import ( "bytes" + "testing" + + "gotest.tools/v3/assert" "github.com/spf13/cobra" @@ -34,9 +37,7 @@ import ( // Helper methods var blankConfig clientcmd.ClientConfig -func init() { - var err error - blankConfig, err = clientcmd.NewClientConfigFromBytes([]byte(`kind: Config +const kubeConfig = `kind: Config version: v1 users: - name: u @@ -49,13 +50,29 @@ contexts: context: user: u cluster: c -current-context: x -`)) +current-context: x` + +func init() { + var err error + blankConfig, err = clientcmd.NewClientConfigFromBytes([]byte(kubeConfig)) if err != nil { panic(err) } } +func TestDomainCommand(t *testing.T) { + knParams := &commands.KnParams{} + domainCmd := NewDomainCommand(knParams) + assert.Equal(t, domainCmd.Name(), "domain") + assert.Equal(t, domainCmd.Use, "domain COMMAND") + var subCommands []string + for _, cmd := range domainCmd.Commands() { + subCommands = append(subCommands, cmd.Name()) + } + expectedSubCommands := []string{"create", "delete", "describe", "list", "update"} + assert.DeepEqual(t, subCommands, expectedSubCommands) +} + func executeDomainCommand(client clientservingv1alpha1.KnServingClient, dynamicClient kndynamic.KnDynamicClient, args ...string) (string, error) { knParams := &commands.KnParams{} knParams.ClientConfig = blankConfig diff --git a/pkg/kn/commands/domain/list.go b/pkg/kn/commands/domain/list.go index 204b42c896..e86df096ba 100644 --- a/pkg/kn/commands/domain/list.go +++ b/pkg/kn/commands/domain/list.go @@ -41,21 +41,25 @@ func NewDomainMappingListCommand(p *commands.KnParams) *cobra.Command { if err != nil { return err } + if namespace == "" { + listFlags.EnsureWithNamespace() + } + client, err := p.NewServingV1alpha1Client(namespace) if err != nil { return err } + domainMappingList, err := client.ListDomainMappings() if err != nil { return err } + if len(domainMappingList.Items) == 0 { fmt.Fprintf(cmd.OutOrStdout(), "No domain mapping found.\n") return nil } - if namespace == "" { - listFlags.EnsureWithNamespace() - } + return listFlags.Print(domainMappingList, cmd.OutOrStdout()) }, } diff --git a/pkg/kn/commands/domain/update.go b/pkg/kn/commands/domain/update.go index bbbbd23b3d..8be9f1e658 100644 --- a/pkg/kn/commands/domain/update.go +++ b/pkg/kn/commands/domain/update.go @@ -29,7 +29,7 @@ import ( func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command { var refFlags flags.SinkFlags cmd := &cobra.Command{ - Use: "update FQDN", + Use: "update NAME", Short: "Update a domain mapping", Example: ` # Update a domain mappings 'hello.example.com' for Knative service 'hello' @@ -43,14 +43,17 @@ func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command { if err != nil { return err } + client, err := p.NewServingV1alpha1Client(namespace) if err != nil { return err } + toUpdate, err := client.GetDomainMapping(name) if err != nil { return err } + if toUpdate.GetDeletionTimestamp() != nil { return fmt.Errorf("can't update domain mapping '%s' because it has been marked for deletion", name) } @@ -59,6 +62,7 @@ func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command { if err != nil { return err } + destination, err := refFlags.ResolveSink(dynamicClient, namespace) if err != nil { return err diff --git a/pkg/serving/v1alpha1/client.go b/pkg/serving/v1alpha1/client.go index c6b17377aa..f89522988c 100644 --- a/pkg/serving/v1alpha1/client.go +++ b/pkg/serving/v1alpha1/client.go @@ -62,10 +62,12 @@ func NewKnServingClient(client clientv1alpha1.ServingV1alpha1Interface, namespac } } +// Namespace in which this client is operating for func (cl *knServingClient) Namespace() string { return cl.namespace } +// GetDomainMapping gets DomainMapping by name func (cl *knServingClient) GetDomainMapping(name string) (*servingv1alpha1.DomainMapping, error) { dm, err := cl.client.DomainMappings(cl.namespace).Get(context.TODO(), name, v1.GetOptions{}) if err != nil { @@ -78,6 +80,7 @@ func (cl *knServingClient) GetDomainMapping(name string) (*servingv1alpha1.Domai return dm, nil } +// CreateDomainMapping creates provided DomainMapping func (cl *knServingClient) CreateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { _, err := cl.client.DomainMappings(cl.namespace).Create(context.TODO(), domainMapping, v1.CreateOptions{}) if err != nil { @@ -86,6 +89,7 @@ func (cl *knServingClient) CreateDomainMapping(domainMapping *servingv1alpha1.Do return updateServingGvk(domainMapping) } +// UpdateDomainMapping updates provided DomainMapping func (cl *knServingClient) UpdateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { _, err := cl.client.DomainMappings(cl.namespace).Update(context.TODO(), domainMapping, v1.UpdateOptions{}) if err != nil { @@ -94,6 +98,7 @@ func (cl *knServingClient) UpdateDomainMapping(domainMapping *servingv1alpha1.Do return updateServingGvk(domainMapping) } +// DeleteDomainMapping deletes DomainMapping by name func (cl *knServingClient) DeleteDomainMapping(name string) error { err := cl.client.DomainMappings(cl.namespace).Delete(context.TODO(), name, v1.DeleteOptions{}) if err != nil { @@ -102,6 +107,7 @@ func (cl *knServingClient) DeleteDomainMapping(name string) error { return nil } +// ListDomainMappings lists all DomainMappings func (cl *knServingClient) ListDomainMappings() (*servingv1alpha1.DomainMappingList, error) { domainMappingList, err := cl.client.DomainMappings(cl.namespace).List(context.TODO(), v1.ListOptions{}) if err != nil { From 88226803de6e6437e633ed32f90651b4092f3a1e Mon Sep 17 00:00:00 2001 From: David Simansky Date: Mon, 22 Mar 2021 13:55:01 +0100 Subject: [PATCH 3/6] fix: Fix prealloc lint error --- pkg/kn/commands/domain/domain_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/kn/commands/domain/domain_test.go b/pkg/kn/commands/domain/domain_test.go index 7e31bf2d45..b5268181bc 100644 --- a/pkg/kn/commands/domain/domain_test.go +++ b/pkg/kn/commands/domain/domain_test.go @@ -65,7 +65,7 @@ func TestDomainCommand(t *testing.T) { domainCmd := NewDomainCommand(knParams) assert.Equal(t, domainCmd.Name(), "domain") assert.Equal(t, domainCmd.Use, "domain COMMAND") - var subCommands []string + subCommands := make([]string, 0, len(domainCmd.Commands())) for _, cmd := range domainCmd.Commands() { subCommands = append(subCommands, cmd.Name()) } From 4a3afa7de273c631d519e9f504db73f7c1b039c3 Mon Sep 17 00:00:00 2001 From: David Simansky Date: Tue, 23 Mar 2021 18:00:04 +0100 Subject: [PATCH 4/6] fix: Add specific reference resolution --- docs/cmd/kn_domain_create.md | 2 +- docs/cmd/kn_domain_update.md | 4 +- pkg/dynamic/fake/fake.go | 3 ++ pkg/kn/commands/domain/create.go | 10 ++-- pkg/kn/commands/domain/domain.go | 75 ++++++++++++++++++++++++++- pkg/kn/commands/domain/domain_test.go | 72 +++++++++++++++++++++++-- pkg/kn/commands/domain/update.go | 14 +++-- 7 files changed, 158 insertions(+), 22 deletions(-) diff --git a/docs/cmd/kn_domain_create.md b/docs/cmd/kn_domain_create.md index 332aac1c77..378addec56 100644 --- a/docs/cmd/kn_domain_create.md +++ b/docs/cmd/kn_domain_create.md @@ -19,7 +19,7 @@ kn domain create NAME ``` -h, --help help for create -n, --namespace string Specify the namespace to operate in. - --ref string Addressable target reference for Domain Mapping. You can specify a Knative Service name. + --ref string Addressable target reference for Domain Mapping. You can specify a Knative service, a Knative soute or a Kubernetes service.Examples: '--ref ksvc:hello' or simply '--ref hello' for a Knative service 'hello', '--ref kroute:hello' for a Knative route 'hello', '--ref svc:hello' for a Kubernetes service 'hello'. If a prefix is not provided, it is considered as a Knative service. ``` ### Options inherited from parent commands diff --git a/docs/cmd/kn_domain_update.md b/docs/cmd/kn_domain_update.md index 3864d94558..d3890c38ec 100644 --- a/docs/cmd/kn_domain_update.md +++ b/docs/cmd/kn_domain_update.md @@ -11,7 +11,7 @@ kn domain update NAME ``` # Update a domain mappings 'hello.example.com' for Knative service 'hello' - kn domain create hello.example.com --ref hello + kn domain create hello.example.com --refFlags hello ``` ### Options @@ -19,7 +19,7 @@ kn domain update NAME ``` -h, --help help for update -n, --namespace string Specify the namespace to operate in. - --ref string Addressable target reference for Domain Mapping. You can specify a Knative Service name. + --ref string Addressable target reference for Domain Mapping. You can specify a Knative service, a Knative soute or a Kubernetes service.Examples: '--ref ksvc:hello' or simply '--ref hello' for a Knative service 'hello', '--ref kroute:hello' for a Knative route 'hello', '--ref svc:hello' for a Kubernetes service 'hello'. If a prefix is not provided, it is considered as a Knative service. ``` ### Options inherited from parent commands diff --git a/pkg/dynamic/fake/fake.go b/pkg/dynamic/fake/fake.go index 78dfc3224a..95caf7a37a 100644 --- a/pkg/dynamic/fake/fake.go +++ b/pkg/dynamic/fake/fake.go @@ -15,6 +15,7 @@ package fake import ( + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" dynamicfake "k8s.io/client-go/dynamic/fake" @@ -28,7 +29,9 @@ import ( // CreateFakeKnDynamicClient gives you a dynamic client for testing containing the given objects. func CreateFakeKnDynamicClient(testNamespace string, objects ...runtime.Object) dynamic.KnDynamicClient { scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}, &v1.Service{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "serving.knative.dev", Version: "v1", Kind: "Service"}, &servingv1.Service{}) + scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "serving.knative.dev", Version: "v1", Kind: "Route"}, &servingv1.Route{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1beta1", Kind: "Broker"}, &eventingv1beta1.Broker{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1beta1", Kind: "Subscription"}, &messagingv1beta1.Subscription{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "messaging.knative.dev", Version: "v1beta1", Kind: "Channel"}, &messagingv1beta1.Channel{}) diff --git a/pkg/kn/commands/domain/create.go b/pkg/kn/commands/domain/create.go index 8001b89595..69e063ed38 100644 --- a/pkg/kn/commands/domain/create.go +++ b/pkg/kn/commands/domain/create.go @@ -22,13 +22,12 @@ import ( knerrors "knative.dev/client/pkg/errors" "knative.dev/client/pkg/kn/commands" - "knative.dev/client/pkg/kn/commands/flags" clientv1alpha1 "knative.dev/client/pkg/serving/v1alpha1" ) // NewDomainMappingCreateCommand to create event channels func NewDomainMappingCreateCommand(p *commands.KnParams) *cobra.Command { - var refFlags flags.SinkFlags + var refFlags RefFlags cmd := &cobra.Command{ Use: "create NAME", Short: "Create a domain mapping", @@ -49,14 +48,14 @@ func NewDomainMappingCreateCommand(p *commands.KnParams) *cobra.Command { if err != nil { return err } - destination, err := refFlags.ResolveSink(dynamicClient, namespace) + reference, err := refFlags.Resolve(dynamicClient, namespace) if err != nil { return err } builder := clientv1alpha1.NewDomainMappingBuilder(name). Namespace(namespace). - Reference(*destination.Ref) + Reference(*reference) client, err := p.NewServingV1alpha1Client(namespace) if err != nil { @@ -72,8 +71,7 @@ func NewDomainMappingCreateCommand(p *commands.KnParams) *cobra.Command { }, } commands.AddNamespaceFlags(cmd.Flags(), false) - refFlags.AddWithFlagName(cmd, "ref", "") - cmd.Flag("ref").Usage = "Addressable target reference for Domain Mapping. You can specify a Knative Service name." + refFlags.Add(cmd) cmd.MarkFlagRequired("ref") return cmd } diff --git a/pkg/kn/commands/domain/domain.go b/pkg/kn/commands/domain/domain.go index 81905e8b1f..6a375aaebf 100644 --- a/pkg/kn/commands/domain/domain.go +++ b/pkg/kn/commands/domain/domain.go @@ -15,9 +15,16 @@ package domain import ( - "github.com/spf13/cobra" + "context" + "fmt" + "strings" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + clientdynamic "knative.dev/client/pkg/dynamic" "knative.dev/client/pkg/kn/commands" + duckv1 "knative.dev/pkg/apis/duck/v1" ) // NewDomainCommand to manage domain mappings @@ -34,3 +41,69 @@ func NewDomainCommand(p *commands.KnParams) *cobra.Command { domainCmd.AddCommand(NewDomainMappingListCommand(p)) return domainCmd } + +type RefFlags struct { + reference string +} + +var refMappings = map[string]schema.GroupVersionResource{ + "ksvc": { + Resource: "services", + Group: "serving.knative.dev", + Version: "v1", + }, + "kroute": { + Resource: "routes", + Group: "serving.knative.dev", + Version: "v1", + }, + "svc": { + Resource: "services", + Group: "", + Version: "v1", + }, +} + +func (f *RefFlags) Add(cmd *cobra.Command) { + cmd.Flags().StringVar(&f.reference, "ref", "", "") + cmd.Flag("ref").Usage = "Addressable target reference for Domain Mapping. " + + "You can specify a Knative service, a Knative soute or a Kubernetes service." + + "Examples: '--ref ksvc:hello' or simply '--ref hello' for a Knative service 'hello', " + + "'--ref kroute:hello' for a Knative route 'hello', " + + "'--ref svc:hello' for a Kubernetes service 'hello'. " + + "If a prefix is not provided, it is considered as a Knative service." +} + +func (f RefFlags) Resolve(knclient clientdynamic.KnDynamicClient, namespace string) (*duckv1.KReference, error) { + client := knclient.RawClient() + if f.reference == "" { + return nil, nil + } + + prefix, name := parseType(f.reference) + gvr, ok := refMappings[prefix] + if !ok { + return nil, fmt.Errorf("unsupported sink prefix: '%s'", prefix) + } + obj, err := client.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + result := &duckv1.KReference{ + Kind: obj.GetKind(), + APIVersion: obj.GetAPIVersion(), + Name: obj.GetName(), + Namespace: namespace, + } + return result, nil +} + +func parseType(ref string) (string, string) { + parts := strings.SplitN(ref, ":", 2) + if len(parts) == 1 { + return "ksvc", parts[0] + } else { + return parts[0], parts[1] + } +} diff --git a/pkg/kn/commands/domain/domain_test.go b/pkg/kn/commands/domain/domain_test.go index b5268181bc..f3e5118b76 100644 --- a/pkg/kn/commands/domain/domain_test.go +++ b/pkg/kn/commands/domain/domain_test.go @@ -18,6 +18,8 @@ import ( "bytes" "testing" + v1 "k8s.io/api/core/v1" + "gotest.tools/v3/assert" "github.com/spf13/cobra" @@ -26,10 +28,11 @@ import ( "k8s.io/client-go/tools/clientcmd" kndynamic "knative.dev/client/pkg/dynamic" + dynamicfake "knative.dev/client/pkg/dynamic/fake" "knative.dev/client/pkg/kn/commands" knflags "knative.dev/client/pkg/kn/flags" clientservingv1alpha1 "knative.dev/client/pkg/serving/v1alpha1" - v1 "knative.dev/pkg/apis/duck/v1" + duckv1 "knative.dev/pkg/apis/duck/v1" servingv1 "knative.dev/serving/pkg/apis/serving/v1" servingv1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1" ) @@ -73,6 +76,67 @@ func TestDomainCommand(t *testing.T) { assert.DeepEqual(t, subCommands, expectedSubCommands) } +type resolveCase struct { + ref string + destination *duckv1.KReference + errContents string +} + +func TestResolve(t *testing.T) { + mysvc := &servingv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "serving.knative.dev/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "default"}, + } + myroute := &servingv1.Route{ + TypeMeta: metav1.TypeMeta{Kind: "Route", APIVersion: "serving.knative.dev/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "myroute", Namespace: "default"}, + } + mykubesvc := &v1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "mykubesvc", Namespace: "default"}, + } + + cases := []resolveCase{ + {"mysvc", &duckv1.KReference{Kind: "Service", + APIVersion: "serving.knative.dev/v1", + Namespace: "default", + Name: "mysvc"}, ""}, + {"ksvc:mysvc", &duckv1.KReference{Kind: "Service", + APIVersion: "serving.knative.dev/v1", + Namespace: "default", + Name: "mysvc"}, ""}, + {"kroute:myroute", &duckv1.KReference{Kind: "Route", + APIVersion: "serving.knative.dev/v1", + Namespace: "default", + Name: "myroute"}, ""}, + {"svc:mykubesvc", &duckv1.KReference{Kind: "Service", + APIVersion: "v1", + Namespace: "default", + Name: "mykubesvc"}, ""}, + + {"k8ssvc:foo", nil, "unsupported sink prefix: 'k8ssvc'"}, + {"service:foo", nil, "unsupported sink prefix: 'service'"}, + } + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", mysvc, myroute, mykubesvc) + for _, c := range cases { + i := &RefFlags{c.ref} + result, err := i.Resolve(dynamicClient, "default") + if c.destination != nil { + assert.DeepEqual(t, result, c.destination) + assert.NilError(t, err) + } else { + assert.ErrorContains(t, err, c.errContents) + } + } +} + +func TestRefFlagAdd(t *testing.T) { + c := &cobra.Command{Use: "reftest"} + refFlag := new(RefFlags) + refFlag.Add(c) + assert.Equal(t, "ref", c.Flag("ref").Name) +} + func executeDomainCommand(client clientservingv1alpha1.KnServingClient, dynamicClient kndynamic.KnDynamicClient, args ...string) (string, error) { knParams := &commands.KnParams{} knParams.ClientConfig = blankConfig @@ -103,12 +167,12 @@ func createService(name string) *servingv1.Service { } } -func createDomainMapping(name string, ref v1.KReference) *servingv1alpha1.DomainMapping { +func createDomainMapping(name string, ref duckv1.KReference) *servingv1alpha1.DomainMapping { return clientservingv1alpha1.NewDomainMappingBuilder(name).Namespace("default").Reference(ref).Build() } -func createServiceRef(service, namespace string) v1.KReference { - return v1.KReference{Name: service, +func createServiceRef(service, namespace string) duckv1.KReference { + return duckv1.KReference{Name: service, Kind: "Service", APIVersion: "serving.knative.dev/v1", Namespace: namespace, diff --git a/pkg/kn/commands/domain/update.go b/pkg/kn/commands/domain/update.go index 8be9f1e658..522276f026 100644 --- a/pkg/kn/commands/domain/update.go +++ b/pkg/kn/commands/domain/update.go @@ -22,18 +22,17 @@ import ( knerrors "knative.dev/client/pkg/errors" "knative.dev/client/pkg/kn/commands" - "knative.dev/client/pkg/kn/commands/flags" ) // NewDomainMappingUpdateCommand to create event channels func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command { - var refFlags flags.SinkFlags + var refFlags RefFlags cmd := &cobra.Command{ Use: "update NAME", Short: "Update a domain mapping", Example: ` # Update a domain mappings 'hello.example.com' for Knative service 'hello' - kn domain create hello.example.com --ref hello`, + kn domain create hello.example.com --refFlags hello`, RunE: func(cmd *cobra.Command, args []string) (err error) { if len(args) != 1 { return errors.New("'kn domain create' requires the domain name given as single argument") @@ -63,11 +62,11 @@ func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command { return err } - destination, err := refFlags.ResolveSink(dynamicClient, namespace) + reference, err := refFlags.Resolve(dynamicClient, namespace) if err != nil { return err } - toUpdate.Spec.Ref = *destination.Ref + toUpdate.Spec.Ref = *reference err = client.UpdateDomainMapping(toUpdate) if err != nil { @@ -79,8 +78,7 @@ func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command { }, } commands.AddNamespaceFlags(cmd.Flags(), false) - refFlags.AddWithFlagName(cmd, "ref", "") - cmd.Flag("ref").Usage = "Addressable target reference for Domain Mapping. You can specify a Knative Service name." - cmd.MarkFlagRequired("ref") + refFlags.Add(cmd) + cmd.MarkFlagRequired("refFlags") return cmd } From 0549eeb1092b5ca0278e01293f0c7c111d8b8a40 Mon Sep 17 00:00:00 2001 From: David Simansky Date: Fri, 26 Mar 2021 14:12:25 +0100 Subject: [PATCH 5/6] fix: Remove Kubernetes support in ref --- docs/cmd/kn_domain_create.md | 2 +- docs/cmd/kn_domain_update.md | 2 +- pkg/kn/commands/domain/domain.go | 36 ++++++++++--------- pkg/kn/commands/domain/domain_test.go | 50 ++++++++++++++++----------- 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/docs/cmd/kn_domain_create.md b/docs/cmd/kn_domain_create.md index 378addec56..b46b24cf04 100644 --- a/docs/cmd/kn_domain_create.md +++ b/docs/cmd/kn_domain_create.md @@ -19,7 +19,7 @@ kn domain create NAME ``` -h, --help help for create -n, --namespace string Specify the namespace to operate in. - --ref string Addressable target reference for Domain Mapping. You can specify a Knative service, a Knative soute or a Kubernetes service.Examples: '--ref ksvc:hello' or simply '--ref hello' for a Knative service 'hello', '--ref kroute:hello' for a Knative route 'hello', '--ref svc:hello' for a Kubernetes service 'hello'. If a prefix is not provided, it is considered as a Knative service. + --ref string Addressable target reference for Domain Mapping. You can specify a Knative service, a Knative route. Examples: '--ref' ksvc:hello' or simply '--ref hello' for a Knative service 'hello', '--ref' kroute:hello' for a Knative route 'hello'. '--ref ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', 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. ``` ### Options inherited from parent commands diff --git a/docs/cmd/kn_domain_update.md b/docs/cmd/kn_domain_update.md index d3890c38ec..7611277b0e 100644 --- a/docs/cmd/kn_domain_update.md +++ b/docs/cmd/kn_domain_update.md @@ -19,7 +19,7 @@ kn domain update NAME ``` -h, --help help for update -n, --namespace string Specify the namespace to operate in. - --ref string Addressable target reference for Domain Mapping. You can specify a Knative service, a Knative soute or a Kubernetes service.Examples: '--ref ksvc:hello' or simply '--ref hello' for a Knative service 'hello', '--ref kroute:hello' for a Knative route 'hello', '--ref svc:hello' for a Kubernetes service 'hello'. If a prefix is not provided, it is considered as a Knative service. + --ref string Addressable target reference for Domain Mapping. You can specify a Knative service, a Knative route. Examples: '--ref' ksvc:hello' or simply '--ref hello' for a Knative service 'hello', '--ref' kroute:hello' for a Knative route 'hello'. '--ref ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', 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. ``` ### Options inherited from parent commands diff --git a/pkg/kn/commands/domain/domain.go b/pkg/kn/commands/domain/domain.go index 6a375aaebf..419d0e8c2f 100644 --- a/pkg/kn/commands/domain/domain.go +++ b/pkg/kn/commands/domain/domain.go @@ -57,21 +57,17 @@ var refMappings = map[string]schema.GroupVersionResource{ Group: "serving.knative.dev", Version: "v1", }, - "svc": { - Resource: "services", - Group: "", - Version: "v1", - }, } func (f *RefFlags) Add(cmd *cobra.Command) { cmd.Flags().StringVar(&f.reference, "ref", "", "") cmd.Flag("ref").Usage = "Addressable target reference for Domain Mapping. " + - "You can specify a Knative service, a Knative soute or a Kubernetes service." + - "Examples: '--ref ksvc:hello' or simply '--ref hello' for a Knative service 'hello', " + - "'--ref kroute:hello' for a Knative route 'hello', " + - "'--ref svc:hello' for a Kubernetes service 'hello'. " + - "If a prefix is not provided, it is considered as a Knative service." + "You can specify a Knative service, a Knative route. " + + "Examples: '--ref' ksvc:hello' or simply '--ref hello' for a Knative service 'hello', " + + "'--ref' kroute:hello' for a Knative route 'hello'. " + + "'--ref ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', " + + "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." } func (f RefFlags) Resolve(knclient clientdynamic.KnDynamicClient, namespace string) (*duckv1.KReference, error) { @@ -80,11 +76,14 @@ func (f RefFlags) Resolve(knclient clientdynamic.KnDynamicClient, namespace stri return nil, nil } - prefix, name := parseType(f.reference) + prefix, name, refNamespace := parseType(f.reference) gvr, ok := refMappings[prefix] if !ok { return nil, fmt.Errorf("unsupported sink prefix: '%s'", prefix) } + if refNamespace != "" { + namespace = refNamespace + } obj, err := client.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}) if err != nil { return nil, err @@ -99,11 +98,14 @@ func (f RefFlags) Resolve(knclient clientdynamic.KnDynamicClient, namespace stri return result, nil } -func parseType(ref string) (string, string) { - parts := strings.SplitN(ref, ":", 2) - if len(parts) == 1 { - return "ksvc", parts[0] - } else { - return parts[0], parts[1] +func parseType(ref string) (string, string, string) { + parts := strings.SplitN(ref, ":", 3) + switch { + case len(parts) == 1: + return "ksvc", parts[0], "" + case len(parts) == 3: + return parts[0], parts[1], parts[2] + default: + return parts[0], parts[1], "" } } diff --git a/pkg/kn/commands/domain/domain_test.go b/pkg/kn/commands/domain/domain_test.go index f3e5118b76..8dac6d2316 100644 --- a/pkg/kn/commands/domain/domain_test.go +++ b/pkg/kn/commands/domain/domain_test.go @@ -18,8 +18,6 @@ import ( "bytes" "testing" - v1 "k8s.io/api/core/v1" - "gotest.tools/v3/assert" "github.com/spf13/cobra" @@ -83,41 +81,53 @@ type resolveCase struct { } func TestResolve(t *testing.T) { - mysvc := &servingv1.Service{ + myksvc := &servingv1.Service{ TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "serving.knative.dev/v1"}, - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "default"}, + ObjectMeta: metav1.ObjectMeta{Name: "myksvc", Namespace: "default"}, } - myroute := &servingv1.Route{ + mykroute := &servingv1.Route{ TypeMeta: metav1.TypeMeta{Kind: "Route", APIVersion: "serving.knative.dev/v1"}, - ObjectMeta: metav1.ObjectMeta{Name: "myroute", Namespace: "default"}, + ObjectMeta: metav1.ObjectMeta{Name: "mykroute", Namespace: "default"}, + } + myksvcInOther := &servingv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "serving.knative.dev/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "myksvc", Namespace: "other"}, } - mykubesvc := &v1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "v1"}, - ObjectMeta: metav1.ObjectMeta{Name: "mykubesvc", Namespace: "default"}, + mykrouteInOther := &servingv1.Route{ + TypeMeta: metav1.TypeMeta{Kind: "Route", APIVersion: "serving.knative.dev/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "mykroute", Namespace: "other"}, } cases := []resolveCase{ - {"mysvc", &duckv1.KReference{Kind: "Service", + // Test 'name' is considered as Knative service + {"myksvc", &duckv1.KReference{Kind: "Service", APIVersion: "serving.knative.dev/v1", Namespace: "default", - Name: "mysvc"}, ""}, - {"ksvc:mysvc", &duckv1.KReference{Kind: "Service", + Name: "myksvc"}, ""}, + // Test 'type:name' format + {"ksvc:myksvc", &duckv1.KReference{Kind: "Service", APIVersion: "serving.knative.dev/v1", Namespace: "default", - Name: "mysvc"}, ""}, - {"kroute:myroute", &duckv1.KReference{Kind: "Route", + Name: "myksvc"}, ""}, + {"kroute:mykroute", &duckv1.KReference{Kind: "Route", APIVersion: "serving.knative.dev/v1", Namespace: "default", - Name: "myroute"}, ""}, - {"svc:mykubesvc", &duckv1.KReference{Kind: "Service", - APIVersion: "v1", - Namespace: "default", - Name: "mykubesvc"}, ""}, + Name: "mykroute"}, ""}, + // Test 'type:name:namespace' format + {"ksvc:myksvc:other", &duckv1.KReference{Kind: "Service", + APIVersion: "serving.knative.dev/v1", + Namespace: "other", + Name: "myksvc"}, ""}, + {"kroute:mykroute:other", &duckv1.KReference{Kind: "Route", + APIVersion: "serving.knative.dev/v1", + Namespace: "other", + Name: "mykroute"}, ""}, {"k8ssvc:foo", nil, "unsupported sink prefix: 'k8ssvc'"}, + {"svc:foo", nil, "unsupported sink prefix: 'svc'"}, {"service:foo", nil, "unsupported sink prefix: 'service'"}, } - dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", mysvc, myroute, mykubesvc) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", myksvc, mykroute, myksvcInOther, mykrouteInOther) for _, c := range cases { i := &RefFlags{c.ref} result, err := i.Resolve(dynamicClient, "default") From b57b7e37908a45a552c68547b338c1f41fee6271 Mon Sep 17 00:00:00 2001 From: David Simansky Date: Fri, 26 Mar 2021 19:00:35 +0100 Subject: [PATCH 6/6] chore: Add context param to client functions --- pkg/kn/commands/domain/create.go | 4 ++-- pkg/kn/commands/domain/delete.go | 2 +- pkg/kn/commands/domain/describe.go | 2 +- pkg/kn/commands/domain/domain.go | 4 ++-- pkg/kn/commands/domain/domain_test.go | 3 ++- pkg/kn/commands/domain/list.go | 2 +- pkg/kn/commands/domain/update.go | 6 ++--- pkg/serving/v1alpha1/client.go | 30 ++++++++++++------------ pkg/serving/v1alpha1/client_mock.go | 11 +++++---- pkg/serving/v1alpha1/client_mock_test.go | 12 ++++++---- pkg/serving/v1alpha1/client_test.go | 19 ++++++++------- 11 files changed, 50 insertions(+), 45 deletions(-) diff --git a/pkg/kn/commands/domain/create.go b/pkg/kn/commands/domain/create.go index 69e063ed38..d240847e7d 100644 --- a/pkg/kn/commands/domain/create.go +++ b/pkg/kn/commands/domain/create.go @@ -48,7 +48,7 @@ func NewDomainMappingCreateCommand(p *commands.KnParams) *cobra.Command { if err != nil { return err } - reference, err := refFlags.Resolve(dynamicClient, namespace) + reference, err := refFlags.Resolve(cmd.Context(), dynamicClient, namespace) if err != nil { return err } @@ -61,7 +61,7 @@ func NewDomainMappingCreateCommand(p *commands.KnParams) *cobra.Command { if err != nil { return err } - err = client.CreateDomainMapping(builder.Build()) + err = client.CreateDomainMapping(cmd.Context(), builder.Build()) if err != nil { return knerrors.GetError(err) } diff --git a/pkg/kn/commands/domain/delete.go b/pkg/kn/commands/domain/delete.go index 0ce6f031f2..7a68a923c3 100644 --- a/pkg/kn/commands/domain/delete.go +++ b/pkg/kn/commands/domain/delete.go @@ -48,7 +48,7 @@ func NewDomainMappingDeleteCommand(p *commands.KnParams) *cobra.Command { return err } - err = client.DeleteDomainMapping(name) + err = client.DeleteDomainMapping(cmd.Context(), name) if err != nil { return knerrors.GetError(err) } diff --git a/pkg/kn/commands/domain/describe.go b/pkg/kn/commands/domain/describe.go index 424829bff8..9941bdf723 100644 --- a/pkg/kn/commands/domain/describe.go +++ b/pkg/kn/commands/domain/describe.go @@ -49,7 +49,7 @@ func NewDomainMappingDescribeCommand(p *commands.KnParams) *cobra.Command { return err } - domainMapping, err := client.GetDomainMapping(args[0]) + domainMapping, err := client.GetDomainMapping(cmd.Context(), args[0]) if err != nil { return err } diff --git a/pkg/kn/commands/domain/domain.go b/pkg/kn/commands/domain/domain.go index 419d0e8c2f..da8aba7d82 100644 --- a/pkg/kn/commands/domain/domain.go +++ b/pkg/kn/commands/domain/domain.go @@ -70,7 +70,7 @@ func (f *RefFlags) Add(cmd *cobra.Command) { "If referring to a Knative service in another namespace, 'ksvc:name:namespace' combination must be provided explicitly." } -func (f RefFlags) Resolve(knclient clientdynamic.KnDynamicClient, namespace string) (*duckv1.KReference, error) { +func (f RefFlags) Resolve(ctx context.Context, knclient clientdynamic.KnDynamicClient, namespace string) (*duckv1.KReference, error) { client := knclient.RawClient() if f.reference == "" { return nil, nil @@ -84,7 +84,7 @@ func (f RefFlags) Resolve(knclient clientdynamic.KnDynamicClient, namespace stri if refNamespace != "" { namespace = refNamespace } - obj, err := client.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + obj, err := client.Resource(gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil, err } diff --git a/pkg/kn/commands/domain/domain_test.go b/pkg/kn/commands/domain/domain_test.go index 8dac6d2316..a72f844e1a 100644 --- a/pkg/kn/commands/domain/domain_test.go +++ b/pkg/kn/commands/domain/domain_test.go @@ -16,6 +16,7 @@ package domain import ( "bytes" + "context" "testing" "gotest.tools/v3/assert" @@ -130,7 +131,7 @@ func TestResolve(t *testing.T) { dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", myksvc, mykroute, myksvcInOther, mykrouteInOther) for _, c := range cases { i := &RefFlags{c.ref} - result, err := i.Resolve(dynamicClient, "default") + result, err := i.Resolve(context.Background(), dynamicClient, "default") if c.destination != nil { assert.DeepEqual(t, result, c.destination) assert.NilError(t, err) diff --git a/pkg/kn/commands/domain/list.go b/pkg/kn/commands/domain/list.go index e86df096ba..00934129d3 100644 --- a/pkg/kn/commands/domain/list.go +++ b/pkg/kn/commands/domain/list.go @@ -50,7 +50,7 @@ func NewDomainMappingListCommand(p *commands.KnParams) *cobra.Command { return err } - domainMappingList, err := client.ListDomainMappings() + domainMappingList, err := client.ListDomainMappings(cmd.Context()) if err != nil { return err } diff --git a/pkg/kn/commands/domain/update.go b/pkg/kn/commands/domain/update.go index 522276f026..fd24a26339 100644 --- a/pkg/kn/commands/domain/update.go +++ b/pkg/kn/commands/domain/update.go @@ -48,7 +48,7 @@ func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command { return err } - toUpdate, err := client.GetDomainMapping(name) + toUpdate, err := client.GetDomainMapping(cmd.Context(), name) if err != nil { return err } @@ -62,13 +62,13 @@ func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command { return err } - reference, err := refFlags.Resolve(dynamicClient, namespace) + reference, err := refFlags.Resolve(cmd.Context(), dynamicClient, namespace) if err != nil { return err } toUpdate.Spec.Ref = *reference - err = client.UpdateDomainMapping(toUpdate) + err = client.UpdateDomainMapping(cmd.Context(), toUpdate) if err != nil { return knerrors.GetError(err) } diff --git a/pkg/serving/v1alpha1/client.go b/pkg/serving/v1alpha1/client.go index f89522988c..262e9b056a 100644 --- a/pkg/serving/v1alpha1/client.go +++ b/pkg/serving/v1alpha1/client.go @@ -34,19 +34,19 @@ type KnServingClient interface { Namespace() string // GetDomainMapping - GetDomainMapping(name string) (*servingv1alpha1.DomainMapping, error) + GetDomainMapping(ctx context.Context, name string) (*servingv1alpha1.DomainMapping, error) // CreateDomainMapping - CreateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error + CreateDomainMapping(ctx context.Context, domainMapping *servingv1alpha1.DomainMapping) error // UpdateDomainMapping - UpdateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error + UpdateDomainMapping(ctx context.Context, domainMapping *servingv1alpha1.DomainMapping) error // DeleteDomainMapping - DeleteDomainMapping(name string) error + DeleteDomainMapping(ctx context.Context, name string) error // ListDomainMappings - ListDomainMappings() (*servingv1alpha1.DomainMappingList, error) + ListDomainMappings(ctx context.Context) (*servingv1alpha1.DomainMappingList, error) } type knServingClient struct { @@ -68,8 +68,8 @@ func (cl *knServingClient) Namespace() string { } // GetDomainMapping gets DomainMapping by name -func (cl *knServingClient) GetDomainMapping(name string) (*servingv1alpha1.DomainMapping, error) { - dm, err := cl.client.DomainMappings(cl.namespace).Get(context.TODO(), name, v1.GetOptions{}) +func (cl *knServingClient) GetDomainMapping(ctx context.Context, name string) (*servingv1alpha1.DomainMapping, error) { + dm, err := cl.client.DomainMappings(cl.namespace).Get(ctx, name, v1.GetOptions{}) if err != nil { return nil, knerrors.GetError(err) } @@ -81,8 +81,8 @@ func (cl *knServingClient) GetDomainMapping(name string) (*servingv1alpha1.Domai } // CreateDomainMapping creates provided DomainMapping -func (cl *knServingClient) CreateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { - _, err := cl.client.DomainMappings(cl.namespace).Create(context.TODO(), domainMapping, v1.CreateOptions{}) +func (cl *knServingClient) CreateDomainMapping(ctx context.Context, domainMapping *servingv1alpha1.DomainMapping) error { + _, err := cl.client.DomainMappings(cl.namespace).Create(ctx, domainMapping, v1.CreateOptions{}) if err != nil { return knerrors.GetError(err) } @@ -90,8 +90,8 @@ func (cl *knServingClient) CreateDomainMapping(domainMapping *servingv1alpha1.Do } // UpdateDomainMapping updates provided DomainMapping -func (cl *knServingClient) UpdateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { - _, err := cl.client.DomainMappings(cl.namespace).Update(context.TODO(), domainMapping, v1.UpdateOptions{}) +func (cl *knServingClient) UpdateDomainMapping(ctx context.Context, domainMapping *servingv1alpha1.DomainMapping) error { + _, err := cl.client.DomainMappings(cl.namespace).Update(ctx, domainMapping, v1.UpdateOptions{}) if err != nil { return knerrors.GetError(err) } @@ -99,8 +99,8 @@ func (cl *knServingClient) UpdateDomainMapping(domainMapping *servingv1alpha1.Do } // DeleteDomainMapping deletes DomainMapping by name -func (cl *knServingClient) DeleteDomainMapping(name string) error { - err := cl.client.DomainMappings(cl.namespace).Delete(context.TODO(), name, v1.DeleteOptions{}) +func (cl *knServingClient) DeleteDomainMapping(ctx context.Context, name string) error { + err := cl.client.DomainMappings(cl.namespace).Delete(ctx, name, v1.DeleteOptions{}) if err != nil { return knerrors.GetError(err) } @@ -108,8 +108,8 @@ func (cl *knServingClient) DeleteDomainMapping(name string) error { } // ListDomainMappings lists all DomainMappings -func (cl *knServingClient) ListDomainMappings() (*servingv1alpha1.DomainMappingList, error) { - domainMappingList, err := cl.client.DomainMappings(cl.namespace).List(context.TODO(), v1.ListOptions{}) +func (cl *knServingClient) ListDomainMappings(ctx context.Context) (*servingv1alpha1.DomainMappingList, error) { + domainMappingList, err := cl.client.DomainMappings(cl.namespace).List(ctx, v1.ListOptions{}) if err != nil { return nil, knerrors.GetError(err) } diff --git a/pkg/serving/v1alpha1/client_mock.go b/pkg/serving/v1alpha1/client_mock.go index 0ae90d5aa5..036e3d1cdc 100644 --- a/pkg/serving/v1alpha1/client_mock.go +++ b/pkg/serving/v1alpha1/client_mock.go @@ -15,6 +15,7 @@ package v1alpha1 import ( + "context" "testing" "knative.dev/client/pkg/util/mock" @@ -65,7 +66,7 @@ func (sr *ServingRecorder) GetDomainMapping(name interface{}, domainMapping *ser } // GetDomainMapping mock function -func (c *MockKnServingClient) GetDomainMapping(name string) (*servingv1alpha1.DomainMapping, error) { +func (c *MockKnServingClient) GetDomainMapping(ctx context.Context, name string) (*servingv1alpha1.DomainMapping, error) { call := c.recorder.r.VerifyCall("GetDomainMapping", name) return call.Result[0].(*servingv1alpha1.DomainMapping), mock.ErrorOrNil(call.Result[1]) } @@ -76,7 +77,7 @@ func (sr *ServingRecorder) CreateDomainMapping(domainMapping interface{}, err er } // CreateDomainMapping mock function -func (c *MockKnServingClient) CreateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { +func (c *MockKnServingClient) CreateDomainMapping(ctx context.Context, domainMapping *servingv1alpha1.DomainMapping) error { call := c.recorder.r.VerifyCall("CreateDomainMapping", domainMapping) return mock.ErrorOrNil(call.Result[0]) } @@ -87,7 +88,7 @@ func (sr *ServingRecorder) UpdateDomainMapping(domainMapping interface{}, err er } // UpdateDomainMapping mock function -func (c *MockKnServingClient) UpdateDomainMapping(domainMapping *servingv1alpha1.DomainMapping) error { +func (c *MockKnServingClient) UpdateDomainMapping(ctx context.Context, domainMapping *servingv1alpha1.DomainMapping) error { call := c.recorder.r.VerifyCall("UpdateDomainMapping", domainMapping) return mock.ErrorOrNil(call.Result[0]) } @@ -98,7 +99,7 @@ func (sr *ServingRecorder) DeleteDomainMapping(name string, err error) { } // DeleteDomainMapping mock function -func (c *MockKnServingClient) DeleteDomainMapping(name string) error { +func (c *MockKnServingClient) DeleteDomainMapping(ctx context.Context, name string) error { call := c.recorder.r.VerifyCall("DeleteDomainMapping", name) return mock.ErrorOrNil(call.Result[0]) } @@ -109,7 +110,7 @@ func (sr *ServingRecorder) ListDomainMappings(domainMappingList *servingv1alpha1 } // ListDomainMappings mock function -func (c *MockKnServingClient) ListDomainMappings() (*servingv1alpha1.DomainMappingList, error) { +func (c *MockKnServingClient) ListDomainMappings(ctx context.Context) (*servingv1alpha1.DomainMappingList, error) { call := c.recorder.r.VerifyCall("ListDomainMappings") return call.Result[0].(*servingv1alpha1.DomainMappingList), mock.ErrorOrNil(call.Result[1]) } diff --git a/pkg/serving/v1alpha1/client_mock_test.go b/pkg/serving/v1alpha1/client_mock_test.go index 2b008f44bb..c819e7c036 100644 --- a/pkg/serving/v1alpha1/client_mock_test.go +++ b/pkg/serving/v1alpha1/client_mock_test.go @@ -15,6 +15,7 @@ package v1alpha1 import ( + "context" "testing" "knative.dev/serving/pkg/apis/serving/v1alpha1" @@ -34,11 +35,12 @@ func TestMockKnClient(t *testing.T) { recorder.ListDomainMappings(&v1alpha1.DomainMappingList{}, nil) // Call all services - client.GetDomainMapping("hello.foo.bar") - client.CreateDomainMapping(&v1alpha1.DomainMapping{}) - client.DeleteDomainMapping("hello.foo.bar") - client.UpdateDomainMapping(&v1alpha1.DomainMapping{}) - client.ListDomainMappings() + ctx := context.Background() + client.GetDomainMapping(ctx, "hello.foo.bar") + client.CreateDomainMapping(ctx, &v1alpha1.DomainMapping{}) + client.DeleteDomainMapping(ctx, "hello.foo.bar") + client.UpdateDomainMapping(ctx, &v1alpha1.DomainMapping{}) + client.ListDomainMappings(ctx) // Validate recorder.Validate() diff --git a/pkg/serving/v1alpha1/client_test.go b/pkg/serving/v1alpha1/client_test.go index 58fa151d9c..63c0a1dd1c 100644 --- a/pkg/serving/v1alpha1/client_test.go +++ b/pkg/serving/v1alpha1/client_test.go @@ -15,6 +15,7 @@ package v1alpha1 import ( + "context" "fmt" "testing" @@ -62,7 +63,7 @@ func TestGetDomainMapping(t *testing.T) { }) t.Run("get domain mapping by name returns object", func(t *testing.T) { - domainMapping, err := client.GetDomainMapping(domainName) + domainMapping, err := client.GetDomainMapping(context.Background(), domainName) assert.NilError(t, err) assert.Equal(t, domainName, domainMapping.Name, "domain mapping name should be equal") validateGroupVersionKind(t, domainMapping) @@ -70,7 +71,7 @@ func TestGetDomainMapping(t *testing.T) { t.Run("get non-existing domain mapping by name returns error", func(t *testing.T) { nonExistingName := "does-not-exist" - service, err := client.GetDomainMapping(nonExistingName) + service, err := client.GetDomainMapping(context.Background(), nonExistingName) assert.Assert(t, service == nil, "no domain mapping should be returned") assert.ErrorContains(t, err, "not found") assert.ErrorContains(t, err, nonExistingName) @@ -94,14 +95,14 @@ func TestCreateDomainMapping(t *testing.T) { }) t.Run("create domain mapping without error creates a new object", func(t *testing.T) { - err := client.CreateDomainMapping(domainMapping) + err := client.CreateDomainMapping(context.Background(), domainMapping) assert.NilError(t, err) assert.Equal(t, domainMapping.Generation, int64(2)) validateGroupVersionKind(t, domainMapping) }) t.Run("create domain mapping with an error returns an error object", func(t *testing.T) { - err := client.CreateDomainMapping(createDomainMapping("unknown", createServiceRef(serviceName, testNamespace))) + err := client.CreateDomainMapping(context.Background(), createDomainMapping("unknown", createServiceRef(serviceName, testNamespace))) assert.ErrorContains(t, err, "unknown") }) } @@ -126,13 +127,13 @@ func TestUpdateDomainMapping(t *testing.T) { }) t.Run("update domain mapping without error", func(t *testing.T) { - err := client.UpdateDomainMapping(domainMappingUpdate) + err := client.UpdateDomainMapping(context.Background(), domainMappingUpdate) assert.NilError(t, err) validateGroupVersionKind(t, domainMappingUpdate) }) t.Run("update domain mapping with error", func(t *testing.T) { - err := client.UpdateDomainMapping(createDomainMapping("unknown", createServiceRef(serviceName, testNamespace))) + err := client.UpdateDomainMapping(context.Background(), createDomainMapping("unknown", createServiceRef(serviceName, testNamespace))) assert.ErrorContains(t, err, "unknown") }) } @@ -153,13 +154,13 @@ func TestDeleteDomainMapping(t *testing.T) { }) t.Run("delete domain mapping returns no error", func(t *testing.T) { - err := client.DeleteDomainMapping(domainName) + err := client.DeleteDomainMapping(context.Background(), domainName) assert.NilError(t, err) }) t.Run("delete non-existing domain mapping returns error", func(t *testing.T) { nonExistingName := "does-not-exist" - err := client.DeleteDomainMapping(nonExistingName) + err := client.DeleteDomainMapping(context.Background(), nonExistingName) assert.ErrorContains(t, err, "not found") assert.ErrorContains(t, err, nonExistingName) assert.ErrorType(t, err, &errors.StatusError{}) @@ -177,7 +178,7 @@ func TestListDomainMappings(t *testing.T) { assert.Equal(t, testNamespace, a.GetNamespace()) return true, &servingv1alpha1.DomainMappingList{Items: []servingv1alpha1.DomainMapping{*dm1, *dm2, *dm3}}, nil }) - listServices, err := client.ListDomainMappings() + listServices, err := client.ListDomainMappings(context.Background()) assert.NilError(t, err) assert.Assert(t, len(listServices.Items) == 3) assert.Equal(t, listServices.Items[0].Name, "dm-1")