Unverified Commit 6dfdd58a authored by TzlilSwimmer123's avatar TzlilSwimmer123 Committed by GitHub
Browse files

feat: local evaluation full implementation (#473)

parent 974165af
Showing with 1092 additions and 528 deletions
+1092 -528
package evaluation
import (
"encoding/json"
"github.com/xeipuuv/gojsonschema"
policy_factory "github.com/datreeio/datree/bl/policy"
"github.com/datreeio/datree/pkg/ciContext"
"github.com/datreeio/datree/pkg/cliClient"
"github.com/datreeio/datree/pkg/extractor"
"github.com/datreeio/datree/pkg/yamlSchemaValidator"
)
type CLIClient interface {
RequestEvaluation(request *cliClient.EvaluationRequest) (*cliClient.EvaluationResponse, error)
CreateEvaluation(request *cliClient.CreateEvaluationRequest) (*cliClient.CreateEvaluationResponse, error)
SendFailedYamlValidation(request *cliClient.UpdateEvaluationValidationRequest) error
SendFailedK8sValidation(request *cliClient.UpdateEvaluationValidationRequest) error
SendEvaluationResult(request *cliClient.EvaluationResultRequest) (*cliClient.SendEvaluationResultsResponse, error)
}
type Evaluator struct {
......@@ -27,12 +30,9 @@ func New(c CLIClient) *Evaluator {
}
}
type FileNameRuleMapper map[string]map[int]*Rule
type FileNameRuleMapper map[string]map[string]*Rule
type FailedRulesByFiles map[string]map[string]cliClient.FailedRule
type ResultType struct {
EvaluationResults *EvaluationResults
NonInteractiveEvaluationResults *NonInteractiveEvaluationResults
}
type EvaluationResults struct {
FileNameRuleMapper FileNameRuleMapper
Summary struct {
......@@ -42,79 +42,132 @@ type EvaluationResults struct {
}
}
func (e *Evaluator) CreateEvaluation(cliId string, cliVersion string, k8sVersion string, policyName string, ciContext *ciContext.CIContext) (*cliClient.CreateEvaluationResponse, error) {
createEvaluationResponse, err := e.cliClient.CreateEvaluation(&cliClient.CreateEvaluationRequest{
K8sVersion: &k8sVersion,
CliId: cliId,
PolicyName: policyName,
type FormattedResults struct {
EvaluationResults *EvaluationResults
NonInteractiveEvaluationResults *NonInteractiveEvaluationResults
}
type EvaluationRequestData struct {
CliId string
CliVersion string
K8sVersion string
PolicyName string
CiContext *ciContext.CIContext
RulesData []cliClient.RuleData
FilesData []cliClient.FileData
FailedYamlFiles []string
FailedK8sFiles []string
PolicyCheckResults FailedRulesByFiles
}
func (e *Evaluator) SendEvaluationResult(evaluationRequestData EvaluationRequestData) (*cliClient.SendEvaluationResultsResponse, error) {
sendEvaluationResultsResponse, err := e.cliClient.SendEvaluationResult(&cliClient.EvaluationResultRequest{
K8sVersion: evaluationRequestData.K8sVersion,
ClientId: evaluationRequestData.CliId,
Token: evaluationRequestData.CliId,
PolicyName: evaluationRequestData.PolicyName,
Metadata: &cliClient.Metadata{
CliVersion: cliVersion,
CliVersion: evaluationRequestData.CliVersion,
Os: e.osInfo.OS,
PlatformVersion: e.osInfo.PlatformVersion,
KernelVersion: e.osInfo.KernelVersion,
CIContext: ciContext,
CIContext: evaluationRequestData.CiContext,
},
FailedYamlFiles: evaluationRequestData.FailedYamlFiles,
FailedK8sFiles: evaluationRequestData.FailedK8sFiles,
AllExecutedRules: evaluationRequestData.RulesData,
AllEvaluatedFiles: evaluationRequestData.FilesData,
PolicyCheckResults: evaluationRequestData.PolicyCheckResults,
})
return createEvaluationResponse, err
return sendEvaluationResultsResponse, err
}
func (e *Evaluator) UpdateFailedYamlValidation(invalidFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
invalidFilesPaths := []*string{}
for _, file := range invalidFiles {
invalidFilesPaths = append(invalidFilesPaths, &file.Path)
}
err := e.cliClient.SendFailedYamlValidation(&cliClient.UpdateEvaluationValidationRequest{
EvaluationId: evaluationId,
InvalidFiles: invalidFilesPaths,
StopEvaluation: stopEvaluation,
})
return err
type PolicyCheckData struct {
FilesConfigurations []*extractor.FileConfigurations
IsInteractiveMode bool
PolicyName string
Policy policy_factory.Policy
}
func (e *Evaluator) UpdateFailedK8sValidation(invalidFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
invalidFilesPaths := []*string{}
for _, file := range invalidFiles {
invalidFilesPaths = append(invalidFilesPaths, &file.Path)
}
err := e.cliClient.SendFailedK8sValidation(&cliClient.UpdateEvaluationValidationRequest{
EvaluationId: evaluationId,
InvalidFiles: invalidFilesPaths,
StopEvaluation: stopEvaluation,
})
return err
type PolicyCheckResultData struct {
FormattedResults FormattedResults
RulesData []cliClient.RuleData
FilesData []cliClient.FileData
RawResults FailedRulesByFiles
RulesCount int
}
func (e *Evaluator) Evaluate(filesConfigurations []*extractor.FileConfigurations, evaluationResponse *cliClient.CreateEvaluationResponse, isInteractiveMode bool) (ResultType, error) {
func (e *Evaluator) Evaluate(policyCheckData PolicyCheckData) (PolicyCheckResultData, error) {
if len(filesConfigurations) == 0 {
return ResultType{}, nil
if len(policyCheckData.FilesConfigurations) == 0 {
return PolicyCheckResultData{FormattedResults{}, []cliClient.RuleData{}, []cliClient.FileData{}, FailedRulesByFiles{}, 0}, nil
}
res, err := e.cliClient.RequestEvaluation(&cliClient.EvaluationRequest{
EvaluationId: evaluationResponse.EvaluationId,
Files: filesConfigurations,
})
if err != nil {
return ResultType{}, err
yamlSchemaValidator := yamlSchemaValidator.New()
rulesCount := len(policyCheckData.Policy.Rules)
// map of files paths to map of rules to failed rule data
failedRulesByFiles := make(FailedRulesByFiles)
rulesData := []cliClient.RuleData{}
var filesData []cliClient.FileData
emptyPolicyCheckResult := PolicyCheckResultData{FormattedResults{}, []cliClient.RuleData{}, []cliClient.FileData{}, nil, 0}
for _, filesConfiguration := range policyCheckData.FilesConfigurations {
filesData = append(filesData, cliClient.FileData{FilePath: filesConfiguration.FileName, ConfigurationsCount: len(filesConfiguration.Configurations)})
for _, configuration := range filesConfiguration.Configurations {
for _, ruleWithSchema := range policyCheckData.Policy.Rules {
rulesData = append(rulesData, cliClient.RuleData{Identifier: ruleWithSchema.RuleIdentifier, Name: ruleWithSchema.RuleName})
configurationKind, configurationName := extractConfigurationInfo(configuration)
configurationJson, err := json.Marshal(configuration)
if err != nil {
return emptyPolicyCheckResult, err
}
ruleSchemaJson, err := json.Marshal(ruleWithSchema.Schema)
if err != nil {
return emptyPolicyCheckResult, err
}
validationResult, err := yamlSchemaValidator.Validate(string(ruleSchemaJson), string(configurationJson))
if err != nil {
return emptyPolicyCheckResult, err
}
failedRulesByFiles = calculateFailedRulesByFiles(failedRulesByFiles, validationResult, filesConfiguration.FileName, ruleWithSchema, configurationName, configurationKind)
}
}
}
resultType := ResultType{}
resultType.EvaluationResults = e.formatEvaluationResults(res.Results, len(filesConfigurations))
if !isInteractiveMode {
resultType.NonInteractiveEvaluationResults = e.formatNonInteractiveEvaluationResults(resultType.EvaluationResults, res.Results, evaluationResponse.PolicyName, evaluationResponse.RulesCount)
formattedResults := FormattedResults{}
formattedResults.EvaluationResults = e.formatEvaluationResults(failedRulesByFiles, len(policyCheckData.FilesConfigurations))
if !policyCheckData.IsInteractiveMode {
formattedResults.NonInteractiveEvaluationResults = e.formatNonInteractiveEvaluationResults(formattedResults.EvaluationResults, failedRulesByFiles, policyCheckData.PolicyName, rulesCount)
}
return resultType, nil
return PolicyCheckResultData{formattedResults, rulesData, filesData, failedRulesByFiles, rulesCount}, nil
}
// This method creates a NonInteractiveEvaluationResults structure
// from EvaluationResults.
func (e *Evaluator) formatNonInteractiveEvaluationResults(evaluationResults *EvaluationResults, listEvaluationResult []*cliClient.EvaluationResult, policyName string, totalRulesInPolicy int) *NonInteractiveEvaluationResults {
fileNameRuleMapper := evaluationResults.FileNameRuleMapper
ruleMapper := make(map[int]string)
for _, result := range listEvaluationResult {
ruleId := getRuleId(result)
ruleMapper[ruleId] = result.Rule.Identifier
func (e *Evaluator) formatNonInteractiveEvaluationResults(formattedEvaluationResults *EvaluationResults, evaluationResults FailedRulesByFiles, policyName string, totalRulesInPolicy int) *NonInteractiveEvaluationResults {
fileNameRuleMapper := formattedEvaluationResults.FileNameRuleMapper
ruleMapper := make(map[string]string)
for filePath := range evaluationResults {
for ruleIdentifier := range evaluationResults[filePath] {
ruleMapper[ruleIdentifier] = ruleIdentifier
}
}
nonInteractiveEvaluationResults := NonInteractiveEvaluationResults{}
for fileName, rules := range fileNameRuleMapper {
......@@ -122,7 +175,7 @@ func (e *Evaluator) formatNonInteractiveEvaluationResults(evaluationResults *Eva
formattedEvaluationResults.FileName = fileName
for _, rule := range rules {
ruleResult := RuleResult{Identifier: ruleMapper[rule.ID], Name: rule.Name, MessageOnFailure: rule.FailSuggestion, OccurrencesDetails: rule.OccurrencesDetails}
ruleResult := RuleResult{Identifier: ruleMapper[rule.Identifier], Name: rule.Name, MessageOnFailure: rule.MessageOnFailure, OccurrencesDetails: rule.OccurrencesDetails}
formattedEvaluationResults.RuleResults = append(
formattedEvaluationResults.RuleResults,
&ruleResult,
......@@ -136,44 +189,43 @@ func (e *Evaluator) formatNonInteractiveEvaluationResults(evaluationResults *Eva
nonInteractiveEvaluationResults.PolicySummary = &PolicySummary{
PolicyName: policyName,
TotalRulesInPolicy: totalRulesInPolicy,
TotalRulesFailed: evaluationResults.Summary.TotalFailedRules,
TotalPassedCount: evaluationResults.Summary.TotalPassedCount,
TotalRulesFailed: formattedEvaluationResults.Summary.TotalFailedRules,
TotalPassedCount: formattedEvaluationResults.Summary.TotalPassedCount,
}
return &nonInteractiveEvaluationResults
}
func (e *Evaluator) formatEvaluationResults(evaluationResults []*cliClient.EvaluationResult, filesCount int) *EvaluationResults {
mapper := make(map[string]map[int]*Rule)
func (e *Evaluator) formatEvaluationResults(evaluationResults FailedRulesByFiles, filesCount int) *EvaluationResults {
mapper := make(map[string]map[string]*Rule)
totalFailedCount := 0
totalPassedCount := filesCount
for _, result := range evaluationResults {
for _, match := range result.Results.Matches {
// file not already exists in mapper
if _, exists := mapper[match.FileName]; !exists {
mapper[match.FileName] = make(map[int]*Rule)
totalPassedCount = totalPassedCount - 1
}
ruleId := getRuleId(result)
for filePath := range evaluationResults {
if _, exists := mapper[filePath]; !exists {
mapper[filePath] = make(map[string]*Rule)
totalPassedCount = totalPassedCount - 1
}
for ruleIdentifier, failedRuleData := range evaluationResults[filePath] {
// file and rule not already exists in mapper
if _, exists := mapper[match.FileName][ruleId]; !exists {
if _, exists := mapper[filePath][ruleIdentifier]; !exists {
totalFailedCount++
mapper[match.FileName][ruleId] = &Rule{
ID: ruleId,
Name: result.Rule.Name,
FailSuggestion: result.Rule.FailSuggestion,
mapper[filePath][ruleIdentifier] = &Rule{
Identifier: ruleIdentifier,
Name: failedRuleData.Name,
MessageOnFailure: failedRuleData.MessageOnFailure,
OccurrencesDetails: []OccurrenceDetails{},
}
}
mapper[match.FileName][ruleId].OccurrencesDetails = append(
mapper[match.FileName][ruleId].OccurrencesDetails,
OccurrenceDetails{MetadataName: match.MetadataName, Kind: match.Kind},
)
for _, configuration := range failedRuleData.Configurations {
mapper[filePath][ruleIdentifier].OccurrencesDetails = append(
mapper[filePath][ruleIdentifier].OccurrencesDetails,
OccurrenceDetails{MetadataName: configuration.Name, Kind: configuration.Kind},
)
}
}
}
......@@ -193,13 +245,32 @@ func (e *Evaluator) formatEvaluationResults(evaluationResults []*cliClient.Evalu
return results
}
func getRuleId(evaluationResult *cliClient.EvaluationResult) int {
var ruleId int
if evaluationResult.Rule.Origin.Type == "default" {
ruleId = *evaluationResult.Rule.Origin.DefaultRuleId
} else {
ruleId = *evaluationResult.Rule.Origin.CustomRuleId
func extractConfigurationInfo(configuration extractor.Configuration) (string, string) {
kind := configuration["kind"].(string)
metadata := configuration["metadata"]
name := metadata.(map[string]interface{})["name"].(string)
return name, kind
}
type Result = gojsonschema.Result
func calculateFailedRulesByFiles(currentFailedRulesByFiles FailedRulesByFiles, validationResult *Result, fileName string, rule policy_factory.RuleWithSchema, configurationName string, configurationKind string) map[string]map[string]cliClient.FailedRule {
if len(validationResult.Errors()) > 0 {
configurationData := cliClient.Configuration{Name: configurationName, Kind: configurationKind, Occurrences: len(validationResult.Errors())}
if fileData, ok := currentFailedRulesByFiles[fileName]; ok {
if ruleData, ok := fileData[rule.RuleIdentifier]; ok {
ruleData.Configurations = append(ruleData.Configurations, configurationData)
currentFailedRulesByFiles[fileName][rule.RuleIdentifier] = ruleData
} else {
currentFailedRulesByFiles[fileName][rule.RuleIdentifier] = cliClient.FailedRule{Name: rule.RuleName, MessageOnFailure: rule.MessageOnFailure, Configurations: []cliClient.Configuration{configurationData}}
}
} else {
currentFailedRulesByFiles[fileName] = map[string]cliClient.FailedRule{rule.RuleIdentifier: {Name: rule.RuleName, MessageOnFailure: rule.MessageOnFailure, Configurations: []cliClient.Configuration{configurationData}}}
}
}
return ruleId
return currentFailedRulesByFiles
}
package evaluation
import (
"encoding/json"
"path/filepath"
"testing"
policy_factory "github.com/datreeio/datree/bl/policy"
"github.com/datreeio/datree/pkg/fileReader"
"github.com/datreeio/datree/pkg/ciContext"
"github.com/datreeio/datree/pkg/cliClient"
"github.com/datreeio/datree/pkg/extractor"
......@@ -15,24 +19,14 @@ type mockCliClient struct {
mock.Mock
}
func (m *mockCliClient) CreateEvaluation(createEvaluationRequest *cliClient.CreateEvaluationRequest) (*cliClient.CreateEvaluationResponse, error) {
args := m.Called(createEvaluationRequest)
return args.Get(0).(*cliClient.CreateEvaluationResponse), args.Error(1)
}
func (m *mockCliClient) RequestEvaluation(evaluationRequest *cliClient.EvaluationRequest) (*cliClient.EvaluationResponse, error) {
args := m.Called(evaluationRequest)
return args.Get(0).(*cliClient.EvaluationResponse), args.Error(1)
func (m *mockCliClient) RequestEvaluationPrerunData(token string) (*cliClient.EvaluationPrerunDataResponse, error) {
args := m.Called(token)
return args.Get(0).(*cliClient.EvaluationPrerunDataResponse), args.Error(1)
}
func (m *mockCliClient) SendFailedYamlValidation(request *cliClient.UpdateEvaluationValidationRequest) error {
args := m.Called(request)
return args.Error(0)
}
func (m *mockCliClient) SendFailedK8sValidation(request *cliClient.UpdateEvaluationValidationRequest) error {
args := m.Called(request)
return args.Error(0)
func (m *mockCliClient) SendEvaluationResult(evaluationResultRequest *cliClient.EvaluationResultRequest) (*cliClient.SendEvaluationResultsResponse, error) {
args := m.Called(evaluationResultRequest)
return args.Get(0).(*cliClient.SendEvaluationResultsResponse), args.Error(1)
}
func (m *mockCliClient) GetVersionMessage(cliVersion string, timeout int) (*cliClient.VersionMessage, error) {
......@@ -41,18 +35,6 @@ func (m *mockCliClient) GetVersionMessage(cliVersion string, timeout int) (*cliC
}
type cliClientMockTestCase struct {
createEvaluation struct {
evaluationId int
k8sVersion string
err error
}
requestEvaluation struct {
response *cliClient.EvaluationResponse
err error
}
updateEvaluationValidation struct {
err error
}
getVersionMessage struct {
response *cliClient.VersionMessage
err error
......@@ -63,8 +45,8 @@ type evaluatorMock struct {
}
// TODO: add actual tests
func TestCreateEvaluation(t *testing.T) {
t.Run("CreateEvaluation should succedd", func(t *testing.T) {
func TestSendEvaluationResult(t *testing.T) {
t.Run("SendEvaluationResult should succeed", func(t *testing.T) {
mockedCliClient := &mockCliClient{}
evaluator := &Evaluator{
cliClient: mockedCliClient,
......@@ -78,6 +60,7 @@ func TestCreateEvaluation(t *testing.T) {
cliId := "test_token"
cliVersion := "0.0.7"
k8sVersion := "1.18.1"
promptMessage := ""
policyName := "Default"
ciContext := &ciContext.CIContext{
IsCI: true,
......@@ -87,23 +70,45 @@ func TestCreateEvaluation(t *testing.T) {
},
}
mockedCliClient.On("CreateEvaluation", mock.Anything).Return(&cliClient.CreateEvaluationResponse{EvaluationId: 1, K8sVersion: k8sVersion}, nil)
mockedCliClient.On("SendEvaluationResult", mock.Anything).Return(&cliClient.SendEvaluationResultsResponse{EvaluationId: 1, PromptMessage: promptMessage}, nil)
expectedSendEvaluationResultsResponse := &cliClient.SendEvaluationResultsResponse{EvaluationId: 1, PromptMessage: promptMessage}
evaluationRequestData := EvaluationRequestData{
CliId: cliId,
CliVersion: cliVersion,
K8sVersion: k8sVersion,
PolicyName: policyName,
CiContext: ciContext,
RulesData: []cliClient.RuleData{},
FilesData: []cliClient.FileData{},
FailedYamlFiles: []string{},
FailedK8sFiles: []string{},
PolicyCheckResults: nil,
}
sendEvaluationResultsResponse, _ := evaluator.SendEvaluationResult(evaluationRequestData)
expectedCreateEvaluationResponse := &cliClient.CreateEvaluationResponse{EvaluationId: 1, K8sVersion: k8sVersion}
createEvaluationResponse, _ := evaluator.CreateEvaluation(cliId, cliVersion, k8sVersion, policyName, ciContext)
mockedCliClient.AssertCalled(t, "CreateEvaluation", &cliClient.CreateEvaluationRequest{
K8sVersion: &k8sVersion,
CliId: cliId,
PolicyName: policyName,
sendEvaluationResultRequestData := &cliClient.EvaluationResultRequest{
K8sVersion: evaluationRequestData.K8sVersion,
ClientId: evaluationRequestData.CliId,
Token: evaluationRequestData.CliId,
PolicyName: evaluationRequestData.PolicyName,
Metadata: &cliClient.Metadata{
CliVersion: cliVersion,
CliVersion: evaluationRequestData.CliVersion,
Os: evaluator.osInfo.OS,
PlatformVersion: evaluator.osInfo.PlatformVersion,
KernelVersion: evaluator.osInfo.KernelVersion,
CIContext: ciContext,
CIContext: evaluationRequestData.CiContext,
},
})
assert.Equal(t, expectedCreateEvaluationResponse, createEvaluationResponse)
FailedYamlFiles: evaluationRequestData.FailedYamlFiles,
FailedK8sFiles: evaluationRequestData.FailedK8sFiles,
AllExecutedRules: evaluationRequestData.RulesData,
AllEvaluatedFiles: evaluationRequestData.FilesData,
PolicyCheckResults: evaluationRequestData.PolicyCheckResults,
}
mockedCliClient.AssertCalled(t, "SendEvaluationResult", sendEvaluationResultRequestData)
assert.Equal(t, expectedSendEvaluationResultsResponse, sendEvaluationResultsResponse)
})
}
......@@ -113,45 +118,49 @@ func TestEvaluate(t *testing.T) {
request_evaluation_all_valid(),
request_evaluation_all_invalid(),
}
prerunData := mockGetPreRunData()
policy, _ := policy_factory.CreatePolicy(prerunData.PoliciesJson, "")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockedCliClient := &mockCliClient{}
mockedCliClient.On("RequestEvaluation", mock.Anything).Return(tt.mock.cliClient.requestEvaluation.response, tt.mock.cliClient.requestEvaluation.err)
evaluator := &Evaluator{
cliClient: mockedCliClient,
osInfo: tt.args.osInfo,
}
// TODO: define and check the rest of the values
results, _ := evaluator.Evaluate(tt.args.validFilesConfigurations, tt.args.response, tt.args.isInteractiveMode)
policyCheckData := PolicyCheckData{
FilesConfigurations: tt.args.policyCheckData.FilesConfigurations,
IsInteractiveMode: tt.args.policyCheckData.IsInteractiveMode,
PolicyName: policy.Name,
Policy: policy,
}
policyCheckResultData, err := evaluator.Evaluate(policyCheckData)
if err != nil {
panic(err)
}
if tt.expected.isRequestEvaluationCalled {
mockedCliClient.AssertCalled(t, "RequestEvaluation", mock.Anything)
assert.Equal(t, tt.expected.response.EvaluationResults.Summary, results.EvaluationResults.Summary)
assert.Equal(t, tt.expected.response.EvaluationResults.FileNameRuleMapper, results.EvaluationResults.FileNameRuleMapper)
if len(policyCheckData.FilesConfigurations) > 0 {
assert.Equal(t, tt.expected.policyCheckResultData.FormattedResults.EvaluationResults.Summary, policyCheckResultData.FormattedResults.EvaluationResults.Summary)
assert.Equal(t, tt.expected.policyCheckResultData.FormattedResults.EvaluationResults.FileNameRuleMapper, policyCheckResultData.FormattedResults.EvaluationResults.FileNameRuleMapper)
} else {
mockedCliClient.AssertNotCalled(t, "RequestEvaluation")
assert.Equal(t, tt.expected.policyCheckResultData.FormattedResults, policyCheckResultData.FormattedResults)
}
})
}
}
type evaluateArgs struct {
validFilesConfigurations []*extractor.FileConfigurations
osInfo *OSInfo
isInteractiveMode bool
rulesCount int
response *cliClient.CreateEvaluationResponse
policyCheckData PolicyCheckData
osInfo *OSInfo
}
type evaluateExpected struct {
response ResultType
err error
isRequestEvaluationCalled bool
isCreateEvaluationCalled bool
isGetVersionMessageCalled bool
policyCheckResultData PolicyCheckResultData
err error
}
type evaluateTestCase struct {
......@@ -162,46 +171,28 @@ type evaluateTestCase struct {
}
func request_evaluation_all_valid() *evaluateTestCase {
validFilePath := "../../internal/fixtures/kube/pass-all.yaml"
validFilePath := "internal/fixtures/kube/pass-all.yaml"
prerunData := mockGetPreRunData()
policy, _ := policy_factory.CreatePolicy(prerunData.PoliciesJson, "")
return &evaluateTestCase{
name: "should request validation without invalid files",
args: &evaluateArgs{
validFilesConfigurations: newFilesConfigurations(validFilePath),
response: &cliClient.CreateEvaluationResponse{
EvaluationId: 1,
PolicyName: "Default",
RulesCount: 21,
policyCheckData: PolicyCheckData{
FilesConfigurations: newFilesConfigurations(validFilePath),
IsInteractiveMode: true,
PolicyName: "Default",
Policy: policy,
},
osInfo: &OSInfo{
OS: "darwin",
PlatformVersion: "1.2.3",
KernelVersion: "4.5.6",
},
isInteractiveMode: true,
},
mock: &evaluatorMock{
cliClient: &cliClientMockTestCase{
createEvaluation: struct {
evaluationId int
k8sVersion string
err error
}{
evaluationId: 1,
err: nil,
},
requestEvaluation: struct {
response *cliClient.EvaluationResponse
err error
}{
response: &cliClient.EvaluationResponse{
Results: []*cliClient.EvaluationResult{},
},
err: nil,
},
updateEvaluationValidation: struct{ err error }{
err: nil,
},
getVersionMessage: struct {
response *cliClient.VersionMessage
err error
......@@ -212,65 +203,49 @@ func request_evaluation_all_valid() *evaluateTestCase {
},
},
expected: &evaluateExpected{
response: ResultType{
EvaluationResults: &EvaluationResults{
FileNameRuleMapper: make(map[string]map[int]*Rule),
Summary: struct {
TotalFailedRules int
FilesCount int
TotalPassedCount int
}{
TotalFailedRules: 0,
FilesCount: 1,
TotalPassedCount: 1,
policyCheckResultData: PolicyCheckResultData{
FormattedResults: FormattedResults{
EvaluationResults: &EvaluationResults{
FileNameRuleMapper: make(map[string]map[string]*Rule),
Summary: struct {
TotalFailedRules int
FilesCount int
TotalPassedCount int
}{
TotalFailedRules: 0,
FilesCount: 1,
TotalPassedCount: 1,
},
},
NonInteractiveEvaluationResults: nil,
},
},
err: nil,
isRequestEvaluationCalled: true,
err: nil,
},
}
}
func request_evaluation_all_invalid() *evaluateTestCase {
prerunData := mockGetPreRunData()
policy, _ := policy_factory.CreatePolicy(prerunData.PoliciesJson, "")
return &evaluateTestCase{
name: "should not request validation if there are no valid files",
args: &evaluateArgs{
validFilesConfigurations: []*extractor.FileConfigurations{},
response: &cliClient.CreateEvaluationResponse{
EvaluationId: 1,
PolicyName: "Default",
RulesCount: 21,
policyCheckData: PolicyCheckData{
FilesConfigurations: []*extractor.FileConfigurations{},
IsInteractiveMode: true,
PolicyName: "Default",
Policy: policy,
},
osInfo: &OSInfo{
OS: "darwin",
PlatformVersion: "1.2.3",
KernelVersion: "4.5.6",
},
isInteractiveMode: true,
},
mock: &evaluatorMock{
cliClient: &cliClientMockTestCase{
createEvaluation: struct {
evaluationId int
k8sVersion string
err error
}{
evaluationId: 1,
err: nil,
},
requestEvaluation: struct {
response *cliClient.EvaluationResponse
err error
}{
response: &cliClient.EvaluationResponse{
Results: []*cliClient.EvaluationResult{},
},
err: nil,
},
updateEvaluationValidation: struct{ err error }{
err: nil,
},
getVersionMessage: struct {
response *cliClient.VersionMessage
err error
......@@ -281,22 +256,10 @@ func request_evaluation_all_invalid() *evaluateTestCase {
},
},
expected: &evaluateExpected{
response: ResultType{
EvaluationResults: &EvaluationResults{
FileNameRuleMapper: make(map[string]map[int]*Rule),
Summary: struct {
TotalFailedRules int
FilesCount int
TotalPassedCount int
}{
TotalFailedRules: 0,
FilesCount: 1,
TotalPassedCount: 0,
},
},
policyCheckResultData: PolicyCheckResultData{
FormattedResults: FormattedResults{},
},
err: nil,
isRequestEvaluationCalled: false,
err: nil,
},
}
}
......@@ -309,3 +272,25 @@ func newFilesConfigurations(path string) []*extractor.FileConfigurations {
})
return filesConfigurations
}
func mockGetPreRunData() *cliClient.EvaluationPrerunDataResponse {
const policiesJsonPath = "../../internal/fixtures/policyAsCode/policies.json"
fileReader := fileReader.CreateFileReader(nil)
policiesJsonStr, err := fileReader.ReadFileContent(policiesJsonPath)
if err != nil {
panic(err)
}
policiesJsonRawData := []byte(policiesJsonStr)
var policiesJson *cliClient.EvaluationPrerunDataResponse
err = json.Unmarshal(policiesJsonRawData, &policiesJson)
if err != nil {
panic(err)
}
return policiesJson
}
......@@ -21,7 +21,7 @@ type Printer interface {
PrintEvaluationSummary(summary printer.EvaluationSummary, k8sVersion string)
}
func PrintResults(results ResultType, invalidYamlFiles []*extractor.InvalidFile, invalidK8sFiles []*extractor.InvalidFile, evaluationSummary printer.EvaluationSummary, loginURL string, outputFormat string, printer Printer, k8sVersion string, policyName string) error {
func PrintResults(results FormattedResults, invalidYamlFiles []*extractor.InvalidFile, invalidK8sFiles []*extractor.InvalidFile, evaluationSummary printer.EvaluationSummary, loginURL string, outputFormat string, printer Printer, k8sVersion string, policyName string) error {
if outputFormat == "json" || outputFormat == "yaml" || outputFormat == "xml" {
nonInteractiveEvaluationResults := results.NonInteractiveEvaluationResults
if nonInteractiveEvaluationResults == nil {
......@@ -163,18 +163,17 @@ func parseToPrinterWarnings(results *EvaluationResults, invalidYamlFiles []*extr
rules := results.FileNameRuleMapper[filename]
var failedRules = []printer.FailedRule{}
rulesIds := []int{}
for ruleId := range rules {
rulesIds = append(rulesIds, ruleId)
rulesUniqueNames := []string{}
for rulesUniqueName := range rules {
rulesUniqueNames = append(rulesUniqueNames, rulesUniqueName)
}
sort.Ints(rulesIds)
for _, ruleId := range rulesIds {
rule := rules[ruleId]
for _, ruleUniqueName := range rulesUniqueNames {
rule := rules[ruleUniqueName]
failedRule := printer.FailedRule{
Name: rule.Name,
Occurrences: rule.GetCount(),
Suggestion: rule.FailSuggestion,
Suggestion: rule.MessageOnFailure,
OccurrencesDetails: []printer.OccurrenceDetails{},
}
for _, occurrenceDetails := range rule.OccurrencesDetails {
......@@ -305,46 +304,3 @@ func parseEvaluationResultsToSummary(results *EvaluationResults, evaluationSumma
}
return *summary
}
func (mapper FileNameRuleMapper) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if len(mapper) == 0 {
return nil
}
tokens := []xml.Token{start}
fileXMLName := xml.Name{Space: "", Local: "File"}
filenameXMLName := xml.Name{Space: "", Local: "filename"}
// Iterate over mapper and create XML tokens for all entries
for filePath, encapsulatedRule := range mapper {
keys := make([]int, 0)
for i := range encapsulatedRule {
keys = append(keys, i)
}
sort.Ints(keys)
for _, key := range keys { // Since rule is encapsulated by its id (int), we don't add is a tag
rule := encapsulatedRule[key]
startToken := xml.StartElement{Name: fileXMLName, Attr: []xml.Attr{{Name: filenameXMLName, Value: filePath}}}
endToken := xml.EndElement{Name: fileXMLName}
tokens = append(tokens, startToken, rule, endToken)
}
}
tokens = append(tokens, xml.EndElement{Name: start.Name})
for _, t := range tokens {
var err error
switch t.(type) {
default:
err = e.EncodeToken(t)
case *Rule:
err = e.EncodeElement(t, xml.StartElement{Name: xml.Name{Space: "", Local: "Rule"}})
}
if err != nil {
return err
}
}
return e.Flush()
}
......@@ -31,7 +31,7 @@ func (c *mockPrinter) PrintEvaluationSummary(summary printer.EvaluationSummary,
}
type printResultsTestCaseArgs struct {
results ResultType
results FormattedResults
invalidYamlFiles []*extractor.InvalidFile
invalidK8sFiles []*extractor.InvalidFile
evaluationSummary printer.EvaluationSummary
......@@ -204,9 +204,9 @@ func print_resultst(outputFormat string) *printResultsTestCase {
return &printResultsTestCase{
name: "Print Results Text",
args: &printResultsTestCaseArgs{
results: ResultType{
results: FormattedResults{
EvaluationResults: &EvaluationResults{
FileNameRuleMapper: map[string]map[int]*Rule{},
FileNameRuleMapper: map[string]map[string]*Rule{},
Summary: struct {
TotalFailedRules int
FilesCount int
......
package evaluation
type Rule struct {
ID int
Identifier string
Name string
FailSuggestion string
MessageOnFailure string
OccurrencesDetails []OccurrenceDetails
}
......
......@@ -2,9 +2,10 @@ package files
import (
"bytes"
"gopkg.in/yaml.v3"
"os"
"gopkg.in/yaml.v3"
"github.com/datreeio/datree/pkg/extractor"
)
......
package policy
import (
"fmt"
"github.com/datreeio/datree/pkg/cliClient"
internal_policy "github.com/datreeio/datree/pkg/policy"
)
type Policy struct {
Name string
Rules []RuleWithSchema
}
type RuleWithSchema struct {
RuleIdentifier string
RuleName string
Schema map[string]interface{}
MessageOnFailure string
}
func CreatePolicy(policies *cliClient.EvaluationPrerunPolicies, policyName string) (Policy, error) {
defaultRules, err := internal_policy.GetDefaultRules()
if err != nil {
return Policy{}, err
}
var rules []RuleWithSchema
if policies != nil {
var chosenPolicy *cliClient.Policy
for _, policy := range policies.Policies {
if policyName == "" && policy.IsDefault {
chosenPolicy = policy
policyName = chosenPolicy.Name
break
} else if policy.Name == policyName {
chosenPolicy = policy
break
}
}
if chosenPolicy == nil {
err := fmt.Errorf("policy %s doesn't exist", policyName)
return Policy{}, err
}
rules, err = populateRules(chosenPolicy.Rules, policies.CustomRules, defaultRules.Rules)
if err != nil {
return Policy{}, err
}
} else {
policy := createDefaultPolicy(defaultRules)
return policy, nil
}
return Policy{policyName, rules}, nil
}
func populateRules(policyRules []cliClient.Rule, customRules []*cliClient.CustomRule, defaultRules []*internal_policy.DefaultRuleDefinition) ([]RuleWithSchema, error) {
var rules = []RuleWithSchema{}
if policyRules == nil {
return rules, nil
}
for _, rule := range policyRules {
customRule := getCustomRuleByIdentifier(customRules, rule.Identifier)
if customRule != nil {
rules = append(rules, RuleWithSchema{rule.Identifier, customRule.Name, customRule.Schema, rule.MessageOnFailure})
} else {
defaultRule := getDefaultRuleByIdentifier(defaultRules, rule.Identifier)
if defaultRule != nil {
rules = append(rules, RuleWithSchema{rule.Identifier, defaultRule.Name, defaultRule.Schema, rule.MessageOnFailure})
} else {
rulesIsNotCustomNorDefaultErr := fmt.Errorf("rule %s is not custom nor default", rule.Identifier)
return nil, rulesIsNotCustomNorDefaultErr
}
}
}
return rules, nil
}
func getDefaultRuleByIdentifier(defaultRules []*internal_policy.DefaultRuleDefinition, identifier string) *internal_policy.DefaultRuleDefinition {
for _, defaultRule := range defaultRules {
if identifier == defaultRule.UniqueName {
return defaultRule
}
}
return nil
}
func getCustomRuleByIdentifier(customRules []*cliClient.CustomRule, identifier string) *cliClient.CustomRule {
for _, customRule := range customRules {
if identifier == customRule.Identifier {
return customRule
}
}
return nil
}
func createDefaultPolicy(defaultRules *internal_policy.DefaultRulesDefinitions) Policy {
var rules []RuleWithSchema
for _, defaultRule := range defaultRules.Rules {
rules = append(rules, RuleWithSchema{defaultRule.UniqueName, defaultRule.Name, defaultRule.Schema, defaultRule.MessageOnFailure})
}
return Policy{"Default", rules}
}
package policy
import (
"encoding/json"
"os"
"testing"
"github.com/datreeio/datree/pkg/fileReader"
internal_policy "github.com/datreeio/datree/pkg/policy"
"github.com/datreeio/datree/pkg/cliClient"
"github.com/stretchr/testify/assert"
)
const policiesJsonPath = "../../internal/fixtures/policyAsCode/policies.json"
func TestCreatePolicy(t *testing.T) {
policiesJson := mockGetPreRunData()
err := os.Chdir("../../")
if err != nil {
panic(err)
}
t.Run("Test Create Policy With Default Policy", func(t *testing.T) {
policy, _ := CreatePolicy(policiesJson.PoliciesJson, "")
var expectedRules []RuleWithSchema
defaultRules, err := internal_policy.GetDefaultRules()
if err != nil {
panic(err)
}
for _, defaultRule := range defaultRules.Rules {
switch defaultRule.UniqueName {
case "WORKLOAD_INCORRECT_NAMESPACE_VALUE_DEFAULT":
expectedRules = append(expectedRules, RuleWithSchema{RuleIdentifier: defaultRule.UniqueName, RuleName: defaultRule.Name, Schema: defaultRule.Schema, MessageOnFailure: "Incorrect value for key `namespace` - use an explicit namespace instead of the default one (`default`)"})
case "CONTAINERS_INCORRECT_PRIVILEGED_VALUE_TRUE":
expectedRules = append(expectedRules, RuleWithSchema{RuleIdentifier: defaultRule.UniqueName, RuleName: defaultRule.Name, Schema: defaultRule.Schema, MessageOnFailure: "Incorrect value for key `privileged` - this mode will allow the container thenhjgjgj same access as processes running on the host"})
}
}
customRuleJsonMap := make(map[string]interface{})
customRuleSchemaStr := "{\"properties\":{\"metadata\":{\"properties\":{\"labels\":{\"additionalProperties\":false,\"patternProperties\":{\"^.*$\":{\"format\":\"hostname\"}}}}}}}"
err = json.Unmarshal([]byte(customRuleSchemaStr), &customRuleJsonMap)
if err != nil {
panic(err)
}
expectedRules = append(expectedRules, RuleWithSchema{RuleIdentifier: "CUSTOM_WORKLOAD_INVALID_LABELS_VALUE", RuleName: "Ensure workload has valid label values [CUSTOM RULE]", Schema: customRuleJsonMap, MessageOnFailure: "All lables values must follow the RFC 1123 hostname standard (https://knowledge.broadcom.com/external/article/49542/restrictions-on-valid-host-names.html)"})
expectedPolicy := Policy{Name: "labels_best_practices", Rules: expectedRules}
assert.Equal(t, expectedPolicy, policy)
})
t.Run("Test Create Policy With Specific Policy", func(t *testing.T) {
policy, err := CreatePolicy(policiesJson.PoliciesJson, "labels_best_practices2")
var expectedRules []RuleWithSchema
if err != nil {
panic(err)
}
customRuleJsonMap := make(map[string]interface{})
customRuleSchemaStr := "{\"properties\":{\"metadata\":{\"properties\":{\"labels\":{\"additionalProperties\":false,\"patternProperties\":{\"^.*$\":{\"format\":\"hostname\"}}}}}}}"
err = json.Unmarshal([]byte(customRuleSchemaStr), &customRuleJsonMap)
if err != nil {
panic(err)
}
expectedRules = append(expectedRules, RuleWithSchema{RuleIdentifier: "CUSTOM_WORKLOAD_INVALID_LABELS_VALUE", RuleName: "Ensure workload has valid label values [CUSTOM RULE]", Schema: customRuleJsonMap, MessageOnFailure: "All lables values must follow the RFC 1123 hostname standard (https://knowledge.broadcom.com/external/article/49542/restrictions-on-valid-host-names.html)"})
expectedPolicy := Policy{Name: "labels_best_practices2", Rules: expectedRules}
assert.Equal(t, expectedPolicy, policy)
})
}
func mockGetPreRunData() *cliClient.EvaluationPrerunDataResponse {
fileReader := fileReader.CreateFileReader(nil)
policiesJsonStr, err := fileReader.ReadFileContent(policiesJsonPath)
if err != nil {
panic(err)
}
policiesJsonRawData := []byte(policiesJsonStr)
var policiesJson *cliClient.EvaluationPrerunDataResponse
err = json.Unmarshal(policiesJsonRawData, &policiesJson)
if err != nil {
panic(err)
}
return policiesJson
}
......@@ -4,12 +4,18 @@ import (
"os"
"strings"
"github.com/datreeio/datree/pkg/cliClient"
"github.com/datreeio/datree/cmd/test"
"github.com/datreeio/datree/pkg/executor"
"github.com/datreeio/datree/pkg/utils"
"github.com/spf13/cobra"
)
type CliClient interface {
RequestEvaluationPrerunData(token string) (*cliClient.EvaluationPrerunDataResponse, error)
}
type KustomizeCommandRunner interface {
BuildCommandDescription(dir string, name string, args []string) string
RunCommand(name string, args []string) (executor.CommandOutput, error)
......@@ -59,7 +65,13 @@ func New(testCtx *test.TestCommandContext, kustomizeCtx *KustomizeContext) *cobr
return err
}
testCommandOptions := test.GenerateTestCommandOptions(testCommandFlags, localConfigContent)
evaluationPrerunData, err := testCtx.CliClient.RequestEvaluationPrerunData(localConfigContent.CliId)
if err != nil {
return err
}
testCommandOptions, err := test.GenerateTestCommandData(testCommandFlags, localConfigContent, evaluationPrerunData)
out, err := kustomizeCtx.CommandRunner.ExecuteKustomizeBin(args)
if err != nil {
......
......@@ -6,7 +6,6 @@ import (
"github.com/datreeio/datree/bl/evaluation"
"github.com/datreeio/datree/bl/messager"
"github.com/datreeio/datree/cmd/test"
"github.com/datreeio/datree/pkg/ciContext"
"github.com/datreeio/datree/pkg/cliClient"
"github.com/datreeio/datree/pkg/executor"
"github.com/datreeio/datree/pkg/extractor"
......@@ -61,24 +60,19 @@ type mockEvaluator struct {
mock.Mock
}
func (m *mockEvaluator) Evaluate(filesConfigurationsChan []*extractor.FileConfigurations, evaluationResponse *cliClient.CreateEvaluationResponse, isInteractiveMode bool) (evaluation.ResultType, error) {
args := m.Called(filesConfigurationsChan, evaluationResponse, isInteractiveMode)
return args.Get(0).(evaluation.ResultType), args.Error(1)
func (m *mockEvaluator) Evaluate(policyCheckData evaluation.PolicyCheckData) (evaluation.PolicyCheckResultData, error) {
args := m.Called(policyCheckData)
return args.Get(0).(evaluation.PolicyCheckResultData), args.Error(1)
}
func (m *mockEvaluator) CreateEvaluation(cliId string, cliVersion string, k8sVersion string, policyName string, ciContext *ciContext.CIContext) (*cliClient.CreateEvaluationResponse, error) {
args := m.Called(cliId, cliVersion, k8sVersion, policyName)
return args.Get(0).(*cliClient.CreateEvaluationResponse), args.Error(1)
func (m *mockEvaluator) SendEvaluationResult(evaluationRequestData evaluation.EvaluationRequestData) (*cliClient.SendEvaluationResultsResponse, error) {
args := m.Called(evaluationRequestData)
return args.Get(0).(*cliClient.SendEvaluationResultsResponse), args.Error(1)
}
func (m *mockEvaluator) UpdateFailedYamlValidation(invalidYamlFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
args := m.Called(invalidYamlFiles, evaluationId, stopEvaluation)
return args.Error(0)
}
func (m *mockEvaluator) UpdateFailedK8sValidation(invalidK8sFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
args := m.Called(invalidK8sFiles, evaluationId, stopEvaluation)
return args.Error(0)
func (m *mockEvaluator) RequestEvaluationPrerunData(token string) (*cliClient.EvaluationPrerunDataResponse, int, error) {
args := m.Called(token)
return args.Get(0).(*cliClient.EvaluationPrerunDataResponse), args.Get(1).(int), args.Error(2)
}
type mockMessager struct {
......
......@@ -40,6 +40,7 @@ func init() {
Printer: app.context.Printer,
Reader: app.context.Reader,
K8sValidator: app.context.K8sValidator,
CliClient: app.context.CliClient,
}))
rootCmd.AddCommand(kustomize.New(&test.TestCommandContext{
......@@ -50,6 +51,7 @@ func init() {
Printer: app.context.Printer,
Reader: app.context.Reader,
K8sValidator: app.context.K8sValidator,
CliClient: app.context.CliClient,
}, &kustomize.KustomizeContext{CommandRunner: app.context.CommandRunner}))
rootCmd.AddCommand(version.New(&version.VersionCommandContext{
......
......@@ -10,6 +10,8 @@ import (
"github.com/datreeio/datree/bl/evaluation"
"github.com/datreeio/datree/bl/files"
"github.com/datreeio/datree/bl/messager"
"github.com/datreeio/datree/bl/policy"
policy_factory "github.com/datreeio/datree/bl/policy"
"github.com/datreeio/datree/pkg/ciContext"
"github.com/datreeio/datree/pkg/cliClient"
"github.com/pkg/errors"
......@@ -25,10 +27,8 @@ import (
)
type Evaluator interface {
Evaluate(filesConfigurations []*extractor.FileConfigurations, evaluationResponse *cliClient.CreateEvaluationResponse, isInteractiveMode bool) (evaluation.ResultType, error)
CreateEvaluation(cliId string, cliVersion string, k8sVersion string, policyName string, ciContext *ciContext.CIContext) (*cliClient.CreateEvaluationResponse, error)
UpdateFailedYamlValidation(invalidFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error
UpdateFailedK8sValidation(invalidFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error
Evaluate(policyCheckData evaluation.PolicyCheckData) (evaluation.PolicyCheckResultData, error)
SendEvaluationResult(evaluationRequestData evaluation.EvaluationRequestData) (*cliClient.SendEvaluationResultsResponse, error)
}
type Messager interface {
......@@ -104,12 +104,16 @@ type LocalConfig interface {
var ViolationsFoundError = errors.New("")
type TestCommandOptions struct {
type CliClient interface {
RequestEvaluationPrerunData(token string) (*cliClient.EvaluationPrerunDataResponse, error)
}
type TestCommandData struct {
Output string
K8sVersion string
IgnoreMissingSchemas bool
OnlyK8sFiles bool
PolicyName string
Policy policy.Policy
SchemaLocations []string
Token string
}
......@@ -122,6 +126,7 @@ type TestCommandContext struct {
K8sValidator K8sValidator
Printer EvaluationPrinter
Reader Reader
CliClient CliClient
}
func LoadVersionMessages(ctx *TestCommandContext, args []string, cmd *cobra.Command) error {
......@@ -190,7 +195,16 @@ func New(ctx *TestCommandContext) *cobra.Command {
return err
}
testCommandOptions := GenerateTestCommandOptions(testCommandFlags, localConfigContent)
evaluationPrerunData, err := ctx.CliClient.RequestEvaluationPrerunData(localConfigContent.CliId)
if err != nil {
return err
}
testCommandOptions, err := GenerateTestCommandData(testCommandFlags, localConfigContent, evaluationPrerunData)
if err != nil {
return err
}
err = Test(ctx, args, testCommandOptions)
if err != nil {
......@@ -219,22 +233,30 @@ func (flags *TestCommandFlags) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVarP(&flags.IgnoreMissingSchemas, "ignore-missing-schemas", "", false, "Ignore missing schemas when executing schema validation step")
}
func GenerateTestCommandOptions(testCommandFlags *TestCommandFlags, localConfigContent *localConfig.ConfigContent) *TestCommandOptions {
func GenerateTestCommandData(testCommandFlags *TestCommandFlags, localConfigContent *localConfig.ConfigContent, evaluationPrerunDataResp *cliClient.EvaluationPrerunDataResponse) (*TestCommandData, error) {
k8sVersion := testCommandFlags.K8sVersion
if k8sVersion == "" {
k8sVersion = localConfigContent.SchemaVersion
}
if k8sVersion == "" {
k8sVersion = evaluationPrerunDataResp.DefaultK8sVersion
}
testCommandOptions := &TestCommandOptions{Output: testCommandFlags.Output,
policy, err := policy_factory.CreatePolicy(evaluationPrerunDataResp.PoliciesJson, testCommandFlags.PolicyName)
if err != nil {
return nil, err
}
testCommandOptions := &TestCommandData{Output: testCommandFlags.Output,
K8sVersion: k8sVersion,
IgnoreMissingSchemas: testCommandFlags.IgnoreMissingSchemas,
OnlyK8sFiles: testCommandFlags.OnlyK8sFiles,
PolicyName: testCommandFlags.PolicyName,
Policy: policy,
SchemaLocations: testCommandFlags.SchemaLocations,
Token: localConfigContent.CliId,
}
return testCommandOptions
return testCommandOptions, nil
}
func validateK8sVersionFormatIfProvided(k8sVersion string) error {
......@@ -252,7 +274,7 @@ func validateK8sVersionFormatIfProvided(k8sVersion string) error {
}
}
func Test(ctx *TestCommandContext, paths []string, options *TestCommandOptions) error {
func Test(ctx *TestCommandContext, paths []string, prerunData *TestCommandData) error {
if paths[0] == "-" {
if len(paths) > 1 {
......@@ -280,35 +302,38 @@ func Test(ctx *TestCommandContext, paths []string, options *TestCommandOptions)
return noFilesErr
}
if options.Output == "simple" {
if prerunData.Output == "simple" {
ctx.Printer.SetTheme(printer.CreateSimpleTheme())
}
validationManager, createEvaluationResponse, results, err := evaluate(ctx, filesPaths, options)
evaluationResultData, err := evaluate(ctx, filesPaths, prerunData)
if err != nil {
return err
}
results := evaluationResultData.FormattedResults
passedPolicyCheckCount := 0
if results.EvaluationResults != nil {
passedPolicyCheckCount = results.EvaluationResults.Summary.TotalPassedCount
}
validationManager := evaluationResultData.ValidationManager
passedYamlValidationCount := filesPathsLen - validationManager.InvalidYamlFilesCount()
evaluationSummary := printer.EvaluationSummary{
FilesCount: filesPathsLen,
RulesCount: createEvaluationResponse.RulesCount,
RulesCount: evaluationResultData.RulesCount,
PassedYamlValidationCount: passedYamlValidationCount,
PassedK8sValidationCount: validationManager.ValidK8sFilesConfigurationsCount(),
ConfigsCount: validationManager.ValidK8sConfigurationsCount(),
PassedPolicyCheckCount: passedPolicyCheckCount,
}
err = evaluation.PrintResults(results, validationManager.InvalidYamlFiles(), validationManager.InvalidK8sFiles(), evaluationSummary, fmt.Sprintf("https://app.datree.io/login?cliId=%s", options.Token), options.Output, ctx.Printer, createEvaluationResponse.K8sVersion, createEvaluationResponse.PolicyName)
err = evaluation.PrintResults(results, validationManager.InvalidYamlFiles(), validationManager.InvalidK8sFiles(), evaluationSummary, fmt.Sprintf("https://app.datree.io/login?cliId=%s", prerunData.Token), prerunData.Output, ctx.Printer, prerunData.K8sVersion, prerunData.Policy.Name)
if len(createEvaluationResponse.PromptMessage) > 0 {
ctx.Printer.PrintPromptMessage(createEvaluationResponse.PromptMessage)
if evaluationResultData.PromptMessage != "" {
ctx.Printer.PrintPromptMessage(evaluationResultData.PromptMessage)
answer, _, err := keyboard.GetSingleKey()
if err != nil {
......@@ -317,7 +342,7 @@ func Test(ctx *TestCommandContext, paths []string, options *TestCommandOptions)
}
if strings.ToLower(string(answer)) != "n" {
promptLoginUrl := fmt.Sprintf("https://app.datree.io/promptLogin?cliId=%s", options.Token)
promptLoginUrl := fmt.Sprintf("https://app.datree.io/promptLogin?cliId=%s", prerunData.Token)
openBrowser(promptLoginUrl)
}
}
......@@ -333,11 +358,18 @@ func Test(ctx *TestCommandContext, paths []string, options *TestCommandOptions)
return nil
}
func evaluate(ctx *TestCommandContext, filesPaths []string, options *TestCommandOptions) (*ValidationManager, *cliClient.CreateEvaluationResponse, evaluation.ResultType, error) {
isInteractiveMode := (options.Output != "json") && (options.Output != "yaml") && (options.Output != "xml")
type EvaluationResultData struct {
ValidationManager *ValidationManager
RulesCount int
FormattedResults evaluation.FormattedResults
PromptMessage string
}
func evaluate(ctx *TestCommandContext, filesPaths []string, prerunData *TestCommandData) (EvaluationResultData, error) {
isInteractiveMode := (prerunData.Output != "json") && (prerunData.Output != "yaml") && (prerunData.Output != "xml")
var _spinner *spinner.Spinner
if isInteractiveMode && options.Output != "simple" {
if isInteractiveMode && prerunData.Output != "simple" {
_spinner = createSpinner(" Loading...", "cyan")
_spinner.Start()
}
......@@ -349,16 +381,8 @@ func evaluate(ctx *TestCommandContext, filesPaths []string, options *TestCommand
}()
validationManager := &ValidationManager{}
filesPathsLen := len(filesPaths)
ciContext := ciContext.Extract()
createEvaluationResponse, err := ctx.Evaluator.CreateEvaluation(options.Token, ctx.CliVersion, options.K8sVersion, options.PolicyName, ciContext)
if err != nil {
return validationManager, nil, evaluation.ResultType{}, err
}
ctx.K8sValidator.InitClient(createEvaluationResponse.K8sVersion, options.IgnoreMissingSchemas, options.SchemaLocations)
ctx.K8sValidator.InitClient(prerunData.K8sVersion, prerunData.IgnoreMissingSchemas, prerunData.SchemaLocations)
concurrency := 100
......@@ -366,41 +390,78 @@ func evaluate(ctx *TestCommandContext, filesPaths []string, options *TestCommand
validationManager.AggregateInvalidYamlFiles(invalidYamlFilesChan)
if options.OnlyK8sFiles {
if prerunData.OnlyK8sFiles {
var ignoredYamlFilesChan chan *extractor.FileConfigurations
validYamlConfigurationsChan, ignoredYamlFilesChan = ctx.K8sValidator.GetK8sFiles(validYamlConfigurationsChan, concurrency)
validationManager.AggregateIgnoredYamlFiles(ignoredYamlFilesChan)
}
filesPathsLen = filesPathsLen - validationManager.InvalidYamlFilesCount() - validationManager.IgnoredFilesCount()
validK8sFilesConfigurationsChan, invalidK8sFilesChan := ctx.K8sValidator.ValidateResources(validYamlConfigurationsChan, concurrency)
validationManager.AggregateInvalidK8sFiles(invalidK8sFilesChan)
validationManager.AggregateValidK8sFiles(validK8sFilesConfigurationsChan)
policyName := prerunData.Policy.Name
policyCheckData := evaluation.PolicyCheckData{
FilesConfigurations: validationManager.ValidK8sFilesConfigurations(),
IsInteractiveMode: isInteractiveMode,
PolicyName: policyName,
Policy: prerunData.Policy,
}
noValidYamlFiles := validationManager.InvalidYamlFilesCount()+validationManager.IgnoredFilesCount() == filesPathsLen
emptyEvaluationResultData := EvaluationResultData{nil, 0, evaluation.FormattedResults{}, ""}
policyCheckResultData, err := ctx.Evaluator.Evaluate(policyCheckData)
if err != nil {
return emptyEvaluationResultData, err
}
var failedYamlFiles []string
if validationManager.InvalidYamlFilesCount() > 0 {
err = ctx.Evaluator.UpdateFailedYamlValidation(validationManager.InvalidYamlFiles(), createEvaluationResponse.EvaluationId, noValidYamlFiles)
if err != nil {
return validationManager, createEvaluationResponse, evaluation.ResultType{}, err
for _, invalidYamlFile := range validationManager.InvalidYamlFiles() {
failedYamlFiles = append(failedYamlFiles, invalidYamlFile.Path)
}
}
validK8sFilesConfigurationsChan, invalidK8sFilesChan := ctx.K8sValidator.ValidateResources(validYamlConfigurationsChan, concurrency)
validationManager.AggregateInvalidK8sFiles(invalidK8sFilesChan)
noValidK8sFiles := validationManager.InvalidYamlFilesCount()+validationManager.InvalidK8sFilesCount()+validationManager.IgnoredFilesCount() == filesPathsLen
var failedK8sFiles []string
if validationManager.InvalidK8sFilesCount() > 0 {
err = ctx.Evaluator.UpdateFailedK8sValidation(validationManager.InvalidK8sFiles(), createEvaluationResponse.EvaluationId, noValidK8sFiles)
if err != nil {
return validationManager, createEvaluationResponse, evaluation.ResultType{}, err
for _, invalidK8sFile := range validationManager.InvalidK8sFiles() {
failedK8sFiles = append(failedK8sFiles, invalidK8sFile.Path)
}
}
validationManager.AggregateValidK8sFiles(validK8sFilesConfigurationsChan)
ciContext := ciContext.Extract()
results, err := ctx.Evaluator.Evaluate(validationManager.ValidK8sFilesConfigurations(), createEvaluationResponse, isInteractiveMode)
evaluationRequestData := evaluation.EvaluationRequestData{
CliId: prerunData.Token,
CliVersion: ctx.CliVersion,
K8sVersion: prerunData.K8sVersion,
PolicyName: policyName,
CiContext: ciContext,
RulesData: policyCheckResultData.RulesData,
FilesData: policyCheckResultData.FilesData,
FailedYamlFiles: failedYamlFiles,
FailedK8sFiles: failedK8sFiles,
PolicyCheckResults: policyCheckResultData.RawResults,
}
sendEvaluationResultsResponse, err := ctx.Evaluator.SendEvaluationResult(evaluationRequestData)
if err != nil {
return emptyEvaluationResultData, err
}
evaluationResultData := EvaluationResultData{
ValidationManager: validationManager,
RulesCount: policyCheckResultData.RulesCount,
FormattedResults: policyCheckResultData.FormattedResults,
PromptMessage: sendEvaluationResultsResponse.PromptMessage,
}
return validationManager, createEvaluationResponse, results, err
return evaluationResultData, err
}
func wereViolationsFound(validationManager *ValidationManager, results *evaluation.ResultType) bool {
func wereViolationsFound(validationManager *ValidationManager, results *evaluation.FormattedResults) bool {
return (validationManager.InvalidYamlFilesCount() > 0 || validationManager.InvalidK8sFilesCount() > 0 || results.EvaluationResults.Summary.TotalFailedRules > 0)
}
package test
import (
"encoding/json"
"testing"
"github.com/datreeio/datree/pkg/ciContext"
policy_factory "github.com/datreeio/datree/bl/policy"
"github.com/datreeio/datree/pkg/fileReader"
"github.com/datreeio/datree/pkg/cliClient"
"github.com/datreeio/datree/pkg/extractor"
"github.com/datreeio/datree/pkg/printer"
......@@ -19,24 +22,19 @@ type mockEvaluator struct {
mock.Mock
}
func (m *mockEvaluator) Evaluate(filesConfigurationsChan []*extractor.FileConfigurations, evaluationResponse *cliClient.CreateEvaluationResponse, isInteractiveMode bool) (evaluation.ResultType, error) {
args := m.Called(filesConfigurationsChan, evaluationResponse, isInteractiveMode)
return args.Get(0).(evaluation.ResultType), args.Error(1)
}
func (m *mockEvaluator) CreateEvaluation(cliId string, cliVersion string, k8sVersion string, policyName string, ciContext *ciContext.CIContext) (*cliClient.CreateEvaluationResponse, error) {
args := m.Called(cliId, cliVersion, k8sVersion, policyName)
return args.Get(0).(*cliClient.CreateEvaluationResponse), args.Error(1)
func (m *mockEvaluator) Evaluate(evaluationData evaluation.PolicyCheckData) (evaluation.PolicyCheckResultData, error) {
args := m.Called(evaluationData)
return args.Get(0).(evaluation.PolicyCheckResultData), args.Error(1)
}
func (m *mockEvaluator) UpdateFailedYamlValidation(invalidYamlFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
args := m.Called(invalidYamlFiles, evaluationId, stopEvaluation)
return args.Error(0)
func (m *mockEvaluator) SendEvaluationResult(evaluationRequestData evaluation.EvaluationRequestData) (*cliClient.SendEvaluationResultsResponse, error) {
args := m.Called(evaluationRequestData)
return args.Get(0).(*cliClient.SendEvaluationResultsResponse), args.Error(1)
}
func (m *mockEvaluator) UpdateFailedK8sValidation(invalidK8sFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
args := m.Called(invalidK8sFiles, evaluationId, stopEvaluation)
return args.Error(0)
func (m *mockEvaluator) RequestEvaluationPrerunData(token string) (*cliClient.EvaluationPrerunDataResponse, int, error) {
args := m.Called(token)
return args.Get(0).(*cliClient.EvaluationPrerunDataResponse), args.Get(1).(int), args.Error(2)
}
type mockMessager struct {
......@@ -126,22 +124,37 @@ func (lc *LocalConfigMock) GetLocalConfiguration() (*localConfig.ConfigContent,
func TestTestCommand(t *testing.T) {
evaluationId := 444
evaluationResponse := cliClient.CreateEvaluationResponse{EvaluationId: evaluationId, K8sVersion: "1.18.0", RulesCount: 21, PolicyName: "Default"}
resultType := evaluation.ResultType{}
resultType.EvaluationResults = &evaluation.EvaluationResults{
FileNameRuleMapper: map[string]map[int]*evaluation.Rule{}, Summary: struct {
prerunData := mockGetPreRunData()
formattedResults := evaluation.FormattedResults{}
policyCheckResultData := evaluation.PolicyCheckResultData{
FormattedResults: formattedResults,
RulesData: []cliClient.RuleData{},
FilesData: []cliClient.FileData{},
RawResults: nil,
RulesCount: 0,
}
formattedResults.EvaluationResults = &evaluation.EvaluationResults{
FileNameRuleMapper: map[string]map[string]*evaluation.Rule{}, Summary: struct {
TotalFailedRules int
FilesCount int
TotalPassedCount int
}{TotalFailedRules: 0, FilesCount: 0, TotalPassedCount: 1},
}
sendEvaluationResultsResponse := &cliClient.SendEvaluationResultsResponse{
EvaluationId: 1,
PromptMessage: "",
}
mockedEvaluator := &mockEvaluator{}
mockedEvaluator.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return(resultType, nil)
mockedEvaluator.On("CreateEvaluation", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&cliClient.CreateEvaluationResponse{EvaluationId: evaluationId, K8sVersion: "1.18.0", RulesCount: 21}, nil)
mockedEvaluator.On("UpdateFailedYamlValidation", mock.Anything, mock.Anything, mock.Anything).Return(nil)
mockedEvaluator.On("UpdateFailedK8sValidation", mock.Anything, mock.Anything, mock.Anything).Return(nil)
mockedEvaluator.On("Evaluate", mock.Anything).Return(policyCheckResultData, nil)
mockedEvaluator.On("SendEvaluationResult", mock.Anything).Return(sendEvaluationResultsResponse, nil)
mockedEvaluator.On("RequestEvaluationPrerunData", mock.Anything).Return(prerunData, nil)
messager := &mockMessager{}
messager.On("LoadVersionMessages", mock.Anything)
......@@ -151,10 +164,10 @@ func TestTestCommand(t *testing.T) {
filesConfigurationsChan := newFilesConfigurationsChan(path)
filesConfigurations := newFilesConfigurations(path)
invelidK8sFilesChan := newInvalidK8sFilesChan()
invalidK8sFilesChan := newInvalidK8sFilesChan()
ignoredFilesChan := newIgnoredYamlFilesChan()
k8sValidatorMock.On("ValidateResources", mock.Anything, mock.Anything).Return(filesConfigurationsChan, invelidK8sFilesChan, newErrorsChan())
k8sValidatorMock.On("ValidateResources", mock.Anything, mock.Anything).Return(filesConfigurationsChan, invalidK8sFilesChan, newErrorsChan())
k8sValidatorMock.On("GetK8sFiles", mock.Anything, mock.Anything).Return(filesConfigurationsChan, ignoredFilesChan, newErrorsChan())
k8sValidatorMock.On("InitClient", mock.Anything, mock.Anything, mock.Anything).Return()
......@@ -181,11 +194,13 @@ func TestTestCommand(t *testing.T) {
Reader: readerMock,
}
policy, _ := policy_factory.CreatePolicy(prerunData.PoliciesJson, "")
test_testCommand_flags_validation(t, ctx)
test_testCommand_no_flags(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, &evaluationResponse, ctx)
test_testCommand_json_output(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, &evaluationResponse, ctx)
test_testCommand_yaml_output(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, &evaluationResponse, ctx)
test_testCommand_xml_output(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, &evaluationResponse, ctx)
test_testCommand_no_flags(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, ctx, policy)
test_testCommand_json_output(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, ctx, policy)
test_testCommand_yaml_output(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, ctx, policy)
test_testCommand_xml_output(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, ctx, policy)
test_testCommand_only_k8s_files(t, k8sValidatorMock, filesConfigurations, evaluationId, ctx)
}
......@@ -242,37 +257,64 @@ func test_testCommand_version_flags_validation(t *testing.T, ctx *TestCommandCon
assert.NoError(t, err)
}
func test_testCommand_no_flags(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, evaluationResponse *cliClient.CreateEvaluationResponse, ctx *TestCommandContext) {
_ = Test(ctx, []string{"8/*"}, &TestCommandOptions{K8sVersion: "1.18.0", Output: "", PolicyName: "Default", Token: "134kh"})
func test_testCommand_no_flags(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, ctx *TestCommandContext, policy policy_factory.Policy) {
_ = Test(ctx, []string{"8/*"}, &TestCommandData{K8sVersion: "1.18.0", Output: "", Policy: policy, Token: "134kh"})
policyCheckData := evaluation.PolicyCheckData{
FilesConfigurations: filesConfigurations,
IsInteractiveMode: true,
PolicyName: policy.Name,
Policy: policy,
}
k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
evaluator.AssertCalled(t, "CreateEvaluation", "134kh", "", "1.18.0", "Default")
evaluator.AssertCalled(t, "Evaluate", filesConfigurations, mock.Anything, mock.Anything)
evaluator.AssertCalled(t, "Evaluate", policyCheckData)
}
func test_testCommand_json_output(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, evaluationResponse *cliClient.CreateEvaluationResponse, ctx *TestCommandContext) {
_ = Test(ctx, []string{"8/*"}, &TestCommandOptions{Output: "json"})
func test_testCommand_json_output(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, ctx *TestCommandContext, policy policy_factory.Policy) {
_ = Test(ctx, []string{"8/*"}, &TestCommandData{Output: "json"})
policyCheckData := evaluation.PolicyCheckData{
FilesConfigurations: filesConfigurations,
IsInteractiveMode: true,
PolicyName: policy.Name,
Policy: policy,
}
k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
evaluator.AssertCalled(t, "Evaluate", filesConfigurations, mock.Anything, mock.Anything)
evaluator.AssertCalled(t, "Evaluate", policyCheckData)
}
func test_testCommand_yaml_output(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, evaluationResponse *cliClient.CreateEvaluationResponse, ctx *TestCommandContext) {
_ = Test(ctx, []string{"8/*"}, &TestCommandOptions{Output: "yaml"})
func test_testCommand_yaml_output(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, ctx *TestCommandContext, policy policy_factory.Policy) {
_ = Test(ctx, []string{"8/*"}, &TestCommandData{Output: "yaml"})
policyCheckData := evaluation.PolicyCheckData{
FilesConfigurations: filesConfigurations,
IsInteractiveMode: true,
PolicyName: policy.Name,
Policy: policy,
}
k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
evaluator.AssertCalled(t, "Evaluate", filesConfigurations, mock.Anything, mock.Anything)
evaluator.AssertCalled(t, "Evaluate", policyCheckData)
}
func test_testCommand_xml_output(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, evaluationResponse *cliClient.CreateEvaluationResponse, ctx *TestCommandContext) {
_ = Test(ctx, []string{"8/*"}, &TestCommandOptions{Output: "xml"})
func test_testCommand_xml_output(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, ctx *TestCommandContext, policy policy_factory.Policy) {
_ = Test(ctx, []string{"8/*"}, &TestCommandData{Output: "xml"})
policyCheckData := evaluation.PolicyCheckData{
FilesConfigurations: filesConfigurations,
IsInteractiveMode: true,
PolicyName: policy.Name,
Policy: policy,
}
k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
evaluator.AssertCalled(t, "Evaluate", filesConfigurations, mock.Anything, mock.Anything)
evaluator.AssertCalled(t, "Evaluate", policyCheckData)
}
func test_testCommand_only_k8s_files(t *testing.T, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, evaluationId int, ctx *TestCommandContext) {
_ = Test(ctx, []string{"8/*"}, &TestCommandOptions{OnlyK8sFiles: true})
_ = Test(ctx, []string{"8/*"}, &TestCommandData{OnlyK8sFiles: true})
k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
k8sValidator.AssertCalled(t, "GetK8sFiles", mock.Anything, 100)
......@@ -335,3 +377,24 @@ func newErrorsChan() chan error {
close(invalidFilesChan)
return invalidFilesChan
}
func mockGetPreRunData() *cliClient.EvaluationPrerunDataResponse {
const policiesJsonPath = "../../internal/fixtures/policyAsCode/policies.json"
fileReader := fileReader.CreateFileReader(nil)
policiesJsonStr, err := fileReader.ReadFileContent(policiesJsonPath)
if err != nil {
panic(err)
}
policiesJsonRawData := []byte(policiesJsonStr)
var policiesJson *cliClient.EvaluationPrerunDataResponse
err = json.Unmarshal(policiesJsonRawData, &policiesJson)
if err != nil {
panic(err)
}
return policiesJson
}
{
"defaultK8sVersion": "1.18.0",
"policiesJson": {
"apiVersion": "v1",
"customRules": [
{
"identifier": "CUSTOM_WORKLOAD_INVALID_LABELS_VALUE",
"name": "Ensure workload has valid label values [CUSTOM RULE]",
"defaultMessageOnFailure": "All lables values must follow the RFC 1123 hostname standard (https://knowledge.broadcom.com/external/article/49542/restrictions-on-valid-host-names.html)",
"schema": {
"properties": {
"metadata": {
"properties": {
"labels": {
"additionalProperties": false,
"patternProperties": {
"^.*$": {
"format": "hostname"
}
}
}
}
}
}
}
}
],
"policies": [
{
"name": "labels_best_practices",
"isDefault": true,
"rules": [
{
"identifier": "WORKLOAD_INCORRECT_NAMESPACE_VALUE_DEFAULT",
"messageOnFailure": "Incorrect value for key `namespace` - use an explicit namespace instead of the default one (`default`)"
},
{
"identifier": "CONTAINERS_INCORRECT_PRIVILEGED_VALUE_TRUE",
"messageOnFailure": "Incorrect value for key `privileged` - this mode will allow the container thenhjgjgj same access as processes running on the host"
},
{
"identifier": "CUSTOM_WORKLOAD_INVALID_LABELS_VALUE",
"messageOnFailure": "All lables values must follow the RFC 1123 hostname standard (https://knowledge.broadcom.com/external/article/49542/restrictions-on-valid-host-names.html)"
}
]
},
{
"name": "labels_best_practices2",
"rules": [
{
"identifier": "CUSTOM_WORKLOAD_INVALID_LABELS_VALUE",
"messageOnFailure": "All lables values must follow the RFC 1123 hostname standard (https://knowledge.broadcom.com/external/article/49542/restrictions-on-valid-host-names.html)"
}
]
}
]
},
"accountExists": true
}
......@@ -9,6 +9,10 @@ import (
"path/filepath"
"testing"
"github.com/datreeio/datree/pkg/ciContext"
"github.com/datreeio/datree/pkg/fileReader"
"github.com/datreeio/datree/bl/files"
"gopkg.in/yaml.v3"
......@@ -33,47 +37,49 @@ func (c *mockHTTPClient) name() {
}
type RequestEvaluationTestCase struct {
type RequestEvaluationPrerunDataTestCase struct {
name string
args struct {
evaluationRequest *EvaluationRequest
token string
}
mock struct {
response struct {
status int
body *EvaluationResponse
body *EvaluationPrerunDataResponse
error error
}
}
expected struct {
request struct {
method string
uri string
body *EvaluationRequest
body interface{}
headers map[string]string
}
response *EvaluationResponse
responseErr error
response *EvaluationPrerunDataResponse
}
}
type CreateEvaluationTestCase struct {
type SendEvaluationResultTestCase struct {
name string
args struct {
createEvaluationRequest *CreateEvaluationRequest
evaluationRequestData *EvaluationResultRequest
}
mock struct {
response struct {
status int
body *CreateEvaluationResponse
body *SendEvaluationResultsResponse
}
}
expected struct {
request struct {
method string
uri string
body *CreateEvaluationRequest
body interface{}
headers map[string]string
}
response *CreateEvaluationResponse
response *SendEvaluationResultsResponse
}
}
......@@ -123,9 +129,9 @@ type PublishPoliciesTestCase struct {
}
}
func TestRequestEvaluation(t *testing.T) {
tests := []*RequestEvaluationTestCase{
test_requestEvaluation_success(),
func TestRequestEvaluationPrerunDataSuccess(t *testing.T) {
tests := []*RequestEvaluationPrerunDataTestCase{
test_requestEvaluationPrerunData_success(),
}
httpClientMock := mockHTTPClient{}
......@@ -134,25 +140,50 @@ func TestRequestEvaluation(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
body, _ := json.Marshal(tt.mock.response.body)
mockedHTTPResponse := httpClient.Response{StatusCode: tt.mock.response.status, Body: body}
httpClientMock.On("Request", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedHTTPResponse, nil)
httpClientMock.On("Request", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedHTTPResponse, tt.mock.response.error).Once()
client := &CliClient{
baseUrl: "http://cli-service.test.io",
httpClient: &httpClientMock,
}
res, _ := client.RequestEvaluation(tt.args.evaluationRequest)
policyCheckData, _ := client.RequestEvaluationPrerunData(tt.args.token)
httpClientMock.AssertCalled(t, "Request", tt.expected.request.method, tt.expected.request.uri, tt.expected.request.body, tt.expected.request.headers)
assert.Equal(t, tt.expected.response, res)
assert.Equal(t, tt.expected.response, policyCheckData)
})
}
}
func TestRequestEvaluationPrerunDataFail(t *testing.T) {
tests := []*RequestEvaluationPrerunDataTestCase{
test_requestEvaluationPrerunData_error(),
}
httpClientMock := mockHTTPClient{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body, _ := json.Marshal(tt.mock.response.body)
mockedHTTPResponse := httpClient.Response{StatusCode: tt.mock.response.status, Body: body}
httpClientMock.On("Request", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedHTTPResponse, tt.mock.response.error).Once()
client := &CliClient{
baseUrl: "http://cli-service.test.io",
httpClient: &httpClientMock,
}
_, err := client.RequestEvaluationPrerunData(tt.args.token)
httpClientMock.AssertCalled(t, "Request", tt.expected.request.method, tt.expected.request.uri, tt.expected.request.body, tt.expected.request.headers)
assert.Equal(t, tt.expected.responseErr, err)
})
}
}
func TestCreateRequestEvaluation(t *testing.T) {
tests := []*CreateEvaluationTestCase{
test_createEvaluation_success(),
func TestSendEvaluationResult(t *testing.T) {
tests := []*SendEvaluationResultTestCase{
test_sendEvaluationResult_success(),
}
httpClientMock := mockHTTPClient{}
......@@ -161,17 +192,17 @@ func TestCreateRequestEvaluation(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
body, _ := json.Marshal(tt.mock.response.body)
mockedHTTPResponse := httpClient.Response{StatusCode: tt.mock.response.status, Body: body}
httpClientMock.On("Request", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedHTTPResponse, nil)
httpClientMock.On("Request", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedHTTPResponse, nil).Once()
client := &CliClient{
baseUrl: "http://cli-service.test.io",
httpClient: &httpClientMock,
}
res, _ := client.CreateEvaluation(tt.args.createEvaluationRequest)
sendEvaluationResultsResponse, _ := client.SendEvaluationResult(tt.args.evaluationRequestData)
httpClientMock.AssertCalled(t, "Request", tt.expected.request.method, tt.expected.request.uri, tt.expected.request.body, tt.expected.request.headers)
assert.Equal(t, tt.expected.response, res)
assert.Equal(t, tt.expected.response, sendEvaluationResultsResponse)
})
}
}
......@@ -310,93 +341,179 @@ func test_getVersionMessage_success() *GetVersionMessageTestCase {
}
}
func test_requestEvaluation_success() *RequestEvaluationTestCase {
return &RequestEvaluationTestCase{
name: "success - request evaluation",
func mockGetPreRunData() *EvaluationPrerunDataResponse {
const policiesJsonPath = "../../internal/fixtures/policyAsCode/policies.json"
fileReader := fileReader.CreateFileReader(nil)
policiesJsonStr, err := fileReader.ReadFileContent(policiesJsonPath)
if err != nil {
panic(err)
}
policiesJsonRawData := []byte(policiesJsonStr)
var policiesJson *EvaluationPrerunDataResponse
err = json.Unmarshal(policiesJsonRawData, &policiesJson)
if err != nil {
panic(err)
}
return policiesJson
}
func test_requestEvaluationPrerunData_success() *RequestEvaluationPrerunDataTestCase {
preRunData := mockGetPreRunData()
return &RequestEvaluationPrerunDataTestCase{
name: "success - get prerun data for evaluation",
args: struct {
evaluationRequest *EvaluationRequest
token string
}{
evaluationRequest: &EvaluationRequest{
EvaluationId: 321,
Files: castPropertiesMock("service_mock", "mocks/service_mock.yaml"),
},
token: "internal_test_token",
},
mock: struct {
response struct {
status int
body *EvaluationResponse
body *EvaluationPrerunDataResponse
error error
}
}{
response: struct {
status int
body *EvaluationResponse
body *EvaluationPrerunDataResponse
error error
}{
status: http.StatusOK,
body: &EvaluationResponse{},
body: preRunData,
},
},
expected: struct {
request struct {
method string
uri string
body *EvaluationRequest
body interface{}
headers map[string]string
}
response *EvaluationResponse
responseErr error
response *EvaluationPrerunDataResponse
}{
request: struct {
method string
uri string
body *EvaluationRequest
body interface{}
headers map[string]string
}{
method: http.MethodPost,
uri: "/cli/evaluate",
body: &EvaluationRequest{
EvaluationId: 321,
Files: castPropertiesMock("service_mock", "mocks/service_mock.yaml"),
},
method: http.MethodGet,
uri: "/cli/evaluation/tokens/internal_test_token/prerun",
body: nil,
headers: nil,
},
response: &EvaluationResponse{},
response: preRunData,
},
}
}
func test_createEvaluation_success() *CreateEvaluationTestCase {
k8sVersion := "1.18.0"
func test_requestEvaluationPrerunData_error() *RequestEvaluationPrerunDataTestCase {
preRunData := mockGetPreRunData()
return &CreateEvaluationTestCase{
name: "success - create evaluation",
return &RequestEvaluationPrerunDataTestCase{
name: "success - get prerun data for evaluation",
args: struct {
createEvaluationRequest *CreateEvaluationRequest
token string
}{
token: "internal_test_token",
},
mock: struct {
response struct {
status int
body *EvaluationPrerunDataResponse
error error
}
}{
response: struct {
status int
body *EvaluationPrerunDataResponse
error error
}{
status: http.StatusBadRequest,
body: preRunData,
error: errors.New("error from cli-service"),
},
},
expected: struct {
request struct {
method string
uri string
body interface{}
headers map[string]string
}
responseErr error
response *EvaluationPrerunDataResponse
}{
createEvaluationRequest: &CreateEvaluationRequest{
K8sVersion: &k8sVersion,
CliId: "cli_id",
PolicyName: "Default",
Metadata: &Metadata{
CliVersion: "0.0.1",
Os: "darwin",
PlatformVersion: "1.2.3",
KernelVersion: "4.5.6",
request: struct {
method string
uri string
body interface{}
headers map[string]string
}{
method: http.MethodGet,
uri: "/cli/evaluation/tokens/internal_test_token/prerun",
body: nil,
headers: nil,
},
responseErr: errors.New("error from cli-service"),
response: preRunData,
},
}
}
func test_sendEvaluationResult_success() *SendEvaluationResultTestCase {
body := &EvaluationResultRequest{
ClientId: "internal_cliId_test",
Token: "internal_cliId_test",
Metadata: &Metadata{
CliVersion: "0.0.01",
Os: "darwin",
PlatformVersion: "1.2.3",
KernelVersion: "4.5.6",
CIContext: &ciContext.CIContext{
IsCI: true,
CIMetadata: &ciContext.CIMetadata{
CIEnvValue: "travis",
ShouldHideEmojis: false,
},
},
},
K8sVersion: "1.18.0",
PolicyName: "Default",
FailedYamlFiles: []string{},
FailedK8sFiles: []string{},
AllExecutedRules: []RuleData{},
AllEvaluatedFiles: []FileData{},
PolicyCheckResults: nil,
}
return &SendEvaluationResultTestCase{
name: "success - send local evaluation result to server",
args: struct {
evaluationRequestData *EvaluationResultRequest
}{
evaluationRequestData: body,
},
mock: struct {
response struct {
status int
body *CreateEvaluationResponse
body *SendEvaluationResultsResponse
}
}{
response: struct {
status int
body *CreateEvaluationResponse
body *SendEvaluationResultsResponse
}{
status: http.StatusOK,
body: &CreateEvaluationResponse{
EvaluationId: 123,
K8sVersion: k8sVersion,
body: &SendEvaluationResultsResponse{
EvaluationId: 1234,
PromptMessage: "",
},
},
},
......@@ -404,35 +521,25 @@ func test_createEvaluation_success() *CreateEvaluationTestCase {
request struct {
method string
uri string
body *CreateEvaluationRequest
body interface{}
headers map[string]string
}
response *CreateEvaluationResponse
response *SendEvaluationResultsResponse
}{
request: struct {
method string
uri string
body *CreateEvaluationRequest
body interface{}
headers map[string]string
}{
method: http.MethodPost,
uri: "/cli/evaluation/create",
body: &CreateEvaluationRequest{
K8sVersion: &k8sVersion,
CliId: "cli_id",
PolicyName: "Default",
Metadata: &Metadata{
CliVersion: "0.0.1",
Os: "darwin",
PlatformVersion: "1.2.3",
KernelVersion: "4.5.6",
},
},
method: http.MethodPost,
uri: "/cli/evaluation/result",
body: body,
headers: nil,
},
response: &CreateEvaluationResponse{
EvaluationId: 123,
K8sVersion: k8sVersion,
response: &SendEvaluationResultsResponse{
EvaluationId: 1234,
PromptMessage: "",
},
},
}
......
......@@ -16,34 +16,9 @@ type Metadata struct {
CIContext *ciContext.CIContext `json:"ciContext"`
}
type CreateEvaluationRequest struct {
CliId string `json:"cliId"`
Metadata *Metadata `json:"metadata"`
K8sVersion *string `json:"k8sVersion"`
PolicyName string `json:"policyName"`
}
type CreateEvaluationResponse struct {
type SendEvaluationResultsResponse struct {
EvaluationId int `json:"evaluationId"`
K8sVersion string `json:"k8sVersion"`
RulesCount int `json:"rulesCount"`
PolicyName string `json:"policyName"`
PromptMessage string `json:"promptMessage"`
}
func (c *CliClient) CreateEvaluation(request *CreateEvaluationRequest) (*CreateEvaluationResponse, error) {
httpRes, err := c.httpClient.Request(http.MethodPost, "/cli/evaluation/create", request, nil)
if err != nil {
return nil, err
}
var res = &CreateEvaluationResponse{}
err = json.Unmarshal(httpRes.Body, &res)
if err != nil {
return nil, err
}
return res, nil
PromptMessage string `json:"promptMessage,omitempty"`
}
type Match struct {
......@@ -91,41 +66,100 @@ type EvaluationRequest struct {
Files []*extractor.FileConfigurations `json:"files"`
}
func (c *CliClient) RequestEvaluation(request *EvaluationRequest) (*EvaluationResponse, error) {
res, err := c.httpClient.Request(http.MethodPost, "/cli/evaluate", request, nil)
if err != nil {
return &EvaluationResponse{}, err
type CustomRule struct {
Identifier string `json:"identifier"`
Name string `json:"name"`
DefaultMessageOnFailure string `json:"defaultMessageOnFailure"`
Schema map[string]interface{} `json:"schema"`
}
type Rule struct {
Identifier string `json:"identifier"`
MessageOnFailure string `json:"messageOnFailure"`
}
type Policy struct {
Name string `json:"name"`
IsDefault bool `json:"isDefault,omitempty"`
Rules []Rule `json:"rules"`
}
type EvaluationPrerunPolicies struct {
ApiVersion string `json:"apiVersion"`
CustomRules []*CustomRule `json:"customRules"`
Policies []*Policy `json:"policies"`
}
type EvaluationPrerunDataResponse struct {
PoliciesJson *EvaluationPrerunPolicies `json:"policiesJson"`
DefaultK8sVersion string `json:"defaultK8sVersion"`
AccountExists bool `json:"accountExists"`
}
const badRequestStatusCode = 400
func (c *CliClient) RequestEvaluationPrerunData(tokenId string) (*EvaluationPrerunDataResponse, error) {
res, err := c.httpClient.Request(http.MethodGet, "/cli/evaluation/tokens/"+tokenId+"/prerun", nil, nil)
if err != nil && res.StatusCode >= badRequestStatusCode {
return &EvaluationPrerunDataResponse{}, err
}
var evaluationResponse = &EvaluationResponse{}
err = json.Unmarshal(res.Body, &evaluationResponse)
var evaluationPrerunDataResponse = &EvaluationPrerunDataResponse{}
err = json.Unmarshal(res.Body, &evaluationPrerunDataResponse)
if err != nil {
return &EvaluationResponse{}, err
return &EvaluationPrerunDataResponse{}, err
}
return evaluationResponse, nil
return evaluationPrerunDataResponse, nil
}
type UpdateEvaluationValidationRequest struct {
EvaluationId int `json:"evaluationId"`
InvalidFiles []*string `json:"failedFiles"`
StopEvaluation bool `json:"stopEvaluation"`
type RuleData struct {
Identifier string `json:"ruleIdentifier"`
Name string `json:"ruleName"`
}
func (c *CliClient) SendFailedYamlValidation(request *UpdateEvaluationValidationRequest) error {
_, err := c.httpClient.Request(http.MethodPost, "/cli/evaluation/validation/yaml", request, nil)
if err != nil {
return err
}
type FileData struct {
FilePath string `json:"filepath"`
ConfigurationsCount int `json:"configurationsCount"`
}
type Configuration struct {
Name string `json:"metadataName"`
Kind string `json:"kind"`
Occurrences int `json:"occurrences"`
}
type FailedRule struct {
Name string `json:"ruleName"`
MessageOnFailure string `json:"messageOnFailure"`
Configurations []Configuration `json:"configurations"`
}
return nil
type EvaluationResultRequest struct {
ClientId string `json:"clientId"`
Token string `json:"token"`
Metadata *Metadata `json:"metadata"`
K8sVersion string `json:"k8sVersion"`
PolicyName string `json:"policyName"`
FailedYamlFiles []string `json:"failedYamlFiles"`
FailedK8sFiles []string `json:"failedK8sFiles"`
AllExecutedRules []RuleData `json:"allExecutedRules"`
AllEvaluatedFiles []FileData `json:"allEvaluatedFiles"`
PolicyCheckResults map[string]map[string]FailedRule `json:"policyCheckResults"`
}
func (c *CliClient) SendFailedK8sValidation(request *UpdateEvaluationValidationRequest) error {
_, err := c.httpClient.Request(http.MethodPost, "/cli/evaluation/validation/k8s", request, nil)
func (c *CliClient) SendEvaluationResult(request *EvaluationResultRequest) (*SendEvaluationResultsResponse, error) {
httpRes, err := c.httpClient.Request(http.MethodPost, "/cli/evaluation/result", request, nil)
if err != nil {
return nil, err
}
var res = &SendEvaluationResultsResponse{}
err = json.Unmarshal(httpRes.Body, &res)
if err != nil {
return err
return nil, err
}
return nil
return res, nil
}
......@@ -8,8 +8,8 @@ import (
const defaultRulesYamlPath = "./pkg/policy/defaultRules.yaml"
type DefaultRulesDefinitions struct {
ApiVersion string `yaml:"apiVersion"`
Rules []DefaultRuleDefinition `yaml:"rules"`
ApiVersion string `yaml:"apiVersion"`
Rules []*DefaultRuleDefinition `yaml:"rules"`
}
type DefaultRuleDefinition struct {
......
......@@ -2,7 +2,7 @@
set -ex
MAJOR_VERSION=0
MINOR_VERSION=15
MINOR_VERSION=16
latestRcTag=$(git tag --sort=-version:refname | grep "^${MAJOR_VERSION}.${MINOR_VERSION}.\d\+\-rc" | head -n 1 | grep --only-matching "^${MAJOR_VERSION}.${MINOR_VERSION}.\d\+" || true)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment