Repository: datreeio/datree
Pull request #836 (2022-11-14 09:42:43)

feat:add quiet mode to CLI
This address #731

1. Adds a `quiet` flag to the CLI
2. Used to stop printing skipped rules
3. Summery will keep displaying the skip rule count
4. Only affects non-formatted output

romanlab: @danielkman @eyarz I think it should be `quiet` not `quite`
danielkman: Thought that looked strange :)
Fixed

Patch:
diff --git a/pkg/evaluation/printer.go b/pkg/evaluation/printer.go
index 1368b1f..988cf22 100644
--- a/pkg/evaluation/printer.go
+++ b/pkg/evaluation/printer.go
@@ -22,7 +22,7 @@ import (
 var out = color.Output
 
 type Printer interface {
-	GetWarningsText(warnings []printer.Warning) string
+	GetWarningsText(warnings []printer.Warning, quiet bool) string
 	GetSummaryTableText(summary printer.Summary) string
 	GetEvaluationSummaryText(summary printer.EvaluationSummary, k8sVersion string) string
 }
@@ -42,6 +42,7 @@ type PrintResultsData struct {
 	K8sValidationWarnings validation.K8sValidationWarningPerValidFile
 	CliVersion            string
 	IsCI                  bool
+	Quiet                 bool
 }
 
 type textOutputData struct {
@@ -55,6 +56,7 @@ type textOutputData struct {
 	Verbose               bool
 	policyName            string
 	k8sValidationWarnings validation.K8sValidationWarningPerValidFile
+	quiet                 bool
 }
 
 func SaveLastResultToJson(resultsData *PrintResultsData) {
@@ -152,6 +154,7 @@ func GetResultsText(resultsData *PrintResultsData) (string, error) {
 			policyName:            resultsData.PolicyName,
 			Verbose:               resultsData.Verbose,
 			k8sValidationWarnings: resultsData.K8sValidationWarnings,
+			quiet:                 resultsData.Quiet,
 		})
 	}
 }
@@ -261,7 +264,7 @@ func getTextOutput(outputData textOutputData) (string, error) {
 		return "", err
 	}
 
-	warningsText := outputData.printer.GetWarningsText(warnings)
+	warningsText := outputData.printer.GetWarningsText(warnings, outputData.quiet)
 	sb.WriteString(warningsText)
 
 	summary := parseEvaluationResultsToSummary(outputData.results, outputData.evaluationSummary, outputData.url, outputData.policyName)
diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go
index dadb965..634b828 100644
--- a/pkg/printer/printer.go
+++ b/pkg/printer/printer.go
@@ -142,7 +142,7 @@ func (p *Printer) getYamlSchemaResultsText(errorsResult []jsonschema.Detailed, e
 	return sb.String()
 }
 
-func (p *Printer) GetWarningsText(warnings []Warning) string {
+func (p *Printer) GetWarningsText(warnings []Warning, quiet bool) string {
 	var sb strings.Builder
 	for _, warning := range warnings {
 		sb.WriteString(p.GetTitleText(warning.Title))
@@ -164,27 +164,30 @@ func (p *Printer) GetWarningsText(warnings []Warning) string {
 			sb.WriteString(p.GetTextInColor("[X] Policy check\n", p.Theme.Colors.Highlight))
 			sb.WriteString("\n")
 
-			if len(warning.SkippedRules) > 0 {
-				sb.WriteString(fmt.Sprintf("%v", p.Theme.Colors.CyanBold.Sprintf("SKIPPED")+"\n\n"))
-			}
+			if !quiet {
 
-			for _, skippedRule := range warning.SkippedRules {
-				ruleName := p.Theme.Colors.CyanBold.Sprint(skippedRule.Name)
+				if len(warning.SkippedRules) > 0 {
+					sb.WriteString(fmt.Sprintf("%v", p.Theme.Colors.CyanBold.Sprintf("SKIPPED")+"\n\n"))
+				}
 
-				sb.WriteString(fmt.Sprintf("%v %v\n", p.Theme.Emoji.Skip, ruleName))
+				for _, skippedRule := range warning.SkippedRules {
+					ruleName := p.Theme.Colors.CyanBold.Sprint(skippedRule.Name)
 
-				if skippedRule.DocumentationUrl != "" {
-					howToFix := p.Theme.Colors.Cyan.Sprint(skippedRule.DocumentationUrl)
-					sb.WriteString(fmt.Sprintf("    How to fix: %v\n", howToFix))
-				}
+					sb.WriteString(fmt.Sprintf("%v %v\n", p.Theme.Emoji.Skip, ruleName))
 
-				for _, occurrenceDetails := range skippedRule.OccurrencesDetails {
-					sb.WriteString(fmt.Sprintf("    - metadata.name: %v (kind: %v)\n", p.getStringOrNotAvailableText(occurrenceDetails.MetadataName), p.getStringOrNotAvailableText(occurrenceDetails.Kind)))
-					m := p.Theme.Colors.Highlight.Sprint(occurrenceDetails.SkipMessage)
-					sb.WriteString(fmt.Sprintf("%v %v\n", p.Theme.Emoji.Suggestion, m))
-				}
+					if skippedRule.DocumentationUrl != "" {
+						howToFix := p.Theme.Colors.Cyan.Sprint(skippedRule.DocumentationUrl)
+						sb.WriteString(fmt.Sprintf("    How to fix: %v\n", howToFix))
+					}
 
-				sb.WriteString("\n")
+					for _, occurrenceDetails := range skippedRule.OccurrencesDetails {
+						sb.WriteString(fmt.Sprintf("    - metadata.name: %v (kind: %v)\n", p.getStringOrNotAvailableText(occurrenceDetails.MetadataName), p.getStringOrNotAvailableText(occurrenceDetails.Kind)))
+						m := p.Theme.Colors.Highlight.Sprint(occurrenceDetails.SkipMessage)
+						sb.WriteString(fmt.Sprintf("%v %v\n", p.Theme.Emoji.Suggestion, m))
+					}
+
+					sb.WriteString("\n")
+				}
 			}
 
 			for _, failedRule := range warning.FailedRules {
diff --git a/scripts/deploy_release_candidate.sh b/scripts/deploy_release_candidate.sh
index 7aebf16..63dca36 100644
--- a/scripts/deploy_release_candidate.sh
+++ b/scripts/deploy_release_candidate.sh
@@ -2,7 +2,7 @@
 set -ex
 
 MAJOR_VERSION=1
-MINOR_VERSION=7
+MINOR_VERSION=8
 
 git fetch --prune --unshallow --tags # needed for getting list of tags
 latestRcTag=$(git tag --sort=-version:refname | grep -E "^${MAJOR_VERSION}\.${MINOR_VERSION}.[0-9]+-rc" | head -n 1 | grep --only-matching "^${MAJOR_VERSION}\.${MINOR_VERSION}.[0-9]\+" || true)


Test patch:
diff --git a/cmd/kustomize/main_test.go b/cmd/kustomize/main_test.go
index 0a8cff3..a48b22a 100644
--- a/cmd/kustomize/main_test.go
+++ b/cmd/kustomize/main_test.go
@@ -144,7 +144,7 @@ type PrinterMock struct {
 	mock.Mock
 }
 
-func (p *PrinterMock) GetWarningsText(warnings []printer.Warning) string {
+func (p *PrinterMock) GetWarningsText(warnings []printer.Warning, quiet bool) string {
 	p.Called(warnings)
 	return ""
 }
diff --git a/cmd/test/main.go b/cmd/test/main.go
index af8a61f..2bd9d84 100644
--- a/cmd/test/main.go
+++ b/cmd/test/main.go
@@ -62,6 +62,7 @@ type TestCommandFlags struct {
 	SkipValidation       string
 	SaveRendered         bool
 	PermissiveSchema     bool
+	Quiet                bool
 }
 
 // TestCommandFlags constructor
@@ -77,6 +78,7 @@ func NewTestCommandFlags() *TestCommandFlags {
 		SkipValidation:       "",
 		SaveRendered:         false,
 		PermissiveSchema:     false,
+		Quiet:                false,
 	}
 }
 
@@ -115,7 +117,7 @@ func (flags *TestCommandFlags) Validate() error {
 }
 
 type EvaluationPrinter interface {
-	GetWarningsText(warnings []printer.Warning) string
+	GetWarningsText(warnings []printer.Warning, quiet bool) string
 	GetSummaryTableText(summary printer.Summary) string
 	PrintMessage(messageText string, messageColor string)
 	PrintPromptMessage(promptMessage string)
@@ -154,6 +156,7 @@ type TestCommandData struct {
 	SkipSchemaValidation  bool
 	SaveRendered          bool
 	PermissiveSchema      bool
+	Quiet                 bool
 }
 
 type TestCommandContext struct {
@@ -270,6 +273,7 @@ func (flags *TestCommandFlags) AddFlags(cmd *cobra.Command) {
 	cmd.Flags().BoolVarP(&flags.IgnoreMissingSchemas, "ignore-missing-schemas", "", false, "Ignore missing schemas when executing schema validation step")
 	cmd.Flags().BoolVarP(&flags.SaveRendered, "save-rendered", "", false, "Don't delete rendered files after the policy check (e.g. helm, kustomize)")
 	cmd.Flags().BoolVarP(&flags.PermissiveSchema, "permissive-schema", "", false, "Perform non-strict schema validation (i.e. allow additional properties)")
+	cmd.Flags().BoolVarP(&flags.Quiet, "quiet", "", false, "Don't print skipped rules messages")
 }
 
 func GenerateTestCommandData(testCommandFlags *TestCommandFlags, localConfigContent *localConfig.LocalConfig, evaluationPrerunDataResp *cliClient.EvaluationPrerunDataResponse) (*TestCommandData, error) {
@@ -326,6 +330,7 @@ func GenerateTestCommandData(testCommandFlags *TestCommandFlags, localConfigCont
 		SkipSchemaValidation:  testCommandFlags.SkipValidation == "schema",
 		SaveRendered:          testCommandFlags.SaveRendered,
 		PermissiveSchema:      testCommandFlags.PermissiveSchema,
+		Quiet:                 testCommandFlags.Quiet,
 	}
 
 	return testCommandOptions, nil
@@ -440,6 +445,7 @@ func test(ctx *TestCommandContext, paths []string, testCommandData *TestCommandD
 		K8sValidationWarnings: validationManager.k8sValidationWarningPerValidFile,
 		CliVersion:            ctx.CliVersion,
 		IsCI:                  ctx.CiContext.IsCI,
+		Quiet:                 testCommandData.Quiet,
 	})
 
 	if evaluationResultData.PromptMessage != "" {
diff --git a/cmd/test/main_test.go b/cmd/test/main_test.go
index 239d047..9e7c954 100644
--- a/cmd/test/main_test.go
+++ b/cmd/test/main_test.go
@@ -144,7 +144,7 @@ type PrinterMock struct {
 	mock.Mock
 }
 
-func (p *PrinterMock) GetWarningsText(warnings []printer.Warning) string {
+func (p *PrinterMock) GetWarningsText(warnings []printer.Warning, quiet bool) string {
 	p.Called(warnings)
 	return ""
 }
diff --git a/pkg/evaluation/printer_test.go b/pkg/evaluation/printer_test.go
index c25a6f8..81d9a08 100644
--- a/pkg/evaluation/printer_test.go
+++ b/pkg/evaluation/printer_test.go
@@ -20,8 +20,8 @@ type mockPrinter struct {
 	mock.Mock
 }
 
-func (m *mockPrinter) GetWarningsText(warnings []printer.Warning) string {
-	m.Called(warnings)
+func (m *mockPrinter) GetWarningsText(warnings []printer.Warning, quiet bool) string {
+	m.Called(warnings, quiet)
 	return ""
 }
 
@@ -70,7 +70,7 @@ func TestPrintResults(t *testing.T) {
 	}
 	for _, tt := range tests {
 		mockedPrinter := &mockPrinter{}
-		mockedPrinter.On("GetWarningsText", mock.Anything, mock.Anything, mock.Anything)
+		mockedPrinter.On("GetWarningsText", mock.Anything, mock.Anything)
 		mockedPrinter.On("GetSummaryTableText", mock.Anything)
 		mockedPrinter.On("GetEvaluationSummaryText", mock.Anything, mock.Anything)
 
@@ -87,6 +87,7 @@ func TestPrintResults(t *testing.T) {
 				K8sVersion:            "1.18.0",
 				Verbose:               false,
 				PolicyName:            "Default",
+				Quiet:                 false,
 				K8sValidationWarnings: validation.K8sValidationWarningPerValidFile{},
 			})
 
@@ -101,7 +102,7 @@ func TestPrintResults(t *testing.T) {
 			} else {
 				pwd, _ := os.Getwd()
 				warnings, _ := parseToPrinterWarnings(tt.args.results.EvaluationResults, tt.args.invalidYamlFiles, tt.args.invalidK8sFiles, pwd, "1.18.0", validation.K8sValidationWarningPerValidFile{}, false)
-				mockedPrinter.AssertCalled(t, "GetWarningsText", warnings)
+				mockedPrinter.AssertCalled(t, "GetWarningsText", warnings, false)
 			}
 		})
 	}
diff --git a/pkg/printer/printer_test.go b/pkg/printer/printer_test.go
index 8e9c5fd..8dc6ca1 100644
--- a/pkg/printer/printer_test.go
+++ b/pkg/printer/printer_test.go
@@ -47,13 +47,36 @@ func TestGetWarningsText(t *testing.T) {
 			ExtraMessages: []ExtraMessage{{Text: "Are you trying to test a raw helm file? To run Datree with Helm - check out the helm plugin README:\nhttps://github.com/datreeio/helm-datree",
 				Color: "cyan"}},
 		},
+		{
+			Title: GetFileNameText("/datree/datree/internal/fixtures/kube/skipRule/k8s-demo-skip-two.yaml\n"),
+			SkippedRules: []FailedRule{
+				{
+					Name:        "Ensure workload has valid label values",
+					Occurrences: 1,
+					OccurrencesDetails: []OccurrenceDetails{{
+						MetadataName: "rss-site",
+						Kind:         "Deployment",
+						SkipMessage:  "skip first k8s-demo rule",
+					}},
+				},
+				{
+					Name:        "Ensure each container has a configured liveness probe",
+					Occurrences: 1,
+					OccurrencesDetails: []OccurrenceDetails{{
+						MetadataName: "rss-site",
+						Kind:         "Deployment",
+						SkipMessage:  "skip second k8s-demo rule",
+					}},
+				},
+			},
+		},
 	}
 
 	t.Run("Test GetWarningsText", func(t *testing.T) {
 
 		out = new(bytes.Buffer)
 
-		got := printer.GetWarningsText(warnings)
+		got := printer.GetWarningsText(warnings, false)
 
 		expected := []byte(
 			`>>  File: ~/.datree/k8-demo.yaml
@@ -100,6 +123,24 @@ Are you trying to test a raw helm file? To run Datree with Helm - check out the
 https://github.com/datreeio/helm-datree
 [?] Policy check didn't run for this file
 
+>>  File: /datree/datree/internal/fixtures/kube/skipRule/k8s-demo-skip-two.yaml
+
+
+[V] YAML validation
+[V] Kubernetes schema validation
+
+[X] Policy check
+
+SKIPPED
+
+⏩  Ensure workload has valid label values
+    - metadata.name: rss-site (kind: Deployment)
+💡  skip first k8s-demo rule
+
+⏩  Ensure each container has a configured liveness probe
+    - metadata.name: rss-site (kind: Deployment)
+💡  skip second k8s-demo rule
+
 
 `)
 		assert.Equal(t, string(expected), got)
@@ -111,7 +152,7 @@ https://github.com/datreeio/helm-datree
 
 		printer.SetTheme(CreateSimpleTheme())
 
-		got := printer.GetWarningsText(warnings)
+		got := printer.GetWarningsText(warnings, true)
 
 		expected := []byte(
 			`>>  File: ~/.datree/k8-demo.yaml
@@ -158,6 +199,14 @@ Are you trying to test a raw helm file? To run Datree with Helm - check out the
 https://github.com/datreeio/helm-datree
 [?] Policy check didn't run for this file
 
+>>  File: /datree/datree/internal/fixtures/kube/skipRule/k8s-demo-skip-two.yaml
+
+
+[V] YAML validation
+[V] Kubernetes schema validation
+
+[X] Policy check
+
 
 `)
 		assert.Equal(t, string(expected), got)