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  }