From 7830f8e24cb875f04b7d121a18b6eb1df60c0599 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Tue, 5 Dec 2023 02:41:54 +1000 Subject: [PATCH] Fixes duplicate resource sub-resources #2564 (#2565) --- docs/CHANGELOG-v1.md | 2 + .../Common/JsonExtensions.cs | 20 ++++ .../Common/ResourceHelper.cs | 56 ++++++++++- .../Data/Template/RuleDataExportVisitor.cs | 74 ++++++++++++-- .../Data/Template/TemplateVisitor.cs | 7 +- .../ResourceHelperTests.cs | 8 +- .../Template.Bicep.2.json | 8 +- .../TemplateVisitorTests.cs | 8 +- .../Tests.Bicep.7.child.bicep | 2 +- .../Tests.Bicep.7.json | 56 ++++------- .../Tests.Bicep.8.bicep | 8 ++ .../Tests.Bicep.8.json | 97 ++++++++++++++++--- .../Tests.Bicep.8.sb.bicep | 25 +++++ 13 files changed, 298 insertions(+), 73 deletions(-) create mode 100644 tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.sb.bicep diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index d461e3e506d..f14c3b11968 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -41,6 +41,8 @@ What's changed since v1.31.3: - Bug fixes: - Fixed additional false positives of `Azure.Deployment.SecureParameter` by @BernieWhite. [#2556](https://github.com/Azure/PSRule.Rules.Azure/issues/2556) + - Fixed expansion with sub-resource handling of deployments with duplicate resources by @BernieWhite. + [#2564](https://github.com/Azure/PSRule.Rules.Azure/issues/2564) ## v1.31.3 diff --git a/src/PSRule.Rules.Azure/Common/JsonExtensions.cs b/src/PSRule.Rules.Azure/Common/JsonExtensions.cs index 67df3fc6e6d..c0c6693611d 100644 --- a/src/PSRule.Rules.Azure/Common/JsonExtensions.cs +++ b/src/PSRule.Rules.Azure/Common/JsonExtensions.cs @@ -127,6 +127,14 @@ internal static bool TryGetResources(this JObject resource, string type, out JOb return results.Count > 0; } + internal static bool TryGetResourcesArray(this JObject resource, out JArray resources) + { + if (resource.TryGetProperty(PROPERTY_RESOURCES, out resources)) + return true; + + return false; + } + internal static bool PropertyEquals(this JObject o, string propertyName, string value) { return o.TryGetProperty(propertyName, out var s) && string.Equals(s, value, StringComparison.OrdinalIgnoreCase); @@ -175,6 +183,18 @@ internal static void AddRange(this JArray o, IEnumerable items) o.Add(item); } + /// + /// Add items to the start of the array instead of the end. + /// + /// The to add items to. + /// A set of items to add. + internal static void AddRangeFromStart(this JArray o, IEnumerable items) + { + var counter = 0; + foreach (var item in items) + o.Insert(counter++, item); + } + internal static IEnumerable GetPeerConditionByField(this JObject o, string field) { return o.BeforeSelf().OfType().Where(peer => peer.TryGetProperty(PROPERTY_FIELD, out var peerField) && diff --git a/src/PSRule.Rules.Azure/Common/ResourceHelper.cs b/src/PSRule.Rules.Azure/Common/ResourceHelper.cs index 84002abef11..45a78b957e3 100644 --- a/src/PSRule.Rules.Azure/Common/ResourceHelper.cs +++ b/src/PSRule.Rules.Azure/Common/ResourceHelper.cs @@ -10,6 +10,7 @@ namespace PSRule.Rules.Azure internal static class ResourceHelper { private const string SLASH = "/"; + private const string DOT = "."; private const string SUBSCRIPTIONS = "subscriptions"; private const string RESOURCEGROUPS = "resourceGroups"; private const string PROVIDERS = "providers"; @@ -39,6 +40,25 @@ internal static bool IsResourceType(string resourceId, string resourceType) return i == idParts.Length; } + /// + /// Determine if the resource type is a sub-resource of the parent resource Id. + /// + /// The resource Id of the parent resource. + /// The resource type of sub-resource. + /// Returns true if the resource type is a sub-resource. Otherwise false is returned. + internal static bool IsSubResourceType(string parentId, string resourceType) + { + if (string.IsNullOrEmpty(parentId) || string.IsNullOrEmpty(parentId)) + return false; + + // Is the resource type has no provider namespace dot it is a sub type. + if (!resourceType.Contains(DOT)) return true; + + // Compare full type names. + var parentType = GetResourceType(parentId); + return resourceType.StartsWith(parentType, StringComparison.OrdinalIgnoreCase); + } + /// /// Get the resource type of the resource. /// @@ -111,10 +131,15 @@ internal static string CombineResourceId(string subscriptionId, string resourceG throw new TemplateFunctionException(nameof(resourceType), FunctionErrorType.MismatchingResourceSegments, PSRuleResources.MismatchingResourceSegments); var parts = resourceTypeLength + nameLength; - parts += parts > 0 ? 1 : 0; parts += subscriptionId != null ? 2 : 0; parts += resourceGroup != null ? 2 : 0; + for (var p = 0; resourceType != null && p < resourceType.Length; p++) + { + if (resourceType[p].Contains(DOT)) + parts++; + } + var result = new string[parts * 2]; var i = 0; var j = 0; @@ -135,10 +160,15 @@ internal static string CombineResourceId(string subscriptionId, string resourceG } if (resourceTypeLength > 0 && depth >= 0) { - result[i++] = SLASH; - result[i++] = PROVIDERS; while (i < result.Length && j <= depth) { + // If a resource provider is included prepend /providers. + if (resourceType[j].Contains(DOT)) + { + result[i++] = SLASH; + result[i++] = PROVIDERS; + } + result[i++] = SLASH; result[i++] = resourceType[j]; result[i++] = SLASH; @@ -148,12 +178,30 @@ internal static string CombineResourceId(string subscriptionId, string resourceG return string.Concat(result); } - internal static string CombineResourceId(string subscriptionId, string resourceGroup, string resourceType, string name, int depth = int.MaxValue) + internal static string CombineResourceId(string subscriptionId, string resourceGroup, string resourceType, string name, int depth = int.MaxValue, string scope = null) { TryResourceIdComponents(resourceType, name, out var typeComponents, out var nameComponents); + + // Handle scoped resource IDs. + if (!string.IsNullOrEmpty(scope) && scope != SLASH && TryResourceIdComponents(scope, out subscriptionId, out resourceGroup, out var parentTypeComponents, nameComponents: out var parentNameComponents)) + { + typeComponents = MergeComponents(parentTypeComponents, typeComponents); + nameComponents = MergeComponents(parentNameComponents, nameComponents); + } return CombineResourceId(subscriptionId, resourceGroup, typeComponents, nameComponents, depth); } + /// + /// Merge type or name components from parent and child. + /// + private static string[] MergeComponents(string[] parent, string[] child) + { + var combined = new string[parent.Length + child.Length]; + Array.Copy(parent, combined, parent.Length); + Array.Copy(child, 0, combined, parent.Length, child.Length); + return combined; + } + /// /// Combines Id fragments to form a resource Id at a management group scope. /// diff --git a/src/PSRule.Rules.Azure/Data/Template/RuleDataExportVisitor.cs b/src/PSRule.Rules.Azure/Data/Template/RuleDataExportVisitor.cs index 994fe6c14bb..81ef02c5f2b 100644 --- a/src/PSRule.Rules.Azure/Data/Template/RuleDataExportVisitor.cs +++ b/src/PSRule.Rules.Azure/Data/Template/RuleDataExportVisitor.cs @@ -322,27 +322,34 @@ private static bool ProjectWebApp(IResourceValue resource) /// private static void MergeResource(TemplateContext context, IResourceValue resource, List unprocessed, HashSet processed) { - if (!ShouldMerge(resource.Type)) - return; + _ = MergeResourceLeft(context, resource, unprocessed, processed) || + MergeResourceRight(context, resource, unprocessed, processed); + } + + private static bool MergeResourceLeft(TemplateContext context, IResourceValue resource, List unprocessed, HashSet processed) + { + if (!ShouldMergeLeft(resource.Type)) + return false; var duplicates = unprocessed.FindAll(x => x.Id == resource.Id); for (var i = 1; duplicates.Count > 1 && i < duplicates.Count; i++) { - MergeResource(duplicates[0].Value, duplicates[i].Value, processed); + MergeResourceLeft(duplicates[0].Value, duplicates[i].Value, processed); unprocessed.Remove(duplicates[i]); context.RemoveResource(duplicates[i]); } + return true; } /// /// Merge specific resources and thier sub-resources. /// - private static void MergeResource(JObject resourceA, JObject resourceB, HashSet processed) + private static void MergeResourceLeft(JObject left, JObject right, HashSet processed) { - resourceA.Merge(resourceB, _MergeSettings); + left.Merge(right, _MergeSettings); // Handle child resources - if (resourceA.TryGetResources(out var resources)) + if (left.TryGetResources(out var resources)) { for (var i = 0; resources != null && i < resources.Length; i++) { @@ -352,7 +359,7 @@ private static void MergeResource(JObject resourceA, JObject resourceB, HashSet< var duplicates = Array.FindAll(resources, x => x[PROPERTY_ID].ToString() == childResourceId); for (var j = 1; duplicates.Length > 1 && j < duplicates.Length; j++) { - MergeResource(duplicates[0].Value(), duplicates[j].Value(), processed); + MergeResourceLeft(duplicates[0].Value(), duplicates[j].Value(), processed); duplicates[j].Remove(); } processed.Add(childResourceId); @@ -360,6 +367,57 @@ private static void MergeResource(JObject resourceA, JObject resourceB, HashSet< } } + /// + /// Merge resources based on duplicates which could occur across modules. + /// Last instance wins but sub-resources are accumulated. + /// + private static bool MergeResourceRight(TemplateContext context, IResourceValue resource, List unprocessed, HashSet processed) + { + var duplicates = unprocessed.FindAll(x => x.Id == resource.Id); + for (var i = 1; duplicates.Count > 1 && i < duplicates.Count; i++) + { + MergeResourceRight(duplicates[i - 1].Value, duplicates[i].Value, processed); + unprocessed.Remove(duplicates[i - 1]); + context.RemoveResource(duplicates[i - 1]); + } + + var right = duplicates[duplicates.Count - 1]; + if (duplicates.Count > 1 && right.Value.TryGetResources(out var subResources)) + { + for (var i = subResources.Length - 1; i >= 0; i--) + { + if (!subResources[i].TryGetProperty(PROPERTY_ID, out var childResourceId)) + continue; + + if (processed.Contains(childResourceId)) + { + subResources[i].Remove(); + } + else + { + processed.Add(childResourceId); + } + } + } + return true; + } + + /// + /// Merge resources sub-resources only. + /// + private static void MergeResourceRight(JObject left, JObject right, HashSet processed) + { + // Get sub-resources. + if (left.TryGetResources(out var leftResources)) + { + if (!right.TryGetResourcesArray(out var rightResources)) + rightResources = new JArray(); + + rightResources.AddRangeFromStart(leftResources); + right.ReplaceProperty(PROPERTY_RESOURCES, rightResources); + } + } + /// /// Move sub-resources based on parent resource relationship. /// This process nests sub-resources so that relationship can be analyzed. @@ -398,7 +456,7 @@ private static bool ShouldMove(string resourceType) /// /// Determines if the specific resource type should be merged. /// - private static bool ShouldMerge(string resourceType) + private static bool ShouldMergeLeft(string resourceType) { return string.Equals(resourceType, "Microsoft.Storage/storageAccounts", StringComparison.OrdinalIgnoreCase); } diff --git a/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs b/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs index fb081739076..9987465efb0 100644 --- a/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs +++ b/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs @@ -1256,16 +1256,19 @@ private static ResourceValue ResourceInstance(TemplateContext context, JObject r var subscriptionId = context.Subscription.SubscriptionId; var resourceGroupName = context.ResourceGroup.Name; - // Handle special case for cross-scope deployments which may have an alternative subscription or resource group set + // Handle special case for cross-scope deployments which may have an alternative subscription or resource group set. if (IsDeploymentResource(type)) { subscriptionId = ResolveDeploymentScopeProperty(context, resource, PROPERTY_SUBSCRIPTIONID, subscriptionId); resourceGroupName = ResolveDeploymentScopeProperty(context, resource, PROPERTY_RESOURCEGROUP, resourceGroupName); } + // Get scope if specified. + var scope = context.TryParentResourceId(resource, out var parentIds) && parentIds != null && parentIds.Length > 0 ? parentIds[0] : null; + string resourceId = null; if (context.Deployment.DeploymentScope == DeploymentScope.ResourceGroup) - resourceId = ResourceHelper.CombineResourceId(subscriptionId, resourceGroupName, type, name); + resourceId = ResourceHelper.CombineResourceId(subscriptionId, resourceGroupName, type, name, scope: scope); if (context.Deployment.DeploymentScope == DeploymentScope.Subscription) resourceId = ResourceHelper.CombineResourceId(subscriptionId, null, type, name); diff --git a/tests/PSRule.Rules.Azure.Tests/ResourceHelperTests.cs b/tests/PSRule.Rules.Azure.Tests/ResourceHelperTests.cs index c7030856c91..ec151557dc1 100644 --- a/tests/PSRule.Rules.Azure.Tests/ResourceHelperTests.cs +++ b/tests/PSRule.Rules.Azure.Tests/ResourceHelperTests.cs @@ -85,13 +85,15 @@ public void CombineResourceId() { "microsoft.operationalinsights/workspaces", "Microsoft.Network/virtualNetworks/subnets", - "Microsoft.KeyVault/vaults/providers/diagnosticSettings" + "Microsoft.KeyVault/vaults/providers/diagnosticSettings", + "Microsoft.Insights/diagnosticSettings" }; var resourceName = new string[] { "workspace001", "vnet-A/GatewaySubnet", - "kv-bicep-app-002/Microsoft.Insights/service" + "kv-bicep-app-002/Microsoft.Insights/service", + "service" }; var id = new string[] { @@ -100,11 +102,13 @@ public void CombineResourceId() "/subscriptions/ffffffff-ffff-ffff-ffff-ffffffffffff/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/kv-bicep-app-002/providers/Microsoft.Insights/diagnosticSettings/service", "/subscriptions/ffffffff-ffff-ffff-ffff-ffffffffffff/resourceGroups/test-rg" }; + var scope = "/subscriptions/ffffffff-ffff-ffff-ffff-ffffffffffff/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/kv-bicep-app-002"; Assert.Equal(id[0], ResourceHelper.CombineResourceId("00000000-0000-0000-0000-000000000000", "rg-test", resourceType[0], resourceName[0])); Assert.Equal(id[1], ResourceHelper.CombineResourceId("ffffffff-ffff-ffff-ffff-ffffffffffff", "test-rg", resourceType[1], resourceName[1])); Assert.Equal(id[2], ResourceHelper.CombineResourceId("ffffffff-ffff-ffff-ffff-ffffffffffff", "test-rg", resourceType[2], resourceName[2])); Assert.Equal(id[3], ResourceHelper.CombineResourceId("ffffffff-ffff-ffff-ffff-ffffffffffff", "test-rg", (string[])null, null)); + Assert.Equal(id[2], ResourceHelper.CombineResourceId("ffffffff-ffff-ffff-ffff-ffffffffffff", "test-rg", resourceType[3], resourceName[3], scope: scope)); } [Fact] diff --git a/tests/PSRule.Rules.Azure.Tests/Template.Bicep.2.json b/tests/PSRule.Rules.Azure.Tests/Template.Bicep.2.json index 681ee13a14a..83ed59bb5d4 100644 --- a/tests/PSRule.Rules.Azure.Tests/Template.Bicep.2.json +++ b/tests/PSRule.Rules.Azure.Tests/Template.Bicep.2.json @@ -158,7 +158,7 @@ "value": "kv-bicep-app-002" }, "workspaceId": { - "value": "notworkspace" + "value": "notworkspace" } }, "template": { @@ -346,7 +346,7 @@ { "type": "Microsoft.Resources/deployments", "apiVersion": "2020-06-01", - "name": "keyvault-deployment", + "name": "keyvault-deployment-2", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -357,7 +357,7 @@ "value": "kv-bicep-app-003" }, "workspaceId": { - "value": "[parameters('workspaceId')]" + "value": "[parameters('workspaceId')]" } }, "template": { @@ -543,4 +543,4 @@ } } ] -} \ No newline at end of file +} diff --git a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs index 97a301a3dbb..15fb0ea1e50 100644 --- a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs +++ b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs @@ -644,7 +644,7 @@ public void MaterializedView() { var resources = ProcessTemplate(GetSourcePath("Tests.Bicep.8.json"), null); Assert.NotNull(resources); - Assert.Equal(8, resources.Length); + Assert.Equal(11, resources.Length); var storage = resources.FirstOrDefault(r => r["type"].ToString() == "Microsoft.Storage/storageAccounts"); Assert.NotNull(storage); @@ -662,6 +662,12 @@ public void MaterializedView() Assert.Equal("abc", sqlServer["properties"]["administrators"]["login"].ToString()); Assert.Equal("ffffffff-ffff-ffff-ffff-ffffffffffff", sqlServer["identity"]["principalId"]); Assert.Equal("ffffffff-ffff-ffff-ffff-ffffffffffff", sqlServer["identity"]["tenantId"]); + + var serviceBus = resources.FirstOrDefault(r => r["type"].ToString() == "Microsoft.ServiceBus/namespaces"); + subResources = serviceBus["resources"].Values().ToArray(); + Assert.Single(subResources); + Assert.Equal("d2", subResources[0]["properties"]["other"].Value()); + Assert.Equal("/subscriptions/ffffffff-ffff-ffff-ffff-ffffffffffff/resourceGroups/ps-rule-test-rg/providers/Microsoft.ServiceBus/namespaces/servicebus/providers/Microsoft.Insights/diagnosticSettings/logs", subResources[0]["id"].Value()); } [Fact] diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.7.child.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.7.child.bicep index d916dac1db5..b1273578473 100644 --- a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.7.child.bicep +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.7.child.bicep @@ -10,7 +10,7 @@ resource vault 'Microsoft.KeyVault/vaults@2021-10-01' existing = { resource kvSecret 'Microsoft.KeyVault/vaults/secrets@2021-10-01' = { parent: vault - name: 'secret1' + name: 'secret2' properties: { value: secret } diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.7.json b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.7.json index 7483ab1284c..644d898bd04 100644 --- a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.7.json +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.7.json @@ -1,13 +1,11 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "1.10-experimental", "contentVersion": "1.0.0.0", "metadata": { - "_EXPERIMENTAL_WARNING": "Symbolic name support in ARM is experimental, and should be enabled for testing purposes only. Do not enable this setting for any production usage, or you may be unexpectedly broken at any time!", "_generator": { "name": "bicep", - "version": "0.16.2.56959", - "templateHash": "10585187775011836453" + "version": "0.23.1.45101", + "templateHash": "12192623794386839246" } }, "parameters": { @@ -20,14 +18,8 @@ "defaultValue": "[newGuid()]" } }, - "resources": { - "vault": { - "existing": true, - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2021-10-01", - "name": "vault1" - }, - "kvSecret": { + "resources": [ + { "type": "Microsoft.KeyVault/vaults/secrets", "apiVersion": "2021-10-01", "name": "[format('{0}/{1}', 'vault1', 'secret1')]", @@ -35,7 +27,7 @@ "value": "[parameters('secret')]" } }, - "storage": { + { "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2021-09-01", "name": "storage1", @@ -45,7 +37,7 @@ }, "kind": "StorageV2" }, - "child": { + { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "child", @@ -66,14 +58,12 @@ }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "1.10-experimental", "contentVersion": "1.0.0.0", "metadata": { - "_EXPERIMENTAL_WARNING": "Symbolic name support in ARM is experimental, and should be enabled for testing purposes only. Do not enable this setting for any production usage, or you may be unexpectedly broken at any time!", "_generator": { "name": "bicep", - "version": "0.16.2.56959", - "templateHash": "13134284129733494715" + "version": "0.23.1.45101", + "templateHash": "4029531724262360110" } }, "parameters": { @@ -81,22 +71,16 @@ "type": "securestring" } }, - "resources": { - "vault": { - "existing": true, - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2021-10-01", - "name": "vault1" - }, - "kvSecret": { + "resources": [ + { "type": "Microsoft.KeyVault/vaults/secrets", "apiVersion": "2021-10-01", - "name": "[format('{0}/{1}', 'vault1', 'secret1')]", + "name": "[format('{0}/{1}', 'vault1', 'secret2')]", "properties": { "value": "[parameters('secret')]" } } - }, + ], "outputs": { "secretFromParameter": { "type": "string", @@ -106,7 +90,7 @@ } } }, - "good": { + { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "good", @@ -117,14 +101,12 @@ "mode": "Incremental", "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "1.10-experimental", "contentVersion": "1.0.0.0", "metadata": { - "_EXPERIMENTAL_WARNING": "Symbolic name support in ARM is experimental, and should be enabled for testing purposes only. Do not enable this setting for any production usage, or you may be unexpectedly broken at any time!", "_generator": { "name": "bicep", - "version": "0.16.2.56959", - "templateHash": "11626155488460482268" + "version": "0.23.1.45101", + "templateHash": "16879825181530868322" } }, "parameters": { @@ -133,7 +115,7 @@ "defaultValue": "" } }, - "resources": {}, + "resources": [], "outputs": { "value": { "type": "string", @@ -147,7 +129,7 @@ } } } - }, + ], "outputs": { "id": { "type": "string", @@ -155,7 +137,7 @@ }, "contentType": { "type": "string", - "value": "[reference('kvSecret').contentType]" + "value": "[reference(resourceId('Microsoft.KeyVault/vaults/secrets', 'vault1', 'secret1'), '2021-10-01').contentType]" }, "secret": { "type": "string", @@ -167,7 +149,7 @@ }, "secretFromChild": { "type": "string", - "value": "[reference('child').outputs.secretFromParameter.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'child'), '2022-09-01').outputs.secretFromParameter.value]" } } } \ No newline at end of file diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.bicep index 9de6926e563..6f6e306744f 100644 --- a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.bicep +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.bicep @@ -75,3 +75,11 @@ resource sqlAdmins 'Microsoft.Sql/servers/administrators@2022-02-01-preview' = { sid: '00000000-0000-0000-0000-000000000000' } } + +module servicebus './Tests.Bicep.8.sb.bicep' = [for item in [ 'd1', 'd2' ]: { + name: item + params: { + location: location + iteration: item + } +}] diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.json b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.json index 52951023f01..3ccd7e5898f 100644 --- a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.json +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.13.1.58284", - "templateHash": "11956986385837715242" + "version": "0.23.1.45101", + "templateHash": "12418087360216097267" } }, "parameters": { @@ -61,7 +61,7 @@ }, { "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", + "apiVersion": "2022-09-01", "name": "storage", "properties": { "expressionEvaluationOptions": { @@ -85,8 +85,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.13.1.58284", - "templateHash": "4480766204783076209" + "version": "0.23.1.45101", + "templateHash": "18148995500193283141" } }, "parameters": { @@ -128,7 +128,7 @@ }, { "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", + "apiVersion": "2022-09-01", "name": "storage2", "properties": { "expressionEvaluationOptions": { @@ -149,8 +149,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.13.1.58284", - "templateHash": "4480766204783076209" + "version": "0.23.1.45101", + "templateHash": "18148995500193283141" } }, "parameters": { @@ -195,7 +195,7 @@ }, { "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", + "apiVersion": "2022-09-01", "name": "blobServices", "properties": { "expressionEvaluationOptions": { @@ -208,8 +208,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.13.1.58284", - "templateHash": "2067010727711821958" + "version": "0.23.1.45101", + "templateHash": "17745890083576888678" } }, "parameters": { @@ -238,7 +238,7 @@ }, { "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", + "apiVersion": "2022-09-01", "name": "blobServices2", "properties": { "expressionEvaluationOptions": { @@ -256,8 +256,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.13.1.58284", - "templateHash": "2067010727711821958" + "version": "0.23.1.45101", + "templateHash": "17745890083576888678" } }, "parameters": { @@ -283,6 +283,75 @@ "dependsOn": [ "[resourceId('Microsoft.Resources/deployments', 'storage')]" ] + }, + { + "copy": { + "name": "servicebus", + "count": "[length(createArray('d1', 'd2'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[createArray('d1', 'd2')[copyIndex()]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "iteration": { + "value": "[createArray('d1', 'd2')[copyIndex()]]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "14604370522227617104" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "iteration": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.ServiceBus/namespaces", + "apiVersion": "2021-11-01", + "name": "servicebus", + "location": "[parameters('location')]" + }, + { + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ServiceBus/namespaces/{0}', 'servicebus')]", + "name": "logs", + "properties": { + "other": "[parameters('iteration')]", + "logs": [ + { + "enabled": true, + "categoryGroup": "AllLogs" + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ServiceBus/namespaces', 'servicebus')]" + ] + } + ] + } + } } ] } \ No newline at end of file diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.sb.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.sb.bicep new file mode 100644 index 00000000000..19f03a9e1da --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.8.sb.bicep @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +param location string = resourceGroup().location +param iteration string + +resource servicebus 'Microsoft.ServiceBus/namespaces@2021-11-01' = { + name: 'servicebus' + location: location +} + +resource dianotics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + scope: servicebus + name: 'logs' + properties: { + #disable-next-line BCP037 + other: iteration + logs: [ + { + enabled: true + categoryGroup: 'AllLogs' + } + ] + } +}