From 6d02f7b8c4747afe40713c5ab388ea1e2dcc74cb Mon Sep 17 00:00:00 2001 From: Bernie White Date: Thu, 15 Aug 2024 01:40:39 +1000 Subject: [PATCH] Fixed MG scope deployment link #3013 (#3019) * Fixed MG scope deployment link #3013 * Fix .NET docs build * Remove separated test * Fix subscription aliases for tagging --- .github/workflows/build.yaml | 5 - .github/workflows/docs.yaml | 3 + docs/CHANGELOG-v1.md | 5 + .../Common/ResourceHelper.cs | 3 + .../Data/Template/TemplateVisitor.cs | 20 ++- .../rules/Common.Selector.Rule.yaml | 17 +++ .../TemplateVisitorTests.cs | 2 +- .../Tests.Bicep.31.bicep | 16 ++- .../Tests.Bicep.31.child2.bicep | 16 +++ .../Tests.Bicep.31.child3.bicep | 11 ++ .../Tests.Bicep.31.child4.bicep | 14 ++ .../Tests.Bicep.31.json | 129 +++++++++++++++++- 12 files changed, 223 insertions(+), 18 deletions(-) create mode 100644 tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child2.bicep create mode 100644 tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child3.bicep create mode 100644 tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child4.bicep diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5a89be05e0..f521cf31fe 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,7 +16,6 @@ on: env: DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true - DOTNET_VERSION: 8.x jobs: build: @@ -32,8 +31,6 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - name: Install dependencies shell: pwsh @@ -110,8 +107,6 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - if: ${{ matrix.shell == 'pwsh' }} name: Install dependencies (PowerShell) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index e177bcc64c..1b4ff9621e 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -41,6 +41,9 @@ jobs: python-version: '3.11' architecture: x64 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + - name: Install dependencies run: | python3 -m pip install --upgrade pip diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index bb3cf93c00..6dc1b57a70 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -41,6 +41,11 @@ What's changed since pre-release v1.39.0-B0029: - Engineering: - Bump development tools to .NET 8.0 SDK by @BernieWhite. [#3017](https://github.com/Azure/PSRule.Rules.Azure/issues/3017) +- Bug fixed: + - Fixed expansion with deployments by resource ID at management group by @BernieWhite + [#3013](https://github.com/Azure/PSRule.Rules.Azure/issues/3013) + - Fixed subscription aliases don't support tags by @BernieWhite. + [#3021](https://github.com/Azure/PSRule.Rules.Azure/issues/3021) ## v1.39.0-B0029 (pre-release) diff --git a/src/PSRule.Rules.Azure/Common/ResourceHelper.cs b/src/PSRule.Rules.Azure/Common/ResourceHelper.cs index 45a78b957e..46e3d7622a 100644 --- a/src/PSRule.Rules.Azure/Common/ResourceHelper.cs +++ b/src/PSRule.Rules.Azure/Common/ResourceHelper.cs @@ -376,6 +376,9 @@ namespace PSRule.Rules.Azure if (start + 3 < parts.Length && StringComparer.OrdinalIgnoreCase.Equals(parts[start], PROVIDERS)) start++; + if (start == 0 && StringComparer.OrdinalIgnoreCase.Equals(parts[1], PROVIDERS)) + start += 2; + provider = parts[start++]; type = parts[start++]; name = parts[start++]; diff --git a/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs b/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs index 7102f189ad..9068b14107 100644 --- a/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs +++ b/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs @@ -281,14 +281,23 @@ namespace PSRule.Rules.Azure.Data.Template internal bool TryParentResourceId(JObject resource, out string[] resourceId) { resourceId = null; - if (!TryResourceScope(resource, out var id) || - !ResourceHelper.TryResourceIdComponents(id, out var subscriptionId, out var resourceGroupName, out string[] resourceTypeComponents, out string[] nameComponents)) + if (!TryResourceScope(resource, out var id)) + return false; + + if (id == TENANT_SCOPE) + { + resourceId = new string[] { TENANT_SCOPE }; + return true; + } + + if (!ResourceHelper.TryResourceIdComponents(id, out var subscriptionId, out var resourceGroupName, out string[] resourceTypeComponents, out string[] nameComponents)) return false; resourceId = new string[nameComponents.Length]; for (var i = 0; i < nameComponents.Length; i++) + { resourceId[i] = ResourceHelper.CombineResourceId(subscriptionId, resourceGroupName, resourceTypeComponents, nameComponents, depth: i); - + } return resourceId.Length > 0; } @@ -1331,7 +1340,10 @@ namespace PSRule.Rules.Azure.Data.Template else if (deploymentScope == DeploymentScope.Subscription) resourceId = ResourceHelper.CombineResourceId(subscriptionId, null, type, name); - else if (deploymentScope == DeploymentScope.ManagementGroup || deploymentScope == DeploymentScope.Tenant) + else if (deploymentScope == DeploymentScope.ManagementGroup) + resourceId = ResourceHelper.CombineResourceId(null, null, type, name, scope: scope ?? context.Deployment.Scope); + + else if (deploymentScope == DeploymentScope.Tenant) resourceId = ResourceHelper.CombineResourceId(null, null, type, name); context.UpdateResourceScope(resource); diff --git a/src/PSRule.Rules.Azure/rules/Common.Selector.Rule.yaml b/src/PSRule.Rules.Azure/rules/Common.Selector.Rule.yaml index 16e43ea86d..e3ff9d90e9 100644 --- a/src/PSRule.Rules.Azure/rules/Common.Selector.Rule.yaml +++ b/src/PSRule.Rules.Azure/rules/Common.Selector.Rule.yaml @@ -46,15 +46,25 @@ spec: # Exclude resource providers that do not support tags - type: '.' notStartsWith: + - Microsoft.ADHybridHealthService/ - Microsoft.Addons/ - Microsoft.Advisor/ - Microsoft.Billing/ + - Microsoft.BillingBenefits/ - Microsoft.Blueprint/ - Microsoft.Capacity/ + - Microsoft.ChangeAnalysis/ - Microsoft.Classic + - Microsoft.Commerce/ - Microsoft.Consumption/ + - Microsoft.CustomerLockbox/ + - Microsoft.Features/ - Microsoft.Gallery/ + - Microsoft.GuestConfiguration/ + - Microsoft.HybridConnectivity/ + - Microsoft.IoTSecurity/ - Microsoft.Security/ + - Microsoft.Subscription/ - microsoft.support/ - Microsoft.WorkloadMonitor/ - Microsoft.ManagedServices/ @@ -134,6 +144,13 @@ spec: - Microsoft.Insights/workbooks - Microsoft.Insights/workbookTemplates + - anyOf: + - type: '.' + notStartsWith: Microsoft.Chaos/ + - type: '.' + in: + - Microsoft.Chaos/experiments + - anyOf: - type: '.' notLike: 'Microsoft.*/*/*' diff --git a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs index d5ee1df178..066c46d28d 100644 --- a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs +++ b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs @@ -986,7 +986,7 @@ namespace PSRule.Rules.Azure public void ManagementGroupScopedResource() { var resources = ProcessTemplate(GetSourcePath("Tests.Bicep.31.json"), null, out _); - Assert.Equal(4, resources.Length); + Assert.Equal(9, resources.Length); var actual = resources[1]; Assert.Equal("Microsoft.Subscription/aliases", actual["type"].Value()); diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.bicep index 4d53e28f97..afb926995a 100644 --- a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.bicep +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.bicep @@ -9,11 +9,23 @@ resource subscriptionAlias 'Microsoft.Subscription/aliases@2021-10-01' = { properties: { workload: 'DevTest' displayName: 'sub1' - billingScope: ' /billingAccounts/nn/enrollmentAccounts/nn' + billingScope: '/billingAccounts/nn/enrollmentAccounts/nn' } } module rbac './Tests.Bicep.31.child.bicep' = { scope: subscription('00000000-0000-0000-0000-000000000000') - name: 'rbac' + name: 'child' +} + +module createSubscription './Tests.Bicep.31.child2.bicep' = { + name: 'child2' + scope: managementGroup() +} + +module createSubscriptionResources './Tests.Bicep.31.child3.bicep' = { + name: 'child3' + params: { + subscriptionId: createSubscription.outputs.subscriptionId + } } diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child2.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child2.bicep new file mode 100644 index 0000000000..a6c8acf926 --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child2.bicep @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +targetScope = 'managementGroup' + +resource subscriptionAlias 'Microsoft.Subscription/aliases@2021-10-01' = { + scope: tenant() + name: 'sub2' + properties: { + workload: 'DevTest' + displayName: 'sub2' + billingScope: '/billingAccounts/nn/enrollmentAccounts/nn' + } +} + +output subscriptionId string = subscriptionAlias.properties.subscriptionId diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child3.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child3.bicep new file mode 100644 index 0000000000..646941454e --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child3.bicep @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +targetScope = 'managementGroup' + +param subscriptionId string + +module deploySub './Tests.Bicep.31.child4.bicep' = { + name: 'child4' + scope: subscription(subscriptionId) +} diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child4.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child4.bicep new file mode 100644 index 0000000000..20491821ff --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.child4.bicep @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +targetScope = 'subscription' + +resource assignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: '48d15605-70a3-4676-bb6a-792f403786b5' + properties: { + principalId: '48d15605-70a3-4676-bb6a-792f403786b5' + roleDefinitionId: '48d15605-70a3-4676-bb6a-792f403786b5' + principalType: 'ServicePrincipal' + description: 'Test role assignment for checking scope and ID.' + } +} diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.json b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.json index dd12a166a2..d06b34dcaf 100644 --- a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.json +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.31.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.23.1.45101", - "templateHash": "1432163185374769317" + "version": "0.29.47.4906", + "templateHash": "18243599897785785680" } }, "resources": [ @@ -17,13 +17,13 @@ "properties": { "workload": "DevTest", "displayName": "sub1", - "billingScope": " /billingAccounts/nn/enrollmentAccounts/nn" + "billingScope": "/billingAccounts/nn/enrollmentAccounts/nn" } }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "rbac", + "name": "child", "subscriptionId": "00000000-0000-0000-0000-000000000000", "location": "[deployment().location]", "properties": { @@ -37,8 +37,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.23.1.45101", - "templateHash": "17477755083280448345" + "version": "0.29.47.4906", + "templateHash": "4248553621957476478" } }, "resources": [ @@ -56,6 +56,123 @@ ] } } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "child2", + "location": "[deployment().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "16465737476591302057" + } + }, + "resources": [ + { + "type": "Microsoft.Subscription/aliases", + "apiVersion": "2021-10-01", + "scope": "/", + "name": "sub2", + "properties": { + "workload": "DevTest", + "displayName": "sub2", + "billingScope": "/billingAccounts/nn/enrollmentAccounts/nn" + } + } + ], + "outputs": { + "subscriptionId": { + "type": "string", + "value": "[reference(tenantResourceId('Microsoft.Subscription/aliases', 'sub2'), '2021-10-01').subscriptionId]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "child3", + "location": "[deployment().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "subscriptionId": { + "value": "[reference(extensionResourceId(managementGroup().id, 'Microsoft.Resources/deployments', 'child2'), '2022-09-01').outputs.subscriptionId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "18410252302990608843" + } + }, + "parameters": { + "subscriptionId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "child4", + "subscriptionId": "[parameters('subscriptionId')]", + "location": "[deployment().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "5988084472097053332" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "48d15605-70a3-4676-bb6a-792f403786b5", + "properties": { + "principalId": "48d15605-70a3-4676-bb6a-792f403786b5", + "roleDefinitionId": "48d15605-70a3-4676-bb6a-792f403786b5", + "principalType": "ServicePrincipal", + "description": "Test role assignment for checking scope and ID." + } + } + ] + } + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(managementGroup().id, 'Microsoft.Resources/deployments', 'child2')]" + ] } ] } \ No newline at end of file