From 64e4d8e880a34eeb126a6d23bf0b85f66e3eb4f4 Mon Sep 17 00:00:00 2001 From: Marcel Bindseil Date: Tue, 5 May 2026 09:56:41 +0100 Subject: [PATCH 1/5] feat(iot-ops): update IoT Operations parameters and configurations - add trust issuer settings and deployment script parameters - bump version for cert-manager and secret sync controller - enhance MQTT broker configurations with new application URI - update README and variable files for consistency Signed-off-by: Marcel Bindseil --- .../full-single-node-cluster/bicep/README.md | 4 ++++ .../full-single-node-cluster/bicep/main.bicep | 21 ++++++++----------- .../109-arc-extensions/bicep/types.bicep | 2 +- .../109-arc-extensions/terraform/README.md | 2 +- .../109-arc-extensions/terraform/variables.tf | 2 +- .../bicep/modules/iot-ops-instance.bicep | 1 + src/100-edge/110-iot-ops/bicep/types.bicep | 4 ++-- src/100-edge/110-iot-ops/terraform/README.md | 4 ++-- .../modules/iot-ops-instance/main.tf | 1 + .../110-iot-ops/terraform/variables.init.tf | 2 +- .../terraform/variables.instance.tf | 2 +- 11 files changed, 24 insertions(+), 21 deletions(-) diff --git a/blueprints/full-single-node-cluster/bicep/README.md b/blueprints/full-single-node-cluster/bicep/README.md index 9240bf11..e64db843 100644 --- a/blueprints/full-single-node-cluster/bicep/README.md +++ b/blueprints/full-single-node-cluster/bicep/README.md @@ -38,9 +38,13 @@ Deploys a complete end-to-end environment for Azure IoT Operations on a single-n | vpnGatewayAzureAdConfig | Azure AD authentication configuration for VPN Gateway. | `[_1.AzureAdConfig](#user-defined-types)` | [variables('_1.azureAdConfigDefaults')] | no | | shouldCreateAks | Whether to create an Azure Kubernetes Service cluster. | `bool` | `false` | no | | customLocationsOid | The object id of the Custom Locations Entra ID application for your tenant.
Can be retrieved using:

  az ad sp show --id bc313c14-388c-4e7d-a58e-70017303ee3b --query id -o tsv
| `string` | n/a | yes | +| trustIssuerSettings | The trust issuer settings for Customer Managed Azure IoT Operations Settings. | `[_3.TrustIssuerConfig](#user-defined-types)` | {'trustSource': 'SelfSigned'} | no | | shouldCreateAnonymousBrokerListener | Whether to enable an insecure anonymous AIO MQ Broker Listener. (Should only be used for dev or test environments) | `bool` | `false` | no | | shouldInitAio | Whether to deploy the Azure IoT Operations initial connected cluster resources, Secret Sync, ACSA, OSM, AIO Platform. | `bool` | `true` | no | | shouldDeployAio | Whether to deploy an Azure IoT Operations Instance and all of its required components into the connected cluster. | `bool` | `true` | no | +| shouldDeployAioDeploymentScripts | Whether to deploy DeploymentScripts for Azure IoT Operations. | `bool` | `false` | no | +| shouldEnableOtelCollector | Whether or not to enable the Open Telemetry Collector for Azure IoT Operations. | `bool` | `true` | no | +| shouldEnableOpcUaSimulator | Whether or not to enable the OPC UA Simulator and deploy ADR Asset for Azure IoT Operations. | `bool` | `false` | no | | namespacedDevices | List of namespaced devices to create. | `[_4.NamespacedDevice](#user-defined-types)[]` | [] | no | | assetEndpointProfiles | List of asset endpoint profiles to create. | `[_4.AssetEndpointProfile](#user-defined-types)[]` | [] | no | | legacyAssets | List of legacy assets to create. | `[_4.LegacyAsset](#user-defined-types)[]` | [] | no | diff --git a/blueprints/full-single-node-cluster/bicep/main.bicep b/blueprints/full-single-node-cluster/bicep/main.bicep index b2326ee6..bf97d878 100644 --- a/blueprints/full-single-node-cluster/bicep/main.bicep +++ b/blueprints/full-single-node-cluster/bicep/main.bicep @@ -7,6 +7,7 @@ import * as assetTypes from '../../../src/100-edge/111-assets/bicep/types.bicep' import * as messagingTypes from '../../../src/100-edge/130-messaging/bicep/types.bicep' import * as aiFoundryTypes from '../../../src/000-cloud/085-ai-foundry/bicep/types.bicep' import * as vpnGatewayTypes from '../../../src/000-cloud/055-vpn-gateway/bicep/types.bicep' +import * as iotOpsTypes from '../../../src/100-edge/110-iot-ops/bicep/types.bicep' targetScope = 'subscription' @@ -154,9 +155,8 @@ param customLocationsOid string */ // Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('The trust issuer settings for Customer Managed Azure IoT Operations Settings.') -// param trustIssuerSettings iotOpsTypes.TrustIssuerConfig = { trustSource: 'SelfSigned' } -var trustIssuerSettings = { trustSource: 'SelfSigned' } +@description('The trust issuer settings for Customer Managed Azure IoT Operations Settings.') +param trustIssuerSettings iotOpsTypes.TrustIssuerConfig = { trustSource: 'SelfSigned' } @description('Whether to enable an insecure anonymous AIO MQ Broker Listener. (Should only be used for dev or test environments)') param shouldCreateAnonymousBrokerListener bool = false @@ -168,21 +168,18 @@ param shouldInitAio bool = true param shouldDeployAio bool = true // Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('Whether to deploy DeploymentScripts for Azure IoT Operations.') -// param shouldDeployAioDeploymentScripts bool = false -var shouldDeployAioDeploymentScripts = false +@description('Whether to deploy DeploymentScripts for Azure IoT Operations.') +param shouldDeployAioDeploymentScripts bool = false // No additional resource group parameters needed // Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('Whether or not to enable the Open Telemetry Collector for Azure IoT Operations.') -// param shouldEnableOtelCollector bool = true -var shouldEnableOtelCollector = false +@description('Whether or not to enable the Open Telemetry Collector for Azure IoT Operations.') +param shouldEnableOtelCollector bool = true // Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('Whether or not to enable the OPC UA Simulator and deploy ADR Asset for Azure IoT Operations.') -// param shouldEnableOpcUaSimulator bool = true -var shouldEnableOpcUaSimulator = false +@description('Whether or not to enable the OPC UA Simulator and deploy ADR Asset for Azure IoT Operations.') +param shouldEnableOpcUaSimulator bool = false /* Device Configuration Parameters diff --git a/src/100-edge/109-arc-extensions/bicep/types.bicep b/src/100-edge/109-arc-extensions/bicep/types.bicep index 07005bed..622e4992 100644 --- a/src/100-edge/109-arc-extensions/bicep/types.bicep +++ b/src/100-edge/109-arc-extensions/bicep/types.bicep @@ -39,7 +39,7 @@ type CertManagerExtension = { var certManagerExtensionDefaults = { enabled: true release: { - version: '0.10.2' + version: '0.11.0' train: 'stable' autoUpgradeMinorVersion: false } diff --git a/src/100-edge/109-arc-extensions/terraform/README.md b/src/100-edge/109-arc-extensions/terraform/README.md index c3b84fc7..42cf7bab 100644 --- a/src/100-edge/109-arc-extensions/terraform/README.md +++ b/src/100-edge/109-arc-extensions/terraform/README.md @@ -23,7 +23,7 @@ cert-manager and Azure Container Storage (ACSA). | Name | Description | Type | Default | Required | |-------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:| | arc\_connected\_cluster | Arc-connected Kubernetes cluster object containing id, name, and location | ```object({ id = string name = string location = string })``` | n/a | yes | -| arc\_extensions | Combined configuration object for Arc extensions (cert-manager and container storage) | ```object({ cert_manager_extension = optional(object({ enabled = optional(bool) version = optional(string) train = optional(string) auto_upgrade_minor_version = optional(bool) agent_operation_timeout_in_minutes = optional(number) global_telemetry_enabled = optional(bool) })) container_storage_extension = optional(object({ enabled = optional(bool) version = optional(string) train = optional(string) auto_upgrade_minor_version = optional(bool) disk_storage_class = optional(string) fault_tolerance_enabled = optional(bool) disk_mount_point = optional(string) })) })``` | ```{ "cert_manager_extension": { "agent_operation_timeout_in_minutes": 20, "auto_upgrade_minor_version": false, "enabled": true, "global_telemetry_enabled": true, "train": "stable", "version": "0.10.2" }, "container_storage_extension": { "auto_upgrade_minor_version": false, "disk_mount_point": "/mnt", "disk_storage_class": "", "enabled": true, "fault_tolerance_enabled": false, "train": "stable", "version": "2.6.0" } }``` | no | +| arc\_extensions | Combined configuration object for Arc extensions (cert-manager and container storage) | ```object({ cert_manager_extension = optional(object({ enabled = optional(bool) version = optional(string) train = optional(string) auto_upgrade_minor_version = optional(bool) agent_operation_timeout_in_minutes = optional(number) global_telemetry_enabled = optional(bool) })) container_storage_extension = optional(object({ enabled = optional(bool) version = optional(string) train = optional(string) auto_upgrade_minor_version = optional(bool) disk_storage_class = optional(string) fault_tolerance_enabled = optional(bool) disk_mount_point = optional(string) })) })``` | ```{ "cert_manager_extension": { "agent_operation_timeout_in_minutes": 20, "auto_upgrade_minor_version": false, "enabled": true, "global_telemetry_enabled": true, "train": "stable", "version": "0.11.0" }, "container_storage_extension": { "auto_upgrade_minor_version": false, "disk_mount_point": "/mnt", "disk_storage_class": "", "enabled": true, "fault_tolerance_enabled": false, "train": "stable", "version": "2.6.0" } }``` | no | ## Outputs diff --git a/src/100-edge/109-arc-extensions/terraform/variables.tf b/src/100-edge/109-arc-extensions/terraform/variables.tf index 81872e9f..ca030d31 100644 --- a/src/100-edge/109-arc-extensions/terraform/variables.tf +++ b/src/100-edge/109-arc-extensions/terraform/variables.tf @@ -22,7 +22,7 @@ variable "arc_extensions" { default = { cert_manager_extension = { enabled = true - version = "0.10.2" + version = "0.11.0" train = "stable" auto_upgrade_minor_version = false agent_operation_timeout_in_minutes = 20 diff --git a/src/100-edge/110-iot-ops/bicep/modules/iot-ops-instance.bicep b/src/100-edge/110-iot-ops/bicep/modules/iot-ops-instance.bicep index dbaf3c60..1612fffa 100644 --- a/src/100-edge/110-iot-ops/bicep/modules/iot-ops-instance.bicep +++ b/src/100-edge/110-iot-ops/bicep/modules/iot-ops-instance.bicep @@ -130,6 +130,7 @@ var defaultConfigurationSettings = { 'AgentOperationTimeoutInMinutes': any(aioExtensionConfig.settings.agentOperationTimeoutInMinutes) 'connectors.values.mqttBroker.address': aioMqBrokerAddress 'connectors.values.mqttBroker.serviceAccountTokenAudience': aioMqBrokerConfig.serviceAccountAudience + 'connectors.values.securityPki.applicationUri': 'urn:microsoft.com:aio:opc:ua:broker:${take(uniqueString(arcConnectedCluster.id), 5)}' 'dataFlows.values.tinyKube.mqttBroker.hostName': '${aioMqBrokerConfig.brokerListenerServiceName}.${aioExtensionConfig.settings.namespace}' 'dataFlows.values.tinyKube.mqttBroker.port': any(aioMqBrokerConfig.brokerListenerPort) 'dataFlows.values.tinyKube.mqttBroker.authentication.serviceAccountTokenAudience': aioMqBrokerConfig.serviceAccountAudience diff --git a/src/100-edge/110-iot-ops/bicep/types.bicep b/src/100-edge/110-iot-ops/bicep/types.bicep index 7c7d638f..5c16d49c 100644 --- a/src/100-edge/110-iot-ops/bicep/types.bicep +++ b/src/100-edge/110-iot-ops/bicep/types.bicep @@ -27,7 +27,7 @@ type SecretStoreExtension = { @export() var secretStoreExtensionDefaults = { release: { - version: '1.3.0' + version: '1.4.0' train: 'stable' } } @@ -53,7 +53,7 @@ type AioExtension = { @export() var aioExtensionDefaults = { release: { - version: '1.3.38' + version: '1.3.70' train: 'stable' } settings: { diff --git a/src/100-edge/110-iot-ops/terraform/README.md b/src/100-edge/110-iot-ops/terraform/README.md index 87fa7885..1801545c 100644 --- a/src/100-edge/110-iot-ops/terraform/README.md +++ b/src/100-edge/110-iot-ops/terraform/README.md @@ -54,9 +54,9 @@ Instance can be created, and after. | mqtt\_broker\_diagnostics\_config | Extended broker diagnostics configuration for metrics, self-check, and distributed tracing | ```object({ metrics = optional(object({ prometheus_port = optional(number) })) self_check = optional(object({ mode = optional(string) interval_seconds = optional(number) timeout_seconds = optional(number) })) traces = optional(object({ mode = optional(string) cache_size_megabytes = optional(number) span_channel_capacity = optional(number) self_tracing = optional(object({ mode = optional(string) interval_seconds = optional(number) })) })) })``` | `null` | no | | mqtt\_broker\_disk\_buffer\_config | Disk-backed message buffer configuration for broker in-memory overflow to disk | ```object({ max_size = string ephemeral_volume_claim_spec = optional(object({ storage_class_name = optional(string) access_modes = optional(list(string)) volume_mode = optional(string) volume_name = optional(string) resources = optional(object({ requests = optional(map(string)) limits = optional(map(string)) })) data_source = optional(object({ api_group = optional(string) kind = string name = string })) data_source_ref = optional(object({ api_group = optional(string) kind = string name = string namespace = optional(string) })) selector = optional(object({ match_labels = optional(map(string)) match_expressions = optional(list(object({ key = string operator = string values = list(string) }))) })) })) persistent_volume_claim_spec = optional(object({ storage_class_name = optional(string) access_modes = optional(list(string)) volume_mode = optional(string) volume_name = optional(string) resources = optional(object({ requests = optional(map(string)) limits = optional(map(string)) })) data_source = optional(object({ api_group = optional(string) kind = string name = string })) data_source_ref = optional(object({ api_group = optional(string) kind = string name = string namespace = optional(string) })) selector = optional(object({ match_labels = optional(map(string)) match_expressions = optional(list(object({ key = string operator = string values = list(string) }))) })) })) })``` | `null` | no | | mqtt\_broker\_persistence\_config | Broker persistence configuration for disk-backed message storage | ```object({ max_size = string encryption_enabled = optional(bool) # Retention Policy retain_policy = optional(object({ mode = string # "All", "None", "Custom" custom_settings = optional(object({ topics = optional(list(string)) dynamic_enabled = optional(bool) })) })) # State Store Policy state_store_policy = optional(object({ mode = string # "All", "None", "Custom" custom_settings = optional(object({ state_store_resources = optional(list(object({ key_type = string # "Pattern", "String", "Binary" keys = list(string) }))) dynamic_enabled = optional(bool) })) })) # Subscriber Queue Policy subscriber_queue_policy = optional(object({ mode = string # "All", "None", "Custom" custom_settings = optional(object({ subscriber_client_ids = optional(list(string)) dynamic_enabled = optional(bool) })) })) # Persistent Volume Claim Specification persistent_volume_claim_spec = optional(object({ storage_class_name = optional(string) access_modes = optional(list(string)) volume_mode = optional(string) volume_name = optional(string) resources = optional(object({ requests = optional(map(string)) limits = optional(map(string)) })) data_source = optional(object({ api_group = optional(string) kind = string name = string })) data_source_ref = optional(object({ api_group = optional(string) kind = string name = string namespace = optional(string) })) selector = optional(object({ match_labels = optional(map(string)) match_expressions = optional(list(object({ key = string operator = string values = list(string) }))) })) })) })``` | `null` | no | -| operations\_config | n/a | ```object({ namespace = string kubernetesDistro = string version = string train = string agentOperationTimeoutInMinutes = number })``` | ```{ "agentOperationTimeoutInMinutes": 120, "kubernetesDistro": "K3s", "namespace": "azure-iot-operations", "train": "stable", "version": "1.3.38" }``` | no | +| operations\_config | n/a | ```object({ namespace = string kubernetesDistro = string version = string train = string agentOperationTimeoutInMinutes = number })``` | ```{ "agentOperationTimeoutInMinutes": 120, "kubernetesDistro": "K3s", "namespace": "azure-iot-operations", "train": "stable", "version": "1.3.70" }``` | no | | registry\_endpoints | List of additional container registry endpoints for pulling custom artifacts (WASM modules, graph definitions, connector templates). MCR (mcr.microsoft.com) is always added automatically with anonymous authentication. The `acr_resource_id` field enables automatic AcrPull role assignment for ACR endpoints using SystemAssignedManagedIdentity authentication. When `should_assign_acr_pull_for_aio` is true and `acr_resource_id` is provided, the AIO extension's identity will be granted AcrPull access to the specified ACR. | ```list(object({ name = string host = string acr_resource_id = optional(string) should_assign_acr_pull_for_aio = optional(bool, false) authentication = object({ method = string system_assigned_managed_identity_settings = optional(object({ audience = optional(string, "https://management.azure.com/") })) user_assigned_managed_identity_settings = optional(object({ client_id = string tenant_id = string scope = optional(string) })) artifact_pull_secret_settings = optional(object({ secret_ref = string })) }) }))``` | `[]` | no | -| secret\_sync\_controller | n/a | ```object({ version = string train = string })``` | ```{ "train": "stable", "version": "1.3.0" }``` | no | +| secret\_sync\_controller | n/a | ```object({ version = string train = string })``` | ```{ "train": "stable", "version": "1.4.0" }``` | no | | should\_assign\_key\_vault\_roles | Whether to assign Key Vault roles to provided Secret Sync identity. | `bool` | `true` | no | | should\_create\_anonymous\_broker\_listener | Whether to enable an insecure anonymous AIO MQ Broker Listener. Should only be used for dev or test environments | `bool` | `false` | no | | should\_deploy\_resource\_sync\_rules | Deploys resource sync rules if set to true | `bool` | `false` | no | diff --git a/src/100-edge/110-iot-ops/terraform/modules/iot-ops-instance/main.tf b/src/100-edge/110-iot-ops/terraform/modules/iot-ops-instance/main.tf index 60f4afea..33d8cb39 100644 --- a/src/100-edge/110-iot-ops/terraform/modules/iot-ops-instance/main.tf +++ b/src/100-edge/110-iot-ops/terraform/modules/iot-ops-instance/main.tf @@ -42,6 +42,7 @@ locals { "AgentOperationTimeoutInMinutes" = tostring(var.operations_config.agentOperationTimeoutInMinutes) "connectors.values.mqttBroker.address" = local.mqtt_broker_address "connectors.values.mqttBroker.serviceAccountTokenAudience" = var.mqtt_broker_config.serviceAccountAudience + "connectors.values.securityPki.applicationUri" = "urn:microsoft.com:aio:opc:ua:broker:${substr(sha256(var.arc_connected_cluster_id), 0, 5)}" "dataFlows.values.tinyKube.mqttBroker.hostName" = local.mqtt_broker_hostname "dataFlows.values.tinyKube.mqttBroker.port" = tostring(var.mqtt_broker_config.brokerListenerPort) "dataFlows.values.tinyKube.mqttBroker.authentication.serviceAccountTokenAudience" = var.mqtt_broker_config.serviceAccountAudience diff --git a/src/100-edge/110-iot-ops/terraform/variables.init.tf b/src/100-edge/110-iot-ops/terraform/variables.init.tf index 81370fa8..898a7430 100644 --- a/src/100-edge/110-iot-ops/terraform/variables.init.tf +++ b/src/100-edge/110-iot-ops/terraform/variables.init.tf @@ -13,7 +13,7 @@ variable "secret_sync_controller" { train = string }) default = { - version = "1.3.0" + version = "1.4.0" train = "stable" } } diff --git a/src/100-edge/110-iot-ops/terraform/variables.instance.tf b/src/100-edge/110-iot-ops/terraform/variables.instance.tf index 688feef3..06573f42 100644 --- a/src/100-edge/110-iot-ops/terraform/variables.instance.tf +++ b/src/100-edge/110-iot-ops/terraform/variables.instance.tf @@ -18,7 +18,7 @@ variable "operations_config" { default = { namespace = "azure-iot-operations" kubernetesDistro = "K3s" - version = "1.3.38" + version = "1.3.70" train = "stable" agentOperationTimeoutInMinutes = 120 } From 09f645fcc9594e518ad972aca9badbb225410f3b Mon Sep 17 00:00:00 2001 From: Marcel Bindseil Date: Tue, 5 May 2026 15:53:40 +0100 Subject: [PATCH 2/5] feat(terraform): enhance schema registry module with blob data contributor role - add role assignment for blob data contributor on schemas container - update README and variables to reflect new role and its purpose - create upgrade guide for Azure IoT Operations Co-authored-by: Copilot Signed-off-by: Marcel Bindseil --- docs/getting-started/general-user.md | 4 +- docs/getting-started/upgrade-aio.md | 104 ++++++++++++++++++ .../modules/schema-registry/README.md | 57 +++++----- .../terraform/modules/schema-registry/main.tf | 15 ++- .../modules/schema-registry/variables.core.tf | 6 + .../scripts/deploy-script-secrets.sh | 2 +- .../scripts/k3s-device-setup.sh | 2 +- 7 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 docs/getting-started/upgrade-aio.md diff --git a/docs/getting-started/general-user.md b/docs/getting-started/general-user.md index 3967dfed..bdba647a 100644 --- a/docs/getting-started/general-user.md +++ b/docs/getting-started/general-user.md @@ -544,12 +544,14 @@ After successful deployment: 1. **Explore the solution**: Familiarize yourself with deployed components 2. **Configure monitoring**: Set up alerts and dashboards in Grafana 3. **Customize settings**: Modify configuration for your specific needs -4. **Learn more**: Review [Azure IoT Operations documentation][iot-ops-docs] for advanced features +4. **Upgrade Azure IoT Operations**: When new AIO releases ship, follow the [Upgrade Azure IoT Operations](upgrade-aio.md) guide to upgrade and reconcile with Terraform or Bicep +5. **Learn more**: Review [Azure IoT Operations documentation][iot-ops-docs] for advanced features ## Additional Resources - **[Blueprint Developer Guide](blueprint-developer.md)** - Create custom blueprints - **[Feature Developer Guide](feature-developer.md)** - Contribute to the platform +- **[Upgrade Azure IoT Operations](upgrade-aio.md)** - Upgrade AIO and reconcile state for Terraform / Bicep - **[Azure IoT Operations Getting Started][iot-ops-quickstart]** - Official Microsoft guide - **[Troubleshooting Documentation](../observability/)** - Detailed troubleshooting guides diff --git a/docs/getting-started/upgrade-aio.md b/docs/getting-started/upgrade-aio.md new file mode 100644 index 00000000..c8cfc285 --- /dev/null +++ b/docs/getting-started/upgrade-aio.md @@ -0,0 +1,104 @@ +--- +title: Upgrade Azure IoT Operations +description: How to upgrade Azure IoT Operations (AIO) and reconcile the upgrade with the edge-ai Terraform or Bicep deployments +author: Edge AI Team +ms.date: 2026-05-05 +ms.topic: how-to +estimated_reading_time: 5 +keywords: + - azure iot operations + - aio upgrade + - terraform + - bicep + - cert-manager + - secret store +--- + +# Upgrade Azure IoT Operations + +This guide describes how to upgrade an Azure IoT Operations (AIO) instance deployed by edge-ai and how to reconcile the upgrade with your Infrastructure as Code (IaC) of choice. + +The `az iot ops upgrade` command updates three cluster components when newer stable versions are available: + +- `certManager` +- `secretStore` +- `iotOperations` + +The reconciliation steps differ between Terraform (stateful) and Bicep (stateless). + +## Prerequisites + +- Azure CLI logged in to the target subscription. +- `azure-iot-ops` CLI extension installed (this repo expects version `2.4.0`). +- `` — the resource group containing the AIO instance. +- `` — the AIO instance name (for edge-ai blueprints this is typically `iotops-arck---`). + +## Run the AIO upgrade + +Run the upgrade against your existing AIO instance. Review the proposed changes and confirm: + +```bash +az iot ops upgrade \ + --resource-group \ + --name +``` + +The CLI prints a table comparing current and desired versions for `certManager`, `secretStore`, and `iotOperations`, then performs the update. + +After this point, Azure has been mutated — the steps below bring your IaC back in sync. + +## Reconcile with Terraform + +Terraform is stateful. The state file holds the previous extension versions; after `az iot ops upgrade` it no longer matches Azure. Refresh state, verify, then re-apply your blueprint at the latest edge-ai versions. + +1. Refresh state from Azure to absorb the new versions: + + ```bash + cd blueprints//terraform + source ../../../scripts/az-sub-init.sh + terraform apply -refresh-only -var-file=terraform.tfvars + ``` + + Confirm when prompted. Terraform updates the in-state `version` attribute for the three extensions to match what Azure now reports. + +2. Verify there is no drift on the upgraded components: + + ```bash + terraform plan -var-file=terraform.tfvars | grep -E "cert_manager|secret_store|iot_operations" + ``` + + No output (or `No changes`) means the IaC and Azure agree on the new versions. + +3. Re-run edge-ai with the latest AIO version pins and any updated components. + + - Pull the latest edge-ai changes (which may bump the version defaults in [variables.init.tf](../../src/100-edge/110-iot-ops/terraform/variables.init.tf), [variables.instance.tf](../../src/100-edge/110-iot-ops/terraform/variables.instance.tf), and [variables.tf](../../src/100-edge/109-arc-extensions/terraform/variables.tf)). + - Apply the blueprint: + + ```bash + terraform apply -var-file=terraform.tfvars + ``` + + If the upstream pins are now newer than what `az iot ops upgrade` installed, Terraform will move the cluster to the newer pins. If they match, the apply is a no-op for the AIO extensions. + +> If you skip step 1, the next `terraform apply` will detect "drift" on the three extensions and roll Azure back to the versions pinned in code. Always run `-refresh-only` first when you upgraded out-of-band. + +## Reconcile with Bicep + +Bicep is stateless — there is no per-deployment record of previously applied versions to reconcile. After `az iot ops upgrade` no further ARM operations are required to "import" the new state. Subsequent Bicep deployments are idempotent against whatever exists in Azure. + +1. Run the AIO upgrade as shown above. + +2. Re-run edge-ai with the latest AIO version pins and any updated components. + + - Pull the latest edge-ai changes (which may bump the defaults in [109-arc-extensions/bicep/types.bicep](../../src/100-edge/109-arc-extensions/bicep/types.bicep) and [110-iot-ops/bicep/types.bicep](../../src/100-edge/110-iot-ops/bicep/types.bicep)). + - Re-deploy your blueprint with `az deployment group create` (or your existing pipeline) using the same parameters. + + If the parameter values are equal to or newer than what `az iot ops upgrade` installed, ARM applies the new versions. Otherwise the deployment is a no-op for the AIO extensions. + +> Because Bicep / ARM compares declared properties to live resource state, you do not need a refresh, import, or state-edit step. The next deployment is the reconciliation. + +## Troubleshooting + +- **Terraform plan still shows version diffs after `-refresh-only`**: confirm that the `azurerm_arc_kubernetes_cluster_extension` resources for `cert_manager`, `secret_store`, and `iot_operations` in state now show the upgraded `version`. If they do, the diff is being driven by code defaults newer than what `az iot ops upgrade` installed — running `terraform apply` will move Azure to those defaults. +- **`az iot ops upgrade` reports no updates**: the cluster is already on the latest stable channel for all three components; nothing to reconcile. +- **Permission errors during refresh**: ensure your principal has read access on the connected cluster, the cluster extensions, and the AIO instance resource group. diff --git a/src/000-cloud/030-data/terraform/modules/schema-registry/README.md b/src/000-cloud/030-data/terraform/modules/schema-registry/README.md index bbf4a14d..fd6fa5df 100644 --- a/src/000-cloud/030-data/terraform/modules/schema-registry/README.md +++ b/src/000-cloud/030-data/terraform/modules/schema-registry/README.md @@ -6,45 +6,48 @@ Storage Blob Data Contributor Role Assignment. ## Requirements -| Name | Version | -|-----------|-----------------| +| Name | Version | +|------|---------| | terraform | >= 1.9.8, < 2.0 | -| azapi | >= 2.3.0 | -| time | >= 0.9.0 | +| azapi | >= 2.3.0 | +| time | >= 0.9.0 | ## Providers -| Name | Version | -|-----------|----------| -| azapi | >= 2.3.0 | -| azurerm | n/a | -| terraform | n/a | -| time | >= 0.9.0 | +| Name | Version | +|------|---------| +| azapi | >= 2.3.0 | +| azurerm | n/a | +| terraform | n/a | +| time | >= 0.9.0 | ## Resources -| Name | Type | -|---------------------------------------------------------------------------------------------------------------------------------------------------------|----------| -| [azapi_resource.schema_registry](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) | resource | +| Name | Type | +|------|------| +| [azapi_resource.schema_registry](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) | resource | | [azurerm_role_assignment.registry_storage_contributor](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | -| [azurerm_storage_container.schema_container](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_container) | resource | -| [terraform_data.defer](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | -| [time_sleep.wait_for_rbac_propagation](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [azurerm_role_assignment.schema_container_blob_data_contributor](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | +| [azurerm_storage_container.schema_container](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_container) | resource | +| [terraform_data.defer](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | +| [time_sleep.wait_for_rbac_propagation](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source | ## Inputs -| Name | Description | Type | Default | Required | -|------------------|-------------------------------------------------------------------------------|----------------------------------------------------------------------------|---------|:--------:| -| environment | Environment for all resources in this module: dev, test, or prod | `string` | n/a | yes | -| instance | Instance identifier for naming resources: 001, 002, etc | `string` | n/a | yes | -| location | Azure region where all resources will be deployed | `string` | n/a | yes | -| resource\_group | Resource group object containing name and id where resources will be deployed | ```object({ id = string name = string })``` | n/a | yes | -| resource\_prefix | Prefix for all resources in this module | `string` | n/a | yes | -| storage\_account | n/a | ```object({ id = string name = string primary_blob_endpoint = string })``` | n/a | yes | +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| environment | Environment for all resources in this module: dev, test, or prod | `string` | n/a | yes | +| instance | Instance identifier for naming resources: 001, 002, etc | `string` | n/a | yes | +| location | Azure region where all resources will be deployed | `string` | n/a | yes | +| resource\_group | Resource group object containing name and id where resources will be deployed | ```object({ id = string name = string })``` | n/a | yes | +| resource\_prefix | Prefix for all resources in this module | `string` | n/a | yes | +| storage\_account | n/a | ```object({ id = string name = string primary_blob_endpoint = string })``` | n/a | yes | +| blob\_data\_contributor\_principal\_id | The principal ID that will be assigned the 'Storage Blob Data Contributor' role on the schemas container so it can upload schema versions. Defaults to the current Azure client when null. | `string` | `null` | no | ## Outputs -| Name | Description | -|------------------|-------------| -| schema\_registry | n/a | +| Name | Description | +|------|-------------| +| schema\_registry | n/a | diff --git a/src/000-cloud/030-data/terraform/modules/schema-registry/main.tf b/src/000-cloud/030-data/terraform/modules/schema-registry/main.tf index 04b30a98..201f15cf 100644 --- a/src/000-cloud/030-data/terraform/modules/schema-registry/main.tf +++ b/src/000-cloud/030-data/terraform/modules/schema-registry/main.tf @@ -57,10 +57,23 @@ resource "azurerm_role_assignment" "registry_storage_contributor" { skip_service_principal_aad_check = true } +// Grant blob data contributor on the schemas container so schema versions can be uploaded. +// Independent of the optional data-lake module so schema uploads work when should_create_data_lake = false. +data "azurerm_client_config" "current" {} + +resource "azurerm_role_assignment" "schema_container_blob_data_contributor" { + principal_id = coalesce(var.blob_data_contributor_principal_id, data.azurerm_client_config.current.object_id) + role_definition_name = "Storage Blob Data Contributor" + scope = azurerm_storage_container.schema_container.id +} + // Azure RBAC propagation delay for blob data-plane access. resource "time_sleep" "wait_for_rbac_propagation" { create_duration = "30s" - depends_on = [azurerm_role_assignment.registry_storage_contributor] + depends_on = [ + azurerm_role_assignment.registry_storage_contributor, + azurerm_role_assignment.schema_container_blob_data_contributor, + ] } resource "terraform_data" "defer" { diff --git a/src/000-cloud/030-data/terraform/modules/schema-registry/variables.core.tf b/src/000-cloud/030-data/terraform/modules/schema-registry/variables.core.tf index 3392bce0..ea1feee9 100644 --- a/src/000-cloud/030-data/terraform/modules/schema-registry/variables.core.tf +++ b/src/000-cloud/030-data/terraform/modules/schema-registry/variables.core.tf @@ -25,3 +25,9 @@ variable "instance" { type = string description = "Instance identifier for naming resources: 001, 002, etc" } + +variable "blob_data_contributor_principal_id" { + type = string + description = "The principal ID that will be assigned the 'Storage Blob Data Contributor' role on the schemas container so it can upload schema versions. Defaults to the current Azure client when null." + default = null +} diff --git a/src/100-edge/100-cncf-cluster/scripts/deploy-script-secrets.sh b/src/100-edge/100-cncf-cluster/scripts/deploy-script-secrets.sh index 85ef93d2..80d23fb4 100755 --- a/src/100-edge/100-cncf-cluster/scripts/deploy-script-secrets.sh +++ b/src/100-edge/100-cncf-cluster/scripts/deploy-script-secrets.sh @@ -125,7 +125,7 @@ fi if [ -z "$SKIP_AZ_LOGIN" ]; then if [ -n "$CLIENT_ID" ]; then log "Logging in with User Assigned Managed Identity (client ID: $CLIENT_ID)" - if ! az login --identity --client-id "$CLIENT_ID"; then + if ! az login --identity --username "$CLIENT_ID"; then err "Failed to login with User Assigned Managed Identity (client ID: $CLIENT_ID)" fi else diff --git a/src/100-edge/100-cncf-cluster/scripts/k3s-device-setup.sh b/src/100-edge/100-cncf-cluster/scripts/k3s-device-setup.sh index e76ab605..e3ecf4cb 100755 --- a/src/100-edge/100-cncf-cluster/scripts/k3s-device-setup.sh +++ b/src/100-edge/100-cncf-cluster/scripts/k3s-device-setup.sh @@ -137,7 +137,7 @@ if [[ ! $SKIP_AZ_LOGIN ]]; then else if [[ $CLIENT_ID ]]; then log "Logging into Azure CLI using managed identity client ID $CLIENT_ID" - if ! az login --identity --client-id "$CLIENT_ID" --allow-no-subscriptions; then + if ! az login --identity --username "$CLIENT_ID" --allow-no-subscriptions; then err "Azure CLI login failed for managed identity client ID $CLIENT_ID" fi else From 989f81787499f5724eb0b56ec2e72a56aeead342 Mon Sep 17 00:00:00 2001 From: Marcel Bindseil Date: Tue, 5 May 2026 16:17:04 +0100 Subject: [PATCH 3/5] style(docs): adjust heading levels in upgrade-aio and schema-registry README files for consistency Co-authored-by: Copilot Signed-off-by: Marcel Bindseil --- docs/getting-started/upgrade-aio.md | 2 +- .../modules/schema-registry/README.md | 62 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/getting-started/upgrade-aio.md b/docs/getting-started/upgrade-aio.md index c8cfc285..f7442684 100644 --- a/docs/getting-started/upgrade-aio.md +++ b/docs/getting-started/upgrade-aio.md @@ -14,7 +14,7 @@ keywords: - secret store --- -# Upgrade Azure IoT Operations +## Upgrade Azure IoT Operations This guide describes how to upgrade an Azure IoT Operations (AIO) instance deployed by edge-ai and how to reconcile the upgrade with your Infrastructure as Code (IaC) of choice. diff --git a/src/000-cloud/030-data/terraform/modules/schema-registry/README.md b/src/000-cloud/030-data/terraform/modules/schema-registry/README.md index fd6fa5df..3f6e420d 100644 --- a/src/000-cloud/030-data/terraform/modules/schema-registry/README.md +++ b/src/000-cloud/030-data/terraform/modules/schema-registry/README.md @@ -6,48 +6,48 @@ Storage Blob Data Contributor Role Assignment. ## Requirements -| Name | Version | -|------|---------| +| Name | Version | +|-----------|-----------------| | terraform | >= 1.9.8, < 2.0 | -| azapi | >= 2.3.0 | -| time | >= 0.9.0 | +| azapi | >= 2.3.0 | +| time | >= 0.9.0 | ## Providers -| Name | Version | -|------|---------| -| azapi | >= 2.3.0 | -| azurerm | n/a | -| terraform | n/a | -| time | >= 0.9.0 | +| Name | Version | +|-----------|----------| +| azapi | >= 2.3.0 | +| azurerm | n/a | +| terraform | n/a | +| time | >= 0.9.0 | ## Resources -| Name | Type | -|------|------| -| [azapi_resource.schema_registry](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) | resource | -| [azurerm_role_assignment.registry_storage_contributor](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | -| [azurerm_role_assignment.schema_container_blob_data_contributor](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | -| [azurerm_storage_container.schema_container](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_container) | resource | -| [terraform_data.defer](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | -| [time_sleep.wait_for_rbac_propagation](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | -| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source | +| Name | Type | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| +| [azapi_resource.schema_registry](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) | resource | +| [azurerm_role_assignment.registry_storage_contributor](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | +| [azurerm_role_assignment.schema_container_blob_data_contributor](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | +| [azurerm_storage_container.schema_container](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_container) | resource | +| [terraform_data.defer](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | +| [time_sleep.wait_for_rbac_propagation](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source | ## Inputs -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| environment | Environment for all resources in this module: dev, test, or prod | `string` | n/a | yes | -| instance | Instance identifier for naming resources: 001, 002, etc | `string` | n/a | yes | -| location | Azure region where all resources will be deployed | `string` | n/a | yes | -| resource\_group | Resource group object containing name and id where resources will be deployed | ```object({ id = string name = string })``` | n/a | yes | -| resource\_prefix | Prefix for all resources in this module | `string` | n/a | yes | -| storage\_account | n/a | ```object({ id = string name = string primary_blob_endpoint = string })``` | n/a | yes | -| blob\_data\_contributor\_principal\_id | The principal ID that will be assigned the 'Storage Blob Data Contributor' role on the schemas container so it can upload schema versions. Defaults to the current Azure client when null. | `string` | `null` | no | +| Name | Description | Type | Default | Required | +|----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|---------|:--------:| +| environment | Environment for all resources in this module: dev, test, or prod | `string` | n/a | yes | +| instance | Instance identifier for naming resources: 001, 002, etc | `string` | n/a | yes | +| location | Azure region where all resources will be deployed | `string` | n/a | yes | +| resource\_group | Resource group object containing name and id where resources will be deployed | ```object({ id = string name = string })``` | n/a | yes | +| resource\_prefix | Prefix for all resources in this module | `string` | n/a | yes | +| storage\_account | n/a | ```object({ id = string name = string primary_blob_endpoint = string })``` | n/a | yes | +| blob\_data\_contributor\_principal\_id | The principal ID that will be assigned the 'Storage Blob Data Contributor' role on the schemas container so it can upload schema versions. Defaults to the current Azure client when null. | `string` | `null` | no | ## Outputs -| Name | Description | -|------|-------------| -| schema\_registry | n/a | +| Name | Description | +|------------------|-------------| +| schema\_registry | n/a | From aaa620f6503888077f8d07eb7e6b1e3632280ddb Mon Sep 17 00:00:00 2001 From: Marcel Bindseil Date: Wed, 6 May 2026 12:22:10 +0100 Subject: [PATCH 4/5] docs: correct az iot ops CLI version to 2.5.0 for 1.3.70 components --- docs/getting-started/upgrade-aio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/upgrade-aio.md b/docs/getting-started/upgrade-aio.md index f7442684..fffcf89d 100644 --- a/docs/getting-started/upgrade-aio.md +++ b/docs/getting-started/upgrade-aio.md @@ -29,7 +29,7 @@ The reconciliation steps differ between Terraform (stateful) and Bicep (stateles ## Prerequisites - Azure CLI logged in to the target subscription. -- `azure-iot-ops` CLI extension installed (this repo expects version `2.4.0`). +- `azure-iot-ops` CLI extension installed (this repo expects version `2.5.0`). - `` — the resource group containing the AIO instance. - `` — the AIO instance name (for edge-ai blueprints this is typically `iotops-arck---`). From 60d1ddfdf9f43eda88db35e403b6fae02ff09b44 Mon Sep 17 00:00:00 2001 From: Marcel Bindseil Date: Wed, 6 May 2026 12:34:50 +0100 Subject: [PATCH 5/5] feat(blueprints): restore AIO params in multi-node and only-edge blueprints - Convert var workarounds back to first-class params with defaults in full-multi-node-cluster and only-edge-iot-ops Bicep blueprints, matching the full-single-node-cluster pattern (DeploymentScripts now supports az CLI 2.71+). - Remove stale 'post May 4' comments left over in full-single-node-cluster. - docs(upgrade-aio): add CLI/AIO/component version matrix, pinned-release callouts for TF and Bicep reconciliation, and a References section linking to upstream supported-versions docs. --- .../full-multi-node-cluster/bicep/main.bicep | 24 ++++++------------ .../full-single-node-cluster/bicep/main.bicep | 4 --- blueprints/only-edge-iot-ops/bicep/main.bicep | 25 +++++++------------ docs/getting-started/upgrade-aio.md | 19 ++++++++++++++ 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/blueprints/full-multi-node-cluster/bicep/main.bicep b/blueprints/full-multi-node-cluster/bicep/main.bicep index 600cf5d5..25f1494c 100644 --- a/blueprints/full-multi-node-cluster/bicep/main.bicep +++ b/blueprints/full-multi-node-cluster/bicep/main.bicep @@ -163,10 +163,8 @@ param shouldCreateAks bool = false IoT Operations Parameters */ -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('The trust issuer settings for Customer Managed Azure IoT Operations Settings.') -// param trustIssuerSettings iotOpsTypes.TrustIssuerConfig = { trustSource: 'SelfSigned' } -var trustIssuerSettings = { trustSource: 'SelfSigned' } +@description('The trust issuer settings for Customer Managed Azure IoT Operations Settings.') +param trustIssuerSettings types.TrustIssuerConfig = { trustSource: 'SelfSigned' } @description('Whether to enable an insecure anonymous AIO MQ Broker Listener. (Should only be used for dev or test environments)') param shouldCreateAnonymousBrokerListener bool = false @@ -177,20 +175,14 @@ param shouldInitAio bool = true @description('Whether to deploy an Azure IoT Operations Instance and all of its required components into the connected cluster.') param shouldDeployAio bool = true -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('Whether to deploy DeploymentScripts for Azure IoT Operations.') -// param shouldDeployAioDeploymentScripts bool = false -var shouldDeployAioDeploymentScripts = false +@description('Whether to deploy DeploymentScripts for Azure IoT Operations.') +param shouldDeployAioDeploymentScripts bool = false -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('Whether or not to enable the Open Telemetry Collector for Azure IoT Operations.') -// param shouldEnableOtelCollector bool = true -var shouldEnableOtelCollector = false +@description('Whether or not to enable the Open Telemetry Collector for Azure IoT Operations.') +param shouldEnableOtelCollector bool = true -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('Whether or not to enable the OPC UA Simulator and deploy ADR Asset for Azure IoT Operations.') -// param shouldEnableOpcUaSimulator bool = true -var shouldEnableOpcUaSimulator = false +@description('Whether or not to enable the OPC UA Simulator and deploy ADR Asset for Azure IoT Operations.') +param shouldEnableOpcUaSimulator bool = false /* Device Configuration Parameters diff --git a/blueprints/full-single-node-cluster/bicep/main.bicep b/blueprints/full-single-node-cluster/bicep/main.bicep index bf97d878..1aa1a115 100644 --- a/blueprints/full-single-node-cluster/bicep/main.bicep +++ b/blueprints/full-single-node-cluster/bicep/main.bicep @@ -154,7 +154,6 @@ param customLocationsOid string IoT Operations Parameters */ -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) @description('The trust issuer settings for Customer Managed Azure IoT Operations Settings.') param trustIssuerSettings iotOpsTypes.TrustIssuerConfig = { trustSource: 'SelfSigned' } @@ -167,17 +166,14 @@ param shouldInitAio bool = true @description('Whether to deploy an Azure IoT Operations Instance and all of its required components into the connected cluster.') param shouldDeployAio bool = true -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) @description('Whether to deploy DeploymentScripts for Azure IoT Operations.') param shouldDeployAioDeploymentScripts bool = false // No additional resource group parameters needed -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) @description('Whether or not to enable the Open Telemetry Collector for Azure IoT Operations.') param shouldEnableOtelCollector bool = true -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) @description('Whether or not to enable the OPC UA Simulator and deploy ADR Asset for Azure IoT Operations.') param shouldEnableOpcUaSimulator bool = false diff --git a/blueprints/only-edge-iot-ops/bicep/main.bicep b/blueprints/only-edge-iot-ops/bicep/main.bicep index 8d262504..cc9dc818 100644 --- a/blueprints/only-edge-iot-ops/bicep/main.bicep +++ b/blueprints/only-edge-iot-ops/bicep/main.bicep @@ -3,6 +3,7 @@ metadata description = 'Deploys Azure IoT Operations on an existing Arc-enabled import * as core from './types.core.bicep' import * as assetTypes from '../../../src/100-edge/111-assets/bicep/types.bicep' +import * as types from '../../../src/100-edge/110-iot-ops/bicep/types.bicep' /* Common Parameters */ @@ -24,10 +25,8 @@ param customLocationName string = '${arcConnectedClusterName}-cl' Trust Configuration Parameters */ -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('The trust issuer settings for Customer Managed Azure IoT Operations Settings.') -// param trustIssuerSettings types.TrustIssuerConfig = { trustSource: 'SelfSigned' } -var trustIssuerSettings = { trustSource: 'SelfSigned' } +@description('The trust issuer settings for Customer Managed Azure IoT Operations Settings.') +param trustIssuerSettings types.TrustIssuerConfig = { trustSource: 'SelfSigned' } /* Secret Sync and Key Vault Parameters @@ -64,10 +63,8 @@ param deployUserTokenSecretName string? @description('The prefix used with constructing the secret name that will have the deployment script.') param deploymentScriptsSecretNamePrefix string = '${common.resourcePrefix}-${common.environment}-${common.instance}' -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('Whether to deploy DeploymentScripts for Azure IoT Operations.') -// param shouldDeployAioDeploymentScripts bool = false -var shouldDeployAioDeploymentScripts = false +@description('Whether to deploy DeploymentScripts for Azure IoT Operations.') +param shouldDeployAioDeploymentScripts bool = false @description('Whether to assign roles to the deploy identity.') param shouldAssignDeployIdentityRoles bool = true @@ -107,15 +104,11 @@ param shouldCreateAnonymousBrokerListener bool = false @description('Whether to deploy Custom Locations Resource Sync Rules for the Azure IoT Operations resources.') param shouldDeployResourceSyncRules bool = true -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('Whether or not to enable the Open Telemetry Collector for Azure IoT Operations.') -// param shouldEnableOtelCollector bool = true -var shouldEnableOtelCollector = false +@description('Whether or not to enable the Open Telemetry Collector for Azure IoT Operations.') +param shouldEnableOtelCollector bool = true -// Currently disable setting shouldDeployAioDeploymentScripts, remove when DeploymentScripts supports AZ CLI 2.71+ (post May 4) -// @description('Whether or not to enable the OPC UA Simulator and deploy ADR Asset for Azure IoT Operations.') -// param shouldEnableOpcUaSimulator bool = true -var shouldEnableOpcUaSimulator = false +@description('Whether or not to enable the OPC UA Simulator and deploy ADR Asset for Azure IoT Operations.') +param shouldEnableOpcUaSimulator bool = false /* Device Configuration Parameters diff --git a/docs/getting-started/upgrade-aio.md b/docs/getting-started/upgrade-aio.md index fffcf89d..344ebfd2 100644 --- a/docs/getting-started/upgrade-aio.md +++ b/docs/getting-started/upgrade-aio.md @@ -26,6 +26,16 @@ The `az iot ops upgrade` command updates three cluster components when newer sta The reconciliation steps differ between Terraform (stateful) and Bicep (stateless). +## Version matrix + +This repository currently targets the **AIO 2604** release. The table below maps `az iot ops` CLI versions to the component versions pinned in edge-ai: + +| CLI extension (`azure-iot-ops`) | AIO release | cert-manager | secret-sync-controller | iotOperations | +|---------------------------------|-------------|--------------|------------------------|---------------| +| 2.5.0 | 2604 | 0.11.0 | 1.4.0 | 1.3.70 | + +For the full upstream compatibility matrix, see [Supported versions — Azure IoT Operations](https://learn.microsoft.com/en-us/azure/iot-operations/deploy-iot-ops/howto-upgrade?tabs=portal#supported-versions). + ## Prerequisites - Azure CLI logged in to the target subscription. @@ -80,6 +90,8 @@ Terraform is stateful. The state file holds the previous extension versions; aft If the upstream pins are now newer than what `az iot ops upgrade` installed, Terraform will move the cluster to the newer pins. If they match, the apply is a no-op for the AIO extensions. +> **Pinned releases**: If your team pins to a specific edge-ai release tag rather than `main`, the version defaults in that release may be older than what `az iot ops upgrade` installed. In that case, after `-refresh-only`, `terraform plan` will show no diff for the AIO extensions (state matches Azure). However, if you later move to a newer edge-ai release with higher version pins, the next `apply` will attempt to upgrade again. To stay aligned, either upgrade edge-ai to the release that matches the AIO versions you upgraded to, or override the version variables in your `terraform.tfvars`. + > If you skip step 1, the next `terraform apply` will detect "drift" on the three extensions and roll Azure back to the versions pinned in code. Always run `-refresh-only` first when you upgraded out-of-band. ## Reconcile with Bicep @@ -95,6 +107,8 @@ Bicep is stateless — there is no per-deployment record of previously applied v If the parameter values are equal to or newer than what `az iot ops upgrade` installed, ARM applies the new versions. Otherwise the deployment is a no-op for the AIO extensions. +> **Pinned releases**: If your team pins to a specific edge-ai release tag, ensure the version parameters passed to the blueprint match or exceed what `az iot ops upgrade` installed. If they are lower, the next deployment will attempt to downgrade the extensions. Override the version parameters explicitly or upgrade to an edge-ai release that includes the newer defaults. + > Because Bicep / ARM compares declared properties to live resource state, you do not need a refresh, import, or state-edit step. The next deployment is the reconciliation. ## Troubleshooting @@ -102,3 +116,8 @@ Bicep is stateless — there is no per-deployment record of previously applied v - **Terraform plan still shows version diffs after `-refresh-only`**: confirm that the `azurerm_arc_kubernetes_cluster_extension` resources for `cert_manager`, `secret_store`, and `iot_operations` in state now show the upgraded `version`. If they do, the diff is being driven by code defaults newer than what `az iot ops upgrade` installed — running `terraform apply` will move Azure to those defaults. - **`az iot ops upgrade` reports no updates**: the cluster is already on the latest stable channel for all three components; nothing to reconcile. - **Permission errors during refresh**: ensure your principal has read access on the connected cluster, the cluster extensions, and the AIO instance resource group. + +## References + +- [Supported versions — Azure IoT Operations](https://learn.microsoft.com/en-us/azure/iot-operations/deploy-iot-ops/howto-upgrade?tabs=portal#supported-versions) +- [Upgrade Azure IoT Operations — Official guide](https://learn.microsoft.com/en-us/azure/iot-operations/deploy-iot-ops/howto-upgrade)