Skip to content

Add a companion meshstack_building_block_definition_version resource #198

@grubmeshi

Description

@grubmeshi

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions