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" + } + } +}