From c623fdde6cd73a6b8c05db5e0a79807d5ecb60f3 Mon Sep 17 00:00:00 2001 From: "warren.veerasingam" Date: Tue, 10 Nov 2020 19:19:09 -0600 Subject: [PATCH] refactor config file logic --- go.sum | 1 + main.go | 389 ++++++++++++++++++++++++++++++++------------------------ version | 2 +- 3 files changed, 222 insertions(+), 170 deletions(-) diff --git a/go.sum b/go.sum index 7d6fda6f..3998a86e 100644 --- a/go.sum +++ b/go.sum @@ -214,5 +214,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= diff --git a/main.go b/main.go index a669385d..63254b19 100644 --- a/main.go +++ b/main.go @@ -25,11 +25,11 @@ import ( "sort" "strings" - "github.com/Masterminds/semver" - // original hashicorp upstream have broken dependencies, so using fork as workaround // TODO: move back to upstream + "github.com/Masterminds/semver" "github.com/kiranjthomas/terraform-config-inspect/tfconfig" + // "github.com/hashicorp/terraform-config-inspect/tfconfig" "github.com/manifoldco/promptui" @@ -47,7 +47,7 @@ const ( tomlFilename = ".tfswitch.toml" ) -var version = "0.8.0\n" +var version = "0.9.0\n" func main() { @@ -66,9 +66,9 @@ func main() { os.Exit(1) } - tfvfile := dir + fmt.Sprintf("/%s", tfvFilename) //settings for .terraform-version file in current directory (tfenv compatible) - rcfile := dir + fmt.Sprintf("/%s", rcFilename) //settings for .tfswitchrc file in current directory (backward compatible purpose) - configfile := dir + fmt.Sprintf("/%s", tomlFilename) //settings for .tfswitch.toml file in current directory (option to specify bin directory) + tfvfile := dir + fmt.Sprintf("/%s", tfvFilename) //settings for .terraform-version file in current directory (tfenv compatible) + rcfile := dir + fmt.Sprintf("/%s", rcFilename) //settings for .tfswitchrc file in current directory (backward compatible purpose) + tomlconfigfile := dir + fmt.Sprintf("/%s", tomlFilename) //settings for .tfswitch.toml file in current directory (option to specify bin directory) switch { case *versionFlag: @@ -77,186 +77,145 @@ func main() { case *helpFlag: //} else if *helpFlag { usageMessage() - case *listAllFlag: - fmt.Println("passing list") - listAll := true //set list all true - all versions including beta and rc will be displayed - installOption(listAll, custBinPath) - case len(args) == 1: - fmt.Println("pass arg number") - if lib.ValidVersionFormat(args[0]) { - - requestedVersion := args[0] - listAll := true //set list all true - all versions including beta and rc will be displayed - tflist, _ := lib.GetTFList(hashiURL, listAll) //get list of versions - exist := lib.VersionExist(requestedVersion, tflist) //check if version exist before downloading it - - if exist { - lib.Install(requestedVersion, *custBinPath) - } else { - fmt.Println("The provided terraform version does not exist. Try `tfswitch -l` to see all available versions.") - } - - } else { - fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # is numbers and @ is word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions") - fmt.Println("Args must be a valid terraform version") - usageMessage() + /* Checks if the .tfswitch.toml file exist */ + /* This block checks to see if the tfswitch toml file is provided in the current path. + * If the .tfswitch.toml file exist, it has a higher precedence than the .tfswitchrc file + * You can specify the custom binary path and the version you desire + * If you provide a custom binary path with the -b option, this will override the bin value in the toml file + * If you provide a version on the command line, this will override the version value in the toml file + */ + case fileExists(tomlconfigfile): + + readingFileMsg(tomlFilename) + version, binPath := getParamsTOML(custBinPath, dir) + + switch { + case version != "": + installVersion(version, &binPath) + case len(args) == 1: + installVersion(args[0], &binPath) + case *listAllFlag: + listAll := true //set list all true - all versions including beta and rc will be displayed + installOption(listAll, &binPath) + case checkTFModuleFileExist(dir): + installTFProvidedModule(dir, &binPath) + default: + listAll := false //set list all false - only official release will be displayed + installOption(listAll, &binPath) } - case fileExists(configfile) && len(args) == 0: - fmt.Println(configfile) - case fileExists(rcfile) && len(args) == 0: - fmt.Println(rcfile) - case fileExists(tfvfile) && len(args) == 0: - fmt.Println(tfvfile) - default: - //} else { - /* This block checks to see if the tfswitch toml file is provided in the current path. - * If the .tfswitch.toml file exist, it has a higher precedence than the .tfswitchrc file - * You can specify the custom binary path and the version you desire - * If you provide a custom binary path with the -b option, this will override the bin value in the toml file - * If you provide a version on the command line, this will override the version value in the toml file - */ - if _, err := os.Stat(configfile); err == nil { - fmt.Printf("Reading configuration from %s\n", tomlFilename) - tfversion := "" - binPath := *custBinPath //takes the default bin (defaultBin) if user does not specify bin path - configfileName := lib.GetFileName(tomlFilename) //get the config file - viper.SetConfigType("toml") - viper.SetConfigName(configfileName) - viper.AddConfigPath(dir) - - errs := viper.ReadInConfig() // Find and read the config file - if errs != nil { - fmt.Printf("Unable to read %s provided\n", tomlFilename) // Handle errors reading the config file - fmt.Println(err) - os.Exit(1) // exit immediately if config file provided but it is unable to read it - } - bin := viper.Get("bin") // read custom binary location - if binPath == defaultBin && bin != nil { // if the bin path is the same as the default binary path and if the custom binary is provided in the toml file (use it) - binPath = os.ExpandEnv(bin.(string)) - } - version := viper.Get("version") //attempt to get the version if it's provided in the toml - - if len(args) == 1 { //if the version is passed in the command line - requestedVersion := args[0] - listAll := true //set list all true - all versions including beta and rc will be displayed - tflist, _ := lib.GetTFList(hashiURL, listAll) //get list of versions - exist := lib.VersionExist(requestedVersion, tflist) //check if version exist before downloading it - - if exist { - tfversion = requestedVersion // set tfversion = the version needed - } - } else if version != nil { // if the required version in the toml file is provided (use it) - tfversion = version.(string) - } - - if *listAllFlag { //show all terraform version including betas and RCs - listAll := true //set list all true - all versions including beta and rc will be displayed - fmt.Println("testing listing all version") - installOption(listAll, &binPath) - } else if tfversion == "" { // if no version is provided, show a dropdown of available release versions - listAll := false //set list all false - only official release will be displayed - installOption(listAll, &binPath) - } else { - if lib.ValidVersionFormat(tfversion) { //check if version is correct - lib.Install(tfversion, binPath) - } else { - fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # is numbers and @ is word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions") - os.Exit(1) - } - } - } else if module, _ := tfconfig.LoadModule(dir); len(module.RequiredCore) >= 1 && len(args) == 0 { //if there is a version.tf file, and no commmand line arguments - - tfversion := "" - tfconstraint := module.RequiredCore[0] //we skip duplicated definitions and use only first one - listAll := true //set list all true - all versions including beta and rc will be displayed - tflist, _ := lib.GetTFList(hashiURL, listAll) //get list of versions - fmt.Printf("Reading required version from terraform file, constraint: %s\n", tfconstraint) - - constrains, 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 - } + // if len(args) == 1 { //if the version is passed in the command line + // if lib.ValidVersionFormat(args[0]) { + // requestedVersion := args[0] + // listAll := true //set list all true - all versions including beta and rc will be displayed + // tflist, _ := lib.GetTFList(hashiURL, listAll) //get list of versions + // exist := lib.VersionExist(requestedVersion, tflist) //check if version exist before downloading it + + // if exist { + // tfversion = requestedVersion // set tfversion = the version needed + // } + // } else if version != "" { // if the required version in the toml file is provided (use it) + // tfversion = version + // } + // } + + // if *listAllFlag { //show all terraform version including betas and RCs + // listAll := true //set list all true - all versions including beta and rc will be displayed + // installOption(listAll, &binPath) + // } else if tfversion == "" { // if no version is provided, show a dropdown of available release versions + // listAll := false //set list all false - only official release will be displayed + // installOption(listAll, &binPath) + // } else { + //if lib.ValidVersionFormat(tfversion) { //check if version is correct + // lib.Install(tfversion, binPath) + // } else { + // printInvalidTFVersion() + // os.Exit(1) + // } + // } + /* list all versions, //show all terraform version including betas and RCs*/ + case *listAllFlag: + installWithListAll(custBinPath) - sort.Sort(sort.Reverse(semver.Collection(versions))) + /* version provided on command line as arg */ + case len(args) == 1: + installVersion(args[0], custBinPath) - for _, element := range versions { + /* provide an tfswitchrc file */ + case fileExists(rcfile) && len(args) == 0: + readingFileMsg(rcfile) + tfversion := retrieveFileContents(rcfile) + installVersion(tfversion, custBinPath) - if constrains.Check(element) { // Validate a version against a constraint - tfversion = element.String() + /* if .terraform-version file found */ + case fileExists(tfvfile) && len(args) == 0: + readingFileMsg(tfvFilename) + tfversion := retrieveFileContents(tfvfile) + installVersion(tfversion, custBinPath) - fmt.Printf("Matched version: %s\n", tfversion) - if lib.ValidVersionFormat(tfversion) { //check if version format is correct - lib.Install(tfversion, *custBinPath) - } else { - fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # is numbers and @ is word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions") - os.Exit(1) - } - } - } + /* if versions.tf file found */ + case checkTFModuleFileExist(dir) && len(args) == 0: + installTFProvidedModule(dir, custBinPath) - 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) + // if no arg is provided + default: + listAll := false //set list all false - only official release will be displayed + installOption(listAll, custBinPath) + } +} - } else if _, err := os.Stat(rcfile); err == nil && len(args) == 0 { //if there is a .tfswitchrc file, and no commmand line arguments - fmt.Printf("Reading required terraform version %s \n", rcFilename) +/* Helper functions */ - fileContents, err := ioutil.ReadFile(rcfile) - if err != nil { - fmt.Printf("Failed to read %s file. Follow the README.md instructions for setup. https://github.com/warrensbox/terraform-switcher/blob/master/README.md\n", rcFilename) - fmt.Printf("Error: %s\n", err) - os.Exit(1) - } - tfversion := strings.TrimSuffix(string(fileContents), "\n") +// install with all possible versions, including beta and rc +func installWithListAll(custBinPath *string) { + listAll := true //set list all true - all versions including beta and rc will be displayed + installOption(listAll, custBinPath) +} - if lib.ValidVersionFormat(tfversion) { //check if version is correct - lib.Install(string(tfversion), *custBinPath) - } else { - fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # is numbers and @ is word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions") - os.Exit(1) - } - } else if _, err := os.Stat(tfvfile); err == nil && len(args) == 0 { //if there is a .terraform-version file, and no command line arguments - fmt.Printf("Reading required terraform version %s \n", tfvFilename) +// install with provided version as argument +func installVersion(arg string, custBinPath *string) { + if lib.ValidVersionFormat(arg) { + requestedVersion := arg + listAll := true //set list all true - all versions including beta and rc will be displayed + tflist, _ := lib.GetTFList(hashiURL, listAll) //get list of versions + exist := lib.VersionExist(requestedVersion, tflist) //check if version exist before downloading it - fileContents, err := ioutil.ReadFile(tfvfile) - if err != nil { - fmt.Printf("Failed to read %s file. Follow the README.md instructions for setup. https://github.com/warrensbox/terraform-switcher/blob/master/README.md\n", tfvFilename) - fmt.Printf("Error: %s\n", err) - os.Exit(1) - } - tfversion := strings.TrimSuffix(string(fileContents), "\n") + if exist { + lib.Install(requestedVersion, *custBinPath) + } else { + fmt.Println("The provided terraform version does not exist. Try `tfswitch -l` to see all available versions.") + } - if lib.ValidVersionFormat(tfversion) { //check if version is correct - lib.Install(string(tfversion), *custBinPath) - } else { - fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # is numbers and @ is word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions") - os.Exit(1) - } - } else if len(args) == 0 { //if there are no commmand line arguments + } else { + printInvalidTFVersion() + fmt.Println("Args must be a valid terraform version") + usageMessage() + } +} - listAll := false //set list all false - only official release will be displayed - installOption(listAll, custBinPath) +// Print invalid TF version +func printInvalidTFVersion() { + fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # is numbers and @ is word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions") +} - } else { - usageMessage() - } +//retrive file content of regular file +func retrieveFileContents(file string) string { + fileContents, err := ioutil.ReadFile(file) + if err != nil { + fmt.Printf("Failed to read %s file. Follow the README.md instructions for setup. https://github.com/warrensbox/terraform-switcher/blob/master/README.md\n", tfvFilename) + fmt.Printf("Error: %s\n", err) + os.Exit(1) } + tfversion := strings.TrimSuffix(string(fileContents), "\n") + return tfversion } -// fileExists checks if a file exists and is not a directory before we -// try using it to prevent further errors. +// Print message reading file content of : +func readingFileMsg(filename string) { + fmt.Printf("Reading required terraform version %s \n", filename) +} + +// fileExists checks if a file exists and is not a directory before we try using it to prevent further errors. func fileExists(filename string) bool { info, err := os.Stat(filename) if os.IsNotExist(err) { @@ -265,6 +224,53 @@ func fileExists(filename string) bool { return !info.IsDir() } +// fileExists checks if a file exists and is not a directory before we +// try using it to prevent further errors. +func checkTFModuleFileExist(dir string) bool { + + module, _ := tfconfig.LoadModule(dir) + if len(module.RequiredCore) >= 1 { + return true + } + return false +} + +/* valid install */ +// func simpleInstall(tfversion string, custBinPath *string) { +// if lib.ValidVersionFormat(tfversion) { //check if version is correct +// lib.Install(string(tfversion), *custBinPath) +// } else { +// fmt.Println("Invalid terraform version format. Format should be #.#.# or #.#.#-@# where # is numbers and @ is word characters. For example, 0.11.7 and 0.11.9-beta1 are valid versions") +// os.Exit(1) +// } +// } + +/* parses everything in the toml file, return required version and bin path */ +func getParamsTOML(custBinPath *string, dir string) (string, string) { + + fmt.Printf("Reading configuration from %s\n", tomlFilename) + binPath := *custBinPath //takes the default bin (defaultBin) if user does not specify bin path + configfileName := lib.GetFileName(tomlFilename) //get the config file + viper.SetConfigType("toml") + viper.SetConfigName(configfileName) + viper.AddConfigPath(dir) + + errs := viper.ReadInConfig() // Find and read the config file + if errs != nil { + fmt.Printf("Unable to read %s provided\n", tomlFilename) // Handle errors reading the config file + fmt.Println(errs) + os.Exit(1) // exit immediately if config file provided but it is unable to read it + } + + bin := viper.Get("bin") // read custom binary location + if binPath == defaultBin && bin != nil { // if the bin path is the same as the default binary path and if the custom binary is provided in the toml file (use it) + binPath = os.ExpandEnv(bin.(string)) + } + version := viper.Get("version") //attempt to get the version if it's provided in the toml + + return version.(string), binPath +} + func usageMessage() { fmt.Print("\n\n") getopt.PrintUsage(os.Stderr) @@ -298,3 +304,48 @@ func installOption(listAll bool, custBinPath *string) { lib.Install(tfversion, *custBinPath) os.Exit(0) } + +// installation when +func installTFProvidedModule(dir string, custBinPath *string) { + tfversion := "" + module, _ := tfconfig.LoadModule(dir) + tfconstraint := module.RequiredCore[0] //we skip duplicated definitions and use only first one + listAll := true //set list all true - all versions including beta and rc will be displayed + tflist, _ := lib.GetTFList(hashiURL, listAll) //get list of versions + fmt.Printf("Reading required version from terraform file, constraint: %s\n", tfconstraint) + + constrains, 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 constrains.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) + } else { + printInvalidTFVersion() + os.Exit(1) + } + } + } + + 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) +} diff --git a/version b/version index 524a135d..512833fc 100644 --- a/version +++ b/version @@ -1 +1 @@ -RELEASE_VERSION=0.8 \ No newline at end of file +RELEASE_VERSION=0.9 \ No newline at end of file