Deferred YAML Function Evaluation in Merge
We've improved how Atmos handles YAML functions during merges across configuration layers. Atmos now postpones merging YAML functions until after the regular merge is done. This avoids the type conflicts that used to happen when a stack layer replaced a plain value—like a string, map, or list—with a YAML function such as a template or an output reference.
The Problem: Type Conflicts During Merge
YAML functions (like !template, !terraform.output, !store.get, and others) are represented as strings during
configuration loading, but they resolve to different types after evaluation.
When Atmos tried to merge a concrete value (like a string "10.0.0.0/16") with a YAML function string
(like !template '{{ .settings.vpc_cidr }}'), it encountered type conflicts. The standard merge process couldn't
handle merging these different representations across nested stack layers.
Consider this common scenario across multiple stack files:
Base catalog (catalog/vpc/defaults.yaml):
components:
terraform:
vpc:
vars:
cidr_block: "10.0.0.0/16"
enable_dns: true
Environment-specific override (stacks/prod/networking.yaml):
components:
terraform:
vpc:
vars:
cidr_block: !template '{{ .settings.vpc_cidr }}' # Template function
availability_zones: 3
When Atmos processed these files, it would:
- Load the base configuration with
cidr_blockas a string - Try to merge with the override where
cidr_blockis a template function (different type) - Encounter a type conflict: string vs. template function
The fundamental issue: YAML functions were being processed before merging, creating type mismatches that broke the merge operation.
Why This Matters
This problem appeared in several real-world scenarios:
- Multi-environment deployments where production uses templates for dynamic values while dev uses static strings
- Team-specific configurations where some teams use
!store.getfor secrets while others hardcode values - Gradual migrations from static to templated configurations
- Mixed configuration sources combining vendored components (static) with custom overrides (templated)
The Solution: Defer, Merge, Then Process
The new deferred merge infrastructure introduces a three-phase approach:
Phase 1: Defer YAML Functions
Before merging, Atmos walks through each configuration file and identifies YAML functions:
!template- Go template rendering!terraform.output- Output from other components!terraform.state- State file queries!store.get/!store- Store lookups!exec- Command execution!env- Environment variable expansion
These functions are temporarily replaced with nil placeholders and stored in a deferred merge context with their:
- Original value
- Path in the configuration tree
- Precedence order (which file they came from)
Phase 2: Merge Without Conflicts
With YAML functions deferred, all values are simple types (strings, numbers, maps, lists). The normal merge process completes without type conflicts.
Phase 3: Apply Deferred Merges
After the standard merge completes, Atmos processes the deferred functions:
- Sorts them by precedence (based on import order - base configurations have lower precedence, overrides have higher precedence)
- Merges multiple values for the same path
- Applies the final merged values back to the configuration
The result: YAML functions work correctly across inheritance hierarchies without type conflicts.
Real-World Example
Here's a complete example showing the deferred merge in action:
Catalog defaults:
# catalog/database/defaults.yaml
components:
terraform:
rds:
vars:
engine: postgres
storage: 100
config:
max_connections: !template '{{ .settings.db_connections }}'
Production override:
# stacks/prod/databases.yaml
import:
- catalog/database/defaults
components:
terraform:
rds:
vars:
storage: !template '{{ .settings.prod_storage }}'
config:
max_connections: !template '{{ .settings.prod_connections }}'
backup_retention: 30
Processing flow:
-
Deferral phase:
- Catalog:
max_connections→ deferred (precedence 0) - Override:
storage→ deferred (precedence 1) - Override:
max_connections→ deferred (precedence 1)
- Catalog:
-
Merge phase:
vars:
engine: postgres # Simple value, merged normally
storage: nil # Deferred, no conflict
config:
max_connections: nil # Deferred, no conflict
backup_retention: 30 # Simple value, merged normally -
Apply deferred phase:
storagehas one deferred value → applied directlymax_connectionshas two deferred values → precedence 1 (override) wins- Result: All templates preserved, hierarchy respected
List Merge Strategy Support
The deferred merge system fully respects Atmos's list_merge_strategy setting:
- Replace (Default)
- Append
- Merge
# Base
tags: !template '{{ .settings.base_tags }}'
# Override
tags: !template '{{ .settings.override_tags }}'
# Result: Override wins
tags: !template '{{ .settings.override_tags }}'
settings:
list_merge_strategy: append
# Base
security_groups: !template '{{ .settings.base_sgs }}'
# Override
security_groups: !template '{{ .settings.additional_sgs }}'
# Result: Both templates evaluated and concatenated
security_groups: [sg-base1, sg-base2, sg-add1, sg-add2]
settings:
list_merge_strategy: merge
# Base
listeners:
- port: !template '{{ .settings.http_port }}'
protocol: HTTP
# Override
listeners:
- port: !template '{{ .settings.https_port }}'
protocol: HTTPS
# Result: Deep merge by index
listeners:
- port: 443 # Override wins
protocol: HTTPS # Override wins
For technical details on the implementation, see the Deferred YAML Function Merge Handling PRD.
Get Involved
This infrastructure improvement enables more flexible configuration patterns in Atmos. We'd love to hear about:
- Real-world scenarios where type conflicts prevented you from using YAML functions
- Performance impact on your large-scale configurations
- Use cases we should prioritize for the stack processor integration
Share your feedback in GitHub Discussions or open an issue for bugs or feature requests.
Learn More
- Deferred YAML Function Merge Handling PRD - Complete technical specification and implementation details
- Atmos YAML Functions Documentation - Guide to using YAML functions in stack configurations
