github.com/zntrio/harp/v2@v2.0.9/pkg/bundle/ruleset/package.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 ruleset 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "os" 25 "strings" 26 27 "github.com/gobwas/glob" 28 29 bundlev1 "github.com/zntrio/harp/v2/api/gen/go/harp/bundle/v1" 30 "github.com/zntrio/harp/v2/pkg/bundle/ruleset/engine" 31 "github.com/zntrio/harp/v2/pkg/bundle/ruleset/engine/cel" 32 "github.com/zntrio/harp/v2/pkg/bundle/ruleset/engine/rego" 33 ) 34 35 // Evaluate given bundl using the loaded ruleset. 36 // 37 //nolint:gocyclo // to refactor 38 func Evaluate(ctx context.Context, b *bundlev1.Bundle, spec *bundlev1.RuleSet) error { 39 // Validate spec 40 if err := Validate(spec); err != nil { 41 return fmt.Errorf("unable to validate spec: %w", err) 42 } 43 if b == nil { 44 return fmt.Errorf("cannot process nil bundle") 45 } 46 47 // Prepare selectors 48 if len(spec.Spec.Rules) == 0 { 49 return fmt.Errorf("empty ruleset") 50 } 51 52 // Process each rule 53 for _, r := range spec.Spec.Rules { 54 // Complie path matcher 55 pathMatcher, err := glob.Compile(r.Path) 56 if err != nil { 57 return fmt.Errorf("unable to compile path matcher: %w", err) 58 } 59 60 var ( 61 vm engine.PackageLinter 62 vmErr error 63 ) 64 65 switch { 66 case len(r.Constraints) > 0: 67 // Compile constraints 68 vm, vmErr = cel.New(r.Constraints) 69 case r.RegoFile != "": 70 // Open policy file 71 f, err := os.Open(r.RegoFile) 72 if err != nil { 73 return fmt.Errorf("unable to open rego policy file: %w", err) 74 } 75 76 // Create a evaluation context 77 vm, vmErr = rego.New(ctx, f) 78 case r.Rego != "": 79 // Create a evaluation context 80 vm, vmErr = rego.New(ctx, strings.NewReader(r.Rego)) 81 default: 82 return errors.New("one of 'constraints', 'rego' or 'rego_file' property must be defined") 83 } 84 if vmErr != nil { 85 return fmt.Errorf("unable to prepare evaluation context: %w", vmErr) 86 } 87 88 // A rule must match at least one time. 89 matchOnce := false 90 91 // For each package 92 for _, p := range b.Packages { 93 if p == nil { 94 // Ignore nil package 95 continue 96 } 97 98 // If package match the path filter. 99 if pathMatcher.Match(p.Name) { 100 matchOnce = true 101 102 errEval := vm.EvaluatePackage(ctx, p) 103 if errEval != nil { 104 if errors.Is(errEval, engine.ErrRuleNotValid) { 105 return fmt.Errorf("package %q doesn't validate rule %q", p.Name, r.Name) 106 } 107 return fmt.Errorf("unexpected error occurred during constraints evaluation: %w", errEval) 108 } 109 } 110 } 111 112 // Check matching constraint 113 if !matchOnce { 114 return fmt.Errorf("rule %q didn't match any packages", r.Name) 115 } 116 } 117 118 // No error 119 return nil 120 }