Repository: FairwindsOps/pluto
Pull request #417 (2022-11-23 11:47:44)

feat: allow hiding headers in output
## Checklist
* [x] I have signed the CLA
* [x] I have updated/added any relevant documentation

## Description
### What's the goal of this PR?
Allow hiding headers in output.
### What changes did you make?
Add a `--no-headers` flag that hide headers in tab, markdown and csv outputs.
### What alternative solution should we consider, if any?
`pluto [snip] -ocsv | grep -v 'KIND,[snip]'`

aslafy-z: @sudermanjr I just applied the changes.
aslafy-z: @transient1 @sudermanjr can one of you trigger the tests please?

Patch:
diff --git a/cmd/root.go b/cmd/root.go
index 3098660..a61d6fd 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -63,6 +63,7 @@ var (
 	componentsFromUser     []string
 	onlyShowRemoved        bool
 	kubeContext            string
+	noHeaders              bool
 	exitCode               int
 )
 
@@ -84,6 +85,7 @@ func init() {
 	rootCmd.PersistentFlags().BoolVar(&ignoreDeprecations, "ignore-deprecations", false, "Ignore the default behavior to exit 2 if deprecated apiVersions are found.")
 	rootCmd.PersistentFlags().BoolVar(&ignoreRemovals, "ignore-removals", false, "Ignore the default behavior to exit 3 if removed apiVersions are found.")
 	rootCmd.PersistentFlags().BoolVarP(&onlyShowRemoved, "only-show-removed", "r", false, "Only display the apiVersions that have been removed in the target version.")
+	rootCmd.PersistentFlags().BoolVarP(&noHeaders, "no-headers", "H", false, "When using the default or custom-column output format, don't print headers (default print headers).")
 	rootCmd.PersistentFlags().StringVarP(&additionalVersionsFile, "additional-versions", "f", "", "Additional deprecated versions file to add to the list. Cannot contain any existing versions")
 	rootCmd.PersistentFlags().StringToStringVarP(&targetVersions, "target-versions", "t", targetVersions, "A map of targetVersions to use. This flag supersedes all defaults in version files.")
 	rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "normal", "The output format to use. (normal|wide|custom|json|yaml|markdown|csv)")
@@ -270,6 +272,7 @@ var rootCmd = &cobra.Command{
 			IgnoreDeprecations: ignoreDeprecations,
 			IgnoreRemovals:     ignoreRemovals,
 			OnlyShowRemoved:    onlyShowRemoved,
+			NoHeaders:          noHeaders,
 			DeprecatedVersions: deprecatedVersionList,
 			Components:         componentList,
 		}
diff --git a/docs/advanced.md b/docs/advanced.md
index e5ddcfb..b43cae2 100644
--- a/docs/advanced.md
+++ b/docs/advanced.md
@@ -9,22 +9,23 @@ Pluto has a wide variety of options that can be used to customize behavior and o
 
 ## Display Options
 
-In addition to the standard output, Pluto can output yaml, json, wide, custom columns or markdown.
+In addition to the standard output, Pluto can output in the following modes: Wide, YAML, JSON, CSV or Markdown.
+
+`--no-headers` option hides headers in the outputs for Text, CSV and Markdown output.
 
 ### Wide
 
 The wide output provides more information about when an apiVersion was removed or deprecated.
 
-```
+```shell
 $ pluto detect-helm -owide
-└─ pluto detect-helm -owide
 NAME                                         NAMESPACE               KIND                           VERSION                                REPLACEMENT                       DEPRECATED   DEPRECATED IN   REMOVED   REMOVED IN
 cert-manager/cert-manager-webhook            cert-manager            MutatingWebhookConfiguration   admissionregistration.k8s.io/v1beta1   admissionregistration.k8s.io/v1   true         v1.16.0         false     v1.19.0
 ```
 
 ### JSON
 
-```
+```shell
 $ pluto detect-helm -ojson | jq .
 {
   "items": [
@@ -74,17 +75,17 @@ target-versions:
 ```
 
 ### Custom columns
-```
+
+```shell
 $ pluto detect-helm -ocustom --columns NAMESPACE,NAME
-└─ pluto detect-helm -ocustom --columns NAMESPACE,NAME
 NAME                                         NAMESPACE
 cert-manager/cert-manager-webhook            cert-manager
 ```
 
 ### Markdown
 
-```
-pluto detect-files -o markdown
+```shell
+$ pluto detect-files -o markdown
 |   NAME    |   NAMESPACE    |    KIND    |      VERSION       | REPLACEMENT | DEPRECATED | DEPRECATED IN | REMOVED | REMOVED IN |
 |-----------|----------------|------------|--------------------|-------------|------------|---------------|---------|------------|
 | utilities | <UNKNOWN>      | Deployment | extensions/v1beta1 | apps/v1     | true       | v1.9.0        | true    | v1.16.0    |
@@ -92,8 +93,8 @@ pluto detect-files -o markdown
 | utilities | yaml-namespace | Deployment | extensions/v1beta1 | apps/v1     | true       | v1.9.0        | true    | v1.16.0    |
 ```
 
-```
-pluto detect-files -o markdown --columns NAMESPACE,NAME,DEPRECATED IN,DEPRECATED,REPLACEMENT,VERSION,KIND,COMPONENT,FILEPATH
+```shell
+$ pluto detect-files -o markdown --columns NAMESPACE,NAME,DEPRECATED IN,DEPRECATED,REPLACEMENT,VERSION,KIND,COMPONENT,FILEPATH
 |     NAME      |    NAMESPACE    |    KIND    |      VERSION       | REPLACEMENT | DEPRECATED | DEPRECATED IN | COMPONENT |   FILEPATH   |
 |---------------|-----------------|------------|--------------------|-------------|------------|---------------|-----------|--------------|
 | some name one | pluto-namespace | Deployment | extensions/v1beta1 | apps/v1     | true       | v1.9.0        | foo       | path-to-file |
@@ -102,17 +103,20 @@ pluto detect-files -o markdown --columns NAMESPACE,NAME,DEPRECATED IN,DEPRECATED
 
 ### CSV
 
-```
-pluto detect-helm -o csv
+```shell
+$ pluto detect-helm -o csv
 NAME,NAMESPACE,KIND,VERSION,REPLACEMENT,DEPRECATED,DEPRECATED IN,REMOVED,REMOVED IN
-some name one,pluto-namespace,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,true,v1.16.0
-some name two,<UNKNOWN>,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,true,v1.16.0
+deploy1,pluto-namespace,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,true,v1.16.0
+deploy1,other-namespace,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,true,v1.16.0
+```
 
-pluto detect-helm -o csv --columns "NAMESPACE,NAME,DEPRECATED IN,DEPRECATED,REPLACEMENT,VERSION,KIND,COMPONENT,FILEPATH"
-NAME,NAMESPACE,KIND,VERSION,REPLACEMENT,DEPRECATED,DEPRECATED IN,COMPONENT,FILEPATH
-some name one,pluto-namespace,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,foo,path-to-file
-some name two,<UNKNOWN>,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,foo,<UNKNOWN>
+```shell
+$ pluto detect-helm -o csv --columns "KIND,NAMESPACE,NAME,VERSION,REPLACEMENT"
+KIND,NAMESPACE,NAME,VERSION,REPLACEMENT
+Deployment,pluto-namespace,deploy1,extensions/v1beta1,apps/v1
+Deployment,other-namespace,deploy1,extensions/v1beta1,apps/v1
 ```
+
 ## CI Pipelines
 
 Pluto has specific exit codes that is uses to indicate certain results:
@@ -123,7 +127,7 @@ Pluto has specific exit codes that is uses to indicate certain results:
 
 If you wish to bypass the generation of exit codes 2 and 3, you may do so with two different flags:
 
-```
+```shell
 --ignore-deprecations              Ignore the default behavior to exit 2 if deprecated apiVersions are found.
 --ignore-removals                  Ignore the default behavior to exit 3 if removed apiVersions are found.
 ```
@@ -138,7 +142,7 @@ You can target the version you are concerned with by using the `--target-version
 
 For example:
 
-```
+```shell
 $ pluto detect-helm --target-versions k8s=v1.15.0
 No output to display
 
@@ -162,7 +166,7 @@ If you want to check additional apiVersions and/or types, you can pass an additi
 
 The file should look something like this:
 
-```
+```yaml
 target-versions:
   custom: v1.0.0
 deprecated-versions:
@@ -176,11 +180,12 @@ deprecated-versions:
 
 You can test that it's working by using `list-versions`:
 
-```
+```shell
 $ pluto list-versions -f new.yaml
 KIND                           NAME                                   DEPRECATED IN   REMOVED IN   REPLACEMENT   COMPONENT
 AnotherCRD                     someother/v1beta1                      v1.9.0          v1.16.0      apps/v1       custom
 ```
+
 _NOTE: This output is truncated to show only the additional version. Normally this will include the defaults as well_
 
 The `target-versions` field in this custom file will set the default target version for that component. You can still override this with `--target-versions custom=vX.X.X` when you run Pluto.
@@ -213,3 +218,4 @@ All environment variables are prefixed with `PLUTO` and use `_` instead of `-`.
 | --output              | PLUTO_OUTPUT              |
 | --columns             | PLUTO_COLUMNS             |
 | --components          | PLUTO_COMPONENTS          |
+| --no-headers          | PLUTO_NO_HEADERS          |
diff --git a/pkg/api/output.go b/pkg/api/output.go
index 05c0a5f..e4502e0 100644
--- a/pkg/api/output.go
+++ b/pkg/api/output.go
@@ -55,6 +55,7 @@ type Instance struct {
 	IgnoreDeprecations bool              `json:"-" yaml:"-"`
 	IgnoreRemovals     bool              `json:"-" yaml:"-"`
 	OnlyShowRemoved    bool              `json:"-" yaml:"-"`
+	NoHeaders          bool              `json:"-" yaml:"-"`
 	OutputFormat       string            `json:"-" yaml:"-"`
 	TargetVersions     map[string]string `json:"target-versions,omitempty" yaml:"target-versions,omitempty"`
 	DeprecatedVersions []Version         `json:"-" yaml:"-"`
@@ -188,15 +189,17 @@ func (instance *Instance) tabOut(columns columnList) *tabwriter.Writer {
 	}
 	sort.Ints(columnIndexes)
 
-	var headers string
-	for _, k := range columnIndexes {
-		if k == 0 {
-			headers = fmt.Sprintf("%s\t", columns[k].header())
-		} else {
-			headers = fmt.Sprintf("%s %s\t", headers, columns[k].header())
+	if !instance.NoHeaders {
+		var headers string
+		for _, k := range columnIndexes {
+			if k == 0 {
+				headers = fmt.Sprintf("%s\t", columns[k].header())
+			} else {
+				headers = fmt.Sprintf("%s %s\t", headers, columns[k].header())
+			}
 		}
+		_, _ = fmt.Fprintln(w, headers)
 	}
-	_, _ = fmt.Fprintln(w, headers)
 
 	var rows string
 	for _, o := range instance.Outputs {
@@ -230,12 +233,14 @@ func (instance *Instance) markdownOut(columns columnList) *tablewriter.Table {
 	}
 	sort.Ints(columnIndexes)
 
-	var headers []string
-	for _, k := range columnIndexes {
-		headers = append(headers, columns[k].header())
-	}
+	if !instance.NoHeaders {
+		var headers []string
+		for _, k := range columnIndexes {
+			headers = append(headers, columns[k].header())
+		}
 
-	table.SetHeader(headers)
+		table.SetHeader(headers)
+	}
 
 	for _, o := range instance.Outputs {
 		var row []string
@@ -263,12 +268,14 @@ func (instance *Instance) csvOut(columns columnList) (*csv.Writer, error) {
 
 	var csvData [][]string
 
-	var headers []string
-	for _, k := range columnIndexes {
-		headers = append(headers, columns[k].header())
-	}
+	if !instance.NoHeaders {
+		var headers []string
+		for _, k := range columnIndexes {
+			headers = append(headers, columns[k].header())
+		}
 
-	csvData = append(csvData, headers)
+		csvData = append(csvData, headers)
+	}
 
 	for _, o := range instance.Outputs {
 		var row []string
diff --git a/pkg/api/versions.go b/pkg/api/versions.go
index f6d2675..453df0d 100644
--- a/pkg/api/versions.go
+++ b/pkg/api/versions.go
@@ -244,12 +244,7 @@ func (v *Version) isRemovedIn(targetVersions map[string]string) bool {
 // in a specific format
 func (instance *Instance) PrintVersionList(outputFormat string) error {
 	switch outputFormat {
-	case "normal":
-		err := instance.printVersionsTabular()
-		if err != nil {
-			return err
-		}
-	case "wide":
+	case "normal", "wide":
 		err := instance.printVersionsTabular()
 		if err != nil {
 			return err
@@ -275,7 +270,7 @@ func (instance *Instance) PrintVersionList(outputFormat string) error {
 		}
 		fmt.Println(string(data))
 	default:
-		errText := "The output format must one of (normal|json|yaml)"
+		errText := "The output format must one of (normal|wide|json|yaml)"
 		fmt.Println(errText)
 		return fmt.Errorf(errText)
 	}
@@ -286,7 +281,9 @@ func (instance *Instance) printVersionsTabular() error {
 	w := new(tabwriter.Writer)
 	w.Init(os.Stdout, 0, 15, 2, padChar, 0)
 
-	_, _ = fmt.Fprintln(w, "KIND\t NAME\t DEPRECATED IN\t REMOVED IN\t REPLACEMENT\t COMPONENT\t")
+	if !instance.NoHeaders {
+		fmt.Fprintln(w, "KIND\t NAME\t DEPRECATED IN\t REMOVED IN\t REPLACEMENT\t COMPONENT\t")
+	}
 
 	for _, version := range instance.DeprecatedVersions {
 		deprecatedIn := version.DeprecatedIn


Test patch:
diff --git a/pkg/api/output_test.go b/pkg/api/output_test.go
index f7a729a..00d1932 100644
--- a/pkg/api/output_test.go
+++ b/pkg/api/output_test.go
@@ -316,6 +316,26 @@ func ExampleInstance_DisplayOutput_csv_customcolumns() {
 	// <UNKNOWN>,some name two,v1.9.0,true,apps/v1,extensions/v1beta1,Deployment,foo,<UNKNOWN>
 }
 
+func ExampleInstance_DisplayOutput_csv_noHeaders() {
+	instance := &Instance{
+		TargetVersions: map[string]string{
+			"foo": "v1.16.0",
+		},
+		Outputs: []*Output{
+			testOutput1,
+			testOutput2,
+		},
+		Components:   []string{"foo"},
+		OutputFormat: "csv",
+		NoHeaders:    true,
+	}
+	_ = instance.DisplayOutput()
+
+	// Output:
+	// some name one,pluto-namespace,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,true,v1.16.0
+	// some name two,<UNKNOWN>,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,true,v1.16.0
+}
+
 func ExampleInstance_DisplayOutput_noOutput() {
 	instance := &Instance{
 		TargetVersions: map[string]string{
diff --git a/pkg/api/versions_test.go b/pkg/api/versions_test.go
index a46d02c..509f98e 100644
--- a/pkg/api/versions_test.go
+++ b/pkg/api/versions_test.go
@@ -440,6 +440,21 @@ func ExampleInstance_printVersionsTabular() {
 	// testkind---- testname------------ n/a------------ n/a--------- n/a---------- custom-----
 }
 
+func ExampleInstance_printVersionsTabular_noHeaders() {
+	instance := Instance{
+		DeprecatedVersions: []Version{
+			testVersionDeployment,
+			{Kind: "testkind", Name: "testname", DeprecatedIn: "", RemovedIn: "", Component: "custom"},
+		},
+		NoHeaders: true,
+	}
+	_ = instance.printVersionsTabular()
+
+	// Output:
+	// Deployment-- extensions/v1beta1-- v1.9.0-- v1.16.0-- apps/v1-- k8s-----
+	// testkind---- testname------------ n/a----- n/a------ n/a------ custom--
+}
+
 func ExampleInstance_PrintVersionList_json() {
 	instance := Instance{
 		DeprecatedVersions: []Version{testVersionDeployment},
@@ -495,7 +510,7 @@ func ExampleInstance_PrintVersionList_badformat() {
 	_ = instance.PrintVersionList("foo")
 
 	// Output:
-	// The output format must one of (normal|json|yaml)
+	// The output format must one of (normal|wide|json|yaml)
 }
 
 func Test_isDuplicate(t *testing.T) {