Skip to content

Commit

Permalink
154 feature request option to dry run (#416)
Browse files Browse the repository at this point in the history
* implement dry-run feature from #154
  • Loading branch information
MatrixCrawler committed Apr 26, 2024
1 parent 9208450 commit a455c66
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 67 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
8 changes: 5 additions & 3 deletions lib/download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import (
"os"
"path/filepath"
"runtime"
"sync"
"testing"

"github.com/mitchellh/go-homedir"
)

// TestDownloadFromURL_FileNameMatch : Check expected filename exist when downloaded
func TestDownloadFromURL_FileNameMatch(t *testing.T) {

logger = InitLogger("DEBUG")
hashiURL := "https://releases.hashicorp.com/terraform/"
installVersion := "terraform_"
tempDir := t.TempDir()
Expand Down Expand Up @@ -45,10 +46,11 @@ func TestDownloadFromURL_FileNameMatch(t *testing.T) {

/* test download old terraform version */
lowestVersion := "0.11.0"

var wg sync.WaitGroup
defer wg.Done()
urlToDownload := hashiURL + lowestVersion + "/" + installVersion + lowestVersion + macOS
expectedFile := filepath.Join(installLocation, installVersion+lowestVersion+macOS)
installedFile, errDownload := downloadFromURL(installLocation, urlToDownload)
installedFile, errDownload := downloadFromURL(installLocation, urlToDownload, &wg)

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 @@ -59,16 +59,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
93 changes: 51 additions & 42 deletions lib/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"

"github.com/manifoldco/promptui"

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 the installDir if it does not exist
func getInstallLocation(installPath string) string {
func GetInstallLocation(installPath string) string {
/* set installation location */
installLocation = filepath.Join(installPath, InstallDir)

Expand All @@ -50,6 +52,7 @@ func getInstallLocation(installPath string) string {

// install : install the provided version in the argument
func install(tfversion string, binPath string, installPath 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 @@ -58,7 +61,7 @@ func install(tfversion string, binPath string, installPath string, mirrorURL str
binPath = installableBinLocation(binPath)

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

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

//if does not have slash - append slash
Expand All @@ -112,6 +115,8 @@ func install(tfversion string, binPath string, installPath string, mirrorURL str
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 @@ -130,13 +135,13 @@ func install(tfversion string, binPath string, installPath string, mirrorURL str
CreateSymlink(installFileVersionPath, binPath)
logger.Infof("Switched terraform to version %q", tfversion)
addRecent(tfversion, installPath) //add to recent file for faster lookup
os.Exit(0)
return
}

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

installLocation = getInstallLocation(installPath) //get installation location - this is where we will put our terraform binary file
installLocation = GetInstallLocation(installPath) //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 @@ -179,7 +184,7 @@ func addRecent(requestedVersion string, installPath string) {
// getRecentVersions : get recent version from file
func getRecentVersions(installPath string) ([]string, error) {

installLocation = getInstallLocation(installPath) //get installation location - this is where we will put our terraform binary file
installLocation = GetInstallLocation(installPath) //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 @@ -217,7 +222,7 @@ func getRecentVersions(installPath string) ([]string, error) {

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

Expand Down Expand Up @@ -274,65 +279,68 @@ func installableBinLocation(userBinPath string) string {
}

// InstallLatestVersion install latest stable tf version
func InstallLatestVersion(customBinaryPath, installPath string, mirrorURL string) {
func InstallLatestVersion(dryRun bool, customBinaryPath, installPath string, mirrorURL string) {
tfversion, _ := getTFLatest(mirrorURL)
install(tfversion, customBinaryPath, installPath, mirrorURL)
if !dryRun {
install(tfversion, customBinaryPath, installPath, mirrorURL)
}
}

// InstallLatestImplicitVersion install latest - argument (version) must be provided
func InstallLatestImplicitVersion(requestedVersion, customBinaryPath, installPath string, mirrorURL string, preRelease bool) {
func InstallLatestImplicitVersion(dryRun bool, requestedVersion, customBinaryPath, installPath string, 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, installPath, mirrorURL)
}
logger.Errorf("Error parsing constraint %q: %v", requestedVersion, err)
PrintInvalidMinorTFVersion()
}

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

//check to see if the requested version has been downloaded before
installLocation := getInstallLocation(installPath)
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, installPath) //add to recent file for faster lookup
os.Exit(0)
}
func InstallVersion(dryRun bool, version, customBinaryPath, installPath, 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(installPath)
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, installPath) //add to recent file for faster lookup
return
}

// 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 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, installPath, mirrorURL)
if exist {
install(requestedVersion, customBinaryPath, installPath, 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, installPath string, mirrorURL string) {
func InstallOption(listAll, dryRun bool, customBinaryPath, installPath string, mirrorURL string) {
tflist, _ := getTFList(mirrorURL, listAll) // Get list of versions
recentVersions, _ := getRecentVersions(installPath) // Get recent versions from RECENT file
tflist = append(recentVersions, tflist...) // Append recent versions to the top of the list
Expand Down Expand Up @@ -360,7 +368,8 @@ func InstallOption(listAll bool, customBinaryPath, installPath string, mirrorURL
logger.Fatalf("Prompt failed %v", errPrompt)
}
}

install(tfversion, customBinaryPath, installPath, mirrorURL)
if !dryRun {
install(tfversion, customBinaryPath, installPath, 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
InstallPath string
LatestFlag bool
Expand All @@ -34,6 +35,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.StringVarLong(&params.InstallPath, "install", 'i', "Custom install path. Ex: tfswitch -i /Users/username. The binaries will be in the sub installDir directory e.g. /Users/username/"+lib.InstallDir)
getopt.BoolVarLong(&params.LatestFlag, "latest", 'u', "Get latest stable version")
Expand Down Expand Up @@ -90,6 +92,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.InstallPath = lib.GetHomeDirectory()
params.LatestFlag = false
Expand Down
Loading

0 comments on commit a455c66

Please sign in to comment.