diff --git a/src/ALZ/Private/Config-Helpers/Request-SpecialInput.ps1 b/src/ALZ/Private/Config-Helpers/Request-SpecialInput.ps1 index 1dbe4da..b82d61d 100644 --- a/src/ALZ/Private/Config-Helpers/Request-SpecialInput.ps1 +++ b/src/ALZ/Private/Config-Helpers/Request-SpecialInput.ps1 @@ -5,7 +5,7 @@ function Request-SpecialInput { [string] $type, [Parameter(Mandatory = $false)] - [string] $starterPath, + [PSCustomObject] $starterConfig, [Parameter(Mandatory = $false)] [PSCustomObject] $bootstrapModules, @@ -17,83 +17,79 @@ function Request-SpecialInput { if ($PSCmdlet.ShouldProcess("ALZ-Terraform module configuration", "modify")) { $result = "" - - if($null -ne $userInputOverrides) { - $userInputOverride = $userInputOverrides.PSObject.Properties | Where-Object { $_.Name -eq $type } - if($null -ne $userInputOverride) { - $result = $userInputOverride.Value - return $result - } + $options = @() + $aliasOptions = @() + $typeDescription = "" + + if($type -eq "iac") { + $options += @{ key = "bicep"; name = "Bicep"; description = "Bicep" } + $options += @{ key = "terraform"; name = "Terraform"; description = "Terraform" } + $typeDescription = "Infrastructure as Code (IaC) language" } - $gotValidInput = $false - - while(!$gotValidInput) { - if($type -eq "starter") { - - $starterFolders = Get-ChildItem -Path $starterPath -Directory - Write-InformationColored "Please select the starter module you would like to use, you can enter one of the following keys:" -ForegroundColor Yellow -InformationAction Continue - - $starterOptions = @() - foreach($starterFolder in $starterFolders) { - if($starterFolder.Name -eq $starterPipelineFolder) { - continue + if($type -eq "bootstrap") { + if($bootstrapModules.PsObject.Properties.Name.Count -eq 0) { + $options += @{ key = "azuredevops"; name = "Azure DevOps"; description = "Azure DevOps" } + $options += @{ key = "github"; name = "GitHub"; description = "GitHub" } + $aliasOptions += @{ key = "alz_azuredevops"; name = "Azure DevOps"; description = "Azure DevOps" } + $aliasOptions += @{ key = "alz_github"; name = "GitHub"; description = "GitHub" } + } else { + foreach ($bootstrapModule in $bootstrapModules.PsObject.Properties) { + $options += @{ key = $bootstrapModule.Name; name = $bootstrapModule.Value.short_name; description = $bootstrapModule.Value.description } + foreach($alias in $bootstrapModule.Value.aliases) { + $aliasOptions += @{ key = $alias; name = $bootstrapModule.Value.short_name; description = $bootstrapModule.Value.description } } + } + } + $typeDescription = "bootstrap module" + } - Write-InformationColored "- $($starterFolder.Name)" -ForegroundColor Yellow -InformationAction Continue - $starterOptions += $starterFolder.Name + if($type -eq "starter") { + foreach($starter in $starterConfig.starter_modules.PsObject.Properties) { + if($starter.Name -eq $starterPipelineFolder) { + continue } - Write-InformationColored ": " -ForegroundColor Yellow -NoNewline -InformationAction Continue - $result = Read-Host + $options += @{ key = $starter.Name; name = $starter.Value.short_name; description = $starter.Value.description } + } + $typeDescription = "starter module" + } - if($result -notin $starterOptions) { - Write-InformationColored "The starter '$result' that you have selected does not exist. Please try again with a valid starter..." -ForegroundColor Red -InformationAction Continue - } else { - $gotValidInput = $true + if($null -ne $userInputOverrides) { + $userInputOverride = $userInputOverrides.PSObject.Properties | Where-Object { $_.Name -eq $type } + if($null -ne $userInputOverride) { + $result = $userInputOverride.Value + if($options.key -notcontains $result -and $aliasOptions.key -notcontains $result) { + Write-InformationColored "The $typeDescription '$result' that you have selected does not exist. Please try again with a valid $typeDescription..." -ForegroundColor Red -InformationAction Continue + throw "The $typeDescription '$result' that you have selected does not exist. Please try again with a valid $typeDescription..." } + return $result } + } - if($type -eq "iac") { - Write-InformationColored "Please select the IAC you would like to use, you can enter one of 'bicep or 'terraform': " -ForegroundColor Yellow -NoNewline -InformationAction Continue - $result = Read-Host - - $validIac = @("bicep", "terraform") - if($result -notin $validIac) { - Write-InformationColored "The IAC '$result' that you have selected does not exist. Please try again with a valid IAC..." -ForegroundColor Red -InformationAction Continue + # Add the options to the choices array + $choices = @() + $usedLetters = @() + foreach($option in $options) { + $letterIndex = 0 - } else { - $gotValidInput = $true - } + Write-Verbose "Checking for used letters in '$($option.name)'. Used letters: $usedLetters" + while($usedLetters -contains $option.name[$letterIndex].ToString().ToLower()) { + $letterIndex++ } - if($type -eq "bootstrap") { - Write-InformationColored "Please select the bootstrap module you would like to use, you can enter one of the following keys:" -ForegroundColor Yellow -InformationAction Continue - - $bootstrapOptions = @() - if($bootstrapModules.PsObject.Properties.Name.Count -eq 0) { - $bootstrapOptions += "azuredevops" - Write-InformationColored "- azuredevops" -ForegroundColor Yellow -InformationAction Continue - $bootstrapOptions += "github" - Write-InformationColored "- github" -ForegroundColor Yellow -InformationAction Continue - } else { - foreach ($bootstrapModule in $bootstrapModules.PsObject.Properties) { - Write-InformationColored "- $($bootstrapModule.Name) ($($bootstrapModule.Value.description))" -ForegroundColor Yellow -InformationAction Continue - $bootstrapOptions += $bootstrapModule.Name - } - } + $usedLetters += $option.name[$letterIndex].ToString().ToLower() + $option.name = $option.name.Insert($letterIndex, "&") + $choices += New-Object System.Management.Automation.Host.ChoiceDescription $option.name, $option.description + } - Write-InformationColored ": " -ForegroundColor Yellow -NoNewline -InformationAction Continue - $result = Read-Host + $message = "Please select the $typeDescription you would like to use." + $title = "Choose $typeDescription" + $resultIndex = $host.ui.PromptForChoice($title, $message, $choices, 0) + $result = $options[$resultIndex].key - if($result -notin $bootstrapOptions) { - Write-InformationColored "The starter '$result' that you have selected does not exist. Please try again with a valid starter..." -ForegroundColor Red -InformationAction Continue - } else { - $gotValidInput = $true - } - } - } + Write-InformationColored "You selected '$result'. Continuing with deployment..." -ForegroundColor Green -InformationAction Continue return $result } -} \ No newline at end of file +} diff --git a/src/ALZ/Private/Deploy-Accelerator-Helpers/Get-BootstrapAndStarterConfig.ps1 b/src/ALZ/Private/Deploy-Accelerator-Helpers/Get-BootstrapAndStarterConfig.ps1 index a837572..857321d 100644 --- a/src/ALZ/Private/Deploy-Accelerator-Helpers/Get-BootstrapAndStarterConfig.ps1 +++ b/src/ALZ/Private/Deploy-Accelerator-Helpers/Get-BootstrapAndStarterConfig.ps1 @@ -20,6 +20,8 @@ function Get-BootstrapAndStarterConfig { $starterModuleSourceFolder = "" $starterReleaseTag = "" $starterPipelineFolder = "" + $starterReleaseArtifactName = "" + $starterConfigFilePath = "" $bootstrapDetails = $null $validationConfig = $null @@ -61,8 +63,10 @@ function Get-BootstrapAndStarterConfig { } $starterModuleUrl = $starterModuleDetails.Value.$iac.url - $starterModuleSourceFolder = $starterModuleDetails.Value.$iac.module_path - $starterPipelineFolder = $starterModuleDetails.Value.$iac.pipeline_folder + $starterModuleSourceFolder = $starterModuleDetails.Value.$iac.release_artifact_root_path + $starterPipelineFolder = $starterModuleDetails.Value.$iac.release_artifact_ci_cd_path + $starterReleaseArtifactName = $starterModuleDetails.Value.$iac.release_artifact_name + $starterConfigFilePath = $starterModuleDetails.Value.$iac.release_artifact_config_file } # Get the bootstrap interface user input config @@ -71,14 +75,16 @@ function Get-BootstrapAndStarterConfig { $inputConfig = Get-ALZConfig -configFilePath $inputConfigFilePath return @{ - bootstrapDetails = $bootstrapDetails - hasStarterModule = $hasStarterModule - starterModuleUrl = $starterModuleUrl - starterModuleSourceFolder = $starterModuleSourceFolder - starterReleaseTag = $starterReleaseTag - starterPipelineFolder = $starterPipelineFolder - validationConfig = $validationConfig - inputConfig = $inputConfig + bootstrapDetails = $bootstrapDetails + hasStarterModule = $hasStarterModule + starterModuleUrl = $starterModuleUrl + starterModuleSourceFolder = $starterModuleSourceFolder + starterReleaseTag = $starterReleaseTag + starterPipelineFolder = $starterPipelineFolder + starterReleaseArtifactName = $starterReleaseArtifactName + starterConfigFilePath = $starterConfigFilePath + validationConfig = $validationConfig + inputConfig = $inputConfig } } } diff --git a/src/ALZ/Private/Deploy-Accelerator-Helpers/Get-StarterConfig.ps1 b/src/ALZ/Private/Deploy-Accelerator-Helpers/Get-StarterConfig.ps1 new file mode 100644 index 0000000..44ce03d --- /dev/null +++ b/src/ALZ/Private/Deploy-Accelerator-Helpers/Get-StarterConfig.ps1 @@ -0,0 +1,19 @@ + +function Get-StarterConfig { + [CmdletBinding(SupportsShouldProcess = $true)] + param( + [Parameter(Mandatory = $false)] + [string]$starterPath, + [Parameter(Mandatory = $false)] + [string]$starterConfigPath + ) + + if ($PSCmdlet.ShouldProcess("Get Configuration for Bootstrap and Starter", "modify")) { + # Get the bootstap configuration + $starterConfigFullPath = Join-Path $starterPath $starterConfigPath + Write-Verbose "Starter config path $starterConfigFullPath" + $starterConfig = Get-ALZConfig -configFilePath $starterConfigFullPath + + return $starterConfig + } +} diff --git a/src/ALZ/Private/Deploy-Accelerator-Helpers/New-Bootstrap.ps1 b/src/ALZ/Private/Deploy-Accelerator-Helpers/New-Bootstrap.ps1 index a31dc63..bc76665 100644 --- a/src/ALZ/Private/Deploy-Accelerator-Helpers/New-Bootstrap.ps1 +++ b/src/ALZ/Private/Deploy-Accelerator-Helpers/New-Bootstrap.ps1 @@ -25,6 +25,9 @@ function New-Bootstrap { [Parameter(Mandatory = $false)] [string] $starterTargetPath, + [Parameter(Mandatory = $false)] + [PSCustomObject] $starterConfig = $null, + [Parameter(Mandatory = $false)] [string] $bootstrapRelease, @@ -83,8 +86,11 @@ function New-Bootstrap { $pipelineModulePath = "" if($hasStarter) { - $starter = Request-SpecialInput -type "starter" -starterPath $starterPath -userInputOverrides $userInputOverrides - $starterModulePath = Resolve-Path (Join-Path -Path $starterPath -ChildPath $starter) + $starter = Request-SpecialInput -type "starter" -starterConfig $starterConfig -userInputOverrides $userInputOverrides + + Write-Verbose "Selected Starter: $starter" + + $starterModulePath = Resolve-Path (Join-Path -Path $starterPath -ChildPath $starterConfig.starter_modules.$starter.location) $pipelineModulePath = Resolve-Path (Join-Path -Path $starterPath -ChildPath $starterPipelineFolder) Write-Verbose "Starter Module Path: $starterModulePath" diff --git a/src/ALZ/Private/Deploy-Accelerator-Helpers/New-FolderStructure.ps1 b/src/ALZ/Private/Deploy-Accelerator-Helpers/New-FolderStructure.ps1 index 05b86e2..d0cc631 100644 --- a/src/ALZ/Private/Deploy-Accelerator-Helpers/New-FolderStructure.ps1 +++ b/src/ALZ/Private/Deploy-Accelerator-Helpers/New-FolderStructure.ps1 @@ -10,6 +10,9 @@ function New-FolderStructure { [Parameter(Mandatory = $false)] [string] $release = "latest", + [Parameter(Mandatory = $false)] + [string] $releaseArtifactName = "", + [Parameter(Mandatory = $true)] [string] $targetFolder, @@ -43,7 +46,7 @@ function New-FolderStructure { } } else { - $releaseTag = Get-GithubRelease -githubRepoUrl $url -targetDirectory $targetDirectory -moduleSourceFolder $sourceFolder -moduleTargetFolder $targetFolder -release $release + $releaseTag = Get-GithubRelease -githubRepoUrl $url -targetDirectory $targetDirectory -moduleSourceFolder $sourceFolder -moduleTargetFolder $targetFolder -release $release -releaseArtifactName $releaseArtifactName $path = Join-Path $targetDirectory $targetFolder $releaseTag } diff --git a/src/ALZ/Private/Deploy-Accelerator-Helpers/New-ModuleSetup.ps1 b/src/ALZ/Private/Deploy-Accelerator-Helpers/New-ModuleSetup.ps1 index 68e98f9..c04b3b0 100644 --- a/src/ALZ/Private/Deploy-Accelerator-Helpers/New-ModuleSetup.ps1 +++ b/src/ALZ/Private/Deploy-Accelerator-Helpers/New-ModuleSetup.ps1 @@ -13,6 +13,8 @@ function New-ModuleSetup { [Parameter(Mandatory = $false)] [string]$release, [Parameter(Mandatory = $false)] + [string]$releaseArtifactName = "", + [Parameter(Mandatory = $false)] [string]$moduleOverrideFolderPath, [Parameter(Mandatory = $false)] [bool]$skipInternetChecks @@ -28,6 +30,7 @@ function New-ModuleSetup { -targetDirectory $targetDirectory ` -url $url ` -release $release ` + -releaseArtifactName $releaseArtifactName ` -targetFolder $targetFolder ` -sourceFolder $sourceFolder ` -overrideSourceDirectoryPath $moduleOverrideFolderPath diff --git a/src/ALZ/Private/Shared/Get-GithubRelease.ps1 b/src/ALZ/Private/Shared/Get-GithubRelease.ps1 index ef46a76..b76011a 100644 --- a/src/ALZ/Private/Shared/Get-GithubRelease.ps1 +++ b/src/ALZ/Private/Shared/Get-GithubRelease.ps1 @@ -42,7 +42,10 @@ function Get-GithubRelease { $moduleSourceFolder = ".", [Parameter(Mandatory = $true, HelpMessage = "The target directory location of the modules.")] - $moduleTargetFolder + $moduleTargetFolder, + + [Parameter(Mandatory = $false, HelpMessage = "The name of the release artifact in the target release. Defaults to standard release zip.")] + $releaseArtifactName = "" ) $parentDirectory = $targetDirectory @@ -110,7 +113,17 @@ function Get-GithubRelease { Write-Verbose "===> Pulling and extracting release $releaseTag into $targetVersionPath" New-Item -ItemType Directory -Path "$targetVersionPath/tmp" | Out-String | Write-Verbose $targetPathForZip = "$targetVersionPath/tmp/$releaseTag.zip" - Invoke-WebRequest -Uri "https://github.com/$repoOrgPlusRepo/archive/refs/tags/$releaseTag.zip" -OutFile $targetPathForZip -RetryIntervalSec 3 -MaximumRetryCount 100 | Out-String | Write-Verbose + + # Get the artifact url + if($releaseArtifactName -ne "") { + $releaseArtifactUrl = $releaseData.assets | Where-Object { $_.name -eq $releaseArtifactName } | Select-Object -ExpandProperty browser_download_url + } else { + $releaseArtifactUrl = $releaseData.zipball_url + } + + Write-Verbose "===> Downloading the release artifact $releaseArtifactUrl from the GitHub repository $repoOrgPlusRepo" + + Invoke-WebRequest -Uri $releaseArtifactUrl -OutFile $targetPathForZip -RetryIntervalSec 3 -MaximumRetryCount 100 | Out-String | Write-Verbose if(!(Test-Path $targetPathForZip)) { Write-InformationColored "Failed to download the release $releaseTag from the GitHub repository $repoOrgPlusRepo" -ForegroundColor Red -InformationAction Continue @@ -120,11 +133,15 @@ function Get-GithubRelease { $targetPathForExtractedZip = "$targetVersionPath/tmp/extracted" Expand-Archive -Path $targetPathForZip -DestinationPath $targetPathForExtractedZip | Out-String | Write-Verbose - $extractedSubFolder = Get-ChildItem -Path $targetPathForExtractedZip -Directory - Write-Verbose "===> Copying all extracted contents into $targetVersionPath." + $extractedSubFolder = $targetPathForExtractedZip + if($releaseArtifactName -eq "") { + $extractedSubFolder = (Get-ChildItem -Path $targetPathForExtractedZip -Directory).FullName + } + + Write-Verbose "===> Copying all extracted contents into $targetVersionPath from $($extractedSubFolder)/$moduleSourceFolder/*." - Copy-Item -Path "$($extractedSubFolder.FullName)/$moduleSourceFolder/*" -Destination "$targetVersionPath" -Recurse -Force | Out-String | Write-Verbose + Copy-Item -Path "$($extractedSubFolder)/$moduleSourceFolder/*" -Destination "$targetVersionPath" -Recurse -Force | Out-String | Write-Verbose Remove-Item -Path "$targetVersionPath/tmp" -Force -Recurse Write-InformationColored "The directory for $targetVersionPath has been created and populated." -ForegroundColor Green -InformationAction Continue diff --git a/src/ALZ/Public/New-ALZEnvironment.ps1 b/src/ALZ/Public/New-ALZEnvironment.ps1 index 095dcdf..6aa7146 100644 --- a/src/ALZ/Public/New-ALZEnvironment.ps1 +++ b/src/ALZ/Public/New-ALZEnvironment.ps1 @@ -5,7 +5,7 @@ function New-ALZEnvironment { .DESCRIPTION This function is used to deploy accelerators consisting or bootstrap and optionally starter modules. The accelerators are designed to simplify and speed up configuration of common Microsoft patterns, such as CI / CD for Azure Landing Zones. .PARAMETER output - The target directory for the accelerator artefacts. Depending on the choice and type of accelerlerator, this may be an intermediate stage or the final result of the accelerator. + The target directory for the accelerator artifacts. Depending on the choice and type of accelerlerator, this may be an intermediate stage or the final result of the accelerator. .PARAMETER iac The type of infrastructure as code that the accelerator implements. For example bicep or terraform. .PARAMETER bootstrap @@ -71,6 +71,10 @@ function New-ALZEnvironment { [string] $bootstrapModuleUrl = "https://github.com/Azure/accelerator-bootstrap-modules", + [Parameter(Mandatory = $false, HelpMessage = "The bootstrap modules release artifact name.")] + [string] + $bootstrapModuleReleaseArtifactName = "bootstrap_modules.zip", + [Parameter(Mandatory = $false, HelpMessage = "The bootstrap config file path within the bootstrap module. This can be overridden for custom modules.")] [string] $bootstrapConfigPath = ".config/ALZ-Powershell.config.json", @@ -155,6 +159,7 @@ function New-ALZEnvironment { -sourceFolder $bootstrapSourceFolder ` -url $bootstrapModuleUrl ` -release $bootstrapRelease ` + -releaseArtifactName $bootstrapModuleReleaseArtifactName ` -moduleOverrideFolderPath $bootstrapModuleOverrideFolderPath ` -skipInternetChecks $skipInternetChecks @@ -179,6 +184,8 @@ function New-ALZEnvironment { $starterModuleSourceFolder = "." $starterReleaseTag = "local" $starterPipelineFolder = "local" + $starterReleaseArtifactName = "" + $starterConfigFilePath = "" $bootstrapDetails = $null $validationConfig = $null @@ -198,6 +205,8 @@ function New-ALZEnvironment { $starterModuleSourceFolder = $bootstrapAndStarterConfig.starterModuleSourceFolder $starterReleaseTag = $bootstrapAndStarterConfig.starterReleaseTag $starterPipelineFolder = $bootstrapAndStarterConfig.starterPipelineFolder + $starterReleaseArtifactName = $bootstrapAndStarterConfig.starterReleaseArtifactName + $starterConfigFilePath = $bootstrapAndStarterConfig.starterConfigFilePath $validationConfig = $bootstrapAndStarterConfig.validationConfig $inputConfig = $bootstrapAndStarterConfig.inputConfig } else { @@ -209,6 +218,7 @@ function New-ALZEnvironment { # Download the starter modules $starterReleaseTag = "" $starterPath = "" + $starterConfig = $null if(($hasStarterModule -or $isLegacyBicep)) { Write-InformationColored "Checking and Downloading the starter module..." -ForegroundColor Green -NewLineBefore -InformationAction Continue @@ -219,11 +229,15 @@ function New-ALZEnvironment { -sourceFolder $starterModuleSourceFolder ` -url $starterModuleUrl ` -release $starterRelease ` + -releaseArtifactName $starterReleaseArtifactName ` -moduleOverrideFolderPath $starterModuleOverrideFolderPath ` -skipInternetChecks $skipInternetChecks $starterReleaseTag = $versionAndPath.releaseTag $starterPath = $versionAndPath.path + if($starterConfigFilePath -ne "") { + $starterConfig = Get-StarterConfig -starterPath $starterPath -starterConfigPath $starterConfigFilePath + } } # Run the bicep parameter setup if the iac is Bicep @@ -262,6 +276,7 @@ function New-ALZEnvironment { -starterTargetPath $starterTargetPath ` -starterPipelineFolder $starterPipelineFolder ` -starterRelease $starterReleaseTag ` + -starterConfig $starterConfig ` -userInputOverrides $userInputOverrides ` -autoApprove:$autoApprove.IsPresent ` -destroy:$destroy.IsPresent