124 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			124 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Package policy provides a policy checker for the helmfile state.
 | |
| package policy
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/helmfile/helmfile/pkg/runtime"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	EnvironmentsAndReleasesWithinSameYamlPartErr = errors.New("environments and releases cannot be defined within the same YAML part. Use --- to extract the environments into a dedicated part")
 | |
| 	topConfigKeysRegex                           = regexp.MustCompile(`^[a-zA-Z]+:`)
 | |
| 	separatorRegex                               = regexp.MustCompile(`^--- *$`)
 | |
| 	topkeysPriority                              = map[string]int{
 | |
| 		"bases":        0,
 | |
| 		"environments": 1,
 | |
| 		"releases":     2,
 | |
| 	}
 | |
| )
 | |
| 
 | |
| // checkerFunc is a function that checks the helmState.
 | |
| type checkerFunc func(filePath string, content []byte) (bool, error)
 | |
| 
 | |
| func forbidEnvironmentsWithReleases(filePath string, content []byte) (bool, error) {
 | |
| 	// forbid environments and releases to be defined at the same yaml part
 | |
| 	topKeys := TopKeys(content, true)
 | |
| 	if len(topKeys) == 0 {
 | |
| 		return true, fmt.Errorf("no top-level config keys are found in %s", filePath)
 | |
| 	}
 | |
| 	result := []string{}
 | |
| 	resultKeys := map[string]interface{}{}
 | |
| 	for _, k := range topKeys {
 | |
| 		if k == "environments" || k == "releases" || k == "---" {
 | |
| 			if _, ok := resultKeys[k]; !ok {
 | |
| 				result = append(result, k)
 | |
| 				if k != "---" {
 | |
| 					resultKeys[k] = nil
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(result) < 2 {
 | |
| 		return false, nil
 | |
| 	}
 | |
| 	for i := 0; i < len(result)-1; i++ {
 | |
| 		if result[i] != "---" && result[i+1] != "---" {
 | |
| 			return runtime.V1Mode, EnvironmentsAndReleasesWithinSameYamlPartErr
 | |
| 		}
 | |
| 	}
 | |
| 	return false, nil
 | |
| }
 | |
| 
 | |
| var checkerFuncs = []checkerFunc{
 | |
| 	TopConfigKeysVerifier,
 | |
| 	forbidEnvironmentsWithReleases,
 | |
| }
 | |
| 
 | |
| // Checker is a policy checker for the helmfile state.
 | |
| func Checker(filePath string, content []byte) (bool, error) {
 | |
| 	for _, fn := range checkerFuncs {
 | |
| 		if isStrict, err := fn(filePath, content); err != nil {
 | |
| 			return isStrict, err
 | |
| 		}
 | |
| 	}
 | |
| 	return false, nil
 | |
| }
 | |
| 
 | |
| // isTopOrderKey checks if the key is a top-level config key that must be defined in the correct order.
 | |
| func isTopOrderKey(key string) bool {
 | |
| 	_, ok := topkeysPriority[key]
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| // TopKeys returns the top-level config keys.
 | |
| func TopKeys(helmfileContent []byte, hasSeparator bool) []string {
 | |
| 	var topKeys []string
 | |
| 	clines := bytes.Split(helmfileContent, []byte("\n"))
 | |
| 
 | |
| 	for _, line := range clines {
 | |
| 		if topConfigKeysRegex.Match(line) {
 | |
| 			lineStr := strings.Split(string(line), ":")[0]
 | |
| 			topKeys = append(topKeys, lineStr)
 | |
| 		}
 | |
| 		if hasSeparator && separatorRegex.Match(line) {
 | |
| 			topKeys = append(topKeys, strings.TrimSpace(string(line)))
 | |
| 		}
 | |
| 	}
 | |
| 	return topKeys
 | |
| }
 | |
| 
 | |
| // TopConfigKeysVerifier verifies the top-level config keys are defined in the correct order.
 | |
| func TopConfigKeysVerifier(filePath string, helmfileContent []byte) (bool, error) {
 | |
| 	var orderKeys, topKeys []string
 | |
| 	topKeys = TopKeys(helmfileContent, false)
 | |
| 
 | |
| 	for _, k := range topKeys {
 | |
| 		if isTopOrderKey(k) {
 | |
| 			orderKeys = append(orderKeys, k)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(topKeys) == 0 {
 | |
| 		return true, fmt.Errorf("no top-level config keys are found in %s", filePath)
 | |
| 	}
 | |
| 
 | |
| 	if len(orderKeys) == 0 {
 | |
| 		return false, nil
 | |
| 	}
 | |
| 
 | |
| 	for i := 1; i < len(orderKeys); i++ {
 | |
| 		preKey := orderKeys[i-1]
 | |
| 		currentKey := orderKeys[i]
 | |
| 		if topkeysPriority[preKey] > topkeysPriority[currentKey] {
 | |
| 			return runtime.V1Mode, fmt.Errorf("top-level config key %s must be defined before %s in %s", preKey, currentKey, filePath)
 | |
| 		}
 | |
| 	}
 | |
| 	return false, nil
 | |
| }
 |