From 44d164e81594364f88fe5725299eb09cc8750a8c Mon Sep 17 00:00:00 2001 From: Prarit Bhargava Date: Wed, 2 Sep 2020 13:10:25 -0400 Subject: [PATCH 1/5] config: Convert config files from HCL to TOML Switch to using TOML format for the config files as the first step of rehauling the Config setup. TOML provides better viper support and is closer to the standard git config file format. HCL format is broken in viper and requires a small hack to convert double square brackets to single square brackets in the viper TOML file format. The test code extensively uses HCL and employs several viper HCL workarounds. The test code has been modified to remove HCL code and those workarounds. Before this change the ~/.config/lab.hcl contains "core" = { "host" = "https://gitlab.com" "token" = "abcdef12345" "user" = "prarit" } After this change the ~/.config/lab/lab.toml contains [core] host = "https://gitlab.com" token = "abcdef12345" user = "prarit" Also move the new config files into their own directories in ~/.config and the local .git. Convert config files from HCL to TOML and move them into their own ~/.config/lab and .git/lab/ directories. Signed-off-by: Prarit Bhargava --- README.md | 6 ++-- cmd/issue_show.go | 4 +-- cmd/mr_show.go | 4 +-- cmd/root_test.go | 11 ++++--- cmd/snippet_browse_test.go | 2 +- internal/config/config.go | 58 ++++++++++++++++++++++++++++++++++ internal/config/config_test.go | 28 ++++++++-------- internal/gitlab/gitlab_test.go | 10 +++--- main.go | 28 +++++++++++----- testdata/lab.hcl | 4 --- testdata/lab.toml | 3 ++ 11 files changed, 114 insertions(+), 44 deletions(-) delete mode 100644 testdata/lab.hcl create mode 100644 testdata/lab.toml diff --git a/README.md b/README.md index b5a2aee6..eed7e4f4 100644 --- a/README.md +++ b/README.md @@ -89,11 +89,11 @@ instance. There are several ways to provide this information to `lab`: 1. environment variables: `LAB_CORE_HOST`, `LAB_CORE_TOKEN`; 2. environment variables: `CI_PROJECT_URL`, `CI_JOB_TOKEN`; - Note: these are meant for when `lab` is running within a GitLab CI pipeline -3. directory-specific configuration file in [HashiCorp configuration language (HCL)](https://github.com/hashicorp/hcl): `./lab.hcl`; -4. user-specific configuration file in HCL: `~/.config/lab.hcl`. +3. directory-specific configuration file in [Tom's Obvious, Minimal Language (TOML)](https://github.com/toml-lang/toml): `./lab.toml`; +4. user-specific configuration file in HCL: `~/.config/lab/lab.toml`. These are checked in order. If no suitable config values are found, `lab` will -prompt for your GitLab information and save it into `~/.config/lab.hcl`. +prompt for your GitLab information and save it into `~/.config/lab/lab.toml`. For example: ``` $ lab diff --git a/cmd/issue_show.go b/cmd/issue_show.go index 8e3ab9cf..aabbf82d 100644 --- a/cmd/issue_show.go +++ b/cmd/issue_show.go @@ -121,12 +121,12 @@ func printDiscussions(discussions []*gitlab.Discussion, since string, issueNum i NewAccessTime := time.Now().UTC() // default path for metadata config file - metadatafile := ".git/lab/show_metadata.hcl" + metadatafile := ".git/lab/show_metadata.toml" viper.Reset() viper.AddConfigPath(".git/lab") viper.SetConfigName("show_metadata") - viper.SetConfigType("hcl") + viper.SetConfigType("toml") // write data if _, ok := viper.ReadInConfig().(viper.ConfigFileNotFoundError); ok { if _, err := os.Stat(".git/lab"); os.IsNotExist(err) { diff --git a/cmd/mr_show.go b/cmd/mr_show.go index 4ee04719..3b92422b 100644 --- a/cmd/mr_show.go +++ b/cmd/mr_show.go @@ -165,12 +165,12 @@ func printMRDiscussions(discussions []*gitlab.Discussion, since string, mrNum in NewAccessTime := time.Now().UTC() // default path for metadata config file - metadatafile := ".git/lab/show_metadata.hcl" + metadatafile := ".git/lab/show_metadata.toml" viper.Reset() viper.AddConfigPath(".git/lab") viper.SetConfigName("show_metadata") - viper.SetConfigType("hcl") + viper.SetConfigType("toml") // write data if _, ok := viper.ReadInConfig().(viper.ConfigFileNotFoundError); ok { if _, err := os.Stat(".git/lab"); os.IsNotExist(err) { diff --git a/cmd/root_test.go b/cmd/root_test.go index 9e2919fa..d5e02939 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -52,20 +52,21 @@ func TestMain(m *testing.M) { } // Load config for non-testbinary based tests viper.SetConfigName("lab") - viper.SetConfigType("hcl") + viper.SetConfigType("toml") viper.AddConfigPath(".") err = viper.ReadInConfig() if err != nil { log.Fatal(err) } - c := viper.AllSettings()["core"] - config := c.([]map[string]interface{})[0] - client, _ := gitlab.NewClient(config["token"].(string), gitlab.WithBaseURL(config["host"].(string)+"/api/v4")) + host := viper.GetString("core.host") + token := viper.GetString("core.token") + + client, _ := gitlab.NewClient(token, gitlab.WithBaseURL(host+"/api/v4")) u, _, err := client.Users.CurrentUser() if err != nil { log.Fatal(err) } - lab.Init(config["host"].(string), u.Username, config["token"].(string), false) + lab.Init(host, u.Username, token, false) code := m.Run() diff --git a/cmd/snippet_browse_test.go b/cmd/snippet_browse_test.go index 4877b709..8aafc3e4 100644 --- a/cmd/snippet_browse_test.go +++ b/cmd/snippet_browse_test.go @@ -9,7 +9,7 @@ import ( func Test_snippetBrowse(t *testing.T) { viper.SetConfigName("lab") - viper.SetConfigType("hcl") + viper.SetConfigType("toml") viper.AddConfigPath("../testdata") err := viper.ReadInConfig() if err != nil { diff --git a/internal/config/config.go b/internal/config/config.go index dca3cc26..d9505fc7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,8 +2,11 @@ package config import ( "bufio" + "bytes" "fmt" "io" + "io/ioutil" + "log" "net/url" "os" "strings" @@ -86,3 +89,58 @@ func CI() (string, string, string) { return ciHost, ciUser, ciToken } + +// ConvertHCLtoTOML() converts an .hcl file to a .toml file +func ConvertHCLtoTOML(oldpath string, newpath string, file string) { + oldconfig := oldpath + "/" + file + ".hcl" + newconfig := newpath + "/" + file + ".toml" + + _, err := os.Stat(oldconfig) + if os.IsNotExist(err) { + fmt.Println("oldfile not found", oldconfig) + return + } + + _, err = os.Stat(newconfig) + if err == nil { + fmt.Println("newfile found", newconfig) + return + } + + // read in the old config HCL file and write out the new TOML file + viper.Reset() + viper.SetConfigName("lab") + viper.SetConfigType("hcl") + viper.AddConfigPath(oldpath) + viper.ReadInConfig() + viper.SetConfigType("toml") + viper.WriteConfigAs(newconfig) + + // delete the old config HCL file + err = os.Remove(oldconfig) + if err != nil { + fmt.Println("Warning: Could not delete old config file", oldconfig) + } + + // HACK + // viper HCL parsing is broken and simply translating it to a TOML file + // results in a broken toml file. The issue is that there are double + // square brackets for each entry where there should be single + // brackets. Note: this hack only works because the config file is + // simple and doesn't contain deeply embedded config entries. + text, err := ioutil.ReadFile(newconfig) + if err != nil { + log.Fatal(err) + } + + text = bytes.Replace(text, []byte("[["), []byte("["), -1) + text = bytes.Replace(text, []byte("]]"), []byte("]"), -1) + + if err = ioutil.WriteFile(newconfig, text, 0666); err != nil { + fmt.Println(err) + os.Exit(1) + } + // END HACK + + fmt.Println("INFO: Converted old config", oldconfig, "to new config", newconfig) +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 855659c0..d90d7402 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -37,7 +37,7 @@ func TestNewConfig(t *testing.T) { readPassword = oldreadPassword }() - err := New(path.Join(testconf, "lab.hcl"), &buf) + err := New(path.Join(testconf, "lab.toml"), &buf) if err != nil { t.Fatal(err) } @@ -58,7 +58,7 @@ func TestNewConfig(t *testing.T) { assert.Contains(t, out, "Enter GitLab host (default: https://gitlab.com): ") assert.Contains(t, out, "Create a token here: https://gitlab.zaquestion.io/profile/personal_access_tokens\nEnter default GitLab token (scope: api):") - cfg, err := os.Open(path.Join(testconf, "lab.hcl")) + cfg, err := os.Open(path.Join(testconf, "lab.toml")) if err != nil { t.Fatal(err) } @@ -67,11 +67,11 @@ func TestNewConfig(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, `"core" = { - "host" = "https://gitlab.zaquestion.io" - - "token" = "abcde12345" -}`, string(cfgData)) + assert.Equal(t, ` +[core] + host = "https://gitlab.zaquestion.io" + token = "abcde12345" +`, string(cfgData)) }) os.RemoveAll(testconf) viper.Reset() @@ -105,7 +105,7 @@ func TestNewConfigHostOverride(t *testing.T) { }() var buf bytes.Buffer - err := New(path.Join(testconf, "lab.hcl"), &buf) + err := New(path.Join(testconf, "lab.toml"), &buf) if err != nil { t.Fatal(err) } @@ -126,7 +126,7 @@ func TestNewConfigHostOverride(t *testing.T) { assert.NotContains(t, out, "Enter GitLab host") assert.Contains(t, out, "Create a token here: https://gitlab2.zaquestion.io/profile/personal_access_tokens\nEnter default GitLab token (scope: api):") - cfg, err := os.Open(path.Join(testconf, "lab.hcl")) + cfg, err := os.Open(path.Join(testconf, "lab.toml")) if err != nil { t.Fatal(err) } @@ -135,11 +135,11 @@ func TestNewConfigHostOverride(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, `"core" = { - "host" = "https://gitlab2.zaquestion.io" - - "token" = "abcde12345" -}`, string(cfgData)) + assert.Equal(t, ` +[core] + host = "https://gitlab2.zaquestion.io" + token = "abcde12345" +`, string(cfgData)) }) os.RemoveAll(testconf) viper.Reset() diff --git a/internal/gitlab/gitlab_test.go b/internal/gitlab/gitlab_test.go index f1a7b0dd..f0b0666c 100644 --- a/internal/gitlab/gitlab_test.go +++ b/internal/gitlab/gitlab_test.go @@ -25,22 +25,22 @@ func TestMain(m *testing.M) { } viper.SetConfigName("lab") - viper.SetConfigType("hcl") + viper.SetConfigType("toml") viper.AddConfigPath(".") err = viper.ReadInConfig() if err != nil { log.Fatal(err) } - c := viper.AllSettings()["core"] - config := c.([]map[string]interface{})[0] + host := viper.GetString("core.host") + token := viper.GetString("core.token") - lab, _ := gitlab.NewClient(config["token"].(string), gitlab.WithBaseURL(config["host"].(string)+"/api/v4")) + lab, _ := gitlab.NewClient(token, gitlab.WithBaseURL(host+"/api/v4")) u, _, err := lab.Users.CurrentUser() if err != nil { log.Fatal(err) } - Init(config["host"].(string), u.Username, config["token"].(string), false) + Init(host, u.Username, token, false) code := m.Run() diff --git a/main.go b/main.go index 9a875e68..a221f312 100644 --- a/main.go +++ b/main.go @@ -31,17 +31,29 @@ func loadConfig() (string, string, string, bool) { if confpath == "" { confpath = path.Join(home, ".config") } - if _, err := os.Stat(confpath); os.IsNotExist(err) { - os.Mkdir(confpath, 0700) + labconfpath := confpath+"/lab" + if _, err := os.Stat(labconfpath); os.IsNotExist(err) { + os.MkdirAll(labconfpath, 0700) } - viper.SetConfigName("lab") - viper.SetConfigType("hcl") - viper.AddConfigPath(".") - viper.AddConfigPath(confpath) + // convert old hcl files to toml format + + config.ConvertHCLtoTOML(".", ".", "lab") + config.ConvertHCLtoTOML(confpath, labconfpath, "lab") + var labgitDir string gitDir, err := git.GitDir() if err == nil { - viper.AddConfigPath(gitDir) + labgitDir = gitDir+"/lab" + config.ConvertHCLtoTOML(gitDir, labgitDir, "lab") + config.ConvertHCLtoTOML(labgitDir, labgitDir, "show_metadata") + } + + viper.SetConfigName("lab") + viper.SetConfigType("toml") + viper.AddConfigPath(".") + viper.AddConfigPath(labconfpath) + if labgitDir != "" { + viper.AddConfigPath(labgitDir) } viper.SetEnvPrefix("LAB") @@ -66,7 +78,7 @@ func loadConfig() (string, string, string, bool) { } if _, ok := viper.ReadInConfig().(viper.ConfigFileNotFoundError); ok { - if err := config.New(path.Join(confpath, "lab.hcl"), os.Stdin); err != nil { + if err := config.New(path.Join(labconfpath, "lab.toml"), os.Stdin); err != nil { log.Fatal(err) } diff --git a/testdata/lab.hcl b/testdata/lab.hcl deleted file mode 100644 index abf18cc2..00000000 --- a/testdata/lab.hcl +++ /dev/null @@ -1,4 +0,0 @@ -"core" = { - "host" = "https://gitlab.com" - "token" = "uDz6czYV412zK-xC5mUu" -} diff --git a/testdata/lab.toml b/testdata/lab.toml new file mode 100644 index 00000000..c44daeb0 --- /dev/null +++ b/testdata/lab.toml @@ -0,0 +1,3 @@ +[core] + host = "https://gitlab.com" + token = "uDz6czYV412zK-xC5mUu" From 2c6e0d8d02bf5a868b34b4e854c64bb91ab54f22 Mon Sep 17 00:00:00 2001 From: Prarit Bhargava Date: Wed, 2 Sep 2020 20:53:30 -0400 Subject: [PATCH 2/5] config: Rework the config code The existing config code uses a lot of workarounds for viper's issues with the HCL format. With the move TOML format for the config files, a lot of this code can be eliminated by using standard viper calls. Rework the config code to remove workarounds and reorganize the config function. Suggested-by: Zaq? Wiedmann Signed-off-by: Prarit Bhargava --- internal/config/config.go | 2 - main.go | 108 ++++++++++---------------------------- 2 files changed, 29 insertions(+), 81 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index d9505fc7..cace9186 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -97,13 +97,11 @@ func ConvertHCLtoTOML(oldpath string, newpath string, file string) { _, err := os.Stat(oldconfig) if os.IsNotExist(err) { - fmt.Println("oldfile not found", oldconfig) return } _, err = os.Stat(newconfig) if err == nil { - fmt.Println("newfile found", newconfig) return } diff --git a/main.go b/main.go index a221f312..27267cb7 100644 --- a/main.go +++ b/main.go @@ -21,23 +21,32 @@ import ( var version = "master" func loadConfig() (string, string, string, bool) { + + // Attempt to auto-configure for GitLab CI. + // Always do this before reading in the config file o/w CI will end up + // with the wrong data. + host, user, token := config.CI() + if host != "" && user != "" && token != "" { + return host, user, token, false + } + + // Try to find XDG_CONFIG_HOME which is declared in XDG base directory + // specification and use it's location as the config directory home, err := os.UserHomeDir() if err != nil { log.Fatal(err) } - - // Try XDG_CONFIG_HOME which is declared in XDG base directory specification confpath := os.Getenv("XDG_CONFIG_HOME") if confpath == "" { confpath = path.Join(home, ".config") } labconfpath := confpath+"/lab" - if _, err := os.Stat(labconfpath); os.IsNotExist(err) { - os.MkdirAll(labconfpath, 0700) + if _, err := os.Stat(confpath); os.IsNotExist(err) { + os.MkdirAll(confpath, 0700) } - // convert old hcl files to toml format - + // Convert old hcl files to toml format. + // NO NEW FILES SHOULD BE ADDED BELOW. config.ConvertHCLtoTOML(".", ".", "lab") config.ConvertHCLtoTOML(confpath, labconfpath, "lab") var labgitDir string @@ -60,92 +69,33 @@ func loadConfig() (string, string, string, bool) { viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() - var tlsSkipVerify bool - tlsSkipVerify = viper.GetBool("tls.skip_verify") - - host, user, token := viper.GetString("core.host"), viper.GetString("core.user"), viper.GetString("core.token") - if host != "" && user != "" && token != "" { - return host, user, token, tlsSkipVerify - } else if host != "" && token != "" { - user = getUser(host, token, tlsSkipVerify) - return host, user, token, tlsSkipVerify - } - - // Attempt to auto-configure for GitLab CI - host, user, token = config.CI() - if host != "" && user != "" && token != "" { - return host, user, token, tlsSkipVerify - } - if _, ok := viper.ReadInConfig().(viper.ConfigFileNotFoundError); ok { - if err := config.New(path.Join(labconfpath, "lab.toml"), os.Stdin); err != nil { + err := config.New(path.Join(labconfpath, "lab.toml"), os.Stdin) + if err != nil { log.Fatal(err) } - if err := viper.ReadInConfig(); err != nil { + err = viper.ReadInConfig() + if err != nil { log.Fatal(err) } } - c := viper.AllSettings() - var cfg map[string]interface{} - switch v := c["core"].(type) { - // Most run this is the type - case []map[string]interface{}: - cfg = v[0] - // On the first run when the cfg is created it comes in as this type - // for whatever reason - case map[string]interface{}: - cfg = v - } + host = viper.GetString("core.host") + user = viper.GetString("core.user") + token = viper.GetString("core.token") + tlsSkipVerify := viper.GetBool("tls.skip_verify") - for _, v := range []string{"host", "token"} { - if cv, ok := cfg[v]; !ok { - log.Println(cv) - log.Fatalf("missing config value core.%s in %s", v, viper.ConfigFileUsed()) - } + if host != "" && user != "" && token != "" { + return host, user, token, tlsSkipVerify } - var tls map[string]interface{} - switch v := c["tls"].(type) { - // Most run this is the type - case []map[string]interface{}: - tls = v[0] - // On the first run when the cfg is created it comes in as this type - // for whatever reason - case map[string]interface{}: - tls = v - } - if v, ok := tls["skip_verify"]; ok { - tlsSkipVerify = v.(bool) + user = getUser(host, token, tlsSkipVerify) + if strings.TrimSpace(os.Getenv("LAB_CORE_TOKEN")) == "" && strings.TrimSpace(os.Getenv("LAB_CORE_HOST")) == "" { + viper.Set("core.user", user) + viper.WriteConfig() } - // Set environment overrides - // Note: the code below that uses `cfg["host"]` to access these values - // is tough to simplify since cfg["host"] is accessing the array "core" - // and viper.GetString("core.host") is expecting a non-array so it - // doens't match - if v := viper.GetString("core.host"); v != "" { - cfg["host"] = v - } - if v := viper.GetString("core.token"); v != "" { - cfg["token"] = v - } - if v := viper.GetString("core.user"); v != "" { - cfg["user"] = v - } - if v := viper.Get("tls.skip_verify"); v != nil { - tlsSkipVerify = v.(string) == "true" - } - host = cfg["host"].(string) - token = cfg["token"].(string) - if v, ok := cfg["user"]; ok { - user = v.(string) - } - if user == "" { - user = getUser(host, token, tlsSkipVerify) - } - viper.Set("core.user", user) return host, user, token, tlsSkipVerify } From 8dda6ffc11faba3abfc79d9a9373cbc49eb7d97b Mon Sep 17 00:00:00 2001 From: Prarit Bhargava Date: Wed, 2 Sep 2020 20:59:25 -0400 Subject: [PATCH 3/5] config: Move config init code Move the config initialization code from main.go to internal/config/config.go. Suggested-by: Bruno Meneguele Signed-off-by: Prarit Bhargava --- internal/config/config.go | 101 +++++++++++++++++++++++++++++++++++++ main.go | 103 +------------------------------------- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index cace9186..7c79399f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,16 +3,21 @@ package config import ( "bufio" "bytes" + "crypto/tls" "fmt" "io" "io/ioutil" "log" + "net/http" "net/url" "os" + "path" "strings" "syscall" "github.com/spf13/viper" + gitlab "github.com/xanzy/go-gitlab" + "github.com/zaquestion/lab/internal/git" "golang.org/x/crypto/ssh/terminal" ) @@ -142,3 +147,99 @@ func ConvertHCLtoTOML(oldpath string, newpath string, file string) { fmt.Println("INFO: Converted old config", oldconfig, "to new config", newconfig) } + +func getUser(host, token string, skipVerify bool) string { + httpClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: skipVerify, + }, + }, + } + lab, _ := gitlab.NewClient(token, gitlab.WithHTTPClient(httpClient), gitlab.WithBaseURL(host+"/api/v4")) + u, _, err := lab.Users.CurrentUser() + if err != nil { + log.Fatal(err) + } + return u.Username +} + +// LoadConfig() loads the main config file +func LoadConfig() (string, string, string, bool) { + + // Attempt to auto-configure for GitLab CI. + // Always do this before reading in the config file o/w CI will end up + // with the wrong data. + host, user, token := CI() + if host != "" && user != "" && token != "" { + return host, user, token, false + } + + // Try to find XDG_CONFIG_HOME which is declared in XDG base directory + // specification and use it's location as the config directory + home, err := os.UserHomeDir() + if err != nil { + log.Fatal(err) + } + confpath := os.Getenv("XDG_CONFIG_HOME") + if confpath == "" { + confpath = path.Join(home, ".config") + } + labconfpath := confpath + "/lab" + if _, err := os.Stat(labconfpath); os.IsNotExist(err) { + os.MkdirAll(labconfpath, 0700) + } + + // Convert old hcl files to toml format. + // NO NEW FILES SHOULD BE ADDED BELOW. + ConvertHCLtoTOML(".", ".", "lab") + ConvertHCLtoTOML(confpath, labconfpath, "lab") + var labgitDir string + gitDir, err := git.GitDir() + if err == nil { + labgitDir = gitDir + "/lab" + ConvertHCLtoTOML(gitDir, labgitDir, "lab") + ConvertHCLtoTOML(labgitDir, labgitDir, "show_metadata") + } + + viper.SetConfigName("lab") + viper.SetConfigType("toml") + viper.AddConfigPath(".") + viper.AddConfigPath(labconfpath) + if labgitDir != "" { + viper.AddConfigPath(labgitDir) + } + + viper.SetEnvPrefix("LAB") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + + if _, ok := viper.ReadInConfig().(viper.ConfigFileNotFoundError); ok { + err := New(path.Join(labconfpath, "lab.toml"), os.Stdin) + if err != nil { + log.Fatal(err) + } + + err = viper.ReadInConfig() + if err != nil { + log.Fatal(err) + } + } + + host = viper.GetString("core.host") + user = viper.GetString("core.user") + token = viper.GetString("core.token") + tlsSkipVerify := viper.GetBool("tls.skip_verify") + + if host != "" && user != "" && token != "" { + return host, user, token, tlsSkipVerify + } + + user = getUser(host, token, tlsSkipVerify) + if strings.TrimSpace(os.Getenv("LAB_CORE_TOKEN")) == "" && strings.TrimSpace(os.Getenv("LAB_CORE_HOST")) == "" { + viper.Set("core.user", user) + viper.WriteConfig() + } + + return host, user, token, tlsSkipVerify +} diff --git a/main.go b/main.go index 27267cb7..5cb2775f 100644 --- a/main.go +++ b/main.go @@ -1,104 +1,19 @@ package main import ( - "crypto/tls" "log" - "net/http" "os" - "path" - "strings" "github.com/rsteube/carapace" "github.com/spf13/viper" - gitlab "github.com/xanzy/go-gitlab" "github.com/zaquestion/lab/cmd" "github.com/zaquestion/lab/internal/config" - "github.com/zaquestion/lab/internal/git" lab "github.com/zaquestion/lab/internal/gitlab" ) // version gets set on releases during build by goreleaser. var version = "master" -func loadConfig() (string, string, string, bool) { - - // Attempt to auto-configure for GitLab CI. - // Always do this before reading in the config file o/w CI will end up - // with the wrong data. - host, user, token := config.CI() - if host != "" && user != "" && token != "" { - return host, user, token, false - } - - // Try to find XDG_CONFIG_HOME which is declared in XDG base directory - // specification and use it's location as the config directory - home, err := os.UserHomeDir() - if err != nil { - log.Fatal(err) - } - confpath := os.Getenv("XDG_CONFIG_HOME") - if confpath == "" { - confpath = path.Join(home, ".config") - } - labconfpath := confpath+"/lab" - if _, err := os.Stat(confpath); os.IsNotExist(err) { - os.MkdirAll(confpath, 0700) - } - - // Convert old hcl files to toml format. - // NO NEW FILES SHOULD BE ADDED BELOW. - config.ConvertHCLtoTOML(".", ".", "lab") - config.ConvertHCLtoTOML(confpath, labconfpath, "lab") - var labgitDir string - gitDir, err := git.GitDir() - if err == nil { - labgitDir = gitDir+"/lab" - config.ConvertHCLtoTOML(gitDir, labgitDir, "lab") - config.ConvertHCLtoTOML(labgitDir, labgitDir, "show_metadata") - } - - viper.SetConfigName("lab") - viper.SetConfigType("toml") - viper.AddConfigPath(".") - viper.AddConfigPath(labconfpath) - if labgitDir != "" { - viper.AddConfigPath(labgitDir) - } - - viper.SetEnvPrefix("LAB") - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() - - if _, ok := viper.ReadInConfig().(viper.ConfigFileNotFoundError); ok { - err := config.New(path.Join(labconfpath, "lab.toml"), os.Stdin) - if err != nil { - log.Fatal(err) - } - - err = viper.ReadInConfig() - if err != nil { - log.Fatal(err) - } - } - - host = viper.GetString("core.host") - user = viper.GetString("core.user") - token = viper.GetString("core.token") - tlsSkipVerify := viper.GetBool("tls.skip_verify") - - if host != "" && user != "" && token != "" { - return host, user, token, tlsSkipVerify - } - - user = getUser(host, token, tlsSkipVerify) - if strings.TrimSpace(os.Getenv("LAB_CORE_TOKEN")) == "" && strings.TrimSpace(os.Getenv("LAB_CORE_HOST")) == "" { - viper.Set("core.user", user) - viper.WriteConfig() - } - - return host, user, token, tlsSkipVerify -} - func loadTLSCerts() string { c := viper.AllSettings() @@ -126,28 +41,12 @@ func loadTLSCerts() string { return tls["ca_file"].(string) } -func getUser(host, token string, skipVerify bool) string { - httpClient := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: skipVerify, - }, - }, - } - lab, _ := gitlab.NewClient(token, gitlab.WithHTTPClient(httpClient), gitlab.WithBaseURL(host+"/api/v4")) - u, _, err := lab.Users.CurrentUser() - if err != nil { - log.Fatal(err) - } - return u.Username -} - func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) cmd.Version = version if !skipInit() { ca := loadTLSCerts() - h, u, t, skipVerify := loadConfig() + h, u, t, skipVerify := config.LoadConfig() if ca != "" { lab.InitWithCustomCA(h, u, t, ca) From 51d9b7d593103a922ab1bd248cc49c46acccc374 Mon Sep 17 00:00:00 2001 From: Prarit Bhargava Date: Wed, 2 Sep 2020 21:55:47 -0400 Subject: [PATCH 4/5] config: Fix and clean up certificate code As noted in issue #152 ("Accept self signed certificates"), the certificate code is no longer working. This is because the certificate code was executed before the configuration was loaded. The code is need of a massive clean up, and with the transition of the config files to TOML format, the code to get the certificate can easily be incorporated into the main configuration code. Fix and clean up the certifcate code. Signed-off-by: Prarit Bhargava --- internal/config/config.go | 12 +++++++----- main.go | 31 +------------------------------ 2 files changed, 8 insertions(+), 35 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 7c79399f..8fb699db 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -164,15 +164,16 @@ func getUser(host, token string, skipVerify bool) string { return u.Username } -// LoadConfig() loads the main config file -func LoadConfig() (string, string, string, bool) { +// LoadConfig() loads the main config file and returns a tuple of +// host, user, token, ca_file, skipVerify +func LoadConfig() (string, string, string, string, bool) { // Attempt to auto-configure for GitLab CI. // Always do this before reading in the config file o/w CI will end up // with the wrong data. host, user, token := CI() if host != "" && user != "" && token != "" { - return host, user, token, false + return host, user, token, "", false } // Try to find XDG_CONFIG_HOME which is declared in XDG base directory @@ -230,9 +231,10 @@ func LoadConfig() (string, string, string, bool) { user = viper.GetString("core.user") token = viper.GetString("core.token") tlsSkipVerify := viper.GetBool("tls.skip_verify") + ca_file := viper.GetString("tls.ca_file") if host != "" && user != "" && token != "" { - return host, user, token, tlsSkipVerify + return host, user, token, ca_file, tlsSkipVerify } user = getUser(host, token, tlsSkipVerify) @@ -241,5 +243,5 @@ func LoadConfig() (string, string, string, bool) { viper.WriteConfig() } - return host, user, token, tlsSkipVerify + return host, user, token, ca_file, tlsSkipVerify } diff --git a/main.go b/main.go index 5cb2775f..5e667277 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "os" "github.com/rsteube/carapace" - "github.com/spf13/viper" "github.com/zaquestion/lab/cmd" "github.com/zaquestion/lab/internal/config" lab "github.com/zaquestion/lab/internal/gitlab" @@ -14,39 +13,11 @@ import ( // version gets set on releases during build by goreleaser. var version = "master" -func loadTLSCerts() string { - c := viper.AllSettings() - - var tls map[string]interface{} - switch v := c["tls"].(type) { - // Most run this is the type - case []map[string]interface{}: - tls = v[0] - // On the first run when the cfg is created it comes in as this type - // for whatever reason - case map[string]interface{}: - tls = v - } - - for _, v := range []string{"ca_file"} { - if _, ok := tls[v]; !ok { - return "" - } - } - - if v := viper.GetString("tls.ca_file"); v != "" { - tls["ca_file"] = v - } - - return tls["ca_file"].(string) -} - func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) cmd.Version = version if !skipInit() { - ca := loadTLSCerts() - h, u, t, skipVerify := config.LoadConfig() + h, u, t, ca, skipVerify := config.LoadConfig() if ca != "" { lab.InitWithCustomCA(h, u, t, ca) From 6d8163f0904cf53ef73378dd70a62f8c952f6496 Mon Sep 17 00:00:00 2001 From: Zaq? Wiedmann Date: Sat, 5 Sep 2020 15:30:44 -0700 Subject: [PATCH 5/5] config: add Convert test note: confpath convert was reordered to happen first to mitigate a very unlikely edge case where lab is run in `.config` causing the converted toml file to land in `.config` instead of `.config/lab/` --- .travis.yml | 2 +- README.md | 2 +- internal/config/config.go | 11 +++---- internal/config/config_test.go | 52 +++++++++++++++++++++++++++------- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0bf01adf..53031927 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ os: - linux go: - - 1.13.x + - 1.15.x # use containers which run faster and have cache sudo: false diff --git a/README.md b/README.md index eed7e4f4..f69d1236 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Head to the [releases](https://github.com/zaquestion/lab/releases) page and down ### Source Required -* [Go 1.13+](https://golang.org/doc/install) +* [Go 1.15+](https://golang.org/doc/install) ``` git clone git@github.com:zaquestion/lab diff --git a/internal/config/config.go b/internal/config/config.go index 8fb699db..ec292841 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -100,13 +100,11 @@ func ConvertHCLtoTOML(oldpath string, newpath string, file string) { oldconfig := oldpath + "/" + file + ".hcl" newconfig := newpath + "/" + file + ".toml" - _, err := os.Stat(oldconfig) - if os.IsNotExist(err) { + if _, err := os.Stat(oldconfig); os.IsNotExist(err) { return } - _, err = os.Stat(newconfig) - if err == nil { + if _, err := os.Stat(newconfig); err == nil { return } @@ -120,8 +118,7 @@ func ConvertHCLtoTOML(oldpath string, newpath string, file string) { viper.WriteConfigAs(newconfig) // delete the old config HCL file - err = os.Remove(oldconfig) - if err != nil { + if err := os.Remove(oldconfig); err != nil { fmt.Println("Warning: Could not delete old config file", oldconfig) } @@ -193,8 +190,8 @@ func LoadConfig() (string, string, string, string, bool) { // Convert old hcl files to toml format. // NO NEW FILES SHOULD BE ADDED BELOW. - ConvertHCLtoTOML(".", ".", "lab") ConvertHCLtoTOML(confpath, labconfpath, "lab") + ConvertHCLtoTOML(".", ".", "lab") var labgitDir string gitDir, err := git.GitDir() if err == nil { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index d90d7402..3556d457 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "strings" "testing" @@ -16,10 +17,7 @@ import ( ) func TestNewConfig(t *testing.T) { - testconf, err := ioutil.TempDir("", "testconf-") - if err != nil { - t.Fatal(err) - } + testconf := t.TempDir() t.Run("create config", func(t *testing.T) { old := os.Stdout // keep backup of the real stdout @@ -73,15 +71,11 @@ func TestNewConfig(t *testing.T) { token = "abcde12345" `, string(cfgData)) }) - os.RemoveAll(testconf) viper.Reset() } func TestNewConfigHostOverride(t *testing.T) { - testconf, err := ioutil.TempDir("", "testconf-") - if err != nil { - t.Fatal(err) - } + testconf := t.TempDir() os.Setenv("LAB_CORE_HOST", "https://gitlab2.zaquestion.io") @@ -141,6 +135,44 @@ func TestNewConfigHostOverride(t *testing.T) { token = "abcde12345" `, string(cfgData)) }) - os.RemoveAll(testconf) viper.Reset() } + +func TestConvertHCLtoTOML(t *testing.T) { + tmpDir := t.TempDir() + oldCnfPath := filepath.Join(tmpDir, "lab.hcl") + newCnfPath := filepath.Join(tmpDir, "lab.toml") + oldCnf, err := os.Create(oldCnfPath) + if err != nil { + t.Fatal(err) + } + oldCnf.WriteString(`"core" = { + "host" = "https://gitlab.com" + "token" = "foobar" + "user" = "lab-testing" +}`) + + ConvertHCLtoTOML(tmpDir, tmpDir, "lab") + + _, err = os.Stat(oldCnfPath) + assert.True(t, os.IsNotExist(err)) + + _, err = os.Stat(newCnfPath) + assert.NoError(t, err) + + newCnf, err := os.Open(newCnfPath) + if err != nil { + t.Fatal(err) + } + cfgData, err := ioutil.ReadAll(newCnf) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, ` +[core] + host = "https://gitlab.com" + token = "foobar" + user = "lab-testing" +`, string(cfgData)) +}