Repository: knative/client (go)

Issue:
kn channel list-types
### Feature request

List available channel types in cluster which can be used for creating channels.

### Use case
As a user I want to list available channel types in my cluster which can be used to create channels.

### UI Example
```
kn channel list-types
TYPE             GROUP                   VERSION
InMemoryChannel  messaging.knative.dev   v1abeta1
KafkaChannel     messaging.knative.dev   v1alpha1
```

### Implementation notes:
- The implementation could follow `kn source list-types` approach to query CRDs using dynamic client,
  filtered by label `messaging.knative.dev/subscribable=true`.


/kind proposal

Pull request:
feat: List available channel types
## Description

List available channel types in the cluster which can be used for creating channels.
## Changes
<!-- Please add list of more detailed changes. These changes should be reflected also in the commit messages -->
Add channel list-types


## Reference

<!-- Please add the corresponding issue number which this pull request is about to fix -->
Fixes #979 

<!--
Please add an entrty to CHANGELOG.adoc file, too, as part of your Pull Request.

In the following cases, add a short description of PR to the unreleased section in CHANGELOG.adoc:

- 🎁 New feature
- 🐛 Bug fix
- ✨ Feature Update
- 🐣 Refactoring
- 🗑️ Remove feature or internal logic

See other entries in CHANGELOG.adoc as an example for how to add the entry, including a reference to the corresponding issue/PR

PLEASE DON'T ADD THAT LINE HERE IN THE PULL-REQUEST DESCRIPTION BUT DIRECTLY IN CHANGELOG.ADOC AND ADD CHANGELOG.ADOC AS PART OF YOUR PULL-REQUEST.
-->

<!--
To automatically lint go code in this pull request uncomment the line below. You get feedback as comments on your pull request then -->

<!--
/lint
-->


Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 2e7bf185..2e1d9cd3 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -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)
diff --git a/docs/cmd/kn_channel.md b/docs/cmd/kn_channel.md
index 9d6f3ba5..e5f2078f 100644
--- a/docs/cmd/kn_channel.md
+++ b/docs/cmd/kn_channel.md
@@ -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
 
diff --git a/docs/cmd/kn_channel_list-types.md b/docs/cmd/kn_channel_list-types.md
new file mode 100644
index 00000000..e6b65ab9
--- /dev/null
+++ b/docs/cmd/kn_channel_list-types.md
@@ -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)
+
diff --git a/pkg/dynamic/client.go b/pkg/dynamic/client.go
index 5c6f0bce..70070154 100644
--- a/pkg/dynamic/client.go
+++ b/pkg/dynamic/client.go
@@ -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
@@ -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
 }
@@ -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
 }
@@ -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
+}
diff --git a/pkg/dynamic/client_mock.go b/pkg/dynamic/client_mock.go
index 051667d2..7a5fe066 100644
--- a/pkg/dynamic/client_mock.go
+++ b/pkg/dynamic/client_mock.go
@@ -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})
@@ -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])
+}
diff --git a/pkg/dynamic/fake/fake.go b/pkg/dynamic/fake/fake.go
index a4529fa5..78dfc322 100644
--- a/pkg/dynamic/fake/fake.go
+++ b/pkg/dynamic/fake/fake.go
@@ -19,11 +19,10 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	dynamicfake "k8s.io/client-go/dynamic/fake"
 
+	"knative.dev/client/pkg/dynamic"
 	eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
 	messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1"
 	servingv1 "knative.dev/serving/pkg/apis/serving/v1"
-
-	"knative.dev/client/pkg/dynamic"
 )
 
 // CreateFakeKnDynamicClient gives you a dynamic client for testing containing the given objects.
@@ -32,6 +31,7 @@ func CreateFakeKnDynamicClient(testNamespace string, objects ...runtime.Object)
 	scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "serving.knative.dev", Version: "v1", Kind: "Service"}, &servingv1.Service{})
 	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{})
 	client := dynamicfake.NewSimpleDynamicClient(scheme, objects...)
 	return dynamic.NewKnDynamicClient(client, testNamespace)
 }
diff --git a/pkg/kn/commands/channel/channel.go b/pkg/kn/commands/channel/channel.go
index 6d018299..8e2f1c4a 100644
--- a/pkg/kn/commands/channel/channel.go
+++ b/pkg/kn/commands/channel/channel.go
@@ -18,10 +18,9 @@ import (
 	"github.com/spf13/cobra"
 
 	"k8s.io/client-go/tools/clientcmd"
-	clientv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1"
-
 	"knative.dev/client/pkg/kn/commands"
 	messagingv1beta1 "knative.dev/client/pkg/messaging/v1beta1"
+	clientv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1"
 )
 
 // NewChannelCommand to manage event channels
@@ -35,6 +34,7 @@ func NewChannelCommand(p *commands.KnParams) *cobra.Command {
 	channelCmd.AddCommand(NewChannelListCommand(p))
 	channelCmd.AddCommand(NewChannelDeleteCommand(p))
 	channelCmd.AddCommand(NewChannelDescribeCommand(p))
+	channelCmd.AddCommand(NewChannelListTypesCommand(p))
 	return channelCmd
 }
 
diff --git a/pkg/kn/commands/channel/flags.go b/pkg/kn/commands/channel/flags.go
index 1ecf0300..633de5af 100644
--- a/pkg/kn/commands/channel/flags.go
+++ b/pkg/kn/commands/channel/flags.go
@@ -15,17 +15,25 @@
 package channel
 
 import (
+	"fmt"
 	"sort"
 
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
 	"k8s.io/apimachinery/pkg/runtime"
 
 	"knative.dev/client/pkg/kn/commands"
+	"knative.dev/client/pkg/printers"
 	hprinters "knative.dev/client/pkg/printers"
 
 	messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1"
 )
 
+var channelTypeDescription = map[string]string{
+	"InMemoryChannel": "The events are stored in memory",
+	"KafkaChannel":    "The events are stored in a Kafka cluster (must be installed separately)",
+}
+
 // ListHandlers handles printing human readable table for `kn channel list` command's output
 func ListHandlers(h hprinters.PrintHandler) {
 	channelColumnDefinitions := []metav1beta1.TableColumnDefinition{
@@ -120,3 +128,51 @@ func printChannelListWithNamespace(channelList *messagingv1beta1.ChannelList, op
 
 	return append(rows, others...), nil
 }
+
+// ListTypesHandlers handles printing human readable table for `kn channel list-types`
+func ListTypesHandlers(h printers.PrintHandler) {
+	channelTypesColumnDefinitions := []metav1beta1.TableColumnDefinition{
+		{Name: "Type", Type: "string", Description: "Kind / Type of the channel", Priority: 1},
+		{Name: "Name", Type: "string", Description: "Name of the channel type", Priority: 1},
+		{Name: "Description", Type: "string", Description: "Description of the channel type", Priority: 1},
+	}
+	h.TableHandler(channelTypesColumnDefinitions, printChannelTypes)
+	h.TableHandler(channelTypesColumnDefinitions, printChannelTypesList)
+}
+
+// printChannelTypes populates a single row of channel types list table
+func printChannelTypes(channelType unstructured.Unstructured, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
+	name := channelType.GetName()
+	content := channelType.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: &channelType},
+	}
+	row.Cells = append(row.Cells, kind, name, channelTypeDescription[kind])
+
+	return []metav1beta1.TableRow{row}, nil
+}
+
+// printChannelTypesList populates the channel types list table rows
+func printChannelTypesList(channelTypesList *unstructured.UnstructuredList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
+	rows := make([]metav1beta1.TableRow, 0, len(channelTypesList.Items))
+
+	sort.SliceStable(channelTypesList.Items, func(i, j int) bool {
+		return channelTypesList.Items[i].GetName() < channelTypesList.Items[j].GetName()
+	})
+	for _, item := range channelTypesList.Items {
+		row, err := printChannelTypes(item, options)
+		if err != nil {
+			return nil, err
+		}
+		rows = append(rows, row...)
+	}
+	return rows, nil
+}
diff --git a/pkg/kn/commands/channel/list_types.go b/pkg/kn/commands/channel/list_types.go
new file mode 100644
index 00000000..6cdeadd6
--- /dev/null
+++ b/pkg/kn/commands/channel/list_types.go
@@ -0,0 +1,103 @@
+/*
+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.
+*/
+package channel
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"knative.dev/client/pkg/dynamic"
+	knerrors "knative.dev/client/pkg/errors"
+	"knative.dev/client/pkg/kn/commands"
+	"knative.dev/client/pkg/kn/commands/flags"
+	messagingv1beta1 "knative.dev/client/pkg/messaging/v1beta1"
+)
+
+// NewChannelListTypesCommand defines and processes `kn channel list-types`
+func NewChannelListTypesCommand(p *commands.KnParams) *cobra.Command {
+	listTypesFlags := flags.NewListPrintFlags(ListTypesHandlers)
+	listTypesCommand := &cobra.Command{
+		Use:   "list-types",
+		Short: "List channel types",
+		Example: `
+  # List available channel types
+  kn channel list-types
+
+  # List available channel types in YAML format
+  kn channel list-types -o yaml`,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			namespace, err := p.GetNamespace(cmd)
+			if err != nil {
+				return err
+			}
+
+			dynamicClient, err := p.NewDynamicClient(namespace)
+			if err != nil {
+				return err
+			}
+
+			channelListTypes, err := dynamicClient.ListChannelsTypes()
+			switch {
+			case knerrors.IsForbiddenError(err):
+				if channelListTypes, err = listBuiltInChannelTypes(dynamicClient); err != nil {
+					return knerrors.GetError(err)
+				}
+			case err != nil:
+				return knerrors.GetError(err)
+			}
+
+			if channelListTypes == nil || len(channelListTypes.Items) == 0 {
+				return fmt.Errorf("no channels found on the backend, please verify the installation")
+			}
+
+			printer, err := listTypesFlags.ToPrinter()
+			if err != nil {
+				return nil
+			}
+
+			err = printer.PrintObj(channelListTypes, cmd.OutOrStdout())
+			if err != nil {
+				return err
+			}
+
+			return nil
+		},
+	}
+	commands.AddNamespaceFlags(listTypesCommand.Flags(), false)
+	listTypesFlags.AddFlags(listTypesCommand)
+	return listTypesCommand
+}
+
+func listBuiltInChannelTypes(d dynamic.KnDynamicClient) (*unstructured.UnstructuredList, error) {
+	var err error
+	uList := unstructured.UnstructuredList{}
+	gvks := messagingv1beta1.BuiltInChannelGVKs()
+	for _, gvk := range gvks {
+		_, err = d.ListChannelsUsingGVKs(&[]schema.GroupVersionKind{gvk})
+		if err != nil {
+			continue
+		}
+		u := dynamic.UnstructuredCRDFromGVK(gvk)
+		uList.Items = append(uList.Items, *u)
+	}
+	// if not even one channel is found
+	if len(uList.Items) == 0 && err != nil {
+		return nil, knerrors.GetError(err)
+	}
+	return &uList, nil
+}
diff --git a/pkg/kn/commands/service/configuration_edit_flags.go b/pkg/kn/commands/service/configuration_edit_flags.go
index ebf31c5f..d14f72fb 100644
--- a/pkg/kn/commands/service/configuration_edit_flags.go
+++ b/pkg/kn/commands/service/configuration_edit_flags.go
@@ -336,14 +336,16 @@ func (p *ConfigurationEditFlags) Apply(
 		}
 	}
 
-	if cmd.Flags().Changed("scale-min") {
+	// Deprecated "min-scale" in 0.19, updated to "scale-min"
+	if cmd.Flags().Changed("scale-min") || cmd.Flags().Changed("min-scale") {
 		err = servinglib.UpdateMinScale(template, p.MinScale)
 		if err != nil {
 			return err
 		}
 	}
 
-	if cmd.Flags().Changed("scale-max") {
+	// Deprecated "max-scale" in 0.19, updated to "scale-max"
+	if cmd.Flags().Changed("scale-max") || cmd.Flags().Changed("max-scale") {
 		err = servinglib.UpdateMaxScale(template, p.MaxScale)
 		if err != nil {
 			return err
diff --git a/pkg/messaging/v1beta1/client.go b/pkg/messaging/v1beta1/client.go
index 9a6c4868..7ea63552 100644
--- a/pkg/messaging/v1beta1/client.go
+++ b/pkg/messaging/v1beta1/client.go
@@ -16,6 +16,7 @@ package v1beta1
 
 import (
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 	messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1"
 	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
 	clientv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1"
@@ -61,3 +62,10 @@ func (c *messagingClient) SubscriptionsClient() KnSubscriptionsClient {
 func updateMessagingGVK(obj runtime.Object) error {
 	return util.UpdateGroupVersionKindWithScheme(obj, messagingv1beta1.SchemeGroupVersion, scheme.Scheme)
 }
+
+// BuiltInChannelGVKs returns the GVKs for built in channel
+func BuiltInChannelGVKs() []schema.GroupVersionKind {
+	return []schema.GroupVersionKind{
+		messagingv1beta1.SchemeGroupVersion.WithKind("InMemoryChannel"),
+	}
+}


Test patch:
diff --git a/lib/test/channel.go b/lib/test/channel.go
index 5eedeb6c..8679dae7 100644
--- a/lib/test/channel.go
+++ b/lib/test/channel.go
@@ -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 {
+	cmd := []string{"channel", "list-types"}
+	cmd = append(cmd, args...)
+	out := r.KnTest().Kn().Run(cmd...)
+	r.AssertNoError(out)
+	return out.Stdout
+}
diff --git a/pkg/dynamic/client_mock_test.go b/pkg/dynamic/client_mock_test.go
index 38ca3f74..31edf639 100644
--- a/pkg/dynamic/client_mock_test.go
+++ b/pkg/dynamic/client_mock_test.go
@@ -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()
 }
diff --git a/pkg/dynamic/client_test.go b/pkg/dynamic/client_test.go
index b0b927dc..e3225156 100644
--- a/pkg/dynamic/client_test.go
+++ b/pkg/dynamic/client_test.go
@@ -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"
 	servingv1 "knative.dev/serving/pkg/apis/serving/v1"
 
 	"knative.dev/client/pkg/util"
@@ -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)
 }
@@ -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})
+	})
+
+}
diff --git a/pkg/kn/commands/channel/list_types_test.go b/pkg/kn/commands/channel/list_types_test.go
new file mode 100644
index 00000000..39c2873e
--- /dev/null
+++ b/pkg/kn/commands/channel/list_types_test.go
@@ -0,0 +1,196 @@
+/*
+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.
+*/
+package channel
+
+import (
+	"strings"
+	"testing"
+
+	"gotest.tools/assert"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/runtime"
+
+	dynamicfake "k8s.io/client-go/dynamic/fake"
+	dynamicfakeClient "knative.dev/client/pkg/dynamic/fake"
+
+	"knative.dev/client/pkg/dynamic"
+	clientdynamic "knative.dev/client/pkg/dynamic"
+	"knative.dev/client/pkg/kn/commands"
+	"knative.dev/client/pkg/util"
+	"knative.dev/eventing/pkg/apis/messaging"
+)
+
+const (
+	crdGroup           = "apiextensions.k8s.io"
+	crdVersion         = "v1beta1"
+	crdKind            = "CustomResourceDefinition"
+	crdKinds           = "customresourcedefinitions"
+	testNamespace      = "current"
+	channelLabelValue  = "true"
+	channelListVersion = "v1beta1"
+	channelListKind    = "ChannelList"
+	inMemoryChannel    = "InMemoryChannel"
+)
+
+// channelFakeCmd takes cmd to be executed using dynamic client
+// pass the objects to be registered to dynamic client
+func channelFakeCmd(args []string, dynamicClient clientdynamic.KnDynamicClient, objects ...runtime.Object) (output []string, err error) {
+	knParams := &commands.KnParams{}
+	cmd, _, buf := commands.CreateDynamicTestKnCommand(NewChannelCommand(knParams), knParams, objects...)
+	cmd.SetArgs(args)
+	knParams.NewDynamicClient = func(namespace string) (clientdynamic.KnDynamicClient, error) {
+		return dynamicClient, nil
+	}
+	err = cmd.Execute()
+	if err != nil {
+		return
+	}
+	output = strings.Split(buf.String(), "\n")
+	return
+}
+
+func TestChannelListTypesNoChannelInstalled(t *testing.T) {
+	dynamicClient := dynamicfakeClient.CreateFakeKnDynamicClient(testNamespace)
+	assert.Equal(t, dynamicClient.Namespace(), testNamespace)
+
+	_, err := channelFakeCmd([]string{"channel", "list-types"}, dynamicClient)
+	assert.Check(t, err != nil)
+	assert.Check(t, util.ContainsAll(err.Error(), "no channels found on the backend, please verify the installation"))
+}
+
+func TestChannelListTypesErrorDynamicClient(t *testing.T) {
+	dynamicClient := dynamicfakeClient.CreateFakeKnDynamicClient("")
+	assert.Check(t, dynamicClient.Namespace() != testNamespace)
+
+	_, err := channelFakeCmd([]string{"channel", "list-types"}, dynamicClient)
+	assert.Check(t, err != nil)
+	assert.Check(t, util.ContainsAll(err.Error(), "no channels found on the backend, please verify the installation"))
+}
+
+func TestChannelListTypes(t *testing.T) {
+	dynamicClient := dynamicfakeClient.CreateFakeKnDynamicClient(testNamespace,
+		newChannelCRDObjWithSpec("InMemoryChannel", "messaging.knative.dev", "v1beta1", "InMemoryChannel"),
+	)
+	assert.Equal(t, dynamicClient.Namespace(), testNamespace)
+	output, err := channelFakeCmd([]string{"channel", "list-types"}, dynamicClient)
+	assert.NilError(t, err)
+	assert.Check(t, util.ContainsAll(output[0], "TYPE", "NAME", "DESCRIPTION"))
+	assert.Check(t, util.ContainsAll(output[1], "InMemoryChannel", "InMemoryChannel"))
+}
+
+func TestChannelListTypesNoHeaders(t *testing.T) {
+	dynamicClient := dynamicfakeClient.CreateFakeKnDynamicClient(testNamespace,
+		newChannelCRDObjWithSpec("InMemoryChannel", "messaging.knative.dev", "v1beta1", "InMemoryChannel"),
+	)
+	assert.Equal(t, dynamicClient.Namespace(), testNamespace)
+	output, err := channelFakeCmd([]string{"channel", "list-types", "--no-headers"}, dynamicClient)
+	assert.NilError(t, err)
+	assert.Check(t, util.ContainsNone(output[0], "TYPE", "NAME", "DESCRIPTION"))
+	assert.Check(t, util.ContainsAll(output[0], "InMemoryChannel", "InMemoryChannel"))
+}
+
+func TestListBuiltInChannelTypes(t *testing.T) {
+	fakeDynamic := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme())
+	channel, err := listBuiltInChannelTypes(dynamic.NewKnDynamicClient(fakeDynamic, "current"))
+	assert.NilError(t, err)
+	assert.Check(t, channel != nil)
+	assert.Equal(t, len(channel.Items), 1)
+}
+
+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 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 TestChannelListTypeErrors(t *testing.T) {
+	dynamicClient := dynamicfakeClient.CreateFakeKnDynamicClient(testNamespace, newChannelCRDObj("InMemoryChannel"))
+	assert.Equal(t, dynamicClient.Namespace(), testNamespace)
+
+	output, err := channelFakeCmd([]string{"channel", "list-types"}, dynamicClient)
+	assert.Check(t, err != nil)
+	assert.Error(t, err, "can't find specs.names.kind for InMemoryChannel")
+
+	obj := newChannelCRDObj(inMemoryChannel)
+	obj.Object["spec"] = map[string]interface{}{
+		"group":   messaging.GroupName,
+		"version": channelListVersion,
+		"names":   map[string]interface{}{},
+	}
+	dynamicClient = dynamicfakeClient.CreateFakeKnDynamicClient(testNamespace, obj)
+	output, err = channelFakeCmd([]string{"channel", "list-types"}, dynamicClient)
+	assert.Check(t, err != nil)
+	assert.Error(t, err, "can't find specs.names.kind for InMemoryChannel")
+
+	obj.Object["spec"] = map[string]interface{}{
+		"group":   messaging.GroupName,
+		"version": channelListVersion,
+		"names": map[string]interface{}{
+			"kind":   true,
+			"plural": strings.ToLower("kind") + "s",
+		},
+	}
+
+	dynamicClient = dynamicfakeClient.CreateFakeKnDynamicClient(testNamespace, obj)
+	output, err = channelFakeCmd([]string{"channel", "list-types"}, dynamicClient)
+	assert.Check(t, err != nil)
+	assert.Error(t, err, ".spec.names.kind accessor error: true is of the type bool, expected string")
+
+	dynamicClient = dynamicfakeClient.CreateFakeKnDynamicClient(testNamespace,
+		newChannelCRDObjWithSpec("InMemoryChannel", "messaging.knative.dev", "v1beta1", "InMemoryChannel"),
+	)
+	_, err = channelFakeCmd([]string{"channel", "list-types", "--noheader"}, dynamicClient)
+	assert.Check(t, err != nil)
+	assert.Error(t, err, "unknown flag: --noheader")
+
+	output, err = channelFakeCmd([]string{"channel", "list-types"}, dynamicClient)
+	assert.NilError(t, err)
+	assert.Check(t, util.ContainsAll(output[0], "TYPE", "NAME", "DESCRIPTION"))
+	assert.Check(t, util.ContainsAll(output[1], "InMemoryChannel", "InMemoryChannel"))
+}
diff --git a/test/e2e/channels_test.go b/test/e2e/channels_test.go
index bebea23e..edacb08d 100644
--- a/test/e2e/channels_test.go
+++ b/test/e2e/channels_test.go
@@ -23,7 +23,6 @@ import (
 	"testing"
 
 	"gotest.tools/assert"
-
 	"knative.dev/client/lib/test"
 	"knative.dev/client/pkg/util"
 )
@@ -101,4 +100,13 @@ func TestChannels(t *testing.T) {
 	test.ChannelDelete(r, "c0")
 	test.ChannelDelete(r, "c1")
 	test.ChannelDelete(r, "c2")
+
+	t.Log("List channel types")
+	listout = test.ChannelListTypes(r)
+	assert.Check(t, util.ContainsAll(listout, "TYPE", "NAME", "DESCRIPTION", "InMemoryChannel"))
+
+	t.Log("List channel types no header")
+	listout = test.ChannelListTypes(r, "--no-headers")
+	assert.Check(t, util.ContainsNone(listout, "TYPE", "NAME", "DESCRIPTION"))
+	assert.Check(t, util.ContainsAll(listout, "InMemoryChannel"))
 }
diff --git a/test/e2e/service_export_test.go b/test/e2e/service_export_test.go
index ae070f6e..546267ab 100644
--- a/test/e2e/service_export_test.go
+++ b/test/e2e/service_export_test.go
@@ -45,9 +45,10 @@ type expectedKNExportOption func(*clientv1alpha1.Export)
 type podSpecOption func(*corev1.PodSpec)
 
 func TestServiceExport(t *testing.T) {
-	//FIXME: enable once 0.18 is available
-	if strings.HasPrefix(os.Getenv("KNATIVE_SERVING_VERSION"), "0.17") {
-		t.Skip("The test is skipped on Serving version 0.17")
+	//FIXME: enable once 0.19 is available
+	// see: https://github.com/knative/serving/pull/9685
+	if strings.HasPrefix(os.Getenv("KNATIVE_SERVING_VERSION"), "0.18") {
+		t.Skip("The test is skipped on Serving version 0.18")
 	}
 	t.Parallel()
 	it, err := test.NewKnTest()
@@ -502,6 +503,8 @@ func getPodSpecWithOptions(options ...podSpecOption) corev1.PodSpec {
 	for _, fn := range options {
 		fn(&spec)
 	}
+	// Service links are disabled by default now see https://github.com/knative/serving/pull/9685
+	spec.EnableServiceLinks = ptr.Bool(false)
 	return spec
 }
 
diff --git a/test/presubmit-integration-tests-latest-release.sh b/test/presubmit-integration-tests-latest-release.sh
index b4d96348..e2d7a399 100755
--- a/test/presubmit-integration-tests-latest-release.sh
+++ b/test/presubmit-integration-tests-latest-release.sh
@@ -18,6 +18,6 @@
 # integration tests against Knative Serving / Eventing of a specific version.
 
 export KNATIVE_SERVING_VERSION="0.18.0"
-export KNATIVE_EVENTING_VERSION="0.18.0"
+#export KNATIVE_EVENTING_VERSION="0.18.0"
 
 $(dirname $0)/presubmit-tests.sh --integration-tests