Skip to content

feat(imports): add mergeStrategy field to support deep merge#3103

Open
JordanGoasdoue wants to merge 2 commits into
devspace-sh:mainfrom
JordanGoasdoue:feat/add-deep-merge-imports
Open

feat(imports): add mergeStrategy field to support deep merge#3103
JordanGoasdoue wants to merge 2 commits into
devspace-sh:mainfrom
JordanGoasdoue:feat/add-deep-merge-imports

Conversation

@JordanGoasdoue
Copy link
Copy Markdown

@JordanGoasdoue JordanGoasdoue commented Jan 15, 2026

What issue type does this pull request address?

/kind feature

What does this pull request do? Which issues does it resolve?

Adds an opt-in mergeStrategy field on imports to support recursive deep merge of config sections.

Problem:
When importing a config (e.g., from a git catalog), the current implementation only does a shallow merge at the first level. If both the imported config and the local config define the same section (like dev.api), nested maps are completely replaced instead of being merged.

Example:

# Catalog
dev:
  api:
    labelSelector:
      app: catalog-app
      version: v1
    command: ["/bin/bash"]

# Local config
dev:
  api:
    labelSelector:
      app: my-app

Before: Local dev.api replaces catalog entirely, loses version: v1, command, etc.
After (with mergeStrategy: deepMerge): Maps are deep merged, keeps both app: my-app and version: v1

Solution:
A new mergeStrategy field on each import entry, with two values:

  • shallowMerge (default, current behavior): only top-level keys within each section are merged. Existing keys in the local config take precedence entirely.
  • deepMerge (opt-in): maps are merged recursively. Arrays and scalars in the local config still take precedence.
imports:
  - git: https://github.com/org/catalog.git
    subPath: devspace.yaml
    mergeStrategy: deepMerge

Merge rules when deepMerge is enabled:

Type Behavior
Maps Recursively merged (local keys override on conflict, catalog keys preserved otherwise)
Arrays Local replaces catalog entirely
Scalars Local overrides catalog

This enables the common use case where organizations maintain a shared catalog in git, and projects only need to override specific values (like labelSelector) while inheriting everything else (ports, sync config, commands, etc.).

Please provide a short message that should be published in the DevSpace release notes

Imports now support a mergeStrategy field. Set mergeStrategy: deepMerge on an import to enable recursive map merging, allowing projects to inherit shared catalog configs while only overriding specific nested values. Default behavior (shallowMerge) is unchanged.

What else do we need to know?

  • Fully backward compatible: default is shallowMerge, which preserves the existing behavior exactly. No existing config is affected.
  • Mutually exclusive fields are safe: since deep merge is opt-in, users who don't use it are not affected. When deepMerge combines fields that shouldn't coexist, the existing validations in validate.go catch it properly. This is tested explicitly:
    • deployments: helm + kubectl on the same deployment triggers "deployments[x].kubectl and deployments[x].helm cannot be used together"
    • dev: imageSelector + labelSelector on the same dev pod triggers "dev.x: image selector and label selector cannot be used together"
    • Both cases are also tested with shallowMerge to prove no regression (the conflicting field from the catalog is simply ignored)
  • Comprehensive test coverage:
    • 11 integration tests via TestImportsDeepMerge (deep merge scenarios, default shallow, explicit shallow, mutually exclusive field validation in both deployments and dev sections)
    • 7 unit tests for deepMerge (edge cases: empty maps, 3-level nesting, type mismatches, nil values)
    • 4 unit tests for shallowMerge
  • Schema files (devspace-schema.json, config-openapi.json) regenerated, IDE autocomplete works out of the box
  • All existing tests pass

@netlify
Copy link
Copy Markdown

netlify Bot commented Jan 15, 2026

Deploy Preview for devspace-docs canceled.

Built without sensitive environment variables

Name Link
🔨 Latest commit d03c8b3
🔍 Latest deploy log https://app.netlify.com/projects/devspace-docs/deploys/6a02fbb89edaee0008a53d11

Signed-off-by: JordanGoasdoue <jordan.goasdoue@dailymotion.com>
@zerbitx
Copy link
Copy Markdown
Collaborator

zerbitx commented Apr 27, 2026

Looking at this again, it looks like it will introduce some regressions with fields that shouldn't exist together like helm and kubectl

@JordanGoasdoue JordanGoasdoue force-pushed the feat/add-deep-merge-imports branch 3 times, most recently from 77c6c15 to 2bf5d7e Compare May 12, 2026 10:01
@JordanGoasdoue
Copy link
Copy Markdown
Author

Good point @zerbitx, you were right. I've reworked it. Deep merge is now opt-in via a mergeStrategy field on each import. Default stays shallowMerge, so no behavior change for existing configs.

imports:
  - path: catalog.yaml
    mergeStrategy: deepMerge

I added tests for the mutually exclusive fields you flagged. When deepMerge combines fields that shouldn't coexist, the existing validations catch it:

  • helm + kubectl in deployments
  • imageSelector + labelSelector in dev

Both cases also tested with shallowMerge to confirm no regression.

Let me know what you think!

Signed-off-by: JordanGoasdoue <jordan.goasdoue@dailymotion.com>
@JordanGoasdoue JordanGoasdoue force-pushed the feat/add-deep-merge-imports branch from 2bf5d7e to d03c8b3 Compare May 12, 2026 10:06
@JordanGoasdoue JordanGoasdoue changed the title feat: deep merge imports to combine local and catalog configs feat(imports): add mergeStrategy field to support deep merge May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants