From ee571c1f2bf5bcdc1bfbdcd7175bb8ef0248eb34 Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Mon, 13 Mar 2023 15:48:30 +0000 Subject: [PATCH 01/12] Stowing this to test something else --- src/ALZ.build.ps1 | 2 +- src/ALZ/ALZ.psd1 | 2 +- .../Private/Create-ALZDeploymentEnvFile.ps1 | 13 +++++++++ .../Initialize-ConfigurationObject.ps1 | 29 +++++++++++++++---- .../Private/Request-ALZEnvironmentConfig.ps1 | 4 +-- src/ALZ/Public/New-ALZEnvironment.ps1 | 1 + .../Initialize-ConfigurationObject.Tests.ps1 | 3 +- 7 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 src/ALZ/Private/Create-ALZDeploymentEnvFile.ps1 diff --git a/src/ALZ.build.ps1 b/src/ALZ.build.ps1 index 5166642d..469b83ef 100644 --- a/src/ALZ.build.ps1 +++ b/src/ALZ.build.ps1 @@ -141,7 +141,7 @@ Add-BuildTask ImportModuleManifest { Write-Build White ' Attempting to load the project module.' try { Import-Module $script:ModuleManifestFile -Force -PassThru -ErrorAction Stop - } catch { + } catch Exception { throw 'Unable to load the project module' } Write-Build Green " ...$script:ModuleName imported successfully" diff --git a/src/ALZ/ALZ.psd1 b/src/ALZ/ALZ.psd1 index d035f8be..842d56bc 100644 --- a/src/ALZ/ALZ.psd1 +++ b/src/ALZ/ALZ.psd1 @@ -54,7 +54,7 @@ RequiredModules = @( @{ ModuleName = 'Az.Resources' - ModuleVersion = '5.6.0' + ModuleVersion = '6.5.2' } ) diff --git a/src/ALZ/Private/Create-ALZDeploymentEnvFile.ps1 b/src/ALZ/Private/Create-ALZDeploymentEnvFile.ps1 new file mode 100644 index 00000000..8f61d14c --- /dev/null +++ b/src/ALZ/Private/Create-ALZDeploymentEnvFile.ps1 @@ -0,0 +1,13 @@ +function Create-ALZDeploymentEnvFile { + param ( + [Parameter(Mandatory = $true)] + [PSCustomObject] $configuration + ) + $envFile = Join-Path $configuration.alzEnvironmentDestination ".env" + $envFileContent = @" + IdentitySubscriptionId=$($configuration.IdentitySubscriptionId.value) + ManagementSubscriptionId=$($configuration.ManagementSubscriptionId.value) + ConnectivitySubscriptionId=$($configuration.ConnectivitySubscriptionId.value) + " + $envFileContent | Out-File $envFile +} \ No newline at end of file diff --git a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 index 4456c32c..09f46e1e 100644 --- a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 +++ b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 @@ -21,30 +21,47 @@ function Initialize-ConfigurationObject { } return [pscustomobject]@{ - Prefix = [pscustomobject]@{ + Prefix = [pscustomobject]@{ description = "The prefix that will be added to all resources created by this deployment." - names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix") + names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix", "parTargetManagementGroupId", "parAssignableScopeManagementGroupId") value = "alz" defaultValue = "alz" } - Suffix = [pscustomobject]@{ + Suffix = [pscustomobject]@{ Description = "The suffix that will be added to all resources created by this deployment." Names = @("parTopLevelManagementGroupSuffix") Value = "" DefaultValue = "" } - Location = [pscustomobject]@{ + Location = [pscustomobject]@{ Description = "Deployment location." Names = @("parLocation") AllowedValues = @(Get-AzLocation | Sort-Object Location | Select-Object -ExpandProperty Location ) Value = "" } - Environment = [pscustomobject]@{ - Description = "The type of environment that will be created . Example: dev, test, qa, staging, prod" + Environment = [pscustomobject]@{ + Description = "The type of environment that will be created. Example: dev, test, qa, staging, prod" Names = @("parEnvironment") DefaultValue = 'prod' Value = "" } + SecurityContact = [pscustomobject]@{ + Description = "The email address of the security contact for the subscription." + Names = @("parSecurityContact", "parMsDefenderForCloudEmailSecurityContact", "emailSecurityContact") + Value = "" + }, + IdentitySubscriptionId = [pscustomobject]@{ + Description = "The id of the identity subscription." + Value = "" + }, + ConnectivitySubscriptionId = [pscustomobject]@{ + Description = "The id of the connectivity subscription." + Value = "" + }, + ManagementSubscriptionId = [pscustomobject]@{ + Description = "The id of the management subscription." + Value = "" + }, } } diff --git a/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 b/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 index 6d07b857..49ba8557 100644 --- a/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 +++ b/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 @@ -9,9 +9,9 @@ function Request-ALZEnvironmentConfig { .SYNOPSIS This function uses a template configuration to prompt for and return a user specified/modified configuration object. .EXAMPLE - New-SlzEnvironmentConfig + Request-ALZEnvironmentConfig .EXAMPLE - New-SlzEnvironmentConfig -sourceConfigurationFile "orchestration/scripts/parameters/sovereignLandingZone.parameters.json" + Request-ALZEnvironmentConfig -alzIacProvider "bicep" .OUTPUTS System.Object. The resultant configuration values. #> diff --git a/src/ALZ/Public/New-ALZEnvironment.ps1 b/src/ALZ/Public/New-ALZEnvironment.ps1 index a17a0c21..54ce28e2 100644 --- a/src/ALZ/Public/New-ALZEnvironment.ps1 +++ b/src/ALZ/Public/New-ALZEnvironment.ps1 @@ -63,6 +63,7 @@ function New-ALZEnvironment { } Edit-ALZConfigurationFilesInPlace -alzEnvironmentDestination $alzEnvironmentDestination -configuration $configuration | Out-Null + #Create-ALZDeploymentEnvFile -configuration $configuration } return $true diff --git a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 index d9cb1dbb..5a277f4b 100644 --- a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 +++ b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 @@ -48,10 +48,9 @@ InModuleScope 'ALZ' { $content.Location.AllowedValues | Should -Be @('eastus', 'ukwest') $content.Environment.Value | Should -Be '' - $content.Environment.Description | Should -Be 'The type of environment that will be created . Example: dev, test, qa, staging, prod' + $content.Environment.Description | Should -Be 'The type of environment that will be created. Example: dev, test, qa, staging, prod' $content.Environment.Names | Should -Be @('parEnvironment') $content.Environment.DefaultValue | Should -Be 'prod' - } } } From f2bbe2e61299a3b96ba9164543becb3bbf481f5d Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Mon, 13 Mar 2023 16:42:01 +0000 Subject: [PATCH 02/12] Now with working --- src/ALZ.build.ps1 | 2 +- .../Private/Build-ALZDeploymentEnvFile.ps1 | 17 ++++++++++++++ .../Private/Create-ALZDeploymentEnvFile.ps1 | 13 ----------- .../Initialize-ConfigurationObject.ps1 | 22 +++++++++---------- src/ALZ/Public/New-ALZEnvironment.ps1 | 2 +- .../Initialize-ConfigurationObject.Tests.ps1 | 2 +- .../Unit/Public/New-ALZEnvironment.Tests.ps1 | 1 + 7 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 delete mode 100644 src/ALZ/Private/Create-ALZDeploymentEnvFile.ps1 diff --git a/src/ALZ.build.ps1 b/src/ALZ.build.ps1 index 469b83ef..5166642d 100644 --- a/src/ALZ.build.ps1 +++ b/src/ALZ.build.ps1 @@ -141,7 +141,7 @@ Add-BuildTask ImportModuleManifest { Write-Build White ' Attempting to load the project module.' try { Import-Module $script:ModuleManifestFile -Force -PassThru -ErrorAction Stop - } catch Exception { + } catch { throw 'Unable to load the project module' } Write-Build Green " ...$script:ModuleName imported successfully" diff --git a/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 b/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 new file mode 100644 index 00000000..9a065f27 --- /dev/null +++ b/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 @@ -0,0 +1,17 @@ +function Build-ALZDeploymentEnvFile { + param ( + [Parameter(Mandatory = $true)] + [PSCustomObject] $configuration, + + [Parameter(Mandatory = $false)] + [string] $destination = "." + ) + + $envFile = Join-Path $destination ".env" + + New-Item -Path $envFile -ItemType file -Force | Out-Null + + Add-Content -Path $envFile -Value "IdentitySubscriptionId=`"$($configuration.IdentitySubscriptionId.value)`"" + Add-Content -Path $envFile -Value "ManagementSubscriptionId=`"$($configuration.ManagementSubscriptionId.value)`"" + Add-Content -Path $envFile -Value "ConnectivitySubscriptionId=`"$($configuration.ConnectivitySubscriptionId.value)`"" +} \ No newline at end of file diff --git a/src/ALZ/Private/Create-ALZDeploymentEnvFile.ps1 b/src/ALZ/Private/Create-ALZDeploymentEnvFile.ps1 deleted file mode 100644 index 8f61d14c..00000000 --- a/src/ALZ/Private/Create-ALZDeploymentEnvFile.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -function Create-ALZDeploymentEnvFile { - param ( - [Parameter(Mandatory = $true)] - [PSCustomObject] $configuration - ) - $envFile = Join-Path $configuration.alzEnvironmentDestination ".env" - $envFileContent = @" - IdentitySubscriptionId=$($configuration.IdentitySubscriptionId.value) - ManagementSubscriptionId=$($configuration.ManagementSubscriptionId.value) - ConnectivitySubscriptionId=$($configuration.ConnectivitySubscriptionId.value) - " - $envFileContent | Out-File $envFile -} \ No newline at end of file diff --git a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 index 09f46e1e..6c50e8fd 100644 --- a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 +++ b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 @@ -21,47 +21,47 @@ function Initialize-ConfigurationObject { } return [pscustomobject]@{ - Prefix = [pscustomobject]@{ + Prefix = [pscustomobject]@{ description = "The prefix that will be added to all resources created by this deployment." names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix", "parTargetManagementGroupId", "parAssignableScopeManagementGroupId") value = "alz" defaultValue = "alz" } - Suffix = [pscustomobject]@{ + Suffix = [pscustomobject]@{ Description = "The suffix that will be added to all resources created by this deployment." Names = @("parTopLevelManagementGroupSuffix") Value = "" DefaultValue = "" } - Location = [pscustomobject]@{ + Location = [pscustomobject]@{ Description = "Deployment location." Names = @("parLocation") AllowedValues = @(Get-AzLocation | Sort-Object Location | Select-Object -ExpandProperty Location ) Value = "" } - Environment = [pscustomobject]@{ + Environment = [pscustomobject]@{ Description = "The type of environment that will be created. Example: dev, test, qa, staging, prod" Names = @("parEnvironment") DefaultValue = 'prod' Value = "" } - SecurityContact = [pscustomobject]@{ + SecurityContact = [pscustomobject]@{ Description = "The email address of the security contact for the subscription." Names = @("parSecurityContact", "parMsDefenderForCloudEmailSecurityContact", "emailSecurityContact") Value = "" - }, - IdentitySubscriptionId = [pscustomobject]@{ + } + IdentitySubscriptionId = [pscustomobject]@{ Description = "The id of the identity subscription." Value = "" - }, + } ConnectivitySubscriptionId = [pscustomobject]@{ Description = "The id of the connectivity subscription." Value = "" - }, - ManagementSubscriptionId = [pscustomobject]@{ + } + ManagementSubscriptionId = [pscustomobject]@{ Description = "The id of the management subscription." Value = "" - }, + } } } diff --git a/src/ALZ/Public/New-ALZEnvironment.ps1 b/src/ALZ/Public/New-ALZEnvironment.ps1 index 54ce28e2..ab94bfac 100644 --- a/src/ALZ/Public/New-ALZEnvironment.ps1 +++ b/src/ALZ/Public/New-ALZEnvironment.ps1 @@ -63,7 +63,7 @@ function New-ALZEnvironment { } Edit-ALZConfigurationFilesInPlace -alzEnvironmentDestination $alzEnvironmentDestination -configuration $configuration | Out-Null - #Create-ALZDeploymentEnvFile -configuration $configuration + Build-ALZDeploymentEnvFile -configuration $configuration -Destination $alzEnvironmentDestination | Out-Null } return $true diff --git a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 index 5a277f4b..31ca6909 100644 --- a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 +++ b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 @@ -35,7 +35,7 @@ InModuleScope 'ALZ' { $content.Prefix.Value | Should -Be 'alz' $content.Prefix.DefaultValue | Should -Be 'alz' $content.Prefix.Description | Should -Be 'The prefix that will be added to all resources created by this deployment.' - $content.Prefix.Names | Should -Be @('parTopLevelManagementGroupPrefix', 'parCompanyPrefix') + $content.Prefix.Names | Should -Be @('parTopLevelManagementGroupPrefix', 'parCompanyPrefix', 'parTargetManagementGroupId', 'parAssignableScopeManagementGroupId') $content.Suffix.Value | Should -Be '' $content.Suffix.DefaultValue | Should -Be '' diff --git a/src/Tests/Unit/Public/New-ALZEnvironment.Tests.ps1 b/src/Tests/Unit/Public/New-ALZEnvironment.Tests.ps1 index f124fc7c..4ec68711 100644 --- a/src/Tests/Unit/Public/New-ALZEnvironment.Tests.ps1 +++ b/src/Tests/Unit/Public/New-ALZEnvironment.Tests.ps1 @@ -39,6 +39,7 @@ InModuleScope 'ALZ' { } Mock -CommandName Edit-ALZConfigurationFilesInPlace + Mock -CommandName Build-ALZDeploymentEnvFile Mock -CommandName Get-ALZBicepSource -MockWith { "C:\temp\source" From 0dbc375b251ce41d893a8f1084c976e6e6379a32 Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Tue, 14 Mar 2023 14:41:44 +0000 Subject: [PATCH 03/12] String replacement done --- .../Private/Build-ALZDeploymentEnvFile.ps1 | 18 +++++-- .../Edit-ALZConfigurationFilesInPlace.ps1 | 10 +++- .../Initialize-ConfigurationObject.ps1 | 48 +++++++++++-------- .../Private/Request-ConfigurationValue.ps1 | 30 ++++++++---- .../Initialize-ConfigurationObject.Tests.ps1 | 8 ++-- 5 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 b/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 index 9a065f27..f4dd24ff 100644 --- a/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 +++ b/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 @@ -6,12 +6,24 @@ function Build-ALZDeploymentEnvFile { [Parameter(Mandatory = $false)] [string] $destination = "." ) + <# + .SYNOPSIS + This function uses configuration to build a .env file for use in the deployment pipeline. + .EXAMPLE + Build-ALZDeploymentEnvFile -configuration configuration + .EXAMPLE + Build-ALZDeploymentEnvFile -configuration configuration -destination "." + .OUTPUTS + N/A + #> $envFile = Join-Path $destination ".env" New-Item -Path $envFile -ItemType file -Force | Out-Null - Add-Content -Path $envFile -Value "IdentitySubscriptionId=`"$($configuration.IdentitySubscriptionId.value)`"" - Add-Content -Path $envFile -Value "ManagementSubscriptionId=`"$($configuration.ManagementSubscriptionId.value)`"" - Add-Content -Path $envFile -Value "ConnectivitySubscriptionId=`"$($configuration.ConnectivitySubscriptionId.value)`"" + foreach ($configurationValue in $configuration.PsObject.Properties) { + if ($configurationValue.Value.Type -eq "Environment") { + Add-Content -Path $envFile -Value "$($($configurationValue.Name))=`"$($configurationValue.Value.Value)`"" + } + } } \ No newline at end of file diff --git a/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 b/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 index 38113f5b..422e84bd 100644 --- a/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 +++ b/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 @@ -25,7 +25,15 @@ function Edit-ALZConfigurationFilesInPlace { foreach ($configKey in $configuration.PsObject.Properties) { foreach ($name in $configKey.Value.Names) { if ($null -ne $bicepConfiguration.parameters[$name]) { - $bicepConfiguration.parameters[$name].value = $configKey.Value.Value + + if ($null -ne $configKey.Value.Replace) { + $bicepConfiguration.parameters[$name].value = ` + $bicepConfiguration.parameters[$name].value -replace [regex]::escape($configKey.Value.Replace), $configKey.Value.Value + } else { + $bicepConfiguration.parameters[$name].value = $configKey.Value.Value + } + + $modified = $true } } diff --git a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 index 6c50e8fd..4dd67080 100644 --- a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 +++ b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 @@ -22,45 +22,55 @@ function Initialize-ConfigurationObject { return [pscustomobject]@{ Prefix = [pscustomobject]@{ - description = "The prefix that will be added to all resources created by this deployment." - names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix", "parTargetManagementGroupId", "parAssignableScopeManagementGroupId") - value = "alz" - defaultValue = "alz" + Type = "Configuration" + Description = "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')" + Names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix", "parTargetManagementGroupId", "parAssignableScopeManagementGroupId") + Value = "alz" + DefaultValue = "alz" + Valid = "^[a-zA-Z]{3,5}$" } Suffix = [pscustomobject]@{ - Description = "The suffix that will be added to all resources created by this deployment." + Type = "Configuration" + Description = "The suffix that will be added to all resources created by this deployment. (e.g. 'test')" Names = @("parTopLevelManagementGroupSuffix") Value = "" DefaultValue = "" + Valid = "^[a-zA-Z]{0,5}$" } Location = [pscustomobject]@{ + Type = "Configuration" Description = "Deployment location." - Names = @("parLocation") - AllowedValues = @(Get-AzLocation | Sort-Object Location | Select-Object -ExpandProperty Location ) + Names = @("parLocation", "parAutomationAccountLocation", "parLogAnalyticsWorkspaceLocation") + AllowedValues = @(Get-AzLocation | Sort-Object Location | Select-Object -ExpandProperty Location) Value = "" } Environment = [pscustomobject]@{ - Description = "The type of environment that will be created. Example: dev, test, qa, staging, prod" + Type = "Configuration" + Description = "The type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')" Names = @("parEnvironment") DefaultValue = 'prod' Value = "" - } - SecurityContact = [pscustomobject]@{ - Description = "The email address of the security contact for the subscription." - Names = @("parSecurityContact", "parMsDefenderForCloudEmailSecurityContact", "emailSecurityContact") - Value = "" + Valid = "^[a-zA-Z0-9]{2,10}$" } IdentitySubscriptionId = [pscustomobject]@{ - Description = "The id of the identity subscription." - Value = "" + ForEnvironment = $true + Description = "The identifier of the Identity subscription. (e.g '00000000-0000-0000-0000-000000000000')" + 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}$" + Value = "" } ConnectivitySubscriptionId = [pscustomobject]@{ - Description = "The id of the connectivity subscription." - Value = "" + ForEnvironment = $true + Description = "The identifier of the Connectivity subscription. (e.g '00000000-0000-0000-0000-000000000000')" + 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}$" + Value = "" } ManagementSubscriptionId = [pscustomobject]@{ - Description = "The id of the management subscription." - Value = "" + ForEnvironment = $true + Description = "The identifier of the Management subscription. (e.g 00000000-0000-0000-0000-000000000000)" + 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}$" + Names = @("parLogAnalyticsWorkspaceResourceId") + Replace = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + Value = "" } } } diff --git a/src/ALZ/Private/Request-ConfigurationValue.ps1 b/src/ALZ/Private/Request-ConfigurationValue.ps1 index f71512bf..f7a1eba1 100644 --- a/src/ALZ/Private/Request-ConfigurationValue.ps1 +++ b/src/ALZ/Private/Request-ConfigurationValue.ps1 @@ -7,17 +7,23 @@ function Request-ConfigurationValue { [object] $configValue ) - $allowedValues = $configValue.allowedValues - $hasAllowedValues = $null -ne $configValue.allowedValues + $allowedValues = $configValue.AllowedValues + $hasAllowedValues = $null -ne $configValue.AllowedValues - $defaultValue = $configValue.defaultValue - $hasDefaultValue = $null -ne $configValue.defaultValue + $defaultValue = $configValue.DefaultValue + $hasDefaultValue = $null -ne $configValue.DefaultValue - Write-InformationColored $configValue.description -ForegroundColor White -InformationAction Continue + $hasValidator = $null -ne $configValue.Valid + + Write-InformationColored $configValue.Description -ForegroundColor White -InformationAction Continue if ($hasAllowedValues) { Write-InformationColored "[allowed: $allowedValues] " -ForegroundColor Yellow -InformationAction Continue } + $hasInvalidText = $true + $isDisallowedValue = $true + $isNotValid = $true + do { Write-InformationColored "$($configName) " -ForegroundColor Yellow -NoNewline -InformationAction Continue if ($hasDefaultValue) { @@ -30,12 +36,20 @@ function Request-ConfigurationValue { $readValue = Read-Host if ($hasDefaultValue -and $readValue -eq "") { - $configValue.value = $configValue.defaultValue + $configValue.Value = $configValue.defaultValue } else { - $configValue.value = $readValue + $configValue.Value = $readValue + } + + $hasInvalidText = ($null -eq $configValue.Value -or "" -eq $configValue.Value) -and ($configValue.Value -ne $configValue.DefaultValue) + $isDisallowedValue = $hasAllowedValues -and $allowedValues.Contains($configValue.Value) -eq $false + $isNotValid = $hasValidator -and $configValue.Value -match $configValue.Valid -eq $false + + if ($hasInvalidText -or $isDisallowedValue -or $isNotValid) { + Write-InformationColored "Please specify a valid value for this field." -ForegroundColor Red -InformationAction Continue } } - while ((($null -eq $configValue.value -or "" -eq $configValue.value) -and ($configValue.value -ne $configValue.defaultValue)) -or ($hasAllowedValues -and $allowedValues.Contains($configValue.value) -eq $false)) + while ($hasInvalidText -or $isDisallowedValue -or $isNotValid) Write-InformationColored "" -InformationAction Continue } diff --git a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 index 31ca6909..f14572df 100644 --- a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 +++ b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 @@ -34,21 +34,21 @@ InModuleScope 'ALZ' { $content = Initialize-ConfigurationObject $content.Prefix.Value | Should -Be 'alz' $content.Prefix.DefaultValue | Should -Be 'alz' - $content.Prefix.Description | Should -Be 'The prefix that will be added to all resources created by this deployment.' + $content.Prefix.Description | Should -Be "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')" $content.Prefix.Names | Should -Be @('parTopLevelManagementGroupPrefix', 'parCompanyPrefix', 'parTargetManagementGroupId', 'parAssignableScopeManagementGroupId') $content.Suffix.Value | Should -Be '' $content.Suffix.DefaultValue | Should -Be '' - $content.Suffix.Description | Should -Be 'The suffix that will be added to all resources created by this deployment.' + $content.Suffix.Description | Should -Be "The suffix that will be added to all resources created by this deployment. (e.g. 'test')" $content.Suffix.Names | Should -Be @('parTopLevelManagementGroupSuffix') $content.Location.Value | Should -Be '' $content.Location.Description | Should -Be 'Deployment location.' - $content.Location.Names | Should -Be @('parLocation') + $content.Location.Names | Should -Be @("parLocation", "parAutomationAccountLocation", "parLogAnalyticsWorkspaceLocation") $content.Location.AllowedValues | Should -Be @('eastus', 'ukwest') $content.Environment.Value | Should -Be '' - $content.Environment.Description | Should -Be 'The type of environment that will be created. Example: dev, test, qa, staging, prod' + $content.Environment.Description | Should -Be "The type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')" $content.Environment.Names | Should -Be @('parEnvironment') $content.Environment.DefaultValue | Should -Be 'prod' } From 7d7d640ca144ca7f47c1ffab7516aaffef3ce7c5 Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Tue, 14 Mar 2023 18:55:40 +0000 Subject: [PATCH 04/12] Adding tests --- .../Edit-ALZConfigurationFilesInPlace.ps1 | 4 +- .../Private/Request-ConfigurationValue.ps1 | 21 ++-- .../Build-ALZDeploymentEnvFile.Tests.ps1 | 21 ++++ .../Create-ALZEnvironmentConfig.Tests.ps1 | 114 ------------------ .../Initialize-ConfigurationObject.Tests.ps1 | 4 + .../Request-ConfigurationValue.Tests.ps1 | 60 ++++++++- 6 files changed, 96 insertions(+), 128 deletions(-) create mode 100644 src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 delete mode 100644 src/Tests/Unit/Private/Create-ALZEnvironmentConfig.Tests.ps1 diff --git a/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 b/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 index 422e84bd..ffbbbaf1 100644 --- a/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 +++ b/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 @@ -26,9 +26,11 @@ function Edit-ALZConfigurationFilesInPlace { foreach ($name in $configKey.Value.Names) { if ($null -ne $bicepConfiguration.parameters[$name]) { + # If we've specified a string to replace - and the value contains that string, then replace it. + # Otherwise overwrite the value completely. if ($null -ne $configKey.Value.Replace) { $bicepConfiguration.parameters[$name].value = ` - $bicepConfiguration.parameters[$name].value -replace [regex]::escape($configKey.Value.Replace), $configKey.Value.Value + $bicepConfiguration.parameters[$name].value -replace $configKey.Value.Replace, $configKey.Value.Value } else { $bicepConfiguration.parameters[$name].value = $configKey.Value.Value } diff --git a/src/ALZ/Private/Request-ConfigurationValue.ps1 b/src/ALZ/Private/Request-ConfigurationValue.ps1 index f7a1eba1..5ab6cd6d 100644 --- a/src/ALZ/Private/Request-ConfigurationValue.ps1 +++ b/src/ALZ/Private/Request-ConfigurationValue.ps1 @@ -4,7 +4,10 @@ function Request-ConfigurationValue { [string] $configName, [Parameter(Mandatory = $true)] - [object] $configValue + [object] $configValue, + + [Parameter(Mandatory = $false)] + [System.Boolean] $withRetries = $true ) $allowedValues = $configValue.AllowedValues @@ -20,10 +23,6 @@ function Request-ConfigurationValue { Write-InformationColored "[allowed: $allowedValues] " -ForegroundColor Yellow -InformationAction Continue } - $hasInvalidText = $true - $isDisallowedValue = $true - $isNotValid = $true - do { Write-InformationColored "$($configName) " -ForegroundColor Yellow -NoNewline -InformationAction Continue if ($hasDefaultValue) { @@ -35,21 +34,27 @@ function Request-ConfigurationValue { $readValue = Read-Host + $previousValue = $configValue.Value + if ($hasDefaultValue -and $readValue -eq "") { $configValue.Value = $configValue.defaultValue } else { $configValue.Value = $readValue } - $hasInvalidText = ($null -eq $configValue.Value -or "" -eq $configValue.Value) -and ($configValue.Value -ne $configValue.DefaultValue) + $hasNotSpecifiedValue = ($null -eq $configValue.Value -or "" -eq $configValue.Value) -and ($configValue.Value -ne $configValue.DefaultValue) $isDisallowedValue = $hasAllowedValues -and $allowedValues.Contains($configValue.Value) -eq $false $isNotValid = $hasValidator -and $configValue.Value -match $configValue.Valid -eq $false - if ($hasInvalidText -or $isDisallowedValue -or $isNotValid) { + if ($hasNotSpecifiedValue -or $isDisallowedValue -or $isNotValid) { Write-InformationColored "Please specify a valid value for this field." -ForegroundColor Red -InformationAction Continue + $configValue.Value = $previousValue + $validationError = $true } + + $shouldRetry = $validationError -and $withRetries } - while ($hasInvalidText -or $isDisallowedValue -or $isNotValid) + while (($hasNotSpecifiedValue -or $isDisallowedValue -or $isNotValid) -and $shouldRetry) Write-InformationColored "" -InformationAction Continue } diff --git a/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 b/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 new file mode 100644 index 00000000..06dccc70 --- /dev/null +++ b/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 @@ -0,0 +1,21 @@ +#------------------------------------------------------------------------- +Set-Location -Path $PSScriptRoot +#------------------------------------------------------------------------- +$ModuleName = 'ALZ' +$PathToManifest = [System.IO.Path]::Combine('..', '..', '..', $ModuleName, "$ModuleName.psd1") +#------------------------------------------------------------------------- +if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') { + #if the module is already in memory, remove it + Remove-Module -Name $ModuleName -Force +} +Import-Module $PathToManifest -Force +#------------------------------------------------------------------------- + +InModuleScope 'ALZ' { + Describe 'Build-AZLDeploymentEnvFile Public Function Tests' -Tag Unit { + BeforeAll { + $WarningPreference = 'SilentlyContinue' + $ErrorActionPreference = 'SilentlyContinue' + } + } +} \ No newline at end of file diff --git a/src/Tests/Unit/Private/Create-ALZEnvironmentConfig.Tests.ps1 b/src/Tests/Unit/Private/Create-ALZEnvironmentConfig.Tests.ps1 deleted file mode 100644 index dc326d35..00000000 --- a/src/Tests/Unit/Private/Create-ALZEnvironmentConfig.Tests.ps1 +++ /dev/null @@ -1,114 +0,0 @@ -#------------------------------------------------------------------------- -Set-Location -Path $PSScriptRoot -#------------------------------------------------------------------------- -$ModuleName = 'ALZ' -$PathToManifest = [System.IO.Path]::Combine('..', '..', '..', $ModuleName, "$ModuleName.psd1") -#------------------------------------------------------------------------- -if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') { - #if the module is already in memory, remove it - Remove-Module -Name $ModuleName -Force -} -Import-Module $PathToManifest -Force -#------------------------------------------------------------------------- - -InModuleScope 'ALZ' { - Describe 'Request-ConfigurationValue Public Function Tests' -Tag Unit { - BeforeAll { - $WarningPreference = 'SilentlyContinue' - $ErrorActionPreference = 'SilentlyContinue' - } - Context 'Non Az Module' { - BeforeEach { - Mock -CommandName Get-Module -MockWith { - $null - } - } - It 'should return the not met for non AZ module' { - Test-ALZRequirement | Should -BeExactly "ALZ requirements are not met." - } - } - Context 'Incompatible Powershell version lower them 7' { - BeforeEach { - Mock -CommandName Get-PSVersion -MockWith { - [PSCustomObject]@{ - PSVersion = [PSCustomObject]@{ - Major = 6 - Minor = 2 - } - } - } - } - It 'should return the not met for non compatible pwsh versions' { - Test-ALZRequirement | Should -BeExactly "ALZ requirements are not met." - } - } - Context 'Incompatible Powershell version 7.0' { - BeforeEach { - Mock -CommandName Get-PSVersion -MockWith { - [PSCustomObject]@{ - PSVersion = [PSCustomObject]@{ - Major = 7 - Minor = 0 - } - } - } - } - It 'should return the not met for non compatible pwsh versions' { - Test-ALZRequirement | Should -BeExactly "ALZ requirements are not met." - } - } - Context 'Git not installed' { - BeforeEach { - Mock -CommandName Get-Command -ParameterFilter { $Name -eq 'git' } -MockWith { - $null - } - } - It 'should return the not met for no git instalation' { - Test-ALZRequirement | Should -BeExactly "ALZ requirements are not met." - } - } - Context 'Bicep not installed' { - BeforeEach { - Mock -CommandName Get-Command -ParameterFilter { $Name -eq 'bicep' } -MockWith { - $null - } - } - It 'should return the not met for no bicep instalation' { - Test-ALZRequirement | Should -BeExactly "ALZ requirements are not met." - } - } - Context 'Success' { - - BeforeEach { - Mock -CommandName Get-Module -MockWith { - [PSCustomObject]@{ - Name = 'Az' - } - } - Mock -CommandName Get-PSVersion -MockWith { - [PSCustomObject]@{ - PSVersion = [PSCustomObject]@{ - Major = 7 - Minor = 1 - } - } - } - Mock -CommandName Get-Command -ParameterFilter { $Name -eq 'git' } -MockWith { - [PSCustomObject]@{ - Name = 'git' - } - } - Mock -CommandName Get-Command -ParameterFilter { $Name -eq 'bicep' } -MockWith { - [PSCustomObject]@{ - Name = 'bicep' - } - } - } - - It 'should return the expected results' { - Test-ALZRequirement | Should -BeExactly "ALZ requirements are met." - } - - } - } -} diff --git a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 index f14572df..fb60a6ee 100644 --- a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 +++ b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 @@ -52,6 +52,10 @@ InModuleScope 'ALZ' { $content.Environment.Names | Should -Be @('parEnvironment') $content.Environment.DefaultValue | Should -Be 'prod' } + + It 'Throws for unsupported Terraform IAC' { + { Initialize-ConfigurationObject -alzIacProvider "terraform" } | Should -Throw -ExpectedMessage "Terraform is not yet supported." + } } } } diff --git a/src/Tests/Unit/Private/Request-ConfigurationValue.Tests.ps1 b/src/Tests/Unit/Private/Request-ConfigurationValue.Tests.ps1 index b3b0b611..1fe1f5da 100644 --- a/src/Tests/Unit/Private/Request-ConfigurationValue.Tests.ps1 +++ b/src/Tests/Unit/Private/Request-ConfigurationValue.Tests.ps1 @@ -29,17 +29,67 @@ InModuleScope 'ALZ' { } It 'Prompt the user for configuration with a default value.' { $configValue = @{ - description = "The prefix that will be added to all resources created by this deployment." - names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix") - value = "alz" - defaultValue = "alz" + Description = "The prefix that will be added to all resources created by this deployment." + Names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix") + Value = "" + DefaultValue = "alz" } Request-ConfigurationValue -configName "prefix" -configValue $configValue Assert-MockCalled -CommandName Write-InformationColored -Times 3 - $configValue.value | Should -BeExactly "user input value" + $configValue.Value | Should -BeExactly "user input value" + } + + It 'Prompt the user with warning text if no value is specified and no default value is present.' { + Mock -CommandName Read-Host -MockWith { + "" + } + + $configValue = @{ + Description = "The prefix that will be added to all resources created by this deployment." + Names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix") + Value = "" + } + + Request-ConfigurationValue -configName "prefix" -configValue $configValue -withRetries $false + + Should -Invoke -CommandName Write-InformationColored -ParameterFilter { $ForegroundColor -eq "Red" } -Scope It + + $configValue.Value | Should -BeExactly "" + } + + It 'Prompt the user with warning text when an invalid value is specified and leave the existing value unchanged.' { + $configValue = @{ + Description = "The prefix that will be added to all resources created by this deployment." + Names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix") + Value = "" + DefaultValue = "alz" + Valid = "^[a-zA-Z]{3,5}$" + } + + Request-ConfigurationValue -configName "prefix" -configValue $configValue -withRetries $false + + Should -Invoke -CommandName Write-InformationColored -ParameterFilter { $ForegroundColor -eq "Red" } -Scope It + $configValue.Value | Should -BeExactly "" + } + + It 'Prompt the user with warning text when a value is specified which isnt in the allowed list and leave the existing value unchanged.' { + Mock -CommandName Read-Host -MockWith { + "notinthelist" + } + + $configValue = @{ + Description = "The prefix that will be added to all resources created by this deployment." + Names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix") + Value = "" + AllowedValues = @("alz", "slz") + } + Request-ConfigurationValue -configName "prefix" -configValue $configValue -withRetries $false + + Should -Invoke -CommandName Write-InformationColored -ParameterFilter { $ForegroundColor -eq "Red" } -Scope It + $configValue.Value | Should -BeExactly "" } } } From 1a0952f36b7fef2b6d09bd34a8bdf170093f0d7d Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Wed, 15 Mar 2023 11:32:39 +0000 Subject: [PATCH 05/12] Adding more tests --- .../Private/Build-ALZDeploymentEnvFile.ps1 | 4 +- .../Build-ALZDeploymentEnvFile.Tests.ps1 | 47 +++++++++++++++++- .../Request-ALZEnvironmentConfig.Tests.ps1 | 48 +++++++++++++++++++ .../Request-ConfigurationValue.Tests.ps1 | 19 ++++++++ .../Unit/Public/New-ALZEnvironment.Tests.ps1 | 8 ++++ .../Unit/Public/Test-ALZRequirement.Tests.ps1 | 1 - 6 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 diff --git a/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 b/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 index f4dd24ff..56f3fd18 100644 --- a/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 +++ b/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 @@ -22,8 +22,8 @@ function Build-ALZDeploymentEnvFile { New-Item -Path $envFile -ItemType file -Force | Out-Null foreach ($configurationValue in $configuration.PsObject.Properties) { - if ($configurationValue.Value.Type -eq "Environment") { - Add-Content -Path $envFile -Value "$($($configurationValue.Name))=`"$($configurationValue.Value.Value)`"" + if ($configurationValue.Value.ForEnvironment -eq $true) { + Add-Content -Path $envFile -Value "$($($configurationValue.Name))=`"$($configurationValue.Value.Value)`"" | Out-Null } } } \ No newline at end of file diff --git a/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 b/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 index 06dccc70..8885ec67 100644 --- a/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 +++ b/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 @@ -12,10 +12,55 @@ Import-Module $PathToManifest -Force #------------------------------------------------------------------------- InModuleScope 'ALZ' { - Describe 'Build-AZLDeploymentEnvFile Public Function Tests' -Tag Unit { + Describe 'Build-AZLDeploymentEnvFile Private Function Tests' -Tag Unit { BeforeAll { $WarningPreference = 'SilentlyContinue' $ErrorActionPreference = 'SilentlyContinue' } + Context 'Build-AZLDeploymentEnvFile should create a .env file correctly' { + It 'Creates a config file based on configuration.' { + + Mock -CommandName New-Item + Mock -CommandName Add-Content + + $configuration = [pscustomobject]@{ + Setting1 = [pscustomobject]@{ + ForEnvironment = $true + Value = "Test1" + } + Setting2 = [pscustomobject]@{ + ForEnvironment = $true + Value = "Test2" + } + } + + Build-ALZDeploymentEnvFile -configuration $configuration -destination "test" + + Should -Invoke New-Item -ParameterFilter { $Path -match ".env$" } -Scope It -Times 1 -Exactly + Should -Invoke Add-Content -ParameterFilter { $Value -match "^Setting1=`"Test1`"$" } -Scope It -Times 1 -Exactly + Should -Invoke Add-Content -ParameterFilter { $Value -match "^Setting2=`"Test2`"$" } -Scope It -Times 1 -Exactly + } + It 'Omits configuration not intended for the .env file.' { + + Mock -CommandName New-Item + Mock -CommandName Add-Content + + $configuration = [pscustomobject]@{ + Setting1 = [pscustomobject]@{ + ForEnvironment = $true + Value = "Test1" + } + Setting2 = [pscustomobject]@{ + ForEnvironment = $false + Value = "Test2" + } + } + + Build-ALZDeploymentEnvFile -configuration $configuration -destination "test" + + Should -Invoke New-Item -ParameterFilter { $Path -match ".env$" } -Scope It -Times 1 -Exactly + Should -Invoke Add-Content -Scope It -Times 1 -Exactly + } + } } } \ No newline at end of file diff --git a/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 b/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 new file mode 100644 index 00000000..5fc63b57 --- /dev/null +++ b/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 @@ -0,0 +1,48 @@ +#------------------------------------------------------------------------- +Set-Location -Path $PSScriptRoot +#------------------------------------------------------------------------- +$ModuleName = 'ALZ' +$PathToManifest = [System.IO.Path]::Combine('..', '..', '..', $ModuleName, "$ModuleName.psd1") +#------------------------------------------------------------------------- +if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') { + #if the module is already in memory, remove it + Remove-Module -Name $ModuleName -Force +} +Import-Module $PathToManifest -Force +#------------------------------------------------------------------------- + +InModuleScope 'ALZ' { + Describe 'Request-ALZEnvironmentConfig Private Function Tests' -Tag Unit { + BeforeAll { + $WarningPreference = 'SilentlyContinue' + $ErrorActionPreference = 'SilentlyContinue' + } + Context 'Request-ALZEnvironmentConfig should request CLI input for configuration.' { + It 'Based on the configuration object' { + + Mock -CommandName Initialize-ConfigurationObject -MockWith { + [pscustomobject]@{ + Setting1 = [pscustomobject]@{ + ForEnvironment = $true + Value = "Test1" + } + Setting2 = [pscustomobject]@{ + ForEnvironment = $true + Value = "Test2" + } + } + } + + Mock -CommandName Request-ConfigurationValue + + Request-ALZEnvironmentConfig + + Should -Invoke Request-ConfigurationValue -Scope It -Times 2 -Exactly + } + + It 'Throws if the unsupported Terraform IAC is specified.' { + { Request-ALZEnvironmentConfig -alzIacProvider "terraform" } | Should -Throw -ExpectedMessage "Terraform is not yet supported." + } + } + } +} \ No newline at end of file diff --git a/src/Tests/Unit/Private/Request-ConfigurationValue.Tests.ps1 b/src/Tests/Unit/Private/Request-ConfigurationValue.Tests.ps1 index 1fe1f5da..cf34c51a 100644 --- a/src/Tests/Unit/Private/Request-ConfigurationValue.Tests.ps1 +++ b/src/Tests/Unit/Private/Request-ConfigurationValue.Tests.ps1 @@ -42,6 +42,25 @@ InModuleScope 'ALZ' { $configValue.Value | Should -BeExactly "user input value" } + It 'Prompt the user for configuration and providing no value selects the default value.' { + Mock -CommandName Read-Host -MockWith { + "" + } + + $configValue = @{ + Description = "The prefix that will be added to all resources created by this deployment." + Names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix") + Value = "" + DefaultValue = "alz" + } + + Request-ConfigurationValue -configName "prefix" -configValue $configValue + + Assert-MockCalled -CommandName Write-InformationColored -Times 3 + + $configValue.Value | Should -BeExactly "alz" + } + It 'Prompt the user with warning text if no value is specified and no default value is present.' { Mock -CommandName Read-Host -MockWith { "" diff --git a/src/Tests/Unit/Public/New-ALZEnvironment.Tests.ps1 b/src/Tests/Unit/Public/New-ALZEnvironment.Tests.ps1 index 4ec68711..8329474d 100644 --- a/src/Tests/Unit/Public/New-ALZEnvironment.Tests.ps1 +++ b/src/Tests/Unit/Public/New-ALZEnvironment.Tests.ps1 @@ -48,6 +48,8 @@ InModuleScope 'ALZ' { Mock -CommandName New-ALZDirectoryEnvironment -MockWith { } Mock -CommandName Copy-Item -MockWith { } + + Mock -CommandName Write-InformationColored } It 'should return the output directory on completion' { @@ -56,6 +58,12 @@ InModuleScope 'ALZ' { Assert-MockCalled -CommandName Edit-ALZConfigurationFilesInPlace -Exactly 1 } + + It 'Warns if the unsupported Terraform IAC is specified.' { + New-ALZEnvironment -alzIacProvider "terraform" + + Should -Invoke -CommandName Write-InformationColored -ParameterFilter { $ForegroundColor -eq "Red" } -Scope It + } } } } diff --git a/src/Tests/Unit/Public/Test-ALZRequirement.Tests.ps1 b/src/Tests/Unit/Public/Test-ALZRequirement.Tests.ps1 index 1ce4229e..af1b1cc8 100644 --- a/src/Tests/Unit/Public/Test-ALZRequirement.Tests.ps1 +++ b/src/Tests/Unit/Public/Test-ALZRequirement.Tests.ps1 @@ -108,7 +108,6 @@ InModuleScope 'ALZ' { It 'should return the expected results' { Test-ALZRequirement | Should -BeExactly "ALZ requirements are met." } - } } } From c430582178430c035b1ef4eec4e6c6e95f842e46 Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Wed, 15 Mar 2023 13:48:07 +0000 Subject: [PATCH 06/12] Something is breaking trying this to see if something gives me an error I can work with --- src/ALZ.build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ALZ.build.ps1 b/src/ALZ.build.ps1 index 5166642d..492278e8 100644 --- a/src/ALZ.build.ps1 +++ b/src/ALZ.build.ps1 @@ -140,7 +140,7 @@ Add-BuildTask TestModuleManifest -Before ImportModuleManifest { Add-BuildTask ImportModuleManifest { Write-Build White ' Attempting to load the project module.' try { - Import-Module $script:ModuleManifestFile -Force -PassThru -ErrorAction Stop + # Import-Module $script:ModuleManifestFile -Force -PassThru -ErrorAction Stop } catch { throw 'Unable to load the project module' } From 132854e24ad8fd276313438fcfc4548a831df74c Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Wed, 15 Mar 2023 13:57:05 +0000 Subject: [PATCH 07/12] Fix the import --- actions_bootstrap.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/actions_bootstrap.ps1 b/actions_bootstrap.ps1 index bc362476..7c1d4805 100644 --- a/actions_bootstrap.ps1 +++ b/actions_bootstrap.ps1 @@ -33,7 +33,7 @@ $null = $modulesToInstall.Add(([PSCustomObject]@{ # Required dependency of the ALZ module itself. $null = $modulesToInstall.Add(([PSCustomObject]@{ ModuleName = 'Az.Resources' - ModuleVersion = '5.6.0' + ModuleVersion = '6.5.2' })) @@ -52,8 +52,7 @@ foreach ($module in $modulesToInstall) { Install-Module @installSplat Import-Module -Name $module.ModuleName -ErrorAction Stop ' - Successfully installed {0}' -f $module.ModuleName - } - catch { + } catch { $message = 'Failed to install {0}' -f $module.ModuleName " - $message" throw From 4f4bbee32bbc085f5b872a3cec2dd30606f1620c Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Wed, 15 Mar 2023 14:00:50 +0000 Subject: [PATCH 08/12] Tidy ups --- actions_bootstrap.ps1 | 2 -- src/ALZ.build.ps1 | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/actions_bootstrap.ps1 b/actions_bootstrap.ps1 index 7c1d4805..896242f4 100644 --- a/actions_bootstrap.ps1 +++ b/actions_bootstrap.ps1 @@ -36,8 +36,6 @@ $null = $modulesToInstall.Add(([PSCustomObject]@{ ModuleVersion = '6.5.2' })) - - 'Installing PowerShell Modules' foreach ($module in $modulesToInstall) { $installSplat = @{ diff --git a/src/ALZ.build.ps1 b/src/ALZ.build.ps1 index 492278e8..5166642d 100644 --- a/src/ALZ.build.ps1 +++ b/src/ALZ.build.ps1 @@ -140,7 +140,7 @@ Add-BuildTask TestModuleManifest -Before ImportModuleManifest { Add-BuildTask ImportModuleManifest { Write-Build White ' Attempting to load the project module.' try { - # Import-Module $script:ModuleManifestFile -Force -PassThru -ErrorAction Stop + Import-Module $script:ModuleManifestFile -Force -PassThru -ErrorAction Stop } catch { throw 'Unable to load the project module' } From 7de8a03d691c51599ba6a43e0f210e73ec64d9fd Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Thu, 16 Mar 2023 16:33:34 +0000 Subject: [PATCH 09/12] Adding in tokenised strings as per discussion with Gabe --- .../Edit-ALZConfigurationFilesInPlace.ps1 | 10 +---- .../Format-TokenizedConfigurationString.ps1 | 21 ++++++++++ .../Initialize-ConfigurationObject.ps1 | 40 ++++++++++++++----- .../Private/Request-ALZEnvironmentConfig.ps1 | 6 ++- ...dit-ALZConfigurationFilesInPlace.Tests.ps1 | 11 +++++ .../Request-ALZEnvironmentConfig.Tests.ps1 | 2 + 6 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 src/ALZ/Private/Format-TokenizedConfigurationString.ps1 diff --git a/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 b/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 index ffbbbaf1..3d2d7f67 100644 --- a/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 +++ b/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 @@ -25,17 +25,11 @@ function Edit-ALZConfigurationFilesInPlace { foreach ($configKey in $configuration.PsObject.Properties) { foreach ($name in $configKey.Value.Names) { if ($null -ne $bicepConfiguration.parameters[$name]) { - - # If we've specified a string to replace - and the value contains that string, then replace it. - # Otherwise overwrite the value completely. - if ($null -ne $configKey.Value.Replace) { - $bicepConfiguration.parameters[$name].value = ` - $bicepConfiguration.parameters[$name].value -replace $configKey.Value.Replace, $configKey.Value.Value + if ($configKey.Value.Type -eq "Computed") { + $bicepConfiguration.parameters[$name].value = Format-TokenizedConfigurationString $configKey.Value.Value $configuration } else { $bicepConfiguration.parameters[$name].value = $configKey.Value.Value } - - $modified = $true } } diff --git a/src/ALZ/Private/Format-TokenizedConfigurationString.ps1 b/src/ALZ/Private/Format-TokenizedConfigurationString.ps1 new file mode 100644 index 00000000..8978101a --- /dev/null +++ b/src/ALZ/Private/Format-TokenizedConfigurationString.ps1 @@ -0,0 +1,21 @@ +function Format-TokenizedConfigurationString { + param( + [Parameter(Mandatory = $true)] + [string] $tokenizedString, + + [Parameter(Mandatory = $true)] + [object] $configuration + ) + $values = $tokenizedString -split "\{\%|\%\}" + + $returnValue = "" + foreach ($value in $values) { + if ($null -ne $configuration.$value) { + $returnValue += $configuration.$value.Value + } else { + $returnValue += $value + } + } + + return $returnValue +} \ No newline at end of file diff --git a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 index 4dd67080..0769b863 100644 --- a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 +++ b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 @@ -22,7 +22,7 @@ function Initialize-ConfigurationObject { return [pscustomobject]@{ Prefix = [pscustomobject]@{ - Type = "Configuration" + Type = "UserInput" Description = "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')" Names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix", "parTargetManagementGroupId", "parAssignableScopeManagementGroupId") Value = "alz" @@ -30,7 +30,7 @@ function Initialize-ConfigurationObject { Valid = "^[a-zA-Z]{3,5}$" } Suffix = [pscustomobject]@{ - Type = "Configuration" + Type = "UserInput" Description = "The suffix that will be added to all resources created by this deployment. (e.g. 'test')" Names = @("parTopLevelManagementGroupSuffix") Value = "" @@ -38,14 +38,14 @@ function Initialize-ConfigurationObject { Valid = "^[a-zA-Z]{0,5}$" } Location = [pscustomobject]@{ - Type = "Configuration" + Type = "UserInput" Description = "Deployment location." Names = @("parLocation", "parAutomationAccountLocation", "parLogAnalyticsWorkspaceLocation") AllowedValues = @(Get-AzLocation | Sort-Object Location | Select-Object -ExpandProperty Location) Value = "" } Environment = [pscustomobject]@{ - Type = "Configuration" + Type = "UserInput" Description = "The type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')" Names = @("parEnvironment") DefaultValue = 'prod' @@ -53,25 +53,47 @@ function Initialize-ConfigurationObject { Valid = "^[a-zA-Z0-9]{2,10}$" } IdentitySubscriptionId = [pscustomobject]@{ + Type = "UserInput" ForEnvironment = $true Description = "The identifier of the Identity subscription. (e.g '00000000-0000-0000-0000-000000000000')" - 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}$" + IsValid = { $Value -match "^( {){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}$" } Value = "" } ConnectivitySubscriptionId = [pscustomobject]@{ + Type = "UserInput" ForEnvironment = $true Description = "The identifier of the Connectivity subscription. (e.g '00000000-0000-0000-0000-000000000000')" - 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}$" + IsValid = { $Value -match "^( {){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}$" } Value = "" } ManagementSubscriptionId = [pscustomobject]@{ + Type = "UserInput" ForEnvironment = $true Description = "The identifier of the Management subscription. (e.g 00000000-0000-0000-0000-000000000000)" - 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}$" - Names = @("parLogAnalyticsWorkspaceResourceId") - Replace = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + IsValid = { $Value -match "^( {){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}$" } Value = "" } + BillingAccountId = [pscustomobject]@{ + Type = "UserInput" + Description = "The identifier of the Billing Account. (e.g 00000000-0000-0000-0000-000000000000)" + IsValid = { $Value -match "^( {){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}$" } + Value = "" + } + LogAnalyticsResourceId = [pscustomobject]@{ + Type = "Computed" + Value = "/subscriptions/{%ManagementSubscriptionId%}/resourcegroups/alz-logging/providers/microsoft.operationalinsights/workspaces/alz-log-analytics" + Names = @("parLogAnalyticsWorkspaceResourceId") + } + EnrollmentAccountId = [pscustomobject]@{ + Type = "UserInput" + Description = "The identifier of the Enrollement Account. (e.g 00000000-0000-0000-0000-000000000000)" + Value = "" + } + SubscriptionBillingScope = [pscustomobject]@{ + Type = "Computed" + Names = @("parSubscriptionBillingScope") + Value = "/providers/Microsoft.Billing/billingAccounts/{%BillingAccountId$%}/enrollmentAccounts/{%EnrollmentAccountId$%}" + } } } diff --git a/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 b/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 index 49ba8557..f8e95129 100644 --- a/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 +++ b/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 @@ -21,10 +21,12 @@ function Request-ALZEnvironmentConfig { $configuration = Initialize-ConfigurationObject -alzIacProvider $alzIacProvider Write-Verbose "Configuration object initialized." - Write-Verbose "Configuration object: $(ConvertTo-Json $configuration)" + # Write-Verbose "Configuration object: $(ConvertTo-Json $configuration -Depth 10)" foreach ($configurationValue in $configuration.PsObject.Properties) { - Request-ConfigurationValue $configurationValue.Name $configurationValue.Value + if ($configurationValue.Value.Type -eq "UserInput") { + Request-ConfigurationValue $configurationValue.Name $configurationValue.Value + } } return $configuration diff --git a/src/Tests/Unit/Private/Edit-ALZConfigurationFilesInPlace.Tests.ps1 b/src/Tests/Unit/Private/Edit-ALZConfigurationFilesInPlace.Tests.ps1 index c44c382d..e93448c7 100644 --- a/src/Tests/Unit/Private/Edit-ALZConfigurationFilesInPlace.Tests.ps1 +++ b/src/Tests/Unit/Private/Edit-ALZConfigurationFilesInPlace.Tests.ps1 @@ -38,6 +38,12 @@ InModuleScope 'ALZ' { DefaultValue = 'prod' Value = "dev" } + Logging = [pscustomobject]@{ + Type = "Computed" + Description = "The type of environment that will be created . Example: dev, test, qa, staging, prod" + Value = "logs/{%Environment%}/{%Location%}" + Names = @("parLogging") + } } $firstFileContent = '{ "parameters": { @@ -46,6 +52,9 @@ InModuleScope 'ALZ' { }, "parTopLevelManagementGroupPrefix": { "value": "" + }, + "parLogging" : { + "value": "" } } }' @@ -97,9 +106,11 @@ InModuleScope 'ALZ' { $contentAfterParsing = ConvertFrom-Json -InputObject $firstFileContent -AsHashtable $contentAfterParsing.parameters.parTopLevelManagementGroupPrefix.value = 'test' $contentAfterParsing.parameters.parCompanyPrefix.value = 'test' + $contentAfterParsing.parameters.parLogging.value = "logs/dev/eastus" $contentStringAfterParsing = ConvertTo-Json -InputObject $contentAfterParsing Write-InformationColored $contentStringAfterParsing -ForegroundColor Yellow -InformationAction Continue Should -Invoke -CommandName Out-File -ParameterFilter { $FilePath -eq "test1.parameters.json" -and $InputObject -eq $contentStringAfterParsing } -Scope It + $contentAfterParsing = ConvertFrom-Json -InputObject $secondFileContent -AsHashtable $contentAfterParsing.parameters.parTopLevelManagementGroupSuffix.value = 'bla' $contentAfterParsing.parameters.parLocation.value = 'eastus' diff --git a/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 b/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 index 5fc63b57..99c255cd 100644 --- a/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 +++ b/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 @@ -23,10 +23,12 @@ InModuleScope 'ALZ' { Mock -CommandName Initialize-ConfigurationObject -MockWith { [pscustomobject]@{ Setting1 = [pscustomobject]@{ + Type = "UserInput" ForEnvironment = $true Value = "Test1" } Setting2 = [pscustomobject]@{ + Type = "UserInput" ForEnvironment = $true Value = "Test2" } From 8b4781b26a26b0e4cd33d95f25c5bcf23052ea05 Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Thu, 16 Mar 2023 17:28:40 +0000 Subject: [PATCH 10/12] Unwinding a partial change to validation --- .../Initialize-ConfigurationObject.ps1 | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 index 0769b863..e12e7e6e 100644 --- a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 +++ b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 @@ -55,44 +55,40 @@ function Initialize-ConfigurationObject { IdentitySubscriptionId = [pscustomobject]@{ Type = "UserInput" ForEnvironment = $true - Description = "The identifier of the Identity subscription. (e.g '00000000-0000-0000-0000-000000000000')" - IsValid = { $Value -match "^( {){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}$" } + Description = "The identifier of the Identity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" + 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}$" Value = "" } ConnectivitySubscriptionId = [pscustomobject]@{ Type = "UserInput" ForEnvironment = $true - Description = "The identifier of the Connectivity subscription. (e.g '00000000-0000-0000-0000-000000000000')" - IsValid = { $Value -match "^( {){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}$" } + Description = "The identifier of the Connectivity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" + 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}$" Value = "" } ManagementSubscriptionId = [pscustomobject]@{ Type = "UserInput" ForEnvironment = $true - Description = "The identifier of the Management subscription. (e.g 00000000-0000-0000-0000-000000000000)" - IsValid = { $Value -match "^( {){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}$" } + Description = "The identifier of the Management Subscription. (e.g 00000000-0000-0000-0000-000000000000)" + 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}$" Value = "" } BillingAccountId = [pscustomobject]@{ Type = "UserInput" Description = "The identifier of the Billing Account. (e.g 00000000-0000-0000-0000-000000000000)" - IsValid = { $Value -match "^( {){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}$" } + IValid = "^( {){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}$" Value = "" } - LogAnalyticsResourceId = [pscustomobject]@{ - Type = "Computed" - Value = "/subscriptions/{%ManagementSubscriptionId%}/resourcegroups/alz-logging/providers/microsoft.operationalinsights/workspaces/alz-log-analytics" - Names = @("parLogAnalyticsWorkspaceResourceId") - } EnrollmentAccountId = [pscustomobject]@{ Type = "UserInput" - Description = "The identifier of the Enrollement Account. (e.g 00000000-0000-0000-0000-000000000000)" + Description = "The identifier of the Enrollment Account. (e.g 00000000-0000-0000-0000-000000000000)" + 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}$" Value = "" } - SubscriptionBillingScope = [pscustomobject]@{ + LogAnalyticsResourceId = [pscustomobject]@{ Type = "Computed" - Names = @("parSubscriptionBillingScope") - Value = "/providers/Microsoft.Billing/billingAccounts/{%BillingAccountId$%}/enrollmentAccounts/{%EnrollmentAccountId$%}" + Value = "/subscriptions/{%ManagementSubscriptionId%}/resourcegroups/alz-logging/providers/microsoft.operationalinsights/workspaces/alz-log-analytics" + Names = @("parLogAnalyticsWorkspaceResourceId") } } } From 075b2fe21c7cfd464a7d1d8aaf68857793fcc703 Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Fri, 17 Mar 2023 10:25:24 +0000 Subject: [PATCH 11/12] Committing this with improved CLI and token replacement --- .../alz-bicep-config/v0.13.0.ux.config.json | 0 .../Private/Build-ALZDeploymentEnvFile.ps1 | 6 +- .../Edit-ALZConfigurationFilesInPlace.ps1 | 8 +- .../Format-TokenizedConfigurationString.ps1 | 4 + .../Initialize-ConfigurationObject.ps1 | 103 +++++++++++---- .../Build-ALZDeploymentEnvFile.Tests.ps1 | 32 +++-- ...dit-ALZConfigurationFilesInPlace.Tests.ps1 | 34 ++++- ...mat-TokenizedConfigurationString.Tests.ps1 | 118 ++++++++++++++++++ .../Initialize-ConfigurationObject.Tests.ps1 | 10 +- 9 files changed, 265 insertions(+), 50 deletions(-) create mode 100644 src/ALZ/Assets/alz-bicep-config/v0.13.0.ux.config.json create mode 100644 src/Tests/Unit/Private/Format-TokenizedConfigurationString.Tests.ps1 diff --git a/src/ALZ/Assets/alz-bicep-config/v0.13.0.ux.config.json b/src/ALZ/Assets/alz-bicep-config/v0.13.0.ux.config.json new file mode 100644 index 00000000..e69de29b diff --git a/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 b/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 index 56f3fd18..31fb1f91 100644 --- a/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 +++ b/src/ALZ/Private/Build-ALZDeploymentEnvFile.ps1 @@ -22,8 +22,10 @@ function Build-ALZDeploymentEnvFile { New-Item -Path $envFile -ItemType file -Force | Out-Null foreach ($configurationValue in $configuration.PsObject.Properties) { - if ($configurationValue.Value.ForEnvironment -eq $true) { - Add-Content -Path $envFile -Value "$($($configurationValue.Name))=`"$($configurationValue.Value.Value)`"" | Out-Null + foreach ($target in $configurationValue.Value.Targets) { + if ($target.Destination -eq "Environment") { + Add-Content -Path $envFile -Value "$($($target.Name))=`"$($configurationValue.Value.Value)`"" | Out-Null + } } } } \ No newline at end of file diff --git a/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 b/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 index 3d2d7f67..8b8325cc 100644 --- a/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 +++ b/src/ALZ/Private/Edit-ALZConfigurationFilesInPlace.ps1 @@ -23,12 +23,12 @@ function Edit-ALZConfigurationFilesInPlace { $bicepConfiguration = Get-Content $file.FullName | ConvertFrom-Json -AsHashtable $modified = $false foreach ($configKey in $configuration.PsObject.Properties) { - foreach ($name in $configKey.Value.Names) { - if ($null -ne $bicepConfiguration.parameters[$name]) { + foreach ($target in $configKey.Value.Targets) { + if ($target.Destination -eq "Parameters" -and $null -ne $bicepConfiguration.parameters[$target.Name]) { if ($configKey.Value.Type -eq "Computed") { - $bicepConfiguration.parameters[$name].value = Format-TokenizedConfigurationString $configKey.Value.Value $configuration + $bicepConfiguration.parameters[$target.Name].value = Format-TokenizedConfigurationString $configKey.Value.Value $configuration } else { - $bicepConfiguration.parameters[$name].value = $configKey.Value.Value + $bicepConfiguration.parameters[$target.Name].value = $configKey.Value.Value } $modified = $true } diff --git a/src/ALZ/Private/Format-TokenizedConfigurationString.ps1 b/src/ALZ/Private/Format-TokenizedConfigurationString.ps1 index 8978101a..bd905822 100644 --- a/src/ALZ/Private/Format-TokenizedConfigurationString.ps1 +++ b/src/ALZ/Private/Format-TokenizedConfigurationString.ps1 @@ -10,8 +10,12 @@ function Format-TokenizedConfigurationString { $returnValue = "" foreach ($value in $values) { + $isToken = $tokenizedString -contains "{%$value%}" if ($null -ne $configuration.$value) { $returnValue += $configuration.$value.Value + } elseif (($null -eq $configuration.$value) -and $isToken) { + Write-InformationColored "Specified replacement token '${value}' not found in configuration." -ForegroundColor Yellow -InformationAction Continue + $returnValue += "{%$value%}" } else { $returnValue += $value } diff --git a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 index e12e7e6e..c4ad9527 100644 --- a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 +++ b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 @@ -1,4 +1,3 @@ - function Initialize-ConfigurationObject { param( [Parameter(Mandatory = $false)] @@ -24,15 +23,35 @@ function Initialize-ConfigurationObject { Prefix = [pscustomobject]@{ Type = "UserInput" Description = "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')" - Names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix", "parTargetManagementGroupId", "parAssignableScopeManagementGroupId") - Value = "alz" + Targets = @( + [pscustomobject]@{ + Name = "parTopLevelManagementGroupPrefix" + Destination = "Parameters" + }, + [pscustomobject]@{ + Name = "parCompanyPrefix" + Destination = "Parameters" + }, + [pscustomobject]@{ + Name = "parTargetManagementGroupId" + Destination = "Parameters" + }, + [pscustomobject]@{ + Name = "parAssignableScopeManagementGroupId" + Destination = "Parameters" + }) + Value = "" DefaultValue = "alz" Valid = "^[a-zA-Z]{3,5}$" } Suffix = [pscustomobject]@{ Type = "UserInput" Description = "The suffix that will be added to all resources created by this deployment. (e.g. 'test')" - Names = @("parTopLevelManagementGroupSuffix") + Targets = @( + [pscustomobject]@{ + Name = "parTopLevelManagementGroupSuffix" + Destination = "Parameters" + }) Value = "" DefaultValue = "" Valid = "^[a-zA-Z]{0,5}$" @@ -40,43 +59,71 @@ function Initialize-ConfigurationObject { Location = [pscustomobject]@{ Type = "UserInput" Description = "Deployment location." - Names = @("parLocation", "parAutomationAccountLocation", "parLogAnalyticsWorkspaceLocation") + Targets = @( + [pscustomobject]@{ + Name = "parLocation" + Destination = "Parameters" + }, + [pscustomobject]@{ + Name = "parAutomationAccountLocation" + Destination = "Parameters" + }, + [pscustomobject]@{ + Name = "parLogAnalyticsWorkspaceLocation" + Destination = "Parameters" + }) AllowedValues = @(Get-AzLocation | Sort-Object Location | Select-Object -ExpandProperty Location) Value = "" } Environment = [pscustomobject]@{ Type = "UserInput" Description = "The type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')" - Names = @("parEnvironment") - DefaultValue = 'prod' + Targets = @( + [pscustomobject]@{ + Name = "parEnvironment" + Destination = "Parameters" + }) Value = "" + DefaultValue = 'prod' Valid = "^[a-zA-Z0-9]{2,10}$" } IdentitySubscriptionId = [pscustomobject]@{ - Type = "UserInput" - ForEnvironment = $true - Description = "The identifier of the Identity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" - 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}$" - Value = "" + Type = "UserInput" + Description = "The identifier of the Identity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" + 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}$" + Targets = @( + [pscustomobject]@{ + Name = "IdentitySubscriptionId" + Destination = "Environment" + }) + Value = "" } ConnectivitySubscriptionId = [pscustomobject]@{ - Type = "UserInput" - ForEnvironment = $true - Description = "The identifier of the Connectivity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" - 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}$" - Value = "" + Type = "UserInput" + Description = "The identifier of the Connectivity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" + 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}$" + Targets = @( + [pscustomobject]@{ + Name = "ConnectivitySubscriptionId" + Destination = "Environment" + }) + Value = "" } ManagementSubscriptionId = [pscustomobject]@{ - Type = "UserInput" - ForEnvironment = $true - Description = "The identifier of the Management Subscription. (e.g 00000000-0000-0000-0000-000000000000)" - 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}$" - Value = "" + Type = "UserInput" + Description = "The identifier of the Management Subscription. (e.g 00000000-0000-0000-0000-000000000000)" + 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}$" + Targets = @( + [pscustomobject]@{ + Name = "ManagementSubscriptionId" + Destination = "Environment" + }) + Value = "" } BillingAccountId = [pscustomobject]@{ Type = "UserInput" Description = "The identifier of the Billing Account. (e.g 00000000-0000-0000-0000-000000000000)" - IValid = "^( {){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}$" + 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}$" Value = "" } EnrollmentAccountId = [pscustomobject]@{ @@ -86,9 +133,13 @@ function Initialize-ConfigurationObject { Value = "" } LogAnalyticsResourceId = [pscustomobject]@{ - Type = "Computed" - Value = "/subscriptions/{%ManagementSubscriptionId%}/resourcegroups/alz-logging/providers/microsoft.operationalinsights/workspaces/alz-log-analytics" - Names = @("parLogAnalyticsWorkspaceResourceId") + Type = "Computed" + Value = "/subscriptions/{%ManagementSubscriptionId%}/resourcegroups/alz-logging/providers/microsoft.operationalinsights/workspaces/alz-log-analytics" + Targets = @( + [pscustomobject]@{ + Name = "parLogAnalyticsWorkspaceResourceId" + Destination = "Parameters" + }) } } } diff --git a/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 b/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 index 8885ec67..4d9cc9e3 100644 --- a/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 +++ b/src/Tests/Unit/Private/Build-ALZDeploymentEnvFile.Tests.ps1 @@ -25,12 +25,20 @@ InModuleScope 'ALZ' { $configuration = [pscustomobject]@{ Setting1 = [pscustomobject]@{ - ForEnvironment = $true - Value = "Test1" + Targets = @( + [pscustomobject]@{ + Name = "Setting1" + Destination = "Environment" + }) + Value = "Test1" } Setting2 = [pscustomobject]@{ - ForEnvironment = $true - Value = "Test2" + Targets = @( + [pscustomobject]@{ + Name = "Setting2" + Destination = "Environment" + }) + Value = "Test2" } } @@ -47,12 +55,20 @@ InModuleScope 'ALZ' { $configuration = [pscustomobject]@{ Setting1 = [pscustomobject]@{ - ForEnvironment = $true - Value = "Test1" + Targets = @( + [pscustomobject]@{ + Name = "Setting1" + Destination = "Environment" + }) + Value = "Test1" } Setting2 = [pscustomobject]@{ - ForEnvironment = $false - Value = "Test2" + Targets = @( + [pscustomobject]@{ + Name = "Setting2" + Destination = "Parameters" + }) + Value = "Test2" } } diff --git a/src/Tests/Unit/Private/Edit-ALZConfigurationFilesInPlace.Tests.ps1 b/src/Tests/Unit/Private/Edit-ALZConfigurationFilesInPlace.Tests.ps1 index e93448c7..210dc34d 100644 --- a/src/Tests/Unit/Private/Edit-ALZConfigurationFilesInPlace.Tests.ps1 +++ b/src/Tests/Unit/Private/Edit-ALZConfigurationFilesInPlace.Tests.ps1 @@ -16,25 +16,45 @@ InModuleScope 'ALZ' { $defaultConfig = [pscustomobject]@{ Prefix = [pscustomobject]@{ Description = "The prefix that will be added to all resources created by this deployment." - Names = @("parTopLevelManagementGroupPrefix", "parCompanyPrefix") + Targets = @( + [pscustomobject]@{ + Name = "parTopLevelManagementGroupPrefix" + Destination = "Parameters" + }, + [pscustomobject]@{ + Name = "parCompanyPrefix" + Destination = "Parameters" + }) Value = "test" DefaultValue = "alz" } Suffix = [pscustomobject]@{ Description = "The suffix that will be added to all resources created by this deployment." - Names = @("parTopLevelManagementGroupSuffix") + Targets = @( + [pscustomobject]@{ + Name = "parTopLevelManagementGroupSuffix" + Destination = "Parameters" + }) Value = "bla" DefaultValue = "" } Location = [pscustomobject]@{ Description = "Deployment location." - Names = @("parLocation") + Targets = @( + [pscustomobject]@{ + Name = "parLocation" + Destination = "Parameters" + }) AllowedValues = @('ukwest', '') Value = "eastus" } Environment = [pscustomobject]@{ Description = "The type of environment that will be created . Example: dev, test, qa, staging, prod" - Names = @("parEnvironment") + Targets = @( + [pscustomobject]@{ + Name = "parEnvironment" + Destination = "Parameters" + }) DefaultValue = 'prod' Value = "dev" } @@ -42,7 +62,11 @@ InModuleScope 'ALZ' { Type = "Computed" Description = "The type of environment that will be created . Example: dev, test, qa, staging, prod" Value = "logs/{%Environment%}/{%Location%}" - Names = @("parLogging") + Targets = @( + [pscustomobject]@{ + Name = "parLogging" + Destination = "Parameters" + }) } } $firstFileContent = '{ diff --git a/src/Tests/Unit/Private/Format-TokenizedConfigurationString.Tests.ps1 b/src/Tests/Unit/Private/Format-TokenizedConfigurationString.Tests.ps1 new file mode 100644 index 00000000..a183ee12 --- /dev/null +++ b/src/Tests/Unit/Private/Format-TokenizedConfigurationString.Tests.ps1 @@ -0,0 +1,118 @@ +#------------------------------------------------------------------------- +Set-Location -Path $PSScriptRoot +#------------------------------------------------------------------------- +$ModuleName = 'ALZ' +$PathToManifest = [System.IO.Path]::Combine('..', '..', '..', $ModuleName, "$ModuleName.psd1") +#------------------------------------------------------------------------- +if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') { + #if the module is already in memory, remove it + Remove-Module -Name $ModuleName -Force +} +Import-Module $PathToManifest -Force +#------------------------------------------------------------------------- + +InModuleScope 'ALZ' { + Describe 'Format-TokenizedConfigurationString tests ' -Tag Unit { + BeforeAll { + $WarningPreference = 'SilentlyContinue' + $ErrorActionPreference = 'SilentlyContinue' + } + Context 'Replace the specified tokens with values in the configuration object.' { + BeforeEach { + } + It 'When there is one token to replace.' { + $configuration = [pscustomobject]@{ + Setting1 = [pscustomobject]@{ + Targets = @( + [pscustomobject]@{ + Name = "Setting1" + Destination = "Environment" + }) + Value = "Test1" + } + Setting2 = [pscustomobject]@{ + Targets = @( + [pscustomobject]@{ + Name = "Setting2" + Destination = "Parameters" + }) + Value = "Test2" + } + } + + Format-TokenizedConfigurationString "{%Setting1%}" $configuration | Should -Be "Test1" + } + + It 'When there are two tokens to replace.' { + + $configuration = [pscustomobject]@{ + Setting1 = [pscustomobject]@{ + Targets = @( + [pscustomobject]@{ + Name = "Setting1" + Destination = "Environment" + }) + Value = "Test1" + } + Setting2 = [pscustomobject]@{ + Targets = @( + [pscustomobject]@{ + Name = "Setting2" + Destination = "Parameters" + }) + Value = "Test2" + } + } + + Format-TokenizedConfigurationString "{%Setting1%}/{%Setting2%}" $configuration | Should -Be "Test1/Test2" + } + + It 'When the token is not found.' { + $configuration = [pscustomobject]@{ + Setting1 = [pscustomobject]@{ + Targets = @( + [pscustomobject]@{ + Name = "Setting1" + Destination = "Environment" + }) + Value = "Test1" + } + Setting2 = [pscustomobject]@{ + Targets = @( + [pscustomobject]@{ + Name = "Setting2" + Destination = "Parameters" + }) + Value = "Test2" + } + } + + Format-TokenizedConfigurationString "{%DoesntMatch%}" $configuration | Should -Be "{%DoesntMatch%}" + } + + It 'When the token is repeated.' { + + $configuration = [pscustomobject]@{ + Setting1 = [pscustomobject]@{ + Targets = @( + [pscustomobject]@{ + Name = "Setting1" + Destination = "Environment" + }) + Value = "Test1" + } + Setting2 = [pscustomobject]@{ + Targets = @( + [pscustomobject]@{ + Name = "Setting2" + Destination = "Parameters" + }) + Value = "Test2" + } + } + + Format-TokenizedConfigurationString "{%Setting1%}/{%Setting1%}/{%Setting1%}/{%Setting1%}" $configuration | Should -Be "Test1/Test1/Test1/Test1" + } + } + } +} diff --git a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 index fb60a6ee..92d8cc63 100644 --- a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 +++ b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 @@ -32,24 +32,24 @@ InModuleScope 'ALZ' { } It 'should return the not met for non AZ module' { $content = Initialize-ConfigurationObject - $content.Prefix.Value | Should -Be 'alz' + $content.Prefix.Value | Should -Be '' $content.Prefix.DefaultValue | Should -Be 'alz' $content.Prefix.Description | Should -Be "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')" - $content.Prefix.Names | Should -Be @('parTopLevelManagementGroupPrefix', 'parCompanyPrefix', 'parTargetManagementGroupId', 'parAssignableScopeManagementGroupId') + # $content.Prefix.Names | Should -Be @('parTopLevelManagementGroupPrefix', 'parCompanyPrefix', 'parTargetManagementGroupId', 'parAssignableScopeManagementGroupId') $content.Suffix.Value | Should -Be '' $content.Suffix.DefaultValue | Should -Be '' $content.Suffix.Description | Should -Be "The suffix that will be added to all resources created by this deployment. (e.g. 'test')" - $content.Suffix.Names | Should -Be @('parTopLevelManagementGroupSuffix') + # $content.Suffix.Names | Should -Be @('parTopLevelManagementGroupSuffix') $content.Location.Value | Should -Be '' $content.Location.Description | Should -Be 'Deployment location.' - $content.Location.Names | Should -Be @("parLocation", "parAutomationAccountLocation", "parLogAnalyticsWorkspaceLocation") + # $content.Location.Names | Should -Be @("parLocation", "parAutomationAccountLocation", "parLogAnalyticsWorkspaceLocation") $content.Location.AllowedValues | Should -Be @('eastus', 'ukwest') $content.Environment.Value | Should -Be '' $content.Environment.Description | Should -Be "The type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')" - $content.Environment.Names | Should -Be @('parEnvironment') + # $content.Environment.Namkes | Should -Be @('parEnvironment') $content.Environment.DefaultValue | Should -Be 'prod' } From ac288ab06aad4ac045ed5e6143f580b2d6cf16a4 Mon Sep 17 00:00:00 2001 From: "guy.pritchard" Date: Fri, 17 Mar 2023 13:01:44 +0000 Subject: [PATCH 12/12] Committing this with a json based configuration object --- .../alz-bicep-config/v0.13.0.ux.config.json | 205 ++++++++++++++++++ src/ALZ/Private/Get-Configuration.ps1 | 31 +++ .../Initialize-ConfigurationObject.ps1 | 146 ------------- .../Private/Request-ALZEnvironmentConfig.ps1 | 14 +- src/ALZ/Public/New-ALZEnvironment.ps1 | 5 +- .../Unit/Private/Get-Configuration.Tests.ps1 | 126 +++++++++++ .../Initialize-ConfigurationObject.Tests.ps1 | 61 ------ .../Request-ALZEnvironmentConfig.Tests.ps1 | 6 +- 8 files changed, 378 insertions(+), 216 deletions(-) create mode 100644 src/ALZ/Private/Get-Configuration.ps1 delete mode 100644 src/ALZ/Private/Initialize-ConfigurationObject.ps1 create mode 100644 src/Tests/Unit/Private/Get-Configuration.Tests.ps1 delete mode 100644 src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 diff --git a/src/ALZ/Assets/alz-bicep-config/v0.13.0.ux.config.json b/src/ALZ/Assets/alz-bicep-config/v0.13.0.ux.config.json index e69de29b..8192c877 100644 --- a/src/ALZ/Assets/alz-bicep-config/v0.13.0.ux.config.json +++ b/src/ALZ/Assets/alz-bicep-config/v0.13.0.ux.config.json @@ -0,0 +1,205 @@ +{ + "Prefix": { + "Type": "UserInput", + "Description": "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')", + "Targets": [ + { + "Name": "parTopLevelManagementGroupPrefix", + "Destination": "Parameters" + }, + { + "Name": "parCompanyPrefix", + "Destination": "Parameters" + }, + { + "Name": "parTargetManagementGroupId", + "Destination": "Parameters" + }, + { + "Name": "parAssignableScopeManagementGroupId", + "Destination": "Parameters" + } + ], + "Value": "", + "DefaultValue": "alz", + "Valid": "^[a-zA-Z]{3,5}$" + }, + "Suffix": { + "Type": "UserInput", + "Description": "The suffix that will be added to all resources created by this deployment. (e.g. 'test')", + "Targets": [ + { + "Name": "parTopLevelManagementGroupSuffix", + "Destination": "Parameters" + } + ], + "Value": "", + "DefaultValue": "", + "Valid": "^[a-zA-Z]{0,5}$" + }, + "Location": { + "Type": "UserInput", + "Description": "Deployment location.", + "Value": "", + "Targets": [ + { + "Name": "parLocation", + "Destination": "Parameters" + }, + { + "Name": "parAutomationAccountLocation", + "Destination": "Parameters" + }, + { + "Name": "parLogAnalyticsWorkspaceLocation", + "Destination": "Parameters" + } + ], + "AllowedValues": [ + "asia", + "asiapacific", + "australia", + "australiacentral", + "australiacentral2", + "australiaeast", + "australiasoutheast", + "brazil", + "brazilsouth", + "brazilsoutheast", + "canada", + "canadacentral", + "canadaeast", + "centralindia", + "centralus", + "centraluseuap", + "centralusstage", + "eastasia", + "eastasiastage", + "eastus", + "eastus2", + "eastus2euap", + "eastus2stage", + "eastusstg", + "europe", + "france", + "francecentral", + "francesouth", + "germany", + "germanynorth", + "germanywestcentral", + "global", + "india", + "japan", + "japaneast", + "japanwest", + "jioindiacentral", + "jioindiawest", + "korea", + "koreacentral", + "koreasouth", + "northcentralus", + "northcentralusstage", + "northeurope", + "norway", + "norwayeast", + "norwaywest", + "qatarcentral", + "singapore", + "southafrica", + "southafricanorth", + "southafricawest", + "southcentralus", + "southcentralusstage", + "southeastasia", + "southindia", + "swedencentral", + "switzerland", + "switzerlandnorth", + "switzerlandwest", + "uaecentral", + "uaenorth", + "uksouth", + "ukwest", + "unitedstates", + "westcentralus", + "westeurope", + "westindia", + "westus", + "westus2", + "westus2stage", + "westus3", + "westusstage" + ] + }, + "Environment": { + "Type": "UserInput", + "Description": "The Type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')", + "Targets": [ + { + "Name": "parEnvironment", + "Destination": "Parameters" + } + ], + "Value": "", + "DefaultValue": "prod", + "Valid": "^[a-zA-Z0-9]{2,10}$" + }, + "IdentitySubscriptionId": { + "Type": "UserInput", + "Description": "The identifier of the Identity Subscription. (e.g '00000000-0000-0000-0000-000000000000')", + "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}$", + "Targets": [ + { + "Name": "IdentitySubscriptionId", + "Destination": "Environment" + } + ], + "Value": "" + }, + "ConnectivitySubscriptionId": { + "Type": "UserInput", + "Description": "The identifier of the Connectivity Subscription. (e.g '00000000-0000-0000-0000-000000000000')", + "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}$", + "Targets": [ + { + "Name": "ConnectivitySubscriptionId", + "Destination": "Environment" + } + ], + "Value": "" + }, + "ManagementSubscriptionId": { + "Type": "UserInput", + "Description": "The identifier of the Management Subscription. (e.g 00000000-0000-0000-0000-000000000000)", + "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}$", + "Targets": [ + { + "Name": "ManagementSubscriptionId", + "Destination": "Environment" + } + ], + "Value": "" + }, + "BillingAccountId": { + "Type": "UserInput", + "Description": "The identifier of the Billing Account. (e.g 00000000-0000-0000-0000-000000000000)", + "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}$", + "Value": "" + }, + "EnrollmentAccountId": { + "Type": "UserInput", + "Description": "The identifier of the Enrollment Account. (e.g 00000000-0000-0000-0000-000000000000)", + "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}$", + "Value": "" + }, + "LogAnalyticsResourceId": { + "Type": "Computed", + "Value": "/subscriptions/{%ManagementSubscriptionId%}/resourcegroups/alz-logging/providers/microsoft.operationalinsights/workspaces/alz-log-analytics", + "Targets": [ + { + "Name": "parLogAnalyticsWorkspaceResourceId", + "Destination": "Parameters" + } + ] + } +} \ No newline at end of file diff --git a/src/ALZ/Private/Get-Configuration.ps1 b/src/ALZ/Private/Get-Configuration.ps1 new file mode 100644 index 00000000..37dbd4a0 --- /dev/null +++ b/src/ALZ/Private/Get-Configuration.ps1 @@ -0,0 +1,31 @@ +function Get-Configuration { + param( + [Parameter(Mandatory = $false)] + [ValidateSet("bicep", "terraform")] + [string] $alzIacProvider = "bicep", + + [Parameter(Mandatory = $false)] + [string] $alzEnvironmentDestination = ".", + + [Parameter(Mandatory = $false)] + [string] $alzBicepVersion = "v0.13.0" + ) + <# + .SYNOPSIS + This function uses a template configuration to prompt for and return a user specified/modified configuration object. + .EXAMPLE + Get-Configuration + .EXAMPLE + Get-Configuration -alzIacProvider "bicep" + .OUTPUTS + System.Object. The resultant configuration values. + #> + + if ($alzIacProvider -eq "terraform") { + throw "Terraform is not yet supported." + } + + $uxConfigurationFile = Join-Path $alzEnvironmentDestination "alz-bicep-config" "$alzBicepVersion.ux.config.json" + return Get-Content -Path $uxConfigurationFile -Raw | ConvertFrom-Json +} + diff --git a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 b/src/ALZ/Private/Initialize-ConfigurationObject.ps1 deleted file mode 100644 index c4ad9527..00000000 --- a/src/ALZ/Private/Initialize-ConfigurationObject.ps1 +++ /dev/null @@ -1,146 +0,0 @@ -function Initialize-ConfigurationObject { - param( - [Parameter(Mandatory = $false)] - [ValidateSet("bicep", "terraform")] - [string] $alzIacProvider = "bicep" - ) - <# - .SYNOPSIS - This function uses a template configuration to prompt for and return a user specified/modified configuration object. - .EXAMPLE - Initialize-ConfigurationObject - .EXAMPLE - Initialize-ConfigurationObject -alzIacProvider "bicep" - .OUTPUTS - System.Object. The resultant configuration values. - #> - - if ($alzIacProvider -eq "terraform") { - throw "Terraform is not yet supported." - } - - return [pscustomobject]@{ - Prefix = [pscustomobject]@{ - Type = "UserInput" - Description = "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')" - Targets = @( - [pscustomobject]@{ - Name = "parTopLevelManagementGroupPrefix" - Destination = "Parameters" - }, - [pscustomobject]@{ - Name = "parCompanyPrefix" - Destination = "Parameters" - }, - [pscustomobject]@{ - Name = "parTargetManagementGroupId" - Destination = "Parameters" - }, - [pscustomobject]@{ - Name = "parAssignableScopeManagementGroupId" - Destination = "Parameters" - }) - Value = "" - DefaultValue = "alz" - Valid = "^[a-zA-Z]{3,5}$" - } - Suffix = [pscustomobject]@{ - Type = "UserInput" - Description = "The suffix that will be added to all resources created by this deployment. (e.g. 'test')" - Targets = @( - [pscustomobject]@{ - Name = "parTopLevelManagementGroupSuffix" - Destination = "Parameters" - }) - Value = "" - DefaultValue = "" - Valid = "^[a-zA-Z]{0,5}$" - } - Location = [pscustomobject]@{ - Type = "UserInput" - Description = "Deployment location." - Targets = @( - [pscustomobject]@{ - Name = "parLocation" - Destination = "Parameters" - }, - [pscustomobject]@{ - Name = "parAutomationAccountLocation" - Destination = "Parameters" - }, - [pscustomobject]@{ - Name = "parLogAnalyticsWorkspaceLocation" - Destination = "Parameters" - }) - AllowedValues = @(Get-AzLocation | Sort-Object Location | Select-Object -ExpandProperty Location) - Value = "" - } - Environment = [pscustomobject]@{ - Type = "UserInput" - Description = "The type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')" - Targets = @( - [pscustomobject]@{ - Name = "parEnvironment" - Destination = "Parameters" - }) - Value = "" - DefaultValue = 'prod' - Valid = "^[a-zA-Z0-9]{2,10}$" - } - IdentitySubscriptionId = [pscustomobject]@{ - Type = "UserInput" - Description = "The identifier of the Identity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" - 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}$" - Targets = @( - [pscustomobject]@{ - Name = "IdentitySubscriptionId" - Destination = "Environment" - }) - Value = "" - } - ConnectivitySubscriptionId = [pscustomobject]@{ - Type = "UserInput" - Description = "The identifier of the Connectivity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" - 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}$" - Targets = @( - [pscustomobject]@{ - Name = "ConnectivitySubscriptionId" - Destination = "Environment" - }) - Value = "" - } - ManagementSubscriptionId = [pscustomobject]@{ - Type = "UserInput" - Description = "The identifier of the Management Subscription. (e.g 00000000-0000-0000-0000-000000000000)" - 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}$" - Targets = @( - [pscustomobject]@{ - Name = "ManagementSubscriptionId" - Destination = "Environment" - }) - Value = "" - } - BillingAccountId = [pscustomobject]@{ - Type = "UserInput" - Description = "The identifier of the Billing Account. (e.g 00000000-0000-0000-0000-000000000000)" - 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}$" - Value = "" - } - EnrollmentAccountId = [pscustomobject]@{ - Type = "UserInput" - Description = "The identifier of the Enrollment Account. (e.g 00000000-0000-0000-0000-000000000000)" - 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}$" - Value = "" - } - LogAnalyticsResourceId = [pscustomobject]@{ - Type = "Computed" - Value = "/subscriptions/{%ManagementSubscriptionId%}/resourcegroups/alz-logging/providers/microsoft.operationalinsights/workspaces/alz-log-analytics" - Targets = @( - [pscustomobject]@{ - Name = "parLogAnalyticsWorkspaceResourceId" - Destination = "Parameters" - }) - } - } -} - diff --git a/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 b/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 index f8e95129..e5d1616e 100644 --- a/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 +++ b/src/ALZ/Private/Request-ALZEnvironmentConfig.ps1 @@ -1,9 +1,15 @@ function Request-ALZEnvironmentConfig { param( - [Parameter(Mandatory = $false)] + [Parameter(Mandatory = $true)] [ValidateSet("bicep", "terraform")] - [string] $alzIacProvider = "bicep" + [string] $alzIacProvider, + + [Parameter(Mandatory = $true)] + [string] $alzEnvironmentDestination, + + [Parameter(Mandatory = $true)] + [string] $alzBicepVersion ) <# .SYNOPSIS @@ -19,9 +25,9 @@ function Request-ALZEnvironmentConfig { throw "Terraform is not yet supported." } - $configuration = Initialize-ConfigurationObject -alzIacProvider $alzIacProvider + $configuration = Get-Configuration -alzIacProvider $alzIacProvider -alzEnvironmentDestination $alzEnvironmentDestination -alzBicepVersion $alzBicepVersion Write-Verbose "Configuration object initialized." - # Write-Verbose "Configuration object: $(ConvertTo-Json $configuration -Depth 10)" + Write-Verbose "Configuration object: $(ConvertTo-Json $configuration -Depth 10)" foreach ($configurationValue in $configuration.PsObject.Properties) { if ($configurationValue.Value.Type -eq "UserInput") { diff --git a/src/ALZ/Public/New-ALZEnvironment.ps1 b/src/ALZ/Public/New-ALZEnvironment.ps1 index ab94bfac..582a83cc 100644 --- a/src/ALZ/Public/New-ALZEnvironment.ps1 +++ b/src/ALZ/Public/New-ALZEnvironment.ps1 @@ -45,12 +45,11 @@ function New-ALZEnvironment { return $false } - $configuration = Request-ALZEnvironmentConfig -alzIacProvider $alzIacProvider - if ($PSCmdlet.ShouldProcess("ALZ-Bicep module configuration", "modify")) { New-ALZDirectoryEnvironment -alzEnvironmentDestination $alzEnvironmentDestination | Out-Null + $assetsDirectory = Join-Path $(Get-ScriptRoot) "../Assets" Copy-Item -Path "$assetsDirectory/*" -Recurse -Destination $alzEnvironmentDestination -Force @@ -62,6 +61,8 @@ function New-ALZEnvironment { Initialize-ALZBicepConfigFile -alzEnvironmentDestination $alzEnvironmentDestination -alzBicepVersion $alzBicepVersion | Out-Null } + $configuration = Request-ALZEnvironmentConfig -alzIacProvider $alzIacProvider -alzEnvironmentDestination $alzEnvironmentDestination -alzBicepVersion $alzBicepVersion + Edit-ALZConfigurationFilesInPlace -alzEnvironmentDestination $alzEnvironmentDestination -configuration $configuration | Out-Null Build-ALZDeploymentEnvFile -configuration $configuration -Destination $alzEnvironmentDestination | Out-Null } diff --git a/src/Tests/Unit/Private/Get-Configuration.Tests.ps1 b/src/Tests/Unit/Private/Get-Configuration.Tests.ps1 new file mode 100644 index 00000000..70182174 --- /dev/null +++ b/src/Tests/Unit/Private/Get-Configuration.Tests.ps1 @@ -0,0 +1,126 @@ +#------------------------------------------------------------------------- +Set-Location -Path $PSScriptRoot +#------------------------------------------------------------------------- +$ModuleName = 'ALZ' +$PathToManifest = [System.IO.Path]::Combine('..', '..', '..', $ModuleName, "$ModuleName.psd1") +#------------------------------------------------------------------------- +if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') { + #if the module is already in memory, remove it + Remove-Module -Name $ModuleName -Force +} +Import-Module $PathToManifest -Force +#------------------------------------------------------------------------- + +InModuleScope 'ALZ' { + Describe 'Get-Configuration Function Tests' -Tag Unit { + BeforeAll { + $WarningPreference = 'SilentlyContinue' + $ErrorActionPreference = 'SilentlyContinue' + } + Context 'Create the correct folders for the environment' { + BeforeEach { + Mock -CommandName Get-Content -MockWith { + ' + { + "Prefix": { + "Type": "UserInput", + "Description": "The prefix that will be added to all resources created by this deployment. (e.g. alz)", + "Targets": [ + { + "Name": "parTopLevelManagementGroupPrefix", + "Destination": "Parameters" + }, + { + "Name": "parCompanyPrefix", + "Destination": "Parameters" + }, + { + "Name": "parTargetManagementGroupId", + "Destination": "Parameters" + }, + { + "Name": "parAssignableScopeManagementGroupId", + "Destination": "Parameters" + } + ], + "Value": "", + "DefaultValue": "alz", + "Valid": "^[a-zA-Z]{3,5}$" + }, + "Suffix": { + "Type": "UserInput", + "Description": "The suffix that will be added to all resources created by this deployment. (e.g. test)", + "Targets": [ + { + "Name": "parTopLevelManagementGroupSuffix", + "Destination": "Parameters" + } + ], + "Value": "", + "DefaultValue": "", + "Valid": "^[a-zA-Z]{0,5}$" + }, + "Location": { + "Type": "UserInput", + "Description": "Deployment location.", + "Value": "", + "Targets": [ + { + "Name": "parLocation", + "Destination": "Parameters" + }, + { + "Name": "parAutomationAccountLocation", + "Destination": "Parameters" + }, + { + "Name": "parLogAnalyticsWorkspaceLocation", + "Destination": "Parameters" + } + ], + "AllowedValues": [ + "eastus", + "ukwest" + ] + }, + "Environment": { + "Type": "UserInput", + "Description": "The Type of environment that will be created. (e.g. dev, test, qa, staging, prod)", + "Targets": [ + { + "Name": "parEnvironment", + "Destination": "Parameters" + } + ], + "Value": "", + "DefaultValue": "prod", + "Valid": "^[a-zA-Z0-9]{2,10}$" + }, + }' + } + } + It 'configuration loads correctly.' { + $content = Get-Configuration + $content.Prefix.Value | Should -Be '' + $content.Prefix.DefaultValue | Should -Be 'alz' + $content.Prefix.Description | Should -Be "The prefix that will be added to all resources created by this deployment. (e.g. alz)" + + $content.Suffix.Value | Should -Be '' + $content.Suffix.DefaultValue | Should -Be '' + $content.Suffix.Description | Should -Be "The suffix that will be added to all resources created by this deployment. (e.g. test)" + + $content.Location.Value | Should -Be '' + $content.Location.Description | Should -Be 'Deployment location.' + $content.Location.AllowedValues | Should -Be @('eastus', 'ukwest') + + $content.Environment.Value | Should -Be '' + $content.Environment.Description | Should -Be "The type of environment that will be created. (e.g. dev, test, qa, staging, prod)" + $content.Environment.DefaultValue | Should -Be 'prod' + } + + It 'Throws for unsupported Terraform IAC' { + { Get-Configuration -alzIacProvider "terraform" } | Should -Throw -ExpectedMessage "Terraform is not yet supported." + } + } + } +} diff --git a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 b/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 deleted file mode 100644 index 92d8cc63..00000000 --- a/src/Tests/Unit/Private/Initialize-ConfigurationObject.Tests.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -#------------------------------------------------------------------------- -Set-Location -Path $PSScriptRoot -#------------------------------------------------------------------------- -$ModuleName = 'ALZ' -$PathToManifest = [System.IO.Path]::Combine('..', '..', '..', $ModuleName, "$ModuleName.psd1") -#------------------------------------------------------------------------- -if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') { - #if the module is already in memory, remove it - Remove-Module -Name $ModuleName -Force -} -Import-Module $PathToManifest -Force -#------------------------------------------------------------------------- - -InModuleScope 'ALZ' { - Describe 'New-ALZDirectoryEnvironment Function Tests' -Tag Unit { - BeforeAll { - $WarningPreference = 'SilentlyContinue' - $ErrorActionPreference = 'SilentlyContinue' - } - Context 'Create the correctr foldes for the environment' { - BeforeEach { - Mock -CommandName Get-AzLocation -MockWith { - @( - [PSCustomObject]@{ - Location = 'ukwest' - }, - [PSCustomObject]@{ - Location = 'eastus' - } - ) - } - } - It 'should return the not met for non AZ module' { - $content = Initialize-ConfigurationObject - $content.Prefix.Value | Should -Be '' - $content.Prefix.DefaultValue | Should -Be 'alz' - $content.Prefix.Description | Should -Be "The prefix that will be added to all resources created by this deployment. (e.g. 'alz')" - # $content.Prefix.Names | Should -Be @('parTopLevelManagementGroupPrefix', 'parCompanyPrefix', 'parTargetManagementGroupId', 'parAssignableScopeManagementGroupId') - - $content.Suffix.Value | Should -Be '' - $content.Suffix.DefaultValue | Should -Be '' - $content.Suffix.Description | Should -Be "The suffix that will be added to all resources created by this deployment. (e.g. 'test')" - # $content.Suffix.Names | Should -Be @('parTopLevelManagementGroupSuffix') - - $content.Location.Value | Should -Be '' - $content.Location.Description | Should -Be 'Deployment location.' - # $content.Location.Names | Should -Be @("parLocation", "parAutomationAccountLocation", "parLogAnalyticsWorkspaceLocation") - $content.Location.AllowedValues | Should -Be @('eastus', 'ukwest') - - $content.Environment.Value | Should -Be '' - $content.Environment.Description | Should -Be "The type of environment that will be created. (e.g. 'dev', 'test', 'qa', 'staging', 'prod')" - # $content.Environment.Namkes | Should -Be @('parEnvironment') - $content.Environment.DefaultValue | Should -Be 'prod' - } - - It 'Throws for unsupported Terraform IAC' { - { Initialize-ConfigurationObject -alzIacProvider "terraform" } | Should -Throw -ExpectedMessage "Terraform is not yet supported." - } - } - } -} diff --git a/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 b/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 index 99c255cd..b622a8b4 100644 --- a/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 +++ b/src/Tests/Unit/Private/Request-ALZEnvironmentConfig.Tests.ps1 @@ -20,7 +20,7 @@ InModuleScope 'ALZ' { Context 'Request-ALZEnvironmentConfig should request CLI input for configuration.' { It 'Based on the configuration object' { - Mock -CommandName Initialize-ConfigurationObject -MockWith { + Mock -CommandName Get-Configuration -MockWith { [pscustomobject]@{ Setting1 = [pscustomobject]@{ Type = "UserInput" @@ -37,13 +37,13 @@ InModuleScope 'ALZ' { Mock -CommandName Request-ConfigurationValue - Request-ALZEnvironmentConfig + Request-ALZEnvironmentConfig -alzIacProvider "bicep" -alzEnvironmentDestination "." -alzBicepVersion "v0.13.0" Should -Invoke Request-ConfigurationValue -Scope It -Times 2 -Exactly } It 'Throws if the unsupported Terraform IAC is specified.' { - { Request-ALZEnvironmentConfig -alzIacProvider "terraform" } | Should -Throw -ExpectedMessage "Terraform is not yet supported." + { Request-ALZEnvironmentConfig -alzIacProvider "terraform" -alzEnvironmentDestination "." -alzBicepVersion "v0.13.0" } | Should -Throw -ExpectedMessage "Terraform is not yet supported." } } }