TFSwitchPS.psm1

#Region Dependencies
$choco = Get-Command 'choco.exe' -ErrorAction SilentlyContinue
if ( $choco ) {
    $chocoInstalledTerraform = . {choco list -local -allversions --limitoutput --exact 'terraform'} #| Where-Object { $_ -match "\d{1,} packages installed" }
    if ( $chocoInstalledTerraform ) {
        Write-Warning 'Chocolatey Terraform Packages detected. Uninstall in order to use tfswitch. (Command: "choco uninstall terraform -a -f -y" )'
        $choco
        exit 1
    }
}

#endregion

#region Configuration
$env:TFSWITCH_BASEDIR = ( Resolve-Path ~\.terraform ).Path

#endregion

#region LoadFunctions
$PublicFunctions = @( Get-ChildItem -Path "$PSScriptRoot/Public/*.ps1" -ErrorAction SilentlyContinue )
$PrivateFunctions = @( Get-ChildItem -Path "$PSScriptRoot/Private/*.ps1" -ErrorAction SilentlyContinue )

# Dot source the functions
foreach ($file in @($PublicFunctions + $PrivateFunctions)) {
    try {
        . $file.FullName
    }
    catch {
        $exception = ([System.ArgumentException]"Function not found")
        $errorId = "Load.Function"
        $errorCategory = 'ObjectNotFound'
        $errorTarget = $file
        $errorItem = New-Object -TypeName System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $errorTarget
        $errorItem.ErrorDetails = "Failed to import function $($file.BaseName)"
        throw $errorItem
    }
}
# Export-ModuleMember -Function $PublicFunctions.BaseName -Alias *
#endregion
Function Set-TerraformVersion {
    [CmdletBinding(DefaultParameterSetName = 'none')]
    [alias('tfswitch','tfs')]
    param (
        # Install a version of terraform
        [Parameter(ParameterSetName = 'Install')]
        [alias('i')]
        [switch]
        $Install,

        [Parameter(ParameterSetName = 'Uninstall')]
        [alias('d')]
        [switch]
        $Uninstall,

        # List installed TF Versions
        [Parameter(ParameterSetName = 'List')]
        [alias('l')]
        [switch]
        $List,

        # Set the active version of Terraform
        [Parameter(ParameterSetName = 'Set')]
        [alias('s')]
        [switch]
        $Set,

        # Set the active version of Terraform
        [Parameter(ParameterSetName = 'Unset')]
        [alias('u', 'clear')]
        [switch]
        $UnSet,

        # Specific version of Terraform to view, install, or set as active
        [Parameter(ParameterSetName = 'Set',Position = 0, Mandatory = $true)]
        [Parameter(ParameterSetName = 'Install', Position = 0, Mandatory = $true)]
        [Parameter(ParameterSetName = 'List', Position = 0)]
        [Parameter(ParameterSetName = 'Uninstall', Position = 0, Mandatory = $true)]
        [Parameter(ParameterSetName = 'none', Position = 0)]
        [alias('v')]
        [string]
        $Version,

        #When listing versions, use to get a list of versions available for install
        [Parameter(ParameterSetName = 'List')]
        [alias('r')]
        [switch]
        $Remote
    )
    Begin {
        $baseInstallDir = $home + '\.terraform'
        if ( -not ( Test-Path $baseInstallDir ) ) {
            Write-Verbose 'Terraform Base Directory Missing'
            New-Item $baseInstallDir -Force -ItemType Directory | Out-Null
        }

    }
    Process {

        if ( $PSCmdlet.ParameterSetName -eq 'none' ) {
            Write-Verbose 'Running Workflow [default]'
            if ( $Version ) {
                tfswitch -Set -Version $Version
            } else {
                Get-TerraformActiveVersion
            }
        }
        if ( $PSCmdlet.ParameterSetName -eq 'Install' ) {
            Write-Verbose 'Running Workflow [Install]'
            $tfTargetVersion = Get-TerraformRemoteVersionList -Version $Version

            if ( [string]::IsNullOrEmpty($tfTargetVersion) ) {
                Write-Warning "Terraform version [$Version] Not found"
                return
            }
            if ( $tfTargetVersion.isInstalled ) {
                return $tfTargetVersion
            } else {
                Install-TerraformVersion -Version $Version
            }
        }
        if ( $PSCmdlet.ParameterSetName -eq 'Uninstall' ) {
            Write-Verbose 'Running Workflow [Uninstall]'
            $tfTargetVersion = Get-TerraformInstalledVersionList -Version $Version

            if ( [string]::IsNullOrEmpty($tfTargetVersion) ) {
                Write-Warning "Terraform version [$Version] Not found"
                return
            }
            if ( $tfTargetVersion.isActive ) {
                Write-Warning 'Version Currently set as active. Unset and try again.'
                return
            }

            $versionPath = Split-Path $tfTargetVersion.Path

            try {
                Remove-Item $versionPath -Force -Confirm:$false -Recurse -ErrorAction Stop
                Write-Warning "Terraform Version [$Version] deleted successfully"
            }
            catch {
                Write-Warning "Failed to delete Terraform Version [$Version]"
            }
        }
        if ( $PSCmdlet.ParameterSetName -eq 'Set' ) {
            Write-Verbose 'Running Workflow [Set]'
            Set-TerraformActiveVersion -Version $Version
            Get-TerraformActiveVersion
        }
        if ( $PSCmdlet.ParameterSetName -eq 'Unset' ) {
            Write-Verbose 'Running Workflow [Unset]'
            Clear-TerraformActiveVersion
            Get-TerraformActiveVersion
        }
        if ( $PSCmdlet.ParameterSetName -eq 'List' ) {
            Write-Verbose 'Running workflow [List]'
            if ( $Remote ) {
                if ( $Version ) {
                    Get-TerraformRemoteVersionList -Version $Version
                } else {
                    Get-TerraformRemoteVersionList
                }
            } else {
                if ( $Version ) {
                    Get-TerraformInstalledVersionList -Version $Version
                }else {
                    Get-TerraformInstalledVersionList
                }
            }
        }
    }
    End {

    }
}
Function Clear-TerraformActiveVersion {
    [CmdletBinding()]
    [OutputType([void])]
    param (
    )
    Process {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting Function"
        Write-Verbose "Clearing Active Terraform Version State"
        Remove-Item Env:\TFSWITCH_PATH -ErrorAction SilentlyContinue
        Remove-Item Env:\TFSWITCH_VERSION -ErrorAction SilentlyContinue
        $pathTemp = $env:Path.Clone()
        $env:Path = ($pathTemp.Split(';') | Where-Object {$_ -notmatch "\.terraform" }) -join ';'
        [System.Environment]::SetEnvironmentVariable( 'TFSWITCH_VERSION', $null, [System.EnvironmentVariableTarget]::User)
        [System.Environment]::SetEnvironmentVariable('path', $env:Path ,[System.EnvironmentVariableTarget]::User)
    }
}

Function Convertfrom-TerraformHTML {
    [CmdletBinding()]
    Param(
        $HTMLInput
    )
    Process {
        $versionName =$htmlInput.trim().TrimEnd('</a>').Split('>')[1]
        $removeVersion = $htmlInput.TrimStart().Split('"')[1].Split('/').Where({$_})[-1]
        $versionPath = $htmlInput.Trim().Split('"')[1]
        $os = if($IsWindows){'windows'}elseif($IsLinux){'linux'}else{throw 'invalid OS'}
        $arch = if([Environment]::Is64BitOperatingSystem){'amd64'}else{'386'}

        $href = 'https://releases.hashicorp.com{0}{1}_{2}_{3}.zip' -f $versionPath, $versionName,$os,$arch
        New-Object psobject -Property ([ordered]@{
            Version = $htmlInput.TrimStart().Split('"')[1].Split('/').Where({$_})[-1]
            Name = $versionName
            isInstalled = [bool](Get-TerraformInstalledVersionList -Version $removeVersion -Verbose:$false -WarningAction SilentlyContinue)
            Link = $href
        })
    }
}
Function Get-TerraformActiveVersion{
    [CmdletBinding()]
    Param()
    # $tfVersionTest = Invoke-Expression "terraform --version"
    
    try {
        Invoke-Expression "terraform --version" | Where-Object { $_ -match 'Terraform v\d{1,}\.\d{1,}\.\d{1,}' }
    } catch {
        Write-Warning 'Terraform not installed or path not set'
    }
}

Function Get-TerraformInstalledVersionList {
    [CmdletBinding()]
    param (

        [Parameter()]
        [string]
        $Version

    )
    Begin {

        Write-Verbose "[$($MyInvocation.MyCommand)] Starting Function"
    
    }
    Process {

        $tfVersions = Get-ChildItem $env:TFSWITCH_BASEDIR -Recurse -File -Filter 'terraform.exe' -ErrorAction SilentlyContinue

        if ( !$tfversions ) {

            Write-Warning "No Terraform Install found in location $env:TFSWITCH_BASEDIR"


        } elseif ( $Version ) {

            Write-Verbose "[$($MyInvocation.MyCommand)] Filtering for Version [$Version]"

        }
        
        $return = $tfVersions | ForEach-Object {

            $versionTemp = Split-Path $_ -Parent | Split-Path -Leaf

            New-Object psobject -Property ([ordered]@{

                Version = $versionTemp

                isActive = $versionTemp -eq $env:TFSWITCH_VERSION

                Path = $_.FullName

            })
        }

        if ( $Version ) {

            $return = $return | Where-Object { $_.Version -eq $Version }

            if ( !$return ) {

                Write-Warning "[$Version] version not found locally."

            }
        }
    
        return $return

    }
}

Function Get-TerraformRemoteVersionList{
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Version
    )
    Begin {
        $splat = @{
            Uri = 'https://releases.hashicorp.com/terraform'
            Verbose = $false
            ErrorAction = 'Stop'
        }
        $versionPattern = "<a href=`"/terraform/\d{1,}\.\d{1,}\.\d{1,}\/"

    }
    Process {
        try {
            $tfReturn = Invoke-WebRequest @splat

            if ( $tfReturn.StatusCode -eq '200') {
                $tfReturn.Content -split "`n" |
                    Where-Object { $_ -match $versionPattern -and $_ -like "*$Version*"} |
                        ForEach-Object { Convertfrom-TerraformHTML $_ } |
                            Where-Object {
                                if ( $Version ) {
                                    $_.Version -like $Version
                                } else {
                                    $true
                                }
                            }
            }
        } catch {
            Write-Warning $($_.Exception.Message)
        }
    }
}

Function Install-TerraformVersion {
    [CmdletBinding()]
    # [Alias('Install-TFVersion')]

    param (

        [Parameter(Mandatory)]
        [string]
        $Version

    )
    Begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting Function"

        $splat = @{
            Uri = ''
            OutFile = ''
            Method = 'Get'
            ErrorAction = 'Stop'
            Verbose = $false
            DisableKeepAlive = $true
        }
    }
    Process {
        #Make Directory Structure
        $versionDirectory = Join-Path -Path $env:TFSWITCH_BASEDIR -ChildPath $Version

        New-Item $versionDirectory -Force -ItemType Directory | Out-Null

        #Get Remote Version
        $targetVersion = Get-TerraformRemoteVersionList -Version $Version

        if ( !$targetVersion ) {

            Write-Verbose "[$($MyInvocation.MyCommand)] [$Version] version not found on remote."

            Write-Warning "[$($MyInvocation.MyCommand)] [$Version] version not found"

            return $null
        } else {

            $splat.Uri = $targetVersion.Link

            $splat.OutFile = "$env:TEMP\$($targetVersion.Name).zip"

            try {
                #Download Zip File to temp location
                Invoke-WebRequest @splat

                #Unzip to destination
                Expand-Archive -Path $splat.OutFile -DestinationPath $versionDirectory -Force

                #Validate File
                $validatedFile = Test-TerraformVersion -Version $Version

                if ( $validatedFile ) {

                    Get-TerraformInstalledVersionList -Version $Version

                } else {

                    throw 'File Validation Failed'

                }

                #Clean up
                Remove-Item -Path $splat.OutFile -Force -ErrorAction SilentlyContinue

            }
            catch {

                Write-Warning "Failed to Install terraform version [$Version]"

                Write-Warning ( $_.Exception | Out-String )

                return $null

            }

        }

    }
}

Function Set-TerraformActiveVersion{
    [CmdletBinding()]
    # [Alias('Set-TFActiveVersion')]
    param (
        [Parameter()]
        [string]
        $Version
    )
    Begin {
        Clear-TerraformActiveVersion
        
        $targetVersion = Get-TerraformInstalledVersionList -Version $Version
    }
    Process {
        if ( [string]::IsNullOrEmpty($targetVersion) ) {
            Write-Warning "No active Terraform Version Set"
            return
        } else {
            Write-Verbose "Setting Variable: TFSWITCH_VERSION ;Value: [$Version]"
            $env:TFSWITCH_VERSION = $Version

            Write-Verbose "Setting Variable: TFSWITCH_PATH ;Value: [$(Split-Path $targetVersion.Path)]"
            $env:TFSWITCH_PATH = Split-Path $targetVersion.Path

            Write-Verbose "Setting Variable: PATH ;Value: [`$env:path + ;$env:TFSWITCH_PATH]"
            $env:Path = $env:Path + ";$($env:TFSWITCH_PATH)"

            [System.Environment]::SetEnvironmentVariable('TFSWITCH_VERSION', $env:TFSWITCH_VERSION, [System.EnvironmentVariableTarget]::User)
            [System.Environment]::SetEnvironmentVariable('TFSWITCH_PATH', $env:TFSWITCH_PATH, [System.EnvironmentVariableTarget]::User)
            [System.Environment]::SetEnvironmentVariable('path', $env:Path ,[System.EnvironmentVariableTarget]::User)
        }
    }
}

Function Test-TerraformVersion {
    [CmdletBinding()]
    [OutputType([bool])]
    param (
        [Parameter()]
        [string]
        $Version
    )
    Process {
        $tfVersion = Get-TerraformInstalledVersionList -Version $Version
        $versionTest = Invoke-Expression "$($tfVersion.Path) --version"
        $versionTestVersion = ($versionTest | Where-Object { $_ -match 'Terraform v\d{1,}\.\d{1,}\.\d{1,}' }).Split()[-1].Trim('v')
        [bool]($tfVersion.Version -eq $versionTestVersion)
    }
}