Repository: FairwindsOps/pluto (go)

Issue:
Set non-zero exit code if there are deprecated resources
Would be great to fail (e.g. in CI/CD) if there are deprecated resources.

Could be done only in the presence of a flag (e.g. `--set-exit-code`)

Pull request description:
Adding exit codes
Fixes #25 

TODO:

- [x] Fix e2e testing once #29  is merged.

Patch:
diff --git a/cmd/root.go b/cmd/root.go
index 402bbee..f6c9a25 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -36,11 +36,13 @@ var (
 	outputFormat      string
 	showNonDeprecated bool
 	helmVersion       string
+	ignoreErrors      bool
 )
 
 func init() {
 	rootCmd.AddCommand(detectFilesCmd)
 	rootCmd.PersistentFlags().BoolVarP(&showNonDeprecated, "show-all", "A", false, "If enabled, will show files that have non-deprecated apiVersion. Only applies to tabular output.")
+	rootCmd.PersistentFlags().BoolVar(&ignoreErrors, "ignore-errors", false, "Default behavior is to exit non-zero if deprecations are found. This will force a return of zero.")
 
 	detectFilesCmd.PersistentFlags().StringVarP(&directory, "directory", "d", "", "The directory to scan. If blank, defaults to current workding dir.")
 	detectFilesCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "tabular", "The output format to use. (tabular|json|yaml)")
@@ -96,6 +98,9 @@ var detectFilesCmd = &cobra.Command{
 			fmt.Println("Error Parsing Output:", err)
 			os.Exit(1)
 		}
+		retCode := api.GetReturnCode(dir.Outputs, ignoreErrors)
+		klog.V(5).Infof("retCode: %d", retCode)
+		os.Exit(retCode)
 	},
 }
 
@@ -115,6 +120,9 @@ var detectHelmCmd = &cobra.Command{
 			fmt.Println("Error Parsing Output:", err)
 			os.Exit(1)
 		}
+		retCode := api.GetReturnCode(h.Outputs, ignoreErrors)
+		klog.V(5).Infof("retCode: %d", retCode)
+		os.Exit(retCode)
 	},
 }
 
@@ -151,7 +159,9 @@ var detectCmd = &cobra.Command{
 				fmt.Println("Error parsing output:", err)
 				os.Exit(1)
 			}
-			os.Exit(0)
+			retCode := api.GetReturnCode(output, ignoreErrors)
+			klog.V(5).Infof("retCode: %d", retCode)
+			os.Exit(retCode)
 		}
 		output, err := finder.CheckForAPIVersion(args[0])
 		if err != nil {
@@ -163,6 +173,9 @@ var detectCmd = &cobra.Command{
 			fmt.Println("Error parsing output:", err)
 			os.Exit(1)
 		}
+		retCode := api.GetReturnCode(output, ignoreErrors)
+		klog.V(5).Infof("retCode: %d", retCode)
+		os.Exit(retCode)
 	},
 }
 
diff --git a/e2e/tests/assets/deployment-extensions-v1beta1.yaml b/e2e/tests/assets/deprecated/deployment-extensions-v1beta1.yaml
similarity index 100%
rename from e2e/tests/assets/deployment-extensions-v1beta1.yaml
rename to e2e/tests/assets/deprecated/deployment-extensions-v1beta1.yaml
diff --git a/e2e/tests/assets/deployment-apps-v1.yaml b/e2e/tests/assets/non-deprecated/deployment-apps-v1.yaml
similarity index 100%
rename from e2e/tests/assets/deployment-apps-v1.yaml
rename to e2e/tests/assets/non-deprecated/deployment-apps-v1.yaml
diff --git a/pkg/api/output.go b/pkg/api/output.go
index 7bd5084..1e4eeaa 100644
--- a/pkg/api/output.go
+++ b/pkg/api/output.go
@@ -84,3 +84,17 @@ func tabOut(outputs []*Output, showNonDeprecated bool) (*tabwriter.Writer, error
 	}
 	return w, nil
 }
+
+// GetReturnCode checks for deprecated versions and returns a code.
+// takes a boolean to ignore any errors.
+func GetReturnCode(outputs []*Output, ignoreErrors bool) int {
+	if ignoreErrors {
+		return 0
+	}
+	for _, output := range outputs {
+		if output.APIVersion.Deprecated {
+			return 1
+		}
+	}
+	return 0
+}


Test patch:
diff --git a/e2e/tests/00_static_files.yaml b/e2e/tests/00_static_files.yaml
index 92f0595..1e813b3 100644
--- a/e2e/tests/00_static_files.yaml
+++ b/e2e/tests/00_static_files.yaml
@@ -5,7 +5,7 @@ testcases:
   steps:
   - script: pluto detect-files -d assets/ -A
     assertions:
-    - result.code ShouldEqual 0
+    - result.code ShouldEqual 1
     - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
     - result.systemout ShouldContainSubstring "Deployment   apps/v1              false        utilities"
     - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         utilities"
@@ -14,7 +14,7 @@ testcases:
   steps:
   - script: pluto detect-files -d assets/
     assertions:
-    - result.code ShouldEqual 0
+    - result.code ShouldEqual 1
     - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
     - result.systemout ShouldNotContainSubstring "Deployment   apps/v1              false        utilities"
     - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         utilities"
@@ -23,7 +23,7 @@ testcases:
   steps:
   - script: helm template assets/helm3chart | pluto detect -A -
     assertions:
-    - result.code ShouldEqual 0
+    - result.code ShouldEqual 1
     - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
     - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         RELEASE-NAME-helm3chart"
     - result.systemout ShouldContainSubstring "Deployment   apps/v1              false        RELEASE-NAME-helm3chart"
@@ -32,7 +32,22 @@ testcases:
   steps:
   - script: helm template assets/helm3chart | pluto detect -
     assertions:
-    - result.code ShouldEqual 0
+    - result.code ShouldEqual 1
     - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
     - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         RELEASE-NAME-helm3chart"
     - result.systemout ShouldNotContainSubstring "Deployment   apps/v1              false        RELEASE-NAME-helm3chart"
+
+- name: static files no deprecated
+  steps:
+  - script: pluto detect-files -d assets/non-deprecated
+    assertions:
+    - result.code ShouldEqual 0
+    - result.systemout ShouldContainSubstring "APIVersions were found, but none were deprecated. Try --show-all."
+
+- name: static files no deprecated
+  steps:
+  - script: pluto detect-files -d assets/non-deprecated -A
+    assertions:
+    - result.code ShouldEqual 0
+    - result.systemout ShouldContainSubstring "KIND         VERSION   DEPRECATED   RESOURCE NAME"
+    - result.systemout ShouldContainSubstring "Deployment   apps/v1   false        utilities"
diff --git a/e2e/tests/01_helm-detect-3.yaml b/e2e/tests/01_helm-detect-3.yaml
index 39aa86e..b4344ee 100644
--- a/e2e/tests/01_helm-detect-3.yaml
+++ b/e2e/tests/01_helm-detect-3.yaml
@@ -11,7 +11,7 @@ testcases:
   steps:
   - script: pluto detect-helm --helm-version=3 -A
     assertions:
-    - result.code ShouldEqual 0
+    - result.code ShouldEqual 1
     - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
     - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         test-helm3chart-v1beta1"
     - result.systemout ShouldContainSubstring "Deployment   apps/v1              false        test-helm3chart"
@@ -19,10 +19,28 @@ testcases:
 - name: helm detect
   steps:
   - script: pluto detect-helm --helm-version=3
+    assertions:
+    - result.code ShouldEqual 1
+    - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
+    - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         test-helm3chart-v1beta1"
+
+- name: helm detect ignore errors
+  steps:
+  - script: pluto detect-helm --helm-version=3 --ignore-errors
+    assertions:
+    - result.code ShouldEqual 0
+    - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
+    - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         test-helm3chart-v1beta1"
+
+- name: helm detect show all ignore errors
+  steps:
+  - script: pluto detect-helm --helm-version=3 -A --ignore-errors
     assertions:
     - result.code ShouldEqual 0
     - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
     - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         test-helm3chart-v1beta1"
+    - result.systemout ShouldContainSubstring "Deployment   apps/v1              false        test-helm3chart"
+
 - name: cleanup
   steps:
   - script: helm delete test
diff --git a/e2e/tests/02_helm-detect-2.yaml b/e2e/tests/02_helm-detect-2.yaml
index 30ef876..1f13e14 100644
--- a/e2e/tests/02_helm-detect-2.yaml
+++ b/e2e/tests/02_helm-detect-2.yaml
@@ -13,7 +13,7 @@ testcases:
   steps:
   - script: pluto detect-helm --helm-version=2 -A
     assertions:
-    - result.code ShouldEqual 0
+    - result.code ShouldEqual 1
     - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
     - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         invincible-zebu-metrics-server"
     - result.systemout ShouldContainSubstring "Deployment   apps/v1              false        lunging-bat-metrics-server"
@@ -22,7 +22,7 @@ testcases:
   steps:
   - script: pluto detect-helm --helm-version=2
     assertions:
-    - result.code ShouldEqual 0
+    - result.code ShouldEqual 1
     - result.systemout ShouldContainSubstring "KIND         VERSION              DEPRECATED   RESOURCE NAME"
     - result.systemout ShouldContainSubstring "Deployment   extensions/v1beta1   true         invincible-zebu-metrics-server"
 - name: cleanup
diff --git a/pkg/api/output_test.go b/pkg/api/output_test.go
index 4e8ef12..ba4323f 100644
--- a/pkg/api/output_test.go
+++ b/pkg/api/output_test.go
@@ -14,6 +14,12 @@
 
 package api
 
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
 var testOutput1 = &Output{
 	Name: "some name one",
 	APIVersion: &Version{
@@ -81,3 +87,58 @@ func ExampleDisplayOutput_zeroLength() {
 
 	// Output: There were no apiVersions found that match our records.
 }
+
+func TestGetReturnCode(t *testing.T) {
+	type args struct {
+		outputs      []*Output
+		ignoreErrors bool
+	}
+	tests := []struct {
+		name string
+		args args
+		want int
+	}{
+		{
+			name: "empty return zero",
+			args: args{
+				outputs:      []*Output{},
+				ignoreErrors: false,
+			},
+			want: 0,
+		},
+		{
+			name: "version is deprecated return one",
+			args: args{
+				outputs: []*Output{
+					&Output{
+						APIVersion: &Version{
+							Deprecated: true,
+						},
+					},
+				},
+				ignoreErrors: false,
+			},
+			want: 1,
+		},
+		{
+			name: "version is deprecated ignore errors",
+			args: args{
+				outputs: []*Output{
+					&Output{
+						APIVersion: &Version{
+							Deprecated: true,
+						},
+					},
+				},
+				ignoreErrors: true,
+			},
+			want: 0,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := GetReturnCode(tt.args.outputs, tt.args.ignoreErrors)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}