From 6e0c5d06ab2693f89894a9c2ece3458ab3a7d49e Mon Sep 17 00:00:00 2001 From: Tyler Sullivan Date: Sun, 29 Dec 2024 16:22:53 -0800 Subject: [PATCH 1/9] initial output wrapper lib --- internal/cmd/organizations/list.go | 46 ++++++-------------- internal/output/output.go | 54 +++++++++++++++++++++++ internal/output/output_test.go | 70 ++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 33 deletions(-) create mode 100644 internal/output/output.go create mode 100644 internal/output/output_test.go diff --git a/internal/cmd/organizations/list.go b/internal/cmd/organizations/list.go index 65df323..140a3ca 100644 --- a/internal/cmd/organizations/list.go +++ b/internal/cmd/organizations/list.go @@ -2,14 +2,13 @@ package organizations import ( "fmt" + "os" resourcemanagerv1alpha "buf.build/gen/go/datum-cloud/datum-os/protocolbuffers/go/datum/os/resourcemanager/v1alpha" - "buf.build/go/protoyaml" - "github.com/rodaine/table" "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/protojson" "go.datum.net/datumctl/internal/keyring" + "go.datum.net/datumctl/internal/output" "go.datum.net/datumctl/internal/resourcemanager" ) @@ -35,36 +34,8 @@ func listOrgsCommand() *cobra.Command { return fmt.Errorf("failed to list organizations: %w", err) } - // TODO: We should look at abstracting the formatting here into a library - // that can be used by multiple commands needing to offer multiple - // output formats from a command. - switch outputFormat { - case "yaml": - marshaller := &protoyaml.MarshalOptions{ - Indent: 2, - } - output, err := marshaller.Marshal(listOrgs) - if err != nil { - return fmt.Errorf("failed to list organizations: %w", err) - } - fmt.Print(string(output)) - case "json": - output, err := protojson.Marshal(listOrgs) - if err != nil { - return fmt.Errorf("failed to list organizations: %w", err) - } - fmt.Print(string(output)) - case "table": - orgTable := table.New("DISPLAY NAME", "RESOURCE ID") - if len(listOrgs.Organizations) == 0 { - fmt.Printf("No organizations found") - } else { - for _, org := range listOrgs.Organizations { - orgTable.AddRow(org.DisplayName, org.OrganizationId) - } - } - orgTable.Print() - } + outTableHeaders, outTableData := getListOrganizationsTableOutputData(listOrgs) + output.CLIPrint(os.Stdout, outputFormat, listOrgs, outTableHeaders, outTableData) return nil }, @@ -75,3 +46,12 @@ func listOrgsCommand() *cobra.Command { return cmd } + +func getListOrganizationsTableOutputData(listOrgs *resourcemanagerv1alpha.ListOrganizationsResponse) ([]any, [][]any) { + headers := []any{"DISPLAY NAME", "RESOURCE ID"} + var rowData [][]any + for _, org := range listOrgs.Organizations { + rowData = append(rowData, []any{org.DisplayName, org.OrganizationId}) + } + return headers, rowData +} diff --git a/internal/output/output.go b/internal/output/output.go new file mode 100644 index 0000000..4415951 --- /dev/null +++ b/internal/output/output.go @@ -0,0 +1,54 @@ +package output + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/rodaine/table" + "gopkg.in/yaml.v3" +) + +func CLIPrint(w io.Writer, format string, data interface{}, headers []any, rowData [][]any) error { + switch format { + case "yaml": + return printYAML(w, data) + case "json": + return printJSON(w, data) + case "table": + if headers == nil || rowData == nil { + return fmt.Errorf("headers and rowData must be provided for table output") + } + return printTable(w, headers, rowData) + default: + return fmt.Errorf("unsupported format: %s", format) + } +} + +func printYAML(w io.Writer, data interface{}) error { + output, err := yaml.Marshal(data) + if err != nil { + return fmt.Errorf("failed to marshal data to YAML: %w", err) + } + fmt.Fprint(w, string(output)) + return nil +} + +func printJSON(w io.Writer, data interface{}) error { + output, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal data to JSON: %w", err) + } + fmt.Fprint(w, string(output)) + return nil +} + +func printTable(w io.Writer, headers []any, rowData [][]any) error { + t := table.New(headers...) + t.WithWriter(w) + for _, row := range rowData { + t.AddRow(row) + } + t.Print() + return nil +} diff --git a/internal/output/output_test.go b/internal/output/output_test.go new file mode 100644 index 0000000..4009e9e --- /dev/null +++ b/internal/output/output_test.go @@ -0,0 +1,70 @@ +package output + +import ( + "bytes" + "testing" +) + +func TestCLIPrint(t *testing.T) { + tests := []struct { + name string + format string + data interface{} + headers []any + rowData [][]any + wantErr bool + wantOutput string + }{ + { + name: "Print YAML", + format: "yaml", + data: map[string]string{"key": "value"}, + wantErr: false, + wantOutput: "key: value\n", + }, + { + name: "Print JSON", + format: "json", + data: map[string]string{"key": "value"}, + wantErr: false, + wantOutput: "{\n \"key\": \"value\"\n}", + }, + // { + // name: "Print Table", + // format: "table", + // headers: []any{"Header1", "Header2"}, + // rowData: [][]any{{"Row1Col1", "Row1Col2"}, {"Row2Col1", "Row2Col2"}}, + // wantErr: false, + // wantOutput: "Header1 Header2\n[Row1Col1 Row1Col2] \n[Row2Col1 Row2Col2] \n", + // }, + { + name: "Unsupported Format", + format: "unsupported", + data: map[string]string{"key": "value"}, + wantErr: true, + }, + { + name: "Table without headers and rowData", + format: "table", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + + err := CLIPrint(&buf, tt.format, tt.data, tt.headers, tt.rowData) + if (err != nil) != tt.wantErr { + t.Errorf("CLIPrint() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + if gotOutput := buf.String(); gotOutput != tt.wantOutput { + t.Errorf("CLIPrint() output = %v, want %v", gotOutput, tt.wantOutput) + } + } + }) + } +} From 5f01c4cf9bbae2c540de9b05d28995365b36ef61 Mon Sep 17 00:00:00 2001 From: Tyler Sullivan Date: Sun, 29 Dec 2024 18:55:48 -0800 Subject: [PATCH 2/9] fix: testing table output --- internal/output/output_test.go | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/internal/output/output_test.go b/internal/output/output_test.go index 4009e9e..a12aca0 100644 --- a/internal/output/output_test.go +++ b/internal/output/output_test.go @@ -2,6 +2,7 @@ package output import ( "bytes" + "strings" "testing" ) @@ -29,14 +30,13 @@ func TestCLIPrint(t *testing.T) { wantErr: false, wantOutput: "{\n \"key\": \"value\"\n}", }, - // { - // name: "Print Table", - // format: "table", - // headers: []any{"Header1", "Header2"}, - // rowData: [][]any{{"Row1Col1", "Row1Col2"}, {"Row2Col1", "Row2Col2"}}, - // wantErr: false, - // wantOutput: "Header1 Header2\n[Row1Col1 Row1Col2] \n[Row2Col1 Row2Col2] \n", - // }, + { + name: "Print Table", + format: "table", + headers: []any{"Header1", "Header2"}, + rowData: [][]any{{"Row1Col1", "Row1Col2"}, {"Row2Col1", "Row2Col2"}}, + wantErr: false, + }, { name: "Unsupported Format", format: "unsupported", @@ -61,8 +61,15 @@ func TestCLIPrint(t *testing.T) { } if !tt.wantErr { - if gotOutput := buf.String(); gotOutput != tt.wantOutput { - t.Errorf("CLIPrint() output = %v, want %v", gotOutput, tt.wantOutput) + if tt.format == "table" { + out := buf.String() + if !strings.Contains(out, tt.headers[0].(string)) || !strings.Contains(out, tt.headers[1].(string)) { + t.Errorf("CLIPrint() output = %v, does not have correct headers", out) + } + } else { + if gotOutput := buf.String(); gotOutput != tt.wantOutput { + t.Errorf("CLIPrint() output = %v, want %v", gotOutput, tt.wantOutput) + } } } }) From 764d6ca1b0b32f8c3c6e60299d95833cc792d94c Mon Sep 17 00:00:00 2001 From: Tyler Sullivan Date: Sun, 29 Dec 2024 19:03:29 -0800 Subject: [PATCH 3/9] add: test for the getListOrganizationsTableOutputData. We dont want this to break if we change the data model --- internal/cmd/organizations/list_test.go | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 internal/cmd/organizations/list_test.go diff --git a/internal/cmd/organizations/list_test.go b/internal/cmd/organizations/list_test.go new file mode 100644 index 0000000..86cf697 --- /dev/null +++ b/internal/cmd/organizations/list_test.go @@ -0,0 +1,48 @@ +package organizations + +import ( + "testing" + + resourcemanagerv1alpha "buf.build/gen/go/datum-cloud/datum-os/protocolbuffers/go/datum/os/resourcemanager/v1alpha" +) + +func TestGetListOrganizationsTableOutputData(t *testing.T) { + listOrgs := &resourcemanagerv1alpha.ListOrganizationsResponse{ + Organizations: []*resourcemanagerv1alpha.Organization{ + { + DisplayName: "Org1", + OrganizationId: "org1", + }, + { + DisplayName: "Org2", + OrganizationId: "org2", + }, + }, + } + + headers, rowData := getListOrganizationsTableOutputData(listOrgs) + if len(headers) != 2 { + t.Errorf("Expected 2 headers, got %d", len(headers)) + } + if headers[0] != "DISPLAY NAME" { + t.Errorf("Expected DISPLAY NAME, got %s", headers[0]) + } + if headers[1] != "RESOURCE ID" { + t.Errorf("Expected RESOURCE ID, got %s", headers[1]) + } + if len(rowData) != 2 { + t.Errorf("Expected 2 rows, got %d", len(rowData)) + } + if rowData[0][0] != "Org1" { + t.Errorf("Expected Org1, got %s", rowData[0][0]) + } + if rowData[0][1] != "org1" { + t.Errorf("Expected org1, got %s", rowData[0][1]) + } + if rowData[1][0] != "Org2" { + t.Errorf("Expected Org2, got %s", rowData[1][0]) + } + if rowData[1][1] != "org2" { + t.Errorf("Expected org2, got %s", rowData[1][1]) + } +} From c921412b7b9f63be7660bc6a2fdd344abcc7b0fe Mon Sep 17 00:00:00 2001 From: Tyler Sullivan Date: Mon, 30 Dec 2024 10:19:01 -0800 Subject: [PATCH 4/9] fix: remove abstraction for table build --- internal/cmd/organizations/list.go | 17 ++++----- internal/cmd/organizations/list_test.go | 48 ------------------------- 2 files changed, 6 insertions(+), 59 deletions(-) delete mode 100644 internal/cmd/organizations/list_test.go diff --git a/internal/cmd/organizations/list.go b/internal/cmd/organizations/list.go index 140a3ca..4a1f3b8 100644 --- a/internal/cmd/organizations/list.go +++ b/internal/cmd/organizations/list.go @@ -34,8 +34,12 @@ func listOrgsCommand() *cobra.Command { return fmt.Errorf("failed to list organizations: %w", err) } - outTableHeaders, outTableData := getListOrganizationsTableOutputData(listOrgs) - output.CLIPrint(os.Stdout, outputFormat, listOrgs, outTableHeaders, outTableData) + headers := []any{"DISPLAY NAME", "RESOURCE ID"} + var rowData [][]any + for _, org := range listOrgs.Organizations { + rowData = append(rowData, []any{org.DisplayName, org.OrganizationId}) + } + output.CLIPrint(os.Stdout, outputFormat, listOrgs, headers, rowData) return nil }, @@ -46,12 +50,3 @@ func listOrgsCommand() *cobra.Command { return cmd } - -func getListOrganizationsTableOutputData(listOrgs *resourcemanagerv1alpha.ListOrganizationsResponse) ([]any, [][]any) { - headers := []any{"DISPLAY NAME", "RESOURCE ID"} - var rowData [][]any - for _, org := range listOrgs.Organizations { - rowData = append(rowData, []any{org.DisplayName, org.OrganizationId}) - } - return headers, rowData -} diff --git a/internal/cmd/organizations/list_test.go b/internal/cmd/organizations/list_test.go deleted file mode 100644 index 86cf697..0000000 --- a/internal/cmd/organizations/list_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package organizations - -import ( - "testing" - - resourcemanagerv1alpha "buf.build/gen/go/datum-cloud/datum-os/protocolbuffers/go/datum/os/resourcemanager/v1alpha" -) - -func TestGetListOrganizationsTableOutputData(t *testing.T) { - listOrgs := &resourcemanagerv1alpha.ListOrganizationsResponse{ - Organizations: []*resourcemanagerv1alpha.Organization{ - { - DisplayName: "Org1", - OrganizationId: "org1", - }, - { - DisplayName: "Org2", - OrganizationId: "org2", - }, - }, - } - - headers, rowData := getListOrganizationsTableOutputData(listOrgs) - if len(headers) != 2 { - t.Errorf("Expected 2 headers, got %d", len(headers)) - } - if headers[0] != "DISPLAY NAME" { - t.Errorf("Expected DISPLAY NAME, got %s", headers[0]) - } - if headers[1] != "RESOURCE ID" { - t.Errorf("Expected RESOURCE ID, got %s", headers[1]) - } - if len(rowData) != 2 { - t.Errorf("Expected 2 rows, got %d", len(rowData)) - } - if rowData[0][0] != "Org1" { - t.Errorf("Expected Org1, got %s", rowData[0][0]) - } - if rowData[0][1] != "org1" { - t.Errorf("Expected org1, got %s", rowData[0][1]) - } - if rowData[1][0] != "Org2" { - t.Errorf("Expected Org2, got %s", rowData[1][0]) - } - if rowData[1][1] != "org2" { - t.Errorf("Expected org2, got %s", rowData[1][1]) - } -} From 37d8bf4ce6cae92957a1b1255b37162c53a995f2 Mon Sep 17 00:00:00 2001 From: Tyler Sullivan Date: Mon, 30 Dec 2024 11:09:48 -0800 Subject: [PATCH 5/9] fix: use proto marshalling, not regular marshalling (facepalm.. sorry) --- internal/output/output.go | 25 ++++++++++++++++++------- internal/output/output_test.go | 23 ++++++++++++++++------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/internal/output/output.go b/internal/output/output.go index 4415951..7f703a2 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -1,15 +1,21 @@ package output import ( - "encoding/json" "fmt" "io" + "buf.build/go/protoyaml" "github.com/rodaine/table" - "gopkg.in/yaml.v3" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" ) -func CLIPrint(w io.Writer, format string, data interface{}, headers []any, rowData [][]any) error { +type ClIPrinter struct { + Writer io.Writer + TableColumns []string +} + +func CLIPrint(w io.Writer, format string, data proto.Message, headers []any, rowData [][]any) error { switch format { case "yaml": return printYAML(w, data) @@ -25,8 +31,12 @@ func CLIPrint(w io.Writer, format string, data interface{}, headers []any, rowDa } } -func printYAML(w io.Writer, data interface{}) error { - output, err := yaml.Marshal(data) +func printYAML(w io.Writer, data proto.Message) error { + marshaller := protoyaml.MarshalOptions{ + Indent: 2, + } + + output, err := marshaller.Marshal(data) if err != nil { return fmt.Errorf("failed to marshal data to YAML: %w", err) } @@ -34,8 +44,9 @@ func printYAML(w io.Writer, data interface{}) error { return nil } -func printJSON(w io.Writer, data interface{}) error { - output, err := json.MarshalIndent(data, "", " ") +func printJSON(w io.Writer, data proto.Message) error { + + output, err := protojson.Marshal(data) if err != nil { return fmt.Errorf("failed to marshal data to JSON: %w", err) } diff --git a/internal/output/output_test.go b/internal/output/output_test.go index a12aca0..cd26e39 100644 --- a/internal/output/output_test.go +++ b/internal/output/output_test.go @@ -4,13 +4,22 @@ import ( "bytes" "strings" "testing" + + resourcemanagerv1alpha "buf.build/gen/go/datum-cloud/datum-os/protocolbuffers/go/datum/os/resourcemanager/v1alpha" + "google.golang.org/protobuf/proto" ) func TestCLIPrint(t *testing.T) { + + testOrgProto := &resourcemanagerv1alpha.Organization{ + DisplayName: "Test Organization", + OrganizationId: "1234", + } + tests := []struct { name string format string - data interface{} + data proto.Message headers []any rowData [][]any wantErr bool @@ -19,16 +28,16 @@ func TestCLIPrint(t *testing.T) { { name: "Print YAML", format: "yaml", - data: map[string]string{"key": "value"}, + data: testOrgProto, wantErr: false, - wantOutput: "key: value\n", + wantOutput: "organizationId: \"1234\"\ndisplayName: Test Organization\n", }, { name: "Print JSON", format: "json", - data: map[string]string{"key": "value"}, + data: testOrgProto, wantErr: false, - wantOutput: "{\n \"key\": \"value\"\n}", + wantOutput: "{\"organizationId\":\"1234\",\"displayName\":\"Test Organization\"}", }, { name: "Print Table", @@ -40,7 +49,7 @@ func TestCLIPrint(t *testing.T) { { name: "Unsupported Format", format: "unsupported", - data: map[string]string{"key": "value"}, + data: testOrgProto, wantErr: true, }, { @@ -68,7 +77,7 @@ func TestCLIPrint(t *testing.T) { } } else { if gotOutput := buf.String(); gotOutput != tt.wantOutput { - t.Errorf("CLIPrint() output = %v, want %v", gotOutput, tt.wantOutput) + t.Errorf("CLIPrint() output = \n%v, want \n%v", gotOutput, tt.wantOutput) } } } From 048d238c07904c42a2026478cbc5cafab636066f Mon Sep 17 00:00:00 2001 From: Tyler Sullivan Date: Mon, 30 Dec 2024 11:12:14 -0800 Subject: [PATCH 6/9] chore: whitespace removal --- internal/output/output.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/output/output.go b/internal/output/output.go index 7f703a2..a385df4 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -45,7 +45,6 @@ func printYAML(w io.Writer, data proto.Message) error { } func printJSON(w io.Writer, data proto.Message) error { - output, err := protojson.Marshal(data) if err != nil { return fmt.Errorf("failed to marshal data to JSON: %w", err) From b3c6fc1ad569d1783150c72258c5e473fad4b89e Mon Sep 17 00:00:00 2001 From: Tyler Sullivan Date: Mon, 30 Dec 2024 15:15:58 -0800 Subject: [PATCH 7/9] remove unused code --- internal/output/output.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/output/output.go b/internal/output/output.go index a385df4..c8b80c4 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -10,11 +10,6 @@ import ( "google.golang.org/protobuf/proto" ) -type ClIPrinter struct { - Writer io.Writer - TableColumns []string -} - func CLIPrint(w io.Writer, format string, data proto.Message, headers []any, rowData [][]any) error { switch format { case "yaml": From 523c82e6834967d64f774e0097728f14cb5e84b9 Mon Sep 17 00:00:00 2001 From: Tyler Sullivan Date: Mon, 30 Dec 2024 18:58:35 -0800 Subject: [PATCH 8/9] guardrails for table formatter --- internal/cmd/organizations/list.go | 16 +++++++++------- internal/output/output.go | 16 +++++++++++----- internal/output/output_test.go | 6 +++++- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/internal/cmd/organizations/list.go b/internal/cmd/organizations/list.go index 4a1f3b8..7226571 100644 --- a/internal/cmd/organizations/list.go +++ b/internal/cmd/organizations/list.go @@ -34,13 +34,15 @@ func listOrgsCommand() *cobra.Command { return fmt.Errorf("failed to list organizations: %w", err) } - headers := []any{"DISPLAY NAME", "RESOURCE ID"} - var rowData [][]any - for _, org := range listOrgs.Organizations { - rowData = append(rowData, []any{org.DisplayName, org.OrganizationId}) - } - output.CLIPrint(os.Stdout, outputFormat, listOrgs, headers, rowData) - + output.CLIPrint(os.Stdout, outputFormat, listOrgs, func() (output.ColumnFormatter, output.RowFormatterFunc) { + return output.ColumnFormatter{"DISPLAY NAME", "RESOURCE ID"}, func() output.RowFormatter { + var rowData output.RowFormatter + for _, org := range listOrgs.Organizations { + rowData = append(rowData, []any{org.DisplayName, org.OrganizationId}) + } + return rowData + } + }) return nil }, } diff --git a/internal/output/output.go b/internal/output/output.go index c8b80c4..bc3ffe7 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -10,17 +10,23 @@ import ( "google.golang.org/protobuf/proto" ) -func CLIPrint(w io.Writer, format string, data proto.Message, headers []any, rowData [][]any) error { +type TableFormatterFunc func() (ColumnFormatter, RowFormatterFunc) +type RowFormatterFunc func() RowFormatter +type RowFormatter [][]any +type ColumnFormatter []any + +func CLIPrint(w io.Writer, format string, data proto.Message, tableFormatterFunc TableFormatterFunc) error { switch format { case "yaml": return printYAML(w, data) case "json": return printJSON(w, data) case "table": - if headers == nil || rowData == nil { - return fmt.Errorf("headers and rowData must be provided for table output") + headers, rowDataFunc := tableFormatterFunc() + if headers == nil { + return fmt.Errorf("headers must be provided for table output") } - return printTable(w, headers, rowData) + return printTable(w, headers, rowDataFunc()) default: return fmt.Errorf("unsupported format: %s", format) } @@ -48,7 +54,7 @@ func printJSON(w io.Writer, data proto.Message) error { return nil } -func printTable(w io.Writer, headers []any, rowData [][]any) error { +func printTable(w io.Writer, headers ColumnFormatter, rowData [][]any) error { t := table.New(headers...) t.WithWriter(w) for _, row := range rowData { diff --git a/internal/output/output_test.go b/internal/output/output_test.go index cd26e39..a76b2ba 100644 --- a/internal/output/output_test.go +++ b/internal/output/output_test.go @@ -63,7 +63,11 @@ func TestCLIPrint(t *testing.T) { t.Run(tt.name, func(t *testing.T) { var buf bytes.Buffer - err := CLIPrint(&buf, tt.format, tt.data, tt.headers, tt.rowData) + err := CLIPrint(&buf, tt.format, tt.data, func() (ColumnFormatter, RowFormatterFunc) { + return tt.headers, func() RowFormatter { + return tt.rowData + } + }) if (err != nil) != tt.wantErr { t.Errorf("CLIPrint() error = %v, wantErr %v", err, tt.wantErr) return From 52ce92821f7a5950217ee83720f48fbcef04d011 Mon Sep 17 00:00:00 2001 From: Tyler Sullivan Date: Mon, 30 Dec 2024 19:01:23 -0800 Subject: [PATCH 9/9] err checking --- internal/cmd/organizations/list.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/cmd/organizations/list.go b/internal/cmd/organizations/list.go index 7226571..bd8f02a 100644 --- a/internal/cmd/organizations/list.go +++ b/internal/cmd/organizations/list.go @@ -34,7 +34,7 @@ func listOrgsCommand() *cobra.Command { return fmt.Errorf("failed to list organizations: %w", err) } - output.CLIPrint(os.Stdout, outputFormat, listOrgs, func() (output.ColumnFormatter, output.RowFormatterFunc) { + if err := output.CLIPrint(os.Stdout, outputFormat, listOrgs, func() (output.ColumnFormatter, output.RowFormatterFunc) { return output.ColumnFormatter{"DISPLAY NAME", "RESOURCE ID"}, func() output.RowFormatter { var rowData output.RowFormatter for _, org := range listOrgs.Organizations { @@ -42,7 +42,9 @@ func listOrgsCommand() *cobra.Command { } return rowData } - }) + }); err != nil { + return fmt.Errorf("a problem occured while printing organizations list: %w", err) + } return nil }, }