Skip to content

Commit 17377f0

Browse files
dd-octo-sts[bot]tbaveliermrdoggopat
authored
Fix YAML mapper for helm2dda for Helm's configmap based configurations (#3373) (#3409)
Fix YAML mapper for helm2dda for Helm's configmap based configurations (#3373) Fix YAML mapper for helm2dda for datadog.otelCollector.config and clusterAgent.confd Add fixes for agents.customAgentConfig and clusterAgent.cluster_yaml Restrict multi-key table fallback to mapFunc destinations Allowing any multi-key table through the fallback (added to support agents.customAgentConfig/clusterAgent.datadog_cluster_yaml) let it also apply to plain string/list destinations like agents.podSecurity.seLinuxContext, which have separate leaf mappings for their sub-fields. That produced invalid duplicate/extra fields in the output. Now the multi-key fallback only fires for mapFunc-based destinations (e.g. mapCustomConfigFile), matching Codex review feedback on PR #3373. Co-authored-by: Cursor <cursoragent@cursor.com> add test coverage for mapCustomConfigFile Co-authored-by: Cursor <cursoragent@cursor.com> fix multi-key table handling for helm2dda confd/customAgentConfig mappings extend helm2dda test fixtures to cover multi-key confd and customAgentConfig mapping Co-authored-by: patrick.liang <patrick.liang@datadoghq.com> (cherry picked from commit 96de46d) Merge branch 'v1.30' into backport-3373-to-v1.30 Co-authored-by: tbavelier <97530782+tbavelier@users.noreply.github.com> Co-authored-by: mrdoggopat <109171317+mrdoggopat@users.noreply.github.com> Co-authored-by: timothee.bavelier <timothee.bavelier@datadoghq.com>
1 parent c0d0e5f commit 17377f0

6 files changed

Lines changed: 388 additions & 11 deletions

File tree

cmd/yaml-mapper/mapper/map_processors.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func mapFuncRegistry() map[string]MappingRunFunc {
3434
mapAppendEnvVar,
3535
mapMergeEnvs,
3636
mapOverrideType,
37+
mapCustomConfigFile,
3738
} {
3839
registry[p.name] = p.runFunc
3940
}
@@ -296,6 +297,44 @@ var mapOverrideType = MappingProcessor{
296297
},
297298
}
298299

300+
// mapCustomConfigFile serializes an object-shaped Helm value (e.g. custom datadog.yaml settings)
301+
// into a YAML string and stores it under CustomConfig's `configData` field, keyed by the given
302+
// filename. The filename is passed via `args` instead of `newPath` since it can contain dots
303+
// (e.g. "datadog.yaml") that shouldn't be split into nested path segments.
304+
// args:
305+
// - fileName: datadog.yaml
306+
var mapCustomConfigFile = MappingProcessor{
307+
name: "mapCustomConfigFile",
308+
runFunc: func(interim map[string]any, newPath string, pathVal any, args []any) {
309+
if len(args) != 1 {
310+
return
311+
}
312+
fileName, ok := utils.GetPathString(args[0], "fileName")
313+
if !ok || fileName == "" {
314+
return
315+
}
316+
317+
var configData string
318+
switch v := pathVal.(type) {
319+
case string:
320+
configData = v
321+
default:
322+
out, err := yaml.Marshal(pathVal)
323+
if err != nil {
324+
slog.Error("failed to marshal custom config content", "path", newPath, "fileName", fileName, "error", err)
325+
return
326+
}
327+
configData = string(out)
328+
}
329+
330+
utils.MergeOrSet(interim, newPath, map[string]any{
331+
fileName: map[string]any{
332+
"configData": configData,
333+
},
334+
})
335+
},
336+
}
337+
299338
// hasDuplicateEnv checks if a given env var name is already present in the given list of env vars.
300339
func hasDuplicateEnv(existingEnvs []any, newEnvName string) bool {
301340
for _, existingEnv := range existingEnvs {

cmd/yaml-mapper/mapper/mapper.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,27 @@ func (m *Mapper) mapValues(sourceValues chartutil.Values, mappingValues chartuti
197197

198198
// Map values.yaml => DDA
199199
for _, sourceKey := range mappingKeys {
200+
destKey, _ := mappingValues[sourceKey]
201+
// Only copy a whole table if none of its sub-keys are mapped separately
202+
// (e.g. agents.podSecurity.seLinuxContext.rule has its own mapping entry,
203+
// so seLinuxContext itself shouldn't be copied as one big table).
204+
hasMappedDescendant := hasDescendantMappingKey(mappingKeys, sourceKey)
205+
200206
pathVal, _ := sourceValues.PathValue(sourceKey)
201207
if pathVal == nil {
202208
if mapVal, ok := utils.GetPathMap(sourceValues[sourceKey]); ok && mapVal != nil {
203209
pathVal = mapVal
204-
} else if tableVal, err := sourceValues.Table(sourceKey); err == nil && len(tableVal) == 1 {
210+
} else if tableVal, err := sourceValues.Table(sourceKey); err == nil && len(tableVal) > 0 && !hasMappedDescendant {
205211
pathVal = tableVal
206212
} else {
207213
continue
208214
}
209215
}
210216

211-
utils.MergeOrSet(sourceKeysRef, sourceKey, map[string]any{"visited": true})
217+
// Mark this key and everything nested under it as visited, since copying the
218+
// whole table also covers all of its nested values.
219+
markVisited(sourceKeysRef, sourceKey)
212220

213-
destKey, _ := mappingValues[sourceKey]
214221
if (destKey == "" || destKey == nil) && !shouldSkipMappingKey(sourceKey) {
215222
slog.Error("DDA destination key not found", "sourceKey", sourceKey)
216223
errorCount++
@@ -383,6 +390,33 @@ func (m *Mapper) updateMapping(sourceValues chartutil.Values, mappingValues char
383390
return nil
384391
}
385392

393+
// hasDescendantMappingKey returns true if mappingKeys contains an entry that is a strict
394+
// descendant of sourceKey (i.e. prefixed with "sourceKey."). Such entries indicate that
395+
// sub-fields of the table at sourceKey are mapped individually, so the table itself must
396+
// not be copied wholesale.
397+
func hasDescendantMappingKey(mappingKeys []string, sourceKey string) bool {
398+
prefix := sourceKey + "."
399+
for _, k := range mappingKeys {
400+
if strings.HasPrefix(k, prefix) {
401+
return true
402+
}
403+
}
404+
return false
405+
}
406+
407+
// markVisited marks sourceKey, and every flattened key nested beneath it, as visited in
408+
// sourceKeysRef. This is used when a table value is consumed as a whole (either copied
409+
// directly or passed to a mapFunc), which implicitly consumes all of its nested values too.
410+
func markVisited(sourceKeysRef map[string]any, sourceKey string) {
411+
utils.MergeOrSet(sourceKeysRef, sourceKey, map[string]any{"visited": true})
412+
prefix := sourceKey + "."
413+
for k := range sourceKeysRef {
414+
if strings.HasPrefix(k, prefix) {
415+
utils.MergeOrSet(sourceKeysRef, k, map[string]any{"visited": true})
416+
}
417+
}
418+
}
419+
386420
// flattenValues builds a mapping of dotted-key paths from a provided Values source.
387421
func flattenValues(sourceValues chartutil.Values, valuesMap map[string]any, prefix string) map[string]any {
388422
for key, value := range sourceValues {

0 commit comments

Comments
 (0)