Skip to content

Commit 1dc0a07

Browse files
lovelysandwichguy.pritchard
andauthored
Improve ALZ Module CLI (#2)
# Pull Request ## Description Adding further CLI 'questions' to allow for configuration and deployment of ALZ. Adding support for replacement by matching patterns in the existing ALZ bicep configuration. Support input validation (using Regex - I'm sorry) Adding tests (of course!) A few fit and finish changes (Note. This change doesn't yet address the issues with USEast DCs for Log Analytics Workspace needing to reside in a paired DC UsEast2. That will be handled in a subsequent change.) #3 ## License By submitting this pull request, I confirm that my contribution is made under the terms of the projects associated license. --------- Co-authored-by: guy.pritchard <guy.pritchard@noreply.com>
1 parent abc2c74 commit 1dc0a07

21 files changed

Lines changed: 847 additions & 259 deletions

actions_bootstrap.ps1

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,9 @@ $null = $modulesToInstall.Add(([PSCustomObject]@{
3333
# Required dependency of the ALZ module itself.
3434
$null = $modulesToInstall.Add(([PSCustomObject]@{
3535
ModuleName = 'Az.Resources'
36-
ModuleVersion = '5.6.0'
36+
ModuleVersion = '6.5.2'
3737
}))
3838

39-
40-
4139
'Installing PowerShell Modules'
4240
foreach ($module in $modulesToInstall) {
4341
$installSplat = @{
@@ -52,8 +50,7 @@ foreach ($module in $modulesToInstall) {
5250
Install-Module @installSplat
5351
Import-Module -Name $module.ModuleName -ErrorAction Stop
5452
' - Successfully installed {0}' -f $module.ModuleName
55-
}
56-
catch {
53+
} catch {
5754
$message = 'Failed to install {0}' -f $module.ModuleName
5855
" - $message"
5956
throw

src/ALZ/ALZ.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
RequiredModules = @(
5555
@{
5656
ModuleName = 'Az.Resources'
57-
ModuleVersion = '5.6.0'
57+
ModuleVersion = '6.5.2'
5858
}
5959
)
6060

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
{
2+
"Prefix": {
3+
"Type": "UserInput",
4+
"Description": "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')",
5+
"Targets": [
6+
{
7+
"Name": "parTopLevelManagementGroupPrefix",
8+
"Destination": "Parameters"
9+
},
10+
{
11+
"Name": "parCompanyPrefix",
12+
"Destination": "Parameters"
13+
},
14+
{
15+
"Name": "parTargetManagementGroupId",
16+
"Destination": "Parameters"
17+
},
18+
{
19+
"Name": "parAssignableScopeManagementGroupId",
20+
"Destination": "Parameters"
21+
}
22+
],
23+
"Value": "",
24+
"DefaultValue": "alz",
25+
"Valid": "^[a-zA-Z]{3,5}$"
26+
},
27+
"Suffix": {
28+
"Type": "UserInput",
29+
"Description": "The suffix that will be added to all resources created by this deployment. (e.g. 'test')",
30+
"Targets": [
31+
{
32+
"Name": "parTopLevelManagementGroupSuffix",
33+
"Destination": "Parameters"
34+
}
35+
],
36+
"Value": "",
37+
"DefaultValue": "",
38+
"Valid": "^[a-zA-Z]{0,5}$"
39+
},
40+
"Location": {
41+
"Type": "UserInput",
42+
"Description": "Deployment location.",
43+
"Value": "",
44+
"Targets": [
45+
{
46+
"Name": "parLocation",
47+
"Destination": "Parameters"
48+
},
49+
{
50+
"Name": "parAutomationAccountLocation",
51+
"Destination": "Parameters"
52+
},
53+
{
54+
"Name": "parLogAnalyticsWorkspaceLocation",
55+
"Destination": "Parameters"
56+
}
57+
],
58+
"AllowedValues": [
59+
"asia",
60+
"asiapacific",
61+
"australia",
62+
"australiacentral",
63+
"australiacentral2",
64+
"australiaeast",
65+
"australiasoutheast",
66+
"brazil",
67+
"brazilsouth",
68+
"brazilsoutheast",
69+
"canada",
70+
"canadacentral",
71+
"canadaeast",
72+
"centralindia",
73+
"centralus",
74+
"centraluseuap",
75+
"centralusstage",
76+
"eastasia",
77+
"eastasiastage",
78+
"eastus",
79+
"eastus2",
80+
"eastus2euap",
81+
"eastus2stage",
82+
"eastusstg",
83+
"europe",
84+
"france",
85+
"francecentral",
86+
"francesouth",
87+
"germany",
88+
"germanynorth",
89+
"germanywestcentral",
90+
"global",
91+
"india",
92+
"japan",
93+
"japaneast",
94+
"japanwest",
95+
"jioindiacentral",
96+
"jioindiawest",
97+
"korea",
98+
"koreacentral",
99+
"koreasouth",
100+
"northcentralus",
101+
"northcentralusstage",
102+
"northeurope",
103+
"norway",
104+
"norwayeast",
105+
"norwaywest",
106+
"qatarcentral",
107+
"singapore",
108+
"southafrica",
109+
"southafricanorth",
110+
"southafricawest",
111+
"southcentralus",
112+
"southcentralusstage",
113+
"southeastasia",
114+
"southindia",
115+
"swedencentral",
116+
"switzerland",
117+
"switzerlandnorth",
118+
"switzerlandwest",
119+
"uaecentral",
120+
"uaenorth",
121+
"uksouth",
122+
"ukwest",
123+
"unitedstates",
124+
"westcentralus",
125+
"westeurope",
126+
"westindia",
127+
"westus",
128+
"westus2",
129+
"westus2stage",
130+
"westus3",
131+
"westusstage"
132+
]
133+
},
134+
"Environment": {
135+
"Type": "UserInput",
136+
"Description": "The Type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')",
137+
"Targets": [
138+
{
139+
"Name": "parEnvironment",
140+
"Destination": "Parameters"
141+
}
142+
],
143+
"Value": "",
144+
"DefaultValue": "prod",
145+
"Valid": "^[a-zA-Z0-9]{2,10}$"
146+
},
147+
"IdentitySubscriptionId": {
148+
"Type": "UserInput",
149+
"Description": "The identifier of the Identity Subscription. (e.g '00000000-0000-0000-0000-000000000000')",
150+
"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}$",
151+
"Targets": [
152+
{
153+
"Name": "IdentitySubscriptionId",
154+
"Destination": "Environment"
155+
}
156+
],
157+
"Value": ""
158+
},
159+
"ConnectivitySubscriptionId": {
160+
"Type": "UserInput",
161+
"Description": "The identifier of the Connectivity Subscription. (e.g '00000000-0000-0000-0000-000000000000')",
162+
"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}$",
163+
"Targets": [
164+
{
165+
"Name": "ConnectivitySubscriptionId",
166+
"Destination": "Environment"
167+
}
168+
],
169+
"Value": ""
170+
},
171+
"ManagementSubscriptionId": {
172+
"Type": "UserInput",
173+
"Description": "The identifier of the Management Subscription. (e.g 00000000-0000-0000-0000-000000000000)",
174+
"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}$",
175+
"Targets": [
176+
{
177+
"Name": "ManagementSubscriptionId",
178+
"Destination": "Environment"
179+
}
180+
],
181+
"Value": ""
182+
},
183+
"BillingAccountId": {
184+
"Type": "UserInput",
185+
"Description": "The identifier of the Billing Account. (e.g 00000000-0000-0000-0000-000000000000)",
186+
"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}$",
187+
"Value": ""
188+
},
189+
"EnrollmentAccountId": {
190+
"Type": "UserInput",
191+
"Description": "The identifier of the Enrollment Account. (e.g 00000000-0000-0000-0000-000000000000)",
192+
"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}$",
193+
"Value": ""
194+
},
195+
"LogAnalyticsResourceId": {
196+
"Type": "Computed",
197+
"Value": "/subscriptions/{%ManagementSubscriptionId%}/resourcegroups/alz-logging/providers/microsoft.operationalinsights/workspaces/alz-log-analytics",
198+
"Targets": [
199+
{
200+
"Name": "parLogAnalyticsWorkspaceResourceId",
201+
"Destination": "Parameters"
202+
}
203+
]
204+
}
205+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
function Build-ALZDeploymentEnvFile {
2+
param (
3+
[Parameter(Mandatory = $true)]
4+
[PSCustomObject] $configuration,
5+
6+
[Parameter(Mandatory = $false)]
7+
[string] $destination = "."
8+
)
9+
<#
10+
.SYNOPSIS
11+
This function uses configuration to build a .env file for use in the deployment pipeline.
12+
.EXAMPLE
13+
Build-ALZDeploymentEnvFile -configuration configuration
14+
.EXAMPLE
15+
Build-ALZDeploymentEnvFile -configuration configuration -destination "."
16+
.OUTPUTS
17+
N/A
18+
#>
19+
20+
$envFile = Join-Path $destination ".env"
21+
22+
New-Item -Path $envFile -ItemType file -Force | Out-Null
23+
24+
foreach ($configurationValue in $configuration.PsObject.Properties) {
25+
foreach ($target in $configurationValue.Value.Targets) {
26+
if ($target.Destination -eq "Environment") {
27+
Add-Content -Path $envFile -Value "$($($target.Name))=`"$($configurationValue.Value.Value)`"" | Out-Null
28+
}
29+
}
30+
}
31+
}

src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ function Edit-ALZConfigurationFilesInPlace {
2323
$bicepConfiguration = Get-Content $file.FullName | ConvertFrom-Json -AsHashtable
2424
$modified = $false
2525
foreach ($configKey in $configuration.PsObject.Properties) {
26-
foreach ($name in $configKey.Value.Names) {
27-
if ($null -ne $bicepConfiguration.parameters[$name]) {
28-
$bicepConfiguration.parameters[$name].value = $configKey.Value.Value
26+
foreach ($target in $configKey.Value.Targets) {
27+
if ($target.Destination -eq "Parameters" -and $null -ne $bicepConfiguration.parameters[$target.Name]) {
28+
if ($configKey.Value.Type -eq "Computed") {
29+
$bicepConfiguration.parameters[$target.Name].value = Format-TokenizedConfigurationString $configKey.Value.Value $configuration
30+
} else {
31+
$bicepConfiguration.parameters[$target.Name].value = $configKey.Value.Value
32+
}
2933
$modified = $true
3034
}
3135
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
function Format-TokenizedConfigurationString {
2+
param(
3+
[Parameter(Mandatory = $true)]
4+
[string] $tokenizedString,
5+
6+
[Parameter(Mandatory = $true)]
7+
[object] $configuration
8+
)
9+
$values = $tokenizedString -split "\{\%|\%\}"
10+
11+
$returnValue = ""
12+
foreach ($value in $values) {
13+
$isToken = $tokenizedString -contains "{%$value%}"
14+
if ($null -ne $configuration.$value) {
15+
$returnValue += $configuration.$value.Value
16+
} elseif (($null -eq $configuration.$value) -and $isToken) {
17+
Write-InformationColored "Specified replacement token '${value}' not found in configuration." -ForegroundColor Yellow -InformationAction Continue
18+
$returnValue += "{%$value%}"
19+
} else {
20+
$returnValue += $value
21+
}
22+
}
23+
24+
return $returnValue
25+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
function Get-Configuration {
2+
param(
3+
[Parameter(Mandatory = $false)]
4+
[ValidateSet("bicep", "terraform")]
5+
[string] $alzIacProvider = "bicep",
6+
7+
[Parameter(Mandatory = $false)]
8+
[string] $alzEnvironmentDestination = ".",
9+
10+
[Parameter(Mandatory = $false)]
11+
[string] $alzBicepVersion = "v0.13.0"
12+
)
13+
<#
14+
.SYNOPSIS
15+
This function uses a template configuration to prompt for and return a user specified/modified configuration object.
16+
.EXAMPLE
17+
Get-Configuration
18+
.EXAMPLE
19+
Get-Configuration -alzIacProvider "bicep"
20+
.OUTPUTS
21+
System.Object. The resultant configuration values.
22+
#>
23+
24+
if ($alzIacProvider -eq "terraform") {
25+
throw "Terraform is not yet supported."
26+
}
27+
28+
$uxConfigurationFile = Join-Path $alzEnvironmentDestination "alz-bicep-config" "$alzBicepVersion.ux.config.json"
29+
return Get-Content -Path $uxConfigurationFile -Raw | ConvertFrom-Json
30+
}
31+

src/ALZ/Private/Initialize-ConfigurationObject.ps1

Lines changed: 0 additions & 50 deletions
This file was deleted.

0 commit comments

Comments
 (0)