From b3259d6e364c6dabfee2d80a7b0f09dd2562d9fb Mon Sep 17 00:00:00 2001 From: Marcel Bindseil Date: Fri, 10 Jul 2026 17:33:13 +0100 Subject: [PATCH 1/2] fix(observability): bind managed Prometheus datasource for AIO Grafana dashboard The AIO sample dashboard is a Grafana export whose panels reference the datasource via the ${DS_MANAGED_PROMETHEUS_INSTANCE} __inputs placeholder. az grafana dashboard import only auto-binds that placeholder when a matching Prometheus datasource is Grafana's default, which is not the case in Azure Managed Grafana, so panels failed with 'Datasource ${DS_MANAGED_PROMETHEUS_INSTANCE} was not found'. Resolve the managed Prometheus datasource uid and bind it (and drop __inputs) before importing. Pass MONITOR_WORKSPACE_NAME from Terraform to identify the datasource, and route the retry helper's diagnostics to stderr so they cannot pollute the captured uid. --- .../scripts/import-grafana-dashboards.sh | 59 ++++++++++++++++--- .../020-observability/terraform/main.tf | 5 +- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/000-cloud/020-observability/scripts/import-grafana-dashboards.sh b/src/000-cloud/020-observability/scripts/import-grafana-dashboards.sh index 85e4ec13b..a9a113305 100755 --- a/src/000-cloud/020-observability/scripts/import-grafana-dashboards.sh +++ b/src/000-cloud/020-observability/scripts/import-grafana-dashboards.sh @@ -8,6 +8,9 @@ set -euo pipefail # Environment variables (passed from Terraform) GRAFANA_NAME="${GRAFANA_NAME:-}" RESOURCE_GROUP_NAME="${RESOURCE_GROUP_NAME:-}" +# Name of the Azure Monitor Workspace whose auto-provisioned managed Prometheus +# datasource backs the AIO sample dashboard panels. +MONITOR_WORKSPACE_NAME="${MONITOR_WORKSPACE_NAME:-}" if [[ -z "$GRAFANA_NAME" || -z "$RESOURCE_GROUP_NAME" ]]; then echo "Error: GRAFANA_NAME and RESOURCE_GROUP_NAME environment variables must be set" @@ -29,10 +32,10 @@ retry() { return 0 fi if ((attempt >= max_attempts)); then - echo "Failed after ${max_attempts} attempts" + echo "Failed after ${max_attempts} attempts" >&2 return 1 fi - echo "Attempt ${attempt}/${max_attempts} failed, retrying in ${delay}s..." + echo "Attempt ${attempt}/${max_attempts} failed, retrying in ${delay}s..." >&2 sleep "$delay" ((attempt++)) done @@ -53,10 +56,52 @@ done # Import dashboard from GitHub echo "Importing AIO sample dashboard from GitHub..." -retry az grafana dashboard import \ - -g "$RESOURCE_GROUP_NAME" \ - -n "$GRAFANA_NAME" \ - --overwrite \ - --definition "https://raw.githubusercontent.com/Azure/azure-iot-operations/refs/heads/main/samples/grafana-dashboard/aio.sample.json" + +AIO_DASHBOARD_URL="https://raw.githubusercontent.com/Azure/azure-iot-operations/refs/heads/main/samples/grafana-dashboard/aio.sample.json" + +# The AIO sample dashboard is a Grafana export: every panel references its +# datasource through the "${DS_MANAGED_PROMETHEUS_INSTANCE}" __inputs placeholder. +# az grafana dashboard import only auto-binds that placeholder when a matching +# Prometheus datasource is Grafana's default. In Azure Managed Grafana the +# managed Prometheus datasource is not the default, so the placeholder would be +# imported literally and panels fail with +# "Datasource ${DS_MANAGED_PROMETHEUS_INSTANCE} was not found". +# Resolve the managed Prometheus datasource uid and bind it before importing. +resolve_prometheus_datasource_uid() { + az grafana data-source list -g "$RESOURCE_GROUP_NAME" -n "$GRAFANA_NAME" -o json | + jq -r --arg name "$MONITOR_WORKSPACE_NAME" ' + (map(select(.name == $name)) | .[0].uid) + // (map(select(.type == "prometheus")) | .[0].uid) + // empty + ' +} + +DS_UID="$(retry resolve_prometheus_datasource_uid)" + +if [[ -z "$DS_UID" ]]; then + echo "Warning: could not resolve a managed Prometheus datasource; importing AIO dashboard as-is" + retry az grafana dashboard import \ + -g "$RESOURCE_GROUP_NAME" \ + -n "$GRAFANA_NAME" \ + --overwrite \ + --definition "$AIO_DASHBOARD_URL" +else + echo "Binding AIO dashboard datasource to Prometheus uid: ${DS_UID}" + TMP_DIR="$(mktemp -d)" + trap 'rm -rf "$TMP_DIR"' EXIT + AIO_DASHBOARD_FILE="${TMP_DIR}/aio.sample.json" + + curl -fsSL "$AIO_DASHBOARD_URL" | + jq --arg uid "$DS_UID" ' + walk(if type == "object" and .uid == "${DS_MANAGED_PROMETHEUS_INSTANCE}" then .uid = $uid else . end) + | del(.__inputs) + ' >"$AIO_DASHBOARD_FILE" + + retry az grafana dashboard import \ + -g "$RESOURCE_GROUP_NAME" \ + -n "$GRAFANA_NAME" \ + --overwrite \ + --definition "$AIO_DASHBOARD_FILE" +fi echo "Dashboard import completed successfully" diff --git a/src/000-cloud/020-observability/terraform/main.tf b/src/000-cloud/020-observability/terraform/main.tf index aa111c687..aea6418b9 100644 --- a/src/000-cloud/020-observability/terraform/main.tf +++ b/src/000-cloud/020-observability/terraform/main.tf @@ -130,8 +130,9 @@ resource "terraform_data" "apply_scripts" { command = "bash ${path.module}/../scripts/import-grafana-dashboards.sh" environment = { - GRAFANA_NAME = azurerm_dashboard_grafana.monitor.name - RESOURCE_GROUP_NAME = var.azmon_resource_group.name + GRAFANA_NAME = azurerm_dashboard_grafana.monitor.name + RESOURCE_GROUP_NAME = var.azmon_resource_group.name + MONITOR_WORKSPACE_NAME = azurerm_monitor_workspace.monitor.name } } } From f8fb13d6ec1dee5247fc6b0ee247416f889c73db Mon Sep 17 00:00:00 2001 From: Marcel Bindseil Date: Fri, 10 Jul 2026 17:44:36 +0100 Subject: [PATCH 2/2] style(observability): satisfy shfmt binary_next_line for dashboard import script Move pipeline operators to the start of continued lines to comply with the repo .editorconfig (binary_next_line=true), fixing the Shell Lint (shfmt) CI check. --- .../scripts/import-grafana-dashboards.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/000-cloud/020-observability/scripts/import-grafana-dashboards.sh b/src/000-cloud/020-observability/scripts/import-grafana-dashboards.sh index a9a113305..926132bcc 100755 --- a/src/000-cloud/020-observability/scripts/import-grafana-dashboards.sh +++ b/src/000-cloud/020-observability/scripts/import-grafana-dashboards.sh @@ -68,8 +68,8 @@ AIO_DASHBOARD_URL="https://raw.githubusercontent.com/Azure/azure-iot-operations/ # "Datasource ${DS_MANAGED_PROMETHEUS_INSTANCE} was not found". # Resolve the managed Prometheus datasource uid and bind it before importing. resolve_prometheus_datasource_uid() { - az grafana data-source list -g "$RESOURCE_GROUP_NAME" -n "$GRAFANA_NAME" -o json | - jq -r --arg name "$MONITOR_WORKSPACE_NAME" ' + az grafana data-source list -g "$RESOURCE_GROUP_NAME" -n "$GRAFANA_NAME" -o json \ + | jq -r --arg name "$MONITOR_WORKSPACE_NAME" ' (map(select(.name == $name)) | .[0].uid) // (map(select(.type == "prometheus")) | .[0].uid) // empty @@ -91,8 +91,8 @@ else trap 'rm -rf "$TMP_DIR"' EXIT AIO_DASHBOARD_FILE="${TMP_DIR}/aio.sample.json" - curl -fsSL "$AIO_DASHBOARD_URL" | - jq --arg uid "$DS_UID" ' + curl -fsSL "$AIO_DASHBOARD_URL" \ + | jq --arg uid "$DS_UID" ' walk(if type == "object" and .uid == "${DS_MANAGED_PROMETHEUS_INSTANCE}" then .uid = $uid else . end) | del(.__inputs) ' >"$AIO_DASHBOARD_FILE"