Skip to content

Commit

Permalink
Added --no-headers flag for resource listing
Browse files Browse the repository at this point in the history
  • Loading branch information
Komal Dhull committed Sep 2, 2019
1 parent 0ff537a commit 59d1a62
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 53 deletions.
1 change: 1 addition & 0 deletions docs/cmd/kn_revision_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down
1 change: 1 addition & 0 deletions docs/cmd/kn_route_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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].
```
Expand Down
1 change: 1 addition & 0 deletions docs/cmd/kn_service_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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].
```
Expand Down
9 changes: 6 additions & 3 deletions pkg/kn/commands/human_readable_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,29 @@ 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
}

// 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
Expand Down
44 changes: 24 additions & 20 deletions pkg/kn/commands/revision/revision_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand All @@ -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") {
Expand All @@ -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") {
Expand All @@ -105,16 +97,32 @@ 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")
revision3 := createMockRevisionWithParams("foo-abcd", "svc2", "1")
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") {
Expand All @@ -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")
}
Expand All @@ -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") {
Expand Down
31 changes: 21 additions & 10 deletions pkg/kn/commands/route/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand All @@ -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") {
Expand All @@ -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") {
Expand Down
1 change: 1 addition & 0 deletions pkg/kn/commands/service/service_list_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
36 changes: 22 additions & 14 deletions pkg/kn/commands/service/service_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand All @@ -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") {
Expand All @@ -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") {
Expand Down Expand Up @@ -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") {
Expand Down
1 change: 1 addition & 0 deletions pkg/printers/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
14 changes: 8 additions & 6 deletions pkg/printers/tableprinter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions pkg/util/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
46 changes: 46 additions & 0 deletions pkg/util/compare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
{
Expand Down Expand Up @@ -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)
}
}
}
}

0 comments on commit 59d1a62

Please sign in to comment.