Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(source): Add 'kn source list' #666

Merged
merged 12 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
|===
| | Description | PR

| 🎁
| Add `kn source list`
| https://github.com/knative/client/pull/666[#666]

| 🎁
| Add JSON/YAML output format for version command
| https://github.com/knative/client/pull/709[#709]
Expand Down
1 change: 1 addition & 0 deletions docs/cmd/kn_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ kn source [flags]
* [kn](kn.md) - Knative client
* [kn source apiserver](kn_source_apiserver.md) - Kubernetes API Server Event Source command group
* [kn source binding](kn_source_binding.md) - Sink binding command group
* [kn source list](kn_source_list.md) - List available sources
* [kn source list-types](kn_source_list-types.md) - List available source types
* [kn source ping](kn_source_ping.md) - Ping source command group

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

List available sources

### Synopsis

List available sources

```
kn source list [flags]
```

### Examples

```

# List available eventing sources
kn source list

# List PingSource type sources
kn source list --type=PingSource

# List PingSource and ApiServerSource types sources
kn source list --type=PingSource --type=apiserversource
```

### 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-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].
-t, --type strings Filter list on given source type. This flag can be given multiple times.
```

### Options inherited from parent commands

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

### SEE ALSO

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

1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ require (
contrib.go.opencensus.io/exporter/prometheus v0.1.0 // indirect
contrib.go.opencensus.io/exporter/stackdriver v0.13.0 // indirect
github.com/google/go-containerregistry v0.0.0-20200304201134-fcc8ea80e26f // indirect
github.com/magiconair/properties v1.8.0
github.com/mitchellh/go-homedir v1.1.0
github.com/openzipkin/zipkin-go v0.2.2 // indirect
github.com/pkg/errors v0.8.1
Expand Down
59 changes: 59 additions & 0 deletions pkg/dynamic/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"

"knative.dev/client/pkg/util"
)

const (
Expand All @@ -43,6 +45,9 @@ type KnDynamicClient interface {
// ListSourceCRDs returns list of eventing sources CRDs
ListSourcesTypes() (*unstructured.UnstructuredList, error)

// ListSources returns list of available source objects
ListSources(types ...WithType) (*unstructured.UnstructuredList, error)

// RawClient returns the raw dynamic client interface
RawClient() dynamic.Interface
}
Expand Down Expand Up @@ -94,3 +99,57 @@ func (c *knDynamicClient) ListSourcesTypes() (*unstructured.UnstructuredList, er
func (c knDynamicClient) RawClient() dynamic.Interface {
return c.client
}

// ListSources returns list of available sources objects
// Provide the list of source types as for example: WithTypes("pingsource", "apiserversource"...) to list
// only given types of source objects
func (c *knDynamicClient) ListSources(types ...WithType) (*unstructured.UnstructuredList, error) {
var (
sourceList unstructured.UnstructuredList
options metav1.ListOptions
numberOfsourceTypesFound int
)
sourceTypes, err := c.ListSourcesTypes()
navidshaikh marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
namespace := c.Namespace()
filters := WithTypes(types).List()
// For each source type available, find out each source types objects
for _, source := range sourceTypes.Items {
// find source kind before hand to fail early
sourceKind, err := kindFromUnstructured(&source)
if err != nil {
return nil, err
}

if len(filters) > 0 && !util.SliceContainsIgnoreCase(filters, sourceKind) {
continue
}

// find source's GVR from unstructured source type object
gvr, err := gvrFromUnstructured(&source)
if err != nil {
return nil, err
}

// list objects of source type with this GVR
sList, err := c.client.Resource(gvr).Namespace(namespace).List(options)
navidshaikh marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

if len(sList.Items) > 0 {
// keep a track if we found source objects of different types
numberOfsourceTypesFound++
sourceList.Items = append(sourceList.Items, sList.Items...)
sourceList.SetGroupVersionKind(sList.GetObjectKind().GroupVersionKind())
}
}
// Clear the Group and Version for list if there are multiple types of source objects found
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// Keep the source's GVK if there is only one type of source objects found or requested via --type filter
if numberOfsourceTypesFound > 1 {
sourceList.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "", Kind: "List"})
}
return &sourceList, nil
}
134 changes: 107 additions & 27 deletions pkg/dynamic/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
package dynamic

import (
"strings"
"testing"

"github.com/magiconair/properties/assert"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
Expand All @@ -26,45 +27,29 @@ import (
dynamicfake "k8s.io/client-go/dynamic/fake"
eventingv1alpha1 "knative.dev/eventing/pkg/apis/eventing/v1alpha1"
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
)

const testNamespace = "testns"
"knative.dev/client/pkg/util"
)

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,
},
},
},
}
}
const testNamespace = "current"

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

func TestListCRDs(t *testing.T) {
client := createFakeKnDynamicClient(
testNamespace,
newUnstructured("foo"),
newUnstructured("bar"),
newSourceCRDObj("foo"),
newSourceCRDObj("bar"),
)
assert.Check(t, client.RawClient() != nil)

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.NilError(t, err)
assert.Equal(t, len(uList.Items), 2)
})

Expand All @@ -84,8 +69,8 @@ func TestListCRDs(t *testing.T) {
func TestListSourceTypes(t *testing.T) {
client := createFakeKnDynamicClient(
testNamespace,
newUnstructured("foo"),
newUnstructured("bar"),
newSourceCRDObj("foo"),
newSourceCRDObj("bar"),
)

t.Run("List source types", func(t *testing.T) {
Expand All @@ -100,12 +85,107 @@ func TestListSourceTypes(t *testing.T) {
})
}

func TestListSources(t *testing.T) {
t.Run("No GVRs set", func(t *testing.T) {
obj := newSourceCRDObj("foo")
client := createFakeKnDynamicClient(testNamespace, obj)
assert.Check(t, client.RawClient() != nil)
_, err := client.ListSources()
assert.Check(t, err != nil)
assert.Check(t, util.ContainsAll(err.Error(), "can't", "find", "source", "kind", "CRD"))
})

t.Run("source list empty", func(t *testing.T) {
client := createFakeKnDynamicClient(testNamespace,
newSourceCRDObjWithSpec("pingsources", "sources.knative.dev", "v1alpha1", "PingSource"),
)
sources, err := client.ListSources()
assert.NilError(t, err)
assert.Equal(t, len(sources.Items), 0)
})

t.Run("source list non empty", func(t *testing.T) {
client := createFakeKnDynamicClient(testNamespace,
newSourceCRDObjWithSpec("pingsources", "sources.knative.dev", "v1alpha1", "PingSource"),
newSourceCRDObjWithSpec("apiserversources", "sources.knative.dev", "v1alpha1", "ApiServerSource"),
newSourceCRDObjWithSpec("cronjobsources", "sources.knative.dev", "v1alpha1", "CronJobSource"),
newSourceUnstructuredObj("p1", "sources.knative.dev/v1alpha1", "PingSource"),
newSourceUnstructuredObj("a1", "sources.knative.dev/v1alpha1", "ApiServerSource"),
newSourceUnstructuredObj("c1", "sources.knative.dev/v1alpha1", "CronJobSource"),
)
sources, err := client.ListSources(WithTypeFilter("pingsource"), WithTypeFilter("ApiServerSource"))
assert.NilError(t, err)
assert.Equal(t, len(sources.Items), 2)
})
}

// createFakeKnDynamicClient gives you a dynamic client for testing containing the given objects.
// See also the one in the fake package. Duplicated here to avoid a dependency loop.
func createFakeKnDynamicClient(testNamespace string, objects ...runtime.Object) KnDynamicClient {
scheme := runtime.NewScheme()
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "serving.knative.dev", Version: "v1alpha1", Kind: "Service"}, &servingv1.Service{})
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1alpha1", Kind: "Broker"}, &eventingv1alpha1.Broker{})

client := dynamicfake.NewSimpleDynamicClient(scheme, objects...)
return NewKnDynamicClient(client, testNamespace)
}

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

func newSourceCRDObjWithSpec(name, group, version, kind string) *unstructured.Unstructured {
obj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": crdGroup + "/" + crdVersion,
"kind": crdKind,
"metadata": map[string]interface{}{
"namespace": testNamespace,
"name": name,
},
},
}

obj.Object["spec"] = map[string]interface{}{
"group": group,
"version": version,
"names": map[string]interface{}{
"kind": kind,
"plural": strings.ToLower(kind) + "s",
},
}
obj.SetLabels(labels.Set{sourcesLabelKey: sourcesLabelValue})
return obj
}

func newSourceUnstructuredObj(name, apiVersion, kind string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": apiVersion,
"kind": kind,
"metadata": map[string]interface{}{
"namespace": "current",
"name": name,
},
"spec": map[string]interface{}{
"sink": map[string]interface{}{
"ref": map[string]interface{}{
"kind": "Service",
"name": "foo",
},
},
},
},
}
}
Loading