Skip to content

Commit

Permalink
kn source list-types (builtin types) (#536)
Browse files Browse the repository at this point in the history
* feat: kn source list-types (builtin types)

 - Only lists the builtin source types
 - Uses client-go dynamic client for listing CRDs
 - Adds DyanmicClient interface to KnParams
 - Adds printing options

* chore(lint): Address golint suggestions

* Rebase and update venodr/modules.txt

* Adds unit tests for DynamicClient in types.go

* Add kn source list-types command in smoke tests

* Constants for the CRD GVR and source identifier label key value

 GVR as:
   - Group: apiextensions.k8s.io
   - Version: v1beta1
   - Resource: customresourcedefinitions
 Label as:
   {"duck.knative.dev/source": "true"}

* Add tests for dynamic client

* Add description about SinkBinding source type

  as - "Binding Pattern for ContainerSource"

* Remove unused imports

* Adds unit tests for list-types command processing

* More unit tests for flags

* Adds e2e tests for kn source list-types

  - also test the YAML output

* Sort the source types while listing them

 - Update the unit tests accordingly

* Add examples

* Add unit tests for CreateDynamicTestKnCommand

* Fix typo in unit tests

* golint fixes

* Updates to vendor/modules.txt after rebase

* Remove the extra lines
  • Loading branch information
navidshaikh authored and knative-prow-robot committed Dec 10, 2019
1 parent 003dba3 commit 7dbb5a5
Show file tree
Hide file tree
Showing 21 changed files with 1,280 additions and 67 deletions.
1 change: 1 addition & 0 deletions docs/cmd/kn_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ kn source [flags]

* [kn](kn.md) - Knative client
* [kn source apiserver](kn_source_apiserver.md) - Kubernetes API Server Event Source command group
* [kn source list-types](kn_source_list-types.md) - List available source types

46 changes: 46 additions & 0 deletions docs/cmd/kn_source_list-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## kn source list-types

List available source types

### Synopsis

List available source types

```
kn source list-types [flags]
```

### Examples

```
# List available eventing source types
kn source list-types
# List available eventing source types in JSON format
kn source list-types -o yaml
```

### 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 list-types
-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-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

```
--config string kn config file (default is $HOME/.kn/config.yaml)
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
--log-http log http traffic
```

### SEE ALSO

* [kn source](kn_source.md) - Event Source command group

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ require (
contrib.go.opencensus.io/exporter/prometheus v0.1.0 // indirect
contrib.go.opencensus.io/exporter/stackdriver v0.12.5 // indirect; indirect needed by knative serving
github.com/google/go-containerregistry v0.0.0-20191029173801-50b26ee28691 // indirect
github.com/magiconair/properties v1.8.0
github.com/markbates/inflect v1.0.4 // indirect
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a // indirect
github.com/mitchellh/go-homedir v1.1.0
Expand Down
89 changes: 89 additions & 0 deletions pkg/dynamic/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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 dynamic

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
)

const (
crdGroup = "apiextensions.k8s.io"
crdVersion = "v1beta1"
crdKind = "CustomResourceDefinition"
crdKinds = "customresourcedefinitions"
sourcesLabelKey = "duck.knative.dev/source"
sourcesLabelValue = "true"
)

// KnDynamicClient to client-go Dynamic client. All methods are relative to the
// namespace specified during construction
type KnDynamicClient interface {
// Namespace in which this client is operating for
Namespace() string

// ListCRDs returns list of CRDs with their type and name
ListCRDs(options metav1.ListOptions) (*unstructured.UnstructuredList, error)

// ListSourceCRDs returns list of eventing sources CRDs
ListSourcesTypes() (*unstructured.UnstructuredList, error)
}

// knDynamicClient is a combination of client-go Dynamic client interface and namespace
type knDynamicClient struct {
client dynamic.Interface
namespace string
}

// NewKnDynamicClient is to invoke Eventing Sources Client API to create object
func NewKnDynamicClient(client dynamic.Interface, namespace string) KnDynamicClient {
return &knDynamicClient{
client: client,
namespace: namespace,
}
}

// Return the client's namespace
func (c *knDynamicClient) Namespace() string {
return c.namespace
}

// TODO(navidshaikh): Use ListConfigs here instead of ListOptions
// ListCRDs returns list of installed CRDs in the cluster and filters based on the given options
func (c *knDynamicClient) ListCRDs(options metav1.ListOptions) (*unstructured.UnstructuredList, error) {
gvr := schema.GroupVersionResource{
Group: crdGroup,
Version: crdVersion,
Resource: crdKinds,
}

uList, err := c.client.Resource(gvr).List(options)
if err != nil {
return nil, err
}

return uList, nil
}

// ListSourcesTypes returns installed knative eventing sources CRDs
func (c *knDynamicClient) ListSourcesTypes() (*unstructured.UnstructuredList, error) {
options := metav1.ListOptions{}
sourcesLabels := labels.Set{sourcesLabelKey: sourcesLabelValue}
options.LabelSelector = sourcesLabels.String()
return c.ListCRDs(options)
}
101 changes: 101 additions & 0 deletions pkg/dynamic/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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 dynamic

import (
"testing"

"github.com/magiconair/properties/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic/fake"
)

const testNamespace = "testns"

func newUnstructured(name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": crdGroup + "/" + crdVersion,
"kind": crdKind,
"metadata": map[string]interface{}{
"namespace": testNamespace,
"name": name,
"labels": map[string]interface{}{
sourcesLabelKey: sourcesLabelValue,
},
},
},
}
}

func createFakeKnDynamicClient(objects ...runtime.Object) KnDynamicClient {
client := fake.NewSimpleDynamicClient(runtime.NewScheme(), objects...)
return NewKnDynamicClient(client, testNamespace)
}

func TestNamespace(t *testing.T) {
client := createFakeKnDynamicClient(newUnstructured("foo"))
assert.Equal(t, client.Namespace(), testNamespace)
}

func TestListCRDs(t *testing.T) {
client := createFakeKnDynamicClient(
newUnstructured("foo"),
newUnstructured("bar"),
)

t.Run("List CRDs with match", func(t *testing.T) {
options := metav1.ListOptions{}
uList, err := client.ListCRDs(options)
if err != nil {
t.Fatal(err)
}

assert.Equal(t, len(uList.Items), 2)
})

t.Run("List CRDs without match", func(t *testing.T) {
options := metav1.ListOptions{}
sourcesLabels := labels.Set{"duck.knative.dev/source": "true1"}
options.LabelSelector = sourcesLabels.String()
uList, err := client.ListCRDs(options)
if err != nil {
t.Fatal(err)
}

assert.Equal(t, len(uList.Items), 0)
})
}

func TestListSourceTypes(t *testing.T) {
client := createFakeKnDynamicClient(
newUnstructured("foo"),
newUnstructured("bar"),
)

t.Run("List source types", func(t *testing.T) {
uList, err := client.ListSourcesTypes()
if err != nil {
t.Fatal(err)
}

assert.Equal(t, len(uList.Items), 2)
assert.Equal(t, uList.Items[0].GetName(), "foo")
assert.Equal(t, uList.Items[1].GetName(), "bar")
})
}
2 changes: 1 addition & 1 deletion pkg/eventing/sources/v1alpha1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ type knSourcesClient struct {
// NewKnSourcesClient is to invoke Eventing Sources Client API to create object
func NewKnSourcesClient(client client_v1alpha1.SourcesV1alpha1Interface, namespace string) KnSourcesClient {
return &knSourcesClient{
namespace: namespace,
client: client,
namespace: namespace,
}
}

Expand Down
85 changes: 85 additions & 0 deletions pkg/kn/commands/source/human_readable_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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 source

import (
"fmt"
"sort"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
hprinters "knative.dev/client/pkg/printers"
)

var sourceTypeDescription = map[string]string{
"ApiServerSource": "Kubernetes API Server events source",
"ContainerSource": "Container events source",
"CronJobSource": "CronJob events source",
"SinkBinding": "Binding Pattern for ContainerSource",
}

func getSourceTypeDescription(kind string) string {
return sourceTypeDescription[kind]
}

// ListTypesHandlers handles printing human readable table for `kn source list-types` command's output
func ListTypesHandlers(h hprinters.PrintHandler) {
sourceTypesColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Type", Type: "string", Description: "Kind / Type of the source type", Priority: 1},
{Name: "Name", Type: "string", Description: "Name of the source type", Priority: 1},
{Name: "Description", Type: "string", Description: "Description of the source type", Priority: 1},
}
h.TableHandler(sourceTypesColumnDefinitions, printSourceTypes)
h.TableHandler(sourceTypesColumnDefinitions, printSourceTypesList)
}

// printSourceTypes populates a single row of source types list table
func printSourceTypes(sourceType unstructured.Unstructured, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
name := sourceType.GetName()
content := sourceType.UnstructuredContent()
kind, found, err := unstructured.NestedString(content, "spec", "names", "kind")
if err != nil {
return nil, err
}

if !found {
return nil, fmt.Errorf("can't find specs.names.kind for %s", name)
}

row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: &sourceType},
}
row.Cells = append(row.Cells, kind, name, getSourceTypeDescription(kind))
return []metav1beta1.TableRow{row}, nil
}

// printSourceTypesList populates the source types list table rows
func printSourceTypesList(sourceTypesList *unstructured.UnstructuredList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(sourceTypesList.Items))

sort.SliceStable(sourceTypesList.Items, func(i, j int) bool {
return sourceTypesList.Items[i].GetName() < sourceTypesList.Items[j].GetName()
})
for _, item := range sourceTypesList.Items {
row, err := printSourceTypes(item, options)
if err != nil {
return nil, err
}

rows = append(rows, row...)
}
return rows, nil
}
Loading

0 comments on commit 7dbb5a5

Please sign in to comment.