Summary
Add a new resource meshstack_building_block_definition_version that manages a building block
definition version standalone, as an alternative to the inline version_spec attribute on
meshstack_building_block_definition. To enable this, version_spec on the definition resource
becomes optional.
The new resource carries:
spec — schema-identical to the current version_spec of meshstack_building_block_definition.
building_block_definition_ref — a { kind, uuid } ref to the owning definition (the context
that version_spec gets implicitly today from being nested in the definition resource).
- computed read-only version outputs —
versions, version_latest, version_latest_release
(same shape as on the definition resource), used to wire
meshstack_building_block_v2.spec.building_block_definition_version_ref.
…and no metadata block.
Motivation: the WIF chicken-and-egg problem
Building blocks can authenticate to a cloud provider via Workload Identity Federation (WIF)
instead of long-lived secrets. The cloud-side trust policy must pin the federated subject to the
definition's UUID, while the definition's version_spec.inputs must carry the role ARN
that the cloud-side module produces from that trust. That mutual dependency cannot be expressed
when both halves live in the same resource.
See ../meshstack-hub/modules/aws/s3_bucket/meshstack_integration.tf: the WIF subject is pinned
to meshstack_building_block_definition.this.metadata.uuid, the backplane module mints a role
ARN from it, and the ARN flows back into version_spec.inputs.AWS_ROLE_ARN.argument. Because
version_spec lives inside the resource that exports metadata.uuid, Terraform sees a
self-referential cycle (Error: Cycle: ...) — there is no ordering that satisfies it.
meshstack_building_block_definition.this ──(metadata.uuid)──▶ module.backplane
▲ │
└──────────(version_spec.inputs.AWS_ROLE_ARN)─────────────┘
Splitting the version into its own resource breaks the cycle into a clean DAG:
meshstack_building_block_definition.this (metadata + spec only) ──▶ metadata.uuid
│
├──▶ module.backplane (consumes uuid, mints role ARN)
│ │
▼ ▼
meshstack_building_block_definition_version.this
building_block_definition_ref = { uuid = ...this.metadata.uuid }
spec = { inputs = { AWS_ROLE_ARN = { argument = jsonencode(module.backplane...role_arn) } } }
Design notes
-
Reuse, don't duplicate. The spec attribute of the new resource is schema- and
model-identical to the existing version_spec. The version schema block, the TF↔DTO
conversion (incl. secret/sensitive handling), the manual-output reconciliation, content
hashing, and the draft/released transition rules should be refactored out of
building_block_definition_* so both the inline and the standalone path share one
implementation. This issue is mostly a refactoring + thin new-resource wrapper, not a
reimplementation.
-
version_spec becomes optional. A definition always has at least one (backend
auto-created) version, so when version_spec is omitted the companion resource takes over
managing it via building_block_definition_ref.
-
building_block_definition_ref replaces the context the inline path infers implicitly:
the owning definition's UUID, and the ownedByWorkspace needed by the version API (derived by
reading the referenced definition, since there is no metadata block here).
-
Version outputs live here. The companion resource exposes the computed versions,
version_latest, and version_latest_release outputs (same shape as on the definition
resource today) — these are what you wire into
meshstack_building_block_v2.spec.building_block_definition_version_ref.
-
No-op delete, with re-adoption on re-create. The version API has no delete (versions are
removed only with the definition). Destroying the resource is therefore a no-op that just drops
it from state; the backend version lingers. When a version resource is (re-)created against a
definition that already has versions, it must pick up / import the existing latest version
from the List API rather than creating a duplicate.
-
Preview API. The version client is v1-preview, so the resource description must append
previewDisclaimer() and the cross-repo -preview compatibility handshake applies.
1:1 definition ↔ version
The relationship is strictly 1:1: a definition is managed by exactly one version source —
either the inline version_spec or a single meshstack_building_block_definition_version
resource. Disallowed: inline version_spec together with a companion resource, or more than one
companion resource pointing at the same building_block_definition_ref.uuid. (Successive backend
versions from the draft→release→new-draft flow are revisions of the one managed version, not
multiple concurrent Terraform resources.) Cross-resource validation isn't generally possible in
the plugin framework, so this is mainly a documentation + example concern, with a runtime guard
where feasible.
Consequence: when the version is owned by the companion resource, the definition resource's
computed version_latest, version_latest_release, and versions outputs are always null —
the definition no longer tracks a version of its own. Use the matching outputs on the companion
resource instead.
Summary
Add a new resource
meshstack_building_block_definition_versionthat manages a building blockdefinition version standalone, as an alternative to the inline
version_specattribute onmeshstack_building_block_definition. To enable this,version_specon the definition resourcebecomes optional.
The new resource carries:
spec— schema-identical to the currentversion_specofmeshstack_building_block_definition.building_block_definition_ref— a{ kind, uuid }ref to the owning definition (the contextthat
version_specgets implicitly today from being nested in the definition resource).versions,version_latest,version_latest_release(same shape as on the definition resource), used to wire
meshstack_building_block_v2.spec.building_block_definition_version_ref.…and no
metadatablock.Motivation: the WIF chicken-and-egg problem
Building blocks can authenticate to a cloud provider via Workload Identity Federation (WIF)
instead of long-lived secrets. The cloud-side trust policy must pin the federated subject to the
definition's UUID, while the definition's
version_spec.inputsmust carry the role ARNthat the cloud-side module produces from that trust. That mutual dependency cannot be expressed
when both halves live in the same resource.
See
../meshstack-hub/modules/aws/s3_bucket/meshstack_integration.tf: the WIF subject is pinnedto
meshstack_building_block_definition.this.metadata.uuid, the backplane module mints a roleARN from it, and the ARN flows back into
version_spec.inputs.AWS_ROLE_ARN.argument. Becauseversion_speclives inside the resource that exportsmetadata.uuid, Terraform sees aself-referential cycle (
Error: Cycle: ...) — there is no ordering that satisfies it.Splitting the version into its own resource breaks the cycle into a clean DAG:
Design notes
Reuse, don't duplicate. The
specattribute of the new resource is schema- andmodel-identical to the existing
version_spec. The version schema block, the TF↔DTOconversion (incl. secret/sensitive handling), the manual-output reconciliation, content
hashing, and the draft/released transition rules should be refactored out of
building_block_definition_*so both the inline and the standalone path share oneimplementation. This issue is mostly a refactoring + thin new-resource wrapper, not a
reimplementation.
version_specbecomes optional. A definition always has at least one (backendauto-created) version, so when
version_specis omitted the companion resource takes overmanaging it via
building_block_definition_ref.building_block_definition_refreplaces the context the inline path infers implicitly:the owning definition's UUID, and the
ownedByWorkspaceneeded by the version API (derived byreading the referenced definition, since there is no
metadatablock here).Version outputs live here. The companion resource exposes the computed
versions,version_latest, andversion_latest_releaseoutputs (same shape as on the definitionresource today) — these are what you wire into
meshstack_building_block_v2.spec.building_block_definition_version_ref.No-op delete, with re-adoption on re-create. The version API has no delete (versions are
removed only with the definition). Destroying the resource is therefore a no-op that just drops
it from state; the backend version lingers. When a version resource is (re-)created against a
definition that already has versions, it must pick up / import the existing latest version
from the List API rather than creating a duplicate.
Preview API. The version client is
v1-preview, so the resource description must appendpreviewDisclaimer()and the cross-repo-previewcompatibility handshake applies.1:1 definition ↔ version
The relationship is strictly 1:1: a definition is managed by exactly one version source —
either the inline
version_specor a singlemeshstack_building_block_definition_versionresource. Disallowed: inline
version_spectogether with a companion resource, or more than onecompanion resource pointing at the same
building_block_definition_ref.uuid. (Successive backendversions from the draft→release→new-draft flow are revisions of the one managed version, not
multiple concurrent Terraform resources.) Cross-resource validation isn't generally possible in
the plugin framework, so this is mainly a documentation + example concern, with a runtime guard
where feasible.
Consequence: when the version is owned by the companion resource, the definition resource's
computed
version_latest,version_latest_release, andversionsoutputs are always null —the definition no longer tracks a version of its own. Use the matching outputs on the companion
resource instead.