Skip to content

Commit b83d98e

Browse files
authored
Merge pull request #125 from warrensbox/master
Added option to get latest implicit version
2 parents d7fb9d1 + 92e0612 commit b83d98e

File tree

9 files changed

+414
-58
lines changed

9 files changed

+414
-58
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ The most recently selected versions are presented at the top of the dropdown.
7070
2. For example, `tfswitch -l` or `tfswitch --list-all` to see all versions.
7171
3. Hit **Enter** to select the desired version.
7272

73+
### Install latest version only
74+
1. Install the latest stable version only.
75+
2. Run `tfswitch -u` or `tfswitch --latest` to see all versions.
76+
3. Hit **Enter** to install.
77+
### Install latest implicit version for stable releases
78+
1. Install the latest implicit stable version.
79+
2. Ex: `tfswitch -s 0.13` or `tfswitch --latest-stable 0.13` downloads 0.13.6 (latest) version.
80+
3. Hit **Enter** to install.
81+
### Install latest implicit version for beta, alpha and release candidates(rc)
82+
1. Install the latest implicit pre-release version.
83+
2. Ex: `tfswitch -p 0.13` or `tfswitch --latest-pre 0.13` downloads 0.13.0-rc1 (latest) version.
84+
3. Hit **Enter** to install.
7385
### Use version.tf file
7486
If a .tf file with the terraform constrain is included in the current directory, it should automatically download or switch to that terraform version. For example, the following should automatically switch terraform to the lastest version:
7587
```ruby

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
77
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
88
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
99
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
10+
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
1011
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
12+
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
1113
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
1214
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
1315
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
@@ -112,6 +114,7 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
112114
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
113115
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw=
114116
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
117+
github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
115118
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
116119
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
117120
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=

go.sum538389371.tmp

Lines changed: 221 additions & 0 deletions
Large diffs are not rendered by default.

lib/list_versions.go

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package lib
22

33
import (
4+
"fmt"
45
"io/ioutil"
56
"log"
67
"net/http"
@@ -14,47 +15,103 @@ type tfVersionList struct {
1415
}
1516

1617
//GetTFList : Get the list of available terraform version given the hashicorp url
17-
func GetTFList(hashiURL string, listAll bool) ([]string, error) {
18+
func GetTFList(hashiURL string, preRelease bool) ([]string, error) {
1819

19-
/* Get list of terraform versions from hashicorp releases */
20-
resp, errURL := http.Get(hashiURL)
21-
if errURL != nil {
22-
log.Printf("Error getting url: %v", errURL)
23-
return nil, errURL
20+
result, error := GetTFURLBody(hashiURL)
21+
if error != nil {
22+
return nil, error
2423
}
25-
defer resp.Body.Close()
2624

27-
body, errBody := ioutil.ReadAll(resp.Body)
28-
if errBody != nil {
29-
log.Printf("Error reading body: %v", errBody)
30-
return nil, errBody
25+
var tfVersionList tfVersionList
26+
var semver string
27+
if preRelease == true {
28+
// Getting versions from body; should return match /X.X.X-@/ where X is a number,@ is a word character between a-z or A-Z
29+
semver = `\/(\d+\.\d+\.\d+)(-[a-zA-z]+\d*)?\/`
30+
} else if preRelease == false {
31+
// Getting versions from body; should return match /X.X.X/ where X is a number
32+
semver = `\/(\d+\.\d+\.\d+)\/`
33+
}
34+
r, _ := regexp.Compile(semver)
35+
for i := range result {
36+
if r.MatchString(result[i]) {
37+
str := r.FindString(result[i])
38+
trimstr := strings.Trim(str, "/") //remove "/" from /X.X.X/
39+
tfVersionList.tflist = append(tfVersionList.tflist, trimstr)
40+
}
3141
}
3242

33-
bodyString := string(body)
34-
result := strings.Split(bodyString, "\n")
43+
return tfVersionList.tflist, nil
3544

36-
var tfVersionList tfVersionList
45+
}
46+
47+
//GetTFLatest : Get the latest terraform version given the hashicorp url
48+
func GetTFLatest(hashiURL string) (string, error) {
3749

50+
result, error := GetTFURLBody(hashiURL)
51+
if error != nil {
52+
return "", error
53+
}
54+
// Getting versions from body; should return match /X.X.X/ where X is a number
55+
semver := `\/(\d+\.\d+\.\d+)\/`
56+
r, _ := regexp.Compile(semver)
3857
for i := range result {
39-
// Getting versions from body; should return match /X.X.X/ where X is a number
40-
// Follow https://semver.org/spec/v2.0.0.html
41-
r, _ := regexp.Compile(`\/(\d+\.\d+\.\d+)\/`)
42-
if listAll {
43-
// Getting versions from body; should return match /X.X.X-@/ where X is a number,@ is a word character between a-z or A-Z
44-
// Follow https://semver.org/spec/v1.0.0-beta.html
45-
// Check regular expression at https://rubular.com/r/ju3PxbaSBALpJB
46-
r, _ = regexp.Compile(`\/(\d+\.\d+\.\d+)(-[a-zA-z]+\d*)?\/`)
58+
if r.MatchString(result[i]) {
59+
str := r.FindString(result[i])
60+
trimstr := strings.Trim(str, "/") //remove "/" from /X.X.X/
61+
return trimstr, nil
4762
}
63+
}
64+
65+
return "", nil
66+
}
4867

68+
//GetTFLatestImplicit : Get the latest implicit terraform version given the hashicorp url
69+
func GetTFLatestImplicit(hashiURL string, preRelease bool, version string) (string, error) {
70+
71+
result, error := GetTFURLBody(hashiURL)
72+
if error != nil {
73+
return "", error
74+
}
75+
var semver string
76+
if preRelease == true {
77+
// Getting versions from body; should return match /X.X.X-@/ where X is a number,@ is a word character between a-z or A-Z
78+
semver = fmt.Sprintf(`\/(%s{1}\.\d+\-[a-zA-z]+\d*)?\/`, version)
79+
} else if preRelease == false {
80+
semver = fmt.Sprintf(`\/(%s{1}\.\d+)\/`, version)
81+
}
82+
r, _ := regexp.Compile(semver)
83+
for i := range result {
4984
if r.MatchString(result[i]) {
5085
str := r.FindString(result[i])
5186
trimstr := strings.Trim(str, "/") //remove "/" from /X.X.X/
52-
tfVersionList.tflist = append(tfVersionList.tflist, trimstr)
87+
return trimstr, nil
5388
}
5489
}
5590

56-
return tfVersionList.tflist, nil
91+
return "", nil
92+
93+
}
94+
95+
//GetTFURLBody : Get list of terraform versions from hashicorp releases
96+
func GetTFURLBody(hashiURL string) ([]string, error) {
97+
98+
resp, errURL := http.Get(hashiURL)
99+
if errURL != nil {
100+
log.Printf("Error getting url: %v", errURL)
101+
return nil, errURL
102+
}
103+
defer resp.Body.Close()
57104

105+
body, errBody := ioutil.ReadAll(resp.Body)
106+
if errBody != nil {
107+
log.Printf("Error reading body: %v", errBody)
108+
return nil, errBody
109+
}
110+
111+
bodyString := string(body)
112+
result := strings.Split(bodyString, "\n")
113+
114+
return result, nil
58115
}
59116

60117
//VersionExist : check if requested version exist
@@ -113,3 +170,16 @@ func ValidVersionFormat(version string) bool {
113170

114171
return semverRegex.MatchString(version)
115172
}
173+
174+
// ValidMinorVersionFormat : returns valid MINOR version format
175+
/* For example: 0.1 = valid
176+
// For example: a.1.2 = invalid
177+
// For example: 0.1.2 = invalid
178+
*/
179+
func ValidMinorVersionFormat(version string) bool {
180+
181+
// Getting versions from body; should return match /X.X./ where X is a number
182+
semverRegex := regexp.MustCompile(`^(\d+\.\d+)$`)
183+
184+
return semverRegex.MatchString(version)
185+
}

main.go

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,22 @@ import (
4141
)
4242

4343
const (
44-
hashiURL = "https://releases.hashicorp.com/terraform/"
45-
defaultBin = "/usr/local/bin/terraform" //default bin installation dir
46-
tfvFilename = ".terraform-version"
47-
rcFilename = ".tfswitchrc"
48-
tomlFilename = ".tfswitch.toml"
44+
hashiURL = "https://releases.hashicorp.com/terraform/"
45+
defaultBin = "/usr/local/bin/terraform" //default bin installation dir
46+
defaultLatest = ""
47+
tfvFilename = ".terraform-version"
48+
rcFilename = ".tfswitchrc"
49+
tomlFilename = ".tfswitch.toml"
4950
)
5051

51-
var version = "0.9.0\n"
52+
var version = "0.10.0\n"
5253

5354
func main() {
54-
55-
custBinPath := getopt.StringLong("bin", 'b', defaultBin, "Custom binary path. For example: /Users/username/bin/terraform")
55+
custBinPath := getopt.StringLong("bin", 'b', defaultBin, "Custom binary path. Ex: /Users/username/bin/terraform")
5656
listAllFlag := getopt.BoolLong("list-all", 'l', "List all versions of terraform - including beta and rc")
57+
latestPre := getopt.StringLong("latest-pre", 'p', defaultLatest, "Latest pre-release implicit version. Ex: tfswitch --latest-pre 0.13 downloads 0.13.0-rc1 (latest)")
58+
latestStable := getopt.StringLong("latest-stable", 's', defaultLatest, "Latest implicit version. Ex: tfswitch --latest 0.13 downloads 0.13.5 (latest)")
59+
latestFlag := getopt.BoolLong("latest", 'u', "Get latest stable version")
5760
versionFlag := getopt.BoolLong("version", 'v', "Displays the version of tfswitch")
5861
helpFlag := getopt.BoolLong("help", 'h', "Displays help message")
5962
_ = versionFlag
@@ -73,10 +76,10 @@ func main() {
7376
os.Exit(1)
7477
}
7578

76-
curr_tfvfile := dir + fmt.Sprintf("/%s", tfvFilename) //settings for .terraform-version file in current directory (tfenv compatible)
77-
curr_rcfile := dir + fmt.Sprintf("/%s", rcFilename) //settings for .tfswitchrc file in current directory (backward compatible purpose)
78-
curr_tomlconfigfile := dir + fmt.Sprintf("/%s", tomlFilename) //settings for .tfswitch.toml file in current directory (option to specify bin directory)
79-
home_tomlconfigfile := homedir + fmt.Sprintf("/%s", tomlFilename) //settings for .tfswitch.toml file in home directory (option to specify bin directory)
79+
TFVersionFile := dir + fmt.Sprintf("/%s", tfvFilename) //settings for .terraform-version file in current directory (tfenv compatible)
80+
RCFile := dir + fmt.Sprintf("/%s", rcFilename) //settings for .tfswitchrc file in current directory (backward compatible purpose)
81+
TOMLConfigFile := dir + fmt.Sprintf("/%s", tomlFilename) //settings for .tfswitch.toml file in current directory (option to specify bin directory)
82+
HomeTOMLConfigFile := homedir + fmt.Sprintf("/%s", tomlFilename) //settings for .tfswitch.toml file in home directory (option to specify bin directory)
8083

8184
switch {
8285
case *versionFlag:
@@ -92,11 +95,11 @@ func main() {
9295
* If you provide a custom binary path with the -b option, this will override the bin value in the toml file
9396
* If you provide a version on the command line, this will override the version value in the toml file
9497
*/
95-
case fileExists(curr_tomlconfigfile) || fileExists(home_tomlconfigfile):
98+
case fileExists(TOMLConfigFile) || fileExists(HomeTOMLConfigFile):
9699

97100
version := ""
98101
binPath := *custBinPath
99-
if fileExists(curr_tomlconfigfile) { //read from toml from current directory
102+
if fileExists(TOMLConfigFile) { //read from toml from current directory
100103
version, binPath = getParamsTOML(binPath, dir)
101104
} else { // else read from toml from home directory
102105
version, binPath = getParamsTOML(binPath, homedir)
@@ -106,15 +109,23 @@ func main() {
106109
case *listAllFlag:
107110
listAll := true //set list all true - all versions including beta and rc will be displayed
108111
installOption(listAll, &binPath)
112+
case *latestPre != "":
113+
preRelease := true
114+
installLatestImplicitVersion(*latestPre, custBinPath, preRelease)
115+
case *latestStable != "":
116+
preRelease := false
117+
installLatestImplicitVersion(*latestStable, custBinPath, preRelease)
118+
case *latestFlag:
119+
installLatestVersion(custBinPath)
109120
case len(args) == 1:
110121
installVersion(args[0], &binPath)
111-
case fileExists(curr_rcfile) && len(args) == 0:
122+
case fileExists(RCFile) && len(args) == 0:
112123
readingFileMsg(rcFilename)
113-
tfversion := retrieveFileContents(curr_rcfile)
124+
tfversion := retrieveFileContents(RCFile)
114125
installVersion(tfversion, &binPath)
115-
case fileExists(curr_tfvfile) && len(args) == 0:
126+
case fileExists(TFVersionFile) && len(args) == 0:
116127
readingFileMsg(tfvFilename)
117-
tfversion := retrieveFileContents(curr_tfvfile)
128+
tfversion := retrieveFileContents(TFVersionFile)
118129
installVersion(tfversion, &binPath)
119130
case checkTFModuleFileExist(dir) && len(args) == 0:
120131
installTFProvidedModule(dir, &binPath)
@@ -125,24 +136,38 @@ func main() {
125136
installOption(listAll, &binPath)
126137
}
127138

128-
/* list all versions, //show all terraform version including betas and RCs*/
139+
/* show all terraform version including betas and RCs*/
129140
case *listAllFlag:
130141
installWithListAll(custBinPath)
131142

143+
/* latest pre-release implicit version. Ex: tfswitch --latest-pre 0.13 downloads 0.13.0-rc1 (latest) */
144+
case *latestPre != "":
145+
preRelease := true
146+
installLatestImplicitVersion(*latestPre, custBinPath, preRelease)
147+
148+
/* latest implicit version. Ex: tfswitch --latest 0.13 downloads 0.13.5 (latest) */
149+
case *latestStable != "":
150+
preRelease := false
151+
installLatestImplicitVersion(*latestStable, custBinPath, preRelease)
152+
153+
/* latest stable version */
154+
case *latestFlag:
155+
installLatestVersion(custBinPath)
156+
132157
/* version provided on command line as arg */
133158
case len(args) == 1:
134159
installVersion(args[0], custBinPath)
135160

136161
/* provide an tfswitchrc file */
137-
case fileExists(curr_rcfile) && len(args) == 0:
162+
case fileExists(RCFile) && len(args) == 0:
138163
readingFileMsg(rcFilename)
139-
tfversion := retrieveFileContents(curr_rcfile)
164+
tfversion := retrieveFileContents(RCFile)
140165
installVersion(tfversion, custBinPath)
141166

142167
/* if .terraform-version file found */
143-
case fileExists(curr_tfvfile) && len(args) == 0:
168+
case fileExists(TFVersionFile) && len(args) == 0:
144169
readingFileMsg(tfvFilename)
145-
tfversion := retrieveFileContents(curr_tfvfile)
170+
tfversion := retrieveFileContents(TFVersionFile)
146171
installVersion(tfversion, custBinPath)
147172

148173
/* if versions.tf file found */
@@ -164,6 +189,22 @@ func installWithListAll(custBinPath *string) {
164189
installOption(listAll, custBinPath)
165190
}
166191

192+
// install latest stable tf version
193+
func installLatestVersion(custBinPath *string) {
194+
tfversion, _ := lib.GetTFLatest(hashiURL)
195+
lib.Install(tfversion, *custBinPath)
196+
}
197+
198+
// install latest - argument (version) must be provided
199+
func installLatestImplicitVersion(requestedVersion string, custBinPath *string, preRelease bool) {
200+
if lib.ValidMinorVersionFormat(requestedVersion) {
201+
tfversion, _ := lib.GetTFLatestImplicit(hashiURL, preRelease, requestedVersion)
202+
lib.Install(tfversion, *custBinPath)
203+
} else {
204+
printInvalidMinorTFVersion()
205+
}
206+
}
207+
167208
// install with provided version as argument
168209
func installVersion(arg string, custBinPath *string) {
169210
if lib.ValidVersionFormat(arg) {
@@ -187,7 +228,12 @@ func installVersion(arg string, custBinPath *string) {
187228

188229
// Print invalid TF version
189230
func printInvalidTFVersion() {
190-
fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # is numbers and @ is word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions")
231+
fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # are numbers and @ are word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions")
232+
}
233+
234+
// Print invalid TF version
235+
func printInvalidMinorTFVersion() {
236+
fmt.Println("Invalid minor terraform version format. Format should be #.# where # are numbers. For example, 0.11 is valid version")
191237
}
192238

193239
//retrive file content of regular file
@@ -295,7 +341,7 @@ func installOption(listAll bool, custBinPath *string) {
295341
os.Exit(0)
296342
}
297343

298-
// installation when
344+
// install when tf file is provided
299345
func installTFProvidedModule(dir string, custBinPath *string) {
300346
tfversion := ""
301347
module, _ := tfconfig.LoadModule(dir)

vendor/github.com/manifoldco/promptui/Gopkg.lock

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/spf13/viper/go.sum

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
RELEASE_VERSION=0.9
1+
RELEASE_VERSION=0.10

0 commit comments

Comments
 (0)