diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 24f996c8..8955ed12 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -45,10 +45,26 @@ They are starting points that should cover the simplest use case. A secondary purpose of these files is to serve as a ready-to-use Terraform module root that IaC runtimes can source directly. - Must use variables for required user inputs. -- Must include `required_providers` (place the `terraform { required_providers { ... } }` block at the **bottom** of the file — resources and variables should come first so readers see the important configuration before technical boilerplate). +- Must include `required_providers` block at the **bottom** of the file — resources and variables should come first so readers see the important configuration before technical boilerplate). - Never include `provider` configuration. - Reference modules using Git URLs and a ref pointing to the feature branch when developing. Once merged into main, the `update-module-refs` tooling in CI pins the ref to an appropriate commit. +### Required providers + +Every `meshstack_integration.tf` must declare the `meshcloud/meshstack` provider in a +`required_providers` block. + +```hcl +terraform { + required_providers { + meshstack = { + source = "meshcloud/meshstack" + version = ">= 0.19.3" + } + } +} +``` + ### Shared Variable Conventions The following variables must appear in every `meshstack_integration.tf`. @@ -67,7 +83,7 @@ variable "hub" { default = {} description = <<-EOT `git_ref`: Hub release reference. Set to a tag (e.g. 'v1.2.3') or branch or commit sha of the meshstack-hub repo. - `bbd_draft`: If true, the building block definition version is kept in draft mode, which allows changing it (useful during development in LCF/ICF). + `bbd_draft`: If true, the building block definition version is kept in draft mode. EOT } ``` @@ -92,7 +108,9 @@ Integrating with meshStack requires context, like a workspace where the resource variable "meshstack" { type = object({ owning_workspace_identifier = string + tags = optional(map(list(string)), {}) }) + description = "Shared meshStack context. Tags are optional and propagated to building block definition metadata." } ``` @@ -102,6 +120,7 @@ Use these variables in the implementation block of building block definitions. resource "meshstack_building_block_definition" "this" { metadata = { owned_by_workspace = var.meshstack.owning_workspace_identifier + tags = var.meshstack.tags } # ... other required fields ... implementation = { @@ -173,6 +192,9 @@ Do **not** commit these relative paths; switch back to the Hub GitHub URL before - [ ] Variables in `snake_case` - [ ] `buildingblock/README.md` with YAML front-matter - [ ] `buildingblock/APP_TEAM_README.md` with shared responsibility matrix +- [ ] `meshstack_integration.tf` declares `meshcloud/meshstack` in `required_providers` +- [ ] `meshstack_integration.tf` uses `variable "hub" { type = object({git_ref = string}) }` and `variable "meshstack" { type = object({owning_workspace_identifier = string}) }` +- [ ] `meshstack_integration.tf` uses relative `./backplane` source (no absolute GitHub URL) - [ ] `ref_name` uses `var.hub.git_ref` — no hardcoded `"main"` - [ ] `version_spec.draft` uses `var.hub.bbd_draft` - [ ] `building_block_definition_version_uuid` output uses `bbd_draft ? version_latest.uuid : version_latest_release.uuid` diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c080880d..6def737a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.81.0 + rev: v1.105.0 hooks: - id: terraform_docs args: @@ -17,7 +17,7 @@ repos: hooks: - id: validate-modules name: Validate modules - entry: sh -c 'bash ci/validate_modules.sh' + entry: sh -c 'bash ci/validate_modules.sh --fix' language: system pass_filenames: true always_run: true \ No newline at end of file diff --git a/modules/meshstack/noop/buildingblock/README.md b/modules/meshstack/noop/buildingblock/README.md new file mode 100644 index 00000000..bbe1df28 --- /dev/null +++ b/modules/meshstack/noop/buildingblock/README.md @@ -0,0 +1,102 @@ +--- +name: meshStack NoOp Building Block +supportedPlatforms: + - meshstack +description: | + Reference building block demonstrating meshStack's complete Terraform interface: + all input types, file inputs, user permissions injection, and pre-run scripts. +--- +# meshStack NoOp Building Block + +This building block is a reference implementation demonstrating how meshStack interfaces with OpenTofu building blocks. It exercises every input type, file input, pre-run script capability, and output type — without provisioning any cloud resources. + +Use it to: +- Understand how meshStack passes inputs to Terraform +- Learn how FILE-type inputs are written to the working directory +- See how `USER_PERMISSIONS` injects project team members into your building block +- Understand the pre-run script execution model + +## Input Types + +| Input | Type | Assignment | Description | +|-------|------|-----------|-------------| +| `user_permissions` | `CODE` | `USER_PERMISSIONS` | Project team members and their roles as a structured list | +| `user_permissions_json` | `CODE` | `USER_PERMISSIONS` | Same as above, as a raw JSON string | +| `sensitive_yaml` | `CODE` | `STATIC` (sensitive) | Encrypted YAML/JSON value, decrypted at runtime | +| `static` | `STRING` | `STATIC` | A platform-engineer-defined string constant | +| `static_code` | `CODE` | `STATIC` | A platform-engineer-defined map | +| `flag` | `BOOLEAN` | `USER_INPUT` | Boolean flag chosen by the user | +| `num` | `INTEGER` | `USER_INPUT` | Integer chosen by the user | +| `text` | `STRING` | `USER_INPUT` | Free-text string from the user | +| `sensitive_text` | `STRING` (sensitive) | `USER_INPUT` | Sensitive string, masked in UI and logs | +| `single_select` | `SINGLE_SELECT` | `USER_INPUT` | One value from a predefined list | +| `multi_select` | `MULTI_SELECT` | `USER_INPUT` | One or more values from a predefined list | +| `multi_select_json` | `MULTI_SELECT` | `USER_INPUT` | Same as above, as a raw JSON string | +| `some-file.yaml` | `FILE` | `STATIC` | Written to working directory; read via `file("some-file.yaml")` | +| `sensitive-file.yaml` | `FILE` | `STATIC` (sensitive) | Like above, encrypted at rest | + +### How FILE Inputs Work + +meshStack writes FILE inputs as files in the Terraform working directory before `tofu init` runs. Access them in Terraform with: + +```hcl +output "some_file_yaml" { + value = yamldecode(file("some-file.yaml")) +} + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [terraform_data.noop](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [flag](#input\_flag) | n/a | `bool` | n/a | yes | +| [multi\_select](#input\_multi\_select) | n/a | `list(string)` | n/a | yes | +| [multi\_select\_json](#input\_multi\_select\_json) | n/a | `string` | n/a | yes | +| [num](#input\_num) | n/a | `number` | n/a | yes | +| [sensitive\_text](#input\_sensitive\_text) | n/a | `string` | n/a | yes | +| [sensitive\_yaml](#input\_sensitive\_yaml) | n/a | `any` | n/a | yes | +| [single\_select](#input\_single\_select) | n/a | `string` | n/a | yes | +| [static](#input\_static) | n/a | `string` | n/a | yes | +| [static\_code](#input\_static\_code) | n/a | `map(string)` | n/a | yes | +| [text](#input\_text) | n/a | `string` | n/a | yes | +| [user\_permissions](#input\_user\_permissions) | n/a |
list(object({
meshIdentifier = string
username = string
firstName = string
lastName = string
email = string
euid = string
roles = list(string)
})) | n/a | yes |
+| [user\_permissions\_json](#input\_user\_permissions\_json) | n/a | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [debug\_input\_files\_json](#output\_debug\_input\_files\_json) | JSON-encoded map of all input files received, including sensitive values in plaintext. |
+| [debug\_input\_variables\_json](#output\_debug\_input\_variables\_json) | JSON-encoded map of all input variables received, including sensitive values in plaintext. |
+| [flag](#output\_flag) | n/a |
+| [multi\_select](#output\_multi\_select) | n/a |
+| [multi\_select\_json](#output\_multi\_select\_json) | n/a |
+| [num](#output\_num) | n/a |
+| [resource\_url](#output\_resource\_url) | n/a |
+| [sensitive\_file\_yaml](#output\_sensitive\_file\_yaml) | n/a |
+| [sensitive\_text](#output\_sensitive\_text) | n/a |
+| [sensitive\_yaml](#output\_sensitive\_yaml) | n/a |
+| [single\_select](#output\_single\_select) | n/a |
+| [some\_file\_yaml](#output\_some\_file\_yaml) | n/a |
+| [static](#output\_static) | n/a |
+| [static\_code](#output\_static\_code) | n/a |
+| [summary](#output\_summary) | n/a |
+| [text](#output\_text) | n/a |
+| [user\_permissions](#output\_user\_permissions) | n/a |
+| [user\_permissions\_json](#output\_user\_permissions\_json) | n/a |
+
\ No newline at end of file
diff --git a/modules/meshstack/noop/buildingblock/logo.png b/modules/meshstack/noop/buildingblock/logo.png
new file mode 100644
index 00000000..c3bafea3
Binary files /dev/null and b/modules/meshstack/noop/buildingblock/logo.png differ
diff --git a/modules/meshstack/noop/buildingblock/logo.svg b/modules/meshstack/noop/buildingblock/logo.svg
new file mode 100644
index 00000000..f96d28a1
--- /dev/null
+++ b/modules/meshstack/noop/buildingblock/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modules/meshstack/noop/buildingblock/main.tf b/modules/meshstack/noop/buildingblock/main.tf
new file mode 100644
index 00000000..e6c514fe
--- /dev/null
+++ b/modules/meshstack/noop/buildingblock/main.tf
@@ -0,0 +1,3 @@
+resource "terraform_data" "noop" {
+ # This resource does nothing and is always up-to-date.
+}
\ No newline at end of file
diff --git a/modules/meshstack/noop/buildingblock/outputs.tf b/modules/meshstack/noop/buildingblock/outputs.tf
new file mode 100644
index 00000000..aa736bf3
--- /dev/null
+++ b/modules/meshstack/noop/buildingblock/outputs.tf
@@ -0,0 +1,123 @@
+output "some_file_yaml" {
+ value = yamldecode(file("some-file.yaml"))
+}
+
+output "sensitive_file_yaml" {
+ value = yamldecode(file("sensitive-file.yaml"))
+}
+
+output "user_permissions" {
+ value = var.user_permissions
+}
+
+output "user_permissions_json" {
+ value = jsondecode(var.user_permissions_json)
+}
+
+output "sensitive_yaml" {
+ value = var.sensitive_yaml
+ sensitive = true
+}
+
+output "static" {
+ value = var.static
+}
+
+output "static_code" {
+ value = var.static_code
+}
+
+output "flag" {
+ value = var.flag
+}
+
+output "num" {
+ value = var.num
+}
+
+output "text" {
+ value = var.text
+}
+
+output "sensitive_text" {
+ value = var.sensitive_text
+ sensitive = true
+}
+
+output "single_select" {
+ value = var.single_select
+}
+
+output "multi_select" {
+ value = var.multi_select
+}
+
+output "multi_select_json" {
+ value = jsondecode(var.multi_select_json)
+}
+
+output "resource_url" {
+ value = "https://hub.meshcloud.io/modules/meshstack/noop"
+}
+
+output "summary" {
+ value = <<-MARKDOWN
+# NoOp Building Block — Deployment Summary
+
+This building block was successfully deployed. It is a **reference implementation**
+demonstrating meshStack's complete Terraform interface — it provisions _no real
+infrastructure_.
+
+The `SUMMARY` output assignment type allows you to provide a rich markdown summary for application teams.
+This summary is rendered like a README for this building block in meshPanel.
+
+## Example: Tables
+
+| Input | Value |
+|----------------|------------------------------|
+| Text | `Hello` |
+| Number | `123` |
+
+## Example: Code blocks
+
+You can use fenced code blocks and `inline code` for formatting.
+We support syntax highlighting for common languages.
+
+```yaml
+some: input
+other: value
+```
+
+## Example: Callout blocks
+
+> **Note**: Use quote blocks to create callouts for important information.
+MARKDOWN
+}
+
+output "debug_input_variables_json" {
+ description = "JSON-encoded map of all input variables received, including sensitive values in plaintext."
+ sensitive = true # For test only. Do not do this in production code.
+ value = jsonencode({
+ flag = var.flag
+ num = var.num
+ text = var.text
+ single_select = var.single_select
+ sensitive_text = var.sensitive_text
+ sensitive_yaml = var.sensitive_yaml
+ multi_select = var.multi_select
+ multi_select_json = var.multi_select_json
+ static = var.static
+ static_code = var.static_code
+ user_permissions = var.user_permissions
+ user_permissions_json = var.user_permissions_json
+ })
+}
+
+output "debug_input_files_json" {
+ description = "JSON-encoded map of all input files received, including sensitive values in plaintext."
+ sensitive = true # For test only. Do not do this in production code.
+ value = jsonencode({
+ "some-file.yaml" = file("some-file.yaml")
+ "sensitive-file.yaml" = file("sensitive-file.yaml")
+ })
+}
diff --git a/modules/meshstack/noop/buildingblock/prerun.sh b/modules/meshstack/noop/buildingblock/prerun.sh
new file mode 100644
index 00000000..69cbf402
--- /dev/null
+++ b/modules/meshstack/noop/buildingblock/prerun.sh
@@ -0,0 +1,43 @@
+echo "=== meshStack Building Block Pre-Run Script ==="
+echo "Running after 'tofu init', before 'tofu apply'"
+echo ""
+
+echo "--- Run Modes ---"
+echo "Run mode APPLY/DESTROY is passed as a positional argument"
+echo "Selected run mode: $1"
+echo ""
+
+echo "--- meshBuildingBlockRun JSON input ---"
+echo "# Read stdin once and extract multiple fields:"
+input=$(cat)
+workspace_id=$(echo "$input" | jq -r '.spec.buildingBlock.spec.workspaceIdentifier')
+buildingblock_uuid=$(echo "$input" | jq -r '.spec.buildingBlock.uuid')
+echo "Workspace identifier: $workspace_id"
+echo "Building Block UUID: $buildingblock_uuid"
+echo ""
+
+echo "--- Working Directory ---"
+echo "Working directory: $(pwd)"
+ls -lah
+echo ""
+
+echo "--- Tool Installation ---"
+echo "Currently not supported via apk add, but coming soon, see https://feedback.meshcloud.io/feature-requests/p/building-block-should-support-aws-cli-and-other"
+# sudo apk add aws-cli
+echo ""
+
+echo "--- Terraform State Manipulation ---"
+echo "The tofu backend is already initialized and a workspace selected"
+tofu show -no-color
+echo ""
+
+echo "--- Capturing System Logs ---"
+echo "Stdout log message from pre-run script"
+echo "Stderr log message from pre-run script" >&2
+echo ""
+
+echo "--- Capturing User Messages ---"
+echo "User message from pre-run script" >> "$MESHSTACK_USER_MESSAGE"
+
+echo "=== Pre-run script completed successfully ==="
+echo "'tofu apply' will now execute."
\ No newline at end of file
diff --git a/modules/meshstack/noop/buildingblock/variables.tf b/modules/meshstack/noop/buildingblock/variables.tf
new file mode 100644
index 00000000..ebd5d408
--- /dev/null
+++ b/modules/meshstack/noop/buildingblock/variables.tf
@@ -0,0 +1,57 @@
+variable "user_permissions" {
+ type = list(object({
+ meshIdentifier = string
+ username = string
+ firstName = string
+ lastName = string
+ email = string
+ euid = string
+ roles = list(string)
+ }))
+}
+
+variable "user_permissions_json" {
+ type = string
+}
+
+variable "sensitive_yaml" {
+ type = any
+ sensitive = true
+}
+
+variable "static" {
+ type = string
+}
+
+variable "static_code" {
+ type = map(string)
+}
+
+variable "flag" {
+ type = bool
+}
+
+variable "num" {
+ type = number
+}
+
+variable "text" {
+ type = string
+}
+
+variable "sensitive_text" {
+ type = string
+ sensitive = true
+}
+
+variable "single_select" {
+ type = string
+}
+
+variable "multi_select" {
+ type = list(string)
+}
+
+variable "multi_select_json" {
+ type = string
+}
diff --git a/modules/meshstack/noop/buildingblock/versions.tf b/modules/meshstack/noop/buildingblock/versions.tf
new file mode 100644
index 00000000..85004373
--- /dev/null
+++ b/modules/meshstack/noop/buildingblock/versions.tf
@@ -0,0 +1,5 @@
+terraform {
+ required_version = ">= 1.0"
+ # No provider required - this building block manages no cloud resources.
+ # It only processes and echoes back the inputs provided by meshStack.
+}
diff --git a/modules/meshstack/noop/meshstack_integration.tf b/modules/meshstack/noop/meshstack_integration.tf
new file mode 100644
index 00000000..43b4f8d7
--- /dev/null
+++ b/modules/meshstack/noop/meshstack_integration.tf
@@ -0,0 +1,198 @@
+variable "hub" {
+ type = object({
+ git_ref = optional(string, "main")
+ bbd_draft = optional(bool, false)
+ })
+ default = {}
+ description = <<-EOT
+ `git_ref`: Hub release reference. Set to a tag (e.g. 'v1.2.3') or branch or commit sha of the meshstack-hub repo.
+ `bbd_draft`: If true, the building block definition version is kept in draft mode.
+ EOT
+}
+
+variable "meshstack" {
+ type = object({
+ owning_workspace_identifier = string
+ tags = optional(map(list(string)), {})
+ })
+ description = "Shared meshStack context. Tags are optional and propagated to building block definition metadata."
+}
+
+resource "meshstack_building_block_definition" "this" {
+ metadata = {
+ owned_by_workspace = var.meshstack.owning_workspace_identifier
+ tags = var.meshstack.tags
+ }
+
+ spec = {
+ display_name = "meshStack NoOp Building Block"
+ description = "Reference building block demonstrating meshStack's complete Terraform interface: all input types, file inputs, user permissions injection, and pre-run scripts."
+ target_type = "WORKSPACE_LEVEL"
+ }
+
+ version_spec = {
+ draft = var.hub.bbd_draft
+ deletion_mode = "PURGE"
+ implementation = {
+ terraform = {
+ ref_name = var.hub.git_ref
+ repository_path = "modules/meshstack/noop/buildingblock"
+ repository_url = "https://github.com/meshcloud/meshstack-hub.git"
+ terraform_version = "1.11.0"
+ pre_run_script = file("${path.module}/buildingblock/prerun.sh")
+ }
+ }
+ inputs = {
+ flag = {
+ assignment_type = "USER_INPUT"
+ display_name = "Flag"
+ type = "BOOLEAN"
+ }
+ multi_select = {
+ assignment_type = "USER_INPUT"
+ display_name = "Multi Select"
+ selectable_values = ["multi1", "multi2"]
+ type = "MULTI_SELECT"
+ }
+ multi_select_json = {
+ assignment_type = "USER_INPUT"
+ display_name = "Multi Select Json"
+ selectable_values = ["multi1", "multi2"]
+ type = "MULTI_SELECT"
+ }
+ num = {
+ assignment_type = "USER_INPUT"
+ display_name = "Num"
+ type = "INTEGER"
+ }
+
+ "sensitive-file.yaml" = {
+ assignment_type = "STATIC"
+ display_name = "Sensitive File.yaml"
+ type = "FILE"
+ sensitive = {
+ argument = {
+ secret_value = "data:application/yaml;base64,c29tZTogaW5wdXQKb3RoZXI6IHZhbHVlCg=="
+ secret_version = null
+ }
+ }
+ }
+ sensitive_text = {
+ assignment_type = "USER_INPUT"
+ display_name = "Sensitive Text"
+ type = "STRING"
+ sensitive = {}
+ }
+ sensitive_yaml = {
+ assignment_type = "STATIC"
+ display_name = "Sensitive Yaml"
+ type = "CODE"
+ sensitive = {
+ argument = {
+ secret_value = "some: yaml\nother: value\n"
+ }
+ }
+ }
+ single_select = {
+ assignment_type = "USER_INPUT"
+ display_name = "Single Select"
+ selectable_values = ["single1", "single2"]
+ type = "SINGLE_SELECT"
+ }
+ "some-file.yaml" = {
+ assignment_type = "STATIC"
+ display_name = "Yaml"
+ type = "FILE"
+ argument = jsonencode("data:application/yaml;base64,c29tZTogaW5wdXQKb3RoZXI6IHZhbHVlCg==")
+ }
+ static = {
+ argument = jsonencode("A static value")
+ assignment_type = "STATIC"
+ display_name = "Static"
+ type = "STRING"
+ }
+ static_code = {
+ argument = jsonencode(jsonencode({ some : "code" }))
+ assignment_type = "STATIC"
+ display_name = "Static Code"
+ type = "CODE"
+ }
+ text = {
+ assignment_type = "USER_INPUT"
+ default_value = jsonencode("")
+ display_name = "Text"
+ type = "STRING"
+ }
+ user_permissions = {
+ assignment_type = "USER_PERMISSIONS"
+ display_name = "User Permissions"
+ type = "CODE"
+ }
+ user_permissions_json = {
+ assignment_type = "USER_PERMISSIONS"
+ display_name = "User Permissions"
+ type = "CODE"
+ }
+ }
+ outputs = {
+ flag = {
+ assignment_type = "NONE"
+ display_name = "Flag"
+ type = "BOOLEAN"
+ }
+ num = {
+ assignment_type = "NONE"
+ display_name = "Num"
+ type = "INTEGER"
+ }
+ text = {
+ assignment_type = "NONE"
+ display_name = "Text"
+ type = "STRING"
+ }
+ static_code = {
+ assignment_type = "NONE"
+ display_name = "Static Code"
+ type = "CODE"
+ }
+ resource_url = {
+ assignment_type = "RESOURCE_URL"
+ display_name = "Resource URL"
+ type = "STRING"
+ }
+ summary = {
+ assignment_type = "SUMMARY"
+ display_name = "Summary"
+ type = "STRING"
+ }
+ debug_input_variables_json = {
+ assignment_type = "NONE"
+ display_name = "Input Variables as JSON for debugging"
+ type = "CODE"
+ }
+ debug_input_files_json = {
+ assignment_type = "NONE"
+ display_name = "Input Files as JSON for debugging"
+ type = "CODE"
+ }
+ }
+ }
+}
+
+output "building_block_definition_uuid" {
+ value = meshstack_building_block_definition.this.metadata.uuid
+}
+
+output "building_block_definition_version_uuid" {
+ description = "UUID of the latest version. In draft mode returns the latest draft; otherwise returns the latest release."
+ value = var.hub.bbd_draft ? meshstack_building_block_definition.this.version_latest.uuid : meshstack_building_block_definition.this.version_latest_release.uuid
+}
+
+terraform {
+ required_providers {
+ meshstack = {
+ source = "meshcloud/meshstack"
+ version = ">= 0.19.3"
+ }
+ }
+}