Skip to content

Commit

Permalink
#154 implement dry-run
Browse files Browse the repository at this point in the history
  • Loading branch information
MatrixCrawler committed Apr 22, 2024
1 parent c18b5d7 commit 770ea4c
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 69 deletions.
25 changes: 15 additions & 10 deletions lib/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,40 @@ import (
"os"
"path/filepath"
"strings"
"sync"
)

// DownloadFromURL : Downloads the terraform binary and its hash from the source url
func DownloadFromURL(installLocation string, mirrorURL string, tfversion string, versionPrefix string, goos string, goarch string) (string, error) {
func DownloadFromURL(installLocation, mirrorURL, tfversion, versionPrefix, goos, goarch string) (string, error) {
var wg sync.WaitGroup
defer wg.Done()
pubKeyFilename := filepath.Join(installLocation, "/", PubKeyPrefix+PubKeyId+pubKeySuffix)
zipUrl := mirrorURL + tfversion + "/" + versionPrefix + tfversion + "_" + goos + "_" + goarch + ".zip"
hashUrl := mirrorURL + tfversion + "/" + versionPrefix + tfversion + "_SHA256SUMS"
hashSignatureUrl := mirrorURL + tfversion + "/" + versionPrefix + tfversion + "_SHA256SUMS." + PubKeyId + ".sig"

err := downloadPublicKey(installLocation, pubKeyFilename)
err := downloadPublicKey(installLocation, pubKeyFilename, &wg)
if err != nil {
logger.Error("Could not download public PGP key file.")
return "", err
}

logger.Infof("Downloading %q", zipUrl)
zipFilePath, err := downloadFromURL(installLocation, zipUrl)
zipFilePath, err := downloadFromURL(installLocation, zipUrl, &wg)
if err != nil {
logger.Error("Could not download zip file.")
return "", err
}

logger.Infof("Downloading %q", hashUrl)
hashFilePath, err := downloadFromURL(installLocation, hashUrl)
hashFilePath, err := downloadFromURL(installLocation, hashUrl, &wg)
if err != nil {
logger.Error("Could not download hash file.")
return "", err
}

logger.Infof("Downloading %q", hashSignatureUrl)
hashSigFilePath, err := downloadFromURL(installLocation, hashSignatureUrl)
hashSigFilePath, err := downloadFromURL(installLocation, hashSignatureUrl, &wg)
if err != nil {
logger.Error("Could not download hash signature file.")
return "", err
Expand Down Expand Up @@ -70,7 +73,7 @@ func DownloadFromURL(installLocation string, mirrorURL string, tfversion string,
var filesToCleanup []string
filesToCleanup = append(filesToCleanup, hashFilePath)
filesToCleanup = append(filesToCleanup, hashSigFilePath)
defer cleanup(filesToCleanup)
defer cleanup(filesToCleanup, &wg)

verified := checkSignatureOfChecksums(publicKeyFile, hashFile, signatureFile)
if !verified {
Expand All @@ -83,7 +86,8 @@ func DownloadFromURL(installLocation string, mirrorURL string, tfversion string,
return zipFilePath, err
}

func downloadFromURL(installLocation string, url string) (string, error) {
func downloadFromURL(installLocation string, url string, wg *sync.WaitGroup) (string, error) {
wg.Add(1)
tokens := strings.Split(url, "/")
fileName := tokens[len(tokens)-1]
logger.Infof("Downloading to %q", filepath.Join(installLocation, "/", fileName))
Expand Down Expand Up @@ -119,12 +123,12 @@ func downloadFromURL(installLocation string, url string) (string, error) {
return filePath, nil
}

func downloadPublicKey(installLocation string, targetFileName string) error {
func downloadPublicKey(installLocation string, targetFileName string, wg *sync.WaitGroup) error {
logger.Debugf("Looking up public key file at %q", targetFileName)
publicKeyFileExists := FileExistsAndIsNotDir(targetFileName)
if !publicKeyFileExists {
// Public key does not exist. Let's grab it from hashicorp
pubKeyFile, errDl := downloadFromURL(installLocation, PubKeyUri)
pubKeyFile, errDl := downloadFromURL(installLocation, PubKeyUri, wg)
if errDl != nil {
logger.Errorf("Error fetching public key file from %s", PubKeyUri)
return errDl
Expand All @@ -138,8 +142,9 @@ func downloadPublicKey(installLocation string, targetFileName string) error {
return nil
}

func cleanup(paths []string) {
func cleanup(paths []string, wg *sync.WaitGroup) {
for _, path := range paths {
wg.Add(1)
logger.Infof("Deleting %q", path)
err := os.Remove(path)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion lib/download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestDownloadFromURL_FileNameMatch(t *testing.T) {

urlToDownload := hashiURL + lowestVersion + "/" + installVersion + lowestVersion + macOS
expectedFile := filepath.Join(installLocation, installVersion+lowestVersion+macOS)
installedFile, errDownload := downloadFromURL(installLocation, urlToDownload)
installedFile, errDownload := downloadFromURL(installLocation, urlToDownload, nil)

if errDownload != nil {
t.Logf("Expected file name %v to be downloaded", expectedFile)
Expand Down
8 changes: 5 additions & 3 deletions lib/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,18 @@ func Unzip(src string, dest string) ([]string, error) {
if err != nil {
logger.Fatalf("Could not open destination: %v", err)
}
var wg sync.WaitGroup
var unzipWaitGroup sync.WaitGroup
for _, f := range reader.File {
wg.Add(1)
unzipErr := unzipFile(f, destination, &wg)
unzipWaitGroup.Add(1)
unzipErr := unzipFile(f, destination, &unzipWaitGroup)
if unzipErr != nil {
logger.Fatalf("Error unzipping %v", unzipErr)
} else {
filenames = append(filenames, filepath.Join(destination, f.Name))
}
}
logger.Debug("Waiting for deferred functions.")
unzipWaitGroup.Wait()
return filenames, nil
}

Expand Down
98 changes: 54 additions & 44 deletions lib/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"

"github.com/hashicorp/go-version"
"github.com/mitchellh/go-homedir"
Expand Down Expand Up @@ -37,9 +39,9 @@ func initialize(binPath string) {
}
}

// getInstallLocation : get location where the terraform binary will be installed,
// GetInstallLocation : get location where the terraform binary will be installed,
// will create a directory in the home location if it does not exist
func getInstallLocation() string {
func GetInstallLocation() string {
/* get current user */
homeDir, errCurr := homedir.Dir()
if errCurr != nil {
Expand All @@ -59,6 +61,7 @@ func getInstallLocation() string {

// install : install the provided version in the argument
func install(tfversion string, binPath string, mirrorURL string) {
var wg sync.WaitGroup
/* Check to see if user has permission to the default bin location which is "/usr/local/bin/terraform"
* If user does not have permission to default bin location, proceed to create $HOME/bin and install the tfswitch there
* Inform user that they don't have permission to default location, therefore tfswitch was installed in $HOME/bin
Expand All @@ -67,7 +70,7 @@ func install(tfversion string, binPath string, mirrorURL string) {
binPath = installableBinLocation(binPath)

initialize(binPath) //initialize path
installLocation = getInstallLocation() //get installation location - this is where we will put our terraform binary file
installLocation = GetInstallLocation() //get installation location - this is where we will put our terraform binary file

goarch := runtime.GOARCH
goos := runtime.GOOS
Expand Down Expand Up @@ -97,7 +100,7 @@ func install(tfversion string, binPath string, mirrorURL string) {
CreateSymlink(installFileVersionPath, binPath)
logger.Infof("Switched terraform to version %q", tfversion)
addRecent(tfversion) //add to recent file for faster lookup
os.Exit(0)
return
}

//if does not have slash - append slash
Expand All @@ -121,6 +124,8 @@ func install(tfversion string, binPath string, mirrorURL string) {
logger.Fatalf("Unable to unzip %q file: %v", zipFile, errUnzip)
}

logger.Debug("Waiting for deferred functions.")
wg.Wait()
/* rename unzipped file to terraform version name - terraform_x.x.x */
installFilePath := ConvertExecutableExt(filepath.Join(installLocation, installFile))
RenameFile(installFilePath, installFileVersionPath)
Expand All @@ -139,13 +144,13 @@ func install(tfversion string, binPath string, mirrorURL string) {
CreateSymlink(installFileVersionPath, binPath)
logger.Infof("Switched terraform to version %q", tfversion)
addRecent(tfversion) //add to recent file for faster lookup
os.Exit(0)
return
}

// addRecent : add to recent file
func addRecent(requestedVersion string) {

installLocation = getInstallLocation() //get installation location - this is where we will put our terraform binary file
installLocation = GetInstallLocation() //get installation location - this is where we will put our terraform binary file
versionFile := filepath.Join(installLocation, recentFile)

fileExist := CheckFileExist(versionFile)
Expand Down Expand Up @@ -188,7 +193,7 @@ func addRecent(requestedVersion string) {
// getRecentVersions : get recent version from file
func getRecentVersions() ([]string, error) {

installLocation = getInstallLocation() //get installation location - this is where we will put our terraform binary file
installLocation = GetInstallLocation() //get installation location - this is where we will put our terraform binary file
versionFile := filepath.Join(installLocation, recentFile)

fileExist := CheckFileExist(versionFile)
Expand Down Expand Up @@ -226,7 +231,7 @@ func getRecentVersions() ([]string, error) {

// CreateRecentFile : create RECENT file
func CreateRecentFile(requestedVersion string) {
installLocation = getInstallLocation() //get installation location - this is where we will put our terraform binary file
installLocation = GetInstallLocation() //get installation location - this is where we will put our terraform binary file
_ = WriteLines([]string{requestedVersion}, filepath.Join(installLocation, recentFile))
}

Expand Down Expand Up @@ -288,65 +293,69 @@ func installableBinLocation(userBinPath string) string {
}

// InstallLatestVersion install latest stable tf version
func InstallLatestVersion(customBinaryPath, mirrorURL string) {
func InstallLatestVersion(dryRun bool, customBinaryPath, mirrorURL string) {
logger.Debugf("Install latest version. Dry run: %s", strconv.FormatBool(dryRun))
tfversion, _ := getTFLatest(mirrorURL)
install(tfversion, customBinaryPath, mirrorURL)
if !dryRun {
install(tfversion, customBinaryPath, mirrorURL)
}
}

// InstallLatestImplicitVersion install latest - argument (version) must be provided
func InstallLatestImplicitVersion(requestedVersion, customBinaryPath, mirrorURL string, preRelease bool) {
func InstallLatestImplicitVersion(dryRun bool, requestedVersion, customBinaryPath, mirrorURL string, preRelease bool) {
_, err := version.NewConstraint(requestedVersion)
if err != nil {
logger.Errorf("Error parsing constraint %q: %v", requestedVersion, err)
}
tfversion, err := getTFLatestImplicit(mirrorURL, preRelease, requestedVersion)
if err == nil && tfversion != "" {
if err == nil && tfversion != "" && !dryRun {
install(tfversion, customBinaryPath, mirrorURL)
}
logger.Errorf("Error parsing constraint %q: %v", requestedVersion, err)
PrintInvalidMinorTFVersion()
}

// InstallVersion install with provided version as argument
func InstallVersion(arg, customBinaryPath, mirrorURL string) {
if validVersionFormat(arg) {
requestedVersion := arg

//check to see if the requested version has been downloaded before
installLocation := getInstallLocation()
installFileVersionPath := ConvertExecutableExt(filepath.Join(installLocation, VersionPrefix+requestedVersion))
recentDownloadFile := CheckFileExist(installFileVersionPath)
if recentDownloadFile {
ChangeSymlink(installFileVersionPath, customBinaryPath)
logger.Infof("Switched terraform to version %q", requestedVersion)
addRecent(requestedVersion) //add to recent file for faster lookup
os.Exit(0)
}

// If the requested version had not been downloaded before
// Set list all true - all versions including beta and rc will be displayed
tflist, _ := getTFList(mirrorURL, true) // Get list of versions
exist := versionExist(requestedVersion, tflist) // Check if version exists before downloading it

if exist {
install(requestedVersion, customBinaryPath, mirrorURL)
func InstallVersion(dryRun bool, version, customBinaryPath, mirrorURL string) {
logger.Debugf("Install version %s. Dry run: %s", version, strconv.FormatBool(dryRun))
if !dryRun {
if validVersionFormat(version) {
requestedVersion := version

//check to see if the requested version has been downloaded before
installLocation := GetInstallLocation()
installFileVersionPath := ConvertExecutableExt(filepath.Join(installLocation, VersionPrefix+requestedVersion))
recentDownloadFile := CheckFileExist(installFileVersionPath)
if recentDownloadFile {
ChangeSymlink(installFileVersionPath, customBinaryPath)
logger.Infof("Switched terraform to version %q", requestedVersion)
addRecent(requestedVersion) //add to recent file for faster lookup
return
} else {
// If the requested version had not been downloaded before
// Set list all true - all versions including beta and rc will be displayed
tflist, _ := getTFList(mirrorURL, true) // Get list of versions
exist := versionExist(requestedVersion, tflist) // Check if version exists before downloading it

if exist {
install(requestedVersion, customBinaryPath, mirrorURL)
} else {
logger.Fatal("The provided terraform version does not exist.\n Try `tfswitch -l` to see all available versions")
}
}
} else {
logger.Fatal("The provided terraform version does not exist.\n Try `tfswitch -l` to see all available versions")
PrintInvalidTFVersion()
logger.Error("Args must be a valid terraform version")
UsageMessage()
os.Exit(1)
}

} else {
PrintInvalidTFVersion()
logger.Error("Args must be a valid terraform version")
UsageMessage()
os.Exit(1)
}
}

// InstallOption displays & installs tf version
/* listAll = true - all versions including beta and rc will be displayed */
/* listAll = false - only official stable release are displayed */
func InstallOption(listAll bool, customBinaryPath, mirrorURL string) {
func InstallOption(listAll, dryRun bool, customBinaryPath, mirrorURL string) {
tflist, _ := getTFList(mirrorURL, listAll) // Get list of versions
recentVersions, _ := getRecentVersions() // Get recent versions from RECENT file
tflist = append(recentVersions, tflist...) // Append recent versions to the top of the list
Expand Down Expand Up @@ -374,7 +383,8 @@ func InstallOption(listAll bool, customBinaryPath, mirrorURL string) {
logger.Fatalf("Prompt failed %v", errPrompt)
}
}

install(tfversion, customBinaryPath, mirrorURL)
if !dryRun {
install(tfversion, customBinaryPath, mirrorURL)
}
os.Exit(0)
}
3 changes: 3 additions & 0 deletions lib/param_parsing/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Params struct {
ChDirPath string
CustomBinaryPath string
DefaultVersion string
DryRun bool
HelpFlag bool
LatestFlag bool
LatestPre string
Expand All @@ -33,6 +34,7 @@ func GetParameters() 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.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")
getopt.BoolVarLong(&params.DryRun, "dry-run", 'r', "Only show what tfswitch would do. Don't download anything.")
getopt.BoolVarLong(&params.HelpFlag, "help", 'h', "Displays help message")
getopt.BoolVarLong(&params.LatestFlag, "latest", 'u', "Get latest stable version")
getopt.StringVarLong(&params.LatestPre, "latest-pre", 'p', "Latest pre-release implicit version. Ex: tfswitch --latest-pre 0.13 downloads 0.13.0-rc1 (latest)")
Expand Down Expand Up @@ -82,6 +84,7 @@ func initParams(params Params) Params {
params.ChDirPath = lib.GetCurrentDirectory()
params.CustomBinaryPath = lib.ConvertExecutableExt(lib.GetDefaultBin())
params.DefaultVersion = lib.DefaultLatest
params.DryRun = false
params.HelpFlag = false
params.LatestFlag = false
params.LatestPre = lib.DefaultLatest
Expand Down
23 changes: 20 additions & 3 deletions lib/param_parsing/parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package param_parsing

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

Expand Down Expand Up @@ -42,10 +44,9 @@ func TestGetParameters_params_are_overridden_by_toml_file(t *testing.T) {
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()
})
logger = lib.InitLogger("DEBUG")
expected := "../../test-data/integration-tests/test_tfswitchtoml"
os.Args = []string{"cmd", "--chdir=" + expected, "--bin=/usr/test/bin"}
params := GetParameters()
Expand All @@ -66,6 +67,22 @@ func TestGetParameters_toml_params_are_overridden_by_cli(t *testing.T) {
}
}

func TestGetParameters_dry_run_wont_download_anything(t *testing.T) {
logger = lib.InitLogger("DEBUG")
installLocation := lib.GetInstallLocation()
expected := "../../test-data/integration-tests/test_versiontf"
//os.Args = []string{"cmd", "--chdir=" + expected, "--bin=/tmp", "--dry-run"}
os.Args = []string{"cmd", "--log-level=DEBUG", "--chdir=" + expected, "--bin=/tmp"}
params := GetParameters()
installFileVersionPath := lib.ConvertExecutableExt(filepath.Join(installLocation, lib.VersionPrefix+params.Version))
// Make sure the file tfswitch WOULD download is absent
_ = os.Remove(installFileVersionPath)
lib.InstallVersion(params.DryRun, params.Version, params.CustomBinaryPath, params.MirrorURL)
if lib.FileExistsAndIsNotDir(installFileVersionPath) {
t.Error("Dry run should NOT download any files.")
}
}

func TestGetParameters_check_config_precedence(t *testing.T) {
t.Cleanup(func() {
getopt.CommandLine = getopt.New()
Expand Down
Loading

0 comments on commit 770ea4c

Please sign in to comment.