From dee435514d7f717e4eb63b15a9d9fdb0722330ac Mon Sep 17 00:00:00 2001 From: Patrick Pichler Date: Mon, 10 Apr 2023 18:22:54 +0200 Subject: [PATCH] feat: switch config file to XDG conform location The config file is now located in an folder according to the XDG specification (`XDG_CONFIG_HOME`). Migration is performed automatically. This fixes #235. Signed-off-by: Patrick Pichler --- README.md | 28 ++++++++++++++-------- cmd/root.go | 60 ++++++++++++++++++++++++++++++++++++++++++------ go.mod | 2 ++ go.sum | 3 +++ pkg/util/util.go | 22 ++++++++++++++++++ 5 files changed, 98 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c6e2eda9a4..53ce2a083d 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ brew install k8sgpt **64 bit:** - + ``` curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.2/k8sgpt_amd64.rpm @@ -54,7 +54,7 @@ brew install k8sgpt ``` **64 bit:** - + ``` curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.2/k8sgpt_amd64.deb @@ -88,7 +88,7 @@ brew install k8sgpt When installing Homebrew on WSL or Linux, you may encounter the following error: ``` - ==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from a bottle and must be + ==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from a bottle and must be built from the source. k8sgpt Install Clang or run brew install gcc. ``` @@ -102,7 +102,7 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t ## Windows -* Download the latest Windows binaries of **k8sgpt** from the [Release](https://github.com/k8sgpt-ai/k8sgpt/releases) +* Download the latest Windows binaries of **k8sgpt** from the [Release](https://github.com/k8sgpt-ai/k8sgpt/releases) tab based on your system architecture. * Extract the downloaded package to your desired location. Configure the system *path* variable with the binary location @@ -117,7 +117,7 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t * Currently the default AI provider is OpenAI, you will need to generate an API key from [OpenAI](https://openai.com) * You can do this by running `k8sgpt generate` to open a browser link to generate it -* Run `k8sgpt auth` to set it in k8sgpt. +* Run `k8sgpt auth` to set it in k8sgpt. * You can provide the password directly using the `--password` flag. * Run `k8sgpt filters` to manage the active filters used by the analyzer. By default, all filters are executed during analysis. * Run `k8sgpt analyze` to run a scan. @@ -127,7 +127,7 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t ## Analyzers -K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but +K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but you will be able to write your own analyzers. ### Built in analyzers @@ -275,17 +275,25 @@ The Kubernetes system is trying to scale a StatefulSet named fake-deployment usi ## What about kubectl-ai? -The kubectl-ai [project](https://github.com/sozercan/kubectl-ai) uses AI to create manifests and apply them to the +The kubectl-ai [project](https://github.com/sozercan/kubectl-ai) uses AI to create manifests and apply them to the cluster. It is not what we are trying to do here, it is focusing on writing YAML manifests. -K8sgpt is focused on triaging and diagnosing issues in your cluster. It is a tool for SRE, Platform & DevOps engineers -to help them understand what is going on in their cluster. Cutting through the noise of logs and multiple tools to find +K8sgpt is focused on triaging and diagnosing issues in your cluster. It is a tool for SRE, Platform & DevOps engineers +to help them understand what is going on in their cluster. Cutting through the noise of logs and multiple tools to find the root cause of an issue. ## Configuration -`k8sgpt` stores config data in `~/.k8sgpt.yaml` the data is stored in plain text, including your OpenAI key. +`k8sgpt` stores config data in the `$XDG_CONFIG_HOME/k8sgpt/k8sgpt.yaml` file. The data is stored in plain text, including your OpenAI key. + +Config file locations: +| OS | Path | +|---------|--------------------------------------------------| +| MacOS | ~/Library/Application Support/k8sgpt/k8sgpt.yaml | +| Linux | ~/.config/k8sgpt/k8sgpt.yaml | +| Windows | %LOCALAPPDATA%/k8sgpt/k8sgpt.yaml | + ## Contributing diff --git a/cmd/root.go b/cmd/root.go index b76f1f4046..bc9eda0a47 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,10 +1,15 @@ package cmd import ( - "github.com/k8sgpt-ai/k8sgpt/cmd/serve" + "fmt" "os" "path/filepath" + "github.com/adrg/xdg" + "github.com/fatih/color" + "github.com/k8sgpt-ai/k8sgpt/cmd/serve" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" + "github.com/k8sgpt-ai/k8sgpt/cmd/analyze" "github.com/k8sgpt-ai/k8sgpt/cmd/auth" "github.com/k8sgpt-ai/k8sgpt/cmd/filters" @@ -43,6 +48,8 @@ func Execute(v string) { } func init() { + performConfigMigrationIfNeeded() + cobra.OnInitialize(initConfig) var kubeconfigPath string @@ -66,14 +73,12 @@ func initConfig() { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) + // the config will belocated under `~/.config/k8sgpt/k8sgpt.yaml` on linux + configDir := filepath.Join(xdg.ConfigHome, "k8sgpt") - // Search config in home directory with name ".k8sgpt.git" (without extension). - viper.AddConfigPath(home) + viper.AddConfigPath(configDir) viper.SetConfigType("yaml") - viper.SetConfigName(".k8sgpt") + viper.SetConfigName("k8sgpt") viper.SafeWriteConfig() } @@ -88,3 +93,44 @@ func initConfig() { // fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } + +func performConfigMigrationIfNeeded() { + oldConfig, err := getLegacyConfigFilePath() + cobra.CheckErr(err) + oldConfigExists, err := util.FileExists(oldConfig) + cobra.CheckErr(err) + + newConfig := getConfigFilePath() + newConfigExists, err := util.FileExists(newConfig) + cobra.CheckErr(err) + + configDir := filepath.Dir(newConfig) + err = util.EnsureDirExists(configDir) + cobra.CheckErr(err) + + if oldConfigExists && newConfigExists { + fmt.Fprintln(os.Stderr, color.RedString("Warning: Legacy config file at `%s` detected! This file will be ignored!", oldConfig)) + return + } + + if oldConfigExists && !newConfigExists { + fmt.Fprintln(os.Stderr, color.RedString("Performing config file migration from `%s` to `%s`", oldConfig, newConfig)) + + err = os.Rename(oldConfig, newConfig) + cobra.CheckErr(err) + } +} + +func getConfigFilePath() string { + return filepath.Join(xdg.ConfigHome, "k8sgpt", "k8sgpt.yaml") +} + +func getLegacyConfigFilePath() (string, error) { + home, err := os.UserHomeDir() + + if err != nil { + return "", err + } + + return filepath.Join(home, ".k8sgpt.yaml"), nil +} diff --git a/go.mod b/go.mod index 29cac8bcc6..baccb7d209 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,8 @@ require ( ) +require github.com/adrg/xdg v0.4.0 // indirect + require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect diff --git a/go.sum b/go.sum index 795f172b38..d5c81568bd 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0A github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -946,6 +948,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/util/util.go b/pkg/util/util.go index 3bd8f0d69b..1c72dcdddc 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -3,8 +3,10 @@ package util import ( "context" "encoding/base64" + "errors" "fmt" "math/rand" + "os" "regexp" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" @@ -150,3 +152,23 @@ func GetPodListByLabels(client k.Interface, return pods, nil } + +func FileExists(path string) (bool, error) { + if _, err := os.Stat(path); err == nil { + return true, nil + } else if errors.Is(err, os.ErrNotExist) { + return false, nil + } else { + return false, err + } +} + +func EnsureDirExists(dir string) error { + err := os.Mkdir(dir, 0755) + + if errors.Is(err, os.ErrExist) { + return nil + } + + return err +}