Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: --no-headers flag for resource listing #262

Merged
merged 1 commit into from
Sep 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"))
navidshaikh marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}
}
}