mirror of
https://github.com/helmfile/helmfile.git
synced 2026-09-30 11:13:11 +02:00
Toolchain and CI:
- Bump go directive from 1.26.8 to 1.27.1 (go.mod)
- Use golang:1.27-alpine builder images in all Dockerfiles
- Bump golangci-lint to v2.13.2 (first release with go1.27 support)
- Add CI gate: `go fix -diff` fails when outdated Go patterns are
detected (locally: `make check-modernize`)
Note: darwin binaries now require macOS 13 or later.
Lint fixes required by golangci-lint v2.13.2:
- goconst: ignore tests (all 436 findings were test-only; goconst
got stricter since v2.12 and this option was added for it)
- openai.go: keep deprecated MaxTokens deliberately with a nolint
rationale (max_tokens is the only form universally supported by
OpenAI-compatible backends like One-API, LiteLLM, Ollama shim)
- state.go: drop always-nil flags param from appendChartVersionFlags
(renamed to chartVersionFlags, unparam)
Modernization (go fix ./..., 62 files):
- interface{} -> any, maps.Copy, strings.SplitSeq, range-over-int,
builtin min/max, slices.Contains/ContainsFunc/Sort, WaitGroup.Go,
reflect.Type.Fields(), new(expr)
- exit_error.go: strings.Builder + fmt.Fprintf instead of string
concatenation and WriteString(fmt.Sprintf(...)) (QF1012)
- chart_dependency.go: strings.CutLast for OCI dependency helpers
Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: Claude <noreply@anthropic.com>
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]any{}
|
|
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.SplitSeq(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.Cut(lineStr, ":")
|
|
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
|
|
}
|