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  }