From 0daf90a44a778b64eac61e59ed88367a8f681050 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 06:29:58 +0100 Subject: [PATCH 01/11] Update Unzip to retain previous signature to avoid breaking external usage Make filesToUnzip a glob of remaining arguments and set default if the argument has not been provided Issue #315 --- lib/files.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/files.go b/lib/files.go index d5148016..d49d757d 100644 --- a/lib/files.go +++ b/lib/files.go @@ -45,9 +45,17 @@ func CheckFileExist(file string) bool { // Unzip will decompress a zip archive, moving all files and folders // within the zip file (parameter 1) to an output directory (parameter 2). -func Unzip(src string, dest string, fileToUnzip string) ([]string, error) { +// fileToUnzip (parameter 3) specifices the file within the zipfile to be extracted. +// This is optional and default to "terraform" +func Unzip(src string, dest string, fileToUnzipSlice ...string) ([]string, error) { logger.Debugf("Unzipping file %q", src) + // Handle old signature of method, where fileToUnzip did not exist + fileToUnzip := "terraform" + if len(fileToUnzipSlice) > 0 { + fileToUnzip = fileToUnzipSlice[0] + } + var filenames []string reader, err := zip.OpenReader(src) @@ -79,9 +87,9 @@ func Unzip(src string, dest string, fileToUnzip string) ([]string, error) { unzipWaitGroup.Wait() if len(filenames) < 1 { - logger.Fatalf("Could not find terraform file in release archive to unzip") + logger.Fatalf("Could not find %s file in release archive to unzip", fileToUnzip) } else if len(filenames) > 1 { - logger.Fatalf("Extracted more files than expected in release archive") + logger.Fatal("Extracted more files than expected in release archive") } return filenames, nil From 619b02de6dd6053b2e4130356a274b96006f897b Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 06:35:07 +0100 Subject: [PATCH 02/11] Improve comments for deprecated functions that are being replaced by "product" functions Issue #315 --- lib/install.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/install.go b/lib/install.go index 3c0b6400..6193e7ec 100644 --- a/lib/install.go +++ b/lib/install.go @@ -286,10 +286,13 @@ func installableBinLocation(product Product, userBinPath string) string { } // InstallLatestVersion install latest stable tf version +// This is a legacy method that is deprecated in favor of InstallLatestProductVersion func InstallLatestVersion(dryRun bool, customBinaryPath, installPath string, mirrorURL string) { product := getLegacyProduct() InstallLatestProductVersion(product, dryRun, customBinaryPath, installPath, mirrorURL) } + +// InstallLatestProductVersion install latest stable tf version func InstallLatestProductVersion(product Product, dryRun bool, customBinaryPath, installPath string, mirrorURL string) { tfversion, _ := getTFLatest(mirrorURL) if !dryRun { @@ -298,10 +301,13 @@ func InstallLatestProductVersion(product Product, dryRun bool, customBinaryPath, } // InstallLatestImplicitVersion install latest - argument (version) must be provided +// This is a legacy method that is deprecated in favor of InstallLatestProductImplicitVersion func InstallLatestImplicitVersion(dryRun bool, requestedVersion, customBinaryPath, installPath string, mirrorURL string, preRelease bool) { product := getLegacyProduct() InstallLatestProductImplicitVersion(product, dryRun, requestedVersion, customBinaryPath, installPath, mirrorURL, preRelease) } + +// InstallLatestProductImplicitVersion install latest - argument (version) must be provided func InstallLatestProductImplicitVersion(product Product, dryRun bool, requestedVersion, customBinaryPath, installPath string, mirrorURL string, preRelease bool) { _, err := version.NewConstraint(requestedVersion) if err != nil { @@ -315,13 +321,14 @@ func InstallLatestProductImplicitVersion(product Product, dryRun bool, requested PrintInvalidMinorTFVersion() } -// InstallVersion install product using legacy product +// InstallVersion install Terraform product +// This is a legacy method that is deprecated in favor of InstallProductVersion func InstallVersion(dryRun bool, version, customBinaryPath, installPath, mirrorURL string) { product := getLegacyProduct() InstallProductVersion(product, dryRun, version, customBinaryPath, installPath, mirrorURL) } -// InstallVersion install with provided version as argument +// InstallProductVersion install with provided version as argument func InstallProductVersion(product Product, dryRun bool, version, customBinaryPath, installPath, mirrorURL string) { logger.Debugf("Install version %s. Dry run: %s", version, strconv.FormatBool(dryRun)) if !dryRun { @@ -358,12 +365,16 @@ func InstallProductVersion(product Product, dryRun bool, version, customBinaryPa } } +// InstallProductOption displays & installs tf version +// This is a legacy method that will be deprecated in favor of InstallProductOption +/* listAll = true - all versions including beta and rc will be displayed */ +/* listAll = false - only official stable release are displayed */ func InstallOption(listAll, dryRun bool, customBinaryPath, installPath string, mirrorURL string) { product := getLegacyProduct() InstallProductOption(product, listAll, dryRun, customBinaryPath, installPath, mirrorURL) } -// InstallOption displays & installs tf version +// 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) { From c99af05ab5abeea9b062b1257194651cab5b9251 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 06:42:40 +0100 Subject: [PATCH 03/11] Improve debug when obtaining list of versions to avoid using Terraform name Issue #315 --- lib/list_versions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/list_versions.go b/lib/list_versions.go index 2aa17b08..03b1da91 100644 --- a/lib/list_versions.go +++ b/lib/list_versions.go @@ -38,7 +38,7 @@ func getVersionsFromBody(body string, preRelease bool, tfVersionList *tfVersionL // getTFList : Get the list of available terraform version given the hashicorp url func getTFList(mirrorURL string, preRelease bool) ([]string, error) { - logger.Debugf("Get list of terraform versions") + logger.Debug("Getting list of versions") result, err := getTFURLBody(mirrorURL) if err != nil { return nil, err From fb7b4c044bc542632fda870724281a22b4de9074 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 06:57:12 +0100 Subject: [PATCH 04/11] Provide default product in help for product command line argument Issue #315 --- lib/defaults.go | 1 + lib/param_parsing/parameters.go | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/defaults.go b/lib/defaults.go index 79681582..474906a0 100644 --- a/lib/defaults.go +++ b/lib/defaults.go @@ -24,4 +24,5 @@ const ( InstallDir = ".terraform.versions" recentFilePrefix = "RECENT" tfDarwinArm64StartVersion = "1.0.2" + DefaultProductId = "terraform" ) diff --git a/lib/param_parsing/parameters.go b/lib/param_parsing/parameters.go index be062035..b22b210a 100644 --- a/lib/param_parsing/parameters.go +++ b/lib/param_parsing/parameters.go @@ -54,12 +54,11 @@ func GetParameters() Params { getopt.StringVarLong(¶ms.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.BoolVarLong(¶ms.ListAllFlag, "list-all", 'l', "List all versions of terraform - including beta and rc") getopt.StringVarLong(¶ms.LogLevel, "log-level", 'g', "Set loglevel for tfswitch. One of (INFO, NOTICE, DEBUG, TRACE)") - getopt.StringVarLong(¶ms.MirrorURL, "mirror", 'm', "install from a remote API other than the default. Default (based on product):\n"+strings.Join(defaultMirrors, "\n")) getopt.BoolVarLong(¶ms.ShowLatestFlag, "show-latest", 'U', "Show latest stable version") getopt.StringVarLong(¶ms.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(¶ms.ShowLatestStable, "show-latest-stable", 'S', "Show latest implicit version. Ex: tfswitch --show-latest-stable 0.13 prints 0.13.7 (latest)") - getopt.StringVarLong(¶ms.Product, "product", 'q', fmt.Sprintf("Specifies which product to use. Ex: `tfswitch --product terraform` will install Terraform. Options: (%s)", strings.Join(productIds, ", "))) + getopt.StringVarLong(¶ms.Product, "product", 'q', fmt.Sprintf("Specifies which product to use. Ex: `tfswitch --product opentofu` will install Terraform. Options: (%s). Default: %s", strings.Join(productIds, ", "), lib.DefaultProductId)) getopt.BoolVarLong(¶ms.VersionFlag, "version", 'v', "Displays the version of tfswitch") // Parse the command line parameters to fetch stuff like chdir @@ -127,7 +126,7 @@ func initParams(params Params) Params { params.ShowLatestPre = lib.DefaultLatest params.ShowLatestStable = lib.DefaultLatest params.Version = lib.DefaultLatest - params.Product = "terraform" + params.Product = lib.DefaultProductId params.VersionFlag = false return params } From c129c5d17a468e150f18508fbd58439387b00e52 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 06:57:40 +0100 Subject: [PATCH 05/11] Use non-formatting logging function where formatting is not used --- lib/products.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/products.go b/lib/products.go index e832d4b0..ff0e68d6 100644 --- a/lib/products.go +++ b/lib/products.go @@ -145,7 +145,7 @@ func GetAllProducts() []Product { func getLegacyProduct() Product { product := GetProductById(legacyProductId) if product == nil { - logger.Fatalf("Default product could not be found") + logger.Fatal("Default product could not be found") } return product } From 77330917d15e0017db22abe9a4748fb51e599611 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 06:59:56 +0100 Subject: [PATCH 06/11] Improve error when invalid product is specified Issue #315 --- lib/param_parsing/parameters.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/param_parsing/parameters.go b/lib/param_parsing/parameters.go index b22b210a..18bff92b 100644 --- a/lib/param_parsing/parameters.go +++ b/lib/param_parsing/parameters.go @@ -88,7 +88,7 @@ func GetParameters() Params { // Set defaults based on product product := lib.GetProductById(params.Product) if product == nil { - logger.Fatalf("Invalid product: " + params.Product) + logger.Fatalf("Invalid \"product\" configuration value: %q", params.Product) } else { // Use else as there is a warning that params maybe nil, as it does not see Fatalf as a break condition params.MirrorURL = product.GetDefaultMirrorUrl() } From 256c26d10a02f7b4208c7a9ef28778309091457c Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 07:10:53 +0100 Subject: [PATCH 07/11] Simplify logic in installableBinLocation. Remove duplicate calls to filepath.Join Reduce duplication from code paths whether ~/.bin exists or not --- lib/install.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/lib/install.go b/lib/install.go index 6193e7ec..b63f19f1 100644 --- a/lib/install.go +++ b/lib/install.go @@ -247,9 +247,6 @@ func ConvertExecutableExt(fpath string) string { // installableBinLocation : Checks if terraform is installable in the location provided by the user. // If not, create $HOME/bin. Ask users to add $HOME/bin to $PATH and return $HOME/bin as install location func installableBinLocation(product Product, userBinPath string) string { - - // @TODO Remove duplicate code in if homeBinExist and rationalise return to single instance - 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 @@ -263,18 +260,16 @@ func installableBinLocation(product Product, userBinPath string) string { // 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 { - - homeBinExist := CheckDirExist(filepath.Join(homedir, "bin")) //check to see if ~/bin exist - if homeBinExist { //if ~/bin exist, install at ~/bin/terraform - logger.Infof("Installing terraform at %q", filepath.Join(homedir, "bin")) - return filepath.Join(homedir, "bin", product.GetExecutableName()) - } else { //if ~/bin directory does not exist, create ~/bin for terraform installation + homeBinDir := filepath.Join(homedir, "bin") + 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", filepath.Join(homedir, "bin")) - createDirIfNotExist(filepath.Join(homedir, "bin")) //create ~/bin - logger.Warnf("Run `export PATH=\"$PATH:%s\"` to append bin to $PATH", filepath.Join(homedir, "bin")) - return filepath.Join(homedir, "bin", product.GetExecutableName()) + logger.Infof("Creating bin directory at %q", homeBinDir) + createDirIfNotExist(homeBinDir) //create ~/bin + logger.Warnf("Run `export PATH=\"$PATH:%s\"` to append bin to $PATH", homeBinDir) } + logger.Infof("Installing terraform at %q", homeBinDir) + return filepath.Join(homeBinDir, product.GetExecutableName()) + } else { // ELSE: the "/usr/local/bin" or custom path provided by user is writable, we will return installable location return filepath.Join(userBinPath) } From 1177f319d39d0cad7a37ae4a24ac0194ca034c1c Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 17:03:30 +0100 Subject: [PATCH 08/11] Reference legacy product for default executable name in Unzip and error when passing too many args Issue #315 --- lib/files.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/files.go b/lib/files.go index d49d757d..9d0bd74c 100644 --- a/lib/files.go +++ b/lib/files.go @@ -51,9 +51,12 @@ func Unzip(src string, dest string, fileToUnzipSlice ...string) ([]string, error logger.Debugf("Unzipping file %q", src) // Handle old signature of method, where fileToUnzip did not exist - fileToUnzip := "terraform" - if len(fileToUnzipSlice) > 0 { + legacyProduct := getLegacyProduct() + fileToUnzip := legacyProduct.GetExecutableName() + if len(fileToUnzipSlice) == 1 { fileToUnzip = fileToUnzipSlice[0] + } else if len(fileToUnzipSlice) > 1 { + logger.Fatal("Too many args passed to Unzip") } var filenames []string From 8bc06a6131ec3e4a4ece10a0385f4cb02deb31ff Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 17:05:22 +0100 Subject: [PATCH 09/11] Add comment to getLegacyProduct about it's use --- lib/products.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/products.go b/lib/products.go index ff0e68d6..da389ba1 100644 --- a/lib/products.go +++ b/lib/products.go @@ -142,6 +142,9 @@ func GetAllProducts() []Product { return products } +// Obtain produced used by deprecated public methods that +// now expect a product to be called. +// Once these public methods are removed, this function can be removed func getLegacyProduct() Product { product := GetProductById(legacyProductId) if product == nil { From 34efd98507c0a5b94f17d03d83d77865465a16d1 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 17:06:08 +0100 Subject: [PATCH 10/11] Update comment for getTFList to exlcude information about Terraform --- lib/list_versions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/list_versions.go b/lib/list_versions.go index 03b1da91..c801daeb 100644 --- a/lib/list_versions.go +++ b/lib/list_versions.go @@ -36,7 +36,7 @@ func getVersionsFromBody(body string, preRelease bool, tfVersionList *tfVersionL } } -// getTFList : Get the list of available terraform version given the hashicorp url +// getTFList : Get the list of available versions given the mirror URL func getTFList(mirrorURL string, preRelease bool) ([]string, error) { logger.Debug("Getting list of versions") result, err := getTFURLBody(mirrorURL) From ec24d69c18767dd0d67bc84e69f82e336aa134d5 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 27 May 2024 17:09:27 +0100 Subject: [PATCH 11/11] Improve log message in installableBinLocation to use product name --- lib/install.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/install.go b/lib/install.go index b63f19f1..faa116bc 100644 --- a/lib/install.go +++ b/lib/install.go @@ -267,7 +267,7 @@ func installableBinLocation(product Product, userBinPath string) string { createDirIfNotExist(homeBinDir) //create ~/bin logger.Warnf("Run `export PATH=\"$PATH:%s\"` to append bin to $PATH", homeBinDir) } - logger.Infof("Installing terraform at %q", homeBinDir) + logger.Infof("Installing %s at %q", product.GetName(), homeBinDir) return filepath.Join(homeBinDir, product.GetExecutableName()) } else { // ELSE: the "/usr/local/bin" or custom path provided by user is writable, we will return installable location