Skip to content

Commit

Permalink
Merge pull request #327 from MatrixCrawler/windows-home-dir
Browse files Browse the repository at this point in the history
Use Windows User Directory for bin path
  • Loading branch information
warrensbox committed Mar 31, 2024
2 parents 3cec6bf + 74bbd31 commit e5ca144
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 506 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go

name: Go

on:
push:
branches: [ '*' ]

jobs:

integration_tests:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
go_version: ['1.22']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go_version }}

- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
33 changes: 18 additions & 15 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,38 @@ go 1.22
require (
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80
github.com/hashicorp/terraform-config-inspect v0.0.0-20211115214459-90acf1ca460f
github.com/hashicorp/terraform-config-inspect v0.0.0-20231204233900-a34142ec2a72
github.com/manifoldco/promptui v0.9.0
github.com/mitchellh/go-homedir v1.1.0
github.com/pborman/getopt v1.1.0
github.com/spf13/viper v1.12.0
github.com/spf13/viper v1.18.2
golang.org/x/sys v0.18.0
)

require (
github.com/agext/levenshtein v1.2.2 // indirect
github.com/apparentlymart/go-textseg v1.0.0 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl/v2 v2.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/zclconf/go-cty v1.1.0 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
501 changes: 53 additions & 448 deletions go.sum

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions lib/defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package lib

import (
"github.com/mitchellh/go-homedir"
"log"
"runtime"
)

// GetDefaultBin Get default binary path
func GetDefaultBin() string {
var defaultBin = "/usr/local/bin/terraform"
if runtime.GOOS == "windows" {
home, err := homedir.Dir()
if err != nil {
log.Fatalf("Could not detect home directory.")
}
defaultBin = home + "/bin/terraform.exe"
}
return defaultBin
}
24 changes: 15 additions & 9 deletions lib/download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package lib_test

import (
"fmt"
"github.com/mitchellh/go-homedir"
"log"
"net/url"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/mitchellh/go-homedir"
"github.com/warrensbox/terraform-switcher/lib"
)

Expand All @@ -21,14 +22,19 @@ func TestDownloadFromURL_FileNameMatch(t *testing.T) {
installPath := fmt.Sprintf(tempDir + string(os.PathSeparator) + ".terraform.versions_test")
macOS := "_darwin_amd64.zip"

// get current user
homedir, errCurr := homedir.Dir()
if errCurr != nil {
log.Fatal(errCurr)
home, err := homedir.Dir()
if err != nil {
log.Fatalf("Could not detect home directory.")
}

fmt.Printf("Current home directory: %v \n", homedir)
installLocation := filepath.Join(homedir, installPath)
fmt.Printf("Current user homedir: %v \n", home)
var installLocation = ""
if runtime.GOOS != "windows" {
installLocation = filepath.Join(home, installPath)
} else {
installLocation = installPath
}
fmt.Printf("Install Location: %v \n", installLocation)

// create /.terraform.versions_test/ directory to store code
if _, err := os.Stat(installLocation); os.IsNotExist(err) {
Expand All @@ -44,7 +50,7 @@ func TestDownloadFromURL_FileNameMatch(t *testing.T) {
lowestVersion := "0.11.0"

url := hashiURL + lowestVersion + "/" + installVersion + lowestVersion + macOS
expectedFile := filepath.Join(homedir, installPath, installVersion+lowestVersion+macOS)
expectedFile := filepath.Join(installLocation, installVersion+lowestVersion+macOS)
installedFile, errDownload := lib.DownloadFromURL(installLocation, url)

if errDownload != nil {
Expand All @@ -63,7 +69,7 @@ func TestDownloadFromURL_FileNameMatch(t *testing.T) {
}

//check file name is what is expected
_, err := os.Stat(expectedFile)
_, err = os.Stat(expectedFile)
if err != nil {
t.Logf("Expected file does not exist %v", expectedFile)
}
Expand Down
8 changes: 4 additions & 4 deletions lib/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func initialize(binPath string) {
cmd := NewCommand(binPath)
next := cmd.Find()

/* overrride installation default binary path if terraform is already installed */
/* override installation default binary path if terraform is already installed */
/* find the last bin path */
for path := next(); len(path) > 0; path = next() {
binPath = path
Expand Down Expand Up @@ -79,7 +79,7 @@ func Install(tfversion string, binPath 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 dont have permission to default location, therefore tfswitch was installed in $HOME/bin
* 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)
Expand Down Expand Up @@ -226,7 +226,7 @@ func GetRecentVersions() ([]string, error) {

for _, line := range lines {
/* checks if versions in the recent file are valid.
If any version is invalid, it will be consider dirty
If any version is invalid, it will be considered dirty
and the recent file will be removed
*/
if !ValidVersionFormat(line) {
Expand Down Expand Up @@ -279,7 +279,7 @@ func InstallableBinLocation(userBinPath string) string {
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 { //if bin path exist - check if we can write to to it
if binPathExist == true { //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
36 changes: 30 additions & 6 deletions lib/symlink.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
package lib

import (
"io"
"log"
"os"
"runtime"
)

//CreateSymlink : create symlink
// CreateSymlink : create symlink or copy file to bin directory if windows
func CreateSymlink(cwd string, dir string) {
// If we are on windows the symlink is not working correctly.
// Copy the desired terraform binary to the path environment.
if runtime.GOOS == "windows" {
r, err := os.Open(cwd)
if err != nil {
log.Fatalf("Unable to open source binary: %s", cwd)
os.Exit(1)
}
defer r.Close()

err := os.Symlink(cwd, dir)
if err != nil {
log.Fatalf(`
w, err := os.Create(dir + ".exe")
if err != nil {
log.Fatalf("Could not create target binary: %s", dir + ".exe")
os.Exit(1)
}
defer func() {
if c := w.Close(); err == nil {
err = c
}
}()
_, err = io.Copy(w, r)
} else {
err := os.Symlink(cwd, dir)
if err != nil {
log.Fatalf(`
Unable to create new symlink.
Maybe symlink already exist. Try removing existing symlink manually.
Try running "unlink %s" to remove existing symlink.
If error persist, you may not have the permission to create a symlink at %s.
Error: %s
`, dir, dir, err)
os.Exit(1)
os.Exit(1)
}
}
}

//RemoveSymlink : remove symlink
// RemoveSymlink : remove symlink
func RemoveSymlink(symlinkPath string) {

_, err := os.Lstat(symlinkPath)
Expand Down
61 changes: 42 additions & 19 deletions lib/symlink_test.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,74 @@
package lib_test

import (
"github.com/mitchellh/go-homedir"
"log"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/mitchellh/go-homedir"
"github.com/warrensbox/terraform-switcher/lib"
)

// TestCreateSymlink : check if symlink exist-remove if exist,
// create symlink, check if symlink exist, remove symlink
func TestCreateSymlink(t *testing.T) {

testSymlinkDest := "/test-tfswitcher-dest"
testSymlinkSrc := "/test-tfswitcher-src"
if runtime.GOOS == "windows" {
testSymlinkSrc = "/test-tfswitcher-src.exe"
}

testSymlinkDest := "/test-tfswitcher-dest"
home, err := homedir.Dir()
if err != nil {
log.Fatalf("Could not detect home directory.")
}
symlinkPathSrc := filepath.Join(home, testSymlinkSrc)
symlinkPathDest := filepath.Join(home, testSymlinkDest)

homedir, errCurr := homedir.Dir()
if errCurr != nil {
log.Fatal(errCurr)
// Create file for test as windows does not like no source
create, err := os.Create(symlinkPathDest)
if err != nil {
log.Fatalf("Could not create test dest file for symlink at %v", symlinkPathDest)
}
symlinkPathSrc := filepath.Join(homedir, testSymlinkSrc)
symlinkPathDest := filepath.Join(homedir, testSymlinkDest)
defer create.Close()

ln, _ := os.Readlink(symlinkPathSrc)
if runtime.GOOS != "windows" {
ln, _ := os.Readlink(symlinkPathSrc)

if ln != symlinkPathDest {
t.Logf("Symlink does not exist %v [expected]", ln)
} else {
t.Logf("Symlink exist %v [expected]", ln)
os.Remove(symlinkPathSrc)
t.Logf("Removed existing symlink for testing purposes")
if ln != symlinkPathDest {
t.Logf("Symlink does not exist %v [expected]", ln)
} else {
t.Logf("Symlink exist %v [expected]", ln)
os.Remove(symlinkPathSrc)
t.Logf("Removed existing symlink for testing purposes")
}
}

lib.CreateSymlink(symlinkPathDest, symlinkPathSrc)

lnCheck, _ := os.Readlink(symlinkPathSrc)
if lnCheck == symlinkPathDest {
t.Logf("Symlink exist %v [expected]", lnCheck)
if runtime.GOOS == "windows" {
_, err := os.Stat(symlinkPathSrc + ".exe")
if err != nil {
t.Logf("Could not stat file copy at %v. [unexpected]", symlinkPathSrc)
t.Error("File copy was not created.")
} else {
t.Logf("File copy exists at %v [expected]", symlinkPathSrc)
}
} else {
t.Logf("Symlink does not exist %v [unexpected]", lnCheck)
t.Error("Symlink was not created")
lnCheck, _ := os.Readlink(symlinkPathSrc)
if lnCheck == symlinkPathDest {
t.Logf("Symlink exist %v [expected]", lnCheck)
} else {
t.Logf("Symlink does not exist %v [unexpected]", lnCheck)
t.Error("Symlink was not created")
}
}

os.Remove(symlinkPathSrc)
os.Remove(symlinkPathDest)
}

// TestRemoveSymlink : check if symlink exist-create if does not exist,
Expand Down
9 changes: 4 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package main

/*** OPERATION WORKFLOW ***/
/*
* 1- Create /usr/local/terraform directory if does not exist
* 1- Create /usr/local/terraform directory if it does not exist
* 2- Download zip file from url to /usr/local/terraform
* 3- Unzip the file to /usr/local/terraform
* 4- Rename the file from `terraform` to `terraform_version`
Expand Down Expand Up @@ -40,7 +40,6 @@ import (

const (
defaultMirror = "https://releases.hashicorp.com/terraform"
defaultBin = "/usr/local/bin/terraform" //default bin installation dir
defaultLatest = ""
tfvFilename = ".terraform-version"
rcFilename = ".tfswitchrc"
Expand All @@ -53,7 +52,7 @@ var version = "0.12.0\n"

func main() {
dir := lib.GetCurrentDirectory()
custBinPath := getopt.StringLong("bin", 'b', lib.ConvertExecutableExt(defaultBin), "Custom binary path. Ex: tfswitch -b "+lib.ConvertExecutableExt("/Users/username/bin/terraform"))
custBinPath := getopt.StringLong("bin", 'b', lib.ConvertExecutableExt(lib.GetDefaultBin()), "Custom binary path. Ex: tfswitch -b "+lib.ConvertExecutableExt("/Users/username/bin/terraform"))
listAllFlag := getopt.BoolLong("list-all", 'l', "List all versions of terraform - including beta and rc")
latestPre := getopt.StringLong("latest-pre", 'p', defaultLatest, "Latest pre-release implicit version. Ex: tfswitch --latest-pre 0.13 downloads 0.13.0-rc1 (latest)")
showLatestPre := getopt.StringLong("show-latest-pre", 'P', defaultLatest, "Show latest pre-release implicit version. Ex: tfswitch --show-latest-pre 0.13 prints 0.13.0-rc1 (latest)")
Expand Down Expand Up @@ -387,8 +386,8 @@ func getParamsTOML(binPath string, dir string) (string, string) {
os.Exit(1) // exit immediately if config file provided but it is unable to read it
}

bin := viper.Get("bin") // read custom binary location
if binPath == lib.ConvertExecutableExt(defaultBin) && bin != nil { // if the bin path is the same as the default binary path and if the custom binary is provided in the toml file (use it)
bin := viper.Get("bin") // read custom binary location
if binPath == lib.ConvertExecutableExt(lib.GetDefaultBin()) && bin != nil { // if the bin path is the same as the default binary path and if the custom binary is provided in the toml file (use it)
binPath = os.ExpandEnv(bin.(string))
}
//fmt.Println(binPath) //uncomment this to debug
Expand Down

0 comments on commit e5ca144

Please sign in to comment.