Skip to content

Commit d59ef38

Browse files
MatrixCrawleryermulnikMatthewJohn
authored
RECENT to JSON Proposal (#437)
* Convert old RECENT file to JSON Format * added tests * updated dependencies * make sure only the last 5 of the versions are marked with *recent and returned in the list --------- Co-authored-by: George L. Yermulnik <yz@yz.kiev.ua> Co-authored-by: Matt John <matthew@dockstudios.co.uk>
1 parent 439cb16 commit d59ef38

File tree

7 files changed

+265
-112
lines changed

7 files changed

+265
-112
lines changed

go.mod

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ require (
66
github.com/gookit/slog v0.5.6
77
github.com/hashicorp/go-version v1.7.0
88
github.com/hashicorp/hcl/v2 v2.20.1
9-
github.com/hashicorp/terraform-config-inspect v0.0.0-20231204233900-a34142ec2a72
9+
github.com/hashicorp/terraform-config-inspect v0.0.0-20240509232506-4708120f8f30
1010
github.com/manifoldco/promptui v0.9.0
1111
github.com/mitchellh/go-homedir v1.1.0
1212
github.com/pborman/getopt v1.1.0
1313
github.com/spf13/viper v1.19.0
14+
github.com/stretchr/testify v1.9.0
1415
golang.org/x/crypto v0.23.0
1516
golang.org/x/sys v0.20.0
1617
)
@@ -19,6 +20,7 @@ require (
1920
github.com/agext/levenshtein v1.2.3 // indirect
2021
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
2122
github.com/chzyer/readline v1.5.1 // indirect
23+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
2224
github.com/fsnotify/fsnotify v1.7.0 // indirect
2325
github.com/google/go-cmp v0.6.0 // indirect
2426
github.com/gookit/color v1.5.4 // indirect
@@ -29,8 +31,9 @@ require (
2931
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
3032
github.com/mitchellh/mapstructure v1.5.0 // indirect
3133
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
34+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3235
github.com/rogpeppe/go-internal v1.12.0 // indirect
33-
github.com/sagikazarmark/locafero v0.4.0 // indirect
36+
github.com/sagikazarmark/locafero v0.5.0 // indirect
3437
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
3538
github.com/sourcegraph/conc v0.3.0 // indirect
3639
github.com/spf13/afero v1.11.0 // indirect
@@ -41,11 +44,11 @@ require (
4144
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
4245
github.com/zclconf/go-cty v1.14.4 // indirect
4346
go.uber.org/multierr v1.11.0 // indirect
44-
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
47+
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
4548
golang.org/x/mod v0.17.0 // indirect
4649
golang.org/x/sync v0.7.0 // indirect
4750
golang.org/x/text v0.15.0 // indirect
48-
golang.org/x/tools v0.20.0 // indirect
51+
golang.org/x/tools v0.21.0 // indirect
4952
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
5053
gopkg.in/ini.v1 v1.67.0 // indirect
5154
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31
3737
github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
3838
github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc=
3939
github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4=
40-
github.com/hashicorp/terraform-config-inspect v0.0.0-20231204233900-a34142ec2a72 h1:nZ5gGjbe5o7XUu1d7j+Y5Ztcxlp+yaumTKH9i0D3wlg=
41-
github.com/hashicorp/terraform-config-inspect v0.0.0-20231204233900-a34142ec2a72/go.mod h1:l8HcFPm9cQh6Q0KSWoYPiePqMvRFenybP1CH2MjKdlg=
40+
github.com/hashicorp/terraform-config-inspect v0.0.0-20240509232506-4708120f8f30 h1:0qwr2oZy9mIIJMWh7W9NTHLWGMbEF5KEQ+QqM9hym34=
41+
github.com/hashicorp/terraform-config-inspect v0.0.0-20240509232506-4708120f8f30/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI=
4242
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
4343
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
4444
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -65,8 +65,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
6565
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6666
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
6767
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
68-
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
69-
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
68+
github.com/sagikazarmark/locafero v0.5.0 h1:zXz2JnQDgE5gDg0R9ThkNT0orQzm47i8IuO6hk6XSYY=
69+
github.com/sagikazarmark/locafero v0.5.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
7070
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
7171
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
7272
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@@ -102,8 +102,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
102102
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
103103
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
104104
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
105-
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
106-
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
105+
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
106+
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
107107
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
108108
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
109109
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
@@ -116,8 +116,8 @@ golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
116116
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
117117
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
118118
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
119-
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
120-
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
119+
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
120+
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
121121
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
122122
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
123123
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

lib/defaults.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ var (
1010
PubKeyUri = "https://www.hashicorp.com/.well-known/pgp-key.txt"
1111
)
1212

13-
const (
14-
pubKeySuffix = ".asc"
15-
)
16-
1713
// GetDefaultBin Get default binary path
1814
func GetDefaultBin() string {
1915
var defaultBin = "/usr/local/bin/terraform"
@@ -27,8 +23,11 @@ func GetDefaultBin() string {
2723
const (
2824
DefaultMirror = "https://releases.hashicorp.com/terraform"
2925
DefaultLatest = ""
26+
distributionTerraform = "terraform"
27+
distributionOpenTofu = "opentofu"
3028
installFile = "terraform"
3129
InstallDir = ".terraform.versions"
30+
pubKeySuffix = ".asc"
3231
recentFile = "RECENT"
3332
tfDarwinArm64StartVersion = "1.0.2"
3433
VersionPrefix = "terraform_"

lib/install.go

Lines changed: 8 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func install(tfversion string, binPath string, installPath string, mirrorURL str
9090
/* set symlink to desired version */
9191
CreateSymlink(installFileVersionPath, binPath)
9292
logger.Infof("Switched terraform to version %q", tfversion)
93-
addRecent(tfversion, installPath) //add to recent file for faster lookup
93+
addRecent(tfversion, installPath, distributionTerraform) //add to recent file for faster lookup
9494
return
9595
}
9696

@@ -134,99 +134,11 @@ func install(tfversion string, binPath string, installPath string, mirrorURL str
134134
/* set symlink to desired version */
135135
CreateSymlink(installFileVersionPath, binPath)
136136
logger.Infof("Switched terraform to version %q", tfversion)
137-
addRecent(tfversion, installPath) //add to recent file for faster lookup
137+
addRecent(tfversion, installPath, distributionTerraform) //add to recent file for faster lookup
138138
return
139139
}
140140

141-
// addRecent : add to recent file
142-
func addRecent(requestedVersion string, installPath string) {
143-
144-
installLocation = GetInstallLocation(installPath) //get installation location - this is where we will put our terraform binary file
145-
versionFile := filepath.Join(installLocation, recentFile)
146-
147-
fileExist := CheckFileExist(versionFile)
148-
if fileExist {
149-
lines, errRead := ReadLines(versionFile)
150-
151-
if errRead != nil {
152-
logger.Errorf("Error reading %q file: %v", versionFile, errRead)
153-
return
154-
}
155-
156-
for _, line := range lines {
157-
if !validVersionFormat(line) {
158-
logger.Infof("File %q is dirty (recreating cache file)", versionFile)
159-
RemoveFiles(versionFile)
160-
CreateRecentFile(requestedVersion, installPath)
161-
return
162-
}
163-
}
164-
165-
versionExist := versionExist(requestedVersion, lines)
166-
167-
if !versionExist {
168-
if len(lines) >= 3 {
169-
_, lines = lines[len(lines)-1], lines[:len(lines)-1]
170-
171-
lines = append([]string{requestedVersion}, lines...)
172-
_ = WriteLines(lines, versionFile)
173-
} else {
174-
lines = append([]string{requestedVersion}, lines...)
175-
_ = WriteLines(lines, versionFile)
176-
}
177-
}
178-
179-
} else {
180-
CreateRecentFile(requestedVersion, installPath)
181-
}
182-
}
183-
184-
// getRecentVersions : get recent version from file
185-
func getRecentVersions(installPath string) ([]string, error) {
186-
187-
installLocation = GetInstallLocation(installPath) //get installation location - this is where we will put our terraform binary file
188-
versionFile := filepath.Join(installLocation, recentFile)
189-
190-
fileExist := CheckFileExist(versionFile)
191-
if fileExist {
192-
193-
lines, errRead := ReadLines(versionFile)
194-
var outputRecent []string
195-
196-
if errRead != nil {
197-
logger.Errorf("Error reading %q file: %f", versionFile, errRead)
198-
return nil, errRead
199-
}
200-
201-
for _, line := range lines {
202-
/* checks if versions in the recent file are valid.
203-
If any version is invalid, it will be considered dirty
204-
and the recent file will be removed
205-
*/
206-
if !validVersionFormat(line) {
207-
RemoveFiles(versionFile)
208-
return nil, errRead
209-
}
210-
211-
/* output can be confusing since it displays the 3 most recent used terraform version
212-
append the string *recent to the output to make it more user friendly
213-
*/
214-
outputRecent = append(outputRecent, fmt.Sprintf("%s *recent", line))
215-
}
216-
217-
return outputRecent, nil
218-
}
219-
220-
return nil, nil
221-
}
222-
223-
// CreateRecentFile : create RECENT file
224-
func CreateRecentFile(requestedVersion string, installPath string) {
225-
installLocation = GetInstallLocation(installPath) //get installation location - this is where we will put our terraform binary file
226-
_ = WriteLines([]string{requestedVersion}, filepath.Join(installLocation, recentFile))
227-
}
228-
229-
// ConvertExecutableExt : convert excutable with local OS extension
141+
// ConvertExecutableExt : convert executable with local OS extension
230142
func ConvertExecutableExt(fpath string) string {
231143
switch runtime.GOOS {
232144
case "windows":
@@ -314,7 +226,7 @@ func InstallVersion(dryRun bool, version, customBinaryPath, installPath, mirrorU
314226
if recentDownloadFile {
315227
ChangeSymlink(installFileVersionPath, customBinaryPath)
316228
logger.Infof("Switched terraform to version %q", requestedVersion)
317-
addRecent(requestedVersion, installPath) //add to recent file for faster lookup
229+
addRecent(requestedVersion, installPath, distributionTerraform) //add to recent file for faster lookup
318230
return
319231
}
320232

@@ -341,10 +253,10 @@ func InstallVersion(dryRun bool, version, customBinaryPath, installPath, mirrorU
341253
/* listAll = true - all versions including beta and rc will be displayed */
342254
/* listAll = false - only official stable release are displayed */
343255
func InstallOption(listAll, dryRun bool, customBinaryPath, installPath string, mirrorURL string) {
344-
tflist, _ := getTFList(mirrorURL, listAll) // Get list of versions
345-
recentVersions, _ := getRecentVersions(installPath) // Get recent versions from RECENT file
346-
tflist = append(recentVersions, tflist...) // Append recent versions to the top of the list
347-
tflist = removeDuplicateVersions(tflist) // Remove duplicate version
256+
tflist, _ := getTFList(mirrorURL, listAll) // Get list of versions
257+
recentVersions, _ := getRecentVersions(installPath, distributionTerraform) // Get recent versions from RECENT file
258+
tflist = append(recentVersions, tflist...) // Append recent versions to the top of the list
259+
tflist = removeDuplicateVersions(tflist) // Remove duplicate version
348260

349261
if len(tflist) == 0 {
350262
logger.Fatalf("Terraform version list is empty: %s", mirrorURL)

lib/recent.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package lib
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
type RecentFiles struct {
11+
Terraform []string `json:"terraform"`
12+
OpenTofu []string `json:"opentofu"`
13+
}
14+
15+
func addRecent(requestedVersion string, installPath string, distribution string) {
16+
if !validVersionFormat(requestedVersion) {
17+
logger.Errorf("The version %q is not a valid version string and won't be stored", requestedVersion)
18+
return
19+
}
20+
installLocation := GetInstallLocation(installPath)
21+
recentFilePath := filepath.Join(installLocation, recentFile)
22+
var recentFileData RecentFiles
23+
if CheckFileExist(recentFilePath) {
24+
unmarshalRecentFileData(recentFilePath, &recentFileData)
25+
}
26+
prependRecentVersionToList(requestedVersion, distribution, &recentFileData)
27+
saveRecentFile(recentFileData, recentFilePath)
28+
}
29+
30+
func prependRecentVersionToList(version, distribution string, r *RecentFiles) {
31+
var sliceToCheck []string
32+
switch distribution {
33+
case distributionTerraform:
34+
sliceToCheck = r.Terraform
35+
case distributionOpenTofu:
36+
sliceToCheck = r.OpenTofu
37+
}
38+
for versionIndex, versionValue := range sliceToCheck {
39+
if versionValue == version {
40+
sliceToCheck = append(sliceToCheck[:versionIndex], sliceToCheck[versionIndex+1:]...)
41+
}
42+
}
43+
sliceToCheck = append([]string{version}, sliceToCheck...)
44+
45+
switch distribution {
46+
case distributionTerraform:
47+
r.Terraform = sliceToCheck
48+
case distributionOpenTofu:
49+
r.OpenTofu = sliceToCheck
50+
}
51+
}
52+
53+
func getRecentVersions(installPath string, dist string) ([]string, error) {
54+
installLocation := GetInstallLocation(installPath)
55+
recentFilePath := filepath.Join(installLocation, recentFile)
56+
var recentFileData RecentFiles
57+
unmarshalRecentFileData(recentFilePath, &recentFileData)
58+
var listOfRecentVersions []string
59+
switch dist {
60+
case distributionTerraform:
61+
listOfRecentVersions = recentFileData.Terraform
62+
case distributionOpenTofu:
63+
listOfRecentVersions = recentFileData.OpenTofu
64+
}
65+
var maxCount int
66+
if len(listOfRecentVersions) >= 5 {
67+
maxCount = 5
68+
} else {
69+
maxCount = len(listOfRecentVersions)
70+
}
71+
var returnedRecentVersions []string
72+
for i := 0; i < maxCount; i++ {
73+
returnedRecentVersions = append(returnedRecentVersions, listOfRecentVersions[i]+" *recent")
74+
}
75+
return returnedRecentVersions, nil
76+
}
77+
78+
func unmarshalRecentFileData(recentFilePath string, recentFileData *RecentFiles) {
79+
recentFileContent, err := os.ReadFile(recentFilePath)
80+
if err != nil {
81+
logger.Errorf("Could not open recent versions file %q", recentFilePath)
82+
}
83+
if string(recentFileContent[0:1]) != "{" {
84+
convertOldRecentFile(recentFileContent, recentFileData)
85+
} else {
86+
err = json.Unmarshal(recentFileContent, &recentFileData)
87+
if err != nil {
88+
logger.Errorf("Could not unmarshal recent versions content from %q file", recentFilePath)
89+
}
90+
}
91+
}
92+
93+
func convertOldRecentFile(content []byte, recentFileData *RecentFiles) {
94+
lines := strings.Split(string(content), "\n")
95+
for _, s := range lines {
96+
if s != "" {
97+
recentFileData.Terraform = append(recentFileData.Terraform, s)
98+
}
99+
}
100+
}
101+
102+
func saveRecentFile(data RecentFiles, path string) {
103+
bytes, err := json.Marshal(data)
104+
if err != nil {
105+
logger.Errorf("Could not marshal data to JSON: %v", err)
106+
}
107+
err = os.WriteFile(path, bytes, 0644)
108+
if err != nil {
109+
logger.Errorf("Could not save file %q: %v", path, err)
110+
}
111+
}

0 commit comments

Comments
 (0)