Files
troubleshoot/pkg/preflight/values_defaults.go
2025-09-17 11:02:53 -05:00

152 lines
4.2 KiB
Go

package preflight
import (
"regexp"
"strings"
)
var (
// Matches occurrences like .Values.minio.enabled or .Values.postgres.create
// Captures group 1 as the dotted path (e.g., "minio" or "postgres");
// group 2 is the leaf key (enabled|create).
valuesBoolRefRe = regexp.MustCompile(`\.Values\.([A-Za-z0-9_\.]+?)\.(enabled|create)\b`)
// Matches general .Values.<path> references used in templates. This is a broad match
// and will be further sanitized before being applied.
valuesAnyRefRe = regexp.MustCompile(`\.Values\.([A-Za-z0-9_\.]+)`)
)
// SeedDefaultBooleans scans the template content for boolean-like value references
// such as .Values.<path>.enabled or .Values.<path>.create and ensures that any
// missing paths in the provided values map are initialized with a default value
// of false. This prevents nil dereference errors during Helm rendering when
// templates access nested fields on absent maps.
func SeedDefaultBooleans(templateContent string, values map[string]interface{}) {
if values == nil {
return
}
matches := valuesBoolRefRe.FindAllStringSubmatch(templateContent, -1)
if len(matches) == 0 {
return
}
for _, m := range matches {
if len(m) < 3 {
continue
}
dottedPath := m[1]
leaf := m[2]
// Build full key path like [minio, enabled]
keys := append(strings.Split(dottedPath, "."), leaf)
setNestedDefaultFalse(values, keys)
}
}
// SeedParentMapsForValueRefs scans the template for .Values.<path> references and ensures
// that any missing parent maps along those paths are created in the provided values map.
// This prevents Helm/template evaluation errors like "nil pointer evaluating interface {}.foo"
// when the template dereferences nested maps that are absent. Only parent maps up to the
// last segment are created; leaf values are never set.
func SeedParentMapsForValueRefs(templateContent string, values map[string]interface{}) {
if values == nil {
return
}
matches := valuesAnyRefRe.FindAllStringSubmatch(templateContent, -1)
if len(matches) == 0 {
return
}
for _, m := range matches {
if len(m) < 2 {
continue
}
dotted := m[1]
// Ignore obviously invalid or empty
if dotted == "" {
continue
}
// Split path into segments; keep only identifier segments
rawSegs := strings.Split(dotted, ".")
segs := make([]string, 0, len(rawSegs))
for _, s := range rawSegs {
if s == "" {
continue
}
// Only allow [A-Za-z0-9_]
valid := true
for i := 0; i < len(s); i++ {
ch := s[i]
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') {
valid = false
break
}
}
if !valid {
break
}
segs = append(segs, s)
}
// Need at least two segments to create parents (e.g., foo.bar)
if len(segs) < 2 {
continue
}
ensureParentMaps(values, segs)
}
}
// ensureParentMaps ensures that for the given dotted path segments, all parent maps
// (up to but not including the last segment) exist. If an existing value conflicts
// (not a map), it is replaced with a new map to allow nested lookups downstream.
func ensureParentMaps(root map[string]interface{}, segs []string) {
cur := root
// up to segs[len-2]
for i := 0; i < len(segs)-1; i++ {
k := segs[i]
next, ok := cur[k]
if ok {
if m, ok := next.(map[string]interface{}); ok {
cur = m
continue
}
}
// create/replace with a map
m := map[string]interface{}{}
cur[k] = m
cur = m
}
}
// setNestedDefaultFalse ensures that the nested path exists. If the leaf key is
// missing, it sets it to false. Existing values are left unchanged.
func setNestedDefaultFalse(root map[string]interface{}, keys []string) {
if len(keys) == 0 {
return
}
cur := root
// Traverse/create intermediate maps for all but the last key
for i := 0; i < len(keys)-1; i++ {
k := keys[i]
next, ok := cur[k]
if !ok {
m := map[string]interface{}{}
cur[k] = m
cur = m
continue
}
if m, ok := next.(map[string]interface{}); ok {
cur = m
continue
}
// If the existing value is not a map, replace it with a nested map to allow
// setting the boolean leaf without panicking.
m := map[string]interface{}{}
cur[k] = m
cur = m
}
leafKey := keys[len(keys)-1]
if _, exists := cur[leafKey]; !exists {
cur[leafKey] = false
}
}