Skip to content

Commit 27745e3

Browse files
Fixing nested replacement (Azure#17)
# Pull Request A number of things being worked on in here... 1. Handling nested configuration items. Be they objects or arrays of objects. 2. Some fix ups based on feedback. ## Issue Issue #, if available: Azure#13 Azure#12 Azure#6 Azure#18 ## License By submitting this pull request, I confirm that my contribution is made under the terms of the projects associated license.
1 parent da4ab0e commit 27745e3

7 files changed

+647
-134
lines changed

Diff for: actions_bootstrap.ps1

-6
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,6 @@ $null = $modulesToInstall.Add(([PSCustomObject]@{
3030
ModuleVersion = '0.12.0'
3131
}))
3232

33-
# Required dependency of the ALZ module itself.
34-
$null = $modulesToInstall.Add(([PSCustomObject]@{
35-
ModuleName = 'Az.Resources'
36-
ModuleVersion = '6.5.2'
37-
}))
38-
3933
'Installing PowerShell Modules'
4034
foreach ($module in $modulesToInstall) {
4135
$installSplat = @{

Diff for: src/ALZ/ALZ.psd1

+1-6
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,7 @@
5151
# ProcessorArchitecture = ''
5252

5353
# Modules that must be imported into the global environment prior to importing this module
54-
RequiredModules = @(
55-
@{
56-
ModuleName = 'Az.Resources'
57-
ModuleVersion = '6.5.2'
58-
}
59-
)
54+
RequiredModules = @()
6055

6156
# Assemblies that must be loaded prior to importing this module
6257
# RequiredAssemblies = @()

Diff for: src/ALZ/Assets/alz-bicep-config/v0.14.0-pre.config.json

+171-32
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,19 @@
133133
"Description": "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')",
134134
"Targets": [
135135
{
136-
"Name": "parTopLevelManagementGroupPrefix",
136+
"Name": "parTopLevelManagementGroupPrefix.value",
137137
"Destination": "Parameters"
138138
},
139139
{
140-
"Name": "parCompanyPrefix",
140+
"Name": "parCompanyPrefix.value",
141141
"Destination": "Parameters"
142142
},
143143
{
144-
"Name": "parTargetManagementGroupId",
144+
"Name": "parTargetManagementGroupId.value",
145145
"Destination": "Parameters"
146146
},
147147
{
148-
"Name": "parAssignableScopeManagementGroupId",
148+
"Name": "parAssignableScopeManagementGroupId.value",
149149
"Destination": "Parameters"
150150
},
151151
{
@@ -162,7 +162,7 @@
162162
"Description": "The suffix that will be added to all resources created by this deployment. (e.g. 'test')",
163163
"Targets": [
164164
{
165-
"Name": "parTopLevelManagementGroupSuffix",
165+
"Name": "parTopLevelManagementGroupSuffix.value",
166166
"Destination": "Parameters"
167167
}
168168
],
@@ -176,15 +176,23 @@
176176
"Value": "",
177177
"Targets": [
178178
{
179-
"Name": "parLocation",
179+
"Name": "parLocation.value",
180180
"Destination": "Parameters"
181181
},
182182
{
183-
"Name": "parAutomationAccountLocation",
183+
"Name": "parAutomationAccountLocation.value",
184184
"Destination": "Parameters"
185185
},
186186
{
187-
"Name": "parLogAnalyticsWorkspaceLocation",
187+
"Name": "parLogAnalyticsWorkspaceLocation.value",
188+
"Destination": "Parameters"
189+
},
190+
{
191+
"Name": "parPolicyAssignmentParameters.value.ascExportResourceGroupLocation.value",
192+
"Destination": "Parameters"
193+
},
194+
{
195+
"Name": "parVirtualWanHubs.value.[0].parHubLocation",
188196
"Destination": "Parameters"
189197
},
190198
{
@@ -194,32 +202,83 @@
194202
],
195203
"AllowedValues": {
196204
"Display": false,
197-
"Description": "Getting Azure deployment locations.",
198-
"Type": "PSScript",
199-
"Script": "Get-AzLocation | Where-Object {$_.RegionType -eq 'Physical'} | Sort-Object Location | Select-Object -ExpandProperty Location",
200-
"Values": []
205+
"Values": [
206+
"australiacentral",
207+
"australiacentral2",
208+
"australiaeast",
209+
"australiasoutheast",
210+
"brazilsouth",
211+
"brazilsoutheast",
212+
"canadacentral",
213+
"canadaeast",
214+
"centralindia",
215+
"centralus",
216+
"centraluseuap",
217+
"eastasia",
218+
"eastus",
219+
"eastus2",
220+
"eastus2euap",
221+
"eastusstg",
222+
"francecentral",
223+
"francesouth",
224+
"germanynorth",
225+
"germanywestcentral",
226+
"japaneast",
227+
"japanwest",
228+
"jioindiacentral",
229+
"jioindiawest",
230+
"koreacentral",
231+
"koreasouth",
232+
"northcentralus",
233+
"northeurope",
234+
"norwayeast",
235+
"norwaywest",
236+
"qatarcentral",
237+
"southafricanorth",
238+
"southafricawest",
239+
"southcentralus",
240+
"southeastasia",
241+
"southindia",
242+
"swedencentral",
243+
"switzerlandnorth",
244+
"switzerlandwest",
245+
"uaecentral",
246+
"uaenorth",
247+
"uksouth",
248+
"ukwest",
249+
"westcentralus",
250+
"westeurope",
251+
"westindia",
252+
"westus",
253+
"westus2",
254+
"westus3"
255+
]
201256
}
202257
},
203258
"Environment": {
204259
"Type": "UserInput",
205-
"Description": "The Type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')",
260+
"Description": "The Type of environment that will be created. (e.g. 'live', 'canary')",
206261
"Targets": [
207262
{
208-
"Name": "parEnvironment",
263+
"Name": "parEnvironment.value",
264+
"Destination": "Parameters"
265+
},
266+
{
267+
"Name": "parTags.value.Environment",
209268
"Destination": "Parameters"
210269
}
211270
],
212271
"Value": "",
213-
"DefaultValue": "prod",
272+
"DefaultValue": "live",
214273
"Valid": "^[a-zA-Z0-9]{2,10}$"
215274
},
216275
"IdentitySubscriptionId": {
217276
"Type": "UserInput",
218277
"Description": "The identifier of the Identity Subscription. (e.g '00000000-0000-0000-0000-000000000000')",
219278
"Valid": "^( {){0,1}[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(}){0,1}$",
220279
"Targets": [
221-
{
222-
"Name": "IdentitySubscriptionId",
280+
{
281+
"Name": "IDENTITY_SUBSCRIPTION_ID",
223282
"Destination": "Environment"
224283
}
225284
],
@@ -230,10 +289,6 @@
230289
"Description": "The identifier of the Connectivity Subscription. (e.g '00000000-0000-0000-0000-000000000000')",
231290
"Valid": "^( {){0,1}[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(}){0,1}$",
232291
"Targets": [
233-
{
234-
"Name": "ConnectivitySubscriptionId",
235-
"Destination": "Environment"
236-
},
237292
{
238293
"Name": "CONNECTIVITY_SUBSCRIPTION_ID",
239294
"Destination": "Environment"
@@ -247,30 +302,114 @@
247302
"Valid": "^( {){0,1}[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(}){0,1}$",
248303
"Targets": [
249304
{
250-
"Name": "ManagementSubscriptionId",
305+
"Name": "MANAGEMENT_SUBSCRIPTION_ID",
251306
"Destination": "Environment"
252307
}
253308
],
254309
"Value": ""
255310
},
256-
"BillingAccountId": {
257-
"Type": "UserInput",
258-
"Description": "The identifier of the Billing Account. (e.g 00000000-0000-0000-0000-000000000000)",
259-
"Valid": "^( {){0,1}[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(}){0,1}$",
260-
"Value": ""
261-
},
262-
"EnrollmentAccountId": {
311+
"SecurityContact": {
263312
"Type": "UserInput",
264-
"Description": "The identifier of the Enrollment Account. (e.g 00000000-0000-0000-0000-000000000000)",
265-
"Valid": "^( {){0,1}[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(}){0,1}$",
313+
"Description": "The email address of the contact for security issues. (e.g. [email protected])",
314+
"Valid": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
315+
"Targets": [
316+
{
317+
"Name":"parPolicyAssignmentParameters.value.emailSecurityContact.value",
318+
"Destination": "Parameters"
319+
}
320+
],
266321
"Value": ""
267322
},
268323
"LogAnalyticsResourceId": {
269324
"Type": "Computed",
270325
"Value": "/subscriptions/{%ManagementSubscriptionId%}/resourcegroups/alz-logging/providers/microsoft.operationalinsights/workspaces/alz-log-analytics",
271326
"Targets": [
272327
{
273-
"Name": "parLogAnalyticsWorkspaceResourceId",
328+
"Name": "parLogAnalyticsWorkspaceResourceId.value",
329+
"Destination": "Parameters"
330+
}
331+
]
332+
},
333+
"AllSubscriptionIds": {
334+
"Type": "Computed",
335+
"Value": [
336+
"{%ManagementSubscriptionId%}",
337+
"{%ConnectivitySubscriptionId%}",
338+
"{%IdentitySubscriptionId%}"
339+
],
340+
"Targets": [
341+
{
342+
"Name": "parSubscriptionIds.value",
343+
"Destination": "Parameters"
344+
}
345+
]
346+
},
347+
"VirtualIdToLink": {
348+
"Type": "Computed",
349+
"Value": "",
350+
"Targets": [
351+
{
352+
"Name": "parVirtualNetworkIdToLink.value",
353+
"Destination": "Parameters"
354+
}
355+
]
356+
},
357+
"VirtualWanName":{
358+
"Type": "Computed",
359+
"Value": "alz-vwan-{%Location%}",
360+
"Targets": [
361+
{
362+
"Name": "parVirtualWanName.value",
363+
"Destination": "Parameters"
364+
}
365+
]
366+
},
367+
"FirewallPoliciesName":{
368+
"Type": "Computed",
369+
"Value": "alz-azfwpolicy-{%Location%}",
370+
"Targets": [
371+
{
372+
"Name": "parAzFirewallPoliciesName.value",
373+
"Destination": "Parameters"
374+
}
375+
]
376+
},
377+
"AK8sPrivateLink": {
378+
"Type": "Computed",
379+
"Value": "privatelink.{%Location%}.azmk8s.io",
380+
"Targets": [
381+
{
382+
"Name": "parPrivateDnsZones.value.[0]",
383+
"Destination": "Parameters"
384+
}
385+
]
386+
},
387+
"BatchPrivateLink": {
388+
"Type": "Computed",
389+
"Value": "privatelink.{%Location%}.batch.azure.com",
390+
"Targets": [
391+
{
392+
"Name": "parPrivateDnsZones.value.[1]",
393+
"Destination": "Parameters"
394+
}
395+
]
396+
},
397+
"KustoPrivateLink": {
398+
"Type": "Computed",
399+
"Value": "privatelink.{%Location%}.kusto.windows.net",
400+
"Targets": [
401+
{
402+
"Name": "parPrivateDnsZones.value.[2]",
403+
"Destination": "Parameters"
404+
}
405+
]
406+
},
407+
"BackupPrivateLink": {
408+
"Type": "Computed",
409+
"Value": "privatelink.{%Location%}.backup.windowsazure.com",
410+
"Targets": [
411+
{
412+
"Name": "parPrivateDnsZones.value.[3]",
274413
"Destination": "Parameters"
275414
}
276415
]

Diff for: src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1

+50-5
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,70 @@ function Edit-ALZConfigurationFilesInPlace {
1111
[object] $configuration
1212
)
1313

14-
$locations = @("orchestration", "config", "customization")
14+
$locations = @("config")
1515
$files = @()
1616

1717
foreach ($location in $locations) {
1818
$bicepModules = Join-Path $alzEnvironmentDestination $location
19-
$files += @(Get-ChildItem -Path $bicepModules -Recurse -Filter *.parameters.json)
19+
$files += @(Get-ChildItem -Path $bicepModules -Recurse -Filter *.parameters.*.json)
2020
}
2121

2222
foreach ($file in $files) {
2323
$bicepConfiguration = Get-Content $file.FullName | ConvertFrom-Json -AsHashtable
2424
$modified = $false
25+
2526
foreach ($configKey in $configuration.PsObject.Properties) {
2627
foreach ($target in $configKey.Value.Targets) {
27-
if ($target.Destination -eq "Parameters" -and $null -ne $bicepConfiguration.parameters[$target.Name]) {
28+
29+
# Find the appropriate item which will be changed in the Bicep file.
30+
# Remove array '[' ']' characters so we can use the index value direct.
31+
$propertyNames = $target.Name -replace "\[|\]","" -split "\."
32+
$bicepConfigNode = $bicepConfiguration.parameters
33+
$index = 0
34+
35+
# Keep navigating into properties which the configuration specifies until we reach the bottom most object,
36+
# e.g. not a value type - but the object reference so the value is persisted.
37+
do {
38+
if ($bicepConfigNode -is [array]) {
39+
# If this is an array - use the property as an array index...
40+
if ($propertyNames[$index] -match "[0-9]+" -eq $false) {
41+
throw "Configuration specifies an array, but the index value '${$propertyNames[$index]}' is not a number"
42+
}
43+
44+
$bicepConfigNode = $bicepConfigNode[$propertyNames[$index]]
45+
46+
} elseif ($bicepConfigNode.ContainsKey($propertyNames[$index]) -eq $true) {
47+
# We found the item, keep indexing into the object.
48+
$bicepConfigNode = $bicepConfigNode[$propertyNames[$index]]
49+
} else {
50+
# This property doesn't exist at this level in the hierarchy,
51+
# this isn't the property we're looking for, stop looking.
52+
$bicepConfigNode = $null
53+
}
54+
55+
++$index
56+
57+
} while (($null -ne $bicepConfigNode) -and ($index -lt $propertyNames.Length - 1))
58+
59+
# If we're here, we've got the object at the bottom of the hierarchy - and we can modify values on it.
60+
if ($target.Destination -eq "Parameters" -and $null -ne $bicepConfigNode) {
61+
$leafPropertyName = $propertyNames[-1]
62+
2863
if ($configKey.Value.Type -eq "Computed") {
29-
$bicepConfiguration.parameters[$target.Name].value = Format-TokenizedConfigurationString $configKey.Value.Value $configuration
64+
# If the value type is computed we replace the value with another which already exists in the configuration hierarchy.
65+
if ($configKey.Value.Value -is [array]) {
66+
$formattedValues = @()
67+
foreach($formatString in $configKey.Value.Value) {
68+
$formattedValues += Format-TokenizedConfigurationString -tokenizedString $formatString -configuration $configuration
69+
}
70+
$bicepConfigNode[$leafPropertyName] = $formattedValues
71+
} else {
72+
$bicepConfigNode[$leafPropertyName] = Format-TokenizedConfigurationString -tokenizedString $configKey.Value.Value -configuration $configuration
73+
}
3074
} else {
31-
$bicepConfiguration.parameters[$target.Name].value = $configKey.Value.Value
75+
$bicepConfigNode[$leafPropertyName] = $configKey.Value.Value
3276
}
77+
3378
$modified = $true
3479
}
3580
}

Diff for: src/ALZ/Private/Format-TokenizedConfigurationString.ps1

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
function Format-TokenizedConfigurationString {
22
param(
3+
[AllowEmptyString()]
34
[Parameter(Mandatory = $true)]
45
[string] $tokenizedString,
56

0 commit comments

Comments
 (0)