github.com/zntrio/harp/v2@v2.0.9/pkg/bundle/selector/match_cel.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package selector 19 20 import ( 21 "errors" 22 "fmt" 23 24 "github.com/google/cel-go/cel" 25 celext "github.com/google/cel-go/ext" 26 "go.uber.org/zap" 27 28 bundlev1 "github.com/zntrio/harp/v2/api/gen/go/harp/bundle/v1" 29 "github.com/zntrio/harp/v2/pkg/bundle/ruleset/engine/cel/ext" 30 "github.com/zntrio/harp/v2/pkg/sdk/log" 31 ) 32 33 // MatchCEL returns a CEL package matcher specification. 34 func MatchCEL(expressions []string) (Specification, error) { 35 // Check arguments 36 if len(expressions) == 0 { 37 return nil, errors.New("CEL expressions could not be empty for matcher") 38 } 39 40 // Prepare CEL Environment 41 env, err := cel.NewEnv( 42 cel.Types(&bundlev1.Bundle{}, &bundlev1.Package{}, &bundlev1.SecretChain{}, &bundlev1.KV{}), 43 ext.Packages(), 44 ext.Secrets(), 45 celext.Strings(), 46 ) 47 if err != nil { 48 return nil, fmt.Errorf("unable to prepare CEL engine environment: %w", err) 49 } 50 51 // Assemble the complete ruleset 52 ruleset := make([]cel.Program, 0, len(expressions)) 53 for _, exp := range expressions { 54 // Parse expression 55 parsed, issues := env.Parse(exp) 56 if issues != nil && issues.Err() != nil { 57 return nil, fmt.Errorf("unable to parse %q, go error: %w", exp, issues.Err()) 58 } 59 60 // Extract AST 61 ast, cerr := env.Check(parsed) 62 if cerr != nil && cerr.Err() != nil { 63 return nil, fmt.Errorf("invalid CEL expression: %w", cerr.Err()) 64 } 65 66 // request matching is a boolean operation, so we don't really know 67 // what to do if the expression returns a non-boolean type 68 if ast.OutputType() != cel.BoolType { 69 return nil, fmt.Errorf("CEL rule engine expects return type of bool, not %s", ast.OutputType()) 70 } 71 72 // Compile the program 73 p, err := env.Program(ast) 74 if err != nil { 75 return nil, fmt.Errorf("error while creating CEL program: %w", err) 76 } 77 78 // Add to context 79 ruleset = append(ruleset, p) 80 } 81 82 // Wrap as a builder 83 return &celMatcher{ 84 cel: env, 85 ruleset: ruleset, 86 }, nil 87 } 88 89 type celMatcher struct { 90 cel *cel.Env 91 ruleset []cel.Program 92 } 93 94 // IsSatisfiedBy returns specification satisfaction status. 95 func (s *celMatcher) IsSatisfiedBy(object interface{}) bool { 96 // If object is a package 97 if p, ok := object.(*bundlev1.Package); ok { 98 // Evaluate filter compliance 99 matched, err := s.celEvaluate(p) 100 if err != nil { 101 log.Bg().Debug("cel evaluation failed", zap.Error(err)) 102 return false 103 } 104 105 return matched 106 } 107 108 return false 109 } 110 111 // ----------------------------------------------------------------------------- 112 113 func (s *celMatcher) celEvaluate(input *bundlev1.Package) (bool, error) { 114 // Check arguments 115 if input == nil { 116 return false, errors.New("unable to evaluate nil package") 117 } 118 119 // Apply evaluation (implicit AND between rules) 120 for _, exp := range s.ruleset { 121 // Evaluate using the bundle context 122 out, _, err := exp.Eval(map[string]interface{}{ 123 "p": input, 124 }) 125 if err != nil { 126 return false, fmt.Errorf("an error occurred during the rule evaluation: %w", err) 127 } 128 129 // Boolean rule returned false 130 if out.Value() == false { 131 return false, nil 132 } 133 } 134 135 // No error 136 return true, nil 137 }