Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add flag for install location (optional) #309

Merged
merged 3 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,36 @@ The installation is minimal and easy.
Once installed, simply select the version you require from the dropdown and start using Terraform.

## Documentation

Click [here](https://tfswitch.warrensbox.com) for our extended documentation.

## NOTE

Going forward we will change the version identifier of `tfswitch` to align with the common go package versioning.
Please be advised to change any automated implementation you might have that is relying on the `tfswitch` version string.
**Old version string:** `0.1.2412`
**New version string:** `v1.0.0` Note the `v` that is preceding all version numbers.

## Installation
`tfswitch` is available as a binary and on various package managers (eg. Homebrew).

`tfswitch` is available as a binary and on various package managers (eg. Homebrew).

## Windows

Download and extract the Windows version of `tfswitch` that is compatible with your system.
We are building binaries for 386, amd64, arm6 and arm7 CPU structure.
See the [release page](https://github.com/warrensbox/terraform-switcher/releases/latest) for your download.

## Homebrew

For macOS or various Linux distributions, Homebrew offers the simplest installation process. <a href="https://brew.sh/" target="_blank">If you do not have homebrew installed, click here</a>.

```ruby
brew install warrensbox/tap/tfswitch
```

## Linux

Installation for Linux operating systems.

```sh
Expand All @@ -59,34 +65,41 @@ Alternatively, you can install the binary from the source <a href="https://githu

See [our installation documentation](https://tfswitch.warrensbox.com/Install) for more details.

> [!IMPORTANT]
> [!IMPORTANT]
> The version identifier of `tfswitch` has changed to align with the common `go` package versioning.
>
> Version numbers will now be prefixed with a `v` - eg. `v1.0.3`.
>
> Please change any automated implementations relying on the `tfswitch` version string.
> Please change any automated implementations relying on the `tfswitch` version string.
>
> **Old version string:** `0.1.2412`
> **New version string:** `v1.0.3`

[Having trouble installing](https://tfswitch.warrensbox.com/Troubleshoot/)

## Quick Start

### Dropdown Menu

Execute `tfswitch` and select the desired Terraform version via the dropdown menu.

### Version on command line

Use `tfswitch 1.7.0` to install Terraform version 1.7.0. Replace the version number as required.

More [usage guide here](https://tfswitch.warrensbox.com/Quick-Start/)

## How to contribute
An open source project becomes meaningful when people collaborate to improve the code.

An open source project becomes meaningful when people collaborate to improve the code.
Feel free to look at the code, critique and make suggestions. Let's make `tfswitch` better!

See step-by-step instructions on how to contribute here: [Contribute](https://tfswitch.warrensbox.com/How-to-Contribute/)

## Additional Info

See how to *upgrade*, *uninstall*, *troubleshoot* here: [More info](https://tfswitch.warrensbox.com/Upgrade-or-Uninstall/)

## Issues
Please open *issues* here: [New Issue](https://github.com/warrensbox/terraform-switcher/issues)

Please open *issues* here: [New Issue](https://github.com/warrensbox/terraform-switcher/issues)
5 changes: 1 addition & 4 deletions lib/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import (

func checkFileExist(file string) bool {
_, err := os.Stat(file)
if err != nil {
return false
}
return true
return err == nil
}

func createFile(path string) {
Expand Down
11 changes: 2 additions & 9 deletions lib/defaults.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package lib

import (
"os"
"runtime"

"github.com/mitchellh/go-homedir"
)

var (
Expand All @@ -21,11 +18,7 @@ const (
func GetDefaultBin() string {
var defaultBin = "/usr/local/bin/terraform"
if runtime.GOOS == "windows" {
home, err := homedir.Dir()
if err != nil {
logger.Fatal("Could not detect home directory.")
os.Exit(1)
}
home := GetHomeDirectory()
defaultBin = home + "/bin/terraform.exe"
}
return defaultBin
Expand All @@ -35,7 +28,7 @@ const (
DefaultMirror = "https://releases.hashicorp.com/terraform"
DefaultLatest = ""
installFile = "terraform"
installPath = ".terraform.versions"
InstallDir = ".terraform.versions"
recentFile = "RECENT"
tfDarwinArm64StartVersion = "1.0.2"
VersionPrefix = "terraform_"
Expand Down
17 changes: 13 additions & 4 deletions lib/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"path/filepath"
"strings"
"sync"

"github.com/mitchellh/go-homedir"
)

// RenameFile : rename file name
Expand Down Expand Up @@ -38,10 +40,7 @@ func RemoveFiles(src string) {
// CheckFileExist : check if file exist in directory
func CheckFileExist(file string) bool {
_, err := os.Stat(file)
if err != nil {
return false
}
return true
return err == nil
}

// Unzip will decompress a zip archive, moving all files and folders
Expand Down Expand Up @@ -205,6 +204,16 @@ func GetCurrentDirectory() string {
return dir
}

// GetHomeDirectory : return the user's home directory
func GetHomeDirectory() string {

homedir, err := homedir.Dir()
if err != nil {
logger.Fatalf("Failed to get user's home directory %v", err)
}
return homedir
}

func unzipFile(f *zip.File, destination string, wg *sync.WaitGroup) error {
defer wg.Done()
// 1. Check if file paths are not vulnerable to Zip Slip
Expand Down
82 changes: 34 additions & 48 deletions lib/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ package lib

import (
"fmt"
"github.com/manifoldco/promptui"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/manifoldco/promptui"

"github.com/hashicorp/go-version"
"github.com/mitchellh/go-homedir"
)

var (
Expand Down Expand Up @@ -38,36 +38,27 @@ func initialize(binPath string) {
}

// getInstallLocation : get location where the terraform binary will be installed,
// will create a directory in the home location if it does not exist
func getInstallLocation() string {
/* get current user */
homeDir, errCurr := homedir.Dir()
if errCurr != nil {
logger.Fatal(errCurr)
os.Exit(1)
}

userCommon := homeDir

// will create the installDir if it does not exist
func getInstallLocation(installPath string) string {
/* set installation location */
installLocation = filepath.Join(userCommon, installPath)
installLocation = filepath.Join(installPath, InstallDir)

/* Create local installation directory if it does not exist */
createDirIfNotExist(installLocation)
return installLocation
}

// install : install the provided version in the argument
func install(tfversion string, binPath string, mirrorURL string) {
func install(tfversion string, binPath string, installPath string, mirrorURL string) {
/* Check to see if user has permission to the default bin location which is "/usr/local/bin/terraform"
* If user does not have permission to default bin location, proceed to create $HOME/bin and install the tfswitch there
* Inform user that they don't have permission to default location, therefore tfswitch was installed in $HOME/bin
* Tell users to add $HOME/bin to their path
*/
binPath = installableBinLocation(binPath)

initialize(binPath) //initialize path
installLocation = getInstallLocation() //get installation location - this is where we will put our terraform binary file
initialize(binPath) //initialize path
installLocation = getInstallLocation(installPath) //get installation location - this is where we will put our terraform binary file

goarch := runtime.GOARCH
goos := runtime.GOOS
Expand Down Expand Up @@ -96,7 +87,7 @@ func install(tfversion string, binPath string, mirrorURL string) {
/* set symlink to desired version */
CreateSymlink(installFileVersionPath, binPath)
logger.Infof("Switched terraform to version %q", tfversion)
addRecent(tfversion) //add to recent file for faster lookup
addRecent(tfversion, installPath) //add to recent file for faster lookup
os.Exit(0)
}

Expand Down Expand Up @@ -138,14 +129,14 @@ func install(tfversion string, binPath string, mirrorURL string) {
/* set symlink to desired version */
CreateSymlink(installFileVersionPath, binPath)
logger.Infof("Switched terraform to version %q", tfversion)
addRecent(tfversion) //add to recent file for faster lookup
addRecent(tfversion, installPath) //add to recent file for faster lookup
os.Exit(0)
}

// addRecent : add to recent file
func addRecent(requestedVersion string) {
func addRecent(requestedVersion string, installPath string) {

installLocation = getInstallLocation() //get installation location - this is where we will put our terraform binary file
installLocation = getInstallLocation(installPath) //get installation location - this is where we will put our terraform binary file
versionFile := filepath.Join(installLocation, recentFile)

fileExist := CheckFileExist(versionFile)
Expand All @@ -161,7 +152,7 @@ func addRecent(requestedVersion string) {
if !validVersionFormat(line) {
logger.Infof("File %q is dirty (recreating cache file)", versionFile)
RemoveFiles(versionFile)
CreateRecentFile(requestedVersion)
CreateRecentFile(requestedVersion, installPath)
return
}
}
Expand All @@ -181,14 +172,14 @@ func addRecent(requestedVersion string) {
}

} else {
CreateRecentFile(requestedVersion)
CreateRecentFile(requestedVersion, installPath)
}
}

// getRecentVersions : get recent version from file
func getRecentVersions() ([]string, error) {
func getRecentVersions(installPath string) ([]string, error) {

installLocation = getInstallLocation() //get installation location - this is where we will put our terraform binary file
installLocation = getInstallLocation(installPath) //get installation location - this is where we will put our terraform binary file
versionFile := filepath.Join(installLocation, recentFile)

fileExist := CheckFileExist(versionFile)
Expand Down Expand Up @@ -225,8 +216,8 @@ func getRecentVersions() ([]string, error) {
}

// CreateRecentFile : create RECENT file
func CreateRecentFile(requestedVersion string) {
installLocation = getInstallLocation() //get installation location - this is where we will put our terraform binary file
func CreateRecentFile(requestedVersion string, installPath string) {
installLocation = getInstallLocation(installPath) //get installation location - this is where we will put our terraform binary file
_ = WriteLines([]string{requestedVersion}, filepath.Join(installLocation, recentFile))
}

Expand All @@ -247,16 +238,11 @@ func ConvertExecutableExt(fpath string) string {
// If not, create $HOME/bin. Ask users to add $HOME/bin to $PATH and return $HOME/bin as install location
func installableBinLocation(userBinPath string) string {

homedir, errCurr := homedir.Dir()
if errCurr != nil {
logger.Fatal(errCurr)
os.Exit(1)
}

homedir := GetHomeDirectory() //get user's home directory
binDir := Path(userBinPath) //get path directory from binary path
binPathExist := CheckDirExist(binDir) //the default is /usr/local/bin but users can provide custom bin locations

if binPathExist == true { //if bin path exist - check if we can write to it
if binPathExist { //if bin path exist - check if we can write to it

binPathWritable := false //assume bin path is not writable
if runtime.GOOS != "windows" {
Expand Down Expand Up @@ -288,38 +274,38 @@ func installableBinLocation(userBinPath string) string {
}

// InstallLatestVersion install latest stable tf version
func InstallLatestVersion(customBinaryPath, mirrorURL string) {
func InstallLatestVersion(customBinaryPath, installPath string, mirrorURL string) {
tfversion, _ := getTFLatest(mirrorURL)
install(tfversion, customBinaryPath, mirrorURL)
install(tfversion, customBinaryPath, installPath, mirrorURL)
}

// InstallLatestImplicitVersion install latest - argument (version) must be provided
func InstallLatestImplicitVersion(requestedVersion, customBinaryPath, mirrorURL string, preRelease bool) {
func InstallLatestImplicitVersion(requestedVersion, customBinaryPath, installPath string, mirrorURL string, preRelease bool) {
_, err := version.NewConstraint(requestedVersion)
if err != nil {
logger.Errorf("Error parsing constraint %q: %v", requestedVersion, err)
}
tfversion, err := getTFLatestImplicit(mirrorURL, preRelease, requestedVersion)
if err == nil && tfversion != "" {
install(tfversion, customBinaryPath, mirrorURL)
install(tfversion, customBinaryPath, installPath, mirrorURL)
}
logger.Errorf("Error parsing constraint %q: %v", requestedVersion, err)
PrintInvalidMinorTFVersion()
}

// InstallVersion install with provided version as argument
func InstallVersion(arg, customBinaryPath, mirrorURL string) {
func InstallVersion(arg, customBinaryPath, installPath string, mirrorURL string) {
if validVersionFormat(arg) {
requestedVersion := arg

//check to see if the requested version has been downloaded before
installLocation := getInstallLocation()
installLocation := getInstallLocation(installPath)
installFileVersionPath := ConvertExecutableExt(filepath.Join(installLocation, VersionPrefix+requestedVersion))
recentDownloadFile := CheckFileExist(installFileVersionPath)
if recentDownloadFile {
ChangeSymlink(installFileVersionPath, customBinaryPath)
logger.Infof("Switched terraform to version %q", requestedVersion)
addRecent(requestedVersion) //add to recent file for faster lookup
addRecent(requestedVersion, installPath) //add to recent file for faster lookup
os.Exit(0)
}

Expand All @@ -329,7 +315,7 @@ func InstallVersion(arg, customBinaryPath, mirrorURL string) {
exist := versionExist(requestedVersion, tflist) // Check if version exists before downloading it

if exist {
install(requestedVersion, customBinaryPath, mirrorURL)
install(requestedVersion, customBinaryPath, installPath, mirrorURL)
} else {
logger.Fatal("The provided terraform version does not exist.\n Try `tfswitch -l` to see all available versions")
os.Exit(1)
Expand All @@ -346,11 +332,11 @@ func InstallVersion(arg, customBinaryPath, mirrorURL string) {
// InstallOption displays & installs tf version
/* listAll = true - all versions including beta and rc will be displayed */
/* listAll = false - only official stable release are displayed */
func InstallOption(listAll bool, customBinaryPath, mirrorURL string) {
tflist, _ := getTFList(mirrorURL, listAll) // Get list of versions
recentVersions, _ := getRecentVersions() // Get recent versions from RECENT file
tflist = append(recentVersions, tflist...) // Append recent versions to the top of the list
tflist = removeDuplicateVersions(tflist) // Remove duplicate version
func InstallOption(listAll bool, customBinaryPath, installPath string, mirrorURL string) {
tflist, _ := getTFList(mirrorURL, listAll) // Get list of versions
recentVersions, _ := getRecentVersions(installPath) // Get recent versions from RECENT file
tflist = append(recentVersions, tflist...) // Append recent versions to the top of the list
tflist = removeDuplicateVersions(tflist) // Remove duplicate version

if len(tflist) == 0 {
logger.Fatalf("Terraform version list is empty: %s", mirrorURL)
Expand All @@ -375,6 +361,6 @@ func InstallOption(listAll bool, customBinaryPath, mirrorURL string) {
}
}

install(tfversion, customBinaryPath, mirrorURL)
install(tfversion, customBinaryPath, installPath, mirrorURL)
os.Exit(0)
}
Loading