Skip to content

Commit 44118b9

Browse files
authored
Fixes policy deployment copy loop Azure#2605 (Azure#2613)
1 parent 4533f73 commit 44118b9

File tree

5 files changed

+245
-9
lines changed

5 files changed

+245
-9
lines changed

docs/CHANGELOG-v1.md

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ What's changed since v1.32.0:
3737
- Bug fixes:
3838
- Fixed quotes get incorrectly duplicated by @BernieWhite.
3939
[#2593](https://github.com/Azure/PSRule.Rules.Azure/issues/2593)
40+
- Fixed failure to expand copy loop in a Azure Policy deployment by @BernieWhite.
41+
[#2605](https://github.com/Azure/PSRule.Rules.Azure/issues/2605)
4042

4143
## v1.32.0
4244

src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs

+5-9
Original file line numberDiff line numberDiff line change
@@ -1764,10 +1764,9 @@ private static JToken ResolveToken(ITemplateContext context, JToken token)
17641764
/// </summary>
17651765
private static TemplateContext.CopyIndexState[] GetPropertyIterator(ITemplateContext context, JObject value)
17661766
{
1767-
if (value.ContainsKey(PROPERTY_COPY))
1767+
if (value.TryArrayProperty(PROPERTY_COPY, out var copyObjectArray))
17681768
{
17691769
var result = new List<TemplateContext.CopyIndexState>();
1770-
var copyObjectArray = value[PROPERTY_COPY].Value<JArray>();
17711770
for (var i = 0; i < copyObjectArray.Count; i++)
17721771
{
17731772
var copyObject = copyObjectArray[i] as JObject;
@@ -1792,13 +1791,12 @@ private static TemplateContext.CopyIndexState[] GetPropertyIterator(ITemplateCon
17921791
/// </summary>
17931792
private static TemplateContext.CopyIndexState[] GetOutputIterator(ITemplateContext context, JObject value)
17941793
{
1795-
if (value.ContainsKey(PROPERTY_COPY))
1794+
if (value.TryObjectProperty(PROPERTY_COPY, out var copyObject))
17961795
{
17971796
var result = new List<TemplateContext.CopyIndexState>();
1798-
var copyObject = value[PROPERTY_COPY].Value<JObject>();
17991797
var state = new TemplateContext.CopyIndexState
18001798
{
1801-
Name = "",
1799+
Name = string.Empty,
18021800
Input = copyObject[PROPERTY_INPUT],
18031801
Count = ExpandPropertyInt(context, copyObject, PROPERTY_COUNT)
18041802
};
@@ -1813,9 +1811,8 @@ private static TemplateContext.CopyIndexState[] GetOutputIterator(ITemplateConte
18131811

18141812
private static IEnumerable<TemplateContext.CopyIndexState> GetVariableIterator(ITemplateContext context, JObject value, bool pushToStack = true)
18151813
{
1816-
if (value.ContainsKey(PROPERTY_COPY))
1814+
if (value.TryArrayProperty(PROPERTY_COPY, out var copyObjectArray))
18171815
{
1818-
var copyObjectArray = value[PROPERTY_COPY].Value<JArray>();
18191816
for (var i = 0; i < copyObjectArray.Count; i++)
18201817
{
18211818
var copyObject = copyObjectArray[i] as JObject;
@@ -1848,9 +1845,8 @@ private static TemplateContext.CopyIndexState GetResourceIterator(TemplateContex
18481845
{
18491846
Input = value
18501847
};
1851-
if (value.ContainsKey(PROPERTY_COPY))
1848+
if (value.TryObjectProperty(PROPERTY_COPY, out var copyObject))
18521849
{
1853-
var copyObject = value[PROPERTY_COPY].Value<JObject>();
18541850
result.Name = ExpandProperty<string>(context, copyObject, PROPERTY_NAME);
18551851
result.Count = ExpandPropertyInt(context, copyObject, PROPERTY_COUNT);
18561852
context.CopyIndex.PushResourceType(result);

tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@
233233
<None Update="Tests.Bicep.10.json">
234234
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
235235
</None>
236+
<None Update="Template.Policy.WithDeployment.json">
237+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
238+
</None>
236239
</ItemGroup>
237240

238241
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
{
2+
"$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
3+
"contentVersion": "1.0.0.0",
4+
"parameters": {
5+
"policyName": {
6+
"metadata": {
7+
"description": "Policy name"
8+
},
9+
"type": "string",
10+
"defaultValue": "Policy-example"
11+
}
12+
},
13+
"resources": [
14+
{
15+
"name": "[parameters('policyName')]",
16+
"type": "Microsoft.Authorization/policyDefinitions",
17+
"apiVersion": "2021-06-01",
18+
"properties": {
19+
"policyType": "Custom",
20+
"mode": "Indexed",
21+
"displayName": "Deploy a route table with specific user defined routes",
22+
"description": "Deploys a route table with specific user defined routes when one does not exist. The route table deployed by the policy must be manually associated to subnet(s)",
23+
"metadata": {
24+
"version": "1.0.0",
25+
"category": "Network",
26+
"source": "https://github.com/Azure/Enterprise-Scale/",
27+
"alzCloudEnvironments": [
28+
"AzureCloud",
29+
"AzureChinaCloud",
30+
"AzureUSGovernment"
31+
]
32+
},
33+
"parameters": {
34+
"effect": {
35+
"type": "String",
36+
"metadata": {
37+
"displayName": "Effect",
38+
"description": "Enable or disable the execution of the policy"
39+
},
40+
"allowedValues": [
41+
"DeployIfNotExists",
42+
"Disabled"
43+
],
44+
"defaultValue": "DeployIfNotExists"
45+
},
46+
"requiredRoutes": {
47+
"type": "Array",
48+
"metadata": {
49+
"displayName": "requiredRoutes",
50+
"description": "Routes that must exist in compliant route tables deployed by this policy"
51+
}
52+
},
53+
"vnetRegion": {
54+
"type": "String",
55+
"metadata": {
56+
"displayName": "vnetRegion",
57+
"description": "Only VNets in this region will be evaluated against this policy"
58+
}
59+
},
60+
"routeTableName": {
61+
"type": "String",
62+
"metadata": {
63+
"displayName": "routeTableName",
64+
"description": "Name of the route table automatically deployed by this policy"
65+
}
66+
},
67+
"disableBgpPropagation": {
68+
"type": "Boolean",
69+
"metadata": {
70+
"displayName": "DisableBgpPropagation",
71+
"description": "Disable BGP Propagation"
72+
},
73+
"defaultValue": false
74+
}
75+
},
76+
"policyRule": {
77+
"if": {
78+
"allOf": [
79+
{
80+
"field": "type",
81+
"equals": "Microsoft.Network/virtualNetworks"
82+
},
83+
{
84+
"field": "location",
85+
"equals": "[[parameters('vnetRegion')]"
86+
}
87+
]
88+
},
89+
"then": {
90+
"effect": "[[parameters('effect')]",
91+
"details": {
92+
"type": "Microsoft.Network/routeTables",
93+
"existenceCondition": {
94+
"allOf": [
95+
{
96+
"field": "name",
97+
"equals": "[[parameters('routeTableName')]"
98+
},
99+
{
100+
"count": {
101+
"field": "Microsoft.Network/routeTables/routes[*]",
102+
"where": {
103+
"value": "[[concat(current('Microsoft.Network/routeTables/routes[*].addressPrefix'), ';', current('Microsoft.Network/routeTables/routes[*].nextHopType'), if(equals(toLower(current('Microsoft.Network/routeTables/routes[*].nextHopType')),'virtualappliance'), concat(';', current('Microsoft.Network/routeTables/routes[*].nextHopIpAddress')), ''))]",
104+
"in": "[[parameters('requiredRoutes')]"
105+
}
106+
},
107+
"equals": "[[length(parameters('requiredRoutes'))]"
108+
}
109+
]
110+
},
111+
"roleDefinitionIds": [
112+
"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/00000000-0000-0000-0000-000000000000"
113+
],
114+
"deployment": {
115+
"properties": {
116+
"mode": "incremental",
117+
"template": {
118+
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
119+
"contentVersion": "1.0.0.0",
120+
"parameters": {
121+
"routeTableName": {
122+
"type": "string"
123+
},
124+
"vnetRegion": {
125+
"type": "string"
126+
},
127+
"requiredRoutes": {
128+
"type": "array"
129+
},
130+
"disableBgpPropagation": {
131+
"type": "bool"
132+
}
133+
},
134+
"variables": {
135+
"copyLoop": [
136+
{
137+
"name": "routes",
138+
"count": "[[[length(parameters('requiredRoutes'))]",
139+
"input": {
140+
"name": "[[[concat('route-',copyIndex('routes'))]",
141+
"properties": {
142+
"addressPrefix": "[[[split(parameters('requiredRoutes')[copyIndex('routes')], ';')[0]]",
143+
"nextHopType": "[[[split(parameters('requiredRoutes')[copyIndex('routes')], ';')[1]]",
144+
"nextHopIpAddress": "[[[if(equals(toLower(split(parameters('requiredRoutes')[copyIndex('routes')], ';')[1]),'virtualappliance'),split(parameters('requiredRoutes')[copyIndex('routes')], ';')[2], null())]"
145+
}
146+
}
147+
}
148+
]
149+
},
150+
"resources": [
151+
{
152+
"type": "Microsoft.Resources/deployments",
153+
"apiVersion": "2021-04-01",
154+
"name": "routeTableDepl",
155+
"properties": {
156+
"mode": "Incremental",
157+
"template": {
158+
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
159+
"contentVersion": "1.0.0.0",
160+
"parameters": {
161+
"routeTableName": {
162+
"type": "string"
163+
},
164+
"vnetRegion": {
165+
"type": "string"
166+
},
167+
"requiredRoutes": {
168+
"type": "array"
169+
},
170+
"disableBgpPropagation": {
171+
"type": "bool"
172+
}
173+
},
174+
"resources": [
175+
{
176+
"type": "Microsoft.Network/routeTables",
177+
"apiVersion": "2021-02-01",
178+
"name": "[[[parameters('routeTableName')]",
179+
"location": "[[[parameters('vnetRegion')]",
180+
"properties": {
181+
"disableBgpRoutePropagation": "[[[parameters('disableBgpPropagation')]",
182+
"copy": "[[variables('copyLoop')]"
183+
}
184+
}
185+
]
186+
},
187+
"parameters": {
188+
"routeTableName": {
189+
"value": "[[parameters('routeTableName')]"
190+
},
191+
"vnetRegion": {
192+
"value": "[[parameters('vnetRegion')]"
193+
},
194+
"requiredRoutes": {
195+
"value": "[[parameters('requiredRoutes')]"
196+
},
197+
"disableBgpPropagation": {
198+
"value": "[[parameters('disableBgpPropagation')]"
199+
}
200+
}
201+
}
202+
}
203+
]
204+
},
205+
"parameters": {
206+
"routeTableName": {
207+
"value": "[[parameters('routeTableName')]"
208+
},
209+
"vnetRegion": {
210+
"value": "[[parameters('vnetRegion')]"
211+
},
212+
"requiredRoutes": {
213+
"value": "[[parameters('requiredRoutes')]"
214+
},
215+
"disableBgpPropagation": {
216+
"value": "[[parameters('disableBgpPropagation')]"
217+
}
218+
}
219+
}
220+
}
221+
}
222+
}
223+
}
224+
}
225+
}
226+
],
227+
"outputs": {}
228+
}

tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs

+7
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,13 @@ public void Quoting()
10571057
Assert.Equal(10, result["value"][0]["parameters"]["debugLength"].Value<int>());
10581058
}
10591059

1060+
[Fact]
1061+
public void PolicyCopyLoop()
1062+
{
1063+
var resources = ProcessTemplate(GetSourcePath("Template.Policy.WithDeployment.json"), null, out var templateContext);
1064+
Assert.Equal(2, resources.Length);
1065+
}
1066+
10601067
#region Helper methods
10611068

10621069
private static string GetSourcePath(string fileName)

0 commit comments

Comments
 (0)