diff --git a/docs/cmd/kn_revision_list.md b/docs/cmd/kn_revision_list.md index 3e658c8572..6baeec1209 100644 --- a/docs/cmd/kn_revision_list.md +++ b/docs/cmd/kn_revision_list.md @@ -34,6 +34,7 @@ kn revision list [name] [flags] --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 List the requested object(s) in given namespace. + --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. -s, --service string Service name --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]. diff --git a/docs/cmd/kn_route_list.md b/docs/cmd/kn_route_list.md index 4391699a29..9771f0b631 100644 --- a/docs/cmd/kn_route_list.md +++ b/docs/cmd/kn_route_list.md @@ -31,6 +31,7 @@ kn route list NAME [flags] --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 List the requested object(s) in given namespace. + --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]. ``` diff --git a/docs/cmd/kn_service_list.md b/docs/cmd/kn_service_list.md index 3d230321e6..a3c24b6df6 100644 --- a/docs/cmd/kn_service_list.md +++ b/docs/cmd/kn_service_list.md @@ -31,6 +31,7 @@ kn service list [name] [flags] --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 List the requested object(s) in given namespace. + --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]. ``` diff --git a/pkg/kn/commands/human_readable_flags.go b/pkg/kn/commands/human_readable_flags.go index a249466987..6d64f868bc 100644 --- a/pkg/kn/commands/human_readable_flags.go +++ b/pkg/kn/commands/human_readable_flags.go @@ -31,18 +31,20 @@ import ( // how to handle printing based on these values. type HumanPrintFlags struct { WithNamespace bool + NoHeaders bool + //TODO: Add more flags as required } // AllowedFormats returns more customized formating options func (f *HumanPrintFlags) AllowedFormats() []string { // TODO: Add more formats eg: wide - return []string{""} + return []string{"no-headers"} } // ToPrinter receives returns a printer capable of // handling human-readable output. func (f *HumanPrintFlags) ToPrinter(getHandlerFunc func(h hprinters.PrintHandler)) (hprinters.ResourcePrinter, error) { - p := hprinters.NewTablePrinter(hprinters.PrintOptions{f.WithNamespace}) + p := hprinters.NewTablePrinter(hprinters.PrintOptions{AllNamespaces: f.WithNamespace, NoHeaders: f.NoHeaders}) getHandlerFunc(p) return p, nil } @@ -50,7 +52,8 @@ func (f *HumanPrintFlags) ToPrinter(getHandlerFunc func(h hprinters.PrintHandler // AddFlags receives a *cobra.Command reference and binds // flags related to human-readable printing to it func (f *HumanPrintFlags) AddFlags(c *cobra.Command) { - // TODO: Add more flags as required + c.Flags().BoolVar(&f.NoHeaders, "no-headers", false, "When using the default output format, don't print headers (default: print headers).") + //TODO: Add more flags as required } // NewHumanPrintFlags returns flags associated with diff --git a/pkg/kn/commands/revision/revision_list_test.go b/pkg/kn/commands/revision/revision_list_test.go index 0d20c250ba..48090e4088 100644 --- a/pkg/kn/commands/revision/revision_list_test.go +++ b/pkg/kn/commands/revision/revision_list_test.go @@ -50,10 +50,7 @@ func fakeRevisionList(args []string, response *v1alpha1.RevisionList) (action cl func TestRevisionListEmpty(t *testing.T) { action, output, err := fakeRevisionList([]string{"revision", "list"}, &v1alpha1.RevisionList{}) - if err != nil { - t.Error(err) - return - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "revisions") { @@ -65,10 +62,7 @@ func TestRevisionListEmpty(t *testing.T) { func TestRevisionListEmptyByName(t *testing.T) { action, _, err := fakeRevisionList([]string{"revision", "list", "name"}, &v1alpha1.RevisionList{}) - if err != nil { - t.Error(err) - return - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "revisions") { @@ -88,9 +82,7 @@ func TestRevisionListDefaultOutput(t *testing.T) { RevisionList := &v1alpha1.RevisionList{Items: []v1alpha1.Revision{ *revision1, *revision2, *revision3, *revision4, *revision5, *revision6}} action, output, err := fakeRevisionList([]string{"revision", "list"}, RevisionList) - if err != nil { - t.Fatal(err) - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "revisions") { @@ -105,6 +97,24 @@ func TestRevisionListDefaultOutput(t *testing.T) { assert.Check(t, util.ContainsAll(output[6], "foo-abcd", "foo", "1")) } +func TestRevisionListDefaultOutputNoHeaders(t *testing.T) { + revision1 := createMockRevisionWithParams("foo-abcd", "foo", "2") + revision2 := createMockRevisionWithParams("bar-wxyz", "bar", "1") + RevisionList := &v1alpha1.RevisionList{Items: []v1alpha1.Revision{*revision1, *revision2}} + action, output, err := fakeRevisionList([]string{"revision", "list", "--no-headers"}, RevisionList) + assert.NilError(t, err) + if action == nil { + t.Errorf("No action") + } else if !action.Matches("list", "revisions") { + t.Errorf("Bad action %v", action) + } + + assert.Check(t, util.ContainsNone(output[0], "NAME", "URL", "GENERATION", "AGE", "CONDITIONS", "READY", "REASON")) + assert.Check(t, util.ContainsAll(output[0], "foo-abcd", "foo", "2")) + assert.Check(t, util.ContainsAll(output[1], "bar-wxyz", "bar", "1")) + +} + func TestRevisionListForService(t *testing.T) { revision1 := createMockRevisionWithParams("foo-abcd", "svc1", "1") revision2 := createMockRevisionWithParams("bar-wxyz", "svc1", "2") @@ -112,9 +122,7 @@ func TestRevisionListForService(t *testing.T) { revision4 := createMockRevisionWithParams("bar-wxyz", "svc2", "2") RevisionList := &v1alpha1.RevisionList{Items: []v1alpha1.Revision{*revision1, *revision2, *revision3, *revision4}} action, output, err := fakeRevisionList([]string{"revision", "list", "-s", "svc1"}, RevisionList) - if err != nil { - t.Fatal(err) - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "revisions") { @@ -137,9 +145,7 @@ func TestRevisionListForService(t *testing.T) { assert.Check(t, util.ContainsAll(output[2], "foo-abcd", "svc2")) //test for non existent service action, output, err = fakeRevisionList([]string{"revision", "list", "-s", "svc3"}, RevisionList) - if err != nil { - t.Fatal(err) - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } @@ -153,9 +159,7 @@ func TestRevisionListOneOutput(t *testing.T) { revision := createMockRevisionWithParams("foo-abcd", "foo", "1") RevisionList := &v1alpha1.RevisionList{Items: []v1alpha1.Revision{*revision}} action, output, err := fakeRevisionList([]string{"revision", "list", "foo-abcd"}, RevisionList) - if err != nil { - t.Fatal(err) - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "revisions") { diff --git a/pkg/kn/commands/route/list_test.go b/pkg/kn/commands/route/list_test.go index fb74401b99..7cbb788c57 100644 --- a/pkg/kn/commands/route/list_test.go +++ b/pkg/kn/commands/route/list_test.go @@ -46,10 +46,7 @@ func fakeRouteList(args []string, response *v1alpha1.RouteList) (action client_t func TestListEmpty(t *testing.T) { action, output, err := fakeRouteList([]string{"route", "list"}, &v1alpha1.RouteList{}) - if err != nil { - t.Error(err) - return - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "routes") { @@ -64,9 +61,7 @@ func TestRouteListDefaultOutput(t *testing.T) { route2 := createMockRouteSingleTarget("bar", "bar-98765", 100) routeList := &v1alpha1.RouteList{Items: []v1alpha1.Route{*route1, *route2}} action, output, err := fakeRouteList([]string{"route", "list"}, routeList) - if err != nil { - t.Fatal(err) - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "routes") { @@ -77,13 +72,29 @@ func TestRouteListDefaultOutput(t *testing.T) { assert.Check(t, util.ContainsAll(output[2], "bar", "100% -> bar-98765")) } +func TestRouteListDefaultOutputNoHeaders(t *testing.T) { + route1 := createMockRouteSingleTarget("foo", "foo-01234", 100) + route2 := createMockRouteSingleTarget("bar", "bar-98765", 100) + routeList := &v1alpha1.RouteList{Items: []v1alpha1.Route{*route1, *route2}} + action, output, err := fakeRouteList([]string{"route", "list", "--no-headers"}, routeList) + assert.NilError(t, err) + if action == nil { + t.Errorf("No action") + } else if !action.Matches("list", "routes") { + t.Errorf("Bad action %v", action) + } + + assert.Check(t, util.ContainsNone(output[0], "NAME", "URL", "GENERATION", "AGE", "CONDITIONS", "READY", "REASON")) + assert.Check(t, util.ContainsAll(output[0], "foo", "100% -> foo-01234")) + assert.Check(t, util.ContainsAll(output[1], "bar", "100% -> bar-98765")) + +} + func TestRouteListWithTwoTargetsOutput(t *testing.T) { route := createMockRouteTwoTarget("foo", "foo-01234", "foo-98765", 20, 80) routeList := &v1alpha1.RouteList{Items: []v1alpha1.Route{*route}} action, output, err := fakeRouteList([]string{"route", "list"}, routeList) - if err != nil { - t.Fatal(err) - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "routes") { diff --git a/pkg/kn/commands/service/service_list_flags.go b/pkg/kn/commands/service/service_list_flags.go index 4b31fe7ebf..1bff01a054 100644 --- a/pkg/kn/commands/service/service_list_flags.go +++ b/pkg/kn/commands/service/service_list_flags.go @@ -58,6 +58,7 @@ func (f *ServiceListFlags) ToPrinter() (hprinters.ResourcePrinter, error) { // flags related to humanreadable and template printing. func (f *ServiceListFlags) AddFlags(cmd *cobra.Command) { f.GenericPrintFlags.AddFlags(cmd) + f.HumanReadableFlags.AddFlags(cmd) } // NewServiceListFlags returns flags associated with humanreadable, diff --git a/pkg/kn/commands/service/service_list_test.go b/pkg/kn/commands/service/service_list_test.go index 15a443edf0..e4eb62c172 100644 --- a/pkg/kn/commands/service/service_list_test.go +++ b/pkg/kn/commands/service/service_list_test.go @@ -49,10 +49,7 @@ func fakeServiceList(args []string, response *v1alpha1.ServiceList) (action clie func TestListEmpty(t *testing.T) { action, output, err := fakeServiceList([]string{"service", "list"}, &v1alpha1.ServiceList{}) - if err != nil { - t.Error(err) - return - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "services") { @@ -64,10 +61,7 @@ func TestListEmpty(t *testing.T) { func TestGetEmpty(t *testing.T) { action, _, err := fakeServiceList([]string{"service", "list", "name"}, &v1alpha1.ServiceList{}) - if err != nil { - t.Error(err) - return - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "services") { @@ -81,9 +75,7 @@ func TestServiceListDefaultOutput(t *testing.T) { service2 := createMockServiceWithParams("bar", "default", "http://bar.default.example.com", 1) serviceList := &v1alpha1.ServiceList{Items: []v1alpha1.Service{*service1, *service2, *service3}} action, output, err := fakeServiceList([]string{"service", "list"}, serviceList) - if err != nil { - t.Fatal(err) - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "services") { @@ -117,13 +109,29 @@ func TestServiceListAllNamespacesOutput(t *testing.T) { assert.Check(t, util.ContainsAll(output[3], "foo", "bar", "bar.foo.example.com", "2")) } +func TestServiceListDefaultOutputNoHeaders(t *testing.T) { + service1 := createMockServiceWithParams("foo", "default", "http://foo.default.example.com", 1) + service2 := createMockServiceWithParams("bar", "default", "http://bar.default.example.com", 2) + serviceList := &v1alpha1.ServiceList{Items: []v1alpha1.Service{*service1, *service2}} + action, output, err := fakeServiceList([]string{"service", "list", "--no-headers"}, serviceList) + assert.NilError(t, err) + if action == nil { + t.Errorf("No action") + } else if !action.Matches("list", "services") { + t.Errorf("Bad action %v", action) + } + + assert.Check(t, util.ContainsNone(output[0], "NAME", "URL", "GENERATION", "AGE", "CONDITIONS", "READY", "REASON")) + assert.Check(t, util.ContainsAll(output[0], "bar", "bar.default.example.com", "2")) + assert.Check(t, util.ContainsAll(output[1], "foo", "foo.default.example.com", "1")) + +} + func TestServiceGetOneOutput(t *testing.T) { service := createMockServiceWithParams("foo", "default", "foo.default.example.com", 1) serviceList := &v1alpha1.ServiceList{Items: []v1alpha1.Service{*service}} action, output, err := fakeServiceList([]string{"service", "list", "foo"}, serviceList) - if err != nil { - t.Fatal(err) - } + assert.NilError(t, err) if action == nil { t.Errorf("No action") } else if !action.Matches("list", "services") { diff --git a/pkg/printers/interface.go b/pkg/printers/interface.go index 1127b20d51..517ff2391a 100644 --- a/pkg/printers/interface.go +++ b/pkg/printers/interface.go @@ -38,6 +38,7 @@ func (fn ResourcePrinterFunc) PrintObj(obj runtime.Object, w io.Writer) error { // PrintOptions for different table printing options type PrintOptions struct { + NoHeaders bool //TODO: Add options for eg: with-kind, server-printing, wide etc AllNamespaces bool } diff --git a/pkg/printers/tableprinter.go b/pkg/printers/tableprinter.go index ac4824eff0..f51bc1390f 100644 --- a/pkg/printers/tableprinter.go +++ b/pkg/printers/tableprinter.go @@ -74,14 +74,16 @@ func printRowsForHandlerEntry(output io.Writer, handler *handlerEntry, obj runti return results[1].Interface().(error) } - var headers []string - for _, column := range handler.columnDefinitions { - if !options.AllNamespaces && column.Priority == 0 { - continue + if !options.NoHeaders { + var headers []string + for _, column := range handler.columnDefinitions { + if !options.AllNamespaces && column.Priority == 0 { + continue + } + headers = append(headers, strings.ToUpper(column.Name)) } - headers = append(headers, strings.ToUpper(column.Name)) + printHeader(headers, output) } - printHeader(headers, output) if results[1].IsNil() { rows := results[0].Interface().([]metav1beta1.TableRow) diff --git a/pkg/util/compare.go b/pkg/util/compare.go index de0064cc15..2c90b4a109 100644 --- a/pkg/util/compare.go +++ b/pkg/util/compare.go @@ -38,3 +38,21 @@ func ContainsAll(target string, substrings ...string) cmp.Comparison { return cmp.ResultSuccess } } + +// ContainsNone is a comparison utility, compares given substrings against +// target string and returns the gotest.tools/assert/cmp.Comaprison function. +// Provide target string as first arg, followed by any number of substring as args +func ContainsNone(target string, substrings ...string) cmp.Comparison { + return func() cmp.Result { + var contains []string + for _, sub := range substrings { + if strings.Contains(target, sub) { + contains = append(contains, sub) + } + } + if len(contains) > 0 { + return cmp.ResultFailure(fmt.Sprintf("\nActual output: %s\nContains strings: %s", target, strings.Join(contains[:], ", "))) + } + return cmp.ResultSuccess + } +} diff --git a/pkg/util/compare_test.go b/pkg/util/compare_test.go index 670f2b669b..9cb9f7341d 100644 --- a/pkg/util/compare_test.go +++ b/pkg/util/compare_test.go @@ -30,6 +30,13 @@ type containsAllTestCase struct { missing []string } +type containsNoneTestCase struct { + target string + substrings []string + success bool + contains []string +} + func TestContainsAll(t *testing.T) { for i, tc := range []containsAllTestCase{ { @@ -68,3 +75,42 @@ func TestContainsAll(t *testing.T) { } } } + +func TestContainsNone(t *testing.T) { + for i, tc := range []containsNoneTestCase{ + { + target: "NAME SERVICE AGE CONDITIONS", + substrings: []string{"REASON", "READY", "foo", "bar"}, + success: true, + }, + { + "NAME SERVICE AGE CONDITIONS READY REASON", + []string{"foo", "bar", "NAME", "AGE"}, + false, + []string{"NAME", "AGE"}, + }, + { + "No resources found", + []string{"NAME", "URL", "DOMAIN", "READY", "resources"}, + false, + []string{"resources"}, + }, + { + target: "Sword!", + substrings: []string{}, + success: true, + }, + } { + comparison := ContainsNone(tc.target, tc.substrings...) + result := comparison() + if result.Success() != tc.success { + t.Errorf("%d: Expecting %s to contain %s", i, tc.target, tc.substrings) + } + if !tc.success { + message := fmt.Sprintf("\nActual output: %s\nContains strings: %s", tc.target, strings.Join(tc.contains[:], ", ")) + if !reflect.DeepEqual(result, cmp.ResultFailure(message)) { + t.Errorf("%d: Incorrect error message returned\nExpecting: %s", i, message) + } + } + } +}