Invoke-Terraform.psm1
Function Copy-TerraformBinary { param( [parameter(Mandatory)] [string]$TFVersion, [string]$ZipPath ) $installPath = (Get-TerraformConfiguration).TFPath if (-not (Test-Path $installPath -PathType Container)) { if (-not (New-Item -Path $installPath -ItemType 'directory')) { throw "Failed to create $($installPath) preference directory" } } $binary = 'terraform' if ($isWindows) { $binary += '.exe' } $binaryVersion = 'terraform_{0}' -f $TFVersion if ($IsWindows) { $binaryVersion += '.exe' } if ($ZipPath) { $tmpPath = [System.IO.Path]::GetTempPath() [string] $guid = [System.Guid]::NewGuid() $tmpfolder = (Join-Path $tmpPath $guid) Expand-Archive -Path $zipPath -DestinationPath $tmpFolder $sourcePath = (Join-Path $tmpFolder $binary) $destPath = (Join-Path $installPath $binaryVersion) } else { $sourcePath = (Join-Path $installPath $binaryVersion) $destPath = (Join-Path $installPath $binary) } Copy-Item -Path $sourcePath -Destination $destPath -Force # TODO: Is Powershelly way? if (-not $IsWindows) { & chmod +x $destPath } } function Get-TerraformLatestRelease { $headers = @{ Accept = 'application/vnd.github.v3+json' } $response = Invoke-RestMethod 'https://api.github.com/repos/hashicorp/terraform/releases/latest' -Method 'GET' -Headers $headers $latest = $response.name.substring(1) return $latest } Function Get-TerraformPath { param( [parameter(Mandatory)] [string]$TFVersion ) if ($isWindows) { $fileExt = '.exe' } return Join-Path (Get-TerraformConfiguration).TFPath "terraform_$($TFVersion)$($fileExt)" } function Get-TerraformPlatform { if ($IsWindows) { return 'windows' } if ($IsLinux) { return 'linux' } if ($IsMacOS) { return 'darwin' } throw 'Unknown platform.' } Function Get-TerraformVersion { param( [parameter(Mandatory)] [string]$Path ) $terraformVersionFile = Join-Path -Path $Path -ChildPath .terraform-version if (Test-Path -Path $terraformVersionFile -PathType Leaf) { return $terraformVersionFile } $Parent = Split-Path $Path if ($Parent -eq $Home) { return $null } if ($Parent) { return Get-TerraformVersion $Parent } # Shouldn't get here return $null } Function Get-TerraformZip { param( [parameter(Mandatory)] [string]$TFVersion, [switch]$SkipChecksum = $False ) $platform = Get-TerraformPlatform $shaKeyId = ((Get-TerraformConfiguration).HashiCorpPGPKeyId).Substring(10) $archiveName = 'terraform_{0}_{1}_amd64.zip' -f $TFVersion, $platform $zipUrl = '{0}/{1}/terraform_{1}_{2}_amd64.zip' -f (Get-TerraformConfiguration).ReleaseUrl, $TFVersion, $platform $shaUrl = '{0}/{1}/terraform_{1}_SHA256SUMS' -f (Get-TerraformConfiguration).ReleaseUrl, $TFVersion $shaSigUrl = '{0}/{1}/terraform_{1}_SHA256SUMS.{2}.sig' -f (Get-TerraformConfiguration).ReleaseUrl, $TFVersion, $shaKeyId $tmpPath = [System.IO.Path]::GetTempPath() [string] $guid = [System.Guid]::NewGuid() $zipPath = (Join-Path $tmpPath "$($guid).zip") $shaPath = (Join-Path $tmpPath "$($guid)_SHA256SUMS") $shaSigPath = (Join-Path $tmpPath "$($guid)_SHA256SUMS.sig") try { Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath } catch { Write-Error "Unable to request $($zipUrl)" throw $_ } try { Invoke-WebRequest -Uri $shaUrl -OutFile $shaPath } catch { throw "Unable to request $($shaUrl)" } try { Invoke-WebRequest -Uri $shaSigUrl -OutFile $shaSigPath } catch { throw "Unable to request $($shaSigUrl)" } if ( -not (Test-TerraformArchiveChecksum -SkipChecksum:$SkipChecksum -ArchiveName $archiveName -ZipPath $zipPath -SHAPath $shaPath -SHASigPath $shaSigPath) ) { throw 'Terraform Archive failed Checksum test.' } return $zipPath } Function Install-TerraformBinary { param( [parameter(Mandatory)] [string]$TFVersion, [switch]$SkipChecksum = $False, [switch]$SkipCodeSignature = $False ) $zipPath = Get-TerraformZip -TFVersion $TFVersion -SkipChecksum:$SkipChecksum try { Copy-TerraformBinary -TFVersion $TFVersion -ZipPath $zipPath } catch { Write-Error "Unable to copy binary from $zipPath." throw $_ } if (-not (Test-TerraformPath -TFVersion $TFVersion)) { throw "Failed to install Terraform $($TFversion) binary." } if ( -not (Test-TerraformCodeSignature -TFVersion $TFVersion -SkipCodeSignature:$SkipCodeSignature)) { Uninstall-Terraform -TFVersion $TFVersion throw "Terraform $($TFversion) fail to pass Code Signature test. Uninstalling." } } function Test-TerraformArchiveChecksum { param ( [parameter(Mandatory)] [string]$ArchiveName, [parameter(Mandatory)] [string]$ZipPath, [parameter(Mandatory)] [string]$SHAPath, [parameter(Mandatory)] [string]$SHASigPath, [switch]$SkipChecksum = $False ) if ($SkipChecksum -or (Get-TerraformConfiguration).SkipChecksum) { Write-Verbose 'Skipping Terraform Archive Checksum test.' return $true } $output = gpg --list-keys (Get-TerraformConfiguration).HashiCorpPGPKeyId 2>&1 if ($LASTEXITCODE -ne 0) { gpg --quiet --keyserver (Get-TerraformConfiguration).PGPKeyServer --recv (Get-TerraformConfiguration).HashiCorpPGPKeyId if ($LASTEXITCODE -ne 0) { throw 'Unable to retrieve HashiCorp key' } } else { # Refresh incase of future revoke user will recevie warning gpg --quiet --keyserver (Get-TerraformConfiguration).PGPKeyServer --recv (Get-TerraformConfiguration).HashiCorpPGPKeyId } $output = gpg --verify $SHASigPath $SHAPath 2>&1 if ($LASTEXITCODE -ne 0) { throw "Unable to verify signature on $($SHAPath) : $($output)" } if (-not ((Get-TerraformConfiguration).SquelchChecksumWarning) -and ($output | Select-String 'WARNING: This key is not certified' -Quiet)) { Write-Warning @' The HashiCorp key has been installed but not certified. Run either of the following - Confirm-TerraformHashiCorpKey - Set-TerraformSquelchChecksumWarning $true '@ } $SHASum = (Get-FileHash $ZipPath).Hash $HashiCorpSHASum = (Get-Content $shaPath | Select-String $ArchiveName).ToString().Split()[0] if ($SHASum -ne $HashiCorpSHASum ) { throw "Unable to verify SHASUM with $($SHAPath)" } Write-Verbose "Terraform archive $($zipPath) passed checksum test" return $true } Function Test-TerraformCodeSignature { param( [parameter(Mandatory)] [string]$TFVersion, [switch]$SkipCodeSignature ) if ($SkipCodeSignature -or (Get-TerraformConfiguration).SkipCodeSignature) { Write-Verbose 'Skipping Code Signature test' return $true } if ($IsWindows) { # HashiCorp started signing with version 0.12.24 # TODO return true and throw a Warning $tfThumbprint = (Get-AuthenticodeSignature -FilePath (Get-TerraformPath -TFVersion $TFVersion)).SignerCertificate.Thumbprint return $tfthumbprint -eq (Get-TerraformConfiguration).HashiCorpWindowsThumbprint } if ($IsMacOs) { if ($PSCmdlet.MyInvocation.BoundParameters['Verbose'].IsPresent) { codesign --verify -d --verbose=2 (Get-TerraformPath -TFVersion $TFVersion) } else { codesign --verify (Get-TerraformPath -TFVersion $TFVersion) } return $LASTEXITCODE -eq 0 } if ($IsLinux) { Write-Verbose 'CodeSignature check at runtime is not supported on Linux.' return $true } Write-Error 'Unable to test terraform CodeSignature. Unknown platform.' throw $_ } Function Test-TerraformPath { param( [parameter(Mandatory)] [string]$TFVersion ) Write-Verbose "Testing path for Terraform version $($TFVersion) " return Test-Path (Get-TerraformPath -TFVersion $TFVersion) -PathType leaf } <# .SYNOPSIS Helper function to sign the Hashi Corp PHP key. .DESCRIPTION Helper function to sign the Hashi Corp PHP key. .EXAMPLE Confirm-TerraformHashicorpKey Runs gpg to sign a HasiCorp PGP key. .INPUTS None. You cannot pipe objects to Confirm-TerraformHashicorpKey. .OUTPUTS None. Confirm-TerraformHashicorpKey returns nothing. .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> Function Confirm-TerraformHashicorpKey { & gpg --sign-key (Get-TerraformConfiguration).HashiCorpPGPKeyId } function Get-TerraformConfiguration { Import-Configuration } <# .SYNOPSIS Get stable path for terraform binary (ie. terraform.exe or terraform) .DESCRIPTION Get stable path for terraform binary (ie. terraform.exe or terraform) .EXAMPLE Get-TerraformStableBinary Returns stable path for terraform binary .INPUTS None. You cannot pipe objects to Set-TerraformStableBinary. .OUTPUTS Returns a path. .LINK Set-TerraformStableBinary .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> function Get-TerraformStableBinary { $installPath = (Get-TerraformConfiguration).TFPath $binary = 'terraform' if ($isWindows) { $binary += '.exe' } $binPath = Join-Path -Path $installPath -ChildPath $binary if (Test-Path $binPath -PathType leaf ) { return $binPath } Write-Error @" Terraform static binary not set. Run either: - Set-TerraformStableBinary or: - Set-TerraformAutoStableBinary `$true - Set-TerraformVersion -TFVersion:[TFversion] or: - Set-TerraformAutoStableBinary `$true - Install-Terraform "@ throw '' } <# .SYNOPSIS Install a version of terraform. .DESCRIPTION Install a version of terraform. .PARAMETER TFVersion The version of terraform to install. .PARAMETER SkipChecksum Skip release archive checksum verification. .PARAMETER SkipCodeSignature Skip code signature verifcation. .EXAMPLE Install-Terraform -TFVersion 0.14.7 Installs terraform version 0.14.7 .INPUTS None. You cannot pipe objects to Install-Terraform. .OUTPUTS None. Install-Terraform returns nothing. .LINK Uninstall-Terraform .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> Function Install-Terraform { param( [string]$TFVersion, [switch]$SkipChecksum = $False, [switch]$SkipCodeSignature = $False ) if (-not $TFVersion) { $TFVersion = Get-TerraformLatestRelease } if (Test-TerraformPath -TFVersion $TFVersion) { Write-Verbose "Terraform $($TFversion) already installed." return } Write-Verbose "Installing terraform version $($TFVersion)" Install-TerraformBinary -TFVersion $TFVersion -SkipChecksum:$SkipChecksum -SkipCodeSignature:$SkipCodeSignature } Function Invoke-Terraform { <# .SYNOPSIS Run terraform version based on user preference. .DESCRIPTION Run terraform version based on user preference. Additional parameters are passed to the terraform binary. .PARAMETER TFVersion Override preferred version of terraform to run. .PARAMETER SkipCodeSignature Skip code signature verifcation. .EXAMPLE Invoke-Terraform -TFVersion 0.14.7 Runs terraform version 0.14.7 .EXAMPLE Invoke-Terraform Runs terraform version based on user preference or default preference. .INPUTS None. You cannot pipe objects to Invoke-Terraform. .OUTPUTS None. Invoke-Terraform returns nothing. .LINK Install-Terraform .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> param( [string]$TFVersion, [switch]$SkipCodeSignature = $False ) # HACK: # # Due to positional parameters the first unnamed parameter # is passed to $TFVersion. This catches non version parameters # intended to pass to the terraform run. if (-not ($TFVersion -match '^\d\d?\.\d\d?\.\d\d?$')) { # Build $TFargs and null $TFVersion for default preference $TFargs = @($TFVersion) + $args $TFVersion = $null } else { $TFArgs = $args } $terraformVersionFile = Get-TerraformVersion -Path (Get-Item .).FullName if ($terraformVersionFile -and (-not $TFVersion)) { $TFVersion = Get-Content $terraformVersionFile # TODO regex validate the version Write-Verbose "Found .terraform-version $TFVersion" } # If Version still isn't set if (-not $TFVersion) { $TFVersion = (Get-TerraformConfiguration).TFVersion } if (-not (Test-TerraformPath -TFVersion $TFVersion)) { Write-Warning "Terraform version $($TFVersion) not found." if ((Get-TerraformConfiguration).AutoDownload) { Write-Verbose "Auto downloading terraform version $($TFVersion)" Install-Terraform -TFVersion $TFVersion } else { Write-Error @" Terraform version $($TFVersion) not installed. Run either: - Install-Terraform -TFVersion $($TFVersion) or: - Set-TerraformAutoDownload `$true "@ throw '' } } if (-not (Test-TerraformCodeSignature -TFVersion $TFVersion -SkipCodeSignature:$SkipCodeSignature)) { throw 'Unable to confirm Code Signature of terraform binary' } & (Get-TerraformPath -TFVersion $TFVersion) $TFargs } Set-Alias -Name terraform -Value Invoke-Terraform <# .SYNOPSIS Set auto download configuration. .DESCRIPTION Set auto download configuration. .PARAMETER AutoDownload Either true or false. .EXAMPLE Set-TerraformAutoDownload $true Sets auto download configuration to true .INPUTS None. You cannot pipe objects to Set-TerraformAutoDownload. .OUTPUTS None. Set-TerraformAutoDownload returns nothing. .LINK Get-TerraformConfiguration .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> function Set-TerraformAutoDownload { [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [parameter(Mandatory)] [boolean]$AutoDownload ) begin { Write-Debug -Message 'Beginning' $configurationPath = Get-ConfigurationPath } process { if ($PSCmdlet.ShouldProcess($configurationPath, "Setting AutoDownload configuration to $($AutoDownload)")) { Write-Verbose "Setting AutoDownload configuration to $($AutoDownload)" Set-TerraformConfiguration @{ AutoDownload = $AutoDownload } -Confirm:$False } } end { Write-Debug -Message 'Ending' } } <# .SYNOPSIS Set auto switch binary configuration. .DESCRIPTION Set auto switch binary configuration. .PARAMETER AutoStableBinary Either true or false. .EXAMPLE Set-TerraformAutoStableBinary $true Sets auto switch binary configuration to true .INPUTS None. You cannot pipe objects to Set-TerraformAutoStableBinary. .OUTPUTS None. Set-TerraformAutoStableBinary returns nothing. .LINK Get-TerraformConfiguration .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> function Set-TerraformAutoStableBinary { [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [parameter(Mandatory)] [boolean]$AutoStableBinary ) begin { Write-Debug -Message 'Beginning' $configurationPath = Get-ConfigurationPath } process { if ($PSCmdlet.ShouldProcess($configurationPath, "Setting AutoStableBinary configuration to $($AutoStableBinary)")) { Write-Verbose "Setting AutoStableBinary configuration to $($AutoStableBinary)" Set-TerraformConfiguration @{ AutoStableBinary = $AutoStableBinary } -Confirm:$False } } end { Write-Debug -Message 'Ending' } } function Set-TerraformConfiguration { [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [parameter(Mandatory)] [hashtable]$Configuration ) begin { Write-Debug -Message 'Beginning' $configurationPath = Get-ConfigurationPath } process { # Merge existing configuration with updates $existingConfiguration = Import-Configuration $existingConfiguration.keys | Where-Object { $_ -notin $Configuration.keys } | ForEach-Object { $Configuration.Add($_, $existingConfiguration.Item($_) ) } # Drop keys not defined by default configuration $remove = $Configuration.keys | Where-Object { $_ -notin $existingConfiguration.keys } $remove | ForEach-Object { $Configuration.Remove($_) } if ($PSCmdlet.ShouldProcess($configurationPath, "Setting Configuration configuration to $($Configuration | ConvertTo-Json -Depth 5)")) { Write-Verbose "Setting configuration to $($Configuration | ConvertTo-Json -Depth 5)" $Configuration | Export-Configuration } } end { Write-Debug -Message 'Ending' } } <# .SYNOPSIS Set squelch checksum warning configuration. .DESCRIPTION Set squelch checksum warning configuration. .PARAMETER SquelchChecksumWarning Either true or false. .EXAMPLE Set-TerraformSquelchChecksumWarning $true Set squelch checksum warning configuration to true .INPUTS None. You cannot pipe objects to Set-TerraformSquelchChecksumWarning. .OUTPUTS None. Set-TerraformSquelchChecksumWarningreturns nothing. .LINK Get-TerraformConfiguration .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> function Set-TerraformSquelchChecksumWarning { [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [parameter(Mandatory)] [boolean]$SquelchChecksumWarning ) begin { Write-Debug -Message 'Beginning' $configurationPath = Get-ConfigurationPath } process { if ($PSCmdlet.ShouldProcess($configurationPath, "Setting SquelchChecksumWarning configuration to $($SquelchChecksumWarning)")) { Write-Verbose "Setting SquelchChecksumWarning configuration to $($SquelchChecksumWarning)" Set-TerraformConfiguration @{ SquelchChecksumWarning = $SquelchChecksumWarning } -Confirm:$False } } end { Write-Debug -Message 'Ending' } } <# .SYNOPSIS Set version for stable terraform binary (ie. terraform.exe or terraform) .DESCRIPTION Set version for stable terraform binary (ie. terraform.exe or terraform) .PARAMETER TFVersion The preferred version. .PARAMETER SkipChecksum Skip release archive checksum verification. .PARAMETER SkipCodeSignature Skip code signature verifcation. .EXAMPLE Set-TerraformStableBinary Sets the latest terraform version to the static name terraform.exe or terraform .EXAMPLE Set-TerraformStableBinary -TFVersion 0.14.7 Sets terraform version 0.14.7 to the static name terraform.exe or terraform .INPUTS None. You cannot pipe objects to Set-TerraformStableBinary. .OUTPUTS None. Set-TerraformStableBinary returns nothing. .LINK Get-TerraformStableBinary .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> function Set-TerraformStableBinary { [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [string]$TFVersion, [switch]$SkipChecksum = $False, [switch]$SkipCodeSignature = $False ) begin { Write-Debug -Message 'Beginning' if (-not $TFVersion) { $TFVersion = Get-TerraformLatestRelease } } process { if ($PSCmdlet.ShouldProcess($configurationPath, "Setting static terraform binary to TFVesion $($TFVersion)")) { if (-not (Test-TerraformPath -TFVersion $TFVersion)) { Write-Verbose "Installing terraform version $($TFVersion)" Install-TerraformBinary -TFVersion $TFVersion -SkipChecksum:$SkipChecksum -SkipCodeSignature:$SkipCodeSignature } Copy-TerraformBinary -TFVersion $TFVersion } } end { Write-Debug -Message 'Ending' } } <# .SYNOPSIS Set configuration version for terraform. .DESCRIPTION Set configuration version for terraform. .PARAMETER TFVersion The preferred version. .EXAMPLE Set-TerraformVersion -TFVersion 0.14.7 Sets configuration version for terraform to 0.14.7 .INPUTS None. You cannot pipe objects to Set-TerraformVersion. .OUTPUTS None. Set-TerraformVersion returns nothing. .LINK Get-TerraformConfiguration .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> Function Set-TerraformVersion { [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [parameter(Mandatory)] [string]$TFVersion ) begin { Write-Debug -Message 'Beginning' $configurationPath = Get-ConfigurationPath $AutoStableBinary = (Get-TerraformConfiguration).AutoStableBinary } process { if ($PSCmdlet.ShouldProcess($configurationPath, "Setting TFVesion configuration version to $($TFVersion)")) { Write-Verbose "Setting TFVersion configuration version to $($TFVersion)" Set-TerraformConfiguration @{ TFVersion = $TfVersion } -Confirm:$False } if ($AutoStableBinary) { Set-TerraformStableBinary -TFVersion $TFVersion -Confirm:$ConfirmPreference } } end { Write-Debug -Message 'Ending' } } Set-Alias -Name Switch-Terraform -Value Set-TerraformVersion <# .SYNOPSIS Uninstall a version of terraform. .DESCRIPTION Unnstall a version of terraform. .PARAMETER TFVersion The version of terraform to uninstall. .EXAMPLE Uninstall-Terraform -TFVersion 0.14.7 Uninstalls terraform version 0.14.7 .INPUTS None. You cannot pipe objects to Uninstall-Terraform. .OUTPUTS None. Uninstall-Terraform returns nothing. .LINK Install-Terraform .LINK Online version: https://github.com/pearcec/Invoke-Terraform #> Function Uninstall-Terraform { param( [parameter(Mandatory)] [string]$TFVersion ) if (Test-TerraformPath -TFVersion $TFVersion) { Write-Verbose "Uninstalling terraform version $($TFVersion)" Remove-Item (Get-TerraformPath -TFVersion $TFVersion) -Force } else { Write-Warning "Unable to uninstall terraform. Version ($TFVersion) not found." } } $PSDefaultParameterValues = @{ 'Invoke-WebRequest:Verbose' = $false 'Invoke-WebRequest:Debug' = $false } $ProgressPreference = 'SilentlyContinue' |