Initial commit
This commit is contained in:
commit
8b24af46e1
82
.gitignore
vendored
Normal file
82
.gitignore
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/macos,visualstudiocode,terraform,windows
|
||||||
|
# Edit at https://www.gitignore.io/?templates=macos,visualstudiocode,terraform,windows
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# Crash log files
|
||||||
|
crash.log
|
||||||
|
|
||||||
|
# Include override files you do wish to add to version control using negated pattern
|
||||||
|
#
|
||||||
|
# !example_override.tf
|
||||||
|
|
||||||
|
### VisualStudioCode ###
|
||||||
|
.vscode/*
|
||||||
|
#!.vscode/settings.json
|
||||||
|
#!.vscode/tasks.json
|
||||||
|
#!.vscode/launch.json
|
||||||
|
#!.vscode/extensions.json
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/macos,visualstudiocode,terraform,windows
|
||||||
|
|
||||||
|
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
|
||||||
|
.vscode/*
|
||||||
|
|
||||||
|
.idea
|
27
Makefile
Normal file
27
Makefile
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
####################################################
|
||||||
|
# Build
|
||||||
|
####################################################
|
||||||
|
build:
|
||||||
|
go build -o versionedTerraform ./cmd
|
||||||
|
####################################################
|
||||||
|
# Clean
|
||||||
|
####################################################
|
||||||
|
clean:
|
||||||
|
rm -f ~/.local/bin/versionedTerraform
|
||||||
|
####################################################
|
||||||
|
# Install
|
||||||
|
####################################################
|
||||||
|
install:
|
||||||
|
mv versionedTerraform ~/.local/bin/
|
||||||
|
####################################################
|
||||||
|
# help feature
|
||||||
|
####################################################
|
||||||
|
help:
|
||||||
|
@echo ''
|
||||||
|
@echo 'Usage: make [TARGET]'
|
||||||
|
@echo 'Targets:'
|
||||||
|
@echo ' build go build -o versionedTerraform ./cmd'
|
||||||
|
@echo ' clean removes installed versionedTerraform file'
|
||||||
|
@echo ' install installs versionedTerraform to local user bin folder'
|
||||||
|
@echo ' all Nothing to do.'
|
||||||
|
@echo ''
|
22
README.md
Normal file
22
README.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#Versioned Terraform
|
||||||
|
A wrapper for terraform to detect the expected version of terraform,
|
||||||
|
download, and execute that version
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
- go compiler (only tested on go1.17)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
`make build install` for installation to local user<br>
|
||||||
|
`make build` will create an executable file for you to place where you'd like
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
```
|
||||||
|
All arguments are passed through to terraform
|
||||||
|
```
|
||||||
|
|
||||||
|
## sample usage
|
||||||
|
`versionedTerraform version` will display the terraform version executed in a folder
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
Currently, does not support semantic versioning between values<br>
|
||||||
|
i.e. `required_version = "~> 0.14", "< 0.14.3"`
|
73
SemVersion.go
Normal file
73
SemVersion.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package versionedTerraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
//todo include others if needed
|
||||||
|
//todo add comparison i.e. >= 0.11.10, < 0.12.0
|
||||||
|
latestRelease = ">="
|
||||||
|
latestPatch = "~>"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SemVersion struct {
|
||||||
|
version string
|
||||||
|
majorVersion int
|
||||||
|
minorVersion int
|
||||||
|
patchVersion int
|
||||||
|
}
|
||||||
|
|
||||||
|
type SemVersionInterface interface {
|
||||||
|
setMajorVersion()
|
||||||
|
setMinorVersion()
|
||||||
|
setPatchVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSemVersion(v string) *SemVersion {
|
||||||
|
s := new(SemVersion)
|
||||||
|
s.version = removeSpacesVersion(v)
|
||||||
|
|
||||||
|
s.setMajorVersion()
|
||||||
|
s.setMinorVersion()
|
||||||
|
s.setPatchVersion()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SemVersion) setMajorVersion() {
|
||||||
|
version := s.version
|
||||||
|
majorVersionString := strings.Split(version, ".")[0]
|
||||||
|
s.majorVersion, _ = strconv.Atoi(majorVersionString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SemVersion) setMinorVersion() {
|
||||||
|
version := s.version
|
||||||
|
minorVersionString := strings.Split(version, ".")[1]
|
||||||
|
s.minorVersion, _ = strconv.Atoi(minorVersionString)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SemVersion) setPatchVersion() {
|
||||||
|
version := s.version
|
||||||
|
patchStringSlice := strings.Split(version, ".")
|
||||||
|
if len(patchStringSlice) < 3 {
|
||||||
|
s.patchVersion = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.patchVersion, _ = strconv.Atoi(patchStringSlice[2])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SemVersion) ToString() string {
|
||||||
|
return s.version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SemVersion) VersionInSlice(sSem []SemVersion) bool {
|
||||||
|
for _, ver := range sSem {
|
||||||
|
if ver.ToString() == s.ToString() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
7
cmd/examples/example.tf
Normal file
7
cmd/examples/example.tf
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = "~> 0.14"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "hello" {
|
||||||
|
value = "world"
|
||||||
|
}
|
76
cmd/main.go
Normal file
76
cmd/main.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"versionedTerraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
configFileLocation = "config"
|
||||||
|
shortConfigDirString = "/.versionedTerraform"
|
||||||
|
pwd = "."
|
||||||
|
terraformPrefix = "/terraform_"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
homeDir, _ := os.UserHomeDir()
|
||||||
|
configDirString := homeDir + shortConfigDirString
|
||||||
|
configDir := os.DirFS(configDirString)
|
||||||
|
workingDir := os.DirFS(pwd)
|
||||||
|
var versionsFromConfig []versionedTerraform.SemVersion
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
needsUpdate, err := versionedTerraform.NeedToUpdateAvailableVersions(configDir, configFileLocation)
|
||||||
|
if needsUpdate {
|
||||||
|
fileHandle, _ := os.OpenFile(configDirString+"/"+configFileLocation, os.O_RDWR, 0666)
|
||||||
|
defer fileHandle.Close()
|
||||||
|
versionedTerraform.UpdateConfig(*fileHandle)
|
||||||
|
}
|
||||||
|
versionsFromConfig, err = versionedTerraform.LoadVersionsFromConfig(configDir, configFileLocation)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to read config: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
installedVersions, err := versionedTerraform.LoadInstalledVersions(configDir)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to verify installed verisons: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var vSlice []string
|
||||||
|
for _, v := range versionsFromConfig {
|
||||||
|
vSlice = append(vSlice, v.ToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
ver, err := versionedTerraform.GetVersionFromFile(workingDir, vSlice)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to retrieve terraform version from files: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ver.Version.VersionInSlice(installedVersions) {
|
||||||
|
fmt.Printf("Installing terraform version %s\n\n", ver.Version.ToString())
|
||||||
|
err = ver.InstallTerraformVersion()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to install terraform version: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terraformFile := configDirString + terraformPrefix + ver.VersionToString()
|
||||||
|
argsForTerraform := append([]string{""}, args...)
|
||||||
|
cmd := exec.Cmd{
|
||||||
|
Path: terraformFile,
|
||||||
|
Args: argsForTerraform,
|
||||||
|
Env: os.Environ(),
|
||||||
|
Dir: pwd,
|
||||||
|
Stdin: os.Stdin,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
cmd.Run()
|
||||||
|
}
|
111
configManagement.go
Normal file
111
configManagement.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package versionedTerraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type configStruct struct {
|
||||||
|
LastUpdate int64
|
||||||
|
AvailableVersions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NeedToUpdateAvailableVersions(fileSystem fs.FS, availableVersions string) (bool, error) {
|
||||||
|
//todo this is used a lot abstract it?
|
||||||
|
fileHandle, err := fileSystem.Open(availableVersions)
|
||||||
|
oneDayAgo := time.Now().AddDate(0, 0, -1).Unix()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer fileHandle.Close()
|
||||||
|
|
||||||
|
fileScanner := bufio.NewScanner(fileHandle)
|
||||||
|
fileScanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
|
for fileScanner.Scan() {
|
||||||
|
_line := fileScanner.Text()
|
||||||
|
if strings.Contains(_line, "LastUpdate: ") {
|
||||||
|
lastUpdateTimeString := strings.SplitAfter(_line, "LastUpdate: ")[1]
|
||||||
|
lastUpdateTimeString = strings.TrimSpace(lastUpdateTimeString)
|
||||||
|
lastUpdateTime, err := strconv.ParseInt(lastUpdateTimeString, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if lastUpdateTime <= oneDayAgo {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadVersionsFromConfig(fileSystem fs.FS, configFile string) ([]SemVersion, error) {
|
||||||
|
fileHandle, err := fileSystem.Open(configFile)
|
||||||
|
removeOpenBracket := regexp.MustCompile("\\[")
|
||||||
|
removeCloseBracket := regexp.MustCompile("]")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fileHandle.Close()
|
||||||
|
|
||||||
|
fileScanner := bufio.NewScanner(fileHandle)
|
||||||
|
fileScanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
|
for fileScanner.Scan() {
|
||||||
|
_line := fileScanner.Text()
|
||||||
|
if strings.Contains(_line, "AvailableVersions: ") {
|
||||||
|
var versionList []SemVersion
|
||||||
|
_line = strings.SplitAfter(_line, "AvailableVersions: ")[1]
|
||||||
|
_line = removeOpenBracket.ReplaceAllString(_line, "")
|
||||||
|
_line = removeCloseBracket.ReplaceAllString(_line, "")
|
||||||
|
versions := strings.Split(_line, " ")
|
||||||
|
for _, version := range versions {
|
||||||
|
versionList = append(versionList, *NewSemVersion(version))
|
||||||
|
}
|
||||||
|
return versionList, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadInstalledVersions(fileSystem fs.FS) ([]SemVersion, error) {
|
||||||
|
dir, err := fs.ReadDir(fileSystem, ".")
|
||||||
|
var installedTerraformVersions []SemVersion
|
||||||
|
terraformRegex := regexp.MustCompile(terraformPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range dir {
|
||||||
|
terraformFileName := f.Name()
|
||||||
|
if strings.Contains(terraformFileName, terraformPrefix) {
|
||||||
|
terraformVersionString := terraformRegex.ReplaceAllString(terraformFileName, "")
|
||||||
|
installedTerraformVersions = append(installedTerraformVersions, *NewSemVersion(terraformVersionString))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return installedTerraformVersions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateConfig(File os.File) error {
|
||||||
|
configValues := new(configStruct)
|
||||||
|
|
||||||
|
configValues.AvailableVersions, _ = GetVersionList()
|
||||||
|
|
||||||
|
timeNow := time.Now()
|
||||||
|
configValues.LastUpdate = timeNow.Unix()
|
||||||
|
|
||||||
|
File.Truncate(0)
|
||||||
|
File.Seek(0, 0)
|
||||||
|
|
||||||
|
lineToByte := []byte(fmt.Sprintf("LastUpdate: %d\n", configValues.LastUpdate))
|
||||||
|
File.Write(lineToByte)
|
||||||
|
lineToByte = []byte(fmt.Sprintf("AvailableVersions: %+v\n", configValues.AvailableVersions))
|
||||||
|
File.Write(lineToByte)
|
||||||
|
return nil
|
||||||
|
}
|
112
configManagement_test.go
Normal file
112
configManagement_test.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package versionedTerraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateAvailableVersions(t *testing.T) {
|
||||||
|
timeNow := time.Now()
|
||||||
|
currentTime := timeNow.Unix()
|
||||||
|
twoDaysAgoTime := timeNow.AddDate(0, 0, -2).Unix()
|
||||||
|
|
||||||
|
successUpdate := fmt.Sprintf("LastUpdate: %d", currentTime)
|
||||||
|
needsUpdate := fmt.Sprintf("LastUpdate: %d", twoDaysAgoTime)
|
||||||
|
|
||||||
|
fs := fstest.MapFS{
|
||||||
|
"successConfig.conf": {Data: []byte(successUpdate)},
|
||||||
|
"failConfig.conf": {Data: []byte(needsUpdate)},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Test success last update time", func(t *testing.T) {
|
||||||
|
want := true
|
||||||
|
got, err := NeedToUpdateAvailableVersions(fs, "successConfig.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("updateAvailableVersions had incorrect output expected %v got %v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test failed last update time", func(t *testing.T) {
|
||||||
|
want := false
|
||||||
|
got, err := NeedToUpdateAvailableVersions(fs, "failConfig.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("updateAvailableVersions had incorrect output expected %v got %v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvailableVersions(t *testing.T) {
|
||||||
|
availableVersionList := fmt.Sprintf("AvailableVersions: %+v", testVersionList())
|
||||||
|
var want []SemVersion
|
||||||
|
for _, version := range testVersionList() {
|
||||||
|
want = append(want, *NewSemVersion(version))
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := fstest.MapFS{
|
||||||
|
"successConfig.conf": {Data: []byte(availableVersionList)},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Test success last update time", func(t *testing.T) {
|
||||||
|
got, err := LoadVersionsFromConfig(fs, "successConfig.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("LoadInstalledVersions had incorrect output expected %+v\n got %+v", want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstalledVersions(t *testing.T) {
|
||||||
|
var want []SemVersion
|
||||||
|
testVersionList := testVersionList()
|
||||||
|
sort.Strings(testVersionList)
|
||||||
|
for _, version := range testVersionList {
|
||||||
|
want = append(want, *NewSemVersion(version))
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := fstest.MapFS{
|
||||||
|
"terraform_0.12.31": {Data: []byte("")},
|
||||||
|
"terraform_0.12.30": {Data: []byte("")},
|
||||||
|
"terraform_0.11.10": {Data: []byte("")},
|
||||||
|
"terraform_0.11.15": {Data: []byte("")},
|
||||||
|
"terraform_1.0.1": {Data: []byte("")},
|
||||||
|
"terraform_1.0.12": {Data: []byte("")},
|
||||||
|
"terraform_1.1.1": {Data: []byte("")},
|
||||||
|
"terraform_1.1.2": {Data: []byte("")},
|
||||||
|
"terraform_1.1.3": {Data: []byte("")},
|
||||||
|
"terraform_1.1.4": {Data: []byte("")},
|
||||||
|
"terraform_1.1.5": {Data: []byte("")},
|
||||||
|
"terraform_1.1.6": {Data: []byte("")},
|
||||||
|
"terraform_1.1.7": {Data: []byte("")},
|
||||||
|
"terraform_1.1.8": {Data: []byte("")},
|
||||||
|
"terraform_1.1.9": {Data: []byte("")},
|
||||||
|
"terraform_1.1.10": {Data: []byte("")},
|
||||||
|
"terraform_1.1.11": {Data: []byte("")},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Test installed versions", func(t *testing.T) {
|
||||||
|
got, err := LoadInstalledVersions(fs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("LoadInstalledVersions had incorrect output expected\n %+v\n got %+v", want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
56
fileHandler.go
Normal file
56
fileHandler.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package versionedTerraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io/fs"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//todo this should be (Version) GetVers...
|
||||||
|
func GetVersionFromFile(fileSystem fs.FS, versionList []string) (*Version, error) {
|
||||||
|
var versionFinal Version
|
||||||
|
dir, err := fs.ReadDir(fileSystem, ".")
|
||||||
|
if err != nil {
|
||||||
|
return &versionFinal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range dir {
|
||||||
|
version, isFinished, err := parseVersionFromFile(fileSystem, f.Name(), versionList)
|
||||||
|
if err != nil {
|
||||||
|
return &versionFinal, err
|
||||||
|
}
|
||||||
|
if isFinished {
|
||||||
|
versionFinal = *version
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &versionFinal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//todo same here
|
||||||
|
func parseVersionFromFile(f fs.FS, fileName string, versionList []string) (*Version, bool, error) {
|
||||||
|
fileHandle, err := f.Open(fileName)
|
||||||
|
regex := regexp.MustCompile("required_version\\s+?=")
|
||||||
|
isComment := "^\\s?#"
|
||||||
|
removeQuotes := regexp.MustCompile("\"")
|
||||||
|
if err != nil {
|
||||||
|
return &Version{}, false, err
|
||||||
|
}
|
||||||
|
defer fileHandle.Close()
|
||||||
|
|
||||||
|
fileScanner := bufio.NewScanner(fileHandle)
|
||||||
|
fileScanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
|
for fileScanner.Scan() {
|
||||||
|
_line := fileScanner.Text()
|
||||||
|
isComment, _ := regexp.MatchString(isComment, _line)
|
||||||
|
if strings.Contains(_line, "required_version") && !isComment {
|
||||||
|
_line = regex.ReplaceAllString(_line, "")
|
||||||
|
_line = removeQuotes.ReplaceAllString(_line, "")
|
||||||
|
return NewVersion(_line, versionList), true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Version{}, false, nil
|
||||||
|
}
|
49
fileHandler_test.go
Normal file
49
fileHandler_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package versionedTerraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileHandler(t *testing.T) {
|
||||||
|
const (
|
||||||
|
firstFile = `
|
||||||
|
resource "aws_mq_broker" "sample" {
|
||||||
|
depends_on = [aws_security_group.mq]
|
||||||
|
broker_name = var.name
|
||||||
|
engine_type = "ActiveMQ"
|
||||||
|
engine_version = var.mqEngineVersion
|
||||||
|
host_instance_type = var.hostInstanceType
|
||||||
|
security_groups = [aws_security_groups.mq.id]
|
||||||
|
apply_immediately = "true"
|
||||||
|
deployment_mode = "ACTIVE_STANDBY_MULTI_AZ"
|
||||||
|
auto_minor_version_upgrade = "true"
|
||||||
|
subnet_ids = ["10.0.0.0/24", "10.0.1.0/24"]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
secondFile = `
|
||||||
|
terraform {
|
||||||
|
required_version = "~> 0.12.4"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
want := NewVersion("0.12.31", testVersionList())
|
||||||
|
|
||||||
|
fs := fstest.MapFS{
|
||||||
|
"main.tf": {Data: []byte(firstFile)},
|
||||||
|
"versions.tf": {Data: []byte(secondFile)},
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := GetVersionFromFile(fs, testVersionList())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := *version
|
||||||
|
|
||||||
|
if got.Version != want.Version {
|
||||||
|
t.Errorf("Expected %v, got %v", want.Version, got.Version)
|
||||||
|
}
|
||||||
|
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module versionedTerraform
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require github.com/blang/semver/v4 v4.0.0
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||||
|
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
194
versionedTerraform.go
Normal file
194
versionedTerraform.go
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
package versionedTerraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*Version) latestMajorVersion() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Version) latestMinorVersion() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Version) latestPatchVersion() {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Version SemVersion
|
||||||
|
availableVersions []SemVersion
|
||||||
|
installedVersions []SemVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
hashicorpUrl = "https://releases.hashicorp.com/terraform/"
|
||||||
|
terraformPrefix = "terraform_"
|
||||||
|
fileSuffix = "_linux_amd64.zip"
|
||||||
|
versionedTerraformFolder = "/.versionedTerraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
//getLatestMajorRelease() returns the latest major release from Version
|
||||||
|
func (v *Version) getLatestMajorRelease() {
|
||||||
|
//todo clean up
|
||||||
|
for _, release := range v.availableVersions {
|
||||||
|
if release.majorVersion == v.Version.majorVersion &&
|
||||||
|
release.minorVersion == v.Version.minorVersion &&
|
||||||
|
release.patchVersion >= v.Version.patchVersion {
|
||||||
|
v.Version = release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//getLatestRelease returns the latest release from Version
|
||||||
|
func (v *Version) getLatestRelease() {
|
||||||
|
//todo clean up
|
||||||
|
for _, release := range v.availableVersions {
|
||||||
|
if release.majorVersion > v.Version.majorVersion {
|
||||||
|
v.Version = release
|
||||||
|
}
|
||||||
|
if release.majorVersion >= v.Version.majorVersion &&
|
||||||
|
release.minorVersion > v.Version.minorVersion {
|
||||||
|
v.Version = release
|
||||||
|
}
|
||||||
|
if release.majorVersion >= v.Version.majorVersion &&
|
||||||
|
release.minorVersion >= v.Version.minorVersion &&
|
||||||
|
release.patchVersion >= v.Version.patchVersion {
|
||||||
|
v.Version = release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//InstallTerraformVersion installs the defined terraform Version in the application
|
||||||
|
//configuration directory
|
||||||
|
func (v *Version) InstallTerraformVersion() error {
|
||||||
|
homeDir, _ := os.UserHomeDir()
|
||||||
|
resp, err := http.Get(hashicorpUrl +
|
||||||
|
v.Version.ToString() +
|
||||||
|
"/" + terraformPrefix +
|
||||||
|
v.Version.ToString() +
|
||||||
|
fileSuffix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
versionedFileName := homeDir + versionedTerraformFolder + "/" + terraformPrefix + v.Version.ToString()
|
||||||
|
versionedFile, err := os.OpenFile(versionedFileName, os.O_WRONLY, 0755)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
//_, err = os.Create(versionedFileName)
|
||||||
|
//if err != nil {
|
||||||
|
// return err
|
||||||
|
//}
|
||||||
|
versionedFile, err = os.OpenFile(versionedFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer versionedFile.Close()
|
||||||
|
|
||||||
|
for _, zipFIle := range zipReader.File {
|
||||||
|
zr, err := zipFIle.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
unzippedFileBytes, _ := ioutil.ReadAll(zr)
|
||||||
|
|
||||||
|
_, err = versionedFile.Write(unzippedFileBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
zr.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewVersion creates a new Version using sem versioning for determining the
|
||||||
|
//latest release
|
||||||
|
func NewVersion(_version string, _vList []string) *Version {
|
||||||
|
v := new(Version)
|
||||||
|
v.Version = *NewSemVersion(_version)
|
||||||
|
|
||||||
|
for _, release := range _vList {
|
||||||
|
v.availableVersions = append(v.availableVersions, *NewSemVersion(release))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(v.Version.ToString(), latestRelease):
|
||||||
|
release := strings.Split(v.Version.ToString(), latestRelease)[1]
|
||||||
|
v.Version = *NewSemVersion(release)
|
||||||
|
v.getLatestRelease()
|
||||||
|
case strings.Contains(v.Version.ToString(), latestPatch):
|
||||||
|
release := strings.Split(v.Version.ToString(), latestPatch)[1]
|
||||||
|
v.Version = *NewSemVersion(release)
|
||||||
|
v.getLatestMajorRelease()
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetVersionList returns a list of available versions from hashicorp's release page
|
||||||
|
func GetVersionList() ([]string, error) {
|
||||||
|
var versionList []string
|
||||||
|
resp, err := http.Get(hashicorpUrl)
|
||||||
|
if err != nil {
|
||||||
|
return versionList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return versionList, errors.New("invalid response code")
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
//todo maybe change this like GetVersionFromFile and consolidate
|
||||||
|
bodyText := string(body)
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(bodyText))
|
||||||
|
for scanner.Scan() {
|
||||||
|
_line := scanner.Text()
|
||||||
|
if strings.Contains(_line, "a href") {
|
||||||
|
_lineSplice := strings.Split(_line, terraformPrefix)
|
||||||
|
if len(_lineSplice) == 2 {
|
||||||
|
_line = _lineSplice[1]
|
||||||
|
_line = strings.Split(_line, "</a")[0]
|
||||||
|
versionList = append(versionList, _line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return versionList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//removeSpacesVersion removes spaces from Version string for parsing
|
||||||
|
func removeSpacesVersion(v string) string {
|
||||||
|
splitV := strings.Split(v, " ")
|
||||||
|
var returnString string
|
||||||
|
for _, version := range splitV {
|
||||||
|
version := strings.TrimSpace(version)
|
||||||
|
returnString += version
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(returnString)
|
||||||
|
}
|
||||||
|
|
||||||
|
//VersionToString returns string of a Version
|
||||||
|
func (v *Version) VersionToString() string {
|
||||||
|
return v.Version.ToString()
|
||||||
|
}
|
91
versionedTerraform_test.go
Normal file
91
versionedTerraform_test.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package versionedTerraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testVersionList() []string {
|
||||||
|
return []string{
|
||||||
|
"1.1.11",
|
||||||
|
"1.1.10",
|
||||||
|
"1.1.9",
|
||||||
|
"1.1.8",
|
||||||
|
"1.1.7",
|
||||||
|
"1.1.6",
|
||||||
|
"1.1.5",
|
||||||
|
"1.1.4",
|
||||||
|
"1.1.3",
|
||||||
|
"1.1.2",
|
||||||
|
"1.1.1",
|
||||||
|
"1.0.12",
|
||||||
|
"1.0.1",
|
||||||
|
"0.12.31",
|
||||||
|
"0.12.30",
|
||||||
|
"0.11.15",
|
||||||
|
"0.11.10",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVersion(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
available []string
|
||||||
|
version, expected string
|
||||||
|
}{
|
||||||
|
{testVersionList(), "0.12.31", "0.12.31"},
|
||||||
|
{testVersionList(), "0.12.30", "0.12.30"},
|
||||||
|
{testVersionList(), "~> 0.12.30", "0.12.31"},
|
||||||
|
{testVersionList(), "~>0.12.30", "0.12.31"},
|
||||||
|
{testVersionList(), "~>0.12.4", "0.12.31"},
|
||||||
|
{testVersionList(), ">= 0.11.15", "1.1.11"},
|
||||||
|
{testVersionList(), ">= 0.12.0", "1.1.11"},
|
||||||
|
{testVersionList(), "~> 0.12", "0.12.31"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run("test Version check with various conditions: "+c.version, func(t *testing.T) {
|
||||||
|
//t.Parallel()
|
||||||
|
got := NewVersion(c.version, c.available)
|
||||||
|
if got.Version.version != c.expected {
|
||||||
|
t.Errorf("got %q, want %q", got.Version.version, c.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveSpacesVersion(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
tesValue, want string
|
||||||
|
}{
|
||||||
|
{"test", "test"},
|
||||||
|
{"test ", "test"},
|
||||||
|
{" test", "test"},
|
||||||
|
{" test ", "test"},
|
||||||
|
{" test test ", "testtest"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run("test remove space in various conditions: "+c.tesValue, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got := removeSpacesVersion(c.tesValue)
|
||||||
|
if got != c.want {
|
||||||
|
t.Errorf("got %q, want %q", got, c.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVersionList(t *testing.T) {
|
||||||
|
//todo write test for this
|
||||||
|
//response, _ := getVersionList()
|
||||||
|
//for _, Version := range response {
|
||||||
|
// t.Errorf("%v", Version)
|
||||||
|
//}
|
||||||
|
//t.Errorf("%v", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstallTerraformVersion(t *testing.T) {
|
||||||
|
//todo write test for this
|
||||||
|
//Version := NewVersion("0.12.31", testVersionList())
|
||||||
|
//response := Version.InstallTerraformVersion()
|
||||||
|
//t.Errorf("%v", response)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user