Skip to content

Commit

Permalink
- extract params into own struct for better usability
Browse files Browse the repository at this point in the history
- extracted all parameter parsing into an explicit package and ensured the precedence
  • Loading branch information
MatrixCrawler committed Apr 3, 2024
1 parent 6aaa2c4 commit 5837518
Show file tree
Hide file tree
Showing 26 changed files with 533 additions and 392 deletions.
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,12 +320,14 @@ jobs:
```
## Order of precedence
| Order | Method |
| --- | ----------- |
| 1 | .tfswitch.toml |
| 2 | .tfswitchrc |
| 3 | .terraform-version |
| 4 | Environment variable |
| Order | Method |
|-------|-----------------------|
| 1 | .tfswitch.toml |
| 2 | .tfswitchrc |
| 3 | .terraform-version |
| 4 | version.tf (TF Modul) |
| 5 | terragrunt-hcl |
| 6 | Environment variable |
With 1 being the highest precedence and 4 the lowest
*(If you disagree with this order of precedence, please open an issue)*
Expand Down
8 changes: 8 additions & 0 deletions lib/param_parsing/environment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package param_parsing

import "os"

func GetParamsFromEnvironment(params Params) Params {
params.Version = os.Getenv("TF_VERSION")
return params
}
17 changes: 17 additions & 0 deletions lib/param_parsing/environment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package param_parsing

import (
"os"
"testing"
)

func TestGetParamsFromEnvironment_version_from_env(t *testing.T) {
var params Params
expected := "1.0.0_from_env"
_ = os.Setenv("TF_VERSION", expected)
params = initParams(params)
params = GetParamsFromEnvironment(params)
if params.Version != expected {
t.Error("Determined version is not matchching. Got " + params.Version + ", expected " + expected)
}
}
106 changes: 106 additions & 0 deletions lib/param_parsing/parameters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package param_parsing

import (
"fmt"
"github.com/pborman/getopt"
"github.com/warrensbox/terraform-switcher/lib"
"os"
)

type Params struct {
CustomBinaryPath string
ListAllFlag bool
LatestPre string
ShowLatestPre string
LatestStable string
ShowLatestStable string
LatestFlag bool
ShowLatestFlag bool
MirrorURL string
ChDirPath string
VersionFlag bool
DefaultVersion string
HelpFlag bool
Version string
}

const (
defaultMirror = "https://releases.hashicorp.com/terraform"
defaultLatest = ""
)

func GetParameters() Params {
var params Params
params = initParams(params)

getopt.StringVarLong(&params.ChDirPath, "chdir", 'c', "Switch to a different working directory before executing the given command. Ex: tfswitch --chdir terraform_project will run tfswitch in the terraform_project directory")
getopt.BoolVarLong(&params.VersionFlag, "version", 'v', "Displays the version of tfswitch")
getopt.BoolVarLong(&params.HelpFlag, "help", 'h', "Displays help message")
getopt.StringVarLong(&params.MirrorURL, "mirror", 'm', "Install from a remote API other than the default. Default: "+defaultMirror)
getopt.StringVarLong(&params.CustomBinaryPath, "bin", 'b', "Custom binary path. Ex: tfswitch -b "+lib.ConvertExecutableExt("/Users/username/bin/terraform"))
getopt.StringVarLong(&params.LatestPre, "latest-pre", 'p', "Latest pre-release implicit version. Ex: tfswitch --latest-pre 0.13 downloads 0.13.0-rc1 (latest)")
getopt.StringVarLong(&params.ShowLatestPre, "show-latest-pre", 'P', "Show latest pre-release implicit version. Ex: tfswitch --show-latest-pre 0.13 prints 0.13.0-rc1 (latest)")
getopt.StringVarLong(&params.LatestStable, "latest-stable", 's', "Latest implicit version based on a constraint. Ex: tfswitch --latest-stable 0.13.0 downloads 0.13.7 and 0.13 downloads 0.15.5 (latest)")
getopt.StringVarLong(&params.ShowLatestStable, "show-latest-stable", 'S', "Show latest implicit version. Ex: tfswitch --show-latest-stable 0.13 prints 0.13.7 (latest)")
getopt.StringVarLong(&params.DefaultVersion, "default", 'd', "Default to this version in case no other versions could be detected. Ex: tfswitch --default 1.2.4")
getopt.BoolVarLong(&params.ListAllFlag, "list-all", 'l', "List all versions of terraform - including beta and rc")
getopt.BoolVarLong(&params.LatestFlag, "latest", 'u', "Get latest stable version")
getopt.BoolVarLong(&params.ShowLatestFlag, "show-latest", 'U', "Show latest stable version")

// Parse the command line parameters to fetch stuff like chdir
getopt.Parse()

// Read configuration files in increasing precedence (least precedence first)
params = GetParamsFromEnvironment(params)
params = GetVersionFromTerragrunt(params)
params = GetVersionFromVersionsTF(params)
params = GetParamsFromTerraformVersion(params)
params = GetParamsFromTfSwitch(params)
params = GetParamsTOML(params)

// Parse again to overwrite anything that might by defined on the cli AND in any config file (CLI always wins)
getopt.Parse()
args := getopt.Args()
if len(args) == 1 {
/* version provided on command line as arg */
params.Version = args[0]
}
return params
}

func getCommandlineParams(params Params) {
getopt.StringVarLong(&params.CustomBinaryPath, "bin", 'b', "Custom binary path. Ex: tfswitch -b "+lib.ConvertExecutableExt("/Users/username/bin/terraform"))
getopt.StringVarLong(&params.LatestPre, "latest-pre", 'p', "Latest pre-release implicit version. Ex: tfswitch --latest-pre 0.13 downloads 0.13.0-rc1 (latest)")
getopt.StringVarLong(&params.ShowLatestPre, "show-latest-pre", 'P', "Show latest pre-release implicit version. Ex: tfswitch --show-latest-pre 0.13 prints 0.13.0-rc1 (latest)")
getopt.StringVarLong(&params.LatestStable, "latest-stable", 's', "Latest implicit version based on a constraint. Ex: tfswitch --latest-stable 0.13.0 downloads 0.13.7 and 0.13 downloads 0.15.5 (latest)")
getopt.StringVarLong(&params.ShowLatestStable, "show-latest-stable", 'S', "Show latest implicit version. Ex: tfswitch --show-latest-stable 0.13 prints 0.13.7 (latest)")
getopt.StringVarLong(&params.DefaultVersion, "default", 'd', "Default to this version in case no other versions could be detected. Ex: tfswitch --default 1.2.4")

getopt.BoolVarLong(&params.ListAllFlag, "list-all", 'l', "List all versions of terraform - including beta and rc")
getopt.BoolVarLong(&params.LatestFlag, "latest", 'u', "Get latest stable version")
getopt.BoolVarLong(&params.ShowLatestFlag, "show-latest", 'U', "Show latest stable version")
}

func initParams(params Params) Params {
params.ChDirPath = lib.GetCurrentDirectory()
params.CustomBinaryPath = lib.ConvertExecutableExt(lib.GetDefaultBin())
params.MirrorURL = defaultMirror
params.LatestPre = defaultLatest
params.ShowLatestPre = defaultLatest
params.LatestStable = defaultLatest
params.ShowLatestStable = defaultLatest
params.MirrorURL = defaultMirror
params.DefaultVersion = defaultLatest
params.ListAllFlag = false
params.LatestFlag = false
params.ShowLatestFlag = false
params.VersionFlag = false
params.HelpFlag = false
return params
}

func UsageMessage() {
fmt.Print("\n\n")
getopt.PrintUsage(os.Stderr)
fmt.Println("Supply the terraform version as an argument, or choose from a menu")
}
78 changes: 78 additions & 0 deletions lib/param_parsing/parameters_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package param_parsing

import (
"github.com/pborman/getopt"
"os"
"testing"
)

func TestGetParameters_version_from_args(t *testing.T) {
expected := "0.13args"
os.Args = []string{"cmd", expected}
params := GetParameters()
actual := params.Version
if actual != expected {
t.Error("Version Param was not parsed correctly. Actual: " + actual + ", Expected: " + expected)
}
t.Cleanup(func() {
getopt.CommandLine = getopt.New()
})
}
func TestGetParameters_params_are_overridden_by_toml_file(t *testing.T) {
t.Cleanup(func() {
getopt.CommandLine = getopt.New()
})
expected := "../../test-data/test_tfswitchtoml"
os.Args = []string{"cmd", "--chdir=" + expected}
params := GetParameters()
actual := params.ChDirPath
if actual != expected {
t.Error("ChDir Param was not parsed correctly. Actual: " + actual + ", Expected: " + expected)
}

expected = "/usr/local/bin/terraform_from_toml"
actual = params.CustomBinaryPath
if actual != expected {
t.Error("CustomBinaryPath Param was not as expected. Actual: " + actual + ", Expected: " + expected)
}
expected = "0.11.3_toml"
actual = params.Version
if actual != expected {
t.Error("Version Param was not as expected. Actual: " + actual + ", Expected: " + expected)
}
}
func TestGetParameters_toml_params_are_overridden_by_cli(t *testing.T) {
t.Cleanup(func() {
getopt.CommandLine = getopt.New()
})
expected := "../../test-data/test_tfswitchtoml"
os.Args = []string{"cmd", "--chdir=" + expected, "--bin=/usr/test/bin"}
params := GetParameters()
actual := params.ChDirPath
if actual != expected {
t.Error("ChDir Param was not parsed correctly. Actual: " + actual + ", Expected: " + expected)
}

expected = "/usr/test/bin"
actual = params.CustomBinaryPath
if actual != expected {
t.Error("CustomBinaryPath Param was not as expected. Actual: " + actual + ", Expected: " + expected)
}
expected = "0.11.3_toml"
actual = params.Version
if actual != expected {
t.Error("Version Param was not as expected. Actual: " + actual + ", Expected: " + expected)
}
}

func TestGetParameters_check_config_precedence(t *testing.T) {
t.Cleanup(func() {
getopt.CommandLine = getopt.New()
})
os.Args = []string{"cmd", "--chdir=../../test-data/test_precedence"}
parameters := GetParameters()
expected := "0.11.3"
if parameters.Version != expected {
t.Error("Version Param was not as expected. Actual: " + parameters.Version + ", Expected: " + expected)
}
}
22 changes: 22 additions & 0 deletions lib/param_parsing/terraform_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package param_parsing

import (
"fmt"
"github.com/warrensbox/terraform-switcher/lib"
"log"
"os"
"strings"
)

func GetParamsFromTerraformVersion(params Params) Params {
filePath := params.ChDirPath + "/.terraform-version"
if lib.CheckFileExist(filePath) {
fmt.Printf("Reading configuration from %s\n", filePath)
content, err := os.ReadFile(filePath)
if err != nil {
log.Fatal("Could not read file content", filePath, err)
}
params.Version = strings.TrimSpace(string(content))
}
return params
}
17 changes: 17 additions & 0 deletions lib/param_parsing/terraform_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package param_parsing

import (
"fmt"
"testing"
)

func TestGetParamsFromTerraformVersion(t *testing.T) {
var params Params
params.ChDirPath = "../../test-data/test_terraform-version"
params = GetParamsFromTerraformVersion(params)
expected := "0.11.0_tfversion"
if params.Version != expected {
fmt.Printf("Version from .terraform-version not read correctly. Got: %v, Expect: %v", params.Version, expected)
t.Errorf("Version from .terraform-version not read correctly. Got: %v, Expect: %v", params.Version, expected)
}
}
36 changes: 36 additions & 0 deletions lib/param_parsing/terragrunt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package param_parsing

import (
"fmt"
"github.com/hashicorp/hcl2/gohcl"
"github.com/hashicorp/hcl2/hclparse"
"github.com/warrensbox/terraform-switcher/lib"
"log"
)

type terragruntVersionConstraints struct {
TerraformVersionConstraint string `hcl:"terraform_version_constraint"`
}

func GetVersionFromTerragrunt(params Params) Params {
filePath := params.ChDirPath + "/terragrunt.hcl"
if lib.CheckFileExist(filePath) {
fmt.Printf("Reading configuration from %s\n", filePath)
parser := hclparse.NewParser()
hclFile, diagnostics := parser.ParseHCLFile(filePath)
if diagnostics.HasErrors() {
log.Fatal("Unable to parse HCL file", filePath)
}
var versionFromTerragrunt terragruntVersionConstraints
diagnostics = gohcl.DecodeBody(hclFile.Body, nil, &versionFromTerragrunt)
if diagnostics.HasErrors() {
log.Fatal("Could not decode body of HCL file.")
}
version, err := lib.GetSemver(versionFromTerragrunt.TerraformVersionConstraint, params.MirrorURL)
if err != nil {
log.Fatal("Could not determine semantic version")
}
params.Version = version
}
return params
}
19 changes: 19 additions & 0 deletions lib/param_parsing/terragrunt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package param_parsing

import (
"github.com/hashicorp/go-version"
"testing"
)

func TestGetVersionFromTerragrunt(t *testing.T) {
var params Params
params = initParams(params)
params.ChDirPath = "../../test-data/test_terragrunt_hcl"
params = GetVersionFromTerragrunt(params)
v1, _ := version.NewVersion("0.13")
v2, _ := version.NewVersion("0.14")
actualVersion, _ := version.NewVersion(params.Version)
if !actualVersion.GreaterThanOrEqual(v1) || !actualVersion.LessThan(v2) {
t.Error("Determined version is not between 0.13 and 0.14")
}
}
22 changes: 22 additions & 0 deletions lib/param_parsing/tfswitch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package param_parsing

import (
"fmt"
"github.com/warrensbox/terraform-switcher/lib"
"log"
"os"
"strings"
)

func GetParamsFromTfSwitch(params Params) Params {
filePath := params.ChDirPath + "/.tfswitchrc"
if lib.CheckFileExist(filePath) {
fmt.Printf("Reading configuration from %s\n", filePath)
content, err := os.ReadFile(filePath)
if err != nil {
log.Fatal("Could not read file content", filePath, err)
}
params.Version = strings.TrimSpace(string(content))
}
return params
}
15 changes: 15 additions & 0 deletions lib/param_parsing/tfswitch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package param_parsing

import (
"testing"
)

func TestGetParamsFromTfSwitch(t *testing.T) {
var params Params
params.ChDirPath = "../../test-data/test_tfswitchrc"
params = GetParamsFromTfSwitch(params)
expected := "0.10.5_tfswitch"
if params.Version != expected {
t.Error("Version from tfswitchrc not read correctly. Actual: " + params.Version + ", Expected: " + expected)
}
}
Loading

0 comments on commit 5837518

Please sign in to comment.