129 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			129 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
// Package policy provides a policy checker for the helmfile state.
 | 
						|
package policy
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"regexp"
 | 
						|
	"slices"
 | 
						|
	"strings"
 | 
						|
	"unicode"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	ErrEnvironmentsAndReleasesWithinSameYamlPart = 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": 0,
 | 
						|
		"releases":     1,
 | 
						|
	}
 | 
						|
)
 | 
						|
 | 
						|
// 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 slices.Contains([]string{"environments", "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 true, ErrEnvironmentsAndReleasesWithinSameYamlPart
 | 
						|
		}
 | 
						|
	}
 | 
						|
	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 {
 | 
						|
		lineStr := strings.TrimRightFunc(string(line), unicode.IsSpace)
 | 
						|
		if lineStr == "" {
 | 
						|
			continue // Skip empty lines
 | 
						|
		}
 | 
						|
		if hasSeparator && separatorRegex.MatchString(lineStr) {
 | 
						|
			topKeys = append(topKeys, lineStr)
 | 
						|
		}
 | 
						|
 | 
						|
		if topConfigKeysRegex.MatchString(lineStr) {
 | 
						|
			topKey := strings.SplitN(lineStr, ":", 2)[0]
 | 
						|
			topKeys = append(topKeys, topKey)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	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 true, fmt.Errorf("top-level config key %s must be defined before %s in %s", currentKey, preKey, filePath)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false, nil
 | 
						|
}
 |