From 092ddbe6729b0a0e15972510f59b496a3772f88c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Badst=C3=BCbner?= Date: Sat, 19 Sep 2026 00:34:16 +0200 Subject: [PATCH] fix `strategy: Merge` for YAMLs in Glob sub-dirs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jonas Badstübner --- internal/builder/builder.go | 7 ++++++- internal/builder/merge_test.go | 30 +++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/internal/builder/builder.go b/internal/builder/builder.go index c247008..0b7c811 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -486,7 +486,7 @@ func copyFileWithRoots(ctx context.Context, } if srcInfo.IsDir() { - return copyDirWithRoots(ctx, srcRoot, srcPath, stagingRoot, destPath, op.Exclude, excludeBasePath) + return copyDirWithRoots(ctx, op, srcRoot, srcPath, stagingRoot, destPath, op.Exclude, excludeBasePath) } if shouldMergeFile(op, stagingRoot, destPath) { @@ -591,6 +591,7 @@ func mergeFileWithRoots(ctx context.Context, // copyDirWithRoots copies a directory recursively using os.Root, // skipping files and sub-dirs matching exclude patterns. func copyDirWithRoots(ctx context.Context, + op swapi.CopyOperation, srcRoot *os.Root, srcPath string, stagingRoot *os.Root, @@ -633,6 +634,10 @@ func copyDirWithRoots(ctx context.Context, return createDirRecursive(stagingRoot, destFilePath) } + if shouldMergeFile(op, stagingRoot, destFilePath) { + return mergeFileWithRoots(ctx, srcRoot, path, stagingRoot, destFilePath) + } + return copyRegularFileWithRoots(ctx, srcRoot, path, stagingRoot, destFilePath) }) } diff --git a/internal/builder/merge_test.go b/internal/builder/merge_test.go index 8b94e7b..3699614 100644 --- a/internal/builder/merge_test.go +++ b/internal/builder/merge_test.go @@ -176,7 +176,7 @@ c: source2Dir := filepath.Join(tmpDir, "source2") workspaceDir := filepath.Join(tmpDir, "workspace") - setupDirs(t, source1Dir, source2Dir, workspaceDir) + setupDirs(t, filepath.Join(source1Dir, "config"), filepath.Join(source2Dir, "config"), workspaceDir) // Create first source with base config createFile(t, source1Dir, "config1.yaml", ` @@ -186,11 +186,17 @@ region: us-west-1 createFile(t, source1Dir, "config2.yaml", ` version: 1.0.0 image: my-app:latest +`) + createFile(t, filepath.Join(source1Dir, "config"), "config3.yaml", ` +version: 1.0.0 +image: my-app:latest `) // Create second source with overlay config - createFile(t, source2Dir, "config1.yaml", "env: prod") // This should overwrite the env - createFile(t, source2Dir, "config2.yaml", "replicas: 5") // This should add a new field + createFile(t, source2Dir, "config1.yaml", "env: prod") // This should overwrite the env + createFile(t, source2Dir, "config2.yaml", "replicas: 5") // This should add a new field in the root directory of the glob pattern + createFile(t, filepath.Join(source2Dir, "config"), "config3.yaml", "replicas: 10") // This should add a new field in a subdirectory matched by the glob pattern + createFile(t, filepath.Join(source2Dir, "config"), "config4.yaml", "content: hello-world") // This should add a new file spec := &swapi.OutputArtifact{ Name: "yaml-to-yaml-dir-merge", @@ -222,6 +228,8 @@ image: my-app:latest stagingDir := filepath.Join(workspaceDir, "yaml-to-yaml-dir-merge") config1Path := filepath.Join(stagingDir, "config1.yaml") config2Path := filepath.Join(stagingDir, "config2.yaml") + config3Path := filepath.Join(stagingDir, "config", "config3.yaml") + config4Path := filepath.Join(stagingDir, "config", "config4.yaml") config1Content, err := os.ReadFile(config1Path) g.Expect(err).ToNot(HaveOccurred()) @@ -231,6 +239,14 @@ image: my-app:latest g.Expect(err).ToNot(HaveOccurred()) g.Expect(config2Content).ToNot(BeEmpty()) + config3Content, err := os.ReadFile(config3Path) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(config3Content).ToNot(BeEmpty()) + + config4Content, err := os.ReadFile(config4Path) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(config4Content).ToNot(BeEmpty()) + // Verify the merged YAML contains expected content g.Expect(config1Content).To(MatchYAML(` env: prod @@ -240,6 +256,14 @@ region: us-west-1 image: my-app:latest replicas: 5 version: 1.0.0 +`)) + g.Expect(config3Content).To(MatchYAML(` +image: my-app:latest +replicas: 10 +version: 1.0.0 +`)) + g.Expect(config4Content).To(MatchYAML(` +content: hello-world `)) }, },