Add DomainMapping CRUD commands (#1267)

* Add DomainMapping CRUD commands

* fix: Reflect review feedback

* fix: Fix prealloc lint error

* fix: Add specific reference resolution

* fix: Remove Kubernetes support in ref

* chore: Add context param to client functions
This commit is contained in:
David Simansky 2021-04-01 17:53:23 +02:00 committed by GitHub
parent 8bcefe59d5
commit 10ea0df157
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2625 additions and 11 deletions

View File

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

View File

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

33
docs/cmd/kn_domain.md Normal file
View File

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

View File

@ -0,0 +1,38 @@
## kn domain create
Create a domain mapping
```
kn domain create NAME
```
### 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, 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
```
--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

View File

@ -0,0 +1,37 @@
## kn domain delete
Delete a domain mapping
```
kn domain delete NAME
```
### 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

View File

@ -0,0 +1,33 @@
## kn domain describe
Show details of a domain mapping
```
kn domain describe NAME
```
### 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

View File

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

View File

@ -0,0 +1,38 @@
## kn domain update
Update a domain mapping
```
kn domain update NAME
```
### Examples
```
# Update a domain mappings 'hello.example.com' for Knative service 'hello'
kn domain create hello.example.com --refFlags 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, 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
```
--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

View File

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

View File

@ -0,0 +1,77 @@
// 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"
clientv1alpha1 "knative.dev/client/pkg/serving/v1alpha1"
)
// NewDomainMappingCreateCommand to create event channels
func NewDomainMappingCreateCommand(p *commands.KnParams) *cobra.Command {
var refFlags RefFlags
cmd := &cobra.Command{
Use: "create NAME",
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
}
reference, err := refFlags.Resolve(cmd.Context(), dynamicClient, namespace)
if err != nil {
return err
}
builder := clientv1alpha1.NewDomainMappingBuilder(name).
Namespace(namespace).
Reference(*reference)
client, err := p.NewServingV1alpha1Client(namespace)
if err != nil {
return err
}
err = client.CreateDomainMapping(cmd.Context(), 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.Add(cmd)
cmd.MarkFlagRequired("ref")
return cmd
}

View File

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

View File

@ -0,0 +1,62 @@
// 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 NAME",
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(cmd.Context(), 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
}

View File

@ -0,0 +1,66 @@
// 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"
"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 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"))
// 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()
}

View File

@ -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"
)
// 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 NAME",
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(cmd.Context(), 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
}

View File

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

View File

@ -0,0 +1,111 @@
// 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 (
"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
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(NewDomainMappingDescribeCommand(p))
domainCmd.AddCommand(NewDomainMappingUpdateCommand(p))
domainCmd.AddCommand(NewDomainMappingDeleteCommand(p))
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",
},
}
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 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(ctx context.Context, knclient clientdynamic.KnDynamicClient, namespace string) (*duckv1.KReference, error) {
client := knclient.RawClient()
if f.reference == "" {
return nil, nil
}
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(ctx, 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, 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], ""
}
}

View File

@ -0,0 +1,191 @@
// 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"
"context"
"testing"
"gotest.tools/v3/assert"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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"
duckv1 "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
const kubeConfig = `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`
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")
subCommands := make([]string, 0, len(domainCmd.Commands()))
for _, cmd := range domainCmd.Commands() {
subCommands = append(subCommands, cmd.Name())
}
expectedSubCommands := []string{"create", "delete", "describe", "list", "update"}
assert.DeepEqual(t, subCommands, expectedSubCommands)
}
type resolveCase struct {
ref string
destination *duckv1.KReference
errContents string
}
func TestResolve(t *testing.T) {
myksvc := &servingv1.Service{
TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "serving.knative.dev/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "myksvc", Namespace: "default"},
}
mykroute := &servingv1.Route{
TypeMeta: metav1.TypeMeta{Kind: "Route", APIVersion: "serving.knative.dev/v1"},
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"},
}
mykrouteInOther := &servingv1.Route{
TypeMeta: metav1.TypeMeta{Kind: "Route", APIVersion: "serving.knative.dev/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "mykroute", Namespace: "other"},
}
cases := []resolveCase{
// Test 'name' is considered as Knative service
{"myksvc", &duckv1.KReference{Kind: "Service",
APIVersion: "serving.knative.dev/v1",
Namespace: "default",
Name: "myksvc"}, ""},
// Test 'type:name' format
{"ksvc:myksvc", &duckv1.KReference{Kind: "Service",
APIVersion: "serving.knative.dev/v1",
Namespace: "default",
Name: "myksvc"}, ""},
{"kroute:mykroute", &duckv1.KReference{Kind: "Route",
APIVersion: "serving.knative.dev/v1",
Namespace: "default",
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", myksvc, mykroute, myksvcInOther, mykrouteInOther)
for _, c := range cases {
i := &RefFlags{c.ref}
result, err := i.Resolve(context.Background(), 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
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 duckv1.KReference) *servingv1alpha1.DomainMapping {
return clientservingv1alpha1.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,
}
}

View File

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

View File

@ -0,0 +1,69 @@
// 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
}
if namespace == "" {
listFlags.EnsureWithNamespace()
}
client, err := p.NewServingV1alpha1Client(namespace)
if err != nil {
return err
}
domainMappingList, err := client.ListDomainMappings(cmd.Context())
if err != nil {
return err
}
if len(domainMappingList.Items) == 0 {
fmt.Fprintf(cmd.OutOrStdout(), "No domain mapping found.\n")
return nil
}
return listFlags.Print(domainMappingList, cmd.OutOrStdout())
},
}
commands.AddNamespaceFlags(cmd.Flags(), true)
listFlags.AddFlags(cmd)
return cmd
}

View File

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

View File

@ -0,0 +1,84 @@
// 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"
)
// NewDomainMappingUpdateCommand to create event channels
func NewDomainMappingUpdateCommand(p *commands.KnParams) *cobra.Command {
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 --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")
}
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(cmd.Context(), 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
}
reference, err := refFlags.Resolve(cmd.Context(), dynamicClient, namespace)
if err != nil {
return err
}
toUpdate.Spec.Ref = *reference
err = client.UpdateDomainMapping(cmd.Context(), 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.Add(cmd)
cmd.MarkFlagRequired("refFlags")
return cmd
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,166 @@
// 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(ctx context.Context, name string) (*servingv1alpha1.DomainMapping, error)
// CreateDomainMapping
CreateDomainMapping(ctx context.Context, domainMapping *servingv1alpha1.DomainMapping) error
// UpdateDomainMapping
UpdateDomainMapping(ctx context.Context, domainMapping *servingv1alpha1.DomainMapping) error
// DeleteDomainMapping
DeleteDomainMapping(ctx context.Context, name string) error
// ListDomainMappings
ListDomainMappings(ctx context.Context) (*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,
}
}
// 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(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)
}
err = updateServingGvk(dm)
if err != nil {
return nil, err
}
return dm, nil
}
// CreateDomainMapping creates provided DomainMapping
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)
}
return updateServingGvk(domainMapping)
}
// UpdateDomainMapping updates provided DomainMapping
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)
}
return updateServingGvk(domainMapping)
}
// DeleteDomainMapping deletes DomainMapping by name
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)
}
return nil
}
// ListDomainMappings lists all DomainMappings
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)
}
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
}

View File

@ -0,0 +1,116 @@
// 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"
"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(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])
}
// 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context) (*servingv1alpha1.DomainMappingList, error) {
call := c.recorder.r.VerifyCall("ListDomainMappings")
return call.Result[0].(*servingv1alpha1.DomainMappingList), mock.ErrorOrNil(call.Result[1])
}

View File

@ -0,0 +1,47 @@
// 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"
"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
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()
}

View File

@ -0,0 +1,212 @@
// 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"
"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(context.Background(), 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(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)
})
}
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(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(context.Background(), 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(context.Background(), domainMappingUpdate)
assert.NilError(t, err)
validateGroupVersionKind(t, domainMappingUpdate)
})
t.Run("update domain mapping with error", func(t *testing.T) {
err := client.UpdateDomainMapping(context.Background(), 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(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(context.Background(), 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(context.Background())
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,
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

2
vendor/modules.txt vendored
View File

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