Skip to content

[FEATURE] Support lets block in define bundle stack for intermediate variables and tm_bundle access in metadata { path } #2320

@mariux

Description

@mariux

Is your feature request related to a problem? Please describe.

When building multi-account, multi-region infrastructure with Terramate Bundles, we need to include account and region path segments in the generated stack paths. These values come from other organizational Bundles (Account, Region) that are looked up via tm_bundle.

The problem is that tm_bundle and tm_bundles functions are not available in metadata { path } expressions inside define bundle stack blocks — they only work in component { inputs }.

This forces two workarounds:

  1. Encoding compound values as split-able strings. Instead of looking up the Region Bundle directly in the path, we store the region input as account_alias/region (e.g., demo-account/us-east-1) and extract the parts with tm_split:

    path = "/stacks/${bundle.environment.id}/${tm_split("/", bundle.input.region.value)[0]}/${tm_split("/", bundle.input.region.value)[1]}/fargate-clusters/${tm_slug(bundle.input.name.value)}/vpc"

    This is fragile, verbose, and not self-documenting. The / separator is a convention with no validation.

  2. Dependent Bundles cannot inherit values for paths. An ECS Service Bundle depends on an ECS Cluster Bundle that already selected a region. Ideally, the service should derive its account/region path segments from the cluster without asking the user again. But since tm_bundle is unavailable in metadata { path }, the service must have its own redundant region input that the user selects separately.

Describe the solution you'd like

A lets block inside define bundle stack that:

  • Supports tm_bundle and tm_bundles function calls
  • Exposes intermediate variables to all other blocks within the same stack definition (metadata, component)
  • Allows chaining lookups across Bundles

Example:

define bundle stack "vpc" {
  lets {
    region_bundle = tm_bundle("example.com/region/v1", bundle.input.region.value, bundle.environment.id)
    account_alias = lets.region_bundle.export.account_alias.value
    region        = lets.region_bundle.export.region.value
  }

  metadata {
    path = "/stacks/${bundle.environment.id}/${lets.account_alias}/${lets.region}/fargate-clusters/${tm_slug(bundle.input.name.value)}/vpc"
  }

  component "vpc" {
    source = "/components/example.com/terramate-aws-vpc/v1"
    inputs = {
      name = tm_join("-", [tm_slug(bundle.input.name.value), bundle.environment.id])
      cidr = bundle.input.vpc_cidr.value
    }
  }
}

This would also enable true inheritance for dependent Bundles. The ECS Service could derive account/region from the cluster without its own input:

define bundle stack "ecs-service" {
  lets {
    cluster = tm_bundle("example.com/tf-aws-complete-ecs-fargate-cluster/v1", bundle.input.cluster_slug.value, bundle.environment.id)
    region_bundle = tm_bundle("example.com/region/v1", lets.cluster.export.region_ref.value, bundle.environment.id)
    account_alias = lets.region_bundle.export.account_alias.value
    region        = lets.region_bundle.export.region.value
  }

  metadata {
    path = "/stacks/${bundle.environment.id}/${lets.account_alias}/${lets.region}/fargate-clusters/${bundle.input.cluster_slug.value}/workloads/${tm_slug(bundle.input.service_name.value)}"
  }
}

Describe alternatives you've considered

  • Making tm_bundle/tm_bundles available directly in metadata { path } — This would solve the immediate problem but without intermediate variables, complex lookups would still result in very long, repeated inline expressions.
  • Adding tm_bundle/tm_bundles support to define bundle { export } blocks — This would allow Bundles to export resolved values from other Bundles, shifting the lookup from path expressions to exports. However, it doesn't eliminate repeated expressions within a single stack definition.

Additional context

  • Terramate Version: v0.17.0-rc2
  • This came up while adding multi-account, multi-region hierarchy to the terramate-catalyst-examples repository (PR #49)
  • The lets concept mirrors Terraform's locals block and is already familiar to the target audience

Metadata

Metadata

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions