Skip to content

Commit

Permalink
feat: Allow to download binary with custom CPU arch
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yermulnik committed Jan 14, 2025
1 parent 09d11fb commit 14c7017
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 42 deletions.
63 changes: 32 additions & 31 deletions lib/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -114,7 +115,7 @@ 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
addRecent(tfversion, installPath, product) // add to recent file for faster lookup
return nil
}

Expand All @@ -135,24 +136,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)
Expand All @@ -171,38 +172,38 @@ 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
}

// 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?
logger.Errorf("Error parsing constraint %q: %v", requestedVersion, err)
}
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()
Expand All @@ -212,18 +213,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()
Expand All @@ -238,9 +239,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 {
Expand All @@ -251,7 +252,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)
Expand Down Expand Up @@ -306,7 +307,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
}
3 changes: 3 additions & 0 deletions lib/param_parsing/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
)

type Params struct {
Arch string
ChDirPath string
CustomBinaryPath string
DefaultVersion string
Expand Down Expand Up @@ -51,6 +52,7 @@ func populateParams(params Params) Params {
defaultMirrors = append(defaultMirrors, fmt.Sprintf("%s: %s", product.GetName(), product.GetDefaultMirrorUrl()))
}

getopt.StringVarLong(&params.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(&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.StringVarLong(&params.CustomBinaryPath, "bin", 'b', "Custom binary path. Ex: tfswitch -b "+lib.ConvertExecutableExt("/Users/username/bin/terraform"))
getopt.StringVarLong(&params.DefaultVersion, "default", 'd', "Default to this version in case no other versions could be detected. Ex: tfswitch --default 1.2.4")
Expand Down Expand Up @@ -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
Expand Down
23 changes: 12 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand Down

0 comments on commit 14c7017

Please sign in to comment.