diff --git a/README.md b/README.md index f451a27..7e7e045 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,22 @@ tgswitch --chdir terragrunt_dir tgswitch -c terragrunt_dir ``` +### Install to non-default location + +By default `tgswitch` will download the Terragrunt binary to the user home directory under this path: `/Users/warrenveerasingam/.terragrunt.versions` + +If you want to install the binaries outside of the home directory then you can provide the `-i` or `--install` to install Terragrunt binaries to a non-standard path. Useful if you want to install versions of Terragrunt that can be shared with multiple users. + +The Terragrunt binaries will then be placed in the directory `.terragrunt.versions` under the custom install path e.g. `/opt/terragrunt/.terragrunt.versions` + +```bash +tgswitch -i /opt/terragrunt/ +``` + +**NOTE** + +* The directory passed in `-i`/`--install` must be created before running `tgswitch` + **Automatically switch with bash** Add the following to the end of your `~/.bashrc` file: diff --git a/lib/install.go b/lib/install.go index e71741c..795bf12 100644 --- a/lib/install.go +++ b/lib/install.go @@ -15,7 +15,7 @@ const ( gruntURL = "https://github.com/gruntwork-io/terragrunt/releases/download/" installFile = "terragrunt" installVersion = "terragrunt_" - installPath = "/.terragrunt.versions/" + installDir = ".terragrunt.versions" recentFile = "RECENT" ) @@ -50,24 +50,22 @@ func initialize() { } // GetInstallLocation : get location where the terragrunt binary will be installed, -// will create a directory in the home location if it does not exist -func GetInstallLocation() string { - /* get current user */ - usr, errCurr := user.Current() - if errCurr != nil { - log.Fatal(errCurr) - } +// will create the installDir if it does not exist +func GetInstallLocation(installPath string) string { /* set installation location */ - installLocation = usr.HomeDir + installPath + installLocation = filepath.Join(installPath, installDir) + /* Create local installation directory if it does not exist */ CreateDirIfNotExist(installLocation) + return installLocation + } // AddRecent : add to recent file -func AddRecent(requestedVersion string) { +func AddRecent(requestedVersion string, installPath string) { - installLocation = GetInstallLocation() + installLocation = GetInstallLocation(installPath) semverRegex := regexp.MustCompile(`\d+(\.\d+){2}\z`) @@ -83,7 +81,7 @@ func AddRecent(requestedVersion string) { for _, line := range lines { if !semverRegex.MatchString(line) { RemoveFiles(installLocation + recentFile) - CreateRecentFile(requestedVersion) + CreateRecentFile(requestedVersion, installPath) return } } @@ -103,14 +101,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() + installLocation = GetInstallLocation(installPath) fileExist := CheckFileExist(installLocation + recentFile) if fileExist { @@ -140,10 +138,10 @@ func GetRecentVersions() ([]string, error) { return nil, nil } -//CreateRecentFile : create a recent file -func CreateRecentFile(requestedVersion string) { +// CreateRecentFile : create a recent file +func CreateRecentFile(requestedVersion string, installPath string) { - installLocation = GetInstallLocation() + installLocation = GetInstallLocation(installPath) WriteLines([]string{requestedVersion}, installLocation+recentFile) } @@ -168,8 +166,8 @@ func ValidVersionFormat(version string) bool { return semverRegex.MatchString(version) } -//Install : Install the provided version in the argument -func Install(tgversion string, usrBinPath string, mirrorURL string) string { +// Install : Install the provided version in the argument +func Install(tgversion string, usrBinPath string, installPath string, mirrorURL string) string { /* Check to see if user has permission to the default bin location which is "/usr/local/bin/terragrunt" * If user does not have permission to default bin location, proceed to create $HOME/bin and install the tgswitch there * Inform user that they dont have permission to default location, therefore tgswitch was installed in $HOME/bin @@ -177,8 +175,8 @@ func Install(tgversion string, usrBinPath string, mirrorURL string) string { */ binPath := InstallableBinLocation(usrBinPath) - initialize() //initialize path - installLocation = GetInstallLocation() //get installation location - this is where we will put our terragrunt binary file + initialize() //initialize path + installLocation = GetInstallLocation(installPath) //get installation location - this is where we will put our terragrunt binary file goarch := runtime.GOARCH goos := runtime.GOOS @@ -200,7 +198,7 @@ func Install(tgversion string, usrBinPath string, mirrorURL string) string { /* set symlink to desired version */ CreateSymlink(installFileVersionPath, binPath) fmt.Printf("Switched terragrunt to version %q \n", tgversion) - AddRecent(tgversion) //add to recent file for faster lookup + AddRecent(tgversion, installPath) //add to recent file for faster lookup os.Exit(0) } @@ -239,14 +237,14 @@ func Install(tgversion string, usrBinPath string, mirrorURL string) string { /* set symlink to desired version */ CreateSymlink(installFileVersionPath, binPath) fmt.Printf("Switched terragrunt to version %q \n", tgversion) - AddRecent(tgversion) //add to recent file for faster lookup + AddRecent(tgversion, installPath) //add to recent file for faster lookup os.Exit(0) return "" } -//InstallableBinLocation : Checks if terragrunt 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 +// InstallableBinLocation : Checks if terragrunt 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(userBinPath string) string { usr, errCurr := user.Current() @@ -294,7 +292,7 @@ func PrintCreateDirStmt(unableDir string, writable string) { fmt.Printf("RUN `export PATH=$PATH:%s` to append bin to $PATH\n", writable) } -//ConvertExecutableExt : convert excutable with local OS extension +// ConvertExecutableExt : convert excutable with local OS extension func ConvertExecutableExt(fpath string) string { switch runtime.GOOS { case "windows": diff --git a/main.go b/main.go index b8a4d67..cc9ae09 100644 --- a/main.go +++ b/main.go @@ -46,7 +46,10 @@ var version = "0.5.0\n" func main() { dir := lib.GetCurrentDirectory() + homedir := lib.GetHomeDirectory() + custBinPath := getopt.StringLong("bin", 'b', lib.ConvertExecutableExt(defaultBin), "Custom binary path. Ex: tgswitch -b "+lib.ConvertExecutableExt("/Users/username/bin/terragrunt")) + installPath := getopt.StringLong("install", 'i', homedir, "Custom install path. Ex: tfswitch -i /Users/username") versionFlag := getopt.BoolLong("version", 'v', "displays the version of tgswitch") helpFlag := getopt.BoolLong("help", 'h', "displays help message") chDirPath := getopt.StringLong("chdir", 'c', dir, "Switch to a different working directory before executing the given command. Ex: tgswitch --chdir terragrunt dir will run tgswitch in the directory") @@ -61,8 +64,6 @@ func main() { os.Exit(1) } - homedir := lib.GetHomeDirectory() - TOMLConfigFile := filepath.Join(*chDirPath, tomlFilename) //settings for .tgswitch.toml file in current directory (option to specify bin directory) HomeTOMLConfigFile := filepath.Join(homedir, tomlFilename) //settings for .tgswitch.toml file in home directory (option to specify bin directory) RCFile := filepath.Join(*chDirPath, rcFilename) //settings for .tgswitchrc file in current directory (backward compatible purpose) @@ -84,9 +85,9 @@ func main() { version := "" binPath := *custBinPath if lib.FileExists(TOMLConfigFile) { //read from toml from current directory - version, binPath = GetParamsTOML(binPath, *chDirPath) + version, binPath, *installPath = GetParamsTOML(binPath, *installPath, *chDirPath) } else { // else read from toml from home directory - version, binPath = GetParamsTOML(binPath, homedir) + version, binPath, *installPath = GetParamsTOML(binPath, *installPath, homedir) } /* GIVEN A TOML FILE, */ @@ -99,7 +100,7 @@ func main() { exist := lib.VersionExist(requestedVersion, listOfVersions) if exist { - installLocation := lib.Install(requestedVersion, binPath, terragruntURL) + installLocation := lib.Install(requestedVersion, binPath, *installPath, terragruntURL) fmt.Println("Install Location:", installLocation) } } else { @@ -110,26 +111,26 @@ func main() { case lib.FileExists(RCFile) && len(args) == 0: lib.ReadingFileMsg(rcFilename) tgversion := lib.RetrieveFileContents(RCFile) - installVersion(tgversion, &binPath) + installVersion(tgversion, &binPath, installPath) /* if .terragrunt-version file found (IN ADDITION TO A TOML FILE) */ case lib.FileExists(TGVersionFile) && len(args) == 0: lib.ReadingFileMsg(TGVersionFile) tgversion := lib.RetrieveFileContents(TGVersionFile) - installVersion(tgversion, &binPath) + installVersion(tgversion, &binPath, installPath) /* if terragrunt.hcl file found (IN ADDITION TO A TOML FILE) */ case lib.FileExists(TGHACLFile) && checkVersionDefinedHCL(&TGHACLFile) && len(args) == 0: - installTGHclFile(&TGHACLFile, binPath, terragruntURL) + installTGHclFile(&TGHACLFile, binPath, *installPath, terragruntURL) /* if terragrunt Version environment variable is set (IN ADDITION TO A TOML FILE)*/ case checkTGEnvExist() && len(args) == 0 && version == "": tgversion := os.Getenv("TG_VERSION") fmt.Printf("Terragrunt version environment variable: %s\n", tgversion) - installVersion(tgversion, &binPath) + installVersion(tgversion, &binPath, installPath) /* if version is specified in the .toml file */ case version != "": - lib.Install(version, binPath, terragruntURL) + lib.Install(version, binPath, *installPath, terragruntURL) /* show dropdown */ default: - installFromList(&binPath) + installFromList(&binPath, installPath) } case len(args) == 1: @@ -140,7 +141,7 @@ func main() { exist := lib.VersionExist(requestedVersion, listOfVersions) if exist { - installLocation := lib.Install(requestedVersion, *custBinPath, terragruntURL) + installLocation := lib.Install(requestedVersion, *custBinPath, *installPath, terragruntURL) fmt.Println("Install Location:", installLocation) } } else { @@ -151,22 +152,22 @@ func main() { case lib.FileExists(RCFile) && len(args) == 0: lib.ReadingFileMsg(rcFilename) tgversion := lib.RetrieveFileContents(RCFile) - installVersion(tgversion, custBinPath) + installVersion(tgversion, custBinPath, installPath) case lib.FileExists(TGVersionFile) && len(args) == 0: lib.ReadingFileMsg(TGVersionFile) tgversion := lib.RetrieveFileContents(TGVersionFile) - installVersion(tgversion, custBinPath) + installVersion(tgversion, custBinPath, installPath) /* if terragrunt.hcl file found */ case lib.FileExists(TGHACLFile) && checkVersionDefinedHCL(&TGHACLFile) && len(args) == 0: - installTGHclFile(&TGHACLFile, *custBinPath, terragruntURL) + installTGHclFile(&TGHACLFile, *custBinPath, *installPath, terragruntURL) /* if terragrunt Version environment variable is set*/ case checkTGEnvExist() && len(args) == 0: tgversion := os.Getenv("TG_VERSION") fmt.Printf("Terragrunt version environment variable: %s\n", tgversion) - installVersion(tgversion, custBinPath) + installVersion(tgversion, custBinPath, installPath) /* show dropdown */ default: - installFromList(custBinPath) + installFromList(custBinPath, installPath) os.Exit(0) } @@ -179,7 +180,7 @@ func usageMessage() { } /* parses everything in the toml file, return required version and bin path */ -func GetParamsTOML(binPath string, dir string) (string, string) { +func GetParamsTOML(binPath string, installPath string, dir string) (string, string, string) { path := lib.GetHomeDirectory() if dir == path { path = "home directory" @@ -207,14 +208,19 @@ func GetParamsTOML(binPath string, dir string) (string, string) { version = "" } - return version.(string), binPath + install := viper.Get("install") //attempt to get the install path if it's provided in the toml + if install != nil { + installPath = os.ExpandEnv(install.(string)) + } + + return version.(string), binPath, installPath } /* installFromList : displays & installs tf version */ -func installFromList(custBinPath *string) { +func installFromList(custBinPath *string, installPath *string) { listOfVersions := lib.GetAppList(proxyUrl) - recentVersions, _ := lib.GetRecentVersions() //get recent versions from RECENT file + recentVersions, _ := lib.GetRecentVersions(*installPath) //get recent versions from RECENT file listOfVersions = append(recentVersions, listOfVersions...) //append recent versions to the top of the list listOfVersions = lib.RemoveDuplicateVersions(listOfVersions) //remove duplicate version @@ -230,23 +236,23 @@ func installFromList(custBinPath *string) { os.Exit(1) } - lib.Install(tgversion, *custBinPath, terragruntURL) + lib.Install(tgversion, *custBinPath, *installPath, terragruntURL) os.Exit(0) } // install with provided version as argument -func installVersion(arg string, custBinPath *string) { +func installVersion(arg string, custBinPath *string, installPath *string) { if lib.ValidVersionFormat(arg) { requestedVersion := arg //check to see if the requested version has been downloaded before - installLocation := lib.GetInstallLocation() + installLocation := lib.GetInstallLocation(*installPath) installFileVersionPath := lib.ConvertExecutableExt(filepath.Join(installLocation, versionPrefix+requestedVersion)) recentDownloadFile := lib.CheckFileExist(installFileVersionPath) if recentDownloadFile { lib.ChangeSymlink(installFileVersionPath, *custBinPath) fmt.Printf("Switched terragrunt to version %q \n", requestedVersion) - lib.AddRecent(requestedVersion) //add to recent file for faster lookup + lib.AddRecent(requestedVersion, *installPath) //add to recent file for faster lookup os.Exit(0) } @@ -257,7 +263,7 @@ func installVersion(arg string, custBinPath *string) { exist := lib.VersionExist(requestedVersion, listOfVersions) if exist { - installLocation := lib.Install(requestedVersion, *custBinPath, terragruntURL) + installLocation := lib.Install(requestedVersion, *custBinPath, *installPath, terragruntURL) fmt.Println("Install Location:", installLocation) } @@ -274,11 +280,11 @@ func checkTGEnvExist() bool { } // install using a version constraint -func installFromConstraint(tgconstraint *string, custBinPath, mirrorURL string) { +func installFromConstraint(tgconstraint *string, custBinPath, installPath, mirrorURL string) { tgversion, err := lib.GetSemver(tgconstraint, mirrorURL) if err == nil { - lib.Install(tgversion, custBinPath, mirrorURL) + lib.Install(tgversion, custBinPath, installPath, mirrorURL) } fmt.Println(err) fmt.Println("No version found to match constraint. Follow the README.md instructions for setup. https://github.com/warrensbox/tgswitch/blob/master/README.md") @@ -286,7 +292,7 @@ func installFromConstraint(tgconstraint *string, custBinPath, mirrorURL string) } // Install using version constraint from terragrunt file -func installTGHclFile(tgFile *string, custBinPath, mirrorURL string) { +func installTGHclFile(tgFile *string, custBinPath, installPath, mirrorURL string) { fmt.Printf("Terragrunt file found: %s\n", *tgFile) parser := hclparse.NewParser() file, diags := parser.ParseHCLFile(*tgFile) //use hcl parser to parse HCL file @@ -296,7 +302,7 @@ func installTGHclFile(tgFile *string, custBinPath, mirrorURL string) { } var version terragruntVersionConstraints gohcl.DecodeBody(file.Body, nil, &version) - installFromConstraint(&version.TerragruntVersionConstraint, custBinPath, mirrorURL) + installFromConstraint(&version.TerragruntVersionConstraint, custBinPath, installPath, mirrorURL) } // check if version is defined in hcl file /* lazy-emergency fix - will improve later */ @@ -312,7 +318,7 @@ func checkVersionDefinedHCL(tgFile *string) bool { } // Install using version constraint from terragrunt file -// func installTGHclFile(tgFile *string, custBinPath *string) { +// func installTGHclFile(tgFile *string, custBinPath *string, installPath *string) { // fmt.Printf("Terragrunt file found: %s\n", *tgFile) // parser := hclparse.NewParser() // file, diags := parser.ParseHCLFile(*tgFile) //use hcl parser to parse HCL file @@ -323,7 +329,7 @@ func checkVersionDefinedHCL(tgFile *string) bool { // var version terragruntVersionConstraints // gohcl.DecodeBody(file.Body, nil, &version) // //TODO figure out sermver -// //installFromConstraint(&version.TerragruntVersionConstraint, custBinPath) +// //installFromConstraint(&version.TerragruntVersionConstraint, custBinPath, installPath) // } type terragruntVersionConstraints struct {