fizz.today

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.

#terraform #jq