Skip to content

Commit 55ebca0

Browse files
committed
Feature: Add flag for install location (optional)
Add the ability to pass -i or --install to change the default install location for the Terragrunt binaries
1 parent d7ef900 commit 55ebca0

File tree

3 files changed

+81
-60
lines changed

3 files changed

+81
-60
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,23 @@ tgswitch --chdir terragrunt_dir
112112
tgswitch -c terragrunt_dir
113113
```
114114

115+
### Install to non-default location
116+
117+
By default `tfswitch` will download the Terraform binary to the user home directory under this path: `/Users/warrenveerasingam/.terraform.versions`
118+
119+
If you want to install the binaries outside of the home directory then you can provide the `-i` or `--install` to install Terraform binaries to a non-standard path. Useful if you want to install versions of Terraform that can be shared with multiple users.
120+
121+
The Terraform binaries will then be placed in the folder `.terraform.versions` under the custom install path e.g. `/opt/terraform/.terraform.versions`
122+
123+
```bash
124+
tfswitch -i /opt/terraform/
125+
```
126+
127+
**NOTE**
128+
129+
* The folder must exists before you run `tfswitch`
130+
* The folder passed in `-i`/`--install` must be created before running `tfswtich`
131+
115132
**Automatically switch with bash**
116133

117134
Add the following to the end of your `~/.bashrc` file:

lib/install.go

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const (
1515
gruntURL = "https://github.com/gruntwork-io/terragrunt/releases/download/"
1616
installFile = "terragrunt"
1717
installVersion = "terragrunt_"
18-
installPath = "/.terragrunt.versions/"
18+
installFolder = ".terragrunt.versions"
1919
recentFile = "RECENT"
2020
)
2121

@@ -50,24 +50,22 @@ func initialize() {
5050
}
5151

5252
// GetInstallLocation : get location where the terragrunt binary will be installed,
53-
// will create a directory in the home location if it does not exist
54-
func GetInstallLocation() string {
55-
/* get current user */
56-
usr, errCurr := user.Current()
57-
if errCurr != nil {
58-
log.Fatal(errCurr)
59-
}
53+
// will create the installFolder if it does not exist
54+
func GetInstallLocation(installPath string) string {
6055
/* set installation location */
61-
installLocation = usr.HomeDir + installPath
56+
installLocation = filepath.Join(installPath, installFolder)
57+
6258
/* Create local installation directory if it does not exist */
6359
CreateDirIfNotExist(installLocation)
60+
6461
return installLocation
62+
6563
}
6664

6765
// AddRecent : add to recent file
68-
func AddRecent(requestedVersion string) {
66+
func AddRecent(requestedVersion string, installPath string) {
6967

70-
installLocation = GetInstallLocation()
68+
installLocation = GetInstallLocation(installPath)
7169

7270
semverRegex := regexp.MustCompile(`\d+(\.\d+){2}\z`)
7371

@@ -83,7 +81,7 @@ func AddRecent(requestedVersion string) {
8381
for _, line := range lines {
8482
if !semverRegex.MatchString(line) {
8583
RemoveFiles(installLocation + recentFile)
86-
CreateRecentFile(requestedVersion)
84+
CreateRecentFile(requestedVersion, installPath)
8785
return
8886
}
8987
}
@@ -103,14 +101,14 @@ func AddRecent(requestedVersion string) {
103101
}
104102

105103
} else {
106-
CreateRecentFile(requestedVersion)
104+
CreateRecentFile(requestedVersion, installPath)
107105
}
108106
}
109107

110108
// GetRecentVersions : get recent version from file
111-
func GetRecentVersions() ([]string, error) {
109+
func GetRecentVersions(installPath string) ([]string, error) {
112110

113-
installLocation = GetInstallLocation()
111+
installLocation = GetInstallLocation(installPath)
114112

115113
fileExist := CheckFileExist(installLocation + recentFile)
116114
if fileExist {
@@ -140,10 +138,10 @@ func GetRecentVersions() ([]string, error) {
140138
return nil, nil
141139
}
142140

143-
//CreateRecentFile : create a recent file
144-
func CreateRecentFile(requestedVersion string) {
141+
// CreateRecentFile : create a recent file
142+
func CreateRecentFile(requestedVersion string, installPath string) {
145143

146-
installLocation = GetInstallLocation()
144+
installLocation = GetInstallLocation(installPath)
147145
WriteLines([]string{requestedVersion}, installLocation+recentFile)
148146
}
149147

@@ -168,17 +166,17 @@ func ValidVersionFormat(version string) bool {
168166
return semverRegex.MatchString(version)
169167
}
170168

171-
//Install : Install the provided version in the argument
172-
func Install(tgversion string, usrBinPath string, mirrorURL string) string {
169+
// Install : Install the provided version in the argument
170+
func Install(tgversion string, usrBinPath string, installPath string, mirrorURL string) string {
173171
/* Check to see if user has permission to the default bin location which is "/usr/local/bin/terragrunt"
174172
* If user does not have permission to default bin location, proceed to create $HOME/bin and install the tgswitch there
175173
* Inform user that they dont have permission to default location, therefore tgswitch was installed in $HOME/bin
176174
* Tell users to add $HOME/bin to their path
177175
*/
178176
binPath := InstallableBinLocation(usrBinPath)
179177

180-
initialize() //initialize path
181-
installLocation = GetInstallLocation() //get installation location - this is where we will put our terragrunt binary file
178+
initialize() //initialize path
179+
installLocation = GetInstallLocation(installPath) //get installation location - this is where we will put our terragrunt binary file
182180

183181
goarch := runtime.GOARCH
184182
goos := runtime.GOOS
@@ -200,7 +198,7 @@ func Install(tgversion string, usrBinPath string, mirrorURL string) string {
200198
/* set symlink to desired version */
201199
CreateSymlink(installFileVersionPath, binPath)
202200
fmt.Printf("Switched terragrunt to version %q \n", tgversion)
203-
AddRecent(tgversion) //add to recent file for faster lookup
201+
AddRecent(tgversion, installPath) //add to recent file for faster lookup
204202
os.Exit(0)
205203
}
206204

@@ -239,14 +237,14 @@ func Install(tgversion string, usrBinPath string, mirrorURL string) string {
239237
/* set symlink to desired version */
240238
CreateSymlink(installFileVersionPath, binPath)
241239
fmt.Printf("Switched terragrunt to version %q \n", tgversion)
242-
AddRecent(tgversion) //add to recent file for faster lookup
240+
AddRecent(tgversion, installPath) //add to recent file for faster lookup
243241
os.Exit(0)
244242
return ""
245243
}
246244

247-
//InstallableBinLocation : Checks if terragrunt is installable in the location provided by the user.
248-
//If not, create $HOME/bin. Ask users to add $HOME/bin to $PATH
249-
//Return $HOME/bin as install location
245+
// InstallableBinLocation : Checks if terragrunt is installable in the location provided by the user.
246+
// If not, create $HOME/bin. Ask users to add $HOME/bin to $PATH
247+
// Return $HOME/bin as install location
250248
func InstallableBinLocation(userBinPath string) string {
251249

252250
usr, errCurr := user.Current()
@@ -294,7 +292,7 @@ func PrintCreateDirStmt(unableDir string, writable string) {
294292
fmt.Printf("RUN `export PATH=$PATH:%s` to append bin to $PATH\n", writable)
295293
}
296294

297-
//ConvertExecutableExt : convert excutable with local OS extension
295+
// ConvertExecutableExt : convert excutable with local OS extension
298296
func ConvertExecutableExt(fpath string) string {
299297
switch runtime.GOOS {
300298
case "windows":

main.go

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ var version = "0.5.0\n"
4646
func main() {
4747

4848
dir := lib.GetCurrentDirectory()
49+
homedir := lib.GetHomeDirectory()
50+
4951
custBinPath := getopt.StringLong("bin", 'b', lib.ConvertExecutableExt(defaultBin), "Custom binary path. Ex: tgswitch -b "+lib.ConvertExecutableExt("/Users/username/bin/terragrunt"))
52+
installPath := getopt.StringLong("install", 'i', homedir, "Custom install path. Ex: tfswitch -i /Users/username")
5053
versionFlag := getopt.BoolLong("version", 'v', "displays the version of tgswitch")
5154
helpFlag := getopt.BoolLong("help", 'h', "displays help message")
5255
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() {
6164
os.Exit(1)
6265
}
6366

64-
homedir := lib.GetHomeDirectory()
65-
6667
TOMLConfigFile := filepath.Join(*chDirPath, tomlFilename) //settings for .tgswitch.toml file in current directory (option to specify bin directory)
6768
HomeTOMLConfigFile := filepath.Join(homedir, tomlFilename) //settings for .tgswitch.toml file in home directory (option to specify bin directory)
6869
RCFile := filepath.Join(*chDirPath, rcFilename) //settings for .tgswitchrc file in current directory (backward compatible purpose)
@@ -84,9 +85,9 @@ func main() {
8485
version := ""
8586
binPath := *custBinPath
8687
if lib.FileExists(TOMLConfigFile) { //read from toml from current directory
87-
version, binPath = GetParamsTOML(binPath, *chDirPath)
88+
version, binPath, *installPath = GetParamsTOML(binPath, *installPath, *chDirPath)
8889
} else { // else read from toml from home directory
89-
version, binPath = GetParamsTOML(binPath, homedir)
90+
version, binPath, *installPath = GetParamsTOML(binPath, *installPath, homedir)
9091
}
9192

9293
/* GIVEN A TOML FILE, */
@@ -99,7 +100,7 @@ func main() {
99100
exist := lib.VersionExist(requestedVersion, listOfVersions)
100101

101102
if exist {
102-
installLocation := lib.Install(requestedVersion, binPath, terragruntURL)
103+
installLocation := lib.Install(requestedVersion, binPath, *installPath, terragruntURL)
103104
fmt.Println("Install Location:", installLocation)
104105
}
105106
} else {
@@ -110,26 +111,26 @@ func main() {
110111
case lib.FileExists(RCFile) && len(args) == 0:
111112
lib.ReadingFileMsg(rcFilename)
112113
tgversion := lib.RetrieveFileContents(RCFile)
113-
installVersion(tgversion, &binPath)
114+
installVersion(tgversion, &binPath, installPath)
114115
/* if .terragrunt-version file found (IN ADDITION TO A TOML FILE) */
115116
case lib.FileExists(TGVersionFile) && len(args) == 0:
116117
lib.ReadingFileMsg(TGVersionFile)
117118
tgversion := lib.RetrieveFileContents(TGVersionFile)
118-
installVersion(tgversion, &binPath)
119+
installVersion(tgversion, &binPath, installPath)
119120
/* if terragrunt.hcl file found (IN ADDITION TO A TOML FILE) */
120121
case lib.FileExists(TGHACLFile) && checkVersionDefinedHCL(&TGHACLFile) && len(args) == 0:
121-
installTGHclFile(&TGHACLFile, binPath, proxyUrl)
122+
installTGHclFile(&TGHACLFile, binPath, *installPath, proxyUrl)
122123
/* if terragrunt Version environment variable is set (IN ADDITION TO A TOML FILE)*/
123124
case checkTGEnvExist() && len(args) == 0 && version == "":
124125
tgversion := os.Getenv("TG_VERSION")
125126
fmt.Printf("Terragrunt version environment variable: %s\n", tgversion)
126-
installVersion(tgversion, &binPath)
127+
installVersion(tgversion, &binPath, installPath)
127128
/* if version is specified in the .toml file */
128129
case version != "":
129-
lib.Install(version, binPath, terragruntURL)
130+
lib.Install(version, binPath, *installPath, terragruntURL)
130131
/* show dropdown */
131132
default:
132-
installFromList(&binPath)
133+
installFromList(&binPath, installPath)
133134
}
134135

135136
case len(args) == 1:
@@ -140,7 +141,7 @@ func main() {
140141
exist := lib.VersionExist(requestedVersion, listOfVersions)
141142

142143
if exist {
143-
installLocation := lib.Install(requestedVersion, *custBinPath, terragruntURL)
144+
installLocation := lib.Install(requestedVersion, *custBinPath, *installPath, terragruntURL)
144145
fmt.Println("Install Location:", installLocation)
145146
}
146147
} else {
@@ -151,22 +152,22 @@ func main() {
151152
case lib.FileExists(RCFile) && len(args) == 0:
152153
lib.ReadingFileMsg(rcFilename)
153154
tgversion := lib.RetrieveFileContents(RCFile)
154-
installVersion(tgversion, custBinPath)
155+
installVersion(tgversion, custBinPath, installPath)
155156
case lib.FileExists(TGVersionFile) && len(args) == 0:
156157
lib.ReadingFileMsg(TGVersionFile)
157158
tgversion := lib.RetrieveFileContents(TGVersionFile)
158-
installVersion(tgversion, custBinPath)
159+
installVersion(tgversion, custBinPath, installPath)
159160
/* if terragrunt.hcl file found */
160161
case lib.FileExists(TGHACLFile) && checkVersionDefinedHCL(&TGHACLFile) && len(args) == 0:
161-
installTGHclFile(&TGHACLFile, *custBinPath, proxyUrl)
162+
installTGHclFile(&TGHACLFile, *custBinPath, *installPath, proxyUrl)
162163
/* if terragrunt Version environment variable is set*/
163164
case checkTGEnvExist() && len(args) == 0:
164165
tgversion := os.Getenv("TG_VERSION")
165166
fmt.Printf("Terragrunt version environment variable: %s\n", tgversion)
166-
installVersion(tgversion, custBinPath)
167+
installVersion(tgversion, custBinPath, installPath)
167168
/* show dropdown */
168169
default:
169-
installFromList(custBinPath)
170+
installFromList(custBinPath, installPath)
170171
os.Exit(0)
171172
}
172173

@@ -179,7 +180,7 @@ func usageMessage() {
179180
}
180181

181182
/* parses everything in the toml file, return required version and bin path */
182-
func GetParamsTOML(binPath string, dir string) (string, string) {
183+
func GetParamsTOML(binPath string, installPath string, dir string) (string, string, string) {
183184
path := lib.GetHomeDirectory()
184185
if dir == path {
185186
path = "home directory"
@@ -207,14 +208,19 @@ func GetParamsTOML(binPath string, dir string) (string, string) {
207208
version = ""
208209
}
209210

210-
return version.(string), binPath
211+
install := viper.Get("install") //attempt to get the install path if it's provided in the toml
212+
if install != nil {
213+
installPath = os.ExpandEnv(install.(string))
214+
}
215+
216+
return version.(string), binPath, installPath
211217
}
212218

213219
/* installFromList : displays & installs tf version */
214-
func installFromList(custBinPath *string) {
220+
func installFromList(custBinPath *string, installPath *string) {
215221

216222
listOfVersions := lib.GetAppList(proxyUrl)
217-
recentVersions, _ := lib.GetRecentVersions() //get recent versions from RECENT file
223+
recentVersions, _ := lib.GetRecentVersions(*installPath) //get recent versions from RECENT file
218224
listOfVersions = append(recentVersions, listOfVersions...) //append recent versions to the top of the list
219225
listOfVersions = lib.RemoveDuplicateVersions(listOfVersions) //remove duplicate version
220226

@@ -230,23 +236,23 @@ func installFromList(custBinPath *string) {
230236
os.Exit(1)
231237
}
232238

233-
lib.Install(tgversion, *custBinPath, terragruntURL)
239+
lib.Install(tgversion, *custBinPath, *installPath, terragruntURL)
234240
os.Exit(0)
235241
}
236242

237243
// install with provided version as argument
238-
func installVersion(arg string, custBinPath *string) {
244+
func installVersion(arg string, custBinPath *string, installPath *string) {
239245
if lib.ValidVersionFormat(arg) {
240246
requestedVersion := arg
241247

242248
//check to see if the requested version has been downloaded before
243-
installLocation := lib.GetInstallLocation()
249+
installLocation := lib.GetInstallLocation(*installPath)
244250
installFileVersionPath := lib.ConvertExecutableExt(filepath.Join(installLocation, versionPrefix+requestedVersion))
245251
recentDownloadFile := lib.CheckFileExist(installFileVersionPath)
246252
if recentDownloadFile {
247253
lib.ChangeSymlink(installFileVersionPath, *custBinPath)
248254
fmt.Printf("Switched terragrunt to version %q \n", requestedVersion)
249-
lib.AddRecent(requestedVersion) //add to recent file for faster lookup
255+
lib.AddRecent(requestedVersion, *installPath) //add to recent file for faster lookup
250256
os.Exit(0)
251257
}
252258

@@ -257,7 +263,7 @@ func installVersion(arg string, custBinPath *string) {
257263
exist := lib.VersionExist(requestedVersion, listOfVersions)
258264

259265
if exist {
260-
installLocation := lib.Install(requestedVersion, *custBinPath, terragruntURL)
266+
installLocation := lib.Install(requestedVersion, *custBinPath, *installPath, terragruntURL)
261267
fmt.Println("Install Location:", installLocation)
262268
}
263269

@@ -274,19 +280,19 @@ func checkTGEnvExist() bool {
274280
}
275281

276282
// install using a version constraint
277-
func installFromConstraint(tgconstraint *string, custBinPath, mirrorURL string) {
283+
func installFromConstraint(tgconstraint *string, custBinPath, installPath, mirrorURL string) {
278284

279285
tgversion, err := lib.GetSemver(tgconstraint, mirrorURL)
280286
if err == nil {
281-
lib.Install(tgversion, custBinPath, mirrorURL)
287+
lib.Install(tgversion, custBinPath, installPath, mirrorURL)
282288
}
283289
fmt.Println(err)
284290
fmt.Println("No version found to match constraint. Follow the README.md instructions for setup. https://github.com/warrensbox/tgswitch/blob/master/README.md")
285291
os.Exit(1)
286292
}
287293

288294
// Install using version constraint from terragrunt file
289-
func installTGHclFile(tgFile *string, custBinPath, mirrorURL string) {
295+
func installTGHclFile(tgFile *string, custBinPath, installPath, mirrorURL string) {
290296
fmt.Printf("Terragrunt file found: %s\n", *tgFile)
291297
parser := hclparse.NewParser()
292298
file, diags := parser.ParseHCLFile(*tgFile) //use hcl parser to parse HCL file
@@ -296,7 +302,7 @@ func installTGHclFile(tgFile *string, custBinPath, mirrorURL string) {
296302
}
297303
var version terragruntVersionConstraints
298304
gohcl.DecodeBody(file.Body, nil, &version)
299-
installFromConstraint(&version.TerragruntVersionConstraint, custBinPath, mirrorURL)
305+
installFromConstraint(&version.TerragruntVersionConstraint, custBinPath, installPath, mirrorURL)
300306
}
301307

302308
// check if version is defined in hcl file /* lazy-emergency fix - will improve later */
@@ -312,7 +318,7 @@ func checkVersionDefinedHCL(tgFile *string) bool {
312318
}
313319

314320
// Install using version constraint from terragrunt file
315-
// func installTGHclFile(tgFile *string, custBinPath *string) {
321+
// func installTGHclFile(tgFile *string, custBinPath *string, installPath *string) {
316322
// fmt.Printf("Terragrunt file found: %s\n", *tgFile)
317323
// parser := hclparse.NewParser()
318324
// file, diags := parser.ParseHCLFile(*tgFile) //use hcl parser to parse HCL file
@@ -323,7 +329,7 @@ func checkVersionDefinedHCL(tgFile *string) bool {
323329
// var version terragruntVersionConstraints
324330
// gohcl.DecodeBody(file.Body, nil, &version)
325331
// //TODO figure out sermver
326-
// //installFromConstraint(&version.TerragruntVersionConstraint, custBinPath)
332+
// //installFromConstraint(&version.TerragruntVersionConstraint, custBinPath, installPath)
327333
// }
328334

329335
type terragruntVersionConstraints struct {

0 commit comments

Comments
 (0)