Repository: knative/client (go)

Issue:
kn trigger list
List all triggers as described in https://docs.google.com/document/d/1B8VK-5nuxt3D-i1Up3onmUbzcztQXE0GRgf1lfx49P8/edit#bookmark=id.lmbmwxw0i4gy :

---------------

Lists all Triggers in a namespace.

```
$ kn trigger list

NAME     AGE     CONDITIONS   READY  REASON  SUBSCRIBER
geary    7d16h   3 OK / 3     True           http://sockeye.default.svc.cluster.local
ochoa    7d16h   3 OK / 3     True           http://event-display.default.svc.cluster.local
```

The output is just an example, and could also contain more or less information like a connected Source or the filter expression.

Pull request:
Add trigger list
Fixes #516 

## Proposed Changes

* Add command to list trigger
* Mark sink as required in `trigger create` and `source apiserver create`

Patch:
diff --git a/docs/cmd/kn_trigger.md b/docs/cmd/kn_trigger.md
index d0a2896e..77f60d90 100644
--- a/docs/cmd/kn_trigger.md
+++ b/docs/cmd/kn_trigger.md
@@ -30,4 +30,5 @@ kn trigger [flags]
 * [kn trigger create](kn_trigger_create.md)	 - Create a trigger
 * [kn trigger delete](kn_trigger_delete.md)	 - Delete a trigger.
 * [kn trigger describe](kn_trigger_describe.md)	 - Describe a trigger.
+* [kn trigger list](kn_trigger_list.md)	 - List available triggers.
 
diff --git a/docs/cmd/kn_trigger_list.md b/docs/cmd/kn_trigger_list.md
new file mode 100644
index 00000000..1c7d5a6e
--- /dev/null
+++ b/docs/cmd/kn_trigger_list.md
@@ -0,0 +1,47 @@
+## kn trigger list
+
+List available triggers.
+
+### Synopsis
+
+List available triggers.
+
+```
+kn trigger list [name] [flags]
+```
+
+### Examples
+
+```
+
+  # List all triggers
+  kn trigger list
+
+  # List all triggers in JSON output format
+  kn trigger 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-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 trigger](kn_trigger.md)	 - Trigger command group
+
diff --git a/pkg/eventing/v1alpha1/client.go b/pkg/eventing/v1alpha1/client.go
index 9431672e..aabde5bc 100644
--- a/pkg/eventing/v1alpha1/client.go
+++ b/pkg/eventing/v1alpha1/client.go
@@ -16,12 +16,18 @@ package v1alpha1
 
 import (
 	apis_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
+	"k8s.io/apimachinery/pkg/runtime"
 	kn_errors "knative.dev/client/pkg/errors"
+	"knative.dev/client/pkg/util"
 	"knative.dev/eventing/pkg/apis/eventing/v1alpha1"
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
 	client_v1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1alpha1"
 )
 
+const (
+	nameFieldKey = "metadata.name"
+)
+
 // KnEventingClient to Eventing Sources. All methods are relative to the
 // namespace specified during construction
 type KnEventingClient interface {
@@ -33,6 +39,8 @@ type KnEventingClient interface {
 	DeleteTrigger(name string) error
 	// GetTrigger is used to get an instance of trigger
 	GetTrigger(name string) (*v1alpha1.Trigger, error)
+	// ListTrigger returns list of trigger CRDs
+	ListTriggers() (*v1alpha1.TriggerList, error)
 }
 
 // KnEventingClient is a combination of Sources client interface and namespace
@@ -78,7 +86,35 @@ func (c *knEventingClient) GetTrigger(name string) (*v1alpha1.Trigger, error) {
 	return trigger, nil
 }
 
+func (c *knEventingClient) ListTriggers() (*v1alpha1.TriggerList, error) {
+	triggerList, err := c.client.Triggers(c.namespace).List(apis_v1.ListOptions{})
+	if err != nil {
+		return nil, kn_errors.GetError(err)
+	}
+	triggerListNew := triggerList.DeepCopy()
+	err = updateTriggerGvk(triggerListNew)
+	if err != nil {
+		return nil, err
+	}
+
+	triggerListNew.Items = make([]v1alpha1.Trigger, len(triggerList.Items))
+	for idx, trigger := range triggerList.Items {
+		triggerClone := trigger.DeepCopy()
+		err := updateTriggerGvk(triggerClone)
+		if err != nil {
+			return nil, err
+		}
+		triggerListNew.Items[idx] = *triggerClone
+	}
+	return triggerListNew, nil
+}
+
 // Return the client's namespace
 func (c *knEventingClient) Namespace() string {
 	return c.namespace
 }
+
+// update with the v1alpha1 group + version
+func updateTriggerGvk(obj runtime.Object) error {
+	return util.UpdateGroupVersionKindWithScheme(obj, v1alpha1.SchemeGroupVersion, scheme.Scheme)
+}
diff --git a/pkg/eventing/v1alpha1/client_mock.go b/pkg/eventing/v1alpha1/client_mock.go
index c5ed01e3..9fcb3b9a 100644
--- a/pkg/eventing/v1alpha1/client_mock.go
+++ b/pkg/eventing/v1alpha1/client_mock.go
@@ -63,7 +63,7 @@ func (sr *EventingRecorder) CreateTrigger(trigger interface{}, err error) {
 	sr.r.Add("CreateTrigger", []interface{}{trigger}, []interface{}{trigger, err})
 }
 
-// CreateTrigger performs a previously recorded action, failing if non has been registered
+// CreateTrigger performs a previously recorded action
 func (c *MockKnEventingClient) CreateTrigger(trigger *v1alpha1.Trigger) (*v1alpha1.Trigger, error) {
 	call := c.recorder.r.VerifyCall("CreateTrigger", trigger)
 	return call.Result[0].(*v1alpha1.Trigger), mock.ErrorOrNil(call.Result[1])
@@ -74,7 +74,7 @@ func (sr *EventingRecorder) GetTrigger(name interface{}, trigger *v1alpha1.Trigg
 	sr.r.Add("GetTrigger", []interface{}{name}, []interface{}{trigger, err})
 }
 
-// GetTrigger performs a previously recorded action, failing if non has been registered
+// GetTrigger performs a previously recorded action
 func (c *MockKnEventingClient) GetTrigger(name string) (*v1alpha1.Trigger, error) {
 	call := c.recorder.r.VerifyCall("GetTrigger", name)
 	return call.Result[0].(*v1alpha1.Trigger), mock.ErrorOrNil(call.Result[1])
@@ -91,6 +91,17 @@ func (c *MockKnEventingClient) DeleteTrigger(name string) error {
 	return mock.ErrorOrNil(call.Result[0])
 }
 
+// ListTriggers records a call for ListTriggers with the expected result and error (nil if none)
+func (sr *EventingRecorder) ListTriggers(triggerList *v1alpha1.TriggerList, err error) {
+	sr.r.Add("ListTriggers", nil, []interface{}{triggerList, err})
+}
+
+// ListTriggers performs a previously recorded action
+func (c *MockKnEventingClient) ListTriggers() (*v1alpha1.TriggerList, error) {
+	call := c.recorder.r.VerifyCall("ListTriggers")
+	return call.Result[0].(*v1alpha1.TriggerList), mock.ErrorOrNil(call.Result[1])
+}
+
 // Validate validates whether every recorded action has been called
 func (sr *EventingRecorder) Validate() {
 	sr.r.CheckThatAllRecordedMethodsHaveBeenCalled()
diff --git a/pkg/kn/commands/flags/list.go b/pkg/kn/commands/flags/list.go
new file mode 100644
index 00000000..5e336f32
--- /dev/null
+++ b/pkg/kn/commands/flags/list.go
@@ -0,0 +1,79 @@
+// 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 flags
+
+import (
+	"github.com/spf13/cobra"
+	"k8s.io/cli-runtime/pkg/genericclioptions"
+	"knative.dev/client/pkg/kn/commands"
+	hprinters "knative.dev/client/pkg/printers"
+)
+
+// ListFlags composes common printer flag structs
+// used in the list command.
+type ListFlags struct {
+	GenericPrintFlags  *genericclioptions.PrintFlags
+	HumanReadableFlags *commands.HumanPrintFlags
+	PrinterHandler     func(h hprinters.PrintHandler)
+}
+
+// AllowedFormats is the list of formats in which data can be displayed
+func (f *ListFlags) AllowedFormats() []string {
+	formats := f.GenericPrintFlags.AllowedFormats()
+	formats = append(formats, f.HumanReadableFlags.AllowedFormats()...)
+	return formats
+}
+
+// ToPrinter attempts to find a composed set of ListTypesFlags suitable for
+// returning a printer based on current flag values.
+func (f *ListFlags) ToPrinter() (hprinters.ResourcePrinter, error) {
+	// if there are flags specified for generic printing
+	if f.GenericPrintFlags.OutputFlagSpecified() {
+		p, err := f.GenericPrintFlags.ToPrinter()
+		if err != nil {
+			return nil, err
+		}
+		return p, nil
+	}
+
+	p, err := f.HumanReadableFlags.ToPrinter(f.PrinterHandler)
+	if err != nil {
+		return nil, err
+	}
+	return p, nil
+}
+
+// AddFlags receives a *cobra.Command reference and binds
+// flags related to humanreadable and template printing.
+func (f *ListFlags) AddFlags(cmd *cobra.Command) {
+	f.GenericPrintFlags.AddFlags(cmd)
+	f.HumanReadableFlags.AddFlags(cmd)
+}
+
+// NewListFlags returns flags associated with humanreadable,
+// template, and "name" printing, with default values set.
+func NewListFlags(printer func(h hprinters.PrintHandler)) *ListFlags {
+	return &ListFlags{
+		GenericPrintFlags:  genericclioptions.NewPrintFlags(""),
+		HumanReadableFlags: commands.NewHumanPrintFlags(),
+		PrinterHandler:     printer,
+	}
+}
+
+// EnsureWithNamespace ensures that humanreadable flags return
+// a printer capable of printing with a "namespace" column.
+func (f *ListFlags) EnsureWithNamespace() {
+	f.HumanReadableFlags.EnsureWithNamespace()
+}
diff --git a/pkg/kn/commands/source/apiserver/create.go b/pkg/kn/commands/source/apiserver/create.go
index d3452324..1755f957 100644
--- a/pkg/kn/commands/source/apiserver/create.go
+++ b/pkg/kn/commands/source/apiserver/create.go
@@ -90,5 +90,6 @@ func NewAPIServerCreateCommand(p *commands.KnParams) *cobra.Command {
 	updateFlags.Add(cmd)
 	sinkFlags.Add(cmd)
 	cmd.MarkFlagRequired("resource")
+	cmd.MarkFlagRequired("sink")
 	return cmd
 }
diff --git a/pkg/kn/commands/trigger/create.go b/pkg/kn/commands/trigger/create.go
index 1bf38c65..e39c3625 100644
--- a/pkg/kn/commands/trigger/create.go
+++ b/pkg/kn/commands/trigger/create.go
@@ -41,7 +41,7 @@ func NewTriggerCreateCommand(p *commands.KnParams) *cobra.Command {
 
 		RunE: func(cmd *cobra.Command, args []string) (err error) {
 			if len(args) != 1 {
-				return errors.New("'trigger create' requires the name of the trigger as single argument")
+				return errors.New("'trigger create' requires the name of the trigger")
 			}
 			name := args[0]
 
@@ -86,7 +86,7 @@ func NewTriggerCreateCommand(p *commands.KnParams) *cobra.Command {
 	commands.AddNamespaceFlags(cmd.Flags(), false)
 	triggerUpdateFlags.Add(cmd)
 	sinkFlags.Add(cmd)
-	cmd.MarkFlagRequired("schedule")
+	cmd.MarkFlagRequired("sink")
 
 	return cmd
 }
diff --git a/pkg/kn/commands/trigger/list.go b/pkg/kn/commands/trigger/list.go
new file mode 100644
index 00000000..089642a1
--- /dev/null
+++ b/pkg/kn/commands/trigger/list.go
@@ -0,0 +1,77 @@
+// 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 trigger
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	"knative.dev/client/pkg/kn/commands"
+	"knative.dev/client/pkg/kn/commands/flags"
+)
+
+// NewTriggerListCommand represents 'kn trigger list' command
+func NewTriggerListCommand(p *commands.KnParams) *cobra.Command {
+	triggerListFlags := flags.NewListFlags(TriggerListHandlers)
+
+	triggerListCommand := &cobra.Command{
+		Use:   "list [name]",
+		Short: "List available triggers.",
+		Example: `
+  # List all triggers
+  kn trigger list
+
+  # List all triggers in JSON output format
+  kn trigger list -o json`,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			namespace, err := p.GetNamespace(cmd)
+			if err != nil {
+				return err
+			}
+			client, err := p.NewEventingClient(namespace)
+			if err != nil {
+				return err
+			}
+			triggerList, err := client.ListTriggers()
+			if err != nil {
+				return err
+			}
+			if len(triggerList.Items) == 0 {
+				fmt.Fprintf(cmd.OutOrStdout(), "No triggers found.\n")
+				return nil
+			}
+
+			// empty namespace indicates all-namespaces flag is specified
+			if namespace == "" {
+				triggerListFlags.EnsureWithNamespace()
+			}
+
+			printer, err := triggerListFlags.ToPrinter()
+			if err != nil {
+				return err
+			}
+
+			err = printer.PrintObj(triggerList, cmd.OutOrStdout())
+			if err != nil {
+				return err
+			}
+			return nil
+		},
+	}
+	commands.AddNamespaceFlags(triggerListCommand.Flags(), true)
+	triggerListFlags.AddFlags(triggerListCommand)
+	return triggerListCommand
+}
diff --git a/pkg/kn/commands/trigger/list_flags.go b/pkg/kn/commands/trigger/list_flags.go
new file mode 100644
index 00000000..3c65041d
--- /dev/null
+++ b/pkg/kn/commands/trigger/list_flags.go
@@ -0,0 +1,115 @@
+// 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 trigger
+
+import (
+	"sort"
+
+	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"knative.dev/client/pkg/kn/commands"
+	hprinters "knative.dev/client/pkg/printers"
+	eventing "knative.dev/eventing/pkg/apis/eventing/v1alpha1"
+)
+
+// TriggerListHandlers handles printing human readable table for `kn source list-types` command's output
+func TriggerListHandlers(h hprinters.PrintHandler) {
+	sourceTypesColumnDefinitions := []metav1beta1.TableColumnDefinition{
+		{Name: "Namespace", Type: "string", Description: "Namespace of the trigger", Priority: 0},
+		{Name: "Name", Type: "string", Description: "Name of the trigger", Priority: 1},
+		{Name: "Broker", Type: "string", Description: "Name of the broker", Priority: 1},
+		{Name: "Subscriber_URI", Type: "string", Description: "URI of the subscriber", Priority: 1},
+		{Name: "Ready", Type: "string", Description: "Ready condition status of the trigger", Priority: 1},
+		{Name: "Reason", Type: "string", Description: "Reason for non-ready condition of the trigger", Priority: 1},
+	}
+	h.TableHandler(sourceTypesColumnDefinitions, printTrigger)
+	h.TableHandler(sourceTypesColumnDefinitions, printTriggerList)
+}
+
+// printKService populates the knative service table rows
+func printTrigger(trigger *eventing.Trigger, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
+	name := trigger.Name
+	broker := trigger.Spec.Broker
+	uri := trigger.Status.SubscriberURI
+	ready := commands.ReadyCondition(trigger.Status.Conditions)
+	reason := commands.NonReadyConditionReason(trigger.Status.Conditions)
+
+	row := metav1beta1.TableRow{
+		Object: runtime.RawExtension{Object: trigger},
+	}
+
+	if options.AllNamespaces {
+		row.Cells = append(row.Cells, trigger.Namespace)
+	}
+
+	row.Cells = append(row.Cells,
+		name,
+		broker,
+		uri,
+		ready,
+		reason)
+	return []metav1beta1.TableRow{row}, nil
+}
+
+// printTriggerListWithNamespace populates the knative service table rows with namespace column
+func printTriggerListWithNamespace(triggerList *eventing.TriggerList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
+	rows := make([]metav1beta1.TableRow, 0, len(triggerList.Items))
+
+	// temporary slice for sorting services in non-default namespace
+	others := []metav1beta1.TableRow{}
+
+	for _, trigger := range triggerList.Items {
+		// Fill in with services in `default` namespace at first
+		if trigger.Namespace == "default" {
+			r, err := printTrigger(&trigger, options)
+			if err != nil {
+				return nil, err
+			}
+			rows = append(rows, r...)
+			continue
+		}
+		// put other services in temporary slice
+		r, err := printTrigger(&trigger, options)
+		if err != nil {
+			return nil, err
+		}
+		others = append(others, r...)
+	}
+
+	// sort other services list alphabetically by namespace
+	sort.SliceStable(others, func(i, j int) bool {
+		return others[i].Cells[0].(string) < others[j].Cells[0].(string)
+	})
+
+	return append(rows, others...), nil
+}
+
+// printSourceTypesList populates the source types list table rows
+func printTriggerList(triggerList *eventing.TriggerList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
+	rows := make([]metav1beta1.TableRow, 0, len(triggerList.Items))
+
+	if options.AllNamespaces {
+		return printTriggerListWithNamespace(triggerList, options)
+	}
+
+	for _, trigger := range triggerList.Items {
+		r, err := printTrigger(&trigger, options)
+		if err != nil {
+			return nil, err
+		}
+		rows = append(rows, r...)
+	}
+	return rows, nil
+}
diff --git a/pkg/kn/commands/trigger/trigger.go b/pkg/kn/commands/trigger/trigger.go
index 22bba086..e5158a2b 100644
--- a/pkg/kn/commands/trigger/trigger.go
+++ b/pkg/kn/commands/trigger/trigger.go
@@ -29,5 +29,6 @@ func NewTriggerCommand(p *commands.KnParams) *cobra.Command {
 	triggerCmd.AddCommand(NewTriggerCreateCommand(p))
 	triggerCmd.AddCommand(NewTriggerDeleteCommand(p))
 	triggerCmd.AddCommand(NewTriggerDescribeCommand(p))
+	triggerCmd.AddCommand(NewTriggerListCommand(p))
 	return triggerCmd
 }


Test patch:
diff --git a/pkg/eventing/v1alpha1/client_mock_test.go b/pkg/eventing/v1alpha1/client_mock_test.go
index bfff269d..ffe746d6 100644
--- a/pkg/eventing/v1alpha1/client_mock_test.go
+++ b/pkg/eventing/v1alpha1/client_mock_test.go
@@ -30,11 +30,13 @@ func TestMockKnClient(t *testing.T) {
 	recorder.GetTrigger("hello", nil, nil)
 	recorder.CreateTrigger(&v1alpha1.Trigger{}, nil)
 	recorder.DeleteTrigger("hello", nil)
+	recorder.ListTriggers(nil, nil)
 
 	// Call all service
 	client.GetTrigger("hello")
 	client.CreateTrigger(&v1alpha1.Trigger{})
 	client.DeleteTrigger("hello")
+	client.ListTriggers()
 
 	// Validate
 	recorder.Validate()
diff --git a/pkg/eventing/v1alpha1/client_test.go b/pkg/eventing/v1alpha1/client_test.go
index 00c94cc1..7f658037 100644
--- a/pkg/eventing/v1alpha1/client_test.go
+++ b/pkg/eventing/v1alpha1/client_test.go
@@ -107,6 +107,27 @@ func TestGetTrigger(t *testing.T) {
 	assert.ErrorContains(t, err, "errorTrigger")
 }
 
+func TestListTrigger(t *testing.T) {
+	serving, client := setup()
+
+	t.Run("list trigger returns a list of triggers", func(t *testing.T) {
+		trigger1 := newTrigger("trigger-1")
+		trigger2 := newTrigger("trigger-2")
+
+		serving.AddReactor("list", "triggers",
+			func(a client_testing.Action) (bool, runtime.Object, error) {
+				assert.Equal(t, testNamespace, a.GetNamespace())
+				return true, &v1alpha1.TriggerList{Items: []v1alpha1.Trigger{*trigger1, *trigger2}}, nil
+			})
+
+		listTriggers, err := client.ListTriggers()
+		assert.NilError(t, err)
+		assert.Assert(t, len(listTriggers.Items) == 2)
+		assert.Equal(t, listTriggers.Items[0].Name, "trigger-1")
+		assert.Equal(t, listTriggers.Items[1].Name, "trigger-2")
+	})
+}
+
 func newTrigger(name string) *v1alpha1.Trigger {
 	obj := &v1alpha1.Trigger{
 		ObjectMeta: metav1.ObjectMeta{
diff --git a/pkg/kn/commands/flags/list_test.go b/pkg/kn/commands/flags/list_test.go
new file mode 100644
index 00000000..c1759800
--- /dev/null
+++ b/pkg/kn/commands/flags/list_test.go
@@ -0,0 +1,50 @@
+// 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 flags
+
+import (
+	"testing"
+
+	"github.com/spf13/cobra"
+	"gotest.tools/assert"
+	hprinters "knative.dev/client/pkg/printers"
+)
+
+func TestListFlagsFormats(t *testing.T) {
+	flags := NewListFlags(nil)
+	formats := flags.AllowedFormats()
+	expected := []string{"json", "yaml", "name", "go-template", "go-template-file", "template", "templatefile", "jsonpath", "jsonpath-file", "no-headers"}
+	assert.DeepEqual(t, formats, expected)
+}
+
+func TestListFlags(t *testing.T) {
+	var cmd *cobra.Command
+	flags := NewListFlags(func(h hprinters.PrintHandler) {})
+
+	cmd = &cobra.Command{}
+	flags.AddFlags(cmd)
+
+	assert.Assert(t, flags != nil)
+	assert.Assert(t, cmd.Flags() != nil)
+
+	allowMissingTemplateKeys, err := cmd.Flags().GetBool("allow-missing-template-keys")
+	assert.NilError(t, err)
+	assert.Assert(t, allowMissingTemplateKeys == true)
+
+	p, err := flags.ToPrinter()
+	assert.NilError(t, err)
+	_, ok := p.(hprinters.ResourcePrinter)
+	assert.Check(t, ok == true)
+}
diff --git a/pkg/kn/commands/source/apiserver/create_test.go b/pkg/kn/commands/source/apiserver/create_test.go
index fb05be64..05f69aa0 100644
--- a/pkg/kn/commands/source/apiserver/create_test.go
+++ b/pkg/kn/commands/source/apiserver/create_test.go
@@ -48,7 +48,7 @@ func TestCreateApiServerSource(t *testing.T) {
 	servingRecorder.Validate()
 }
 
-func TestNoSinkError(t *testing.T) {
+func TestSinkNotFoundError(t *testing.T) {
 	servingClient := knserving_client.NewMockKnServiceClient(t)
 	apiServerClient := knsources_v1alpha1.NewMockKnAPIServerSourceClient(t)
 
@@ -61,3 +61,9 @@ func TestNoSinkError(t *testing.T) {
 	assert.Assert(t, util.ContainsAll(out, errorMsg, "Usage"))
 	servingRecorder.Validate()
 }
+
+func TestNoSinkError(t *testing.T) {
+	apiServerClient := knsources_v1alpha1.NewMockKnAPIServerSourceClient(t)
+	_, err := executeAPIServerSourceCommand(apiServerClient, nil, "create", "testsource", "--resource", "Event:v1:false", "--service-account", "testsa", "--mode", "Ref")
+	assert.ErrorContains(t, err, "required flag(s)", "sink", "not set")
+}
diff --git a/pkg/kn/commands/trigger/create_test.go b/pkg/kn/commands/trigger/create_test.go
index 659a2688..2c7ab2d6 100644
--- a/pkg/kn/commands/trigger/create_test.go
+++ b/pkg/kn/commands/trigger/create_test.go
@@ -20,13 +20,10 @@ import (
 	"testing"
 
 	"gotest.tools/assert"
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	eventing_client "knative.dev/client/pkg/eventing/v1alpha1"
 	knserving_client "knative.dev/client/pkg/serving/v1alpha1"
 	"knative.dev/client/pkg/util"
-	"knative.dev/eventing/pkg/apis/eventing/v1alpha1"
-	duckv1 "knative.dev/pkg/apis/duck/v1"
 	serving_v1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1"
 )
 
@@ -45,7 +42,7 @@ func TestTriggerCreate(t *testing.T) {
 	}, nil)
 
 	eventingRecorder := eventingClient.Recorder()
-	eventingRecorder.CreateTrigger(createTrigger(triggerName, map[string]string{"type": "dev.knative.foo"}, "mybroker", "mysvc"), nil)
+	eventingRecorder.CreateTrigger(createTrigger("default", triggerName, map[string]string{"type": "dev.knative.foo"}, "mybroker", "mysvc"), nil)
 
 	out, err := executeTriggerCommand(eventingClient, servingClient, "create", triggerName, "--broker", "mybroker",
 		"--filter", "type=dev.knative.foo", "--sink", "svc:mysvc")
@@ -56,7 +53,7 @@ func TestTriggerCreate(t *testing.T) {
 	servingRecorder.Validate()
 }
 
-func TestNoSinkError(t *testing.T) {
+func TestSinkNotFoundError(t *testing.T) {
 	eventingClient := eventing_client.NewMockKnEventingClient(t)
 	servingClient := knserving_client.NewMockKnServiceClient(t)
 
@@ -71,6 +68,13 @@ func TestNoSinkError(t *testing.T) {
 	servingRecorder.Validate()
 }
 
+func TestNoSinkError(t *testing.T) {
+	eventingClient := eventing_client.NewMockKnEventingClient(t)
+	_, err := executeTriggerCommand(eventingClient, nil, "create", triggerName, "--broker", "mybroker",
+		"--filter", "type=dev.knative.foo")
+	assert.ErrorContains(t, err, "required flag(s)", "sink", "not set")
+}
+
 func TestTriggerCreateMultipleFilter(t *testing.T) {
 	eventingClient := eventing_client.NewMockKnEventingClient(t)
 	servingClient := knserving_client.NewMockKnServiceClient(t)
@@ -82,7 +86,7 @@ func TestTriggerCreateMultipleFilter(t *testing.T) {
 	}, nil)
 
 	eventingRecorder := eventingClient.Recorder()
-	eventingRecorder.CreateTrigger(createTrigger(triggerName, map[string]string{"type": "dev.knative.foo", "source": "event.host"}, "mybroker", "mysvc"), nil)
+	eventingRecorder.CreateTrigger(createTrigger("default", triggerName, map[string]string{"type": "dev.knative.foo", "source": "event.host"}, "mybroker", "mysvc"), nil)
 
 	out, err := executeTriggerCommand(eventingClient, servingClient, "create", triggerName, "--broker", "mybroker",
 		"--filter", "type=dev.knative.foo", "--filter", "source=event.host", "--sink", "svc:mysvc")
@@ -92,26 +96,3 @@ func TestTriggerCreateMultipleFilter(t *testing.T) {
 	eventingRecorder.Validate()
 	servingRecorder.Validate()
 }
-
-func createTrigger(name string, filters map[string]string, broker string, svcname string) *v1alpha1.Trigger {
-	triggerFilterAttributes := v1alpha1.TriggerFilterAttributes(filters)
-	wanted := &v1alpha1.Trigger{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      name,
-			Namespace: "default",
-		},
-		Spec: v1alpha1.TriggerSpec{
-			Broker: broker,
-			Filter: &v1alpha1.TriggerFilter{
-				Attributes: &triggerFilterAttributes,
-			},
-			Subscriber: &duckv1.Destination{
-				Ref: &corev1.ObjectReference{
-					Name: svcname,
-					Kind: "Service",
-				},
-			},
-		},
-	}
-	return wanted
-}
diff --git a/pkg/kn/commands/trigger/delete_test.go b/pkg/kn/commands/trigger/delete_test.go
index 778a189f..5e5f86fd 100644
--- a/pkg/kn/commands/trigger/delete_test.go
+++ b/pkg/kn/commands/trigger/delete_test.go
@@ -15,7 +15,6 @@
 package trigger
 
 import (
-	"errors"
 	"fmt"
 	"testing"
 
@@ -43,7 +42,7 @@ func TestTriggerDeleteWithError(t *testing.T) {
 
 	eventingClient := eventing_client.NewMockKnEventingClient(t)
 	eventingRecorder := eventingClient.Recorder()
-	eventingRecorder.DeleteTrigger(triggerName, errors.New(fmt.Sprintf("trigger %s not found", triggerName)))
+	eventingRecorder.DeleteTrigger(triggerName, fmt.Errorf("trigger %s not found", triggerName))
 
 	out, err := executeTriggerCommand(eventingClient, nil, "delete", triggerName)
 	assert.ErrorContains(t, err, triggerName)
diff --git a/pkg/kn/commands/trigger/list_test.go b/pkg/kn/commands/trigger/list_test.go
new file mode 100644
index 00000000..1b355d6b
--- /dev/null
+++ b/pkg/kn/commands/trigger/list_test.go
@@ -0,0 +1,98 @@
+// 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 trigger
+
+import (
+	"strings"
+	"testing"
+
+	"gotest.tools/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	eventing_client "knative.dev/client/pkg/eventing/v1alpha1"
+	knserving_client "knative.dev/client/pkg/serving/v1alpha1"
+	"knative.dev/client/pkg/util"
+	"knative.dev/eventing/pkg/apis/eventing/v1alpha1"
+	serving_v1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1"
+)
+
+func TestTriggerList(t *testing.T) {
+	servingClient := knserving_client.NewMockKnServiceClient(t)
+	servingRecorder := servingClient.Recorder()
+	servingRecorder.GetService("mysink", &serving_v1alpha1.Service{
+		TypeMeta:   metav1.TypeMeta{Kind: "Service"},
+		ObjectMeta: metav1.ObjectMeta{Name: "mysink"},
+	}, nil)
+
+	eventingClient := eventing_client.NewMockKnEventingClient(t)
+	eventingRecorder := eventingClient.Recorder()
+
+	trigger1 := createTriggerWithStatus("default", "trigger1", map[string]string{"type": "dev.knative.foo"}, "mybroker1", "mysink")
+	trigger2 := createTriggerWithStatus("default", "trigger2", map[string]string{"source": "svc.service.knative"}, "mybroker2", "mysink")
+	trigger3 := createTriggerWithStatus("default", "trigger3", map[string]string{"type": "src.eventing.knative"}, "mybroker3", "mysink")
+	triggerList := &v1alpha1.TriggerList{Items: []v1alpha1.Trigger{*trigger1, *trigger2, *trigger3}}
+	eventingRecorder.ListTriggers(triggerList, nil)
+
+	output, err := executeTriggerCommand(eventingClient, servingClient, "list")
+	assert.NilError(t, err)
+
+	outputLines := strings.Split(output, "\n")
+	assert.Check(t, util.ContainsAll(outputLines[0], "NAME", "BROKER", "SUBSCRIBER_URI", "READY", "REASON"))
+	assert.Check(t, util.ContainsAll(outputLines[1], "trigger1", "mybroker1", "mysink"))
+	assert.Check(t, util.ContainsAll(outputLines[2], "trigger2", "mybroker2", "mysink"))
+	assert.Check(t, util.ContainsAll(outputLines[3], "trigger3", "mybroker3", "mysink"))
+
+	eventingRecorder.Validate()
+}
+
+func TestTriggerListEmpty(t *testing.T) {
+	eventingClient := eventing_client.NewMockKnEventingClient(t)
+	eventingRecorder := eventingClient.Recorder()
+
+	eventingRecorder.ListTriggers(&v1alpha1.TriggerList{}, nil)
+	output, err := executeTriggerCommand(eventingClient, nil, "list")
+	assert.NilError(t, err)
+	assert.Assert(t, util.ContainsAll(output, "No", "triggers", "found"))
+
+	eventingRecorder.Validate()
+}
+
+func TestTriggerListAllNamespace(t *testing.T) {
+	servingClient := knserving_client.NewMockKnServiceClient(t)
+	servingRecorder := servingClient.Recorder()
+	servingRecorder.GetService("mysink", &serving_v1alpha1.Service{
+		TypeMeta:   metav1.TypeMeta{Kind: "Service"},
+		ObjectMeta: metav1.ObjectMeta{Name: "mysink"},
+	}, nil)
+
+	eventingClient := eventing_client.NewMockKnEventingClient(t)
+	eventingRecorder := eventingClient.Recorder()
+
+	trigger1 := createTriggerWithStatus("default1", "trigger1", map[string]string{"type": "dev.knative.foo"}, "mybroker1", "mysink")
+	trigger2 := createTriggerWithStatus("default2", "trigger2", map[string]string{"source": "svc.service.knative"}, "mybroker2", "mysink")
+	trigger3 := createTriggerWithStatus("default3", "trigger3", map[string]string{"type": "src.eventing.knative"}, "mybroker3", "mysink")
+	triggerList := &v1alpha1.TriggerList{Items: []v1alpha1.Trigger{*trigger1, *trigger2, *trigger3}}
+	eventingRecorder.ListTriggers(triggerList, nil)
+
+	output, err := executeTriggerCommand(eventingClient, servingClient, "list", "--all-namespaces")
+	assert.NilError(t, err)
+
+	outputLines := strings.Split(output, "\n")
+	assert.Check(t, util.ContainsAll(outputLines[0], "NAMESPACE", "NAME", "BROKER", "SUBSCRIBER_URI", "READY", "REASON"))
+	assert.Check(t, util.ContainsAll(outputLines[1], "default1", "trigger1", "mybroker1", "mysink"))
+	assert.Check(t, util.ContainsAll(outputLines[2], "default2", "trigger2", "mybroker2", "mysink"))
+	assert.Check(t, util.ContainsAll(outputLines[3], "default3", "trigger3", "mybroker3", "mysink"))
+
+	eventingRecorder.Validate()
+}
diff --git a/pkg/kn/commands/trigger/trigger_test.go b/pkg/kn/commands/trigger/trigger_test.go
index 6640ca0a..555e6cdb 100644
--- a/pkg/kn/commands/trigger/trigger_test.go
+++ b/pkg/kn/commands/trigger/trigger_test.go
@@ -17,11 +17,16 @@ package trigger
 import (
 	"bytes"
 
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/tools/clientcmd"
 
 	eventc_v1alpha1 "knative.dev/client/pkg/eventing/v1alpha1"
 	"knative.dev/client/pkg/kn/commands"
 	serving_client_v1alpha1 "knative.dev/client/pkg/serving/v1alpha1"
+	"knative.dev/eventing/pkg/apis/eventing/v1alpha1"
+	"knative.dev/pkg/apis"
+	duckv1 "knative.dev/pkg/apis/duck/v1"
 )
 
 // Helper methods
@@ -70,3 +75,40 @@ func executeTriggerCommand(triggerClient eventc_v1alpha1.KnEventingClient, servi
 
 	return output.String(), err
 }
+
+func createTrigger(namespace string, name string, filters map[string]string, broker string, svcname string) *v1alpha1.Trigger {
+	triggerFilterAttributes := v1alpha1.TriggerFilterAttributes(filters)
+	wanted := &v1alpha1.Trigger{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+		Spec: v1alpha1.TriggerSpec{
+			Broker: broker,
+			Filter: &v1alpha1.TriggerFilter{
+				Attributes: &triggerFilterAttributes,
+			},
+			Subscriber: &duckv1.Destination{
+				Ref: &corev1.ObjectReference{
+					Name: svcname,
+					Kind: "Service",
+				},
+			},
+		},
+	}
+	return wanted
+}
+
+func createTriggerWithStatus(namespace string, name string, filters map[string]string, broker string, svcname string) *v1alpha1.Trigger {
+	wanted := createTrigger(namespace, name, filters, broker, svcname)
+	wanted.Status = v1alpha1.TriggerStatus{
+		Status: duckv1.Status{
+			Conditions: []apis.Condition{{
+				Type:   "Ready",
+				Status: "True",
+			}},
+		},
+		SubscriberURI: apis.HTTP(svcname),
+	}
+	return wanted
+}