Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

154 feature request option to dry run #416

Merged
merged 5 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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