From 7cc5fe03584bddd93f7d1f3cf9f49dc449b9f41e Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Sun, 3 May 2026 20:59:03 +0800 Subject: [PATCH] docs: deduplicate Technical Details sections in values-and-merging.md (#2575) Signed-off-by: yxxhero --- docs/values-and-merging.md | 165 +++++++++---------------------------- 1 file changed, 38 insertions(+), 127 deletions(-) diff --git a/docs/values-and-merging.md b/docs/values-and-merging.md index 8c7e6832..5b39d80f 100644 --- a/docs/values-and-merging.md +++ b/docs/values-and-merging.md @@ -412,133 +412,6 @@ Here's the complete data flow when running `helmfile sync`: - Defaults & Values: `ArrayMergeStrategySparse` (auto-detect nil values) - CLIOverrides: `ArrayMergeStrategyMerge` (always element-by-element) -## Technical Details - -### Secret Handling Intern - -Helmfile processes secrets in a special way: - -**Non-HCL secrets (.yaml, .yaml.gotmpl):** -1. Decrypted using helm-secrets plugin -2. Parsed into values immediately (during load phase) -3. Stored separately from regular values -4. **Mrged last** (highest priority) after all environment values are loaded - -**HCL secrets (.hcl):** -1. Decrypted using helm-secrets plugin -2. Decrypted file paths added to values file list -3. Processed in step 2 (HCL loading phase) -4. Can reference values from other HCL files (using `hv.` accessor) - -This separation allows: -- Secrets to override regular values without being re-decrypted multiple times -- HCL secrets to participate in HCL's cross-file referencing system - -### Multiple Helmfiles and State files - -When using multi-part helmfiles (multiple YAML documents separated by `---`): - -```yaml -# Part 1: base.yaml -helmDefaults: - wait: true - timeout: 300 ---- -# Part 2: environments.yaml -environments: - default: - production: -``` - -Each part is processed in order: - and the results are merged with later parts taking precedence. - -## Technical Details - -### Environment Structure Internals - -The `Environment` struct has three key fields that affect merging: - -```go -type Environment struct { - Name string - KubeContext string - Values map[string]any // Environment values + secrets - Defaults map[string]any // Root-level values: block - CLIOverrides map[string]any // CLI --state-values-set -} -``` - -### Final Merge Process (GetMergedValues) - -When you access `.Values` in templates, Helmfile calls `GetMergedValues()` which merges in this order: - -```go -func (e *Environment) GetMergedValues() (map[string]any, error) { - vals := map[string]any{} - vals = maputil.MergeMaps(vals, e.Defaults) // 1. Defaults (root-level values:) - vals = maputil.MergeMaps(vals, e.Values) // 2. Values (environment values + secrets) - vals = maputil.MergeMaps(vals, e.CLIOverrides, // 3. CLIOverrides (highest priority) - maputil.MergeOptions{ArrayStrategy: maputil.ArrayMergeStrategyMerge}) - return vals, nil -} -``` - -**Important:** CLIOverrides uses `ArrayMergeStrategyMerge` (element-by-element merging), while Defaults and Values use the default strategy (sparse auto-detection). - -### Merging Library: mergo - -Helmfile uses the [mergo](https://github.com/imdario/mergo) library for deep merging with these key features: - -1. **Deep merge for maps**: Nested maps are merged recursively -2. **WithOverride option**: Later values override earlier values -3. **Type-safe**: Preserves value types during merge - -Example from code: -```go -// In loadEnvValues() -if err := mergo.Merge(&valuesVals, &secretVals, mergo.WithOverride); err != nil { - return nil, err -} -``` - -### Array Merge Strategies Implementation - -The `maputil.MergeMaps` function supports three array merge strategies: - -```go -type ArrayMergeStrategy int - -const ( - ArrayMergeStrategySparse ArrayMergeStrategy = iota // Auto-detect based on nil values - ArrayMergeStrategyReplace ArrayMergeStrategy = iota // Always replace arrays - ArrayMergeStrategyMerge ArrayMergeStrategy = iota // Always merge element-by-element -) -``` - -**Sparse Strategy (Default for most cases):** -```go -func mergeSlices(base, override []any, strategy ArrayMergeStrategy) []any { - if strategy == ArrayMergeStrategySparse { - isSparse := false - for _, v := range override { - if v == nil { - isSparse = true - break - } - } - if !isSparse { - return override // Replace entirely - } - // Otherwise merge element-by-element - } -} -``` - -This means: -- `[1, 2, 3]` merged with `[4, 5]` → result: `[4, 5]` (replaced, no nils) -- `[null, 2]` merged with `[1, 2, 3]` → result: `[1, 2, 3]` (merged, has nil) - ## Common Patterns ### Pattern 1: Global Defaults with Environment Overrides @@ -633,6 +506,44 @@ environments: ## Technical Implementation Details +### Secret Handling + +Helmfile processes secrets in a special way: + +**Non-HCL secrets (.yaml, .yaml.gotmpl):** +1. Decrypted using helm-secrets plugin +2. Parsed into values immediately (during load phase) +3. Stored separately from regular values +4. **Merged last** (highest priority) after all environment values are loaded + +**HCL secrets (.hcl):** +1. Decrypted using helm-secrets plugin +2. Decrypted file paths added to values file list +3. Processed in step 2 (HCL loading phase) +4. Can reference values from other HCL files (using `hv.` accessor) + +This separation allows: +- Secrets to override regular values without being re-decrypted multiple times +- HCL secrets to participate in HCL's cross-file referencing system + +### Multiple Helmfiles and State files + +When using multi-part helmfiles (multiple YAML documents separated by `---`): + +```yaml +# Part 1: base.yaml +helmDefaults: + wait: true + timeout: 300 +--- +# Part 2: environments.yaml +environments: + default: + production: +``` + +Each part is processed in order and the results are merged with later parts taking precedence. + ### Environment Structure The `Environment` struct is the core data structure that holds all values: