Skip to content

Commit

Permalink
added option to install on home bin path
Browse files Browse the repository at this point in the history
  • Loading branch information
warrensbox committed Jul 11, 2021
1 parent 4880748 commit 27c5474
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 28 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.4.0
github.com/zclconf/go-cty v1.8.0 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 h1:vsphBvatvfbhlb4PO1BYSr9dzugGxJ/SQHoNufZJq1w=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
Expand Down
14 changes: 6 additions & 8 deletions lib/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,35 @@ import (
func DownloadFromURL(installLocation string, url string) (string, error) {
tokens := strings.Split(url, "/")
fileName := tokens[len(tokens)-1]
fmt.Println("Downloading", url, "to", fileName)
fmt.Println("Downloading ...")
fmt.Printf("Downloading to: %s\n", installLocation)

response, err := http.Get(url)

if err != nil {
fmt.Println("Error while downloading", url, "-", err)
fmt.Println("[Error] : Error while downloading", url, "-", err)
return "", err
}
defer response.Body.Close()

if response.StatusCode != 200 {
//Sometimes hashicorp terraform file names are not consistent
//For example 0.12.0-alpha4 naming convention in the release repo is not consistent
return "", fmt.Errorf("Unable to download from %s\nPlease download manually from https://releases.hashicorp.com/terraform/", url)
return "", fmt.Errorf("[Error] : Unable to download from %s", url)
}

zipFile := filepath.Join(installLocation, fileName)
output, err := os.Create(zipFile)
if err != nil {
fmt.Println("Error while creating", zipFile, "-", err)
fmt.Println("[Error] : Error while creating", zipFile, "-", err)
return "", err
}
defer output.Close()

n, err := io.Copy(output, response.Body)
if err != nil {
fmt.Println("Error while downloading", url, "-", err)
fmt.Println("[Error] : Error while downloading", url, "-", err)
return "", err
}

fmt.Println(n, "bytes downloaded.")
fmt.Println(n, "bytes downloaded")
return zipFile, nil
}
39 changes: 35 additions & 4 deletions lib/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"os"
"path/filepath"
"strings"

"golang.org/x/sys/unix"
)

// RenameFile : rename file name
Expand Down Expand Up @@ -103,10 +105,10 @@ func Unzip(src string, dest string) ([]string, error) {
//CreateDirIfNotExist : create directory if directory does not exist
func CreateDirIfNotExist(dir string) {
if _, err := os.Stat(dir); os.IsNotExist(err) {
log.Printf("Creating directory for terraform: %v", dir)
fmt.Printf("Creating directory for terraform binary at: %v\n", dir)
err = os.MkdirAll(dir, 0755)
if err != nil {
fmt.Printf("Unable to create directory for terraform: %v", dir)
fmt.Printf("Unable to create directory for terraform binary at: %v", dir)
panic(err)
}
}
Expand Down Expand Up @@ -204,7 +206,7 @@ func CheckDirHasTGBin(dir, prefix string) bool {

//CheckDirExist : check if directory exist
//dir=path to file
//return path to directory
//return bool
func CheckDirExist(dir string) bool {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return false
Expand All @@ -220,6 +222,35 @@ func Path(value string) string {

// GetFileName : remove file ext. .tfswitch.config returns .tfswitch
func GetFileName(configfile string) string {

return strings.TrimSuffix(configfile, filepath.Ext(configfile))
}

//Check if user has permission to directory :
//dir=path to file
//return bool
func CheckDirWritable(dir string) bool {
return unix.Access(dir, unix.W_OK) == nil
}

// func WindowsCheckDirWritable(path string) bool {

// info, err := os.Stat(path)
// if err != nil {
// fmt.Println("Path doesn't exist")
// return false
// }

// err = nil
// if !info.IsDir() {
// fmt.Println("Path isn't a directory")
// return false
// }

// // Check if the user bit is enabled in file permission
// if info.Mode().Perm()&(1<<(uint(7))) == 0 {
// fmt.Println("Write permission bit is not set on this file for user")
// return false
// }

// return true
// }
74 changes: 62 additions & 12 deletions lib/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ import (
"os/user"
"path/filepath"
"runtime"
"strings"
)

const (
installFile = "terraform"
installVersion = "terraform_"
installPath = ".terraform.versions"
recentFile = "RECENT"
defaultBin = "/usr/local/bin/terraform" //default bin installation dir
)

var (
installLocation = "/tmp"
)

// initialize : removes existing symlink to terraform binary
// initialize : removes existing symlink to terraform binary// I Don't think this is needed
func initialize() {

/* Step 1 */
Expand Down Expand Up @@ -69,7 +71,6 @@ func getInstallLocation() string {

/* set installation location */
installLocation = filepath.Join(userCommon, installPath)
fmt.Printf("installLocation: %s", installLocation)

/* Create local installation directory if it does not exist */
CreateDirIfNotExist(installLocation)
Expand All @@ -86,14 +87,14 @@ func Install(tfversion string, binPath string, mirrorURL string) {
os.Exit(1)
}

pathDir := Path(binPath) //get path directory from binary path
binDirExist := CheckDirExist(pathDir) //check bin path exist

if !binDirExist {
fmt.Printf("Error - Binary path does not exist: %s\n", pathDir)
fmt.Printf("Create binary path: %s for terraform installation\n", pathDir)
os.Exit(1)
}
pathDir := Path(binPath) //get path directory from binary path
//binDirExist := CheckDirExist(pathDir) //check bin path exist
/* 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
* Tell users to add $HOME/bin to their path
*/
binPath = InstallableBinLocation(pathDir)

initialize() //initialize path
installLocation = getInstallLocation() //get installation location - this is where we will put our terraform binary file
Expand Down Expand Up @@ -127,6 +128,12 @@ func Install(tfversion string, binPath string, mirrorURL string) {
os.Exit(0)
}

//if does not have slash - append slash
hasSlash := strings.HasSuffix(mirrorURL, "/")
if !hasSlash {
mirrorURL = fmt.Sprintf("%s/", mirrorURL)
}

/* if selected version already exist, */
/* proceed to download it from the hashicorp release page */
url := mirrorURL + tfversion + "/" + installVersion + tfversion + "_" + goos + "_" + goarch + ".zip"
Expand All @@ -141,7 +148,7 @@ func Install(tfversion string, binPath string, mirrorURL string) {
/* unzip the downloaded zipfile */
_, errUnzip := Unzip(zipFile, installLocation)
if errUnzip != nil {
fmt.Println("Unable to unzip downloaded zip file")
fmt.Println("[Error] : Unable to unzip downloaded zip file")
log.Fatal(errUnzip)
os.Exit(1)
}
Expand Down Expand Up @@ -178,7 +185,7 @@ func AddRecent(requestedVersion string) {
lines, errRead := ReadLines(versionFile)

if errRead != nil {
fmt.Printf("Error: %s\n", errRead)
fmt.Printf("[Error] : %s\n", errRead)
return
}

Expand Down Expand Up @@ -269,3 +276,46 @@ func ConvertExecutableExt(fpath string) string {
return fpath
}
}

//InstallableBinLocation : Checks if terraform is installable in the location provided by the user.
//If not, create $HOME/bin. Ask users to add $HOME/bin to $PATH
//Return $HOME/bin as install location
func InstallableBinLocation(userBin string) string {

usr, errCurr := user.Current()
if errCurr != nil {
log.Fatal(errCurr)
}

existDefaultBin := CheckDirExist(userBin)

if existDefaultBin { //if exist - now see if we can write to to it

writableToDefault := false
if runtime.GOOS != "windows" {
writableToDefault = CheckDirWritable(userBin) //check if is writable on ( only works on LINUX)
}

if !writableToDefault {
exisHomeBin := CheckDirExist(filepath.Join(usr.HomeDir, "bin"))
if exisHomeBin {
fmt.Printf("Installing terraform at %s\n", filepath.Join(usr.HomeDir, "bin"))
return filepath.Join(usr.HomeDir, "bin", "terraform")
}
PrintCreateDirStmt(userBin, filepath.Join(usr.HomeDir, "bin"))
CreateDirIfNotExist(filepath.Join(usr.HomeDir, "bin"))
return filepath.Join(usr.HomeDir, "bin", "terraform")
}
return filepath.Join(userBin, "terraform")
}
fmt.Printf("[Error] : Binary path does not exist: %s\n", userBin)
fmt.Printf("[Error] : Manually create bin directory at: %s and try again.\n", userBin)
os.Exit(1)
return ""
}

func PrintCreateDirStmt(unableDir string, writable string) {
fmt.Printf("Unable to write to: %s\n", unableDir)
fmt.Printf("Creating bin directory at: %s\n", writable)
fmt.Printf("RUN `export PATH=$PATH:%s` to append bin to $PATH\n", writable)
}
18 changes: 15 additions & 3 deletions lib/list_versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io/ioutil"
"log"
"net/http"
"os"
"reflect"
"regexp"
"strings"
Expand Down Expand Up @@ -40,6 +41,10 @@ func GetTFList(mirrorURL string, preRelease bool) ([]string, error) {
}
}

if len(tfVersionList.tflist) == 0 {
fmt.Printf("Cannot get list from mirror: %s\n", mirrorURL)
}

return tfVersionList.tflist, nil

}
Expand Down Expand Up @@ -95,20 +100,27 @@ func GetTFLatestImplicit(mirrorURL string, preRelease bool, version string) (str
//GetTFURLBody : Get list of terraform versions from hashicorp releases
func GetTFURLBody(mirrorURL string) ([]string, error) {

hasSlash := strings.HasSuffix(mirrorURL, "/")
if !hasSlash { //if does not have slash - append slash
mirrorURL = fmt.Sprintf("%s/", mirrorURL)
}
resp, errURL := http.Get(mirrorURL)
if errURL != nil {
log.Printf("Error getting url: %v", errURL)
log.Printf("[Error] : Getting url: %v", errURL)
os.Exit(1)
return nil, errURL
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
log.Printf("Error retrieving contents from url: %s", mirrorURL)
log.Printf("[Error] : Retrieving contents from url: %s", mirrorURL)
os.Exit(1)
}

body, errBody := ioutil.ReadAll(resp.Body)
if errBody != nil {
log.Printf("Error reading body: %v", errBody)
log.Printf("[Error] : reading body: %v", errBody)
os.Exit(1)
return nil, errBody
}

Expand Down
6 changes: 5 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import (
)

const (
defaultMirror = "https://releases.hashicorp.com/terraform/"
defaultMirror = "https://releases.hashicorp.com/terraform"
defaultBin = "/usr/local/bin/terraform" //default bin installation dir
defaultLatest = ""
tfvFilename = ".terraform-version"
Expand Down Expand Up @@ -366,6 +366,10 @@ func installOption(listAll bool, custBinPath, mirrorURL *string) {
tflist = append(recentVersions, tflist...) //append recent versions to the top of the list
tflist = lib.RemoveDuplicateVersions(tflist) //remove duplicate version

if len(tflist) == 0 {
fmt.Println("[ERROR] : List is empty")
os.Exit(1)
}
/* prompt user to select version of terraform */
prompt := promptui.Select{
Label: "Select Terraform version",
Expand Down

0 comments on commit 27c5474

Please sign in to comment.