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: List available channel types #1027

Merged
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 @@ -38,6 +38,10 @@
| Add alias to commands
| https://github.com/knative/client/pull/1041[#1041]

| 🎁
| Add channel list-types
| https://github.com/knative/client/pull/1027[#1027]

|===

## v0.17.0 (2020-08-26)
Expand Down
1 change: 1 addition & 0 deletions docs/cmd/kn_channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ kn channel COMMAND
* [kn channel delete](kn_channel_delete.md) - Delete a channel
* [kn channel describe](kn_channel_describe.md) - Show details of a channel
* [kn channel list](kn_channel_list.md) - List channels (alias: 'ls')
* [kn channel list-types](kn_channel_list-types.md) - List channel types
Copy link
Contributor

Choose a reason for hiding this comment

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

While just thinking about this, why not just kn channel types ? I know this violates the schema, but it looks imo nice (same for kn source list-types). At the end "list-types" is also not a verb but an artificial word construct.
wdyt @navidshaikh @maximilien ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I liked that. I'd keep it list-types for this PR and change for source and channel together in a subsequent PR (keeping list-types as an alias for source). cc @Kaustubh-pande


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

List channel types

### Synopsis

List channel types

```
kn channel list-types
```

### Examples

```

# List available channel types
kn channel list-types

# List available channel types in YAML format
kn channel 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 configuration file (default: ~/.config/kn/config.yaml)
--kubeconfig string kubectl configuration file (default: ~/.kube/config)
--log-http log http traffic
```

### SEE ALSO

* [kn channel](kn_channel.md) - Manage event channels (alias: channels)

9 changes: 9 additions & 0 deletions lib/test/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,12 @@ func ChannelDelete(r *KnRunResultCollector, cname string) {
r.AssertNoError(out)
assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "channel", cname, "deleted"))
}

//ChannelListTypes return available channel types
func ChannelListTypes(r *KnRunResultCollector, args ...string) string {
Kaustubh-pande marked this conversation as resolved.
Show resolved Hide resolved
cmd := []string{"channel", "list-types"}
cmd = append(cmd, args...)
out := r.KnTest().Kn().Run(cmd...)
r.AssertNoError(out)
return out.Stdout
}
91 changes: 81 additions & 10 deletions pkg/dynamic/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,29 @@ import (
"strings"

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"

"knative.dev/client/pkg/util"
"knative.dev/eventing/pkg/apis/messaging"
)

const (
crdGroup = "apiextensions.k8s.io"
crdVersion = "v1beta1"
crdKind = "CustomResourceDefinition"
crdKinds = "customresourcedefinitions"
sourcesLabelKey = "duck.knative.dev/source"
sourcesLabelValue = "true"
sourceListGroup = "client.knative.dev"
sourceListVersion = "v1alpha1"
sourceListKind = "SourceList"
crdGroup = "apiextensions.k8s.io"
crdVersion = "v1beta1"
crdKind = "CustomResourceDefinition"
crdKinds = "customresourcedefinitions"
sourcesLabelKey = "duck.knative.dev/source"
sourcesLabelValue = "true"
sourceListGroup = "client.knative.dev"
sourceListVersion = "v1alpha1"
sourceListKind = "SourceList"
channelLabelValue = "true"
channelListVersion = "v1beta1"
channelListKind = "ChannelList"
channelKind = "Channel"
)

// KnDynamicClient to client-go Dynamic client. All methods are relative to the
Expand All @@ -58,6 +63,12 @@ type KnDynamicClient interface {
// ListSourcesUsingGVKs returns list of available source objects using given list of GVKs
ListSourcesUsingGVKs(*[]schema.GroupVersionKind, ...WithType) (*unstructured.UnstructuredList, error)

// ListChannelsTypes returns installed knative channel CRDs
ListChannelsTypes() (*unstructured.UnstructuredList, error)

// ListChannelsUsingGVKs returns list of available channel objects using given list of GVKs
ListChannelsUsingGVKs(*[]schema.GroupVersionKind, ...WithType) (*unstructured.UnstructuredList, error)

// RawClient returns the raw dynamic client interface
RawClient() dynamic.Interface
}
Expand Down Expand Up @@ -106,6 +117,30 @@ func (c *knDynamicClient) ListSourcesTypes() (*unstructured.UnstructuredList, er
return c.ListCRDs(options)
}

// ListChannelsTypes returns installed knative channel CRDs
func (c *knDynamicClient) ListChannelsTypes() (*unstructured.UnstructuredList, error) {
var ChannelTypeList unstructured.UnstructuredList
options := metav1.ListOptions{}
channelsLabels := labels.Set{messaging.SubscribableDuckVersionAnnotation: channelLabelValue}
options.LabelSelector = channelsLabels.String()
uList, err := c.ListCRDs(options)
if err != nil {
return nil, err
}
ChannelTypeList.Object = uList.Object
for _, channelType := range uList.Items {
content := channelType.UnstructuredContent()
channelTypeKind, _, err := unstructured.NestedString(content, "spec", "names", "kind")
if err != nil {
return nil, err
}
if !util.SliceContainsIgnoreCase([]string{channelKind}, channelTypeKind) {
ChannelTypeList.Items = append(ChannelTypeList.Items, channelType)
}
}
return &ChannelTypeList, nil
}

func (c knDynamicClient) RawClient() dynamic.Interface {
return c.client
}
Expand Down Expand Up @@ -198,3 +233,39 @@ func (c *knDynamicClient) ListSourcesUsingGVKs(gvks *[]schema.GroupVersionKind,
}
return &sourceList, nil
}

// ListChannelsUsingGVKs returns list of available channel objects using given list of GVKs
func (c *knDynamicClient) ListChannelsUsingGVKs(gvks *[]schema.GroupVersionKind, types ...WithType) (*unstructured.UnstructuredList, error) {
if gvks == nil {
return nil, nil
}

var (
channelList unstructured.UnstructuredList
options metav1.ListOptions
)
namespace := c.Namespace()
filters := WithTypes(types).List()

for _, gvk := range *gvks {
if len(filters) > 0 && !util.SliceContainsIgnoreCase(filters, gvk.Kind) {
continue
}

gvr := gvk.GroupVersion().WithResource(strings.ToLower(gvk.Kind) + "s")

// list objects of chaneel type with this GVR
cList, err := c.client.Resource(gvr).Namespace(namespace).List(context.TODO(), options)
if err != nil {
return nil, err
}

if len(cList.Items) > 0 {
channelList.Items = append(channelList.Items, cList.Items...)
}
}
if len(channelList.Items) > 0 {
channelList.SetGroupVersionKind(schema.GroupVersionKind{Group: messaging.GroupName, Version: channelListVersion, Kind: channelListKind})
}
return &channelList, nil
}
22 changes: 22 additions & 0 deletions pkg/dynamic/client_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ func (c *MockKnDynamicClient) ListSourcesTypes() (*unstructured.UnstructuredList
return call.Result[0].(*unstructured.UnstructuredList), mock.ErrorOrNil(call.Result[1])
}

// ListChannelsTypes returns installed knative messaging CRDs
func (dr *ClientRecorder) ListChannelsTypes(ulist *unstructured.UnstructuredList, err error) {
dr.r.Add("ListChannelsTypes", []interface{}{}, []interface{}{ulist, err})
}

// ListChannelsTypes returns installed knative messaging CRDs
func (c *MockKnDynamicClient) ListChannelsTypes() (*unstructured.UnstructuredList, error) {
call := c.recorder.r.VerifyCall("ListChannelsTypes")
return call.Result[0].(*unstructured.UnstructuredList), mock.ErrorOrNil(call.Result[1])
}

// ListSources returns list of available sources objects
func (dr *ClientRecorder) ListSources(types interface{}, ulist *unstructured.UnstructuredList, err error) {
dr.r.Add("ListSources", []interface{}{types}, []interface{}{ulist, err})
Expand Down Expand Up @@ -119,3 +130,14 @@ func (c *MockKnDynamicClient) ListSourcesUsingGVKs(gvks *[]schema.GroupVersionKi
func (dr *ClientRecorder) Validate() {
dr.r.CheckThatAllRecordedMethodsHaveBeenCalled()
}

// ListChannelsUsingGVKs returns list of available channel objects using given list of GVKs
func (dr *ClientRecorder) ListChannelsUsingGVKs(gvks interface{}, types interface{}, ulist *unstructured.UnstructuredList, err error) {
dr.r.Add("ListChannelsUsingGVKs", []interface{}{gvks, types}, []interface{}{ulist, err})
}

// ListChannelsUsingGVKs returns list of available channel objects using given list of GVKs
func (c *MockKnDynamicClient) ListChannelsUsingGVKs(gvks *[]schema.GroupVersionKind, types ...WithType) (*unstructured.UnstructuredList, error) {
call := c.recorder.r.VerifyCall("ListChannelsUsingGVKs")
return call.Result[0].(*unstructured.UnstructuredList), mock.ErrorOrNil(call.Result[1])
}
5 changes: 5 additions & 0 deletions pkg/dynamic/client_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@ func TestMockKnDynamicClient(t *testing.T) {
recorder.ListCRDs(mock.Any(), nil, nil)
recorder.ListSourcesTypes(nil, nil)
recorder.ListSources(mock.Any(), nil, nil)
recorder.ListChannelsTypes(nil, nil)
recorder.RawClient(&fake.FakeDynamicClient{})
recorder.ListSourcesUsingGVKs(mock.Any(), mock.Any(), nil, nil)
recorder.ListChannelsUsingGVKs(mock.Any(), mock.Any(), nil, nil)

client.ListCRDs(metav1.ListOptions{})
client.ListSourcesTypes()
client.ListChannelsTypes()
client.ListSources(WithTypeFilter("blub"))
client.RawClient()
client.ListSourcesUsingGVKs(&[]schema.GroupVersionKind{}, WithTypeFilter("blub"))
client.ListChannelsUsingGVKs(&[]schema.GroupVersionKind{}, WithTypeFilter("blub"))

// Validate
recorder.Validate()
}
127 changes: 126 additions & 1 deletion pkg/dynamic/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
dynamicfake "k8s.io/client-go/dynamic/fake"
eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
"knative.dev/eventing/pkg/apis/messaging"
messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1"
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder whether we should already go to v1 API (and then change eventing also to v1 in a subsequent PR).

Copy link
Collaborator

Choose a reason for hiding this comment

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

lets keep it at v1beta1 to also support working with earlier eventing installs?

servingv1 "knative.dev/serving/pkg/apis/serving/v1"

"knative.dev/client/pkg/util"
Expand Down Expand Up @@ -172,7 +174,7 @@ func createFakeKnDynamicClient(testNamespace string, objects ...runtime.Object)
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"}, &eventingv1beta1.Broker{})

scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "messaging.knative.dev", Version: "v1beta1", Kind: "Channel"}, &messagingv1beta1.Channel{})
client := dynamicfake.NewSimpleDynamicClient(scheme, objects...)
return NewKnDynamicClient(client, testNamespace)
}
Expand Down Expand Up @@ -236,3 +238,126 @@ func newSourceUnstructuredObj(name, apiVersion, kind string) *unstructured.Unstr
},
}
}

func newChannelCRDObj(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{messaging.SubscribableDuckVersionAnnotation: channelLabelValue})
return obj
}

func newChannelCRDObjWithSpec(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{messaging.SubscribableDuckVersionAnnotation: channelLabelValue})
return obj
}

func newChannelUnstructuredObj(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{}{
"name": "foo",
},
},
},
},
}
}
func TestListChannelsTypes(t *testing.T) {
t.Run("List channel types", func(t *testing.T) {
client := createFakeKnDynamicClient(
testNamespace,
newChannelCRDObjWithSpec("Channel", "messaging.knative.dev", "v1beta1", "Channel"),
newChannelCRDObjWithSpec("InMemoryChannel", "messaging.knative.dev", "v1beta1", "InMemoryChannel"),
)

uList, err := client.ListChannelsTypes()
if err != nil {
t.Fatal(err)
}
assert.Equal(t, len(uList.Items), 1)
assert.Equal(t, uList.Items[0].GetName(), "InMemoryChannel")
})

t.Run("List channel types error", func(t *testing.T) {
client := createFakeKnDynamicClient(
testNamespace,
newChannelCRDObj("foo"),
)
uList, err := client.ListChannelsTypes()
assert.Check(t, err == nil)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, len(uList.Items), 1)
assert.Equal(t, uList.Items[0].GetName(), "foo")
})
}

func TestListChannelsUsingGVKs(t *testing.T) {
t.Run("No GVKs given", func(t *testing.T) {
client := createFakeKnDynamicClient(testNamespace)
assert.Check(t, client.RawClient() != nil)
s, err := client.ListChannelsUsingGVKs(nil)
assert.NilError(t, err)
assert.Check(t, s == nil)
})

t.Run("channel list with given GVKs", func(t *testing.T) {
client := createFakeKnDynamicClient(testNamespace,
newChannelCRDObjWithSpec("InMemoryChannel", "messaging.knative.dev", "v1beta1", "InMemoryChannel"),
newChannelUnstructuredObj("i1", "messaging.knative.dev/v1beta1", "InMemoryChannel"),
)
assert.Check(t, client.RawClient() != nil)
gv := schema.GroupVersion{"messaging.knative.dev", "v1beta1"}
gvks := []schema.GroupVersionKind{gv.WithKind("InMemoryChannel")}

s, err := client.ListChannelsUsingGVKs(&gvks)
assert.NilError(t, err)
assert.Check(t, s != nil)
assert.Equal(t, len(s.Items), 1)
assert.DeepEqual(t, s.GroupVersionKind(), schema.GroupVersionKind{messaging.GroupName, channelListVersion, channelListKind})

// withType
s, err = client.ListChannelsUsingGVKs(&gvks, WithTypeFilter("InMemoryChannel"))
assert.NilError(t, err)
assert.Check(t, s != nil)
assert.Equal(t, len(s.Items), 1)
assert.DeepEqual(t, s.GroupVersionKind(), schema.GroupVersionKind{messaging.GroupName, channelListVersion, channelListKind})
})

}
Loading