YQ Default Values Now Work Reliably in YAML Functions
We've fixed an issue where YQ default values (using the // fallback operator) in !terraform.state and !terraform.output YAML functions were not being evaluated when components weren't provisioned or outputs didn't exist.
What Changed
YQ expressions with default values now work correctly in all scenarios:
# This now works reliably when vpc component isn't provisioned
vars:
vpc_id: !terraform.output vpc {{ .stack }} ".vpc_id // ""default-vpc"""
subnets: !terraform.state vpc {{ .stack }} ".subnets // [""subnet-1"", ""subnet-2""]"
The Problem
Previously, when a terraform component wasn't provisioned or an output didn't exist, the YAML function wrappers would return nil or exit before the YQ pipeline could evaluate default expressions. This caused sporadic failures where users expected the YQ // operator to provide fallback values.
The issue manifested as:
- Stack configurations failing when referencing unprovisioned components
- Inconsistent behavior depending on component state
- No way to gracefully handle missing outputs with defaults
The Solution
We refactored the YAML function processing to:
-
Properly classify errors: Distinguish between recoverable errors (component not provisioned, output missing) and non-recoverable API errors (S3 timeouts, network failures)
-
Evaluate YQ defaults for recoverable errors: When a component isn't provisioned but the expression includes a default (
//), evaluate the YQ expression against an empty map to extract the default value -
Propagate API errors: Infrastructure failures like S3 timeouts correctly propagate as errors rather than silently using defaults
Examples
String Defaults
vars:
bucket_name: !terraform.output s3 {{ .stack }} ".bucket_name // ""default-bucket"""
If the s3 component isn't provisioned, bucket_name will be "default-bucket".
List Defaults
vars:
subnets: !terraform.state vpc {{ .stack }} ".private_subnets // [""subnet-a"", ""subnet-b""]"
If the vpc component isn't provisioned, subnets will be ["subnet-a", "subnet-b"].
Map Defaults
vars:
tags: !terraform.output common {{ .stack }} ".tags // {\"env\": \"dev\", \"team\": \"platform\"}"
No Default (Error on Missing)
vars:
# This will error if vpc component isn't provisioned (expected behavior)
vpc_id: !terraform.output vpc {{ .stack }} .vpc_id
Error Handling Behavior
| Scenario | Has Default (//) | Result |
|---|---|---|
| Component not provisioned | Yes | Uses default value |
| Component not provisioned | No | Returns error |
| Output missing | Yes | Uses default value |
| Output missing | No | Returns nil |
| API error (S3 timeout) | Yes/No | Returns error |
Migration
No migration is required. Existing configurations that use YQ defaults will now work as expected. Configurations without defaults maintain their existing behavior.
