Set-TFAlias.ps1

#Requires -Version 7.0.0
Set-StrictMode -Version 3.0
<#
.SYNOPSIS
    Set "terraform" alias like tfenv
.PARAMETER Initialize
    Setup TFAlias environment
.PARAMETER Latest
    Set alias to the latest Terraform version
.PARAMETER Version
    Set alias to Terraform ver.X.Y.Z
.PARAMETER Pin
    Write current version to .terraform-version file
.PARAMETER Help
     Show help message
.EXAMPLE
    PS > Set-TFAlias -Initialize # Initialize alias
 
    PS > Set-TFAlias -Latast # Download latest version and set alias
    PS > terraform version
    Terraform vX.Y.Z
 
    PS > Set-TFAlias -Version 1.2.3 # Download ver.1.2.3 and set alias
    PS > terraform version
    Terraform v1.2.3
#>

function Set-TFAlias {
    [CmdletBinding(DefaultParameterSetName = 'Help')]
    param (
        [Parameter(ParameterSetName = 'Initialize', Mandatory = $true)]
        [Switch]$Initialize,
        [Parameter(ParameterSetName = 'Latest', Mandatory = $true)]
        [Switch]$Latest,
        [Parameter(ParameterSetName = 'Version', Mandatory = $true)]
        [semver]$Version,
        [Parameter(ParameterSetName = 'Pin', Mandatory = $true)]
        [Switch]$Pin,
        [Parameter(ParameterSetName = 'Help', Mandatory = $false)]
        [Switch]$Helps,
        [Parameter(ParameterSetName = 'Initialize', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Latest', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Version', Mandatory = $false)]
        [Switch]$Force
    )
    switch ($PSCmdlet.ParameterSetName) {
        'Initialize' {
            InvokeTFAliasInitialize
            return
        }
        'Latest' {
            InvokeTFAliasLatestVersion -IsForce $Force
            return
        }
        'Version' {
            InvokeTFAliasVersion -Version $Version -IsForce $Force
            return
        }
        'Pin' {
            InvokeTFAliasPin
            return
        }
        Default {
            ShowHelpMessage
        }
    }
}

function ShowHelpMessage () {
    @"
Set "terraform" alias like tfenv
 
Usage:
  -Initialize Setup TFAlias environment
  -Latast Set alias to the latest Terraform version
  -Version X.Y.Z Set alias to Terraform ver.X.Y.Z
  -Pin Write current version to .terraform-version file
  -Help Show this message
 
Example:
  PS > Set-TFAlias -Initialize # Initialize alias
  PS > Set-TFAlias -Latast # Download latest version and set alias
  PS > terraform version
  Terraform vX.Y.Z
  PS > Set-TFAlias -Version 1.2.3 # Download ver.1.2.3 and set alias
  PS > terraform version
  Terraform v1.2.3
"@
 | Out-Host
}

function InvokeTFAliasInitialize() {
    $ailasAppPath = GetTFAliasAppPath
    $shimBinPath = GetShimBinPath

    # Check Alias path
    if (-not (Test-Path -LiteralPath $ailasAppPath -PathType Container)) {
        WriteInfo ("Create path : {0}" -f $ailasAppPath)
        [void](New-Item -Path $ailasAppPath -ItemType Directory)
    }
    # Check shim bin path
    if (-not (Test-Path -LiteralPath $shimBinPath -PathType Container)) {
        WriteInfo ("Create path : {0}" -f $shimBinPath)
        [void](New-Item -Path $shimBinPath -ItemType Directory)
    }
    # Copy templates if nessecary
    InstallTemplateFiles -Destination $shimBinPath

    # Check current version
    $version = Get-TFInstalledAlias -Current
    if ($version) {
        # Set current version silently
        Write-Verbose ('Use current Terraform v{0}.' -f ($version.Version))
        [void](InvokeTFAliasVersion -Version $version.Version -IsForce $true 6>&1)
        return
    }
    WriteInfo ('Use the latest version of Terraform.')
    InvokeTFAliasLatestVersion -IsForce $true
}

function InvokeTFAliasLatestVersion ([bool]$IsForce) {
    $ailasAppPath = GetTFAliasAppPath

    # Check Alias path
    if (-not (Test-Path -LiteralPath $ailasAppPath -PathType Container)) {
        Write-Warning ("Alias path {0} not found." -f $ailasAppPath)
        Write-Warning "Do Set-TFAlias -Initialize first."
        return  
    }

    # get the latest version
    $version = Find-TFRelease -Latest | Select-Object -ExpandProperty Version
    Writeinfo ("Terraform v{0} is the latest version." -f $version)
    InvokeTFAliasVersion -Version $version -IsForce $IsForce
}

function InvokeTFAliasVersion ([semver]$Version, [bool]$IsForce) {
    $ailasAppPath = GetTFAliasAppPath

    # Check Alias path
    if (-not (Test-Path -LiteralPath $ailasAppPath -PathType Container)) {
        Write-Warning ("Alias path {0} not found." -f $ailasAppPath)
        Write-Warning "Do Set-TFAlias -Initialize first."
        return
    }

    # Check version exists
    if (-not (Find-TFRelease -Version $Version)) {
        Write-Warning ("Terraform v{0} not found." -f $Version)
        return
    }

    # Check .terraform-version file
    if (-not $IsForce) {
        $resultFromFile = GetVersionFromVersionFile
        if ($resultFromFile.Version) {
            # override
            Write-Warning ($resultFromFile.Message)
            Write-Warning ('Override version {0} to {1}' -f $Version, ($resultFromFile.Version) )
            $Version = $resultFromFile.Version
        }
        if (-not $resultFromFile.Version -and -not [string]::IsNullOrEmpty( $resultFromFile.FilePath) ) {
            # something failed. show warning only
            Write-Warning ($resultFromFile.Message)
        }
    }

    # Check terraform binary
    $binaryName = GetTerraformBinaryName
    $versionPath = Join-Path $ailasAppPath "$Version"
    $versionBinaryPath = Join-Path $versionPath $binaryName
    if (-not (Test-Path -LiteralPath $versionPath -PathType Container)) {
        [void](New-Item -Path $versionPath -ItemType Directory)  
    }
    if (-not (Test-Path -LiteralPath $versionBinaryPath -PathType Leaf)) {
        # Install Terraform
        WriteInfo ("Install Terraform v{0} to {1}..." -f $Version, $versionPath)
        Save-TFBinary -Version $Version -DestinationPath $versionPath  
    }

    # update version file
    UpdateVersionFile -Version $Version

    # Set alias
    Writeinfo ("Set v{0} terraform alias." -f $Version)
    DoSetAlias
}

function InvokeTFAliasPin () {
    $currentVersion = Get-TFInstalledAlias -Current
    if (-not $currentVersion) {
        Write-Warning "Failed to get current version."
        return
    }
    $filePath = [System.IO.Path]::Join($pwd.Path, '.terraform-version')
    WriteInfo ('Pinned version by writing "{0}" to {1}' -f $currentVersion.Version, $filePath )
    $currentVersion.Version.ToString() | Out-File -FilePath $filePath -NoNewline -Force
}

function GetShimBinPath () {
    return [System.IO.Path]::Join((GetTFAliasRoot), 'bin')
}

function CopyFileWithTimeStampCheck ([string]$ParentPath, [string]$FileName, [string]$Destination) {
    $sourcePath = [System.IO.Path]::Combine($ParentPath, $FileName)
    $destPath = [System.IO.Path]::Combine($Destination, $FileName)
    if (-not [System.IO.File]::Exists($sourcePath)) {
        Write-Warning ('Source file {0} not found.' -f $sourcePath)
        return
    }
    if (-not [System.IO.File]::Exists($destPath)) {
        Write-Verbose ('Copy {0} to {1}' -f $sourcePath, $destPath)
        [System.IO.File]::Copy($sourcePath, $destPath, $true)
        return
    }
    if (-not ([System.IO.File]::GetLastWriteTime($sourcePath) -eq [System.IO.File]::GetLastWriteTime($destPath))) {
        Write-Verbose ('Copy {0} to {1}' -f $sourcePath, $destPath)
        [System.IO.File]::Copy($sourcePath, $destPath, $true)
        return
    }
}

function InstallTemplateFiles ([string]$Destination) {
    $templatePath = [System.IO.Path]::Join($PSScriptRoot, 'Templates')
    # All platforms
    CopyFileWithTimeStampCheck -ParentPath $templatePath -FileName 'terraform.ps1' -Destination $Destination
    # WindowsOnly
    if ($IsWindows) {
        CopyFileWithTimeStampCheck -ParentPath $templatePath -FileName 'terraform.cmd' -Destination $Destination
        CopyFileWithTimeStampCheck -ParentPath $templatePath -FileName 'tfalias.ps1' -Destination $Destination
        CopyFileWithTimeStampCheck -ParentPath $templatePath -FileName 'tfalias.cmd' -Destination $Destination
    }
}

function GetVersionFromVersionFile () {
    # Note.1 : tfenv searches .terraform-version in two phases, from $pwd recursively and from $HOME recursively.
    # But this function does not search $HOME recursively, this is intentional.
    # Note.2 : Must use Test-Path to resolve relative path.
    $testPath = $pwd.Path
    do {
        Write-Verbose ('Seacrh .terraform-version : {0}' -f $testPath)
        if (Test-Path -Path ([System.IO.Path]::Join($testPath, '.terraform-version')) -PathType Leaf) {
            break
        }
        $testPath = [System.IO.Path]::GetDirectoryName($testPath)
    } while (-not [string]::IsNullOrEmpty($testPath))
    if ([string]::IsNullOrEmpty($testPath)) {
        Write-Verbose ('Seacrh .terraform-version : {0}' -f $HOME)
        if (Test-Path -Path ([System.IO.Path]::Join($HOME, '.terraform-version')) -PathType Leaf) {
            $testPath = $HOME
        }
    }
    # .terraform-version not found
    if ([string]::IsNullOrEmpty($testPath)) {
        Write-Verbose '.terraform-version not found'
        return [PSCustomObject]@{
            Version  = $null
            FilePath = ''
            Message  = '.terraform-version not found'
        }
    }
    # .terraform-version found
    Write-Verbose ('.terraform-version is detected at {0}' -f $testPath)
    $fileVersion = Get-TFVersionFromFile -LiteralPath ([System.IO.Path]::Join($testPath, '.terraform-version'))
    if (-not $fileVersion) {
        return [PSCustomObject]@{
            Version  = $null
            FilePath = [System.IO.Path]::Join($testPath, '.terraform-version')
            Message  = '.terraform-version is detected, but failed to parse.'
        }

    }
    return [PSCustomObject]@{
        Version  = $fileVersion
        FilePath = [System.IO.Path]::Join($testPath, '.terraform-version')
        Message  = 'Preferred version.{0} is detected from {1}.' -f $fileVersion, ([System.IO.Path]::Join($testPath, '.terraform-version'))
    }
}

function UpdateVersionFile ([semver]$Version) {
    $versionFilePath = GetTFAliasVersionFilePath
    Write-Verbose ("Update version {0} to {1}" -f $Version, $versionFilePath)
    $Version.ToString() | Out-File -FilePath $versionFilePath -NoNewline
}

function DoSetAlias () {
    # Set-Alias global
    $targetPath = [System.IO.Path]::Join((GetShimBinPath), 'terraform.ps1')
    Set-Alias -Name 'terraform' -Value $targetPath -Scope Global    
    # Register auto completion
    Register-TFArgumentCompleter
}