github.com/datreeio/datree@v1.9.22-rc/pkg/jsonSchemaValidator/extensions/customKeyCELDefinition.go (about) 1 // This file defines a custom key to implement the logic for cel rule: 2 3 package jsonSchemaValidator 4 5 import ( 6 "encoding/json" 7 "fmt" 8 9 "github.com/google/cel-go/cel" 10 "github.com/santhosh-tekuri/jsonschema/v5" 11 ) 12 13 const CELDefinitionCustomKey = "CELDefinition" 14 15 type CustomKeyCELDefinitionCompiler struct{} 16 17 type CustomKeyCELDefinitionSchema []interface{} 18 19 var CustomKeyCELRule = jsonschema.MustCompileString("customKeyCELDefinition.json", `{ 20 "properties" : { 21 "CELDefinition": { 22 "type": "array" 23 } 24 } 25 }`) 26 27 func (CustomKeyCELDefinitionCompiler) Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (jsonschema.ExtSchema, error) { 28 if customKeyCELRule, ok := m[CELDefinitionCustomKey]; ok { 29 customKeyCELRuleObj, validObject := customKeyCELRule.([]interface{}) 30 if !validObject { 31 return nil, fmt.Errorf("CELDefinition must be an array") 32 } 33 34 CELDefinitionSchema, err := convertCustomKeyCELDefinitionSchemaToCELDefinitionSchema(customKeyCELRuleObj) 35 if err != nil { 36 return nil, err 37 } 38 39 if len(CELDefinitionSchema.CELExpressions) == 0 { 40 return nil, fmt.Errorf("CELDefinition can't be empty") 41 } 42 43 return CustomKeyCELDefinitionSchema(customKeyCELRuleObj), nil 44 } 45 return nil, nil 46 } 47 48 func (customKeyCELDefinitionSchema CustomKeyCELDefinitionSchema) Validate(ctx jsonschema.ValidationContext, dataValue interface{}) error { 49 CELDefinitionSchema, err := convertCustomKeyCELDefinitionSchemaToCELDefinitionSchema(customKeyCELDefinitionSchema) 50 if err != nil { 51 return ctx.Error(CustomKeyValidationErrorKeyPath, err.Error()) 52 } 53 // wrap dataValue (the resource that should be validated) inside a struct with parent object key 54 resourceWithParentKey := make(map[string]interface{}) 55 resourceWithParentKey["object"] = dataValue 56 57 // prepare CEL env inputs - in our case the only input is the resource that should be validated 58 inputs, err := getCELEnvInputs(resourceWithParentKey) 59 if err != nil { 60 return ctx.Error(CustomKeyValidationErrorKeyPath, err.Error()) 61 } 62 63 env, err := cel.NewEnv(inputs...) 64 if err != nil { 65 return ctx.Error(CustomKeyValidationErrorKeyPath, err.Error()) 66 } 67 68 for _, celExpression := range CELDefinitionSchema.CELExpressions { 69 ast, issues := env.Compile(celExpression.Expression) 70 if issues != nil && issues.Err() != nil { 71 return ctx.Error(CustomKeyValidationErrorKeyPath, "cel expression compile error: %s", issues.Err()) 72 } 73 74 prg, err := env.Program(ast) 75 if err != nil { 76 return ctx.Error(CustomKeyValidationErrorKeyPath, "cel program construction error: %s", err) 77 } 78 79 res1, _, err := prg.Eval(resourceWithParentKey) 80 if err != nil { 81 return ctx.Error(CustomKeyValidationErrorKeyPath, "cel evaluation error: %s", err) 82 } 83 84 if res1.Type().TypeName() != "bool" { 85 return ctx.Error(CustomKeyValidationErrorKeyPath, "cel expression needs to return a boolean") 86 } 87 88 celReturnValue, ok := res1.Value().(bool) 89 if !ok { 90 return ctx.Error(CustomKeyValidationErrorKeyPath, "cel expression needs to return a boolean") 91 } 92 if !celReturnValue { 93 return ctx.Error(CELDefinitionCustomKey, "values in data value %v do not match", dataValue) 94 } 95 } 96 97 return nil 98 } 99 100 type CELExpression struct { 101 Expression string `json:"expression"` 102 } 103 104 type CELDefinition struct { 105 CELExpressions []CELExpression 106 } 107 108 func convertCustomKeyCELDefinitionSchemaToCELDefinitionSchema(CELDefinitionSchema CustomKeyCELDefinitionSchema) (*CELDefinition, error) { 109 var CELDefinition CELDefinition 110 for _, CELExpressionFromSchema := range CELDefinitionSchema { 111 var CELExpression CELExpression 112 b, err := json.Marshal(CELExpressionFromSchema) 113 if err != nil { 114 return nil, fmt.Errorf("CELExpression failed to marshal to json, %s", err.Error()) 115 } 116 err = json.Unmarshal(b, &CELExpression) 117 if err != nil { 118 return nil, fmt.Errorf("CELExpression must be an object of type CELExpression %s", err.Error()) 119 } 120 CELDefinition.CELExpressions = append(CELDefinition.CELExpressions, CELExpression) 121 } 122 123 return &CELDefinition, nil 124 } 125 126 func getCELEnvInputs(dataValue map[string]interface{}) ([]cel.EnvOption, error) { 127 inputVars := make([]cel.EnvOption, 0, len(dataValue)) 128 for input := range dataValue { 129 inputVars = append(inputVars, cel.Variable(input, cel.DynType)) 130 } 131 return inputVars, nil 132 }