From cdc0c92f4bf99fad79bd1986b45de42a28b57dd9 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 15:24:30 -0700 Subject: [PATCH 01/25] provision Azure Managed Grafana workspace --- azure-managed-grafana.bicep | 57 +++++++++ azure-pipelines-managed-grafana.yml | 49 ++++++++ eng/deploy-managed-grafana.yml | 30 +++++ eng/provision-grafana.yaml | 180 ++++++++++++++++++++++++++++ 4 files changed, 316 insertions(+) create mode 100644 azure-managed-grafana.bicep create mode 100644 azure-pipelines-managed-grafana.yml create mode 100644 eng/deploy-managed-grafana.yml create mode 100644 eng/provision-grafana.yaml diff --git a/azure-managed-grafana.bicep b/azure-managed-grafana.bicep new file mode 100644 index 000000000..c5b647324 --- /dev/null +++ b/azure-managed-grafana.bicep @@ -0,0 +1,57 @@ +// Azure Managed Grafana Workspace Bicep Template +@description('The Azure region where the Grafana workspace will be deployed') +param location string + +@description('The name of the Grafana workspace') +param grafanaWorkspaceName string + +@description('The pricing tier for the Grafana workspace') +@allowed([ + 'Standard' + 'Essential' +]) +param skuName string = 'Standard' + +@description('Object ID of the .NET Eng Services Azure AD group') +param dotnetEngServicesGroupObjectId string + +// Azure Managed Grafana Workspace +resource grafanaWorkspace 'Microsoft.Dashboard/grafana@2023-09-01' = { + name: grafanaWorkspaceName + location: location + sku: { + name: skuName + } + identity: { + type: 'SystemAssigned' + } + properties: { + deterministicOutboundIP: 'Enabled' + apiKey: 'Enabled' + autoGeneratedDomainNameLabelScope: 'TenantReuse' + zoneRedundancy: 'Disabled' + publicNetworkAccess: 'Enabled' + grafanaIntegrations: { + azureMonitorWorkspaceIntegrations: [] + } + } +} + +// Role assignment to grant .NET Eng Services group Grafana Admin access +resource grafanaAdminRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(grafanaWorkspace.id, dotnetEngServicesGroupObjectId, 'Grafana Admin') + scope: grafanaWorkspace + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '22926164-76b3-42b3-bc55-97df8dab3e41') // Grafana Admin role + principalId: dotnetEngServicesGroupObjectId + principalType: 'Group' + } +} + +// Output the Grafana workspace details +output grafanaWorkspaceId string = grafanaWorkspace.id +output grafanaWorkspaceName string = grafanaWorkspace.name +output grafanaWorkspaceUrl string = grafanaWorkspace.properties.endpoint +output grafanaPrincipalId string = grafanaWorkspace.identity.principalId +output grafanaTenantId string = grafanaWorkspace.identity.tenantId +output grafanaWorkspaceLocation string = grafanaWorkspace.location diff --git a/azure-pipelines-managed-grafana.yml b/azure-pipelines-managed-grafana.yml new file mode 100644 index 000000000..470180949 --- /dev/null +++ b/azure-pipelines-managed-grafana.yml @@ -0,0 +1,49 @@ +trigger: + batch: true + branches: + include: + - haruna/managed-grafana-new + - production +pr: none + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: + name: NetCore1ESPool-Internal + image: 1es-windows-2019 + os: windows + sdl: + policheck: + enabled: true + tsa: + enabled: true + + stages: + - ${{ if in(variables['Build.SourceBranch'], 'refs/heads/haruna/managed-grafana-new', 'refs/heads/production')}}: + - template: /eng/deploy-managed-grafana.yml@self + parameters: + ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/production') }}: + DeploymentEnvironment: Staging + ServiceConnectionName: dnceng-managed-grafana-staging + GrafanaWorkspaceName: dnceng-grafana-staging + GrafanaKeyVault: dnceng-grafana-int-kv + GrafanaVariableGroup: Dnceng-Managed-Grafana-Staging-Vg + ServiceConnectionClientId: 4ad9ae35-2d42-4245-a954-9003b7e31349 + ServiceConnectionId: f955b932-c7e3-48f7-9d67-4e6542b3568a + ${{ else }}: + DeploymentEnvironment: Production + ServiceConnectionName: dnceng-managed-grafana + GrafanaWorkspaceName: dnceng-grafana + GrafanaKeyVault: dnceng-grafana-prod-kv + GrafanaVariableGroup: Dnceng-Managed-Grafana-Vg + ServiceConnectionClientId: 0ceeca1a-31e7-49ee-9bf4-15f14ed28fa4 + ServiceConnectionId: 332b249e-769b-49a9-9dc9-d82afe28ec0a + + diff --git a/eng/deploy-managed-grafana.yml b/eng/deploy-managed-grafana.yml new file mode 100644 index 000000000..edc0b0e13 --- /dev/null +++ b/eng/deploy-managed-grafana.yml @@ -0,0 +1,30 @@ +parameters: +- name: ServiceConnectionName + type: string +- name: ServiceConnectionClientId + type: string +- name: ServiceConnectionId + type: string +- name: DeploymentEnvironment + type: string +- name: GrafanaWorkspaceName + type: string +- name: GrafanaKeyVault + type: string +- name: GrafanaVariableGroup + type: string + + +stages: +- stage: ProvisionGrafana + displayName: 'Provision Grafana Infrastructure' + jobs: + - template: /eng/provision-grafana.yaml@self + parameters: + DeploymentEnvironment: ${{ parameters.DeploymentEnvironment }} + ServiceConnectionName: ${{ parameters.ServiceConnectionName }} + GrafanaResourceGroup: 'monitoring-managed' + GrafanaWorkspaceName: ${{ parameters.GrafanaWorkspaceName }} + GrafanaLocation: 'westus2' + GrafanaKeyVault: ${{ parameters.GrafanaKeyVault }} + GrafanaVariableGroup: ${{ parameters.GrafanaVariableGroup }} \ No newline at end of file diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml new file mode 100644 index 000000000..4acc3d91a --- /dev/null +++ b/eng/provision-grafana.yaml @@ -0,0 +1,180 @@ +# Azure Managed Grafana Provisioning Template +# This template provisions Azure Managed Grafana workspaces as part of the deployment process + +parameters: +- name: DeploymentEnvironment + type: string + +- name: ServiceConnectionName + type: string + +- name: GrafanaResourceGroup + type: string + +- name: GrafanaWorkspaceName + type: string + +- name: GrafanaLocation + type: string + +- name: GrafanaKeyVault + type: string + +- name: GrafanaVariableGroup + type: string + +- name: SkipGrafanaProvisioning + type: boolean + default: false + +variables: + - group: ${{ parameters.GrafanaVariableGroup }} + +jobs: +- job: ProvisionGrafana + displayName: 'Provision Azure Managed Grafana' + condition: and(succeeded(), not('${{ parameters.SkipGrafanaProvisioning }}')) + pool: + name: NetCore1ESPool-Internal + demands: ImageOverride -equals 1es-windows-2022 + + steps: + - checkout: self + displayName: 'Checkout Repository' + + - task: AzureCLI@2 + displayName: 'Install Bicep CLI' + inputs: + azureSubscription: '${{ parameters.ServiceConnectionName }}' + scriptType: 'pwsh' + scriptLocation: 'inlineScript' + inlineScript: | + Write-Host "Installing Bicep CLI..." + az bicep install + az bicep version + Write-Host "āœ… Bicep CLI installed successfully" + + - task: AzureCLI@2 + displayName: 'Validate Bicep Template' + inputs: + azureSubscription: '${{ parameters.ServiceConnectionName }}' + scriptType: 'pwsh' + scriptLocation: 'inlineScript' + inlineScript: | + Write-Host "Validating Grafana Bicep template..." + if (!(Test-Path "azure-managed-grafana.bicep")) { + throw "Bicep template not found: azure-managed-grafana.bicep" + } + + az bicep build --file azure-managed-grafana.bicep + if ($LASTEXITCODE -ne 0) { + throw "Bicep template validation failed" + } + Write-Host "āœ… Bicep template validation successful" + + - task: AzureCLI@2 + displayName: 'Ensure Resource Group Exists' + inputs: + azureSubscription: '${{ parameters.ServiceConnectionName }}' + scriptType: 'pwsh' + scriptLocation: 'inlineScript' + inlineScript: | + $rgName = "${{ parameters.GrafanaResourceGroup }}" + $location = "${{ parameters.GrafanaLocation }}" + + Write-Host "Checking if resource group '$rgName' exists..." + $rg = az group show --name $rgName --query "name" --output tsv 2>$null + + if ($LASTEXITCODE -ne 0) { + Write-Host "Creating resource group '$rgName' in '$location'..." + az group create --name $rgName --location $location + if ($LASTEXITCODE -ne 0) { + throw "Failed to create resource group '$rgName'" + } + Write-Host "āœ… Resource group created successfully" + } else { + Write-Host "āœ… Resource group already exists" + } + + - task: AzureResourceManagerTemplateDeployment@3 + displayName: 'Deploy Grafana Workspace' + inputs: + deploymentScope: 'Resource Group' + azureResourceManagerConnection: '${{ parameters.ServiceConnectionName }}' + action: 'Create Or Update Resource Group' + resourceGroupName: '${{ parameters.GrafanaResourceGroup }}' + location: '${{ parameters.GrafanaLocation }}' + templateLocation: 'Linked artifact' + csmFile: 'azure-managed-grafana.bicep' + overrideParameters: | + -subscriptionId $(az account show --query id --output tsv) + -resourceGroupName "${{ parameters.GrafanaResourceGroup }}" + -location "${{ parameters.GrafanaLocation }}" + -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" + -skuName "Standard" + -dotnetEngServicesGroupObjectId "$(dotnet-eng-services-group-object-id)" + deploymentMode: 'Incremental' + deploymentName: 'grafana-${{ parameters.DeploymentEnvironment }}-$(Build.BuildNumber)' + deploymentOutputs: 'grafanaOutputs' + + - task: AzureCLI@2 + displayName: 'Verify Grafana Deployment' + inputs: + azureSubscription: '${{ parameters.ServiceConnectionName }}' + scriptType: 'pwsh' + scriptLocation: 'inlineScript' + inlineScript: | + $workspaceName = "${{ parameters.GrafanaWorkspaceName }}" + $rgName = "${{ parameters.GrafanaResourceGroup }}" + + Write-Host "Verifying Grafana workspace deployment..." + + # Wait for deployment to complete + $maxAttempts = 30 + $attempt = 0 + do { + $attempt++ + Write-Host "Verification attempt $attempt of $maxAttempts..." + + $workspace = az grafana show --name $workspaceName --resource-group $rgName 2>$null | ConvertFrom-Json + if ($workspace -and $workspace.properties.provisioningState -eq "Succeeded") { + break + } + + if ($attempt -lt $maxAttempts) { + Write-Host "Workspace not ready yet, waiting 30 seconds..." + Start-Sleep -Seconds 30 + } + } while ($attempt -lt $maxAttempts) + + if (!$workspace) { + throw "Failed to verify Grafana workspace deployment" + } + + Write-Host "šŸ“Š Grafana Workspace Details:" + Write-Host " Name: $($workspace.name)" + Write-Host " URL: $($workspace.properties.endpoint)" + Write-Host " Location: $($workspace.location)" + Write-Host " SKU: $($workspace.sku.name)" + Write-Host " Status: $($workspace.properties.provisioningState)" + Write-Host " Identity: $($workspace.identity.principalId)" + + # Verify role assignments + Write-Host "Checking role assignments..." + $roleAssignments = az role assignment list --scope $workspace.id --query "[].{principalId:principalId, roleDefinitionName:roleDefinitionName}" 2>$null | ConvertFrom-Json + if ($roleAssignments) { + $roleAssignments | ForEach-Object { + Write-Host " Role: $($_.roleDefinitionName) - Principal: $($_.principalId)" + } + } else { + Write-Host " No role assignments found" + } + + # Store outputs for downstream usage + Write-Host "##vso[task.setvariable variable=GrafanaUrl;isOutput=true]$($workspace.properties.endpoint)" + Write-Host "##vso[task.setvariable variable=GrafanaPrincipalId;isOutput=true]$($workspace.identity.principalId)" + Write-Host "##vso[task.setvariable variable=GrafanaResourceId;isOutput=true]$($workspace.id)" + + Write-Host "āœ… ${{ parameters.DeploymentEnvironment }} Grafana deployment verification completed" + + \ No newline at end of file From fb7997db3aee7c3c3ce7a11578de0b0c2f3c5496 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 16:57:54 -0700 Subject: [PATCH 02/25] put variables in the right position --- eng/provision-grafana.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 4acc3d91a..536cb2169 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -27,9 +27,6 @@ parameters: type: boolean default: false -variables: - - group: ${{ parameters.GrafanaVariableGroup }} - jobs: - job: ProvisionGrafana displayName: 'Provision Azure Managed Grafana' @@ -38,6 +35,9 @@ jobs: name: NetCore1ESPool-Internal demands: ImageOverride -equals 1es-windows-2022 + variables: + - group: ${{ parameters.GrafanaVariableGroup }} + steps: - checkout: self displayName: 'Checkout Repository' From e13fb60ed26b7ec00ba8230ec09c9b0c473e2ef2 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 17:02:25 -0700 Subject: [PATCH 03/25] remove conditional statement --- eng/provision-grafana.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 536cb2169..e0eac777c 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -30,7 +30,6 @@ parameters: jobs: - job: ProvisionGrafana displayName: 'Provision Azure Managed Grafana' - condition: and(succeeded(), not('${{ parameters.SkipGrafanaProvisioning }}')) pool: name: NetCore1ESPool-Internal demands: ImageOverride -equals 1es-windows-2022 From 03b5d4f7ed4482ee58b6f5554faef490d67884dd Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 17:06:01 -0700 Subject: [PATCH 04/25] update windows to use 1es-windows-2022 --- azure-pipelines-managed-grafana.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-managed-grafana.yml b/azure-pipelines-managed-grafana.yml index 470180949..51bd53271 100644 --- a/azure-pipelines-managed-grafana.yml +++ b/azure-pipelines-managed-grafana.yml @@ -17,7 +17,7 @@ extends: parameters: pool: name: NetCore1ESPool-Internal - image: 1es-windows-2019 + image: 1es-windows-2022 os: windows sdl: policheck: From 4a5ed6d43f0d12136afea219b70cbf59df1ec298 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 17:42:44 -0700 Subject: [PATCH 05/25] remove bicep installation task --- eng/provision-grafana.yaml | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index e0eac777c..d793f1375 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -41,23 +41,11 @@ jobs: - checkout: self displayName: 'Checkout Repository' - - task: AzureCLI@2 - displayName: 'Install Bicep CLI' - inputs: - azureSubscription: '${{ parameters.ServiceConnectionName }}' - scriptType: 'pwsh' - scriptLocation: 'inlineScript' - inlineScript: | - Write-Host "Installing Bicep CLI..." - az bicep install - az bicep version - Write-Host "āœ… Bicep CLI installed successfully" - - task: AzureCLI@2 displayName: 'Validate Bicep Template' inputs: azureSubscription: '${{ parameters.ServiceConnectionName }}' - scriptType: 'pwsh' + scriptType: 'ps' scriptLocation: 'inlineScript' inlineScript: | Write-Host "Validating Grafana Bicep template..." @@ -75,7 +63,7 @@ jobs: displayName: 'Ensure Resource Group Exists' inputs: azureSubscription: '${{ parameters.ServiceConnectionName }}' - scriptType: 'pwsh' + scriptType: 'ps' scriptLocation: 'inlineScript' inlineScript: | $rgName = "${{ parameters.GrafanaResourceGroup }}" @@ -120,7 +108,7 @@ jobs: displayName: 'Verify Grafana Deployment' inputs: azureSubscription: '${{ parameters.ServiceConnectionName }}' - scriptType: 'pwsh' + scriptType: 'ps' scriptLocation: 'inlineScript' inlineScript: | $workspaceName = "${{ parameters.GrafanaWorkspaceName }}" From ec7b9565b7a7fb3af2683706a3c3b6a4c0f475ae Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 20:56:53 -0700 Subject: [PATCH 06/25] remove parameters that are not needed --- eng/provision-grafana.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index d793f1375..22c4b029e 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -94,8 +94,6 @@ jobs: templateLocation: 'Linked artifact' csmFile: 'azure-managed-grafana.bicep' overrideParameters: | - -subscriptionId $(az account show --query id --output tsv) - -resourceGroupName "${{ parameters.GrafanaResourceGroup }}" -location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard" From 28e0edaa6f05aeaa69e4ce44e85570d73c28aa81 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 21:41:48 -0700 Subject: [PATCH 07/25] changed parameters file format for bicep --- eng/provision-grafana.yaml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 22c4b029e..b1b9eda01 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -94,10 +94,20 @@ jobs: templateLocation: 'Linked artifact' csmFile: 'azure-managed-grafana.bicep' overrideParameters: | - -location "${{ parameters.GrafanaLocation }}" - -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" - -skuName "Standard" - -dotnetEngServicesGroupObjectId "$(dotnet-eng-services-group-object-id)" + { + "location": { + "value": "${{ parameters.GrafanaLocation }}" + }, + "grafanaWorkspaceName": { + "value": "${{ parameters.GrafanaWorkspaceName }}" + }, + "skuName": { + "value": "Standard" + }, + "dotnetEngServicesGroupObjectId": { + "value": "$(dotnet-eng-services-group-object-id)" + } + } deploymentMode: 'Incremental' deploymentName: 'grafana-${{ parameters.DeploymentEnvironment }}-$(Build.BuildNumber)' deploymentOutputs: 'grafanaOutputs' From a5805d6036a102c69f0b10d19ce780011a8dedc0 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 22:20:47 -0700 Subject: [PATCH 08/25] changed parameters file format for bicep --- eng/provision-grafana.yaml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index b1b9eda01..c80552a0c 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -93,21 +93,7 @@ jobs: location: '${{ parameters.GrafanaLocation }}' templateLocation: 'Linked artifact' csmFile: 'azure-managed-grafana.bicep' - overrideParameters: | - { - "location": { - "value": "${{ parameters.GrafanaLocation }}" - }, - "grafanaWorkspaceName": { - "value": "${{ parameters.GrafanaWorkspaceName }}" - }, - "skuName": { - "value": "Standard" - }, - "dotnetEngServicesGroupObjectId": { - "value": "$(dotnet-eng-services-group-object-id)" - } - } + overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard" -dotnetEngServicesGroupObjectId "$(dotnet-eng-services-group-object-id)"' deploymentMode: 'Incremental' deploymentName: 'grafana-${{ parameters.DeploymentEnvironment }}-$(Build.BuildNumber)' deploymentOutputs: 'grafanaOutputs' From 5424b0c4424f4facdd0ee2850944a0d9883c0996 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 23:48:41 -0700 Subject: [PATCH 09/25] remove role assignment from bicep --- eng/provision-grafana.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index c80552a0c..bcd8a16fb 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -93,7 +93,7 @@ jobs: location: '${{ parameters.GrafanaLocation }}' templateLocation: 'Linked artifact' csmFile: 'azure-managed-grafana.bicep' - overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard" -dotnetEngServicesGroupObjectId "$(dotnet-eng-services-group-object-id)"' + overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard"' deploymentMode: 'Incremental' deploymentName: 'grafana-${{ parameters.DeploymentEnvironment }}-$(Build.BuildNumber)' deploymentOutputs: 'grafanaOutputs' From e0004fcdf864d735f32b7a9140a1e1559b47bba3 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Wed, 8 Oct 2025 23:49:55 -0700 Subject: [PATCH 10/25] remove role assignment from bicep --- azure-managed-grafana.bicep | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/azure-managed-grafana.bicep b/azure-managed-grafana.bicep index c5b647324..21d97e5ec 100644 --- a/azure-managed-grafana.bicep +++ b/azure-managed-grafana.bicep @@ -12,9 +12,6 @@ param grafanaWorkspaceName string ]) param skuName string = 'Standard' -@description('Object ID of the .NET Eng Services Azure AD group') -param dotnetEngServicesGroupObjectId string - // Azure Managed Grafana Workspace resource grafanaWorkspace 'Microsoft.Dashboard/grafana@2023-09-01' = { name: grafanaWorkspaceName @@ -37,17 +34,6 @@ resource grafanaWorkspace 'Microsoft.Dashboard/grafana@2023-09-01' = { } } -// Role assignment to grant .NET Eng Services group Grafana Admin access -resource grafanaAdminRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(grafanaWorkspace.id, dotnetEngServicesGroupObjectId, 'Grafana Admin') - scope: grafanaWorkspace - properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '22926164-76b3-42b3-bc55-97df8dab3e41') // Grafana Admin role - principalId: dotnetEngServicesGroupObjectId - principalType: 'Group' - } -} - // Output the Grafana workspace details output grafanaWorkspaceId string = grafanaWorkspace.id output grafanaWorkspaceName string = grafanaWorkspace.name From 0ef9267b3b55e08ed1c56a2624f024857db8604e Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 00:11:24 -0700 Subject: [PATCH 11/25] changed parameters file format for bicep --- eng/provision-grafana.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index bcd8a16fb..786726015 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -142,7 +142,7 @@ jobs: # Verify role assignments Write-Host "Checking role assignments..." - $roleAssignments = az role assignment list --scope $workspace.id --query "[].{principalId:principalId, roleDefinitionName:roleDefinitionName}" 2>$null | ConvertFrom-Json + $roleAssignments = az role assignment list --scope $workspace.id --query '[].{principalId:principalId, roleDefinitionName:roleDefinitionName}' 2>$null | ConvertFrom-Json if ($roleAssignments) { $roleAssignments | ForEach-Object { Write-Host " Role: $($_.roleDefinitionName) - Principal: $($_.principalId)" From 0382325c7793d6d6dcfccd31779fcad13170eb44 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 00:27:16 -0700 Subject: [PATCH 12/25] changed parameters file format for bicep --- eng/provision-grafana.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 786726015..30b38d369 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -57,7 +57,7 @@ jobs: if ($LASTEXITCODE -ne 0) { throw "Bicep template validation failed" } - Write-Host "āœ… Bicep template validation successful" + Write-Host "SUCCESS: Bicep template validation successful" - task: AzureCLI@2 displayName: 'Ensure Resource Group Exists' @@ -78,9 +78,9 @@ jobs: if ($LASTEXITCODE -ne 0) { throw "Failed to create resource group '$rgName'" } - Write-Host "āœ… Resource group created successfully" + Write-Host "SUCCESS: Resource group created successfully" } else { - Write-Host "āœ… Resource group already exists" + Write-Host "SUCCESS: Resource group already exists" } - task: AzureResourceManagerTemplateDeployment@3 @@ -132,7 +132,7 @@ jobs: throw "Failed to verify Grafana workspace deployment" } - Write-Host "šŸ“Š Grafana Workspace Details:" + Write-Host "GRAFANA WORKSPACE DETAILS:" Write-Host " Name: $($workspace.name)" Write-Host " URL: $($workspace.properties.endpoint)" Write-Host " Location: $($workspace.location)" @@ -156,6 +156,6 @@ jobs: Write-Host "##vso[task.setvariable variable=GrafanaPrincipalId;isOutput=true]$($workspace.identity.principalId)" Write-Host "##vso[task.setvariable variable=GrafanaResourceId;isOutput=true]$($workspace.id)" - Write-Host "āœ… ${{ parameters.DeploymentEnvironment }} Grafana deployment verification completed" + Write-Host "SUCCESS: ${{ parameters.DeploymentEnvironment }} Grafana deployment verification completed" \ No newline at end of file From e8a9e30e71ea774d2a8b7f42bb65346867e9faad Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 10:22:23 -0700 Subject: [PATCH 13/25] add task to install amg extension --- eng/provision-grafana.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 30b38d369..6a2713a82 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -98,6 +98,21 @@ jobs: deploymentName: 'grafana-${{ parameters.DeploymentEnvironment }}-$(Build.BuildNumber)' deploymentOutputs: 'grafanaOutputs' + - task: AzureCLI@2 + displayName: 'Install Azure Managed Grafana Extension' + inputs: + azureSubscription: '${{ parameters.ServiceConnectionName }}' + scriptType: 'ps' + scriptLocation: 'inlineScript' + inlineScript: | + Write-Host "Installing Azure CLI Azure Managed Grafana extension..." + az extension add --name amg --allow-preview-versions --yes + if ($LASTEXITCODE -ne 0) { + Write-Host "Warning: Failed to install amg extension, will use alternative verification method" + } else { + Write-Host "SUCCESS: Azure Managed Grafana extension installed" + } + - task: AzureCLI@2 displayName: 'Verify Grafana Deployment' inputs: From aa6018d058d405ba7fb73ccbc480907439ca7e83 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 10:38:31 -0700 Subject: [PATCH 14/25] remove allow-preview-versions flag --- eng/provision-grafana.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 6a2713a82..3bc4f575c 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -106,7 +106,7 @@ jobs: scriptLocation: 'inlineScript' inlineScript: | Write-Host "Installing Azure CLI Azure Managed Grafana extension..." - az extension add --name amg --allow-preview-versions --yes + az extension add --name amg if ($LASTEXITCODE -ne 0) { Write-Host "Warning: Failed to install amg extension, will use alternative verification method" } else { From ac429bd7ca6c74b03e60a7a0029de8d04372ca71 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 11:27:01 -0700 Subject: [PATCH 15/25] assign grafana admin role to .net eng services --- azure-managed-grafana.bicep | 17 +++++++++++++++++ azure-pipelines-managed-grafana.yml | 2 ++ eng/deploy-managed-grafana.yml | 5 ++++- eng/provision-grafana.yaml | 5 ++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/azure-managed-grafana.bicep b/azure-managed-grafana.bicep index 21d97e5ec..537dcb51d 100644 --- a/azure-managed-grafana.bicep +++ b/azure-managed-grafana.bicep @@ -12,6 +12,12 @@ param grafanaWorkspaceName string ]) param skuName string = 'Standard' +@description('Object ID of the .NET Eng Services Azure AD group') +param dotnetEngServicesGroupObjectId string = '' + +@description('Whether to create role assignment for .NET Eng Services group') +param createRoleAssignment bool = true + // Azure Managed Grafana Workspace resource grafanaWorkspace 'Microsoft.Dashboard/grafana@2023-09-01' = { name: grafanaWorkspaceName @@ -34,6 +40,17 @@ resource grafanaWorkspace 'Microsoft.Dashboard/grafana@2023-09-01' = { } } +// Role assignment to grant .NET Eng Services group Grafana Admin access +resource grafanaAdminRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (createRoleAssignment && !empty(dotnetEngServicesGroupObjectId)) { + name: guid(grafanaWorkspace.id, dotnetEngServicesGroupObjectId, 'Grafana Admin') + scope: grafanaWorkspace + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '22926164-76b3-42b3-bc55-97df8dab3e41') // Grafana Admin role + principalId: dotnetEngServicesGroupObjectId + principalType: 'Group' + } +} + // Output the Grafana workspace details output grafanaWorkspaceId string = grafanaWorkspace.id output grafanaWorkspaceName string = grafanaWorkspace.name diff --git a/azure-pipelines-managed-grafana.yml b/azure-pipelines-managed-grafana.yml index 51bd53271..d88774edf 100644 --- a/azure-pipelines-managed-grafana.yml +++ b/azure-pipelines-managed-grafana.yml @@ -37,6 +37,7 @@ extends: GrafanaVariableGroup: Dnceng-Managed-Grafana-Staging-Vg ServiceConnectionClientId: 4ad9ae35-2d42-4245-a954-9003b7e31349 ServiceConnectionId: f955b932-c7e3-48f7-9d67-4e6542b3568a + DotnetEngServicesGroupObjectId: "65d7fc1d-2744-4669-8779-5cd7d7a6b95b" ${{ else }}: DeploymentEnvironment: Production ServiceConnectionName: dnceng-managed-grafana @@ -45,5 +46,6 @@ extends: GrafanaVariableGroup: Dnceng-Managed-Grafana-Vg ServiceConnectionClientId: 0ceeca1a-31e7-49ee-9bf4-15f14ed28fa4 ServiceConnectionId: 332b249e-769b-49a9-9dc9-d82afe28ec0a + DotnetEngServicesGroupObjectId: "65d7fc1d-2744-4669-8779-5cd7d7a6b95b" diff --git a/eng/deploy-managed-grafana.yml b/eng/deploy-managed-grafana.yml index edc0b0e13..fb4aa3053 100644 --- a/eng/deploy-managed-grafana.yml +++ b/eng/deploy-managed-grafana.yml @@ -13,6 +13,8 @@ parameters: type: string - name: GrafanaVariableGroup type: string +- name: DotnetEngServicesGroupObjectId + type: string stages: @@ -27,4 +29,5 @@ stages: GrafanaWorkspaceName: ${{ parameters.GrafanaWorkspaceName }} GrafanaLocation: 'westus2' GrafanaKeyVault: ${{ parameters.GrafanaKeyVault }} - GrafanaVariableGroup: ${{ parameters.GrafanaVariableGroup }} \ No newline at end of file + GrafanaVariableGroup: ${{ parameters.GrafanaVariableGroup }} + DotnetEngServicesGroupObjectId: ${{ parameters.DotnetEngServicesGroupObjectId }} \ No newline at end of file diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 3bc4f575c..ed356eac4 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -23,6 +23,9 @@ parameters: - name: GrafanaVariableGroup type: string +- name: DotnetEngServicesGroupObjectId + type: string + - name: SkipGrafanaProvisioning type: boolean default: false @@ -93,7 +96,7 @@ jobs: location: '${{ parameters.GrafanaLocation }}' templateLocation: 'Linked artifact' csmFile: 'azure-managed-grafana.bicep' - overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard"' + overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard" -dotnetEngServicesGroupObjectId "${{ parameters.DotnetEngServicesGroupObjectId }}" -createRoleAssignment true' deploymentMode: 'Incremental' deploymentName: 'grafana-${{ parameters.DeploymentEnvironment }}-$(Build.BuildNumber)' deploymentOutputs: 'grafanaOutputs' From 56839820dce762b70247726b40389d7d8ea911da Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 12:50:29 -0700 Subject: [PATCH 16/25] assign grafana admin role to .net eng services --- azure-pipelines-managed-grafana.yml | 2 +- eng/deploy-managed-grafana.yml | 1 + eng/provision-grafana.yaml | 43 ++++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/azure-pipelines-managed-grafana.yml b/azure-pipelines-managed-grafana.yml index d88774edf..b0c045fd3 100644 --- a/azure-pipelines-managed-grafana.yml +++ b/azure-pipelines-managed-grafana.yml @@ -32,7 +32,7 @@ extends: ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/production') }}: DeploymentEnvironment: Staging ServiceConnectionName: dnceng-managed-grafana-staging - GrafanaWorkspaceName: dnceng-grafana-staging + GrafanaWorkspaceName: dnceng-grafana-staging-1 GrafanaKeyVault: dnceng-grafana-int-kv GrafanaVariableGroup: Dnceng-Managed-Grafana-Staging-Vg ServiceConnectionClientId: 4ad9ae35-2d42-4245-a954-9003b7e31349 diff --git a/eng/deploy-managed-grafana.yml b/eng/deploy-managed-grafana.yml index fb4aa3053..7d93b679a 100644 --- a/eng/deploy-managed-grafana.yml +++ b/eng/deploy-managed-grafana.yml @@ -15,6 +15,7 @@ parameters: type: string - name: DotnetEngServicesGroupObjectId type: string + default: "65d7fc1d-2744-4669-8779-5cd7d7a6b95b" stages: diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index ed356eac4..82850c8b8 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -96,7 +96,7 @@ jobs: location: '${{ parameters.GrafanaLocation }}' templateLocation: 'Linked artifact' csmFile: 'azure-managed-grafana.bicep' - overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard" -dotnetEngServicesGroupObjectId "${{ parameters.DotnetEngServicesGroupObjectId }}" -createRoleAssignment true' + overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard" -dotnetEngServicesGroupObjectId "${{ parameters.DotnetEngServicesGroupObjectId }}" -createRoleAssignment false' deploymentMode: 'Incremental' deploymentName: 'grafana-${{ parameters.DeploymentEnvironment }}-$(Build.BuildNumber)' deploymentOutputs: 'grafanaOutputs' @@ -176,4 +176,45 @@ jobs: Write-Host "SUCCESS: ${{ parameters.DeploymentEnvironment }} Grafana deployment verification completed" + - task: AzureCLI@2 + displayName: 'Assign Grafana Admin Role to .NET Eng Services' + continueOnError: true + inputs: + azureSubscription: '${{ parameters.ServiceConnectionName }}' + scriptType: 'ps' + scriptLocation: 'inlineScript' + inlineScript: | + $workspaceName = "${{ parameters.GrafanaWorkspaceName }}" + $rgName = "${{ parameters.GrafanaResourceGroup }}" + $groupObjectId = "${{ parameters.DotnetEngServicesGroupObjectId }}" + + Write-Host "Attempting to assign Grafana Admin role to .NET Eng Services group..." + + # Get the Grafana workspace resource ID + $subscriptionId = az account show --query id --output tsv + $resourceId = "/subscriptions/$subscriptionId/resourceGroups/$rgName/providers/Microsoft.Dashboard/grafana/$workspaceName" + + # Check if role assignment already exists + $existingAssignment = az role assignment list --scope $resourceId --assignee $groupObjectId --role "Grafana Admin" --query "[0].id" --output tsv 2>$null + + if ($existingAssignment) { + Write-Host "SUCCESS: .NET Eng Services group already has Grafana Admin role" + } else { + # Try to assign the role + Write-Host "Assigning Grafana Admin role to .NET Eng Services group ($groupObjectId)..." + az role assignment create --assignee $groupObjectId --role "Grafana Admin" --scope $resourceId 2>$null + + if ($LASTEXITCODE -eq 0) { + Write-Host "SUCCESS: Grafana Admin role assigned to .NET Eng Services group" + } else { + Write-Host "WARNING: Failed to assign Grafana Admin role automatically" + Write-Host "Manual assignment required:" + Write-Host " 1. Go to Azure Portal > Resource Groups > $rgName > $workspaceName" + Write-Host " 2. Access control (IAM) > Add role assignment" + Write-Host " 3. Role: Grafana Admin" + Write-Host " 4. Assign access to: Group" + Write-Host " 5. Select: .NET Eng Services group" + } + } + \ No newline at end of file From 340831d496885cc25e19c8975ccf563faf406c2f Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 13:03:13 -0700 Subject: [PATCH 17/25] assign grafana admin role to .net eng services --- azure-pipelines-managed-grafana.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-managed-grafana.yml b/azure-pipelines-managed-grafana.yml index b0c045fd3..6302a1bf2 100644 --- a/azure-pipelines-managed-grafana.yml +++ b/azure-pipelines-managed-grafana.yml @@ -32,7 +32,7 @@ extends: ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/production') }}: DeploymentEnvironment: Staging ServiceConnectionName: dnceng-managed-grafana-staging - GrafanaWorkspaceName: dnceng-grafana-staging-1 + GrafanaWorkspaceName: dnceng-grafana-staging1 GrafanaKeyVault: dnceng-grafana-int-kv GrafanaVariableGroup: Dnceng-Managed-Grafana-Staging-Vg ServiceConnectionClientId: 4ad9ae35-2d42-4245-a954-9003b7e31349 From c76bcc49d8e6f5362602d26ef9f4af5ad091e70a Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 13:35:09 -0700 Subject: [PATCH 18/25] remove grafana admin role assignment --- azure-managed-grafana.bicep | 17 ---------- azure-pipelines-managed-grafana.yml | 2 -- eng/deploy-managed-grafana.yml | 7 +--- eng/provision-grafana.yaml | 50 ++--------------------------- 4 files changed, 3 insertions(+), 73 deletions(-) diff --git a/azure-managed-grafana.bicep b/azure-managed-grafana.bicep index 537dcb51d..21d97e5ec 100644 --- a/azure-managed-grafana.bicep +++ b/azure-managed-grafana.bicep @@ -12,12 +12,6 @@ param grafanaWorkspaceName string ]) param skuName string = 'Standard' -@description('Object ID of the .NET Eng Services Azure AD group') -param dotnetEngServicesGroupObjectId string = '' - -@description('Whether to create role assignment for .NET Eng Services group') -param createRoleAssignment bool = true - // Azure Managed Grafana Workspace resource grafanaWorkspace 'Microsoft.Dashboard/grafana@2023-09-01' = { name: grafanaWorkspaceName @@ -40,17 +34,6 @@ resource grafanaWorkspace 'Microsoft.Dashboard/grafana@2023-09-01' = { } } -// Role assignment to grant .NET Eng Services group Grafana Admin access -resource grafanaAdminRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (createRoleAssignment && !empty(dotnetEngServicesGroupObjectId)) { - name: guid(grafanaWorkspace.id, dotnetEngServicesGroupObjectId, 'Grafana Admin') - scope: grafanaWorkspace - properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '22926164-76b3-42b3-bc55-97df8dab3e41') // Grafana Admin role - principalId: dotnetEngServicesGroupObjectId - principalType: 'Group' - } -} - // Output the Grafana workspace details output grafanaWorkspaceId string = grafanaWorkspace.id output grafanaWorkspaceName string = grafanaWorkspace.name diff --git a/azure-pipelines-managed-grafana.yml b/azure-pipelines-managed-grafana.yml index 6302a1bf2..b57c1cf5b 100644 --- a/azure-pipelines-managed-grafana.yml +++ b/azure-pipelines-managed-grafana.yml @@ -37,7 +37,6 @@ extends: GrafanaVariableGroup: Dnceng-Managed-Grafana-Staging-Vg ServiceConnectionClientId: 4ad9ae35-2d42-4245-a954-9003b7e31349 ServiceConnectionId: f955b932-c7e3-48f7-9d67-4e6542b3568a - DotnetEngServicesGroupObjectId: "65d7fc1d-2744-4669-8779-5cd7d7a6b95b" ${{ else }}: DeploymentEnvironment: Production ServiceConnectionName: dnceng-managed-grafana @@ -46,6 +45,5 @@ extends: GrafanaVariableGroup: Dnceng-Managed-Grafana-Vg ServiceConnectionClientId: 0ceeca1a-31e7-49ee-9bf4-15f14ed28fa4 ServiceConnectionId: 332b249e-769b-49a9-9dc9-d82afe28ec0a - DotnetEngServicesGroupObjectId: "65d7fc1d-2744-4669-8779-5cd7d7a6b95b" diff --git a/eng/deploy-managed-grafana.yml b/eng/deploy-managed-grafana.yml index 7d93b679a..777095736 100644 --- a/eng/deploy-managed-grafana.yml +++ b/eng/deploy-managed-grafana.yml @@ -13,10 +13,6 @@ parameters: type: string - name: GrafanaVariableGroup type: string -- name: DotnetEngServicesGroupObjectId - type: string - default: "65d7fc1d-2744-4669-8779-5cd7d7a6b95b" - stages: - stage: ProvisionGrafana @@ -30,5 +26,4 @@ stages: GrafanaWorkspaceName: ${{ parameters.GrafanaWorkspaceName }} GrafanaLocation: 'westus2' GrafanaKeyVault: ${{ parameters.GrafanaKeyVault }} - GrafanaVariableGroup: ${{ parameters.GrafanaVariableGroup }} - DotnetEngServicesGroupObjectId: ${{ parameters.DotnetEngServicesGroupObjectId }} \ No newline at end of file + GrafanaVariableGroup: ${{ parameters.GrafanaVariableGroup }} \ No newline at end of file diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 82850c8b8..548953b0a 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -23,9 +23,6 @@ parameters: - name: GrafanaVariableGroup type: string -- name: DotnetEngServicesGroupObjectId - type: string - - name: SkipGrafanaProvisioning type: boolean default: false @@ -96,7 +93,7 @@ jobs: location: '${{ parameters.GrafanaLocation }}' templateLocation: 'Linked artifact' csmFile: 'azure-managed-grafana.bicep' - overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard" -dotnetEngServicesGroupObjectId "${{ parameters.DotnetEngServicesGroupObjectId }}" -createRoleAssignment false' + overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard"' deploymentMode: 'Incremental' deploymentName: 'grafana-${{ parameters.DeploymentEnvironment }}-$(Build.BuildNumber)' deploymentOutputs: 'grafanaOutputs' @@ -174,47 +171,4 @@ jobs: Write-Host "##vso[task.setvariable variable=GrafanaPrincipalId;isOutput=true]$($workspace.identity.principalId)" Write-Host "##vso[task.setvariable variable=GrafanaResourceId;isOutput=true]$($workspace.id)" - Write-Host "SUCCESS: ${{ parameters.DeploymentEnvironment }} Grafana deployment verification completed" - - - task: AzureCLI@2 - displayName: 'Assign Grafana Admin Role to .NET Eng Services' - continueOnError: true - inputs: - azureSubscription: '${{ parameters.ServiceConnectionName }}' - scriptType: 'ps' - scriptLocation: 'inlineScript' - inlineScript: | - $workspaceName = "${{ parameters.GrafanaWorkspaceName }}" - $rgName = "${{ parameters.GrafanaResourceGroup }}" - $groupObjectId = "${{ parameters.DotnetEngServicesGroupObjectId }}" - - Write-Host "Attempting to assign Grafana Admin role to .NET Eng Services group..." - - # Get the Grafana workspace resource ID - $subscriptionId = az account show --query id --output tsv - $resourceId = "/subscriptions/$subscriptionId/resourceGroups/$rgName/providers/Microsoft.Dashboard/grafana/$workspaceName" - - # Check if role assignment already exists - $existingAssignment = az role assignment list --scope $resourceId --assignee $groupObjectId --role "Grafana Admin" --query "[0].id" --output tsv 2>$null - - if ($existingAssignment) { - Write-Host "SUCCESS: .NET Eng Services group already has Grafana Admin role" - } else { - # Try to assign the role - Write-Host "Assigning Grafana Admin role to .NET Eng Services group ($groupObjectId)..." - az role assignment create --assignee $groupObjectId --role "Grafana Admin" --scope $resourceId 2>$null - - if ($LASTEXITCODE -eq 0) { - Write-Host "SUCCESS: Grafana Admin role assigned to .NET Eng Services group" - } else { - Write-Host "WARNING: Failed to assign Grafana Admin role automatically" - Write-Host "Manual assignment required:" - Write-Host " 1. Go to Azure Portal > Resource Groups > $rgName > $workspaceName" - Write-Host " 2. Access control (IAM) > Add role assignment" - Write-Host " 3. Role: Grafana Admin" - Write-Host " 4. Assign access to: Group" - Write-Host " 5. Select: .NET Eng Services group" - } - } - - \ No newline at end of file + Write-Host "SUCCESS: ${{ parameters.DeploymentEnvironment }} Grafana deployment verification completed" \ No newline at end of file From eed1aed0aacf6ad48aca23612ec10f903a5fb6ba Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 15:23:33 -0700 Subject: [PATCH 19/25] add release job type --- azure-pipelines-managed-grafana.yml | 2 +- eng/provision-grafana.yaml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/azure-pipelines-managed-grafana.yml b/azure-pipelines-managed-grafana.yml index b57c1cf5b..51bd53271 100644 --- a/azure-pipelines-managed-grafana.yml +++ b/azure-pipelines-managed-grafana.yml @@ -32,7 +32,7 @@ extends: ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/production') }}: DeploymentEnvironment: Staging ServiceConnectionName: dnceng-managed-grafana-staging - GrafanaWorkspaceName: dnceng-grafana-staging1 + GrafanaWorkspaceName: dnceng-grafana-staging GrafanaKeyVault: dnceng-grafana-int-kv GrafanaVariableGroup: Dnceng-Managed-Grafana-Staging-Vg ServiceConnectionClientId: 4ad9ae35-2d42-4245-a954-9003b7e31349 diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 548953b0a..493529052 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -33,6 +33,9 @@ jobs: pool: name: NetCore1ESPool-Internal demands: ImageOverride -equals 1es-windows-2022 + templateContext: + type: releaseJob + isProduction: false variables: - group: ${{ parameters.GrafanaVariableGroup }} From 90cdefc1b08abc3ede7d439f9eafbbcedda4f24c Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Thu, 9 Oct 2025 15:32:29 -0700 Subject: [PATCH 20/25] remove release job type --- eng/provision-grafana.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 493529052..548953b0a 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -33,9 +33,6 @@ jobs: pool: name: NetCore1ESPool-Internal demands: ImageOverride -equals 1es-windows-2022 - templateContext: - type: releaseJob - isProduction: false variables: - group: ${{ parameters.GrafanaVariableGroup }} From f5337974e3d74143ece2a43891ad361f9733b4e1 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Mon, 13 Oct 2025 11:35:05 -0700 Subject: [PATCH 21/25] provision azure managed grafana workspace --- azure-pipelines.yml | 22 ++++++++++++++++++- .../deployment/azure-managed-grafana.bicep | 0 2 files changed, 21 insertions(+), 1 deletion(-) rename azure-managed-grafana.bicep => eng/deployment/azure-managed-grafana.bicep (100%) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f14b3978b..1b70f57ab 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -252,4 +252,24 @@ extends: GrafanaKeyVault: dotnet-grafana GrafanaVariableGroup: Dotnet-Grafana-Production ServiceConnectionClientId: fc1eb341-aea4-4a11-8f80-d14b8775b2ba - ServiceConnectionId: 4a511f6f-b538-48e6-a389-207e430634d1 \ No newline at end of file + ServiceConnectionId: 4a511f6f-b538-48e6-a389-207e430634d1 + + - ${{ if in(variables['Build.SourceBranch'], 'refs/heads/haruna/managed-grafana-new', 'refs/heads/production')}}: + - template: /eng/deploy-managed-grafana.yml@self + parameters: + ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/production') }}: + DeploymentEnvironment: Staging + ServiceConnectionName: dnceng-managed-grafana-staging + GrafanaWorkspaceName: dnceng-grafana-staging + GrafanaKeyVault: dnceng-grafana-int-kv + GrafanaVariableGroup: Dnceng-Managed-Grafana-Staging-Vg + ServiceConnectionClientId: 4ad9ae35-2d42-4245-a954-9003b7e31349 + ServiceConnectionId: f955b932-c7e3-48f7-9d67-4e6542b3568a + ${{ else }}: + DeploymentEnvironment: Production + ServiceConnectionName: dnceng-managed-grafana + GrafanaWorkspaceName: dnceng-grafana + GrafanaKeyVault: dnceng-grafana-prod-kv + GrafanaVariableGroup: Dnceng-Managed-Grafana-Vg + ServiceConnectionClientId: 0ceeca1a-31e7-49ee-9bf4-15f14ed28fa4 + ServiceConnectionId: 332b249e-769b-49a9-9dc9-d82afe28ec0a \ No newline at end of file diff --git a/azure-managed-grafana.bicep b/eng/deployment/azure-managed-grafana.bicep similarity index 100% rename from azure-managed-grafana.bicep rename to eng/deployment/azure-managed-grafana.bicep From a074e783d77856e7ab722cc772145b26e51b5e24 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Mon, 13 Oct 2025 11:53:53 -0700 Subject: [PATCH 22/25] fix bicep file path --- azure-pipelines.yml | 1 - eng/provision-grafana.yaml | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1b70f57ab..a8348fd4d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -254,7 +254,6 @@ extends: ServiceConnectionClientId: fc1eb341-aea4-4a11-8f80-d14b8775b2ba ServiceConnectionId: 4a511f6f-b538-48e6-a389-207e430634d1 - - ${{ if in(variables['Build.SourceBranch'], 'refs/heads/haruna/managed-grafana-new', 'refs/heads/production')}}: - template: /eng/deploy-managed-grafana.yml@self parameters: ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/production') }}: diff --git a/eng/provision-grafana.yaml b/eng/provision-grafana.yaml index 548953b0a..72faafe97 100644 --- a/eng/provision-grafana.yaml +++ b/eng/provision-grafana.yaml @@ -49,11 +49,11 @@ jobs: scriptLocation: 'inlineScript' inlineScript: | Write-Host "Validating Grafana Bicep template..." - if (!(Test-Path "azure-managed-grafana.bicep")) { + if (!(Test-Path "eng/deployment/azure-managed-grafana.bicep")) { throw "Bicep template not found: azure-managed-grafana.bicep" } - - az bicep build --file azure-managed-grafana.bicep + + az bicep build --file eng/deployment/azure-managed-grafana.bicep if ($LASTEXITCODE -ne 0) { throw "Bicep template validation failed" } @@ -92,7 +92,7 @@ jobs: resourceGroupName: '${{ parameters.GrafanaResourceGroup }}' location: '${{ parameters.GrafanaLocation }}' templateLocation: 'Linked artifact' - csmFile: 'azure-managed-grafana.bicep' + csmFile: 'eng/deployment/azure-managed-grafana.bicep' overrideParameters: '-location "${{ parameters.GrafanaLocation }}" -grafanaWorkspaceName "${{ parameters.GrafanaWorkspaceName }}" -skuName "Standard"' deploymentMode: 'Incremental' deploymentName: 'grafana-${{ parameters.DeploymentEnvironment }}-$(Build.BuildNumber)' From 7fc2c347fe9f4c27d4911121ea620236dab956b9 Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Mon, 13 Oct 2025 15:17:49 -0700 Subject: [PATCH 23/25] add provsion grafana stage to the deployment --- eng/deploy-managed-grafana.yml | 3 +++ eng/deploy.yaml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/eng/deploy-managed-grafana.yml b/eng/deploy-managed-grafana.yml index 777095736..15b7fd166 100644 --- a/eng/deploy-managed-grafana.yml +++ b/eng/deploy-managed-grafana.yml @@ -17,6 +17,9 @@ parameters: stages: - stage: ProvisionGrafana displayName: 'Provision Grafana Infrastructure' + dependsOn: + - predeploy + - approval jobs: - template: /eng/provision-grafana.yaml@self parameters: diff --git a/eng/deploy.yaml b/eng/deploy.yaml index efd8a04cf..3e18cd66a 100644 --- a/eng/deploy.yaml +++ b/eng/deploy.yaml @@ -156,6 +156,7 @@ stages: demands: ImageOverride -equals 1es-windows-2019 dependsOn: - deploy + - ProvisionGrafana variables: - group: ${{ parameters.StatusVariableGroup }} - group: ${{ parameters.GrafanaVariableGroup }} @@ -201,6 +202,7 @@ stages: demands: ImageOverride -equals 1es-windows-2019 dependsOn: - deploy + - ProvisionGrafana jobs: - job: scenario displayName: Scenario tests From b5ea9cae1516cc3418ec5725094f5827c4aa419d Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Mon, 13 Oct 2025 17:15:19 -0700 Subject: [PATCH 24/25] add deploy azure managed grafana script --- eng/deployment/deploy-grafana.ps1 | 141 ++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 eng/deployment/deploy-grafana.ps1 diff --git a/eng/deployment/deploy-grafana.ps1 b/eng/deployment/deploy-grafana.ps1 new file mode 100644 index 000000000..f41b7efe8 --- /dev/null +++ b/eng/deployment/deploy-grafana.ps1 @@ -0,0 +1,141 @@ +# Azure Managed Grafana Deployment Script +# This script deploys an Azure Managed Grafana workspace using Bicep + +param( + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + + [Parameter(Mandatory = $true)] + [string]$Location, + + [Parameter(Mandatory = $true)] + [string]$GrafanaWorkspaceName, + + [Parameter(Mandatory = $false)] + [string]$DeploymentName = "grafana-deployment-$(Get-Date -Format 'yyyyMMdd-HHmmss')", + + [Parameter(Mandatory = $false)] + [switch]$WhatIf = $false +) + +# Set error action preference +$ErrorActionPreference = "Stop" + +Write-Host "=======================================" -ForegroundColor Cyan +Write-Host "Azure Managed Grafana Deployment Script" -ForegroundColor Cyan +Write-Host "=======================================" -ForegroundColor Cyan + +try { + # Check if Azure CLI is installed + Write-Host "Checking Azure CLI installation..." -ForegroundColor Yellow + az version 2>$null | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "Azure CLI is not installed or not in PATH. Please install Azure CLI first." + } + Write-Host "āœ“ Azure CLI is installed" -ForegroundColor Green + + # Check if user is logged in + Write-Host "Checking Azure authentication..." -ForegroundColor Yellow + $account = az account show 2>$null | ConvertFrom-Json + if ($LASTEXITCODE -ne 0) { + Write-Host "Not logged in to Azure. Please login..." -ForegroundColor Yellow + az login + if ($LASTEXITCODE -ne 0) { + throw "Failed to login to Azure" + } + } + Write-Host "āœ“ Authenticated as: $($account.user.name)" -ForegroundColor Green + + # Set the subscription + Write-Host "Setting subscription to: $SubscriptionId" -ForegroundColor Yellow + az account set --subscription $SubscriptionId + if ($LASTEXITCODE -ne 0) { + throw "Failed to set subscription. Please check if the subscription ID is correct and you have access." + } + Write-Host "āœ“ Subscription set successfully" -ForegroundColor Green + + # Check if resource group exists, create if it doesn't + Write-Host "Checking if resource group '$ResourceGroupName' exists..." -ForegroundColor Yellow + az group show --name $ResourceGroupName 2>$null | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host "Resource group doesn't exist. Creating..." -ForegroundColor Yellow + az group create --name $ResourceGroupName --location $Location + if ($LASTEXITCODE -ne 0) { + throw "Failed to create resource group" + } + Write-Host "āœ“ Resource group created successfully" -ForegroundColor Green + } else { + Write-Host "āœ“ Resource group already exists" -ForegroundColor Green + } + + # Get the Bicep file path + $bicepFile = Join-Path $PSScriptRoot "azure-managed-grafana.bicep" + if (!(Test-Path $bicepFile)) { + throw "Bicep file not found at: $bicepFile" + } + Write-Host "āœ“ Bicep file found: $bicepFile" -ForegroundColor Green + + # Prepare deployment parameters + $parameters = @{ + location = $Location + grafanaWorkspaceName = $GrafanaWorkspaceName + skuName = "Standard" + } + + # Convert parameters to string format for Azure CLI + $paramString = ($parameters.GetEnumerator() | ForEach-Object { "$($_.Key)=`"$($_.Value)`"" }) -join " " + + # Run deployment + if ($WhatIf) { + Write-Host "Running what-if deployment..." -ForegroundColor Yellow + $cmd = "az deployment group what-if --resource-group $ResourceGroupName --template-file `"$bicepFile`" --parameters $paramString" + Write-Host "Command: $cmd" -ForegroundColor Gray + Invoke-Expression $cmd + } else { + Write-Host "Starting deployment..." -ForegroundColor Yellow + Write-Host "Deployment name: $DeploymentName" -ForegroundColor Gray + Write-Host "Resource group: $ResourceGroupName" -ForegroundColor Gray + Write-Host "Grafana workspace name: $GrafanaWorkspaceName" -ForegroundColor Gray + + $cmd = "az deployment group create --resource-group $ResourceGroupName --name $DeploymentName --template-file `"$bicepFile`" --parameters $paramString" + Write-Host "Command: $cmd" -ForegroundColor Gray + + $result = Invoke-Expression $cmd | ConvertFrom-Json + + if ($LASTEXITCODE -eq 0) { + Write-Host "=======================================" -ForegroundColor Green + Write-Host "āœ“ Deployment completed successfully!" -ForegroundColor Green + Write-Host "=======================================" -ForegroundColor Green + + # Display outputs + if ($result.properties.outputs) { + Write-Host "Deployment Outputs:" -ForegroundColor Cyan + $result.properties.outputs | ConvertTo-Json -Depth 3 | Write-Host + } + + # Get the Grafana workspace details + Write-Host "`nGrafana Workspace Details:" -ForegroundColor Cyan + $grafana = az grafana show --name $GrafanaWorkspaceName --resource-group $ResourceGroupName | ConvertFrom-Json + Write-Host "Workspace Name: $($grafana.name)" -ForegroundColor White + Write-Host "Workspace URL: $($grafana.properties.endpoint)" -ForegroundColor White + Write-Host "Location: $($grafana.location)" -ForegroundColor White + Write-Host "SKU: $($grafana.sku.name)" -ForegroundColor White + Write-Host "System Managed Identity: $($grafana.identity.principalId)" -ForegroundColor White + } else { + throw "Deployment failed" + } + } +} +catch { + Write-Host "=======================================" -ForegroundColor Red + Write-Host "āŒ Error occurred during deployment:" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + Write-Host "=======================================" -ForegroundColor Red + exit 1 +} + +Write-Host "`nšŸŽ‰ Script completed successfully!" -ForegroundColor Green +Write-Host "You can now access your Grafana workspace and configure it as needed." -ForegroundColor Yellow From 6ff2dba12422300c3030bae32c569776edd600ec Mon Sep 17 00:00:00 2001 From: Haruna Ogweda Date: Tue, 14 Oct 2025 11:26:28 -0700 Subject: [PATCH 25/25] remove test pipeline --- azure-pipelines-managed-grafana.yml | 49 ----------------------------- 1 file changed, 49 deletions(-) delete mode 100644 azure-pipelines-managed-grafana.yml diff --git a/azure-pipelines-managed-grafana.yml b/azure-pipelines-managed-grafana.yml deleted file mode 100644 index 51bd53271..000000000 --- a/azure-pipelines-managed-grafana.yml +++ /dev/null @@ -1,49 +0,0 @@ -trigger: - batch: true - branches: - include: - - haruna/managed-grafana-new - - production -pr: none - -resources: - repositories: - - repository: 1ESPipelineTemplates - type: git - name: 1ESPipelineTemplates/1ESPipelineTemplates - ref: refs/tags/release -extends: - template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates - parameters: - pool: - name: NetCore1ESPool-Internal - image: 1es-windows-2022 - os: windows - sdl: - policheck: - enabled: true - tsa: - enabled: true - - stages: - - ${{ if in(variables['Build.SourceBranch'], 'refs/heads/haruna/managed-grafana-new', 'refs/heads/production')}}: - - template: /eng/deploy-managed-grafana.yml@self - parameters: - ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/production') }}: - DeploymentEnvironment: Staging - ServiceConnectionName: dnceng-managed-grafana-staging - GrafanaWorkspaceName: dnceng-grafana-staging - GrafanaKeyVault: dnceng-grafana-int-kv - GrafanaVariableGroup: Dnceng-Managed-Grafana-Staging-Vg - ServiceConnectionClientId: 4ad9ae35-2d42-4245-a954-9003b7e31349 - ServiceConnectionId: f955b932-c7e3-48f7-9d67-4e6542b3568a - ${{ else }}: - DeploymentEnvironment: Production - ServiceConnectionName: dnceng-managed-grafana - GrafanaWorkspaceName: dnceng-grafana - GrafanaKeyVault: dnceng-grafana-prod-kv - GrafanaVariableGroup: Dnceng-Managed-Grafana-Vg - ServiceConnectionClientId: 0ceeca1a-31e7-49ee-9bf4-15f14ed28fa4 - ServiceConnectionId: 332b249e-769b-49a9-9dc9-d82afe28ec0a - -