Skip to content

Commit

Permalink
Enhancement add terraform rerun and destroy (#93)
Browse files Browse the repository at this point in the history
# Pull Request

## Issue

Issue #, if available:
Azure/alz-terraform-accelerator#70

## Description

Description of changes:

This PR adds re-run, upgrade and destroy features for Terraform. These
features are designed to simplify usage of the accelerator, provide any
easy way to apply changes, fix issues and upgrade to a newer version.
The destroy will make it much easier to clean up test environments.

## License

By submitting this pull request, I confirm that my contribution is made
under the terms of the projects associated license.
  • Loading branch information
jaredfholgate authored Dec 21, 2023
1 parent c5bd475 commit 176ec96
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 18 deletions.
4 changes: 4 additions & 0 deletions src/ALZ/Private/Get-ALZConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ function Get-ALZConfig {
[string] $configFilePath = ""
)

if(!(Test-Path $configFilePath)) {
return $null
}

# Import the config and transform it to a PowerShell object
$extension = (Get-Item -Path $configFilePath).Extension.ToLower()
if($extension -eq ".yml" -or $extension -eq ".yaml") {
Expand Down
27 changes: 22 additions & 5 deletions src/ALZ/Private/Invoke-Terraform.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,33 @@ function Invoke-Terraform {
[string] $tfvarsFileName,

[Parameter(Mandatory = $false)]
[switch] $autoApprove
[switch] $autoApprove,

[Parameter(Mandatory = $false)]
[switch] $destroy
)

if ($PSCmdlet.ShouldProcess("Apply Terraform", "modify")) {
terraform -chdir="$moduleFolderPath" init
Write-InformationColored "Terraform init has completed, now running the apply..." -ForegroundColor Green -InformationAction Continue
if($autoApprove) {
terraform -chdir="$moduleFolderPath" apply -var-file="$tfvarsFileName" -auto-approve
$action = "apply"
if($destroy) {
$action = "destroy"
}

Write-InformationColored "Terraform init has completed, now running the $action..." -ForegroundColor Green -InformationAction Continue

if($destroy) {
if($autoApprove) {
terraform -chdir="$moduleFolderPath" destroy -var-file="$tfvarsFileName" -auto-approve
} else {
terraform -chdir="$moduleFolderPath" destroy -var-file="$tfvarsFileName"
}
} else {
terraform -chdir="$moduleFolderPath" apply -var-file="$tfvarsFileName"
if($autoApprove) {
terraform -chdir="$moduleFolderPath" apply -var-file="$tfvarsFileName" -auto-approve
} else {
terraform -chdir="$moduleFolderPath" apply -var-file="$tfvarsFileName"
}
}
}
}
95 changes: 95 additions & 0 deletions src/ALZ/Private/Invoke-Upgrade.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
function Invoke-Upgrade {
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory = $false)]
[string] $alzEnvironmentDestination,

[Parameter(Mandatory = $false)]
[string] $bootstrapCacheFileName,

[Parameter(Mandatory = $false)]
[string] $starterCacheFileNamePattern,

[Parameter(Mandatory = $false)]
[string] $stateFilePathAndFileName,

[Parameter(Mandatory = $false)]
[string] $currentVersion,

[Parameter(Mandatory = $false)]
[switch] $autoApprove
)

if ($PSCmdlet.ShouldProcess("Upgrade Release", "Operation")) {

$directories = Get-ChildItem -Path $alzEnvironmentDestination -Filter "v*" -Directory
$previousBootstrapCachedValuesPath = $null
$previousStarterCachedValuesPath = $null
$previousStateFilePath = $null
$previousVersion = $null
$foundPreviousRelease = $false

foreach ($directory in $directories | Sort-Object -Descending -Property Name) {
$releasePath = Join-Path -Path $alzEnvironmentDestination -ChildPath $directory.Name
$releaseBootstrapCachedValuesPath = Join-Path -Path $releasePath -ChildPath $bootstrapCacheFileName
$releaseStateFilePath = Join-Path -Path $releasePath -ChildPath $stateFilePathAndFileName

if(Test-Path $releaseBootstrapCachedValuesPath) {
$previousBootstrapCachedValuesPath = $releaseBootstrapCachedValuesPath
}

$starterCacheFiles = Get-ChildItem -Path $releasePath -Filter $starterCacheFileNamePattern -File

if($starterCacheFiles) {
$previousStarterCachedValuesPath = $starterCacheFiles[0].FullName
}

if(Test-Path $releaseStateFilePath) {
$previousStateFilePath = $releaseStateFilePath
}

if($null -ne $previousStateFilePath) {
if($directory.Name -eq $currentVersion) {
# If the current version has already been run, then skip the upgrade process
break
}

$foundPreviousRelease = $true
$previousVersion = $directory.Name
break
}
}

if($foundPreviousRelease) {
Write-InformationColored "AUTOMATIC UPGRADE: We found version $previousVersion that has been previously run. You can upgrade from this version to the new version $currentVersion" -ForegroundColor Yellow -InformationAction Continue
$upgrade = ""
if($autoApprove) {
$upgrade = "upgrade"
} else {
$upgrade = Read-Host "If you would like to upgrade, enter 'upgrade' or just hit 'enter' to continue with a new environment. (upgrade/exit)"
}

if($upgrade.ToLower() -eq "upgrade") {
$currentPath = Join-Path -Path $alzEnvironmentDestination -ChildPath $currentVersion
$currentBootstrapCachedValuesPath = Join-Path -Path $currentPath -ChildPath $bootstrapCacheFileName
$currentStarterCachedValuesPath = $currentPath
$currentStateFilePath = Join-Path -Path $currentPath -ChildPath $stateFilePathAndFileName

# Copy the previous cached values to the current release
if($null -ne $previousBootstrapCachedValuesPath) {
Write-InformationColored "AUTOMATIC UPGRADE: Copying $previousBootstrapCachedValuesPath to $currentBootstrapCachedValuesPath" -ForegroundColor Green -InformationAction Continue
Copy-Item -Path $previousBootstrapCachedValuesPath -Destination $currentBootstrapCachedValuesPath -Force | Out-String | Write-Verbose
}
if($null -ne $previousStarterCachedValuesPath) {
Write-InformationColored "AUTOMATIC UPGRADE: Copying $previousStarterCachedValuesPath to $currentStarterCachedValuesPath" -ForegroundColor Green -InformationAction Continue
Copy-Item -Path $previousStarterCachedValuesPath -Destination $currentStarterCachedValuesPath -Force | Out-String | Write-Verbose
}

Write-InformationColored "AUTOMATIC UPGRADE: Copying $previousStateFilePath to $currentStateFilePath" -ForegroundColor Green -InformationAction Continue
Copy-Item -Path $previousStateFilePath -Destination $currentStateFilePath -Force | Out-String | Write-Verbose

Write-InformationColored "AUTOMATIC UPGRADE: Upgrade complete. If any files in the starter have been updated, you will need to remove branch protection in order for the Terraform apply to succeed..." -ForegroundColor Yellow -InformationAction Continue
}
}
}
}
36 changes: 31 additions & 5 deletions src/ALZ/Private/New-ALZEnvironmentTerraform.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ function New-ALZEnvironmentTerraform {
[string] $userInputOverridePath = "",

[Parameter(Mandatory = $false)]
[switch] $autoApprove
[switch] $autoApprove,

[Parameter(Mandatory = $false)]
[switch] $destroy
)

if ($PSCmdlet.ShouldProcess("ALZ-Terraform module configuration", "modify")) {
Expand All @@ -36,10 +39,20 @@ function New-ALZEnvironmentTerraform {
$userInputOverrides = Get-ALZConfig -configFilePath $userInputOverridePath
}

# Setup Cache Paths
$bootstrapCacheFileName = "cache-bootstrap-$alzCicdPlatform.json"
$starterCacheFileNamePattern = "cache-starter-*.json"

# Downloading the latest or specified version of the alz-terraform-accelerator module
if(!($alzVersion.StartsWith("v"))) {
$alzVersion = "v$alzVersion"
}
$releaseTag = Get-ALZGithubRelease -directoryForReleases $alzEnvironmentDestination -iac "terraform" -release $alzVersion
$releasePath = Join-Path -Path $alzEnvironmentDestination -ChildPath $releaseTag

# Run upgrade
Invoke-Upgrade -alzEnvironmentDestination $alzEnvironmentDestination -bootstrapCacheFileName $bootstrapCacheFileName -starterCacheFileNamePattern $starterCacheFileNamePattern -stateFilePathAndFileName "bootstrap/$alzCicdPlatform/terraform.tfstate" -currentVersion $releaseTag -autoApprove:$autoApprove.IsPresent

# Getting the configuration for the initial bootstrap user input and validators
$bootstrapConfigFilePath = Join-Path -Path $releasePath -ChildPath "bootstrap/.config/ALZ-Powershell.config.json"
$bootstrapConfig = Get-ALZConfig -configFilePath $bootstrapConfigFilePath
Expand All @@ -52,8 +65,12 @@ function New-ALZEnvironmentTerraform {

Write-InformationColored "Got configuration and downloaded alz-terraform-accelerator Terraform module version $releaseTag to $alzEnvironmentDestination" -ForegroundColor Green -InformationAction Continue

#Checking for cached bootstrap values for retry / upgrade scenarios
$bootstrapCachedValuesPath = Join-Path -Path $releasePath -ChildPath $bootstrapCacheFileName
$cachedBootstrapConfig = Get-ALZConfig -configFilePath $bootstrapCachedValuesPath

# Getting the user input for the bootstrap module
$bootstrapConfiguration = Request-ALZEnvironmentConfig -configurationParameters $bootstrapParameters -respectOrdering -userInputOverrides $userInputOverrides -treatEmptyDefaultAsValid $true
$bootstrapConfiguration = Request-ALZEnvironmentConfig -configurationParameters $bootstrapParameters -respectOrdering -userInputOverrides $userInputOverrides -userInputDefaultOverrides $cachedBootstrapConfig -treatEmptyDefaultAsValid $true

# Getting the configuration for the starter module user input
$starterTemplate = $bootstrapConfiguration.PsObject.Properties["starter_module"].Value.Value
Expand All @@ -63,8 +80,13 @@ function New-ALZEnvironmentTerraform {

Write-InformationColored "The following inputs are specific to the '$starterTemplate' starter module that you selected..." -ForegroundColor Green -InformationAction Continue

# Checking for cached starter module values for retry / upgrade scenarios
$starterCacheFileName = "cache-starter-$starterTemplate.json"
$starterModuleCachedValuesPath = Join-Path -Path $releasePath -ChildPath $starterCacheFileName
$cachedStarterModuleConfig = Get-ALZConfig -configFilePath $starterModuleCachedValuesPath

# Getting the user input for the starter module
$starterModuleConfiguration = Request-ALZEnvironmentConfig -configurationParameters $starterModuleParameters -respectOrdering -userInputOverrides $userInputOverrides -treatEmptyDefaultAsValid $true
$starterModuleConfiguration = Request-ALZEnvironmentConfig -configurationParameters $starterModuleParameters -respectOrdering -userInputOverrides $userInputOverrides -userInputDefaultOverrides $cachedStarterModuleConfig -treatEmptyDefaultAsValid $true

# Getting subscription ids
Import-SubscriptionData -starterModuleConfiguration $starterModuleConfiguration -bootstrapConfiguration $bootstrapConfiguration
Expand All @@ -75,14 +97,18 @@ function New-ALZEnvironmentTerraform {
Write-TfvarsFile -tfvarsFilePath $bootstrapTfvarsPath -configuration $bootstrapConfiguration
Write-TfvarsFile -tfvarsFilePath $starterModuleTfvarsPath -configuration $starterModuleConfiguration

# Caching the bootstrap and starter module values paths for retry / upgrade scenarios
Write-ConfigurationCache -filePath $bootstrapCachedValuesPath -configuration $bootstrapConfiguration
Write-ConfigurationCache -filePath $starterModuleCachedValuesPath -configuration $starterModuleConfiguration

# Running terraform init and apply
Write-InformationColored "Thank you for providing those inputs, we are now initializing and applying Terraform to bootstrap your environment..." -ForegroundColor Green -InformationAction Continue

if($autoApprove) {
Invoke-Terraform -moduleFolderPath $bootstrapPath -tfvarsFileName "override.tfvars" -autoApprove
Invoke-Terraform -moduleFolderPath $bootstrapPath -tfvarsFileName "override.tfvars" -autoApprove -destroy:$destroy.IsPresent
} else {
Write-InformationColored "Once the plan is complete you will be prompted to confirm the apply. You must enter 'yes' to apply." -ForegroundColor Green -InformationAction Continue
Invoke-Terraform -moduleFolderPath $bootstrapPath -tfvarsFileName "override.tfvars"
Invoke-Terraform -moduleFolderPath $bootstrapPath -tfvarsFileName "override.tfvars" -destroy:$destroy.IsPresent
}
}
}
35 changes: 34 additions & 1 deletion src/ALZ/Private/Request-ALZEnvironmentConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ function Request-ALZEnvironmentConfig {
[Parameter(Mandatory = $false)]
[PSCustomObject] $userInputOverrides = $null,
[Parameter(Mandatory = $false)]
[System.Boolean] $treatEmptyDefaultAsValid = $false
[PSCustomObject] $userInputDefaultOverrides = $null,
[Parameter(Mandatory = $false)]
[System.Boolean] $treatEmptyDefaultAsValid = $false,
[Parameter(Mandatory = $false)]
[switch] $autoApprove
)
<#
.SYNOPSIS
Expand All @@ -23,6 +27,21 @@ function Request-ALZEnvironmentConfig {

$configurations = $configurationParameters.PsObject.Properties

$hasDefaultOverrides = $false
if($userInputDefaultOverrides -ne $null) {
$hasDefaultOverrides = $true
Write-InformationColored "We found you have cached values from a previous run." -ForegroundColor Yellow -InformationAction Continue
$useDefaults = ""
if($autoApprove) {
$useDefaults = "use"
} else {
$useDefaults = Read-Host "Would you like to use these values or see each of them to validate and change them? Enter 'use' to use the cached value or just hit 'enter' to see and validate each value. (use/see)"
}
if($useDefaults.ToLower() -eq "use") {
$userInputOverrides = $userInputDefaultOverrides
}
}

$hasInputOverrides = $false
if($userInputOverrides -ne $null) {
$hasInputOverrides = $true
Expand All @@ -34,6 +53,20 @@ function Request-ALZEnvironmentConfig {

foreach ($configurationValue in $configurations) {
if ($configurationValue.Value.Type -eq "UserInput") {

# Check for and add cached as default
if($hasDefaultOverrides) {
$defaultOverride = $userInputDefaultOverrides.PsObject.Properties | Where-Object { $_.Name -eq $configurationValue.Name }
if($null -ne $defaultOverride) {
if(!($configurationValue.Value.PSObject.Properties.Name -match "DefaultValue")) {
$configurationValue.Value | Add-Member -NotePropertyName "DefaultValue" -NotePropertyValue $defaultOverride.Value
} else {
$configurationValue.Value.DefaultValue = $defaultOverride.Value
}
}
}

# Check for and use override
if($hasInputOverrides) {
$userInputOverride = $userInputOverrides.PsObject.Properties | Where-Object { $_.Name -eq $configurationValue.Name }
if($null -ne $userInputOverride) {
Expand Down
24 changes: 24 additions & 0 deletions src/ALZ/Private/Write-ConfigurationCache.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
function Write-ConfigurationCache {
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory = $false)]
[string] $filePath,

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

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

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

$cache = [PSCustomObject]@{}
foreach ($configurationItem in $configuration.PSObject.Properties) {
$cache | Add-Member -NotePropertyName $configurationItem.Name -NotePropertyValue $configurationItem.Value.Value
}

$cache | ConvertTo-Json | Out-File -FilePath $filePath
}
}
13 changes: 7 additions & 6 deletions src/ALZ/Public/New-ALZEnvironment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ function New-ALZEnvironment {
A json file containing user input overrides for the user input prompts. This will cause the tool to by pass requesting user input for that field and use the value(s) provided. E.g { "starter_module": "basic", "azure_location": "uksouth" }
.PARAMETER autoApprove
Automatically approve the terraform apply.
.PARAMETER destroy
Destroy the terraform environment.
.EXAMPLE
New-ALZEnvironment
.EXAMPLE
Expand Down Expand Up @@ -56,7 +58,10 @@ function New-ALZEnvironment {
[string] $userInputOverridePath = "",

[Parameter(Mandatory = $false)]
[switch] $autoApprove
[switch] $autoApprove,

[Parameter(Mandatory = $false)]
[switch] $destroy
)

Write-InformationColored "Getting ready to create a new ALZ environment with you..." -ForegroundColor Green -InformationAction Continue
Expand All @@ -67,11 +72,7 @@ function New-ALZEnvironment {
}

if($alzIacProvider -eq "terraform") {
if($autoApprove) {
New-ALZEnvironmentTerraform -alzEnvironmentDestination $alzEnvironmentDestination -alzVersion $alzVersion -alzCicdPlatform $alzCicdPlatform -userInputOverridePath $userInputOverridePath -autoApprove
} else {
New-ALZEnvironmentTerraform -alzEnvironmentDestination $alzEnvironmentDestination -alzVersion $alzVersion -alzCicdPlatform $alzCicdPlatform -userInputOverridePath $userInputOverridePath
}
New-ALZEnvironmentTerraform -alzEnvironmentDestination $alzEnvironmentDestination -alzVersion $alzVersion -alzCicdPlatform $alzCicdPlatform -userInputOverridePath $userInputOverridePath -autoApprove:$autoApprove.IsPresent -destroy:$destroy.IsPresent
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/Tests/Unit/Public/New-ALZEnvironment.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,13 @@ InModuleScope 'ALZ' {

Mock -CommandName Write-TfvarsFile -MockWith { }

Mock -CommandName Write-ConfigurationCache -MockWith { }

Mock -CommandName Invoke-Terraform -MockWith { }

Mock -CommandName Import-SubscriptionData -MockWith { }

Mock -CommandName Invoke-Upgrade -MockWith { }
}

It 'should return the output directory on completion' {
Expand All @@ -120,7 +123,7 @@ InModuleScope 'ALZ' {
}

It 'should clone the git repo and apply if terraform is selected' {
New-ALZEnvironment -IaC "terraform" -
New-ALZEnvironment -IaC "terraform"
Assert-MockCalled -CommandName Get-ALZGithubRelease -Exactly 1
Assert-MockCalled -CommandName Invoke-Terraform -Exactly 1
}
Expand Down

0 comments on commit 176ec96

Please sign in to comment.