Deep merge functionality for complex Terraform configurations, supporting recursive merging of maps and objects with fine-grained control over merge behavior.
Terraform's built-in merge()
function only performs shallow merging, which means nested structures are completely replaced rather than intelligently combined. This provider enables:
- Recursive merging of deeply nested structures
- Flexible merge strategies with multiple modes of operation
- List concatenation instead of replacement
- Null value handling for optional configurations
- Type-safe merging that preserves your data structure integrity
- Environment-specific configurations: Merge base configurations with environment overrides
- Module defaults: Provide sensible defaults that users can partially override
- Multi-team configurations: Combine configurations from different teams or sources
- Dynamic resource generation: Build complex resources from modular configuration fragments
terraform {
required_providers {
deepmerge = {
source = "isometry/deepmerge"
version = "~> 1.0"
}
}
}
provider "deepmerge" {}
A distinctive feature of mergo
is its use of string arguments to control merge strategies. Simply append one or more control strings after your maps to change how the merge operates:
Mode | Description | Use Case |
---|---|---|
"override" / "replace" (default) |
Later values replace earlier ones | Standard configuration layering |
"no_override" |
Earlier values are preserved | Setting immutable defaults |
"no_null_override" |
Null values don't replace existing values | Optional configuration fields |
"append" / "append_lists" |
Lists are concatenated instead of replaced | Accumulating features, rules, or tags |
"union" / "union_lists" |
Lists are merged as sets (unique elements) | Deduplicating tags, IPs, or identifiers |
locals {
config1 = { a = 1, b = { x = 10, y = 20 } }
config2 = { a = 2, b = { y = 30, z = 40 } }
result = provider::deepmerge::mergo(local.config1, local.config2)
# Result: { a = 2, b = { x = 10, y = 30, z = 40 } }
}
locals {
defaults = { timeout = 30, retries = 3, debug = false }
user_config = { timeout = 60, debug = true, custom = "value" }
result = provider::deepmerge::mergo(local.defaults, local.user_config, "no_override")
# Result: { timeout = 30, retries = 3, debug = false, custom = "value" }
# Note: defaults are preserved, only new keys from user_config are added
}
locals {
base = { name = "service", port = 8080, optional_setting = "enabled" }
overrides = { port = 9090, optional_setting = null }
result = provider::deepmerge::mergo(local.base, local.overrides, "no_null_override")
# Result: { name = "service", port = 9090, optional_setting = "enabled" }
# Note: null doesn't override the existing value
}
locals {
base_rules = {
firewall = {
allowed_ports = [22, 80, 443]
denied_ips = ["192.168.1.100"]
}
}
additional_rules = {
firewall = {
allowed_ports = [8080, 9090]
denied_ips = ["192.168.1.101", "192.168.1.102"]
}
}
result = provider::deepmerge::mergo(local.base_rules, local.additional_rules, "append")
# Result: {
# firewall = {
# allowed_ports = [22, 80, 443, 8080, 9090]
# denied_ips = ["192.168.1.100", "192.168.1.101", "192.168.1.102"]
# }
# }
}
locals {
base_tags = {
security = {
allowed_ports = [22, 80, 443]
tags = ["security", "prod", "critical"]
}
}
additional_tags = {
security = {
allowed_ports = [8080, 80, 9090]
tags = ["monitoring", "prod", "audit"]
}
}
result = provider::deepmerge::mergo(local.base_tags, local.additional_tags, "union_lists")
# Result: {
# security = {
# allowed_ports = [22, 80, 443, 8080, 9090] # Unique elements only
# tags = ["security", "prod", "critical", "monitoring", "audit"] # Duplicates removed
# }
# }
}
See docs/functions/mergo.md for detailed examples.
Feature | Terraform merge() |
deepmerge::mergo() |
---|---|---|
Shallow merge | ✅ | ✅ |
Deep merge | ❌ | ✅ |
List handling | Replace only | Replace or append |
Null handling | Overwrites | Configurable |
Merge strategies | None | Multiple modes |
Performance | Faster for flat structures | Optimized for nested structures |
Use Terraform's merge()
when:
- Working with flat maps
- Performance is critical for large, flat structures
- You want complete replacement of nested values
Use deepmerge::mergo()
when:
- Working with nested configurations
- You need fine-grained control over merge behavior
- Building layered configurations
- Handling optional/nullable values
-
Unexpected list behavior: By default, lists are replaced. Use
"append"
mode to concatenate them. -
Null values overriding: Use
"no_null_override"
mode to prevent nulls from replacing existing values. -
Large data structures: For very large nested structures, consider breaking them into smaller, manageable pieces.
If you wish to work on the provider, you'll first need Go installed on your machine (see Requirements above).
To compile the provider, run go install
. This will build the provider and put the provider binary in the $GOPATH/bin
directory.
To generate or update documentation, run go generate
.
In order to run the full suite of Acceptance tests, run make testacc
.
Note: Acceptance tests create real resources, and often cost money to run.
make testacc
Contributions are welcome! Please feel free to submit a Pull Request.
This provider is distributed under the Mozilla Public License 2.0.