Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

Commit

Permalink
feat: print validation errors in custom key rules (#926)
Browse files Browse the repository at this point in the history
* feat: print validation errors in custom key rules

---------

Co-authored-by: teselil <[email protected]>
Co-authored-by: Yishay Mendelsohn <[email protected]>
  • Loading branch information
3 people authored Apr 13, 2023
1 parent 77080ed commit 9ba67d4
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 56 deletions.
13 changes: 7 additions & 6 deletions pkg/cliClient/evaluation.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,13 @@ type FailureLocation struct {
}

type Configuration struct {
Name string `json:"metadataName"`
Kind string `json:"kind"`
Occurrences int `json:"occurrences"`
IsSkipped bool `json:"isSkipped"`
SkipMessage string `json:"skipMessage"`
FailureLocations []FailureLocation `json:"failureLocation"`
Name string `json:"metadataName"`
Kind string `json:"kind"`
Occurrences int `json:"occurrences"`
IsSkipped bool `json:"isSkipped"`
SkipMessage string `json:"skipMessage"`
FailureLocations []FailureLocation `json:"failureLocation"`
ValidationFailureMessages []string `json:"validationFailureMessages"`
}

type FailedRule struct {
Expand Down
33 changes: 21 additions & 12 deletions pkg/evaluation/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/datreeio/datree/pkg/cliClient"
"github.com/datreeio/datree/pkg/extractor"
"github.com/datreeio/datree/pkg/jsonSchemaValidator"
extensions "github.com/datreeio/datree/pkg/jsonSchemaValidator/extensions"
"github.com/datreeio/datree/pkg/utils"
"github.com/mikefarah/yq/v4/pkg/yqlib"

Expand Down Expand Up @@ -216,15 +217,20 @@ func (e *Evaluator) evaluateRule(rule policy_factory.RuleWithSchema, configurati
}

configuration := cliClient.Configuration{
Name: configurationName,
Kind: configurationKind,
Occurrences: occurrences,
IsSkipped: false,
SkipMessage: "",
FailureLocations: []cliClient.FailureLocation{},
Name: configurationName,
Kind: configurationKind,
Occurrences: occurrences,
IsSkipped: false,
SkipMessage: "",
FailureLocations: []cliClient.FailureLocation{},
ValidationFailureMessages: []string{},
}

var validationFailureMessages []string
for _, detailedResult := range validationResult {
if strings.Contains(detailedResult.KeywordLocation, extensions.CustomKeyValidationErrorKeyPath) {
validationFailureMessages = append(validationFailureMessages, detailedResult.Error)
}
failedErrorLine, failedErrorColumn := e.getFailedRuleLineAndColumn(detailedResult.InstanceLocation, yamlNode)

failureLocation := cliClient.FailureLocation{
Expand All @@ -236,6 +242,8 @@ func (e *Evaluator) evaluateRule(rule policy_factory.RuleWithSchema, configurati
configuration.FailureLocations = append(configuration.FailureLocations, failureLocation)
}

configuration.ValidationFailureMessages = validationFailureMessages

if skipRuleExists {
configuration.IsSkipped = true
configuration.SkipMessage = skipMessage
Expand Down Expand Up @@ -332,12 +340,13 @@ func (e *Evaluator) formatEvaluationResults(evaluationResults FailedRulesByFiles
mapper[filePath][ruleIdentifier].OccurrencesDetails = append(
mapper[filePath][ruleIdentifier].OccurrencesDetails,
OccurrenceDetails{
MetadataName: configuration.Name,
Kind: configuration.Kind,
Occurrences: configuration.Occurrences,
IsSkipped: configuration.IsSkipped,
SkipMessage: configuration.SkipMessage,
FailureLocations: configuration.FailureLocations,
MetadataName: configuration.Name,
Kind: configuration.Kind,
Occurrences: configuration.Occurrences,
IsSkipped: configuration.IsSkipped,
SkipMessage: configuration.SkipMessage,
FailureLocations: configuration.FailureLocations,
ValidationFailureMessages: configuration.ValidationFailureMessages,
},
)
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/evaluation/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"path/filepath"
"testing"

"github.com/datreeio/datree/pkg/defaultPolicies"

"github.com/datreeio/datree/pkg/defaultRules"
"gopkg.in/yaml.v3"

Expand Down Expand Up @@ -190,6 +192,23 @@ func TestGetFailedRuleLineAndColumn(t *testing.T) {
assert.Equal(t, 18, column)
}

//go:embed test_fixtures/customRuleWithRegoCodeThatCantBeCompiled.yaml
var customRuleWithRegoCodeThatCantBeCompiledStr string

func TestEvaluateRuleWithCustomKeyThatIsNotValid(t *testing.T) {
customRuleWithRegoByteArray := []byte(customRuleWithRegoCodeThatCantBeCompiledStr)
var customRegoRule defaultPolicies.CustomRule
_ = yaml.Unmarshal(customRuleWithRegoByteArray, &customRegoRule)
customRuleWithRegoObj := policy_factory.RuleWithSchema{RuleIdentifier: customRegoRule.Identifier, RuleName: customRegoRule.Name, Schema: customRegoRule.Schema, MessageOnFailure: customRegoRule.DefaultMessageOnFailure}

mockedCliClient := &mockCliClient{}
evaluator := New(mockedCliClient, nil)

failedRule, _ := evaluator.evaluateRule(customRuleWithRegoObj, []byte(FailureLocationsStr), "test", "Deployment", nil, yaml.Node{})
assert.NotEmpty(t, failedRule.Configurations[0].ValidationFailureMessages)
assert.Contains(t, failedRule.Configurations[0].ValidationFailureMessages[0], "can't compile rego code")
}

type evaluateArgs struct {
policyCheckData PolicyCheckData
}
Expand Down
7 changes: 4 additions & 3 deletions pkg/evaluation/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,10 @@ func parseToPrinterWarnings(results *EvaluationResults, invalidYamlFiles []*extr
failedRule.OccurrencesDetails = append(
failedRule.OccurrencesDetails,
printer.OccurrenceDetails{
MetadataName: occurrenceDetails.MetadataName,
Kind: occurrenceDetails.Kind,
FailureLocations: occurrenceDetails.FailureLocations,
MetadataName: occurrenceDetails.MetadataName,
Kind: occurrenceDetails.Kind,
FailureLocations: occurrenceDetails.FailureLocations,
ValidationFailureMessages: occurrenceDetails.ValidationFailureMessages,
},
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"policyValidationResults":[{"fileName":"File1","ruleResults":[{"identifier":"CONTAINERS_MISSING_IMAGE_VALUE_VERSION","name":"Ensure each container image has a pinned (tag) version","messageOnFailure":"Incorrect value for key `image` - specify an image version to avoid unpleasant \"version surprises\" in the future","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0.image","failedErrorLine":10,"failedErrorColumn":20}]}]},{"identifier":"CONTAINERS_MISSING_MEMORY_LIMIT_KEY","name":"Ensure each container has a configured memory limit","messageOnFailure":"Missing property object `limits.memory` - value should be within the accepted boundaries recommended by the organization","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0.resources.limits","failedErrorLine":95,"failedErrorColumn":15}]}]},{"identifier":"WORKLOAD_INVALID_LABELS_VALUE","name":"Ensure workload has valid label values","messageOnFailure":"Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"metadata.labels.owner","failedErrorLine":7,"failedErrorColumn":12}]}]},{"identifier":"CONTAINERS_MISSING_LIVENESSPROBE_KEY","name":"Ensure each container has a configured liveness probe","messageOnFailure":"Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0","failedErrorLine":22,"failedErrorColumn":11}]}]}]}],"policySummary":{"policyName":"Default","totalRulesInPolicy":21,"totalSkippedRules":0,"totalRulesFailed":4,"totalPassedCount":0},"evaluationSummary":{"configsCount":1,"filesCount":1,"passedYamlValidationCount":1,"k8sValidation":"1/1","passedPolicyValidationCount":0},"yamlValidationResults":null,"k8sValidationResults":null,"loginUrl":"https://app.datree.io/login?t=tDJhAU478UTDeSwxGAy99y"}
{"policyValidationResults":[{"fileName":"File1","ruleResults":[{"identifier":"CONTAINERS_MISSING_IMAGE_VALUE_VERSION","name":"Ensure each container image has a pinned (tag) version","messageOnFailure":"Incorrect value for key `image` - specify an image version to avoid unpleasant \"version surprises\" in the future","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0.image","failedErrorLine":10,"failedErrorColumn":20}],"validationFailureMessages":null}]},{"identifier":"CONTAINERS_MISSING_MEMORY_LIMIT_KEY","name":"Ensure each container has a configured memory limit","messageOnFailure":"Missing property object `limits.memory` - value should be within the accepted boundaries recommended by the organization","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0.resources.limits","failedErrorLine":95,"failedErrorColumn":15}],"validationFailureMessages":null}]},{"identifier":"WORKLOAD_INVALID_LABELS_VALUE","name":"Ensure workload has valid label values","messageOnFailure":"Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"metadata.labels.owner","failedErrorLine":7,"failedErrorColumn":12}],"validationFailureMessages":null}]},{"identifier":"CONTAINERS_MISSING_LIVENESSPROBE_KEY","name":"Ensure each container has a configured liveness probe","messageOnFailure":"Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0","failedErrorLine":22,"failedErrorColumn":11}],"validationFailureMessages":null}]}]}],"policySummary":{"policyName":"Default","totalRulesInPolicy":21,"totalSkippedRules":0,"totalRulesFailed":4,"totalPassedCount":0},"evaluationSummary":{"configsCount":1,"filesCount":1,"passedYamlValidationCount":1,"k8sValidation":"1/1","passedPolicyValidationCount":0},"yamlValidationResults":null,"k8sValidationResults":null,"loginUrl":"https://app.datree.io/login?t=tDJhAU478UTDeSwxGAy99y"}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"policyValidationResults":[{"fileName":"File1","ruleResults":[{"identifier":"CONTAINERS_MISSING_IMAGE_VALUE_VERSION","name":"Ensure each container image has a pinned (tag) version","messageOnFailure":"Incorrect value for key `image` - specify an image version to avoid unpleasant \"version surprises\" in the future","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0.image","failedErrorLine":10,"failedErrorColumn":20}]}],"documentationUrl":"https://hub.datree.io/ensure-image-pinned-version"},{"identifier":"CONTAINERS_MISSING_MEMORY_LIMIT_KEY","name":"Ensure each container has a configured memory limit","messageOnFailure":"Missing property object `limits.memory` - value should be within the accepted boundaries recommended by the organization","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0.resources.limits","failedErrorLine":95,"failedErrorColumn":15}]}],"documentationUrl":"https://hub.datree.io/ensure-memory-limit"},{"identifier":"WORKLOAD_INVALID_LABELS_VALUE","name":"Ensure workload has valid label values","messageOnFailure":"Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"metadata.labels.owner","failedErrorLine":7,"failedErrorColumn":12}]}],"documentationUrl":"https://hub.datree.io/ensure-labels-value-valid"},{"identifier":"CONTAINERS_MISSING_LIVENESSPROBE_KEY","name":"Ensure each container has a configured liveness probe","messageOnFailure":"Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0","failedErrorLine":22,"failedErrorColumn":11}]}],"documentationUrl":"https://hub.datree.io/ensure-liveness-probe"}]}],"policySummary":{"policyName":"Default","totalRulesInPolicy":21,"totalSkippedRules":0,"totalRulesFailed":4,"totalPassedCount":0},"evaluationSummary":{"configsCount":1,"filesCount":1,"passedYamlValidationCount":1,"k8sValidation":"1/1","passedPolicyValidationCount":0},"yamlValidationResults":null,"k8sValidationResults":null,"loginUrl":"https://app.datree.io/login?t=tDJhAU478UTDeSwxGAy99y"}
{"policyValidationResults":[{"fileName":"File1","ruleResults":[{"identifier":"CONTAINERS_MISSING_IMAGE_VALUE_VERSION","name":"Ensure each container image has a pinned (tag) version","messageOnFailure":"Incorrect value for key `image` - specify an image version to avoid unpleasant \"version surprises\" in the future","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0.image","failedErrorLine":10,"failedErrorColumn":20}],"validationFailureMessages":null}],"documentationUrl":"https://hub.datree.io/ensure-image-pinned-version"},{"identifier":"CONTAINERS_MISSING_MEMORY_LIMIT_KEY","name":"Ensure each container has a configured memory limit","messageOnFailure":"Missing property object `limits.memory` - value should be within the accepted boundaries recommended by the organization","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0.resources.limits","failedErrorLine":95,"failedErrorColumn":15}],"validationFailureMessages":null}],"documentationUrl":"https://hub.datree.io/ensure-memory-limit"},{"identifier":"WORKLOAD_INVALID_LABELS_VALUE","name":"Ensure workload has valid label values","messageOnFailure":"Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"metadata.labels.owner","failedErrorLine":7,"failedErrorColumn":12}],"validationFailureMessages":null}],"documentationUrl":"https://hub.datree.io/ensure-labels-value-valid"},{"identifier":"CONTAINERS_MISSING_LIVENESSPROBE_KEY","name":"Ensure each container has a configured liveness probe","messageOnFailure":"Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks","occurrencesDetails":[{"metadataName":"rss-site","kind":"Deployment","skipMessage":"","occurrences":1,"isSkipped":false,"failureLocations":[{"schemaPath":"spec.template.spec.containers.0","failedErrorLine":22,"failedErrorColumn":11}],"validationFailureMessages":null}],"documentationUrl":"https://hub.datree.io/ensure-liveness-probe"}]}],"policySummary":{"policyName":"Default","totalRulesInPolicy":21,"totalSkippedRules":0,"totalRulesFailed":4,"totalPassedCount":0},"evaluationSummary":{"configsCount":1,"filesCount":1,"passedYamlValidationCount":1,"k8sValidation":"1/1","passedPolicyValidationCount":0},"yamlValidationResults":null,"k8sValidationResults":null,"loginUrl":"https://app.datree.io/login?t=tDJhAU478UTDeSwxGAy99y"}
4 changes: 4 additions & 0 deletions pkg/evaluation/printer_test_expected_outputs/yaml_output.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ policyValidationResults:
- schemapath: spec.template.spec.containers.0.image
failederrorline: 10
failederrorcolumn: 20
validationFailureMessages: []
- identifier: CONTAINERS_MISSING_MEMORY_LIMIT_KEY
name: Ensure each container has a configured memory limit
messageOnFailure: Missing property object `limits.memory` - value should be within
Expand All @@ -29,6 +30,7 @@ policyValidationResults:
- schemapath: spec.template.spec.containers.0.resources.limits
failederrorline: 95
failederrorcolumn: 15
validationFailureMessages: []
- identifier: WORKLOAD_INVALID_LABELS_VALUE
name: Ensure workload has valid label values
messageOnFailure: Incorrect value for key(s) under `labels` - the vales syntax
Expand All @@ -43,6 +45,7 @@ policyValidationResults:
- schemapath: metadata.labels.owner
failederrorline: 7
failederrorcolumn: 12
validationFailureMessages: []
- identifier: CONTAINERS_MISSING_LIVENESSPROBE_KEY
name: Ensure each container has a configured liveness probe
messageOnFailure: Missing property object `livenessProbe` - add a properly configured
Expand All @@ -57,6 +60,7 @@ policyValidationResults:
- schemapath: spec.template.spec.containers.0
failederrorline: 22
failederrorcolumn: 11
validationFailureMessages: []
policySummary:
policyName: Default
totalRulesInPolicy: 21
Expand Down
13 changes: 7 additions & 6 deletions pkg/evaluation/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ func (rp *Rule) GetFailedOccurrencesCount() int {
}

type OccurrenceDetails struct {
MetadataName string `yaml:"metadataName" json:"metadataName" xml:"metadataName"`
Kind string `yaml:"kind" json:"kind" xml:"kind"`
SkipMessage string `yaml:"skipMessage" json:"skipMessage" xml:"skipMessage"`
Occurrences int `yaml:"occurrences" json:"occurrences" xml:"occurrences"`
IsSkipped bool `yaml:"isSkipped" json:"isSkipped" xml:"isSkipped"`
FailureLocations []cliClient.FailureLocation `yaml:"failureLocations" json:"failureLocations" xml:"failureLocations"`
MetadataName string `yaml:"metadataName" json:"metadataName" xml:"metadataName"`
Kind string `yaml:"kind" json:"kind" xml:"kind"`
SkipMessage string `yaml:"skipMessage" json:"skipMessage" xml:"skipMessage"`
Occurrences int `yaml:"occurrences" json:"occurrences" xml:"occurrences"`
IsSkipped bool `yaml:"isSkipped" json:"isSkipped" xml:"isSkipped"`
FailureLocations []cliClient.FailureLocation `yaml:"failureLocations" json:"failureLocations" xml:"failureLocations"`
ValidationFailureMessages []string `yaml:"validationFailureMessages" json:"validationFailureMessages" xml:"validationFailureMessages"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
identifier: CUSTOM_DEPLOYMENT_BILLING_LABEL_EXISTS
name: Ensure Deployment has billing label [CUSTOM RULE]
defaultMessageOnFailure: deployment labels should contain billing label
schema:
if:
properties:
kind:
type: string
enum:
- Deployment
then:
regoDefinition:
libs:
- |
package lib.helpers
check_if_missing(missing) = isMissing {
isMissing := count(missing) > 0
}
code: |
package foosystemrequiredlabels
package foosystemrequiredlabels123
import data.lib.helpers
violation[labelIsMissing] {
provided := {label | input.metadata.labels[label]}
required := {"billing"}
missing := required - provided
labelIsMissing := helpers.check_if_missing(missing)
}
Loading

0 comments on commit 9ba67d4

Please sign in to comment.