Skip to content

Commit

Permalink
Feature: Add flag for install location (optional)
Browse files Browse the repository at this point in the history
Add the ability to pass -i or --install to change the default install location for the Terragrunt binaries
  • Loading branch information
ArronaxKP committed Apr 19, 2024
1 parent 6150678 commit 8c721b5
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 60 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
54 changes: 26 additions & 28 deletions lib/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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`)

Expand All @@ -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
}
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}

Expand All @@ -168,17 +166,17 @@ 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
* Tell users to add $HOME/bin to their path
*/
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
Expand All @@ -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)
}

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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":
Expand Down
70 changes: 38 additions & 32 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand All @@ -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, */
Expand All @@ -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 {
Expand All @@ -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:
Expand All @@ -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 {
Expand All @@ -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)
}

Expand All @@ -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"
Expand Down Expand Up @@ -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

Expand All @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -274,19 +280,19 @@ 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")
os.Exit(1)
}

// 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
Expand All @@ -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 */
Expand All @@ -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
Expand All @@ -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 {
Expand Down

0 comments on commit 8c721b5

Please sign in to comment.