From 106832b855e6d0c010e65aff74492b85295ca83e Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Fri, 7 Aug 2026 17:47:56 +0200 Subject: [PATCH 1/4] feat(meshstack): add composition building block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reference implementation of the composition pattern: a building block that provisions meshObjects through the meshStack API with its run's ephemeral API key rather than cloud resources. It creates a building block definition and a building block from it, which meshStack attributes to the creating block, so it doubles as an end-to-end check of that provenance with no cloud platform involved. The created definition runs the hub's `link` building block instead of an implementation of its own — `link` provisions nothing but a terraform_data and needs neither a cloud provider nor an operator, which keeps the whole chain automatic. Co-Authored-By: Claude Opus 5 (1M context) --- .../composition/buildingblock/README.md | 86 +++++++++ .../composition/buildingblock/logo.png | Bin 0 -> 45498 bytes .../composition/buildingblock/main.tf | 107 +++++++++++ .../composition/buildingblock/outputs.tf | 23 +++ .../composition/buildingblock/provider.tf | 4 + .../composition/buildingblock/variables.tf | 19 ++ .../composition/buildingblock/versions.tf | 8 + .../composition/meshstack_integration.tf | 177 ++++++++++++++++++ 8 files changed, 424 insertions(+) create mode 100644 modules/meshstack/composition/buildingblock/README.md create mode 100644 modules/meshstack/composition/buildingblock/logo.png create mode 100644 modules/meshstack/composition/buildingblock/main.tf create mode 100644 modules/meshstack/composition/buildingblock/outputs.tf create mode 100644 modules/meshstack/composition/buildingblock/provider.tf create mode 100644 modules/meshstack/composition/buildingblock/variables.tf create mode 100644 modules/meshstack/composition/buildingblock/versions.tf create mode 100644 modules/meshstack/composition/meshstack_integration.tf diff --git a/modules/meshstack/composition/buildingblock/README.md b/modules/meshstack/composition/buildingblock/README.md new file mode 100644 index 00000000..10323f8c --- /dev/null +++ b/modules/meshstack/composition/buildingblock/README.md @@ -0,0 +1,86 @@ +--- +name: meshStack Composition Building Block +supportedPlatforms: + - meshstack +description: | + Reference building block demonstrating the composition pattern: it uses the run's + ephemeral API key to create a building block definition and a building block from it. +# Creates only meshObjects through the meshStack API, so there is nothing to set up cloud-side. +requiresBackplane: false +--- +# meshStack Composition Building Block + +This building block is a reference implementation of the **composition** pattern: a building block +that provisions other meshObjects through the meshStack API instead of cloud resources. It creates a +`meshBuildingBlockDefinition` in the consuming workspace and a `meshBuildingBlock` from that +definition. + +The created definition runs the hub's [`link`](../../link) building block, which provisions nothing +but a `terraform_data` and needs neither a cloud provider nor an operator. Reusing it keeps the whole +chain automatic and avoids inventing a throwaway implementation just to have something to create. + +## How it works + +The building block definition declares `permissions`, so meshStack issues an **ephemeral API key** +scoped to the consuming workspace for the duration of each run and injects it as `MESHSTACK_ENDPOINT` +/ `MESHSTACK_API_TOKEN`. The `meshstack` provider picks those up with no explicit configuration, so +`provider "meshstack" {}` is all this module needs. + +Because both meshObjects are created with that key, meshStack records it as their creation author and +surfaces **"created by building block"** provenance on the definition and the building block, linking +back to the composition that created them. That makes this module a convenient end-to-end check of +that provenance without any cloud platform involved. + +## Permissions + +| Permission | Why | +|---|---| +| `BUILDINGBLOCKDEFINITION_LIST` / `_SAVE` / `_DELETE` | Manage the created building block definition | +| `BUILDINGBLOCK_LIST` / `_SAVE` / `_DELETE` | Manage the created building block | + +The created definition itself declares no permissions, so it stays within meshStack's privilege +escalation guard (a nested definition may only request a subset of its parent's permissions). + +## Notes + +- This module does not wait for the created building block's run. That run needs a terraform runner, + and a stack with a single one cannot start it before this run returns. Provenance is recorded when + the block is created, so nothing here depends on the result. +- `hub_git_ref` is wired in as a static input from the composition's own `var.hub.git_ref`, so the + created definition clones the `link` module from the same hub revision. + + +## Requirements + +| Name | Version | +|------|---------| +| [meshstack](#requirement\_meshstack) | >= 0.24.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [meshstack_building_block.created](https://registry.terraform.io/providers/meshcloud/meshstack/latest/docs/resources/building_block) | resource | +| [meshstack_building_block_definition.created](https://registry.terraform.io/providers/meshcloud/meshstack/latest/docs/resources/building_block_definition) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [hub\_git\_ref](#input\_hub\_git\_ref) | Hub reference the created building block definition clones its implementation from. Wired in as a static input from the composition's own `var.hub.git_ref`, so both definitions stay on the same hub revision. | `string` | n/a | yes | +| [link\_url](#input\_link\_url) | Target of the link the created building block publishes. | `string` | n/a | yes | +| [name](#input\_name) | Name used for the building block definition and building block this composition creates. | `string` | n/a | yes | +| [workspace\_identifier](#input\_workspace\_identifier) | Workspace the created building block definition is owned by and the created building block is attached to. Wired in as a WORKSPACE\_IDENTIFIER input, so it is always the consuming workspace — the same one the run's ephemeral API key is scoped to. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [created\_building\_block\_definition\_uuid](#output\_created\_building\_block\_definition\_uuid) | UUID of the building block definition this composition created. | +| [created\_building\_block\_uuid](#output\_created\_building\_block\_uuid) | UUID of the building block this composition created. | +| [summary](#output\_summary) | Markdown summary shown on the building block's detail page. | + diff --git a/modules/meshstack/composition/buildingblock/logo.png b/modules/meshstack/composition/buildingblock/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..634d5dcb2523b984e21b427281672441a1481f9d GIT binary patch literal 45498 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%0DKrt;&ZsQO|M`{|cJ&6?z~s-oZ(SSbk13C-M~; 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 h4pw~xtEC^)^$& Date: Fri, 7 Aug 2026 17:56:09 +0200 Subject: [PATCH 2/4] fix(meshstack): reference version_latest in the composition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The created definition's version stays DRAFT: releasing needs admin approval, which the run's ephemeral key — a plain workspace key — cannot obtain. So draft = false left the version DRAFT anyway and version_latest_release null, failing the apply with "Attempt to get attribute from null value". Declare the draft honestly and reference version_latest instead, which a draft permits because the definition and the building block's target are the same workspace (BuildingBlockCreationValidator.requireAccess's selfOwning branch). Co-Authored-By: Claude Opus 5 (1M context) --- .../meshstack/composition/buildingblock/README.md | 5 +++++ .../meshstack/composition/buildingblock/main.tf | 14 ++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/modules/meshstack/composition/buildingblock/README.md b/modules/meshstack/composition/buildingblock/README.md index 10323f8c..42dcfed9 100644 --- a/modules/meshstack/composition/buildingblock/README.md +++ b/modules/meshstack/composition/buildingblock/README.md @@ -48,6 +48,11 @@ escalation guard (a nested definition may only request a subset of its parent's the block is created, so nothing here depends on the result. - `hub_git_ref` is wired in as a static input from the composition's own `var.hub.git_ref`, so the created definition clones the `link` module from the same hub revision. +- The created definition's version stays a **draft**. Releasing needs admin approval, which the run's + ephemeral key — a plain workspace key — cannot obtain, so `draft = false` would leave the version + `DRAFT` regardless, warn on every run, and leave `version_latest_release` null. The created + building block therefore references `version_latest`, which a draft permits because the definition + and the building block's target are the same workspace. ## Requirements diff --git a/modules/meshstack/composition/buildingblock/main.tf b/modules/meshstack/composition/buildingblock/main.tf index 6929851b..a9330e85 100644 --- a/modules/meshstack/composition/buildingblock/main.tf +++ b/modules/meshstack/composition/buildingblock/main.tf @@ -25,8 +25,12 @@ resource "meshstack_building_block_definition" "created" { } version_spec = { - # Released, not a draft: meshstack_building_block.created below is created from this version. - draft = false + # Stays a draft: releasing a version needs admin approval, which the run's ephemeral key — a + # plain workspace key — cannot obtain. Setting draft = false would leave the version DRAFT + # anyway, warn on every run, and leave version_latest_release null. The building block below can + # still be created from a draft because the definition and the target are the same workspace, + # which satisfies BuildingBlockCreationValidator.requireAccess's `selfOwning` branch. + draft = true deletion_mode = "DELETE" implementation = { @@ -84,9 +88,11 @@ resource "meshstack_building_block_definition" "created" { resource "meshstack_building_block" "created" { spec = { + # version_latest, not version_latest_release: the definition above stays a draft, so + # version_latest_release is null. building_block_definition_version_ref = { - uuid = meshstack_building_block_definition.created.version_latest_release.uuid - content_hash = meshstack_building_block_definition.created.version_latest_release.content_hash + uuid = meshstack_building_block_definition.created.version_latest.uuid + content_hash = meshstack_building_block_definition.created.version_latest.content_hash } display_name = var.name From f893194abc48439ce293acd139d47767be04a3e6 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Fri, 7 Aug 2026 18:07:26 +0200 Subject: [PATCH 3/4] refactor(meshstack): name the composition and its created objects distinctly The composition and the objects it created were both called "Composition Demo", which made the two building block definitions in the panel hard to tell apart. Name the composition "Composition Demo" and the objects it creates after their implementation, "Link", via a link_name input defaulting to that. Renames the `name` input to `link_name` so it pairs with `link_url` and reads as belonging to the created objects rather than to the composition itself. Co-Authored-By: Claude Opus 5 (1M context) --- .../composition/buildingblock/README.md | 8 +++++--- .../meshstack/composition/buildingblock/main.tf | 10 +++++----- .../composition/buildingblock/outputs.tf | 6 +++--- .../composition/buildingblock/variables.tf | 12 ++++++------ .../composition/meshstack_integration.tf | 16 +++++++++++----- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/modules/meshstack/composition/buildingblock/README.md b/modules/meshstack/composition/buildingblock/README.md index 42dcfed9..4e801ac4 100644 --- a/modules/meshstack/composition/buildingblock/README.md +++ b/modules/meshstack/composition/buildingblock/README.md @@ -1,5 +1,5 @@ --- -name: meshStack Composition Building Block +name: Composition Demo supportedPlatforms: - meshstack description: | @@ -8,7 +8,7 @@ description: | # Creates only meshObjects through the meshStack API, so there is nothing to set up cloud-side. requiresBackplane: false --- -# meshStack Composition Building Block +# Composition Demo This building block is a reference implementation of the **composition** pattern: a building block that provisions other meshObjects through the meshStack API instead of cloud resources. It creates a @@ -18,6 +18,8 @@ definition. The created definition runs the hub's [`link`](../../link) building block, which provisions nothing but a `terraform_data` and needs neither a cloud provider nor an operator. Reusing it keeps the whole chain automatic and avoids inventing a throwaway implementation just to have something to create. +Both created meshObjects are named after `link_name` (default `Link`) to keep them distinguishable +from the `Composition Demo` block that created them. ## How it works @@ -77,8 +79,8 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [hub\_git\_ref](#input\_hub\_git\_ref) | Hub reference the created building block definition clones its implementation from. Wired in as a static input from the composition's own `var.hub.git_ref`, so both definitions stay on the same hub revision. | `string` | n/a | yes | +| [link\_name](#input\_link\_name) | Name given to the building block definition and building block this composition creates. | `string` | n/a | yes | | [link\_url](#input\_link\_url) | Target of the link the created building block publishes. | `string` | n/a | yes | -| [name](#input\_name) | Name used for the building block definition and building block this composition creates. | `string` | n/a | yes | | [workspace\_identifier](#input\_workspace\_identifier) | Workspace the created building block definition is owned by and the created building block is attached to. Wired in as a WORKSPACE\_IDENTIFIER input, so it is always the consuming workspace — the same one the run's ephemeral API key is scoped to. | `string` | n/a | yes | ## Outputs diff --git a/modules/meshstack/composition/buildingblock/main.tf b/modules/meshstack/composition/buildingblock/main.tf index a9330e85..7dae98ba 100644 --- a/modules/meshstack/composition/buildingblock/main.tf +++ b/modules/meshstack/composition/buildingblock/main.tf @@ -11,8 +11,8 @@ resource "meshstack_building_block_definition" "created" { } spec = { - display_name = var.name - description = "Link building block definition created by the meshStack Composition building block." + display_name = var.link_name + description = "Link building block definition created by the Composition Demo building block." target_type = "WORKSPACE_LEVEL" run_transparency = true @@ -59,7 +59,7 @@ resource "meshstack_building_block_definition" "created" { description = "Human-readable name of the linked resource." type = "STRING" assignment_type = "STATIC" - argument = jsonencode(var.name) + argument = jsonencode(var.link_name) } # Empty falls back to the summary the link module generates from title and url. summary = { @@ -73,7 +73,7 @@ resource "meshstack_building_block_definition" "created" { outputs = { url = { - display_name = var.name + display_name = var.link_name type = "STRING" assignment_type = "RESOURCE_URL" } @@ -95,7 +95,7 @@ resource "meshstack_building_block" "created" { content_hash = meshstack_building_block_definition.created.version_latest.content_hash } - display_name = var.name + display_name = var.link_name target_ref = { kind = "meshWorkspace" diff --git a/modules/meshstack/composition/buildingblock/outputs.tf b/modules/meshstack/composition/buildingblock/outputs.tf index b0bd2163..e35a573b 100644 --- a/modules/meshstack/composition/buildingblock/outputs.tf +++ b/modules/meshstack/composition/buildingblock/outputs.tf @@ -10,13 +10,13 @@ output "created_building_block_uuid" { output "summary" { value = chomp(<<-EOT - Created a link building block definition and a building block from it. Both show this building + Created a Link building block definition and a building block from it. Both show this building block as their creator. | meshObject | Name | UUID | |---|---|---| - | Building Block Definition | ${var.name} | `${meshstack_building_block_definition.created.metadata.uuid}` | - | Building Block | ${var.name} | `${meshstack_building_block.created.metadata.uuid}` | + | Building Block Definition | ${var.link_name} | `${meshstack_building_block_definition.created.metadata.uuid}` | + | Building Block | ${var.link_name} | `${meshstack_building_block.created.metadata.uuid}` | EOT ) description = "Markdown summary shown on the building block's detail page." diff --git a/modules/meshstack/composition/buildingblock/variables.tf b/modules/meshstack/composition/buildingblock/variables.tf index 87bdc96c..ee51e94c 100644 --- a/modules/meshstack/composition/buildingblock/variables.tf +++ b/modules/meshstack/composition/buildingblock/variables.tf @@ -1,16 +1,16 @@ -variable "name" { +variable "link_name" { type = string - description = "Name used for the building block definition and building block this composition creates." + description = "Name given to the building block definition and building block this composition creates." } -variable "workspace_identifier" { +variable "link_url" { type = string - description = "Workspace the created building block definition is owned by and the created building block is attached to. Wired in as a WORKSPACE_IDENTIFIER input, so it is always the consuming workspace — the same one the run's ephemeral API key is scoped to." + description = "Target of the link the created building block publishes." } -variable "link_url" { +variable "workspace_identifier" { type = string - description = "Target of the link the created building block publishes." + description = "Workspace the created building block definition is owned by and the created building block is attached to. Wired in as a WORKSPACE_IDENTIFIER input, so it is always the consuming workspace — the same one the run's ephemeral API key is scoped to." } variable "hub_git_ref" { diff --git a/modules/meshstack/composition/meshstack_integration.tf b/modules/meshstack/composition/meshstack_integration.tf index 8ce2f070..e14c33f9 100644 --- a/modules/meshstack/composition/meshstack_integration.tf +++ b/modules/meshstack/composition/meshstack_integration.tf @@ -1,3 +1,9 @@ +variable "link_name" { + type = string + default = "Link" + description = "Name given to the building block definition and building block this composition creates. Both are the hub's `link` building block, hence the default." +} + variable "link_url" { type = string default = "https://docs.meshcloud.io" @@ -44,8 +50,8 @@ resource "meshstack_building_block_definition" "this" { } spec = { - display_name = "meshStack Composition Building Block" - description = "Reference building block demonstrating the composition pattern: creates a link building block definition and a building block from it using the run's ephemeral API key." + display_name = "Composition Demo" + description = "Reference building block demonstrating the composition pattern: creates a Link building block definition and a building block from it using the run's ephemeral API key." target_type = "WORKSPACE_LEVEL" symbol = "https://raw.githubusercontent.com/meshcloud/meshstack-hub/${var.hub.git_ref}/modules/meshstack/composition/buildingblock/logo.png" @@ -105,12 +111,12 @@ resource "meshstack_building_block_definition" "this" { } inputs = { - name = { + link_name = { assignment_type = "USER_INPUT" type = "STRING" - display_name = "Name" + display_name = "Link Name" description = "Name given to the building block definition and building block this composition creates." - default_value = jsonencode("Composition Demo") + default_value = jsonencode(var.link_name) } link_url = { assignment_type = "USER_INPUT" From 93702c17468f98d26237ec96d148f94b70a51936 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Fri, 7 Aug 2026 18:19:51 +0200 Subject: [PATCH 4/4] fix(meshstack): let the composition tear itself down Two teardown failures. link_name had no default, so a destroy run of a building block whose stored inputs predate the rename from `name` aborted with "No value for required variable". A destroy only has to delete the created meshObjects, and their display names have no bearing on that, so default it. The created definition used deletion_mode = DELETE, which schedules a deprovisioning run for the created building block on teardown. That run cannot start until the composition's own destroy run returns, deadlocking wherever the runner pool has a single worker. PURGE skips it and leaks nothing, since `link` provisions no infrastructure. Co-Authored-By: Claude Opus 5 (1M context) --- modules/meshstack/composition/buildingblock/README.md | 5 ++++- modules/meshstack/composition/buildingblock/main.tf | 9 +++++++-- modules/meshstack/composition/buildingblock/variables.tf | 4 ++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/meshstack/composition/buildingblock/README.md b/modules/meshstack/composition/buildingblock/README.md index 4e801ac4..6c67ff62 100644 --- a/modules/meshstack/composition/buildingblock/README.md +++ b/modules/meshstack/composition/buildingblock/README.md @@ -48,6 +48,9 @@ escalation guard (a nested definition may only request a subset of its parent's - This module does not wait for the created building block's run. That run needs a terraform runner, and a stack with a single one cannot start it before this run returns. Provenance is recorded when the block is created, so nothing here depends on the result. +- For the same reason the created definition uses `deletion_mode = "PURGE"`. `DELETE` would schedule + a deprovisioning run on teardown that cannot start until the composition's own destroy run returns. + Purging leaks nothing, since `link` provisions no infrastructure. - `hub_git_ref` is wired in as a static input from the composition's own `var.hub.git_ref`, so the created definition clones the `link` module from the same hub revision. - The created definition's version stays a **draft**. Releasing needs admin approval, which the run's @@ -79,7 +82,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [hub\_git\_ref](#input\_hub\_git\_ref) | Hub reference the created building block definition clones its implementation from. Wired in as a static input from the composition's own `var.hub.git_ref`, so both definitions stay on the same hub revision. | `string` | n/a | yes | -| [link\_name](#input\_link\_name) | Name given to the building block definition and building block this composition creates. | `string` | n/a | yes | +| [link\_name](#input\_link\_name) | Name given to the building block definition and building block this composition creates. | `string` | `"Link"` | no | | [link\_url](#input\_link\_url) | Target of the link the created building block publishes. | `string` | n/a | yes | | [workspace\_identifier](#input\_workspace\_identifier) | Workspace the created building block definition is owned by and the created building block is attached to. Wired in as a WORKSPACE\_IDENTIFIER input, so it is always the consuming workspace — the same one the run's ephemeral API key is scoped to. | `string` | n/a | yes | diff --git a/modules/meshstack/composition/buildingblock/main.tf b/modules/meshstack/composition/buildingblock/main.tf index 7dae98ba..6a4a4da3 100644 --- a/modules/meshstack/composition/buildingblock/main.tf +++ b/modules/meshstack/composition/buildingblock/main.tf @@ -30,8 +30,13 @@ resource "meshstack_building_block_definition" "created" { # anyway, warn on every run, and leave version_latest_release null. The building block below can # still be created from a draft because the definition and the target are the same workspace, # which satisfies BuildingBlockCreationValidator.requireAccess's `selfOwning` branch. - draft = true - deletion_mode = "DELETE" + draft = true + + # PURGE, not DELETE: DELETE would schedule a deprovisioning run for the building block below when + # this composition is torn down, and that run cannot start until the composition's own destroy run + # returns — a deadlock wherever the terraform runner pool has a single worker. Nothing is leaked + # by purging, because the `link` implementation provisions nothing but a terraform_data. + deletion_mode = "PURGE" implementation = { terraform = { diff --git a/modules/meshstack/composition/buildingblock/variables.tf b/modules/meshstack/composition/buildingblock/variables.tf index ee51e94c..885df83e 100644 --- a/modules/meshstack/composition/buildingblock/variables.tf +++ b/modules/meshstack/composition/buildingblock/variables.tf @@ -1,5 +1,9 @@ +# Defaulted, unlike the other inputs, so that a run can still destroy a building block whose stored +# inputs predate a rename of this one. A destroy only has to delete the created meshObjects, and their +# display names have no bearing on that. variable "link_name" { type = string + default = "Link" description = "Name given to the building block definition and building block this composition creates." }