Skip to content
24 changes: 12 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,18 @@ Example folder structure:
┗ 📂acc
┣ 📂bicep
┃ ┣ 📂config
┃ ┃ ┣ 📜inputs-azuredevops.yaml # ./docs/wiki/examples/powershell-inputs/inputs-azure-devops-bicep.yaml
┃ ┃ ┣ 📜inputs-github.yaml # ./docs/wiki/examples/powershell-inputs/inputs-github-bicep.yaml
┃ ┃ ┗ 📜inputs-local.yaml # ./docs/wiki/examples/powershell-inputs/inputs-local-bicep.yaml
┃ ┃ ┣ 📜inputs-azuredevops.yaml # ./docs/wiki/examples/powershell-inputs/inputs-azure-devops-bicep-complete.yaml
┃ ┃ ┣ 📜inputs-github.yaml # ./docs/wiki/examples/powershell-inputs/inputs-github-bicep-complete.yaml
┃ ┃ ┗ 📜inputs-local.yaml # ./docs/wiki/examples/powershell-inputs/inputs-local-bicep-complete.yaml
┃ ┗ 📂output
┃ ┣ 📂azuredevops
┃ ┣ 📂github
┃ ┗ 📂local
┗ 📂terraform
┣ 📂config
┃ ┣ 📜inputs-azuredevops.yaml # ./docs/wiki/examples/powershell-inputs/inputs-azure-devops-terraform.yaml
┃ ┣ 📜inputs-github.yaml # ./docs/wiki/examples/powershell-inputs/inputs-github-terraform.yaml
┃ ┗ 📜inputs-local.yaml # ./docs/wiki/examples/powershell-inputs/inputs-local-terraform.yaml
┃ ┣ 📜inputs-azuredevops.yaml # ./docs/wiki/examples/powershell-inputs/inputs-azure-devops-terraform-complete.yaml
┃ ┣ 📜inputs-github.yaml # ./docs/wiki/examples/powershell-inputs/inputs-github-terraform-complete.yaml
┃ ┗ 📜inputs-local.yaml # ./docs/wiki/examples/powershell-inputs/inputs-local-terraform-complete.yaml
┗ 📂output
┣ 📂azuredevops
┣ 📂github
Expand Down Expand Up @@ -94,12 +94,12 @@ cd /

$exampleFolder = "$targetFolder/code/ALZ-PowerShell-Module/docs/wiki/examples/powershell-inputs"

Copy-Item -Path "$exampleFolder/inputs-azure-devops-bicep.yaml" -Destination "$bicepConfigFolder/inputs-azuredevops.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-github-bicep.yaml" -Destination "$bicepConfigFolder/inputs-github.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-local-bicep.yaml" -Destination "$bicepConfigFolder/inputs-local.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-azure-devops-terraform.yaml" -Destination "$terraformConfigFolder/inputs-azuredevops.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-github-terraform.yaml" -Destination "$terraformConfigFolder/inputs-github.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-local-terraform.yaml" -Destination "$terraformConfigFolder/inputs-local.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-azure-devops-bicep-complete.yaml" -Destination "$bicepConfigFolder/inputs-azuredevops.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-github-bicep-complete.yaml" -Destination "$bicepConfigFolder/inputs-github.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-local-bicep-complete.yaml" -Destination "$bicepConfigFolder/inputs-local.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-azure-devops-terraform-complete.yaml" -Destination "$terraformConfigFolder/inputs-azuredevops.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-github-terraform-complete.yaml" -Destination "$terraformConfigFolder/inputs-github.yaml" -Force
Copy-Item -Path "$exampleFolder/inputs-local-terraform-complete.yaml" -Destination "$terraformConfigFolder/inputs-local.yaml" -Force

```

Expand Down
52 changes: 43 additions & 9 deletions docs/wiki/Frequently-Asked-Questions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,51 @@ This article answers frequently asked questions relating to the Azure landing zo

### How do I use my own naming convention for the resources that are deployed?

Follow these steps to customise the resource names:
You can add any hidden variables to your inputs file, including the `resource_names` map. This map is used to set the names of the resources that are deployed. You can find the default values in the `terraform.tfvars` file in the bootstrap module.

1. At step 2.2 of the quickstart, you will run the `Deploy-Accelerator` command to start the user input process.
1. Once the prompt appears for the first question open the relevant bootstrap `terraform.tfvars` file:
1. Azure DevOps: `./bootstrap/v#.#.#/alz/azuredevops/terraform.tfvars`
1. GitHub: `./bootstrap/v#.#.#/alz/github/terraform.tfvars`
1. Look for the variable called `resource_names`.
1. Update this map to have the names you desire and save it.
1. Continue with the user input as normal.
For example adding this to the end of your inputs file and updating to your standard:

You'll now get the names you specified instead of the default ones.
```yaml
# Extra Inputs
resource_names:
resource_group_state: "rg-{{service_name}}-{{environment_name}}-state-{{azure_location}}-{{postfix_number}}-test"
resource_group_identity: "rg-{{service_name}}-{{environment_name}}-identity-{{azure_location}}-{{postfix_number}}"
resource_group_agents: "rg-{{service_name}}-{{environment_name}}-agents-{{azure_location}}-{{postfix_number}}"
resource_group_network: "rg-{{service_name}}-{{environment_name}}-network-{{azure_location}}-{{postfix_number}}"
user_assigned_managed_identity_plan: "id-{{service_name}}-{{environment_name}}-{{azure_location}}-plan-{{postfix_number}}"
user_assigned_managed_identity_apply: "id-{{service_name}}-{{environment_name}}-{{azure_location}}-apply-{{postfix_number}}"
user_assigned_managed_identity_federated_credentials_plan: "id-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number}}-plan"
user_assigned_managed_identity_federated_credentials_apply: "id-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number}}-apply"
storage_account: "sto{{service_name_short}}{{environment_name_short}}{{azure_location_short}}{{postfix_number}}{{random_string}}"
storage_container: "{{environment_name}}-tfstate"
container_instance_01: "aci-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number}}"
container_instance_02: "aci-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number_plus_1}}"
container_instance_managed_identity: "id-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number}}-aci"
agent_01: "agent-{{service_name}}-{{environment_name}}-{{postfix_number}}"
agent_02: "agent-{{service_name}}-{{environment_name}}-{{postfix_number_plus_1}}"
version_control_system_repository: "{{service_name}}-{{environment_name}}"
version_control_system_repository_templates: "{{service_name}}-{{environment_name}}-templates"
version_control_system_service_connection_plan: "sc-{{service_name}}-{{environment_name}}-plan"
version_control_system_service_connection_apply: "sc-{{service_name}}-{{environment_name}}-apply"
version_control_system_environment_plan: "{{service_name}}-{{environment_name}}-plan"
version_control_system_environment_apply: "{{service_name}}-{{environment_name}}-apply"
version_control_system_variable_group: "{{service_name}}-{{environment_name}}"
version_control_system_agent_pool: "{{service_name}}-{{environment_name}}"
version_control_system_group: "{{service_name}}-{{environment_name}}-approvers"
version_control_system_pipeline_name_ci: "01 Azure Landing Zones Continuous Integration"
version_control_system_pipeline_name_cd: "02 Azure Landing Zones Continuous Delivery"
virtual_network: "vnet-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number}}"
public_ip: "pip-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number}}"
nat_gateway: "nat-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number}}"
subnet_container_instances: "subnet-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number}}-aci"
subnet_private_endpoints: "subnet-{{service_name}}-{{environment_name}}-{{azure_location}}-{{postfix_number}}-pe"
storage_account_private_endpoint: "pe-{{service_name}}-{{environment_name}}-{{azure_location}}-sto-{{postfix_number}}"
container_registry: "acr{{service_name}}{{environment_name}}{{azure_location_short}}{{postfix_number}}{{random_string}}"
container_registry_private_endpoint: "pe-{{service_name}}-{{environment_name}}-{{azure_location}}-acr-{{postfix_number}}"
container_image_name: "azure-devops-agent"
```

Alternatively, you can take a copy of the `terraform.tfvars` file from the bootstrap module, update it and supply it via the `-bootstrapTfVarsOverridePath` parameter as an absolute path.

## Questions about bootstrap clean up

Expand Down
72 changes: 72 additions & 0 deletions src/ALZ/Private/Config-Helpers/Get-AzureRegionData.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
function Get-AzureRegionData {
param(
[Parameter(Mandatory = $false)]
[string]$toolsPath = ".\region"
)

$terraformCode = @'
terraform {
required_providers {
azapi = {
source = "azure/azapi"
version = "~> 1.14"
}
}
}

data "azapi_client_config" "current" {}

data "azapi_resource_action" "locations" {
type = "Microsoft.Resources/subscriptions@2022-12-01"
action = "locations"
method = "GET"
resource_id = "/subscriptions/${data.azapi_client_config.current.subscription_id}"
response_export_values = ["value"]
}

locals {
regions = { for region in jsondecode(data.azapi_resource_action.locations.output).value : region.name => {
display_name = region.displayName
zones = try([ for zone in region.availabilityZoneMappings : zone.logicalZone ], [])
} if region.metadata.regionType == "Physical"
}
}

output "regions_and_zones" {
value = local.regions
}
'@

$regionFolder = Join-Path $toolsPath "azure-regions"
if(Test-Path $regionFolder) {
Remove-Item $regionFolder -Recurse -Force
}

New-Item $regionFolder -ItemType "Directory"

$regionCodeFileName = Join-Path $regionFolder "main.tf"
$terraformCode | Out-File $regionCodeFileName -Force

$outputFilePath = Join-Path $regionFolder "output.json"

Invoke-Terraform -moduleFolderPath $regionFolder -autoApprove -output "regions_and_zones" -outputFilePath $outputFilePath -silent

$json = Get-Content $outputFilePath
$regionsAndZones = ConvertFrom-Json $json

$zonesSupport = @()
$supportedRegions = @()

foreach($region in $regionsAndZones.PSObject.Properties) {
$supportedRegions += $region.Name
$zonesSupport += @{
region = $region.Name
zones = $region.Value.zones
}
}

return @{
zonesSupport = $zonesSupport
supportedRegions = $supportedRegions
}
}
62 changes: 41 additions & 21 deletions src/ALZ/Private/Config-Helpers/Request-ConfigurationValue.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ function Request-ConfigurationValue {
Write-InformationColored "[allowed: $allowedValues] " -ForegroundColor Yellow -InformationAction Continue
}

$dataType = $configValue.DataType
Write-Verbose "Data Type: $dataType"

$attempt = 0
$maxAttempts = 10

Expand All @@ -54,41 +57,58 @@ function Request-ConfigurationValue {
$readValue = Read-Host
}

$previousValue = $configValue.Value

if ($hasDefaultValue -and $readValue -eq "") {
$configValue.Value = $configValue.defaultValue
} else {
$configValue.Value = $readValue
}

$hasNotSpecifiedValue = ($null -eq $configValue.Value -or "" -eq $configValue.Value) -and ($configValue.Value -ne $configValue.DefaultValue)
$isDisallowedValue = $hasAllowedValues -and $allowedValues.Contains($configValue.Value) -eq $false
$skipValidationForEmptyDefault = $treatEmptyDefaultAsValid -and $hasDefaultValue -and (($defaultValue -eq "" -and $configValue.Value -eq "") -or ($configValue.Value -eq "-"))

# Reset the value to empty if we have a default and the user entered a dash (this is to handle cached situations and provide a method to clear a value)
if($skipValidationForEmptyDefault -and $configValue.Value -eq "-") {
$configValue.Value = ""
$valuesToCheck = @( $configValue.Value )
if($dataType -eq "list(string)") {
$valuesToCheck = ($configValue.Value -split ",").Trim() | Where-Object {$_ -ne ''}
$configValue.Value = $valuesToCheck -join ","
}

if($skipValidationForEmptyDefault) {
$isNotValid = $false
} else {
Write-Verbose "Checking '$($configValue.Value)' against '$($configValue.Valid)'"
$isNotValid = $hasValidator -and $configValue.Value -match $configValue.Valid -eq $false
}
$isValid = $false

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
foreach($valueToCheck in $valuesToCheck) {
$isValid = $true

$hasNotSpecifiedValue = ($null -eq $valueToCheck -or "" -eq $valueToCheck) -and ($valueToCheck -ne $configValue.DefaultValue)

if($hasNotSpecifiedValue) {
Write-InformationColored "A value must be specified for this input. It cannot be left empty." -ForegroundColor Red -InformationAction Continue
$isValid = $false
break
}

$skipValidationForEmptyDefault = $treatEmptyDefaultAsValid -and $hasDefaultValue -and (($defaultValue -eq "" -and $valueToCheck -eq ""))
if(!$skipValidationForEmptyDefault) {
if($hasAllowedValues) {
Write-Verbose "Checking '$($valueToCheck)' against list '$($allowedValues)'"
$isValid = $allowedValues.Contains($valueToCheck)
if(!$isValid) {
Write-InformationColored "The input value '$valueToCheck' is not valid. It must be in the allowed list: '$($allowedValues)'" -ForegroundColor Red -InformationAction Continue
break
}
}

if($hasValidator) {
Write-Verbose "Checking '$($valueToCheck)' against validator '$($configValue.Valid)'"
$isValid = $valueToCheck -match $configValue.Valid
if(!$isValid) {
Write-InformationColored "The input value '$valueToCheck' is not valid. It must match to specified regular expression: '$($configValue.Valid)'" -ForegroundColor Red -InformationAction Continue
break
}
}
}
}

$shouldRetry = $validationError -and $withRetries
$shouldRetry = !$isValid -and $withRetries

$attempt += 1
}
while (($hasNotSpecifiedValue -or $isDisallowedValue -or $isNotValid) -and $shouldRetry -and $attempt -lt $maxAttempts)
while ($shouldRetry -and $attempt -lt $maxAttempts)

if($attempt -eq $maxAttempts) {
Write-InformationColored "Max attempts reached for getting input value. Exiting..." -ForegroundColor Red -InformationAction Continue
Expand Down
49 changes: 49 additions & 0 deletions src/ALZ/Private/Config-Helpers/Write-TfvarsJsonFile.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
function Write-TfvarsJsonFile {
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory = $false)]
[string] $tfvarsFilePath,

[Parameter(Mandatory = $false)]
[PSObject] $configuration
)

if ($PSCmdlet.ShouldProcess("Download Terraform Tools", "modify")) {

if(Test-Path $tfvarsFilePath) {
Remove-Item -Path $tfvarsFilePath
}

$jsonObject = @{}

foreach($configurationProperty in $configuration.PSObject.Properties) {
$configurationValue = $configurationProperty.Value.Value

if($configurationProperty.Value.Validator -eq "configuration_file_path") {
$configurationValue = [System.IO.Path]::GetFileName($configurationValue)
}

if($configurationProperty.Value.DataType -eq "list(string)" -and !($configurationValue -is [array])) {
if($configurationValue -eq "") {
$configurationValue = @()
} else {
$configurationValue = @($configurationValue -split ",")
}
}

if($configurationProperty.Value.DataType -eq "number") {
$configurationValue = [int]($configurationValue)
}

if($configurationProperty.Value.DataType -eq "bool") {
$configurationValue = [bool]($configurationValue)
}

$jsonObject["$($configurationProperty.Name)"] = $configurationValue
}

$jsonString = ConvertTo-Json $jsonObject

$jsonString | Out-File $tfvarsFilePath
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ function Get-BootstrapAndStarterConfig {
[Parameter(Mandatory = $false)]
[string]$bootstrapConfigPath,
[Parameter(Mandatory = $false)]
[PSCustomObject]$userInputOverrides
[PSCustomObject]$userInputOverrides,
[Parameter(Mandatory = $false)]
[string]$toolsPath
)

if ($PSCmdlet.ShouldProcess("Get Configuration for Bootstrap and Starter", "modify")) {
Expand All @@ -31,7 +33,14 @@ function Get-BootstrapAndStarterConfig {
Write-Verbose "Bootstrap config path $bootstrapConfigFullPath"
$bootstrapConfig = Get-ALZConfig -configFilePath $bootstrapConfigFullPath
$validationConfig = $bootstrapConfig.validators
$zonesSupport = $bootstrapConfig.zonesSupport

Write-Verbose "Getting Supported Regions and Availability Zones with Terraform"
$regionsAndZones = Get-AzureRegionData -toolsPath $toolsPath
Write-Verbose "Supported Regions: $($regionsAndZones.supportedRegions)"

$zonesSupport = $regionsAndZones.zonesSupport
$azureLocationValidator = $validationConfig.PSObject.Properties["azure_location"].Value
$azureLocationValidator.AllowedValues.Values = $regionsAndZones.supportedRegions

# Get the available bootstrap modules
$bootstrapModules = $bootstrapConfig.bootstrap_modules
Expand Down
Loading