2022-02-23 18:15:12 +00:00
|
|
|
package versionedTerraform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/zip"
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
2024-09-12 00:27:10 +00:00
|
|
|
"fmt"
|
2022-02-23 18:15:12 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2022-04-27 11:35:49 +00:00
|
|
|
"regexp"
|
2022-02-23 18:15:12 +00:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Version struct {
|
|
|
|
Version SemVersion
|
|
|
|
availableVersions []SemVersion
|
|
|
|
installedVersions []SemVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
hashicorpUrl = "https://releases.hashicorp.com/terraform/"
|
|
|
|
terraformPrefix = "terraform_"
|
|
|
|
versionedTerraformFolder = "/.versionedTerraform"
|
|
|
|
)
|
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
// getLatestMajorRelease() returns the latest major release from Version
|
2022-02-23 18:15:12 +00:00
|
|
|
func (v *Version) getLatestMajorRelease() {
|
|
|
|
for _, release := range v.availableVersions {
|
|
|
|
if release.majorVersion == v.Version.majorVersion &&
|
|
|
|
release.minorVersion == v.Version.minorVersion &&
|
2022-06-16 02:52:19 +00:00
|
|
|
release.patchVersion >= v.Version.patchVersion &&
|
|
|
|
(v.Version.isStable || !needsStable) {
|
2022-02-23 18:15:12 +00:00
|
|
|
v.Version = release
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
// getGreatestRelease() returns less than release
|
2022-04-27 11:35:49 +00:00
|
|
|
func (v *Version) getOneLessRelease() {
|
|
|
|
var vSlice []Version
|
|
|
|
|
|
|
|
for _, release := range v.availableVersions {
|
|
|
|
_v := Version{
|
|
|
|
Version: release,
|
|
|
|
availableVersions: v.availableVersions,
|
|
|
|
installedVersions: v.installedVersions,
|
|
|
|
}
|
|
|
|
|
|
|
|
if isVersionGreater(*v, _v) {
|
|
|
|
vSlice = append(vSlice, _v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, ver := range vSlice {
|
|
|
|
if isVersionGreater(ver, *v) || i == 0 {
|
|
|
|
*v = ver
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
// getLatestRelease returns the latest release from Version
|
2022-02-23 18:15:12 +00:00
|
|
|
func (v *Version) getLatestRelease() {
|
|
|
|
for _, release := range v.availableVersions {
|
2022-06-16 02:52:19 +00:00
|
|
|
if release.majorVersion > v.Version.majorVersion &&
|
|
|
|
(release.isStable || !needsStable) {
|
2022-02-23 18:15:12 +00:00
|
|
|
v.Version = release
|
|
|
|
}
|
|
|
|
if release.majorVersion >= v.Version.majorVersion &&
|
2022-06-16 02:52:19 +00:00
|
|
|
release.minorVersion > v.Version.minorVersion &&
|
|
|
|
(release.isStable || !needsStable) {
|
2022-02-23 18:15:12 +00:00
|
|
|
v.Version = release
|
|
|
|
}
|
|
|
|
if release.majorVersion >= v.Version.majorVersion &&
|
|
|
|
release.minorVersion >= v.Version.minorVersion &&
|
2022-06-16 02:52:19 +00:00
|
|
|
release.patchVersion >= v.Version.patchVersion &&
|
|
|
|
(release.isStable || !needsStable) {
|
2022-02-23 18:15:12 +00:00
|
|
|
v.Version = release
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
// InstallTerraformVersion installs the defined terraform Version in the application
|
|
|
|
// configuration directory
|
2022-02-23 18:15:12 +00:00
|
|
|
func (v *Version) InstallTerraformVersion() error {
|
|
|
|
homeDir, _ := os.UserHomeDir()
|
2023-10-03 23:26:18 +00:00
|
|
|
suffix := fileSuffix
|
|
|
|
minV := NewSemVersion(minVersion)
|
|
|
|
if v.Version.IsLessThan(*minV) {
|
|
|
|
suffix = alternateSuffix
|
|
|
|
}
|
2024-09-12 00:27:10 +00:00
|
|
|
url := hashicorpUrl +
|
2022-02-23 18:15:12 +00:00
|
|
|
v.Version.ToString() +
|
|
|
|
"/" + terraformPrefix +
|
|
|
|
v.Version.ToString() +
|
2024-09-12 00:27:10 +00:00
|
|
|
suffix
|
|
|
|
|
|
|
|
resp, err := http.Get(url)
|
2022-02-23 18:15:12 +00:00
|
|
|
if err != nil {
|
2024-09-12 00:27:10 +00:00
|
|
|
return fmt.Errorf("failed to download Terraform: %v", err)
|
2022-02-23 18:15:12 +00:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
2024-09-12 00:27:10 +00:00
|
|
|
return fmt.Errorf("failed to read response body: %v", err)
|
2022-02-23 18:15:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
|
|
|
|
if err != nil {
|
2024-09-12 00:27:10 +00:00
|
|
|
return fmt.Errorf("failed to create zip reader: %v", err)
|
2022-02-23 18:15:12 +00:00
|
|
|
}
|
2024-09-12 00:27:10 +00:00
|
|
|
|
2022-02-23 18:15:12 +00:00
|
|
|
versionedFileName := homeDir + versionedTerraformFolder + "/" + terraformPrefix + v.Version.ToString()
|
2024-09-12 00:27:10 +00:00
|
|
|
versionedFile, err := os.OpenFile(versionedFileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
2022-02-23 18:15:12 +00:00
|
|
|
if err != nil {
|
2024-09-12 00:27:10 +00:00
|
|
|
return fmt.Errorf("failed to create output file: %v", err)
|
2022-02-23 18:15:12 +00:00
|
|
|
}
|
|
|
|
defer versionedFile.Close()
|
|
|
|
|
2023-10-03 23:26:18 +00:00
|
|
|
for _, zipFile := range zipReader.File {
|
2024-09-12 00:27:10 +00:00
|
|
|
if zipFile.Name != "terraform" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-10-03 23:26:18 +00:00
|
|
|
zr, err := zipFile.Open()
|
2022-02-23 18:15:12 +00:00
|
|
|
if err != nil {
|
2024-09-12 00:27:10 +00:00
|
|
|
return fmt.Errorf("failed to open zip file: %v", err)
|
2022-02-23 18:15:12 +00:00
|
|
|
}
|
2024-09-12 00:27:10 +00:00
|
|
|
defer zr.Close()
|
2022-02-23 18:15:12 +00:00
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
_, err = io.Copy(versionedFile, zr)
|
2022-02-23 18:15:12 +00:00
|
|
|
if err != nil {
|
2024-09-12 00:27:10 +00:00
|
|
|
return fmt.Errorf("failed to write terraform binary: %v", err)
|
2022-02-23 18:15:12 +00:00
|
|
|
}
|
2024-09-12 00:27:10 +00:00
|
|
|
|
|
|
|
break
|
2022-02-23 18:15:12 +00:00
|
|
|
}
|
2024-09-12 00:27:10 +00:00
|
|
|
|
2022-02-23 18:15:12 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
// NewVersion creates a new Version using sem versioning for determining the
|
|
|
|
// latest release
|
2022-02-23 18:15:12 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2022-04-27 11:35:49 +00:00
|
|
|
hasMultiVersions, _ := regexp.MatchString("\\d+,", v.Version.ToString())
|
|
|
|
|
|
|
|
if hasMultiVersions {
|
|
|
|
releases := strings.Split(v.Version.ToString(), ",")
|
|
|
|
for iteration, _release := range releases {
|
|
|
|
_v := new(Version)
|
|
|
|
_v.availableVersions = v.availableVersions
|
|
|
|
switch {
|
|
|
|
case strings.Contains(_release, latestRelease):
|
|
|
|
release := strings.Split(_release, latestRelease)[1]
|
|
|
|
_v.Version = *NewSemVersion(release)
|
|
|
|
_v.getLatestRelease()
|
|
|
|
case strings.Contains(_release, latestPatch):
|
|
|
|
release := strings.Split(_release, latestPatch)[1]
|
|
|
|
_v.Version = *NewSemVersion(release)
|
|
|
|
_v.getLatestMajorRelease()
|
|
|
|
case strings.Contains(_release, versionLessOrEqual):
|
|
|
|
release := strings.Split(_release, versionLessOrEqual)[1]
|
|
|
|
_v.Version = *NewSemVersion(release)
|
|
|
|
case strings.Contains(_release, versionLessThan):
|
|
|
|
release := strings.Split(_release, versionLessThan)[1]
|
|
|
|
_v.Version = *NewSemVersion(release)
|
|
|
|
_v.getOneLessRelease()
|
|
|
|
}
|
|
|
|
|
|
|
|
if isVersionGreater(*_v, *v) || iteration == 0 {
|
|
|
|
v = _v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2022-02-23 18:15:12 +00:00
|
|
|
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()
|
2022-04-27 11:35:49 +00:00
|
|
|
case strings.Contains(v.Version.ToString(), versionLessOrEqual):
|
|
|
|
release := strings.Split(v.Version.ToString(), versionLessOrEqual)[1]
|
|
|
|
v.Version = *NewSemVersion(release)
|
|
|
|
case strings.Contains(v.Version.ToString(), versionLessThan):
|
|
|
|
release := strings.Split(v.Version.ToString(), versionLessThan)[1]
|
|
|
|
v.Version = *NewSemVersion(release)
|
|
|
|
v.getOneLessRelease()
|
2022-02-23 18:15:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
// GetVersionList returns a list of available versions from hashicorp's release page
|
2022-02-23 18:15:12 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
// removeSpacesVersion removes spaces from Version string for parsing
|
2022-02-23 18:15:12 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
// VersionToString returns string of a Version
|
2022-02-23 18:15:12 +00:00
|
|
|
func (v *Version) VersionToString() string {
|
|
|
|
return v.Version.ToString()
|
|
|
|
}
|
2022-04-27 11:35:49 +00:00
|
|
|
|
2024-09-12 00:27:10 +00:00
|
|
|
// isVersionGreater returns true if v1 is greater than v2
|
2022-04-27 11:35:49 +00:00
|
|
|
func isVersionGreater(v1 Version, v2 Version) bool {
|
|
|
|
if v1.Version.majorVersion != v2.Version.majorVersion {
|
|
|
|
if v1.Version.majorVersion > v2.Version.majorVersion {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if v1.Version.minorVersion != v2.Version.minorVersion {
|
|
|
|
if v1.Version.minorVersion > v2.Version.minorVersion {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if v1.Version.patchVersion != v2.Version.patchVersion {
|
|
|
|
if v1.Version.patchVersion > v2.Version.patchVersion {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|