terraform show -json hides resources in child modules from naive jq queries
Needed to check the force_destroy setting on an S3 bucket before running terraform destroy. The bucket lives inside module.bedrock_tenant. Should be a one-liner with terraform show -json and jq.
The broken query
terraform show -json | jq -r \
'.values.root_module.resources[]
| select(.address == "module.bedrock_tenant.aws_s3_bucket.vectors")
| .values.force_destroy'
Returns nothing. No error, no output. The resource exists in the state – terraform state list shows it. But the jq query comes back empty.
The problem
terraform show -json structures state as a tree. Resources defined at the root level live in .values.root_module.resources[]. Resources inside a module call live in .values.root_module.child_modules[].resources[]. Modules calling other modules nest further: .child_modules[].child_modules[].resources[].
The naive query only searches one level. The resource is one level deeper, inside a child module. jq doesn’t search recursively by default – you get exactly the path you asked for.
The fix
Use jq’s recurse to walk the entire module tree:
terraform show -json | jq -r \
'.values.root_module
| recurse(.child_modules[]?)
| .resources[]?
| select(.address == "module.bedrock_tenant.aws_s3_bucket.vectors")
| .values.force_destroy'
recurse(.child_modules[]?) starts at root_module and walks every child module at every depth. The ? operator makes the expression non-fatal when a module has no child_modules key. .resources[]? catches resources at each level, again with ? to handle modules that have children but no direct resources.
Handling missing state
In CI, this query runs before the resource might exist (first-time tenant creation, no prior state). An empty result is valid, not an error. Wrap it with a default:
VFD=$(terraform show -json | jq -r \
'.values.root_module
| recurse(.child_modules[]?)
| .resources[]?
| select(.address == "module.bedrock_tenant.aws_s3_bucket.vectors")
| .values.force_destroy' || true)
FORCE_DESTROY="${VFD:-false}"
|| true prevents set -e from killing the script on empty output. The parameter expansion defaults to false when the variable is empty or unset.
The general rule
Any time you query terraform show -json for a specific resource and get nothing back, check whether the resource is inside a module. If it is, your jq path needs to walk child modules. recurse(.child_modules[]?) is the universal fix – it works regardless of nesting depth.