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