From 235ea4e4aacd5cac723907f7a94fbe337bf1479c Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Wed, 22 Jan 2025 22:55:59 +0200 Subject: [PATCH] feat: Allow to download binary with custom CPU arch (#532) * feat: Allow to download binary with custom CPU arch Fixes #318 Caveats: - Doesn't override if binary had already been downloaded before - Remove existing binary from `tfswitch` download dir manually (see `--install` option) - Spits out Warn log message if requested arch doesn't match actual - Doesn't check arch value validity and just passes it to download function directly, which will fail if download file doesn't exist on remote --- lib/install.go | 65 ++++++++++--------- lib/param_parsing/environment.go | 4 ++ lib/param_parsing/environment_test.go | 13 ++++ lib/param_parsing/parameters.go | 3 + lib/param_parsing/parameters_test.go | 31 ++++++++- lib/param_parsing/toml.go | 4 ++ main.go | 23 +++---- .../test_tfswitchtoml/.tfswitch.toml | 5 +- www/docs/usage/commandline.md | 37 +++++++++-- 9 files changed, 134 insertions(+), 51 deletions(-) diff --git a/lib/install.go b/lib/install.go index 29911f75..38167d5f 100644 --- a/lib/install.go +++ b/lib/install.go @@ -13,9 +13,7 @@ import ( "github.com/hashicorp/go-version" ) -var ( - installLocation = "/tmp" -) +var installLocation = "/tmp" // initialize : removes existing symlink to terraform binary based on provided binPath func initialize(binPath string) { @@ -50,10 +48,10 @@ func GetInstallLocation(installPath string) string { } // install : install the provided version in the argument -func install(product Product, tfversion string, binPath string, installPath string, mirrorURL string) error { +func install(product Product, tfversion, binPath, installPath, mirrorURL, goarch string) error { var wg sync.WaitGroup - //check to see if the requested version has been downloaded before + // check to see if the requested version has been downloaded before installLocation := GetInstallLocation(installPath) installFileVersionPath := ConvertExecutableExt(filepath.Join(installLocation, product.GetVersionPrefix()+tfversion)) recentDownloadFile := CheckFileExist(installFileVersionPath) @@ -70,7 +68,10 @@ func install(product Product, tfversion string, binPath string, installPath stri return fmt.Errorf("the provided %s version does not exist: %q.\n Try `tfswitch -l` to see all available versions", product.GetId(), tfversion) } - goarch := runtime.GOARCH + if goarch != runtime.GOARCH { + logger.Warnf("Installing for %q architecture on %q!", goarch, runtime.GOARCH) + } + goos := runtime.GOOS // Terraform darwin arm64 comes with 1.0.2 and next version @@ -114,7 +115,9 @@ func switchToVersion(product Product, tfversion string, binPath string, installP } logger.Infof("Switched %s to version %q", product.GetName(), tfversion) - addRecent(tfversion, installPath, product) //add to recent file for faster lookup + + // add to recent file for faster lookup + addRecent(tfversion, installPath, product) return nil } @@ -135,24 +138,24 @@ func ConvertExecutableExt(fpath string) string { // If not, create $HOME/bin. Ask users to add $HOME/bin to $PATH and return $HOME/bin as install location // Deprecated: This function has been deprecated and will be removed in v2.0.0 func installableBinLocation(product Product, userBinPath string) string { - homedir := GetHomeDirectory() //get user's home directory - binDir := Path(userBinPath) //get path directory from binary path - binPathExist := CheckDirExist(binDir) //the default is /usr/local/bin but users can provide custom bin locations + homedir := GetHomeDirectory() // get user's home directory + binDir := Path(userBinPath) // get path directory from binary path + binPathExist := CheckDirExist(binDir) // the default is /usr/local/bin but users can provide custom bin locations - if binPathExist { //if bin path exist - check if we can write to it + if binPathExist { // if bin path exist - check if we can write to it - binPathWritable := false //assume bin path is not writable + binPathWritable := false // assume bin path is not writable if runtime.GOOS != "windows" { - binPathWritable = CheckDirWritable(binDir) //check if is writable on ( only works on LINUX) + binPathWritable = CheckDirWritable(binDir) // check if is writable on ( only works on LINUX) } // IF: "/usr/local/bin" or `custom bin path` provided by user is non-writable, (binPathWritable == false), we will attempt to install terraform at the ~/bin location. See ELSE if !binPathWritable { homeBinDir := filepath.Join(homedir, "bin") - if !CheckDirExist(homeBinDir) { //if ~/bin exist, install at ~/bin/terraform + if !CheckDirExist(homeBinDir) { // if ~/bin exist, install at ~/bin/terraform logger.Noticef("Unable to write to %q", userBinPath) logger.Infof("Creating bin directory at %q", homeBinDir) - createDirIfNotExist(homeBinDir) //create ~/bin + createDirIfNotExist(homeBinDir) // create ~/bin logger.Warnf("Run `export PATH=\"$PATH:%s\"` to append bin to $PATH", homeBinDir) } logger.Infof("Installing %s at %q", product.GetName(), homeBinDir) @@ -171,16 +174,16 @@ func installableBinLocation(product Product, userBinPath string) string { // InstallLatestVersion install latest stable tf version // // Deprecated: This function has been deprecated in favor of InstallLatestProductVersion and will be removed in v2.0.0 -func InstallLatestVersion(dryRun bool, customBinaryPath, installPath string, mirrorURL string) { +func InstallLatestVersion(dryRun bool, customBinaryPath, installPath, mirrorURL, arch string) { product := getLegacyProduct() - InstallLatestProductVersion(product, dryRun, customBinaryPath, installPath, mirrorURL) + InstallLatestProductVersion(product, dryRun, customBinaryPath, installPath, mirrorURL, arch) } // InstallLatestProductVersion install latest stable tf version -func InstallLatestProductVersion(product Product, dryRun bool, customBinaryPath, installPath string, mirrorURL string) error { +func InstallLatestProductVersion(product Product, dryRun bool, customBinaryPath, installPath, mirrorURL, arch string) error { tfversion, _ := getTFLatest(mirrorURL) if !dryRun { - return install(product, tfversion, customBinaryPath, installPath, mirrorURL) + return install(product, tfversion, customBinaryPath, installPath, mirrorURL, arch) } return nil } @@ -188,13 +191,13 @@ func InstallLatestProductVersion(product Product, dryRun bool, customBinaryPath, // InstallLatestImplicitVersion install latest - argument (version) must be provided // // Deprecated: This function has been deprecated in favor of InstallLatestProductImplicitVersion and will be removed in v2.0.0 -func InstallLatestImplicitVersion(dryRun bool, requestedVersion, customBinaryPath, installPath string, mirrorURL string, preRelease bool) { +func InstallLatestImplicitVersion(dryRun bool, requestedVersion, customBinaryPath, installPath, mirrorURL, arch string, preRelease bool) { product := getLegacyProduct() - InstallLatestProductImplicitVersion(product, dryRun, requestedVersion, customBinaryPath, installPath, mirrorURL, preRelease) + InstallLatestProductImplicitVersion(product, dryRun, requestedVersion, customBinaryPath, installPath, mirrorURL, arch, preRelease) } // InstallLatestProductImplicitVersion install latest - argument (version) must be provided -func InstallLatestProductImplicitVersion(product Product, dryRun bool, requestedVersion, customBinaryPath, installPath string, mirrorURL string, preRelease bool) error { +func InstallLatestProductImplicitVersion(product Product, dryRun bool, requestedVersion, customBinaryPath, installPath, mirrorURL, arch string, preRelease bool) error { _, err := version.NewConstraint(requestedVersion) if err != nil { // @TODO Should this return an error? @@ -202,7 +205,7 @@ func InstallLatestProductImplicitVersion(product Product, dryRun bool, requested } tfversion, err := getTFLatestImplicit(mirrorURL, preRelease, requestedVersion) if err == nil && tfversion != "" && !dryRun { - install(product, tfversion, customBinaryPath, installPath, mirrorURL) + install(product, tfversion, customBinaryPath, installPath, mirrorURL, arch) return nil } PrintInvalidMinorTFVersion() @@ -212,18 +215,18 @@ func InstallLatestProductImplicitVersion(product Product, dryRun bool, requested // InstallVersion install Terraform product // // Deprecated: This function has been deprecated in favor of InstallProductVersion and will be removed in v2.0.0 -func InstallVersion(dryRun bool, version, customBinaryPath, installPath, mirrorURL string) { +func InstallVersion(dryRun bool, version, customBinaryPath, installPath, mirrorURL, arch string) { product := getLegacyProduct() - InstallProductVersion(product, dryRun, version, customBinaryPath, installPath, mirrorURL) + InstallProductVersion(product, dryRun, version, customBinaryPath, installPath, mirrorURL, arch) } // InstallProductVersion install with provided version as argument -func InstallProductVersion(product Product, dryRun bool, version, customBinaryPath, installPath, mirrorURL string) error { +func InstallProductVersion(product Product, dryRun bool, version, customBinaryPath, installPath, mirrorURL, arch string) error { logger.Debugf("Install version %s. Dry run: %s", version, strconv.FormatBool(dryRun)) if !dryRun { if validVersionFormat(version) { requestedVersion := version - return install(product, requestedVersion, customBinaryPath, installPath, mirrorURL) + return install(product, requestedVersion, customBinaryPath, installPath, mirrorURL, arch) } else { PrintInvalidTFVersion() UsageMessage() @@ -238,9 +241,9 @@ func InstallProductVersion(product Product, dryRun bool, version, customBinaryPa // listAll = false - only official stable release are displayed */ // // Deprecated: This function has been deprecated in favor of InstallProductOption and will be removed in v2.0.0 -func InstallOption(listAll, dryRun bool, customBinaryPath, installPath string, mirrorURL string) { +func InstallOption(listAll, dryRun bool, customBinaryPath, installPath, mirrorURL, arch string) { product := getLegacyProduct() - InstallProductOption(product, listAll, dryRun, customBinaryPath, installPath, mirrorURL) + InstallProductOption(product, listAll, dryRun, customBinaryPath, installPath, mirrorURL, arch) } type VersionSelector struct { @@ -251,7 +254,7 @@ type VersionSelector struct { // InstallProductOption displays & installs tf version /* listAll = true - all versions including beta and rc will be displayed */ /* listAll = false - only official stable release are displayed */ -func InstallProductOption(product Product, listAll, dryRun bool, customBinaryPath, installPath string, mirrorURL string) error { +func InstallProductOption(product Product, listAll, dryRun bool, customBinaryPath, installPath, mirrorURL, arch string) error { var selectVersions []VersionSelector var versionMap map[string]bool = make(map[string]bool) @@ -306,7 +309,7 @@ func InstallProductOption(product Product, listAll, dryRun bool, customBinaryPat } } if !dryRun { - return install(product, selectVersions[selectedItx].Version, customBinaryPath, installPath, mirrorURL) + return install(product, selectVersions[selectedItx].Version, customBinaryPath, installPath, mirrorURL, arch) } return nil } diff --git a/lib/param_parsing/environment.go b/lib/param_parsing/environment.go index 1d94e7e7..872fd531 100644 --- a/lib/param_parsing/environment.go +++ b/lib/param_parsing/environment.go @@ -3,6 +3,10 @@ package param_parsing import "os" func GetParamsFromEnvironment(params Params) Params { + if envArch := os.Getenv("TF_ARCH"); envArch != "" { + params.Arch = envArch + logger.Debugf("Using architecture from environment variable \"TF_ARCH\": %q", envArch) + } if envVersion := os.Getenv("TF_VERSION"); envVersion != "" { params.Version = envVersion logger.Debugf("Using version from environment variable \"TF_VERSION\": %q", envVersion) diff --git a/lib/param_parsing/environment_test.go b/lib/param_parsing/environment_test.go index f4574100..81b16a84 100644 --- a/lib/param_parsing/environment_test.go +++ b/lib/param_parsing/environment_test.go @@ -7,6 +7,19 @@ import ( "github.com/warrensbox/terraform-switcher/lib" ) +func TestGetParamsFromEnvironment_arch_from_env(t *testing.T) { + logger = lib.InitLogger("DEBUG") + var params Params + expected := "amd64_from_env" + _ = os.Setenv("TF_ARCH", expected) + params = initParams(params) + params = GetParamsFromEnvironment(params) + _ = os.Unsetenv("TF_ARCH") + if params.Arch != expected { + t.Error("Determined arch is not matching. Got " + params.Arch + ", expected " + expected) + } +} + func TestGetParamsFromEnvironment_version_from_env(t *testing.T) { logger = lib.InitLogger("DEBUG") var params Params diff --git a/lib/param_parsing/parameters.go b/lib/param_parsing/parameters.go index ba7e7f0e..94f1289b 100644 --- a/lib/param_parsing/parameters.go +++ b/lib/param_parsing/parameters.go @@ -12,6 +12,7 @@ import ( ) type Params struct { + Arch string ChDirPath string CustomBinaryPath string DefaultVersion string @@ -51,6 +52,7 @@ func populateParams(params Params) Params { defaultMirrors = append(defaultMirrors, fmt.Sprintf("%s: %s", product.GetName(), product.GetDefaultMirrorUrl())) } + getopt.StringVarLong(¶ms.Arch, "arch", 'A', fmt.Sprintf("Override CPU architecture type for downloaded binary. Ex: `tfswitch --arch amd64` will attempt to download the amd64 version of the binary. Default: %s", runtime.GOARCH)) getopt.StringVarLong(¶ms.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.StringVarLong(¶ms.CustomBinaryPath, "bin", 'b', "Custom binary path. Ex: tfswitch -b "+lib.ConvertExecutableExt("/Users/username/bin/terraform")) getopt.StringVarLong(¶ms.DefaultVersion, "default", 'd', "Default to this version in case no other versions could be detected. Ex: tfswitch --default 1.2.4") @@ -160,6 +162,7 @@ func populateParams(params Params) Params { } func initParams(params Params) Params { + params.Arch = runtime.GOARCH params.ChDirPath = lib.GetCurrentDirectory() params.CustomBinaryPath = "" params.DefaultVersion = lib.DefaultLatest diff --git a/lib/param_parsing/parameters_test.go b/lib/param_parsing/parameters_test.go index 69e13236..b4a3c6d1 100644 --- a/lib/param_parsing/parameters_test.go +++ b/lib/param_parsing/parameters_test.go @@ -10,6 +10,19 @@ import ( "github.com/warrensbox/terraform-switcher/lib" ) +func TestGetParameters_arch_from_args(t *testing.T) { + expected := "arch_from_args" + os.Args = []string{"cmd", "--arch=" + expected} + params := GetParameters() + actual := params.Arch + if actual != expected { + t.Error("Arch Param was not parsed correctly. Actual: " + actual + ", Expected: " + expected) + } + t.Cleanup(func() { + getopt.CommandLine = getopt.New() + }) +} + func TestGetParameters_version_from_args(t *testing.T) { expected := "0.13args" os.Args = []string{"cmd", expected} @@ -45,6 +58,12 @@ func TestGetParameters_params_are_overridden_by_toml_file(t *testing.T) { t.Error("CustomBinaryPath Param was not as expected. Actual: " + actual + ", Expected: " + expected) } + expected = "amd64" + actual = params.Arch + if actual != expected { + t.Error("Arch Param was not as expected. Actual: " + actual + ", Expected: " + expected) + } + expected = "1.6.2" actual = params.Version if actual != expected { @@ -64,7 +83,7 @@ func TestGetParameters_params_are_overridden_by_toml_file(t *testing.T) { func TestGetParameters_toml_params_are_overridden_by_cli(t *testing.T) { logger = lib.InitLogger("DEBUG") expected := "../../test-data/integration-tests/test_tfswitchtoml" - os.Args = []string{"cmd", "--chdir=" + expected, "--bin=/usr/test/bin", "--product=terraform", "1.6.0"} + os.Args = []string{"cmd", "--chdir=" + expected, "--bin=/usr/test/bin", "--product=terraform", "--arch=arch_from_args", "1.6.0"} params := Params{} params = initParams(params) params.TomlDir = expected @@ -93,6 +112,12 @@ func TestGetParameters_toml_params_are_overridden_by_cli(t *testing.T) { t.Error("Product Param was not as expected. Actual: " + actual + ", Expected: " + expected) } + expected = "arch_from_args" + actual = params.Arch + if actual != expected { + t.Error("Arch Param was not as expected. Actual: " + actual + ", Expected: " + expected) + } + t.Cleanup(func() { getopt.CommandLine = getopt.New() }) @@ -137,7 +162,7 @@ func TestGetParameters_dry_run_wont_download_anything(t *testing.T) { installFileVersionPath := lib.ConvertExecutableExt(filepath.Join(installLocation, product.GetVersionPrefix()+params.Version)) // Make sure the file tfswitch WOULD download is absent _ = os.Remove(installFileVersionPath) - lib.InstallProductVersion(product, params.DryRun, params.Version, params.CustomBinaryPath, params.InstallPath, params.MirrorURL) + lib.InstallProductVersion(product, params.DryRun, params.Version, params.CustomBinaryPath, params.InstallPath, params.MirrorURL, params.Arch) if lib.FileExistsAndIsNotDir(installFileVersionPath) { t.Error("Dry run should NOT download any files.") } @@ -148,7 +173,7 @@ func TestGetParameters_dry_run_wont_download_anything(t *testing.T) { func writeTestFile(t *testing.T, basePath string, fileName string, fileContent string) { fullPath := filepath.Join(basePath, fileName) - if err := os.WriteFile(fullPath, []byte(fileContent), 0600); err != nil { + if err := os.WriteFile(fullPath, []byte(fileContent), 0o600); err != nil { t.Fatal(err) } t.Cleanup(func() { diff --git a/lib/param_parsing/toml.go b/lib/param_parsing/toml.go index 2ac6ba8c..9019f78e 100644 --- a/lib/param_parsing/toml.go +++ b/lib/param_parsing/toml.go @@ -27,6 +27,10 @@ func getParamsTOML(params Params) (Params, error) { return params, errs } + if viperParser.Get("arch") != nil { + params.Arch = os.ExpandEnv(viperParser.GetString("arch")) + logger.Debugf("Using \"arch\" from %q: %q", tomlPath, params.Arch) + } if viperParser.Get("bin") != nil { params.CustomBinaryPath = os.ExpandEnv(viperParser.GetString("bin")) logger.Debugf("Using \"bin\" from %q: %q", tomlPath, params.CustomBinaryPath) diff --git a/main.go b/main.go index 1ce6f1e5..0bb3f4a3 100644 --- a/main.go +++ b/main.go @@ -25,12 +25,13 @@ import ( "github.com/warrensbox/terraform-switcher/lib/param_parsing" ) -var parameters = param_parsing.GetParameters() -var logger = lib.InitLogger(parameters.LogLevel) -var version string +var ( + parameters = param_parsing.GetParameters() + logger = lib.InitLogger(parameters.LogLevel) + version string +) func main() { - var err error = nil switch { case parameters.VersionFlag: @@ -45,33 +46,33 @@ func main() { os.Exit(0) case parameters.ListAllFlag: /* show all terraform version including betas and RCs*/ - err = lib.InstallProductOption(parameters.ProductEntity, true, parameters.DryRun, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL) + err = lib.InstallProductOption(parameters.ProductEntity, true, parameters.DryRun, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL, parameters.Arch) case parameters.LatestPre != "": /* latest pre-release implicit version. Ex: tfswitch --latest-pre 0.13 downloads 0.13.0-rc1 (latest) */ - err = lib.InstallLatestProductImplicitVersion(parameters.ProductEntity, parameters.DryRun, parameters.LatestPre, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL, true) + err = lib.InstallLatestProductImplicitVersion(parameters.ProductEntity, parameters.DryRun, parameters.LatestPre, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL, parameters.Arch, true) case parameters.ShowLatestPre != "": /* show latest pre-release implicit version. Ex: tfswitch --latest-pre 0.13 downloads 0.13.0-rc1 (latest) */ lib.ShowLatestImplicitVersion(parameters.ShowLatestPre, parameters.MirrorURL, true) case parameters.LatestStable != "": /* latest implicit version. Ex: tfswitch --latest-stable 0.13 downloads 0.13.5 (latest) */ - err = lib.InstallLatestProductImplicitVersion(parameters.ProductEntity, parameters.DryRun, parameters.LatestStable, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL, false) + err = lib.InstallLatestProductImplicitVersion(parameters.ProductEntity, parameters.DryRun, parameters.LatestStable, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL, parameters.Arch, false) case parameters.ShowLatestStable != "": /* show latest implicit stable version. Ex: tfswitch --show-latest-stable 0.13 downloads 0.13.5 (latest) */ lib.ShowLatestImplicitVersion(parameters.ShowLatestStable, parameters.MirrorURL, false) case parameters.LatestFlag: /* latest stable version */ - err = lib.InstallLatestProductVersion(parameters.ProductEntity, parameters.DryRun, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL) + err = lib.InstallLatestProductVersion(parameters.ProductEntity, parameters.DryRun, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL, parameters.Arch) case parameters.ShowLatestFlag: /* show latest stable version */ lib.ShowLatestVersion(parameters.MirrorURL) case parameters.Version != "": - err = lib.InstallProductVersion(parameters.ProductEntity, parameters.DryRun, parameters.Version, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL) + err = lib.InstallProductVersion(parameters.ProductEntity, parameters.DryRun, parameters.Version, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL, parameters.Arch) case parameters.DefaultVersion != "": /* if default version is provided - Pick this instead of going for prompt */ - err = lib.InstallProductVersion(parameters.ProductEntity, parameters.DryRun, parameters.DefaultVersion, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL) + err = lib.InstallProductVersion(parameters.ProductEntity, parameters.DryRun, parameters.DefaultVersion, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL, parameters.Arch) default: // Set list all false - only official release will be displayed - err = lib.InstallProductOption(parameters.ProductEntity, false, parameters.DryRun, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL) + err = lib.InstallProductOption(parameters.ProductEntity, false, parameters.DryRun, parameters.CustomBinaryPath, parameters.InstallPath, parameters.MirrorURL, parameters.Arch) } if err != nil { logger.Fatal(err) diff --git a/test-data/integration-tests/test_tfswitchtoml/.tfswitch.toml b/test-data/integration-tests/test_tfswitchtoml/.tfswitch.toml index 81f28c35..d50d6033 100644 --- a/test-data/integration-tests/test_tfswitchtoml/.tfswitch.toml +++ b/test-data/integration-tests/test_tfswitchtoml/.tfswitch.toml @@ -1,5 +1,6 @@ +arch = "amd64" bin = "/usr/local/bin/terraform_from_toml" -version = "1.6.2" +default-version = "1.5.4" log-level = "NOTICE" product = "opentofu" -default-version = "1.5.4" \ No newline at end of file +version = "1.6.2" diff --git a/www/docs/usage/commandline.md b/www/docs/usage/commandline.md index e47662d2..52c8fe24 100644 --- a/www/docs/usage/commandline.md +++ b/www/docs/usage/commandline.md @@ -33,7 +33,7 @@ For example: ```bash export TF_VERSION=0.14.4 -tfswitch #will automatically switch to terraform version 0.14.4 +tfswitch # Will automatically switch to terraform version 0.14.4 ``` ### `TF_DEFAULT_VERSION` @@ -44,7 +44,7 @@ For example: ```bash export TF_DEFAULT_VERSION=0.14.4 -tfswitch #will automatically switch to terraform version 0.14.4 +tfswitch # Will automatically switch to terraform version 0.14.4 ``` ### `TF_PRODUCT` @@ -60,7 +60,24 @@ For example: ```bash export TF_PRODUCT=opentofu -tfswitch # Will install opentofu +tfswitch # Will install opentofu instead of terraform +``` + +### `TF_ARCH` + +`TF_ARCH` environment variable can be set to override default CPU architecture for downloaded Terraform binary. + +- This can be set to any string, though incorrect values will result in download failure. +- Suggested values: `amd64`, `arm64`, `386`. +- Check available Arch types at: + - [Terraform Downloads](https://releases.hashicorp.com/terraform/) + - [OpenTofu Downloads](https://get.opentofu.org/tofu/) + +For example: + +```bash +export TF_ARCH=amd64 +tfswitch # Will install Terraform binary for amd64 architecture ``` ## Install latest version only @@ -119,4 +136,16 @@ The Terraform binaries will then be placed in the directory `.terraform.versions tfswitch -i /opt/terraform ``` -**NOTE** - The directory passed in `-i`/`--install` must be created before running `tfswitch` +**NOTE**: The directory passed in `-i`/`--install` must be created before running `tfswitch` + +## Install binary for non-default architecture + +By default `tfswitch` will download the binary for the architecture of the host machine. + +If you want to download the binary for non-default CPU architecture then you can provide the `-A` or `--arch` command line argument to download binaries for custom CPU architecture. Useful if you need to override binary architecture for whatever reason. + +```bash +tfswitch --arch amd64 +``` + +**NOTE**: If the target file already exists in the download directory (See [Install to non-default location](#install-to-non-default-location) section above), it will be not downloaded. Downloaded files are stored without the architecture in the filename. Format of the filenames in download directory: `_`. E.g. `terraform_1.10.4`.