diff --git a/modules/azure/storage-account/backplane/README.md b/modules/azure/storage-account/backplane/README.md
index 1f7a1fd1..283078cd 100644
--- a/modules/azure/storage-account/backplane/README.md
+++ b/modules/azure/storage-account/backplane/README.md
@@ -108,8 +108,8 @@ module "storage_account_backplane" {
| Name | Version |
|------|---------|
| [terraform](#requirement\_terraform) | >= 1.0 |
-| [azuread](#requirement\_azuread) | ~> 3.7.0 |
-| [azurerm](#requirement\_azurerm) | 3.116.0 |
+| [azuread](#requirement\_azuread) | ~> 3.8 |
+| [azurerm](#requirement\_azurerm) | ~> 4.64 |
## Modules
@@ -123,9 +123,9 @@ No modules.
| [azuread_application_federated_identity_credential.buildingblock_deploy](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/application_federated_identity_credential) | resource |
| [azuread_application_password.buildingblock_deploy](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/application_password) | resource |
| [azuread_service_principal.buildingblock_deploy](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/service_principal) | resource |
-| [azurerm_role_assignment.created_principal](https://registry.terraform.io/providers/hashicorp/azurerm/3.116.0/docs/resources/role_assignment) | resource |
-| [azurerm_role_assignment.existing_principals](https://registry.terraform.io/providers/hashicorp/azurerm/3.116.0/docs/resources/role_assignment) | resource |
-| [azurerm_role_definition.buildingblock_deploy](https://registry.terraform.io/providers/hashicorp/azurerm/3.116.0/docs/resources/role_definition) | resource |
+| [azurerm_role_assignment.created_principal](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource |
+| [azurerm_role_assignment.existing_principals](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource |
+| [azurerm_role_definition.buildingblock_deploy](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_definition) | resource |
## Inputs
diff --git a/modules/azure/storage-account/backplane/main.tf b/modules/azure/storage-account/backplane/main.tf
index 44e93d18..cf3533f8 100644
--- a/modules/azure/storage-account/backplane/main.tf
+++ b/modules/azure/storage-account/backplane/main.tf
@@ -14,11 +14,15 @@ resource "azuread_service_principal" "buildingblock_deploy" {
# Create federated identity credentials (one per subject)
+# Use a map with static numeric string keys so that for_each keys are known at plan time,
+# even when subject values contain apply-time unknowns (e.g. building block definition UUIDs).
resource "azuread_application_federated_identity_credential" "buildingblock_deploy" {
- for_each = var.create_service_principal_name != null && var.workload_identity_federation != null ? toset(var.workload_identity_federation.subjects) : toset([])
+ for_each = var.create_service_principal_name != null && var.workload_identity_federation != null ? {
+ for i, s in var.workload_identity_federation.subjects : tostring(i) => s
+ } : {}
application_id = azuread_application.buildingblock_deploy[0].id
- display_name = reverse(split(":", each.value))[0]
+ display_name = "subject-${each.key}"
audiences = ["api://AzureADTokenExchange"]
issuer = var.workload_identity_federation.issuer
subject = each.value
diff --git a/modules/azure/storage-account/backplane/versions.tf b/modules/azure/storage-account/backplane/versions.tf
index a6b8fd3c..630c0652 100644
--- a/modules/azure/storage-account/backplane/versions.tf
+++ b/modules/azure/storage-account/backplane/versions.tf
@@ -4,11 +4,11 @@ terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
- version = "3.116.0"
+ version = "~> 4.64"
}
azuread = {
source = "hashicorp/azuread"
- version = "~> 3.7.0"
+ version = "~> 3.8"
}
}
}
diff --git a/modules/azure/storage-account/buildingblock/README.md b/modules/azure/storage-account/buildingblock/README.md
index 880c40ef..3a5bf4d2 100644
--- a/modules/azure/storage-account/buildingblock/README.md
+++ b/modules/azure/storage-account/buildingblock/README.md
@@ -37,9 +37,9 @@ provider "azurerm" {
| Name | Version |
|------|---------|
-| [azuread](#requirement\_azuread) | 3.1.0 |
-| [azurerm](#requirement\_azurerm) | 4.18.0 |
-| [random](#requirement\_random) | 3.6.3 |
+| [azuread](#requirement\_azuread) | ~> 3.8 |
+| [azurerm](#requirement\_azurerm) | ~> 4.64 |
+| [random](#requirement\_random) | ~> 3.8 |
## Modules
@@ -49,11 +49,11 @@ No modules.
| Name | Type |
|------|------|
-| [azurerm_resource_group.storage_account_rg](https://registry.terraform.io/providers/hashicorp/azurerm/4.18.0/docs/resources/resource_group) | resource |
-| [azurerm_storage_account.storage_account](https://registry.terraform.io/providers/hashicorp/azurerm/4.18.0/docs/resources/storage_account) | resource |
-| [random_string.resource_code](https://registry.terraform.io/providers/hashicorp/random/3.6.3/docs/resources/string) | resource |
-| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/4.18.0/docs/data-sources/client_config) | data source |
-| [azurerm_subscription.current](https://registry.terraform.io/providers/hashicorp/azurerm/4.18.0/docs/data-sources/subscription) | data source |
+| [azurerm_resource_group.storage_account_rg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
+| [azurerm_storage_account.storage_account](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account) | resource |
+| [random_string.resource_code](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
+| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source |
+| [azurerm_subscription.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subscription) | data source |
## Inputs
@@ -61,7 +61,6 @@ No modules.
|------|-------------|------|---------|:--------:|
| [location](#input\_location) | The location/region where the storage account is created. | `string` | n/a | yes |
| [storage\_account\_name](#input\_storage\_account\_name) | The name of the storage account. Must be unique across entire Azure Region, not just within a Subscription. | `string` | n/a | yes |
-| [storage\_account\_resource\_group\_name](#input\_storage\_account\_resource\_group\_name) | The name of the resource group containing the storage account. | `string` | n/a | yes |
## Outputs
diff --git a/modules/azure/storage-account/buildingblock/main.tf b/modules/azure/storage-account/buildingblock/main.tf
index 7773a0b7..938cf847 100644
--- a/modules/azure/storage-account/buildingblock/main.tf
+++ b/modules/azure/storage-account/buildingblock/main.tf
@@ -9,7 +9,7 @@ resource "random_string" "resource_code" {
}
resource "azurerm_resource_group" "storage_account_rg" {
- name = var.storage_account_resource_group_name
+ name = "rg-${var.storage_account_name}"
location = var.location
}
diff --git a/modules/azure/storage-account/buildingblock/variables.tf b/modules/azure/storage-account/buildingblock/variables.tf
index 128cf455..a64caa25 100644
--- a/modules/azure/storage-account/buildingblock/variables.tf
+++ b/modules/azure/storage-account/buildingblock/variables.tf
@@ -4,12 +4,6 @@ variable "storage_account_name" {
description = "The name of the storage account. Must be unique across entire Azure Region, not just within a Subscription."
}
-variable "storage_account_resource_group_name" {
- type = string
- nullable = false
- description = "The name of the resource group containing the storage account."
-}
-
variable "location" {
type = string
description = "The location/region where the storage account is created."
diff --git a/modules/azure/storage-account/buildingblock/versions.tf b/modules/azure/storage-account/buildingblock/versions.tf
index b44089e0..9441fdee 100644
--- a/modules/azure/storage-account/buildingblock/versions.tf
+++ b/modules/azure/storage-account/buildingblock/versions.tf
@@ -2,15 +2,15 @@ terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
- version = "4.18.0"
+ version = "~> 4.64"
}
azuread = {
source = "hashicorp/azuread"
- version = "3.1.0"
+ version = "~> 3.8"
}
random = {
source = "hashicorp/random"
- version = "3.6.3"
+ version = "~> 3.8"
}
}
}
diff --git a/modules/azure/storage-account/defintion/definition.json b/modules/azure/storage-account/defintion/definition.json
deleted file mode 100644
index 77cb0918..00000000
--- a/modules/azure/storage-account/defintion/definition.json
+++ /dev/null
@@ -1,69 +0,0 @@
-{
- "apiVersion": "v1",
- "metadata": {
- "uuid": null,
- "markedForDeletionOn": null,
- "markedForDeletionBy": null,
- "tags": {
- "environment": ["dev"],
- "team": ["devops"]
- },
- "ownedByWorkspace": "workspace-123"
- },
- "spec": {
- "displayName": "Example Building Block",
- "symbol": "EBB",
- "description": "This is an example building block definition.",
- "supportedPlatforms": ["aws", "azure"],
- "useInLandingZonesOnly": false,
- "supportUrl": "https://example.com/support",
- "documentationUrl": "https://example.com/docs",
- "latestVersion": {
- "number": 1,
- "onlyApplyOncePerTenant": true,
- "deletionMode": "DELETE",
- "implementation": {
- "terraform": {
- "terraformVersion": "0.14.7",
- "repositoryUrl": "https://github.com/example/terraform-repo",
- "async": false,
- "repositoryPath": "path/to/module",
- "sshPrivateKey": "private-key",
- "refName": "main",
- "knownHost": null,
- "useMeshHttpBackendFallback": false
- }
- },
- "inputs": [
- {
- "inputKey": "example-input",
- "displayName": "Example Input",
- "type": "STRING",
- "assignmentType": "USER_INPUT",
- "argument": null,
- "isEnvironment": false,
- "isSensitive": false,
- "updateableByConsumer": true,
- "selectableValues": ["value1", "value2"],
- "defaultValue": "value1",
- "description": "An example input.",
- "inputValueValidationRegex": "^[a-zA-Z0-9]+$",
- "validationRegexErrorMessage": "Only alphanumeric characters are allowed."
- }
- ],
- "outputs": [
- {
- "outputKey": "example-output",
- "displayName": "Example Output",
- "type": "STRING",
- "assignmentType": "NONE"
- }
- ],
- "state": "DRAFT",
- "dependencies": ["dependency1", "dependency2"]
- },
- "notificationSubscriber": ["user1", "user2"]
- },
- "kind": "MeshBuildingBlockDefinition",
- "meaningfulIdentifier": "meshBuildingBlockDefinition[Example Building Block (123e4567-e89b-12d3-a456-426614174000)]"
-}
diff --git a/modules/azure/storage-account/meshstack_integration.tf b/modules/azure/storage-account/meshstack_integration.tf
new file mode 100644
index 00000000..5863679e
--- /dev/null
+++ b/modules/azure/storage-account/meshstack_integration.tf
@@ -0,0 +1,222 @@
+variable "hub" {
+ type = object({
+ git_ref = optional(string, "main")
+ bbd_draft = optional(bool, true)
+ })
+ 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).
+ EOT
+}
+
+variable "meshstack" {
+ type = object({
+ owning_workspace_identifier = string
+ })
+ description = "`owning_workspace_identifier`: Identifier of the workspace that owns the building block."
+}
+
+# Retrieves the workload identity federation configuration from meshStack.
+# The building block runners share the same OIDC issuer and namespace prefix as meshStack integrations.
+# For self-hosted runners running outside our cluster, this does not hold true.
+data "meshstack_integrations" "integrations" {}
+
+variable "azure" {
+ type = object({
+ tenant_id = string
+ subscription_id = string
+ scope = string
+ location = optional(string, "germanywestcentral")
+ })
+ description = <<-EOT
+ `tenant_id`: Azure Entra tenant ID where the storage accounts will be deployed.
+ `subscription_id`: Azure subscription ID where storage accounts will be deployed.
+ `scope`: Azure management group or subscription ID used as the scope for the backplane role definition and assignment.
+ `location`: Default Azure region where storage accounts will be created (e.g. 'germanywestcentral').
+ EOT
+}
+
+variable "backplane_name" {
+ type = string
+ default = "azure-storage-account"
+ description = "Name for the backplane resources (service principal, role definition). Must match pattern ^[-a-z0-9]+$."
+}
+
+variable "notification_subscribers" {
+ type = list(string)
+ default = []
+ description = "List of email addresses to notify on building block lifecycle events."
+}
+
+module "backplane" {
+ source = "github.com/meshcloud/meshstack-hub//modules/azure/storage-account/backplane?ref=${var.hub.git_ref}"
+
+ name = var.backplane_name
+ scope = var.azure.scope
+
+ create_service_principal_name = var.backplane_name
+
+ workload_identity_federation = {
+ issuer = data.meshstack_integrations.integrations.workload_identity_federation.replicator.issuer
+ subjects = [
+ "${trimsuffix(data.meshstack_integrations.integrations.workload_identity_federation.replicator.subject, ":replicator")}:workspace.${var.meshstack.owning_workspace_identifier}.buildingblockdefinition.${meshstack_building_block_definition.this.metadata.uuid}"
+ ]
+ }
+}
+
+resource "meshstack_building_block_definition" "this" {
+ metadata = {
+ owned_by_workspace = var.meshstack.owning_workspace_identifier
+ }
+
+ spec = {
+ display_name = "Azure Storage Account"
+ description = "Provisions an Azure Storage Account as a highly scalable, durable, and secure container in the target Azure subscription."
+ support_url = "mailto:support@meshcloud.io"
+ documentation_url = "https://hub.meshcloud.io/platforms/azure/definitions/azure-storage-account"
+ notification_subscribers = var.notification_subscribers
+ symbol = "https://raw.githubusercontent.com/meshcloud/meshstack-hub/main/modules/azure/storage-account/buildingblock/logo.png"
+ target_type = "WORKSPACE_LEVEL"
+
+ readme = chomp(<<-EOT
+ ## Azure Storage Account
+
+ This building block provisions an **Azure Storage Account** in your Azure subscription, providing scalable and durable cloud storage for blobs, files, queues, and tables.
+
+ ## When to use it?
+
+ Use this building block when you need a managed Azure Storage Account with consistent naming, resource group organisation, and a pre-configured lifecycle policy.
+
+ ## Shared Responsibilities
+
+ | Responsibility | Platform Team | Application Team |
+ | ------------------------------------------- | :-----------: | :--------------: |
+ | Provision and configure storage account | ✅ | ❌ |
+ | Manage storage account lifecycle | ✅ | ❌ |
+ | Choose storage account name and region | ❌ | ✅ |
+ | Manage data stored in the storage account | ❌ | ✅ |
+ | Define access policies for stored data | ❌ | ✅ |
+ EOT
+ )
+ }
+
+ version_spec = {
+ draft = var.hub.bbd_draft
+
+ deletion_mode = "DELETE"
+
+ implementation = {
+ terraform = {
+ terraform_version = "1.9.0"
+ repository_url = "https://github.com/meshcloud/meshstack-hub.git"
+ repository_path = "modules/azure/storage-account/buildingblock"
+ ref_name = var.hub.git_ref
+ use_mesh_http_backend_fallback = true
+ }
+ }
+
+ inputs = {
+ ARM_CLIENT_ID = {
+ type = "STRING"
+ display_name = "ARM Client ID"
+ description = "Client ID of the service principal used to authenticate with Azure."
+ assignment_type = "STATIC"
+ is_environment = true
+ argument = jsonencode(module.backplane.created_service_principal.client_id)
+ }
+ ARM_TENANT_ID = {
+ type = "STRING"
+ display_name = "ARM Tenant ID"
+ description = "Azure Entra tenant ID for authentication."
+ assignment_type = "STATIC"
+ is_environment = true
+ argument = jsonencode(var.azure.tenant_id)
+ }
+ ARM_SUBSCRIPTION_ID = {
+ type = "STRING"
+ display_name = "Azure Subscription ID"
+ description = "The Azure subscription ID where the storage account will be deployed."
+ assignment_type = "STATIC"
+ is_environment = true
+ argument = jsonencode(var.azure.subscription_id)
+ }
+ ARM_USE_OIDC = {
+ type = "STRING"
+ display_name = "ARM Use OIDC"
+ description = "Enables OIDC-based workload identity federation for the Azure provider."
+ assignment_type = "STATIC"
+ is_environment = true
+ argument = jsonencode("true")
+ }
+ ARM_OIDC_TOKEN_FILE_PATH = {
+ type = "STRING"
+ display_name = "ARM OIDC Token File Path"
+ description = "Path to the OIDC token file used for workload identity federation authentication."
+ assignment_type = "STATIC"
+ is_environment = true
+ argument = jsonencode("/var/run/secrets/workload-identity/azure/token")
+ }
+ storage_account_name = {
+ type = "STRING"
+ display_name = "Storage Account Name"
+ description = "A name prefix for the storage account. A random 5-character suffix will be appended to ensure uniqueness (e.g. 'myapp' becomes 'myappx7k2q'). Only lowercase letters and numbers, 3–19 characters."
+ assignment_type = "USER_INPUT"
+ value_validation_regex = "^[a-z0-9]{3,19}$"
+ validation_regex_error_message = "Only lowercase letters and numbers are allowed, between 3 and 19 characters (a 5-character suffix will be appended, keeping the final name within Azure's 24-character limit)."
+ }
+ location = {
+ type = "STRING"
+ display_name = "Location"
+ description = "The Azure region where the storage account will be created."
+ assignment_type = "STATIC"
+ argument = jsonencode(var.azure.location)
+ }
+ }
+
+ outputs = {
+ storage_account_id = {
+ type = "STRING"
+ display_name = "Storage Account ID"
+ description = "The Azure resource ID of the created storage account."
+ assignment_type = "NONE"
+ }
+ storage_account_name = {
+ type = "STRING"
+ display_name = "Storage Account Name"
+ description = "The name of the created storage account."
+ assignment_type = "NONE"
+ }
+ storage_account_resource_group = {
+ type = "STRING"
+ display_name = "Resource Group"
+ description = "The name of the resource group containing the storage account."
+ assignment_type = "NONE"
+ }
+ }
+ }
+}
+
+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_version = ">= 1.11.0"
+
+ required_providers {
+ meshstack = {
+ source = "meshcloud/meshstack"
+ version = "~> 0.19.3"
+ }
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "~> 4.64"
+ }
+ azuread = {
+ source = "hashicorp/azuread"
+ version = "~> 3.8"
+ }
+ }
+}