diff --git a/go.mod b/go.mod index 6de2b7a2..9e6e30f3 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ require ( github.com/chzyer/logex v1.1.10 // indirect github.com/chzyer/readline v0.0.0-20171208011716-f6d7a1f6fbf3 // indirect github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect - github.com/hashicorp/go-version v1.4.0 // indirect + github.com/hashicorp/go-version v1.4.0 github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80 - github.com/hashicorp/terraform-config-inspect v0.0.0-20211115214459-90acf1ca460f // indirect + github.com/hashicorp/terraform-config-inspect v0.0.0-20211115214459-90acf1ca460f github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect github.com/kiranjthomas/terraform-config-inspect v0.0.0-20191120205521-a1d709eb2824 github.com/lunixbochs/vtclean v0.0.0-20170504063817-d14193dfc626 // indirect diff --git a/lib/download_test.go b/lib/download_test.go index 851ff968..22d3358c 100644 --- a/lib/download_test.go +++ b/lib/download_test.go @@ -9,7 +9,7 @@ import ( "path/filepath" "testing" - lib "github.com/warrensbox/terraform-switcher/lib" + "github.com/warrensbox/terraform-switcher/lib" ) // TestDownloadFromURL_FileNameMatch : Check expected filename exist when downloaded @@ -17,7 +17,8 @@ func TestDownloadFromURL_FileNameMatch(t *testing.T) { hashiURL := "https://releases.hashicorp.com/terraform/" installVersion := "terraform_" - installPath := GetInstallLocation(".terraform.versions_test") + tempDir := t.TempDir() + installPath := fmt.Sprintf(tempDir + string(os.PathSeparator) + ".terraform.versions_test") macOS := "_darwin_amd64.zip" // get current user @@ -31,16 +32,16 @@ func TestDownloadFromURL_FileNameMatch(t *testing.T) { // create /.terraform.versions_test/ directory to store code if _, err := os.Stat(installLocation); os.IsNotExist(err) { - log.Printf("Creating directory for terraform: %v", installLocation) + t.Logf("Creating directory for terraform: %v", installLocation) err = os.MkdirAll(installLocation, 0755) if err != nil { - fmt.Printf("Unable to create directory for terraform: %v", installLocation) - panic(err) + t.Logf("Unable to create directory for terraform: %v", installLocation) + t.Error("Test fail") } } - /* test download lowest terraform version */ - lowestVersion := "0.1.0" + /* test download old terraform version */ + lowestVersion := "0.11.0" url := hashiURL + lowestVersion + "/" + installVersion + lowestVersion + macOS expectedFile := filepath.Join(usr.HomeDir, installPath, installVersion+lowestVersion+macOS) @@ -61,156 +62,27 @@ func TestDownloadFromURL_FileNameMatch(t *testing.T) { t.Error("Download file mismatches expected file (unexpected)") } - /* test download latest terraform version */ - latestVersion := "0.11.7" - - url = hashiURL + latestVersion + "/" + installVersion + latestVersion + macOS - expectedFile = filepath.Join(usr.HomeDir, installPath, installVersion+latestVersion+macOS) - installedFile, errDownload = lib.DownloadFromURL(installLocation, url) - - if errDownload != nil { - t.Logf("Expected file name %v to be downloaded", expectedFile) - t.Error("Download not possible (unexpected)") - } - - if installedFile == expectedFile { - t.Logf("Expected file name %v", expectedFile) - t.Logf("Downloaded file name %v", installedFile) - t.Log("Download file name matches expected file") - } else { - t.Logf("Expected file name %v", expectedFile) - t.Logf("Downloaded file name %v", installedFile) - t.Error("Dowload file name mismatches expected file (unexpected)") - } - - cleanUp(installLocation) -} - -// TestDownloadFromURL_FileExist : Check expected file exist when downloaded -func TestDownloadFromURL_FileExist(t *testing.T) { - - hashiURL := "https://releases.hashicorp.com/terraform/" - installFile := "terraform" - installVersion := "terraform_" - installPath := GetInstallLocation(".terraform.versions_test") - macOS := "_darwin_amd64.zip" - - // get current user - usr, errCurr := user.Current() - if errCurr != nil { - log.Fatal(errCurr) - } - - fmt.Printf("Current user: %v \n", usr.HomeDir) - installLocation := filepath.Join(usr.HomeDir, installPath) - - // create /.terraform.versions_test/ directory to store code - if _, err := os.Stat(installLocation); os.IsNotExist(err) { - log.Printf("Creating directory for terraform: %v", installLocation) - err = os.MkdirAll(installLocation, 0755) - if err != nil { - fmt.Printf("Unable to create directory for terraform: %v", installLocation) - panic(err) - } - } - - /* test download lowest terraform version */ - lowestVersion := "0.1.0" - - url := hashiURL + lowestVersion + "/" + installVersion + lowestVersion + macOS - expectedFile := filepath.Join(usr.HomeDir, installPath, installVersion+lowestVersion+macOS) - installedFile, errDownload := lib.DownloadFromURL(installLocation, url) - - if errDownload != nil { - t.Logf("Expected file name %v to be downloaded", expectedFile) - t.Error("Download not possible (unexpected)") - } - - if checkFileExist(expectedFile) { - t.Logf("Expected file %v", expectedFile) - t.Logf("Downloaded file %v", installedFile) - t.Log("Download file matches expected file") - } else { - t.Logf("Expected file %v", expectedFile) - t.Logf("Downloaded file %v", installedFile) - t.Error("Download file mismatches expected file (unexpected)") - } - - /* test download latest terraform version */ - latestVersion := "0.11.7" - - url = hashiURL + latestVersion + "/" + installVersion + latestVersion + macOS - expectedFile = filepath.Join(usr.HomeDir, installPath, installVersion+latestVersion+macOS) - installFile, errDownload = lib.DownloadFromURL(installLocation, url) - - if errDownload != nil { - t.Logf("Expected file name %v to be downloaded", expectedFile) - t.Error("Download not possible (unexpected)") - } - - if checkFileExist(expectedFile) { - t.Logf("Expected file %v", expectedFile) - t.Logf("Downloaded file %v", installFile) - t.Log("Download file matches expected file") - } else { - t.Logf("Expected file %v", expectedFile) - t.Logf("Downloaded file %v", installFile) - t.Error("Download file mismatches expected file (unexpected)") - } - - cleanUp(installLocation) -} - -// TestInvalidURL : Invalid url should throw an error -func TestInvalidURL(t *testing.T) { - - hashiURL := "https://releases.hashicorp.com/terraform/" - installVersion := "terraform_" - installPath := GetInstallLocation(".terraform.versions_test") - macOS := "_darwin_amd64.zip" - invalidVersion := "0.11.7-nonexistent" - - // get current user - usr, errCurr := user.Current() - if errCurr != nil { - log.Fatal(errCurr) - } - - fmt.Printf("Current user: %v \n", usr.HomeDir) - installLocation := filepath.Join(usr.HomeDir, installPath) - - // create /.terraform.versions_test/ directory to store code - if _, err := os.Stat(installLocation); os.IsNotExist(err) { - log.Printf("Creating directory for terraform: %v\n", installLocation) - err = os.MkdirAll(installLocation, 0755) - if err != nil { - fmt.Printf("Unable to create directory for terraform: %v\n", installLocation) - panic(err) - } - } - - url := hashiURL + invalidVersion + "/" + installVersion + invalidVersion + macOS - //expectedFile :=filepath.Join(usr.HomeDir, installPath, installVersion + invalidVersion + macOS) - _, errDownload := lib.DownloadFromURL(installLocation, url) - - if errDownload != nil { - t.Logf("Unable to download from %s - invalid url or version (expected)\n", url) - t.Logf("Download not possible (expected)") + //check file name is what is expected + _, err := os.Stat(expectedFile) + if err != nil { + t.Logf("Expected file does not exist %v", expectedFile) } - cleanUp(installLocation) + t.Cleanup(func() { + defer os.Remove(tempDir) + fmt.Println("Cleanup temporary directory") + }) } -// TestDownloadFromURL_Valid : Test if https://releases.hashicorp.com/terraform/ is still valid +// // TestDownloadFromURL_Valid : Test if https://releases.hashicorp.com/terraform/ is still valid func TestDownloadFromURL_Valid(t *testing.T) { hashiURL := "https://releases.hashicorp.com/terraform/" url, err := url.ParseRequestURI(hashiURL) if err != nil { - t.Errorf("Valid URL provided: %v", err) - t.Errorf("Invalid URL %v", err) + t.Errorf("Invalid URL %v [unexpected]", err) } else { - t.Logf("Valid URL from %v", url) + t.Logf("Valid URL from %v [expected]", url) } } diff --git a/lib/semver.go b/lib/semver.go new file mode 100644 index 00000000..5dcaa83d --- /dev/null +++ b/lib/semver.go @@ -0,0 +1,61 @@ +package lib + +import ( + "fmt" + "sort" + + semver "github.com/hashicorp/go-version" +) + +// GetSemver : returns version that will be installed based on server constaint provided +func GetSemver(tfconstraint *string, mirrorURL *string) (string, error) { + + listAll := true + tflist, _ := GetTFList(*mirrorURL, listAll) //get list of versions + fmt.Printf("Reading required version from constraint: %s\n", *tfconstraint) + tfversion, err := SemVerParser(tfconstraint, tflist) + return tfversion, err +} + +// ValidateSemVer : Goes through the list of terraform version, return a valid tf version for contraint provided +func SemVerParser(tfconstraint *string, tflist []string) (string, error) { + tfversion := "" + constraints, err := semver.NewConstraint(*tfconstraint) //NewConstraint returns a Constraints instance that a Version instance can be checked against + if err != nil { + return "", fmt.Errorf("error parsing constraint: %s", *tfconstraint) + } + versions := make([]*semver.Version, len(tflist)) + //put tfversion into semver object + for i, tfvals := range tflist { + version, err := semver.NewVersion(tfvals) //NewVersion parses a given version and returns an instance of Version or an error if unable to parse the version. + if err != nil { + return "", fmt.Errorf("error parsing constraint: %s", err) + } + versions[i] = version + } + + sort.Sort(sort.Reverse(semver.Collection(versions))) + + for _, element := range versions { + if constraints.Check(element) { // Validate a version against a constraint + tfversion = element.String() + fmt.Printf("Matched version: %s\n", tfversion) + if ValidVersionFormat(tfversion) { //check if version format is correct + return tfversion, nil + } + } + } + + PrintInvalidTFVersion() + return "", fmt.Errorf("error parsing constraint: %s", *tfconstraint) +} + +// Print invalid TF version +func PrintInvalidTFVersion() { + fmt.Println("Version does not exist or invalid terraform version format.\n Format should be #.#.# or #.#.#-@# where # are numbers and @ are word characters.\n For example, 0.11.7 and 0.11.9-beta1 are valid versions") +} + +// Print invalid TF version +func PrintInvalidMinorTFVersion() { + fmt.Println("Invalid minor terraform version format. Format should be #.# where # are numbers. For example, 0.11 is valid version") +} diff --git a/lib/semver_test.go b/lib/semver_test.go new file mode 100644 index 00000000..4e2ec39b --- /dev/null +++ b/lib/semver_test.go @@ -0,0 +1,92 @@ +package lib_test + +import ( + "testing" + + "github.com/warrensbox/terraform-switcher/lib" +) + +var versionsRaw = []string{ + "1.1", + "1.2.1", + "1.2.2", + "1.2.3", + "1.3", + "1.1.4", + "0.7.1", + "1.4-beta", + "1.4", + "2"} + +// TestSemverParser1 : Test to see if SemVerParser parses valid version +// Test version 1.1 +func TestSemverParserCase1(t *testing.T) { + + tfconstraint := "1.1" + tfversion, _ := lib.SemVerParser(&tfconstraint, versionsRaw) + expected := "1.1.0" + if tfversion == expected { + t.Logf("Version exist in list %v [expected]", expected) + } else { + t.Logf("Version does not exist in list %v [unexpected]", tfconstraint) + t.Errorf("This is unexpected. Parsing failed. Expected: %v", expected) + } +} + +// TestSemverParserCase2 : Test to see if SemVerParser parses valid version +// Test version ~> 1.1 should return 1.1.4 +func TestSemverParserCase2(t *testing.T) { + + tfconstraint := "~> 1.1.0" + tfversion, _ := lib.SemVerParser(&tfconstraint, versionsRaw) + expected := "1.1.4" + if tfversion == expected { + t.Logf("Version exist in list %v [expected]", expected) + } else { + t.Logf("Version does not exist in list %v [unexpected]", tfconstraint) + t.Errorf("This is unexpected. Parsing failed. Expected: %v", expected) + } +} + +// TestSemverParserCase3 : Test to see if SemVerParser parses valid version +// Test version ~> 1.1 should return 1.1.4 +func TestSemverParserCase3(t *testing.T) { + + tfconstraint := "~> 1.A.0" + _, err := lib.SemVerParser(&tfconstraint, versionsRaw) + if err != nil { + t.Logf("This test is suppose to error %v [expected]", tfconstraint) + } else { + t.Errorf("This test is suppose to error but passed %v [expected]", tfconstraint) + } +} + +// TestSemverParserCase4 : Test to see if SemVerParser parses valid version +// Test version ~> >= 1.0, < 1.4 should return 1.3.0 +func TestSemverParserCase4(t *testing.T) { + + tfconstraint := ">= 1.0, < 1.4" + tfversion, _ := lib.SemVerParser(&tfconstraint, versionsRaw) + expected := "1.3.0" + if tfversion == expected { + t.Logf("Version exist in list %v [expected]", expected) + } else { + t.Logf("Version does not exist in list %v [unexpected]", tfconstraint) + t.Errorf("This is unexpected. Parsing failed. Expected: %v", expected) + } +} + +// TestSemverParserCase5 : Test to see if SemVerParser parses valid version +// Test version ~> >= 1.0 should return 2.0.0 +func TestSemverParserCase5(t *testing.T) { + + tfconstraint := ">= 1.0" + tfversion, _ := lib.SemVerParser(&tfconstraint, versionsRaw) + expected := "2.0.0" + if tfversion == expected { + t.Logf("Version exist in list %v [expected]", expected) + } else { + t.Logf("Version does not exist in list %v [unexpected]", tfconstraint) + t.Errorf("This is unexpected. Parsing failed. Expected: %v", expected) + } +} diff --git a/main.go b/main.go index 652ebf23..4fa7d6ce 100644 --- a/main.go +++ b/main.go @@ -23,10 +23,8 @@ import ( "log" "os" "path/filepath" - "sort" "strings" - semver "github.com/hashicorp/go-version" "github.com/hashicorp/hcl2/gohcl" "github.com/hashicorp/hcl2/hclparse" "github.com/hashicorp/terraform-config-inspect/tfconfig" @@ -249,7 +247,7 @@ func installLatestImplicitVersion(requestedVersion string, custBinPath, mirrorUR tfversion, _ := lib.GetTFLatestImplicit(*mirrorURL, preRelease, requestedVersion) lib.Install(tfversion, *custBinPath, *mirrorURL) } else { - printInvalidMinorTFVersion() + lib.PrintInvalidMinorTFVersion() } } @@ -264,7 +262,7 @@ func showLatestImplicitVersion(requestedVersion string, custBinPath, mirrorURL * os.Exit(1) } } else { - printInvalidMinorTFVersion() + lib.PrintInvalidMinorTFVersion() } } @@ -297,23 +295,13 @@ func installVersion(arg string, custBinPath *string, mirrorURL *string) { } } else { - printInvalidTFVersion() + lib.PrintInvalidTFVersion() fmt.Println("Args must be a valid terraform version") usageMessage() os.Exit(1) } } -// Print invalid TF version -func printInvalidTFVersion() { - fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # are numbers and @ are word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions") -} - -// Print invalid TF version -func printInvalidMinorTFVersion() { - fmt.Println("Invalid minor terraform version format. Format should be #.# where # are numbers. For example, 0.11 is valid version") -} - //retrive file content of regular file func retrieveFileContents(file string) string { fileContents, err := ioutil.ReadFile(file) @@ -442,43 +430,12 @@ func installTFProvidedModule(dir string, custBinPath, mirrorURL *string) { // install using a version constraint func installFromConstraint(tfconstraint *string, custBinPath, mirrorURL *string) { - tfversion := "" - listAll := true //set list all true - all versions including beta and rc will be displayed - tflist, _ := lib.GetTFList(*mirrorURL, listAll) //get list of versions - fmt.Printf("Reading required version from constraint: %s\n", *tfconstraint) - - constraints, err := semver.NewConstraint(*tfconstraint) //NewConstraint returns a Constraints instance that a Version instance can be checked against - if err != nil { - fmt.Printf("Error parsing constraint: %s\nPlease check constrain syntax on terraform file.\n", err) - fmt.Println() - os.Exit(1) - } - versions := make([]*semver.Version, len(tflist)) - for i, tfvals := range tflist { - version, err := semver.NewVersion(tfvals) //NewVersion parses a given version and returns an instance of Version or an error if unable to parse the version. - if err != nil { - fmt.Printf("Error parsing version: %s", err) - os.Exit(1) - } - - versions[i] = version - } - sort.Sort(sort.Reverse(semver.Collection(versions))) - - for _, element := range versions { - if constraints.Check(element) { // Validate a version against a constraint - tfversion = element.String() - fmt.Printf("Matched version: %s\n", tfversion) - if lib.ValidVersionFormat(tfversion) { //check if version format is correct - lib.Install(tfversion, *custBinPath, *mirrorURL) - } else { - printInvalidTFVersion() - os.Exit(1) - } - } + tfversion, err := lib.GetSemver(tfconstraint, mirrorURL) + if err == nil { + lib.Install(tfversion, *custBinPath, *mirrorURL) } - + fmt.Println(err) fmt.Println("No version found to match constraint. Follow the README.md instructions for setup. https://github.com/warrensbox/terraform-switcher/blob/master/README.md") os.Exit(1) }