diff --git a/.azuredevops/pipelineTemplates/jobs.validateModuleDeployment.yml b/.azuredevops/pipelineTemplates/jobs.validateModuleDeployment.yml index 66901fb038..9e31481666 100644 --- a/.azuredevops/pipelineTemplates/jobs.validateModuleDeployment.yml +++ b/.azuredevops/pipelineTemplates/jobs.validateModuleDeployment.yml @@ -162,35 +162,47 @@ jobs: pwsh: true inline: | # Load used functions - . (Join-Path '$(moduleRepoRoot)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path '$(moduleRepoRoot)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Load Settings File - $Settings = Get-Content -Path (Join-Path '$(moduleRepoRoot)' 'settings.json') | ConvertFrom-Json - - # Initialize Default Parameter File Tokens - $DefaultParameterFileTokens = @( - @{ Name = 'resourceGroupName'; Value = '${{ parameters.resourceGroupName }}' } - @{ Name = 'subscriptionId'; Value = '${{ parameters.subscriptionId }}' } - @{ Name = 'managementGroupId'; Value = '${{ parameters.managementGroupId }}' } - @{ Name = "tenantId"; Value = '$(ARM_TENANT_ID)' } - @{ Name = "deploymentSpId"; Value = '$(DEPLOYMENT_SP_ID)' } - ) | ForEach-Object { [PSCustomObject]$PSItem } - - # Get additional Custom Parameter File Tokens from input - Write-Verbose 'Additional Custom Parameter File Tokens: ${{ deploymentBlock.customParameterFileTokens }}' -Verbose - $OtherCustomParameterFileTokens = '${{ deploymentBlock.customParameterFileTokens }}' | ConvertFrom-Json + $Settings = Get-Content -Path (Join-Path '$(moduleRepoRoot)' 'settings.json') | ConvertFrom-Json -AsHashTable # Construct Token Function Input $ConvertTokensInputs = @{ - ParameterFilePath = Join-Path '$(parametersRepoRoot)' '${{ deploymentBlock.path }}' - DefaultParameterFileTokens = $DefaultParameterFileTokens - OtherCustomParameterFileTokens = $OtherCustomParameterFileTokens - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + Tokens = @{} + FilePath = Join-Path '$(parametersRepoRoot)' '${{ deploymentBlock.path }}' + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + } + + # Add defaults + $ConvertTokensInputs.Tokens += @{ + resourceGroupName = '${{ parameters.resourceGroupName }}' + subscriptionId = '${{ parameters.subscriptionId }}' + managementGroupId = '${{ parameters.managementGroupId }}' + tenantId = '$(ARM_TENANT_ID)' + deploymentSpId = '$(DEPLOYMENT_SP_ID)' + } + + # Add local tokens + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens += $tokenMap } + + # Add custom tokens (passed in via the pipeline) + if(-not [String]::IsNullOrEmpty('${{ deploymentBlock.customParameterFileTokens }}')) { + $customTokens = '${{ deploymentBlock.customParameterFileTokens }}' | ConvertFrom-Json -AsHashTable + Write-Verbose ('Using custom parameter file tokens [{0}]' -f ($customTokens.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens += $customTokens + } + # Invoke Token Replacement Functionality - $null = Convert-TokensInParameterFile @ConvertTokensInputs -Verbose + $null = Convert-TokensInFile @ConvertTokensInputs # [Validation] task(s) #--------------------- diff --git a/.azuredevops/platformPipelines/platform.dependencies.yml b/.azuredevops/platformPipelines/platform.dependencies.yml index 755b70a2be..e6b7c4321f 100644 --- a/.azuredevops/platformPipelines/platform.dependencies.yml +++ b/.azuredevops/platformPipelines/platform.dependencies.yml @@ -186,21 +186,30 @@ stages: azureSubscription: $(serviceConnection) ScriptType: 'InlineScript' Inline: | - $parameterFilePath = Join-Path '$(Build.SourcesDirectory)' '$(dependencyPath)' '$(resourceType)' 'parameters' 'parameters.json' # Load used functions . (Join-Path '$(Build.SourcesDirectory)' 'utilities' 'pipelines' 'sharedScripts' 'Export-ContentToBlob.ps1') - . (Join-Path '$(Build.SourcesDirectory)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path '$(Build.SourcesDirectory)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Replace tokens in parameter file - $Settings = Get-Content -Path (Join-Path '$(Build.SourcesDirectory)' 'settings.json') | ConvertFrom-Json + $Settings = Get-Content -Path (Join-Path '$(Build.SourcesDirectory)' 'settings.json') | ConvertFrom-Json -AsHashTable $ConvertTokensInputs = @{ - ParameterFilePath = $parameterFilePath - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + FilePath = $parameterFilePath + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + } + + # Add local tokens + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens = $tokenMap } - $null = Convert-TokensInParameterFile @ConvertTokensInputs -Verbose + + $null = Convert-TokensInFile @ConvertTokensInputs # Get storage account name $storageAccountParameters = (ConvertFrom-Json (Get-Content -path $parameterFilePath -Raw)).parameters @@ -307,14 +316,21 @@ stages: Inline: | # Load used functions - . (Join-Path '$(Build.SourcesDirectory)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path '$(Build.SourcesDirectory)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Prepare replace tokens in parameter file - $Settings = Get-Content -Path (Join-Path '$(Build.SourcesDirectory)' 'settings.json') | ConvertFrom-Json + $Settings = Get-Content -Path (Join-Path '$(Build.SourcesDirectory)' 'settings.json') | ConvertFrom-Json -AsHashTable $ConvertTokensInputs = @{ - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + } + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens = $tokenMap } # Retrieving parameters from previous job outputs and parameter files @@ -324,12 +340,12 @@ stages: Write-Verbose "Retrieving parameters from storage account parameter files" -Verbose $parameterFilePath = Join-Path '$(Build.SourcesDirectory)' '$(dependencyPath)' '$(saResourceType)' 'parameters' 'parameters.json' - $null = Convert-TokensInParameterFile @ConvertTokensInputs -ParameterFilePath $parameterFilePath -Verbose + $null = Convert-TokensInFile @ConvertTokensInputs -FilePath $parameterFilePath -Verbose $storageAccountParameters = (ConvertFrom-Json (Get-Content -path $parameterFilePath -Raw)).parameters Write-Verbose "Retrieving parameters from image template parameter files" -Verbose $parameterFilePath = Join-Path '$(Build.SourcesDirectory)' '$(dependencyPath)' '$(resourceType)' 'parameters' 'parameters.json' - $null = Convert-TokensInParameterFile @ConvertTokensInputs -ParameterFilePath $parameterFilePath -Verbose + $null = Convert-TokensInFile @ConvertTokensInputs -FilePath $parameterFilePath -Verbose $imageTemplateParameters = (ConvertFrom-Json (Get-Content -path $parameterFilePath -Raw)).parameters # Initializing parameters before the blob copy @@ -540,7 +556,7 @@ stages: - path: $(dependencyPath)/$(resourceType)/parameters/parameters.json templateFilePath: $(templateFilePath) displayName: Default recovery services vault - customParameterFileTokens: '[{"Name":"msiPrincipalId","Value":"$(msiPrincipalId)"}]' + customParameterFileTokens: '{"msiPrincipalId":"$(msiPrincipalId)"}' - stage: deploy_kv displayName: Deploy key vaults @@ -561,17 +577,17 @@ stages: templateFilePath: $(templateFilePath) displayName: Default Key Vault jobName: default_kv - customParameterFileTokens: '[{"Name":"msiPrincipalId","Value":"$(msiPrincipalId)"}]' + customParameterFileTokens: '{"msiPrincipalId":"$(msiPrincipalId)"}' - path: $(dependencyPath)/$(resourceType)/parameters/pe.parameters.json templateFilePath: $(templateFilePath) displayName: Private Endpoint Key Vault - customParameterFileTokens: '[{"Name":"msiPrincipalId","Value":"$(msiPrincipalId)"}]' + customParameterFileTokens: '{"msiPrincipalId":"$(msiPrincipalId)"}' - ${{ if eq( parameters.deploySqlMiDependencies, true) }}: - path: $(dependencyPath)/$(resourceType)/parameters/sqlmi.parameters.json templateFilePath: $(templateFilePath) displayName: SQLMI key vault jobName: sqlmi_kv - customParameterFileTokens: '[{"Name":"msiPrincipalId","Value":"$(msiPrincipalId)"}]' + customParameterFileTokens: '{"msiPrincipalId":"$(msiPrincipalId)"}' - job: displayName: Set key vault secrets keys and certificates dependsOn: @@ -604,20 +620,26 @@ stages: azureSubscription: $(serviceConnection) ScriptType: 'InlineScript' Inline: | - $parameterFilePath = Join-Path '$(Build.SourcesDirectory)' '$(dependencyPath)' '$(resourceType)' 'parameters' 'parameters.json' # Load used functions - . (Join-Path '$(Build.SourcesDirectory)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path '$(Build.SourcesDirectory)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Replace tokens in parameter file - $Settings = Get-Content -Path (Join-Path '$(Build.SourcesDirectory)' 'settings.json') | ConvertFrom-Json + $Settings = Get-Content -Path (Join-Path '$(Build.SourcesDirectory)' 'settings.json') | ConvertFrom-Json -AsHashTable $ConvertTokensInputs = @{ - ParameterFilePath = $parameterFilePath - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + FilePath = $parameterFilePath + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix } - $null = Convert-TokensInParameterFile @ConvertTokensInputs -Verbose + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens = $tokenMap + } + $null = Convert-TokensInFile @ConvertTokensInputs # Get key vault name $keyVaultParameters = (ConvertFrom-Json (Get-Content -Path $parameterFilePath -Raw)).parameters @@ -704,17 +726,24 @@ stages: $parameterFilePath = Join-Path '$(Build.SourcesDirectory)' '$(dependencyPath)' '$(resourceType)' 'parameters' 'sqlmi.parameters.json' # Load used functions - . (Join-Path '$(Build.SourcesDirectory)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path '$(Build.SourcesDirectory)' 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Replace tokens in parameter file - $Settings = Get-Content -Path (Join-Path '$(Build.SourcesDirectory)' 'settings.json') | ConvertFrom-Json + $Settings = Get-Content -Path (Join-Path '$(Build.SourcesDirectory)' 'settings.json') | ConvertFrom-Json -AsHashTable $ConvertTokensInputs = @{ - ParameterFilePath = $parameterFilePath - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + FilePath = $parameterFilePath + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + } + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens = $tokenMap } - $null = Convert-TokensInParameterFile @ConvertTokensInputs -Verbose + $null = Convert-TokensInFile @ConvertTokensInputs # Get key vault name $keyVaultParameters = (ConvertFrom-Json (Get-Content -Path $parameterFilePath -Raw)).parameters @@ -777,7 +806,7 @@ stages: - path: $(dependencyPath)/$(resourceType)/parameters/parameters.json templateFilePath: $(templateFilePath) displayName: MSI Role Assignment - customParameterFileTokens: '[{"Name":"msiPrincipalId","Value":"$(msiPrincipalId)"}]' + customParameterFileTokens: '{"msiPrincipalId":"$(msiPrincipalId)"}' - stage: deploy_vnet displayName: Deploy virtual networks diff --git a/.github/actions/templates/validateModuleDeployment/action.yml b/.github/actions/templates/validateModuleDeployment/action.yml index 956635b4d9..92acf6c86b 100644 --- a/.github/actions/templates/validateModuleDeployment/action.yml +++ b/.github/actions/templates/validateModuleDeployment/action.yml @@ -21,7 +21,7 @@ inputs: description: 'The managementGroupId to deploy to' required: false customParameterFileTokens: - description: 'Additional parameter file token pairs in json format. e.g. [{"Name":"tokenName","Value":"tokenValue"}]' + description: 'Additional parameter file token pairs in json format. e.g. {"tokenName":"tokenValue"}' required: false removeDeployment: description: 'Set "true" to set module up for removal' @@ -76,34 +76,47 @@ runs: shell: pwsh run: | # Load used functions - . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Load Settings File - $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json - # Initialize Default Parameter File Tokens - $DefaultParameterFileTokens = @( - @{ Name = 'resourceGroupName'; Value = '${{ inputs.resourceGroupName }}' } - @{ Name = 'subscriptionId'; Value = '${{ inputs.subscriptionId }}' } - @{ Name = 'managementGroupId'; Value = '${{ inputs.managementGroupId }}' } - @{ Name = "tenantId"; Value = '${{ env.ARM_TENANT_ID }}' } - @{ Name = "deploymentSpId"; Value = '${{ env.DEPLOYMENT_SP_ID }}' } - ) | ForEach-Object { [PSCustomObject]$PSItem } - - # Get additional Custom Parameter File Tokens from input - Write-Verbose 'Additional Custom Parameter File Tokens: ${{ inputs.customParameterFileTokens }}' -Verbose - $OtherCustomParameterFileTokens = '${{ inputs.customParameterFileTokens }}' | ConvertFrom-Json + $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json -AsHashTable # Construct Token Function Input $ConvertTokensInputs = @{ - ParameterFilePath = '${{ inputs.parameterFilePath }}' - DefaultParameterFileTokens = $DefaultParameterFileTokens - OtherCustomParameterFileTokens = $OtherCustomParameterFileTokens - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + Tokens = @{} + FilePath = '${{ inputs.parameterFilePath }}' + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix } + + # Local tokens + $ConvertTokensInputs.Tokens += @{ + resourceGroupName = '${{ inputs.resourceGroupName }}' + subscriptionId = '${{ inputs.subscriptionId }}' + managementGroupId = '${{ inputs.managementGroupId }}' + tenantId = '${{ env.ARM_TENANT_ID }}' + deploymentSpId = '${{ env.DEPLOYMENT_SP_ID }}' + } + + # Add local tokens + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens += $tokenMap + } + + # Add custom tokens (passed in via the pipeline) + if(-not [String]::IsNullOrEmpty('${{ inputs.customParameterFileTokens }}')) { + $customTokens = '${{ inputs.customParameterFileTokens }}' | ConvertFrom-Json -AsHashTable + Write-Verbose ('Using custom parameter file tokens [{0}]' -f ($customTokens.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens += $customTokens + } + # Invoke Token Replacement Functionality - $null = Convert-TokensInParameterFile @ConvertTokensInputs -Verbose + $null = Convert-TokensInFile @ConvertTokensInputs # [Deployment validation] task(s) # ------------------------------- diff --git a/.github/workflows/platform.dependencies.yml b/.github/workflows/platform.dependencies.yml index 8cf071085f..0a1619f510 100644 --- a/.github/workflows/platform.dependencies.yml +++ b/.github/workflows/platform.dependencies.yml @@ -243,21 +243,31 @@ jobs: uses: azure/powershell@v1 with: inlineScript: | - $parameterFilePath = Join-Path $env:GITHUB_WORKSPACE '${{ env.dependencyPath }}' '${{ env.namespace }}' 'parameters' 'parameters.json' # Load used functions . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'sharedScripts' 'Export-ContentToBlob.ps1') - . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Replace tokens in parameter file - $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json + $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json -AsHashTable $ConvertTokensInputs = @{ - ParameterFilePath = $parameterFilePath - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + FilePath = $parameterFilePath + Tokens = @{} + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + } + + # Add local tokens + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens += $tokenMap } - $null = Convert-TokensInParameterFile @ConvertTokensInputs -Verbose + + $null = Convert-TokensInFile @ConvertTokensInputs # Get storage account name $storageAccountParameters = (ConvertFrom-Json (Get-Content -path $parameterFilePath -Raw)).parameters @@ -398,16 +408,25 @@ jobs: uses: azure/powershell@v1 with: inlineScript: | - # Load used functions - . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Prepare replace tokens in parameter file - $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json + $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json -AsHashTable $ConvertTokensInputs = @{ - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + Tokens = @{} + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + } + + # Add local tokens + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens += $tokenMap } # Retrieving parameters from previous job outputs and parameter files @@ -417,12 +436,12 @@ jobs: Write-Verbose "Retrieving parameters from storage account parameter files" -Verbose $parameterFilePath = Join-Path $env:GITHUB_WORKSPACE '${{ env.dependencyPath }}' '${{ env.saNamespace }}' 'parameters' 'parameters.json' - $null = Convert-TokensInParameterFile @ConvertTokensInputs -ParameterFilePath $parameterFilePath -Verbose + $null = Convert-TokensInFile @ConvertTokensInputs -FilePath $parameterFilePath -Verbose $storageAccountParameters = (ConvertFrom-Json (Get-Content -path $parameterFilePath -Raw)).parameters Write-Verbose "Retrieving parameters from image template parameter files" -Verbose $parameterFilePath = Join-Path $env:GITHUB_WORKSPACE '${{ env.dependencyPath }}' '${{ env.imgtNamespace }}' 'parameters' 'parameters.json' - $null = Convert-TokensInParameterFile @ConvertTokensInputs -ParameterFilePath $parameterFilePath -Verbose + $null = Convert-TokensInFile @ConvertTokensInputs -FilePath $parameterFilePath -Verbose $imageTemplateParameters = (ConvertFrom-Json (Get-Content -path $parameterFilePath -Raw)).parameters # Initializing parameters before the blob copy @@ -782,7 +801,7 @@ jobs: subscriptionId: '${{ secrets.ARM_SUBSCRIPTION_ID }}' managementGroupId: '${{ secrets.ARM_MGMTGROUP_ID }}' removeDeployment: '${{ env.removeDeployment }}' - customParameterFileTokens: '[{"Name":"msiPrincipalId","Value":"${{ needs.job_deploy_msi.outputs.msiPrincipalId }}"}]' + customParameterFileTokens: '{"msiPrincipalId":"${{ needs.job_deploy_msi.outputs.msiPrincipalId }}"}' job_deploy_kv: runs-on: ubuntu-20.04 @@ -813,7 +832,7 @@ jobs: subscriptionId: '${{ secrets.ARM_SUBSCRIPTION_ID }}' managementGroupId: '${{ secrets.ARM_MGMTGROUP_ID }}' removeDeployment: '${{ env.removeDeployment }}' - customParameterFileTokens: '[{"Name":"msiPrincipalId","Value":"${{ needs.job_deploy_msi.outputs.msiPrincipalId }}"}]' + customParameterFileTokens: '{"msiPrincipalId":"${{ needs.job_deploy_msi.outputs.msiPrincipalId }}"}' job_deploy_kv_secrets: runs-on: ubuntu-20.04 @@ -852,17 +871,25 @@ jobs: $parameterFilePath = Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'dependencies' '${{ env.namespace }}' 'parameters' 'parameters.json' # Load used functions - . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Replace tokens in parameter file - $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json + $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json -AsHashTable $ConvertTokensInputs = @{ - ParameterFilePath = $parameterFilePath - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + FilePath = $parameterFilePath + Tokens = @{} + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + } + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens += $tokenMap } - $null = Convert-TokensInParameterFile @ConvertTokensInputs -Verbose + $null = Convert-TokensInFile @ConvertTokensInputs # Get key vault name $keyVaultParameters = (ConvertFrom-Json (Get-Content -Path $parameterFilePath -Raw)).parameters @@ -941,7 +968,7 @@ jobs: subscriptionId: '${{ secrets.ARM_SUBSCRIPTION_ID }}' managementGroupId: '${{ secrets.ARM_MGMTGROUP_ID }}' removeDeployment: '${{ env.removeDeployment }}' - customParameterFileTokens: '[{"Name":"msiPrincipalId","Value":"${{ needs.job_deploy_msi.outputs.msiPrincipalId }}"}]' + customParameterFileTokens: '{"msiPrincipalId":"${{ needs.job_deploy_msi.outputs.msiPrincipalId }}"}' job_deploy_sqlmi_kv_secrets: runs-on: ubuntu-20.04 @@ -979,20 +1006,30 @@ jobs: uses: azure/powershell@v1 with: inlineScript: | - $parameterFilePath = Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'dependencies' '${{ env.namespace }}' 'parameters' 'sqlmi.parameters.json' # Load used functions - . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path $env:GITHUB_WORKSPACE 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') # Replace tokens in parameter file - $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json + $Settings = Get-Content -Path "settings.json" | ConvertFrom-Json -AsHashTable $ConvertTokensInputs = @{ - ParameterFilePath = $parameterFilePath - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + FilePath = $parameterFilePath + Tokens = @{} + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix } - $null = Convert-TokensInParameterFile @ConvertTokensInputs -Verbose + + # Add local tokens + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) -Verbose + $ConvertTokensInputs.Tokens += $tokenMap + } + + $null = Convert-TokensInFile @ConvertTokensInputs # Get key vault name $keyVaultParameters = (ConvertFrom-Json (Get-Content -Path $parameterFilePath -Raw)).parameters @@ -1077,7 +1114,7 @@ jobs: subscriptionId: '${{ secrets.ARM_SUBSCRIPTION_ID }}' managementGroupId: '${{ secrets.ARM_MGMTGROUP_ID }}' removeDeployment: '${{ env.removeDeployment }}' - customParameterFileTokens: '[{"Name":"msiPrincipalId","Value":"${{ needs.job_deploy_msi.outputs.msiPrincipalId }}"}]' + customParameterFileTokens: '{"msiPrincipalId":"${{ needs.job_deploy_msi.outputs.msiPrincipalId }}"}' job_deploy_vnet: runs-on: ubuntu-20.04 diff --git a/docs/wiki/UtilitiesTestModuleLocally.md b/docs/wiki/UtilitiesTestModuleLocally.md index 7fc200b269..2917f6c836 100644 --- a/docs/wiki/UtilitiesTestModuleLocally.md +++ b/docs/wiki/UtilitiesTestModuleLocally.md @@ -20,15 +20,14 @@ You can find the script under `/utilities/tools/Test-ModuleLocally.ps1` If the switch for pester tests (`-PesterTest`) was provided the script will 1. Invoke the global module test for the provided template file path and run all tests for it -If the switch for either the validation test (`-ValidationTest`) or deployment test (`-DeploymentTest`) was provided alongside a hashtable for the token replacement (`-ValidateOrDeployParameters`), the script will -1. Search all parameter files for the given module template -1. Craft a dictionary to replace all tokens in these parameter files with actual values. This dictionary will consist +If the switch for either the validation test (`-ValidationTest`) or deployment test (`-DeploymentTest`) was provided alongside a HashTable for the token replacement (`-ValidateOrDeployParameters`), the script will +1. either fetch all parameter files of the module's parameter folder (default) or you can specify a single parameter file by leveraging the `parameterFilePath` parameter instead. +1. Create a dictionary to replace all tokens in these parameter files with actual values. This dictionary will consist - of the subscriptionID & managementGroupID of the provided `ValidateOrDeployParameters` object, - add all key-value pairs of the `-AdditionalTokens` object to it, - - and optionally also add all key-value pairs specified in the `settings.json`'s `parameterFileTokens` object if the `-SkipParameterFileTokens` parameter was not set -1. It replaces all tokens in the parameter files as per the object created in the previous step + - and optionally also add all key-value pairs specified in the `settings.json`'s `parameterFileTokens` object 1. If the `-ValidationTest` parameter was set, it runs a deployment validation using the `Test-TemplateWithParameterFile` script -1. If the `-DeploymentTest` parameter was set, it runs a deployment using the `New-ModuleDeployment` script (with no retries). By default it uses the standard `parameters.json` parameter file. However, if the switch `-DeployAllModuleParameterFiles` was set, it runs the deployment for all parameter files in the module's `.parameters` folder +1. If the `-DeploymentTest` parameter was set, it runs a deployment using the `New-ModuleDeployment` script (with no retries). 1. As a final step it rolls the parameter files back to their original state if either the `-ValidationTest` or `-DeploymentTest` parameters were provided. # How to use it diff --git a/settings.json b/settings.json index 6f05750ecd..6db8ca684d 100644 --- a/settings.json +++ b/settings.json @@ -2,16 +2,14 @@ "parameterFileTokens": { "tokenPrefix": "<<", "tokenSuffix": ">>", - "localTokens": { - "tokens": [ - { - "name": "namePrefix", - "value": "sxx", - "metadata": { - "description": "A 3-5 character length string, included in the resources names" - } + "localTokens": [ + { + "name": "namePrefix", + "value": "sxx", + "metadata": { + "description": "A 3-4 character length string, included in the resources names" } - ] - } + } + ] } } diff --git a/utilities/pipelines/resourceRemoval/helper/Get-DependencyResourceNameList.ps1 b/utilities/pipelines/resourceRemoval/helper/Get-DependencyResourceNameList.ps1 index 9280ac244b..5d9ac940e9 100644 --- a/utilities/pipelines/resourceRemoval/helper/Get-DependencyResourceNameList.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Get-DependencyResourceNameList.ps1 @@ -24,7 +24,7 @@ function Get-DependencyResourceNameList { # Load used function $repoRootPath = (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName - . (Join-Path $repoRootPath 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInParameterFile.ps1') + . (Join-Path $repoRootPath 'utilities' 'pipelines' 'tokensReplacement' 'Convert-TokensInFile.ps1') $parameterFolders = Get-ChildItem -Path $dependencyParameterPath -Recurse -Filter 'parameters' -Directory $parameterFilePaths = [System.Collections.ArrayList]@() @@ -33,16 +33,26 @@ function Get-DependencyResourceNameList { } # Replace tokens in dependency parameter files - $Settings = Get-Content -Path (Join-Path $repoRootPath 'settings.json') | ConvertFrom-Json - foreach ($parameterFilePath in $parameterFilePaths) { - $ConvertTokensInputs = @{ - ParameterFilePath = $parameterFilePath - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix - Verbose = $false + $Settings = Get-Content -Path (Join-Path $repoRootPath 'settings.json') | ConvertFrom-Json -AsHashtable + + # Add local tokens + if ($Settings.parameterFileTokens.localTokens) { + $tokenMap = @{} + foreach ($token in $Settings.parameterFileTokens.localTokens) { + $tokenMap += @{ $token.name = $token.value } + } + Write-Verbose ('Using local tokens [{0}]' -f ($tokenMap.Keys -join ', ')) + + foreach ($parameterFilePath in $parameterFilePaths) { + $ConvertTokensInputs = @{ + FilePath = $parameterFilePath + Tokens = $tokenMap + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + Verbose = $false + } + $null = Convert-TokensInFile @ConvertTokensInputs } - $null = Convert-TokensInParameterFile @ConvertTokensInputs } $dependencyResourceNames = [System.Collections.ArrayList]@() diff --git a/utilities/pipelines/tokensReplacement/Convert-TokensInParameterFile.ps1 b/utilities/pipelines/tokensReplacement/Convert-TokensInFile.ps1 similarity index 50% rename from utilities/pipelines/tokensReplacement/Convert-TokensInParameterFile.ps1 rename to utilities/pipelines/tokensReplacement/Convert-TokensInFile.ps1 index 83399d51e5..8611d71c7f 100644 --- a/utilities/pipelines/tokensReplacement/Convert-TokensInParameterFile.ps1 +++ b/utilities/pipelines/tokensReplacement/Convert-TokensInFile.ps1 @@ -5,14 +5,11 @@ This Function Helps with Testing A Module Locally .DESCRIPTION This Function aggregates all the different token types (Default and Local) and then passes them to the Convert Tokens Script to replace tokens in a parameter file -.PARAMETER ParameterFilePath -Mandatory. The Path to the Parameter File that contains tokens to be replaced. +.PARAMETER FilePath +Mandatory. The Path to the file that contains tokens to be replaced. -.PARAMETER DefaultParameterFileTokens -Optional. An object containing the default parameter file tokens that are always available. - -.PARAMETER LocalCustomParameterFileTokens -Optional. An object containing the local parameter file tokens to be injected for replacement +.PARAMETER Tokens +Mandatory. An object containing the parameter file tokens to set .PARAMETER TokenPrefix Mandatory. The prefix used to identify a token in the parameter file (i.e. <<) @@ -20,9 +17,6 @@ Mandatory. The prefix used to identify a token in the parameter file (i.e. <<) .PARAMETER TokenSuffix Mandatory. The suffix used to identify a token in the parameter file (i.e. >>) -.PARAMETER OtherCustomParameterFileTokens -Optional. An object containing other optional tokens that are to be replaced in the parameter file (used for testing) - .PARAMETER SwapValueWithName Optional. A boolean that enables the search for the original value and replaces it with a token. Used to revert configuration. Default is false @@ -39,68 +33,56 @@ Optional. A string for a custom output directory of the modified parameter file - If providing TokenKeyVaultName parameter, ensure you have read access to secrets in the key vault to be able to retrieve the tokens. #> -function Convert-TokensInParameterFile { +function Convert-TokensInFile { [CmdletBinding()] param ( [parameter(Mandatory = $true)] - [string]$ParameterFilePath, - - [parameter(Mandatory = $false)] - [psobject]$DefaultParameterFileTokens, - - [parameter(Mandatory = $false)] - [psobject]$LocalCustomParameterFileTokens, + [string] $FilePath, [parameter(Mandatory = $true)] - [string]$TokenPrefix, + [hashtable] $Tokens, [parameter(Mandatory = $true)] - [string]$TokenSuffix, + [string] $TokenPrefix, - [parameter(Mandatory = $false)] - [psobject]$OtherCustomParameterFileTokens, + [parameter(Mandatory = $true)] + [string] $TokenSuffix, [parameter(Mandatory = $false)] - [bool]$SwapValueWithName = $false, + [bool] $SwapValueWithName = $false, [parameter(Mandatory = $false)] - [string]$OutputDirectory + [string] $OutputDirectory ) begin { # Load used funtions . (Join-Path $PSScriptRoot './helper/Convert-TokenInFile.ps1') - $AllCustomParameterFileTokens = @() } process { - Write-Verbose "Default Tokens Count: ($($DefaultParameterFileTokens.Count)) Tokens (From Input Parameter)" - ## Get Local Custom Parameter File Tokens (Should not Contain Sensitive Information) - Write-Verbose "Local Custom Tokens Count: ($($LocalCustomParameterFileTokens.Count)) Tokens (From Settings File)" - $AllCustomParameterFileTokens += ($LocalCustomParameterFileTokens | Select-Object -Property Name, Value) - # Combine All Input Token Types, Remove Duplicates and Only Select Name, Value if they contain other unrequired properties - $AllCustomParameterFileTokens = $DefaultParameterFileTokens + $LocalCustomParameterFileTokens | - ForEach-Object { [PSCustomObject]$PSItem } | - Sort-Object Name -Unique | - Select-Object -Property Name, Value | - Where-Object { $null -ne $PSitem.Name -and $null -ne $PSitem.Value } - - # Other Custom Parameter File Tokens (Can be used for testing) - if ($OtherCustomParameterFileTokens) { - $AllCustomParameterFileTokens += $OtherCustomParameterFileTokens | ForEach-Object { [PSCustomObject]$PSItem } + # Combine All Input Token Types, Remove Duplicates and Only Select entries with on empty values + $FilteredTokens = ($Tokens | Sort-Object -Unique).Clone() + @($FilteredTokens.Keys) | ForEach-Object { + if ([String]::IsNullOrEmpty($FilteredTokens[$_])) { + $FilteredTokens.Remove($_) + } } - Write-Verbose ("All Parameter File Tokens Count: ($($AllCustomParameterFileTokens.Count))") + Write-Verbose ('Using [{0}] tokens' -f $FilteredTokens.Keys.Count) + # Apply Prefix and Suffix to Tokens and Prepare Object for Conversion Write-Verbose ("Applying Token Prefix '$TokenPrefix' and Token Suffix '$TokenSuffix'") - foreach ($ParameterFileToken in $AllCustomParameterFileTokens) { - $ParameterFileToken.Name = -join ($TokenPrefix, $ParameterFileToken.Name, $TokenSuffix) + foreach ($Token in @($FilteredTokens.Keys)) { + $newKey = -join ($TokenPrefix, $Token, $TokenSuffix) + $FilteredTokens[$newKey] = $FilteredTokens[$Token] # Add formatted entry + $FilteredTokens.Remove($Token) # Replace original } # Convert Tokens in Parameter Files try { # Prepare Input to Token Converter Function $ConvertTokenListFunctionInput = @{ - FilePath = $ParameterFilePath - TokenNameValueObject = $AllCustomParameterFileTokens + FilePath = $FilePath + TokenNameValueObject = $FilteredTokens SwapValueWithName = $SwapValueWithName } if ($OutputDirectory) { @@ -116,6 +98,6 @@ function Convert-TokensInParameterFile { } end { Write-Verbose "Token Replacement Status: $ConversionStatus" - return [bool]$ConversionStatus + return [bool] $ConversionStatus } } diff --git a/utilities/pipelines/tokensReplacement/helper/Convert-TokenInFile.ps1 b/utilities/pipelines/tokensReplacement/helper/Convert-TokenInFile.ps1 index 53689d407d..bb7be0eb72 100644 --- a/utilities/pipelines/tokensReplacement/helper/Convert-TokenInFile.ps1 +++ b/utilities/pipelines/tokensReplacement/helper/Convert-TokenInFile.ps1 @@ -53,10 +53,10 @@ function Convert-TokenInFile { # Swap Value with Name instead if ($SwapValueWithName) { Write-Verbose "Swapping 'Value' with 'Name'" - $TokenNameValueObject | ForEach-Object { - $Name = $PSitem.Value - $Value = $PSItem.Name - $PSitem.Name = $Name; $PSitem.Value = $Value + @($TokenNameValueObject.Keys) | ForEach-Object { + $newKey = $TokenNameValueObject[$_] + $TokenNameValueObject[$newKey] = $_ # Add swapped entry + $TokenNameValueObject.Remove($_) # Remove original } } # Begin the Replace Function @@ -71,14 +71,13 @@ function Convert-TokenInFile { } Write-Verbose "Processing Tokens for file: $FileName" # Perform the Replace of Tokens in the File - $TokenNameValueObject | - ForEach-Object { - # If type is secure string - if (($PSItem.Value | Get-Member -MemberType Property | Select-Object -ExpandProperty 'TypeName') -eq 'System.Security.SecureString') { - $PSItem.Value = $PSItem.Value | ConvertFrom-SecureString -AsPlainText - } - $File = $File -replace $PSItem.Name, $PSItem.Value + $TokenNameValueObject.Keys | ForEach-Object { + # If type is secure string + if ($TokenNameValueObject[$_] -is [System.Security.SecureString]) { + $TokenNameValueObject[$_] = $TokenNameValueObject[$_] | ConvertFrom-SecureString -AsPlainText } + $File = $File -replace $_, $TokenNameValueObject[$_] + } # Set Content if ($OutputDirectory -and (Test-Path -Path $OutputDirectory -PathType Container)) { # If Specific Output Directory Provided diff --git a/utilities/tools/Test-ModuleLocally.ps1 b/utilities/tools/Test-ModuleLocally.ps1 index 80f4ab52a0..bd4753d56d 100644 --- a/utilities/tools/Test-ModuleLocally.ps1 +++ b/utilities/tools/Test-ModuleLocally.ps1 @@ -1,142 +1,145 @@  <# .SYNOPSIS -This Function Helps with Testing A Module Locally +This function helps with testing a module locally .DESCRIPTION -This Function Helps with Testing A Module Locally. Use this Function To perform Pester Testing for a Module and then attempting to deploy it. It Also allows you to use your own -subscription Id, Principal Id, tenant ID and other parameters that need to be tokenized. +This function helps with testing a module locally. Use this function To perform Pester testing for a module and then attempting to deploy it. It also allows you to use your own +subscription Id, principal Id, tenant ID and other parameters that need to be tokenized. -.PARAMETER templateFilePath +.PARAMETER TemplateFilePath Mandatory. Path to the Bicep/ARM module that is being tested +.PARAMETER parameterFilePath +Optional. Path to the template file/folder that is to be tested with the template file. Defaults to the module's default '.parameter' folder. Will be used if the DeploymentTest/ValidationTest switches are set. + .PARAMETER PesterTest -Optional. A Switch Parameter that triggers a Pester Test for the Module +Optional. A switch parameter that triggers a Pester test for the module .PARAMETER ValidateOrDeployParameters -An Object consisting of the components that are required when using the Validate Test or DeploymentTest Switch parameter. See example: +Optional. An object consisting of the components that are required when using the Validate test or DeploymentTest switch parameter. Mandatory if the DeploymentTest/ValidationTest switches are set. .PARAMETER DeploymentTest -Optional. A Switch Parameter that triggers the Deployment of the Module +Optional. A switch parameter that triggers the deployment of the module .PARAMETER ValidationTest -Optional. A Switch Parameter that triggers the Validation of the Module Only without Deployment - -.PARAMETER DeployAllModuleParameterFiles -Optional. A Boolean Parameter that enables directory based search for parameter files and deploys all of them. If not true, it will only deploy the 'parameters.json' file. Default is false. +Optional. A switch parameter that triggers the validation of the module only without deployment .PARAMETER SkipParameterFileTokens -Optional. A Switch Parameter that enables you to skip the search for local custom parameter file tokens. +Optional. A switch parameter that enables you to skip the search for local custom parameter file tokens. .PARAMETER AdditionalTokens -Optional. A Hashtable Parameter that contains custom tokens to be replaced in the paramter files for deployment +Optional. A hashtable parameter that contains custom tokens to be replaced in the paramter files for deployment .EXAMPLE $TestModuleLocallyInput = @{ - templateFilePath = 'Microsoft.Network\applicationSecurityGroups' - PesterTest = $true - DeploymentTest = $true - ValidationTest = $false - ValidateOrDeployParameters = @{ - Location = 'australiaeast' + TemplateFilePath = 'C:\Microsoft.Network\routeTables\deploy.bicep' + parameterFilePath = 'C:\Microsoft.Network\routeTables\.parameters\parameters.json' + PesterTest = $false + DeploymentTest = $false + ValidationTest = $true + ValidateOrDeployParameters = @{ + Location = 'westeurope' ResourceGroupName = 'validation-rg' - SubscriptionId = '12345678-1234-1234-1234-123456789123' - ManagementGroupId = 'mg-contoso' + SubscriptionId = '00000000-0000-0000-0000-000000000000' + ManagementGroupId = '00000000-0000-0000-0000-000000000000' + RemoveDeployment = $false + } + AdditionalTokens = @{ + deploymentSpId = '00000000-0000-0000-0000-000000000000' } - AdditionalTokens = @( - @{ Name = 'deploymentSpId'; Value = '12345678-1234-1234-1234-123456789123' } - @{ Name = 'tenantId'; Value = '12345678-1234-1234-1234-123456789123' } - ) } - Test-ModuleLocally @TestModuleLocallyInput -Verbose +Run a Test-Az*Deployment using a specific parameter-template combination with the provided tokens + .EXAMPLE $TestModuleLocallyInput = @{ - templateFilePath = 'Microsoft.Network\applicationSecurityGroups' - PesterTest = $true - DeploymentTest = $true - ValidationTest = $false - ValidateOrDeployParameters = @{ - Location = 'australiaeast' + TemplateFilePath = 'C:\Microsoft.Network\routeTables\deploy.bicep' + PesterTest = $true + DeploymentTest = $false + ValidationTest = $true + ValidateOrDeployParameters = @{ + Location = 'westeurope' ResourceGroupName = 'validation-rg' - SubscriptionId = '12345678-1234-1234-1234-123456789123' - ManagementGroupId = 'mg-contoso' + SubscriptionId = '00000000-0000-0000-0000-000000000000' + ManagementGroupId = '00000000-0000-0000-0000-000000000000' + RemoveDeployment = $false + } + AdditionalTokens = @{ + deploymentSpId = '00000000-0000-0000-0000-000000000000' } - DeployAllModuleParameterFiles = $true - GetParameterFileTokens = $true - AdditionalTokens = @( - @{ Name = 'deploymentSpId'; Value = '12345678-1234-1234-1234-123456789123' } - @{ Name = 'tenantId'; Value = '12345678-1234-1234-1234-123456789123' } - ) } +Test-ModuleLocally @TestModuleLocallyInput -Verbose +Run all Pesters test for a given template and a Test-Az*Deployment using each parameter file in the module's parameter folder in combination with the template and the provided tokens + +.EXAMPLE + +$TestModuleLocallyInput = @{ + TemplateFilePath = 'C:\Microsoft.Network\routeTables\deploy.bicep' + PesterTest = $true +} Test-ModuleLocally @TestModuleLocallyInput -Verbose +Run all Pester tests for the given template file + .NOTES - Make sure you provide the right information in the 'ValidateOrDeployParameters' parameter for this function to work. -- Ensure you have the ability to perform the deployment operations using your account - +- Ensure you have the ability to perform the deployment operations using your account (if planning to test deploy) #> function Test-ModuleLocally { - [CmdletBinding()] - param ( - [parameter(Mandatory)] - [string]$templateFilePath, - [parameter(Mandatory = $false)] - [switch]$PesterTest, + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory)] + [string] $TemplateFilePath, - [parameter(Mandatory)] - [psobject]$ValidateOrDeployParameters, + [Parameter(Mandatory = $false)] + [string] $parameterFilePath = (Join-Path (Split-Path $TemplateFilePath -Parent) '.parameters'), - [parameter(Mandatory = $false)] - [switch]$DeploymentTest, + [Parameter(Mandatory = $false)] + [Psobject] $ValidateOrDeployParameters = @{}, - [parameter(Mandatory = $false)] - [switch]$ValidationTest, + [Parameter(Mandatory = $false)] + [hashtable] $AdditionalTokens = @{}, - [parameter(Mandatory = $false)] - [bool]$DeployAllModuleParameterFiles = $false, + [Parameter(Mandatory = $false)] + [switch] $PesterTest, - [parameter(Mandatory = $false)] - [switch]$SkipParameterFileTokens, + [Parameter(Mandatory = $false)] + [switch] $DeploymentTest, - [parameter(Mandatory = $false)] - [psobject]$AdditionalTokens + [Parameter(Mandatory = $false)] + [switch] $ValidationTest ) begin { - $ModuleName = Split-Path (Split-Path $templateFilePath -Parent) -Leaf + $ModuleName = Split-Path (Split-Path $TemplateFilePath -Parent) -Leaf Write-Verbose "Running Local Tests for $($ModuleName)" # Load Tokens Converter Scripts - . (Join-Path $PSScriptRoot '../pipelines/tokensReplacement/Convert-TokensInParameterFile.ps1') + . (Join-Path $PSScriptRoot '../pipelines/tokensReplacement/Convert-TokensInFile.ps1') # Load Modules Validation / Deployment Scripts . (Join-Path $PSScriptRoot '../pipelines/resourceDeployment/New-ModuleDeployment.ps1') . (Join-Path $PSScriptRoot '../pipelines/resourceValidation/Test-TemplateWithParameterFile.ps1') } process { - # Test Module + + ################ + # PESTER Tests # + ################ if ($PesterTest) { Write-Verbose "Pester Testing Module: $ModuleName" try { Invoke-Pester -Configuration @{ - Run = @{ - Container = New-PesterContainer -Path (Join-Path $PSScriptRoot '../..' 'arm/.global/global.module.tests.ps1') -Data @{ - moduleFolderPaths = Split-Path $templateFilePath -Parent + Run = @{ + Container = New-PesterContainer -Path (Join-Path (Get-Item $PSScriptRoot).Parent.Parent 'arm/.global/global.module.tests.ps1') -Data @{ + moduleFolderPaths = Split-Path $TemplateFilePath -Parent } } - Filter = @{ - #ExcludeTag = 'ApiCheck' - #Tag = 'ApiCheck' - } - TestResult = @{ - TestSuiteName = 'Global Module Tests' - Enabled = $false - } - Output = @{ + Output = @{ Verbosity = 'Detailed' } } @@ -144,80 +147,100 @@ function Test-ModuleLocally { $PSItem.Exception.Message } } - # Deploy Module + + ################################# + # Validation & Deployment tests # + ################################# if (($ValidationTest -or $DeploymentTest) -and $ValidateOrDeployParameters) { + # Find Test Parameter Files - $ModuleParameterFiles = Get-ChildItem -Path (Join-Path (Split-Path $templateFilePath -Parent) '.parameters') -Recurse - # Replace Tokens with Values For Local Testing - $DefaultParameterFileTokens = @( - @{ Name = 'subscriptionId'; Value = "$($ValidateOrDeployParameters.SubscriptionId)" } - @{ Name = 'managementGroupId'; Value = "$($ValidateOrDeployParameters.ManagementGroupId)" } - ) | ForEach-Object { [PSCustomObject]$PSItem } - - # Look for Local Custom Parameter File Tokens (Source Control) - if (-not $SkipParameterFileTokens) { - # Get Settings JSON File - $Settings = Get-Content -Path (Join-Path $PSScriptRoot '../..' 'settings.json') | ConvertFrom-Json - # Get Custom Parameter File Tokens (Local) - $ConvertTokensInputs = @{ - DefaultParameterFileTokens = $DefaultParameterFileTokens - LocalCustomParameterFileTokens = $Settings.parameterFileTokens.localTokens.tokens - TokenPrefix = $Settings.parameterFileTokens.tokenPrefix - TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + # ------------------------- + if ((Get-Item -Path $parameterFilePath) -is [System.IO.DirectoryInfo]) { + $ModuleParameterFiles = (Get-ChildItem -Path $parameterFilePath).FullName + } else { + $ModuleParameterFiles = @($parameterFilePath) + } + + # Replace parameter file tokens + # ----------------------------- + + # Default Tokens + $ConvertTokensInputs = @{ + Tokens = @{ + subscriptionId = $ValidateOrDeployParameters.SubscriptionId + managementGroupId = $ValidateOrDeployParameters.ManagementGroupId } - #Add Other Parameter File Tokens (For Testing) - if ($AdditionalTokens) { - $ConvertTokensInputs += @{ OtherCustomParameterFileTokens = $AdditionalTokens - } + } + + #Add Other Parameter File Tokens (For Testing) + if ($AdditionalTokens) { + $ConvertTokensInputs.Tokens += $AdditionalTokens + } + + # Tokens in settings.json + $settingsFilePath = Join-Path (Get-Item $PSScriptRoot).Parent.Parent 'settings.json' + if (Test-Path $settingsFilePath) { + $Settings = Get-Content -Path $settingsFilePath -Raw | ConvertFrom-Json -AsHashtable + $ConvertTokensInputs += @{ + TokenPrefix = $Settings.parameterFileTokens.tokenPrefix + TokenSuffix = $Settings.parameterFileTokens.tokenSuffix + } + + if ($Settings.parameterFileTokens.localTokens) { + $ConvertTokensInputs.Tokens += $Settings.parameterFileTokens.localTokens } } + # Invoke Token Replacement Functionality and Convert Tokens in Parameter Files - $ModuleParameterFiles | ForEach-Object { $null = Convert-TokensInParameterFile @ConvertTokensInputs -ParameterFilePath $PSItem.FullName } - # Build Modules Validation and Deployment Inputs + $ModuleParameterFiles | ForEach-Object { $null = Convert-TokensInFile @ConvertTokensInputs -ParameterFilePath $_ } + + # Deployment & Validation Testing + # ------------------------------- $functionInput = @{ - templateFilePath = $templateFilePath - parameterFilePath = (Join-Path (Split-Path $templateFilePath -Parent) '.parameters/parameters.json') - location = "$($ValidateOrDeployParameters.Location)" - resourceGroupName = "$($ValidateOrDeployParameters.ResourceGroupName)" - subscriptionId = "$($ValidateOrDeployParameters.SubscriptionId)" - managementGroupId = "$($ValidateOrDeployParameters.ManagementGroupId)" + TemplateFilePath = $TemplateFilePath + location = $ValidateOrDeployParameters.Location + resourceGroupName = $ValidateOrDeployParameters.ResourceGroupName + subscriptionId = $ValidateOrDeployParameters.SubscriptionId + managementGroupId = $ValidateOrDeployParameters.ManagementGroupId + Verbose = $true } try { - # Validate Template + # Validate template + # ----------------- if ($ValidationTest) { - Write-Verbose "Validating Module: $ModuleName" - # Invoke Validation - Test-TemplateWithParameterFile @functionInput -Verbose + # Loop through test parameter files + foreach ($paramFilePath in $moduleParameterFiles) { + Write-Verbose ('Validating module [{0}] with parameter file [{1}]' -f $ModuleName, (Split-Path $paramFilePath -Leaf)) -Verbose + Test-TemplateWithParameterFile @functionInput -ParameterFilePath $parameterFilePath + } } - # Deploy Template + + + # Deploy template + # --------------- if ($DeploymentTest) { - Write-Verbose "Deploying Module: $ModuleName" - # Set the ParameterFilePath to Directory instead of the default 'parameters.json' - if ($DeployAllModuleParameterFiles) { - $functionInput.parameterFilePath = (Join-Path (Split-Path $templateFilePath -Parent) $ModuleName '.parameters') - } - # Append to Function Input the required parameters for Deployment - $functionInput += @{ - retryLimit = 1 + $functionInput['retryLimit'] = 1 # Overwrite default of 3 + # Loop through test parameter files + foreach ($paramFilePath in $moduleParameterFiles) { + Write-Verbose ('Deploy module [{0}] with parameter file [{1}]' -f $ModuleName, (Split-Path $paramFilePath -Leaf)) -Verbose + if ($PSCmdlet.ShouldProcess(('Module [{0}] with parameter file [{1}]' -f $ModuleName, (Split-Path $paramFilePath -Leaf)), 'Deploy')) { + New-ModuleDeployment @functionInput -ParameterFilePath $parameterFilePath + } } - # Invoke Deployment - New-ModuleDeployment @functionInput -Verbose } } catch { - Write-Error $PSItem.Exception - # Replace Values with Tokens For Repo Updates and Set Restore Flag to True to Prevent Running Restore Twice - $RestoreAlreadyTriggered = $true - Write-Verbose 'Restoring Tokens' - $ModuleParameterFiles | ForEach-Object { $null = Convert-TokensInParameterFile @ConvertTokensInputs -ParameterFilePath $PSItem.FullName -SwapValueWithName $true } + Write-Error $_ + } finally { + # Restore parameter files + # ----------------------- + if (($ValidationTest -or $DeploymentTest) -and $ValidateOrDeployParameters -and -not $RestoreAlreadyTriggered) { + # Replace Values with Tokens For Repo Updates + Write-Verbose 'Restoring Tokens' + $ModuleParameterFiles | ForEach-Object { $null = Convert-TokensInFile @ConvertTokensInputs -ParameterFilePath $_ -SwapValueWithName $true } + } } } } end { - # Restore Parameter Files - if (($ValidationTest -or $DeploymentTest) -and $ValidateOrDeployParameters -and !($RestoreAlreadyTriggered)) { - # Replace Values with Tokens For Repo Updates - Write-Verbose 'Restoring Tokens' - $ModuleParameterFiles | ForEach-Object { $null = Convert-TokensInParameterFile @ConvertTokensInputs -ParameterFilePath $PSItem.FullName -SwapValueWithName $true } - } } }