From b2e8adda333fbd508f0f01f2afcabc57bf9948c2 Mon Sep 17 00:00:00 2001 From: Thomas Schuetz Date: Sun, 2 Apr 2023 10:45:14 +0200 Subject: [PATCH 1/7] feat: first version of serve Signed-off-by: Thomas Schuetz --- cmd/root.go | 2 + cmd/serve/serve.go | 60 +++++++++++++++++++++++ pkg/server/main.go | 118 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 cmd/serve/serve.go create mode 100644 pkg/server/main.go diff --git a/cmd/root.go b/cmd/root.go index 9fe5fa6ab1..b76f1f4046 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + "github.com/k8sgpt-ai/k8sgpt/cmd/serve" "os" "path/filepath" @@ -53,6 +54,7 @@ func init() { rootCmd.AddCommand(filters.FiltersCmd) rootCmd.AddCommand(generate.GenerateCmd) rootCmd.AddCommand(integration.IntegrationCmd) + rootCmd.AddCommand(serve.ServeCmd) rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.yaml)") rootCmd.PersistentFlags().StringVar(&kubecontext, "kubecontext", "", "Kubernetes context to use. Only required if out-of-cluster.") rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", kubeconfigPath, "Path to a kubeconfig. Only required if out-of-cluster.") diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go new file mode 100644 index 0000000000..e6350902e3 --- /dev/null +++ b/cmd/serve/serve.go @@ -0,0 +1,60 @@ +package serve + +import ( + "fmt" + "github.com/fatih/color" + server2 "github.com/k8sgpt-ai/k8sgpt/pkg/server" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "os" +) + +var ( + port string + backend string + token string +) + +var ServeCmd = &cobra.Command{ + Use: "serve", + Short: "Runs k8sgpt as a server", + Long: `Runs k8sgpt as a server to allow for easy integration with other applications.`, + Run: func(cmd *cobra.Command, args []string) { + + backendType := viper.GetString("backend_type") + if backendType == "" { + color.Red("No backend set. Please run k8sgpt auth") + os.Exit(1) + } + + if backend != "" { + backendType = backend + } + + token := viper.GetString(fmt.Sprintf("%s_key", backendType)) + // check if nil + if token == "" { + color.Red("No %s key set. Please run k8sgpt auth", backendType) + os.Exit(1) + } + + server := server2.K8sGPTServer{ + Backend: backend, + Port: port, + Token: token, + } + + err := server.Serve() + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + // override the default backend if a flag is provided + }, +} + +func init() { + // add flag for backend + ServeCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to run the server on") + ServeCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider") +} diff --git a/pkg/server/main.go b/pkg/server/main.go new file mode 100644 index 0000000000..a760349f99 --- /dev/null +++ b/pkg/server/main.go @@ -0,0 +1,118 @@ +package server + +import ( + "context" + "encoding/json" + "fmt" + "github.com/fatih/color" + "github.com/k8sgpt-ai/k8sgpt/pkg/ai" + "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" + "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" + "github.com/schollz/progressbar/v3" + "github.com/spf13/viper" + "net/http" + "os" + "strings" +) + +type K8sGPTServer struct { + Port string + Backend string + Key string + Token string +} + +type Result struct { + Analysis []analyzer.Analysis `json:"analysis"` +} + +func (s *K8sGPTServer) analyzeHandler(w http.ResponseWriter, r *http.Request) { + namespace := r.URL.Query().Get("namespace") + ex := r.URL.Query().Get("explain") + + explain := false + + if ex == "true" { + explain = true + } + + output := Result{} + + var aiClient ai.IAI + switch s.Backend { + case "openai": + aiClient = &ai.OpenAIClient{} + if err := aiClient.Configure(s.Token, "english"); err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + default: + color.Red("Backend not supported") + os.Exit(1) + } + + ctx := context.Background() + // Get kubernetes client from viper + client := viper.Get("kubernetesClient").(*kubernetes.Client) + // Analysis configuration + config := &analyzer.AnalysisConfiguration{ + Namespace: namespace, + Explain: explain, + } + + var analysisResults *[]analyzer.Analysis = &[]analyzer.Analysis{} + if err := analyzer.RunAnalysis(ctx, []string{}, config, client, + aiClient, analysisResults); err != nil { + color.Red("Error: %v", err) + } + + fmt.Println(analysisResults) + if len(*analysisResults) == 0 { + fmt.Fprintf(w, "{ \"status\": \"OK\" }") + } + + var bar = progressbar.Default(int64(len(*analysisResults))) + if !explain { + bar.Clear() + } + var printOutput []analyzer.Analysis + + for _, analysis := range *analysisResults { + + if explain { + parsedText, err := analyzer.ParseViaAI(ctx, config, aiClient, analysis.Error) + if err != nil { + // Check for exhaustion + if strings.Contains(err.Error(), "status code: 429") { + fmt.Fprintf(w, "Exhausted API quota. Please try again later") + os.Exit(1) + } + color.Red("Error: %v", err) + continue + } + analysis.Details = parsedText + bar.Add(1) + } + printOutput = append(printOutput, analysis) + + analysis.Error = analysis.Error[0:] + output.Analysis = append(output.Analysis, analysis) + } + j, err := json.MarshalIndent(output, "", " ") + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + fmt.Fprintf(w, "%s", j) + +} + +func (s *K8sGPTServer) Serve() error { + http.HandleFunc("/analyze", s.analyzeHandler) + err := http.ListenAndServe(":"+s.Port, nil) + if err != nil { + fmt.Printf("error starting server: %s\n", err) + return err + } + return nil +} From adae2ef71d81431711c552159362336e496b21ee Mon Sep 17 00:00:00 2001 From: Thomas Schuetz Date: Wed, 12 Apr 2023 16:30:09 +0200 Subject: [PATCH 2/7] feat: updated api Signed-off-by: Thomas Schuetz --- cmd/serve/serve.go | 2 + go.mod | 1 + go.sum | 1 + pkg/server/main.go | 122 ++++++++++++++++++++++++--------------------- 4 files changed, 68 insertions(+), 58 deletions(-) diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go index e6350902e3..c00aefe70a 100644 --- a/cmd/serve/serve.go +++ b/cmd/serve/serve.go @@ -44,6 +44,8 @@ var ServeCmd = &cobra.Command{ Token: token, } + fmt.Println(server) + err := server.Serve() if err != nil { color.Red("Error: %v", err) diff --git a/go.mod b/go.mod index 2e6b3ce7b7..7df99fe4a5 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( github.com/aquasecurity/trivy-operator v0.13.0 github.com/fatih/color v1.15.0 + github.com/julienschmidt/httprouter v1.3.0 github.com/magiconair/properties v1.8.7 github.com/mittwald/go-helm-client v0.12.1 github.com/sashabaranov/go-openai v1.7.0 diff --git a/go.sum b/go.sum index 24fb5fcd61..39c6e29a15 100644 --- a/go.sum +++ b/go.sum @@ -409,6 +409,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= diff --git a/pkg/server/main.go b/pkg/server/main.go index a760349f99..ade7012bb6 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -2,17 +2,14 @@ package server import ( "context" - "encoding/json" "fmt" "github.com/fatih/color" "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" + "github.com/k8sgpt-ai/k8sgpt/pkg/analysis" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" - "github.com/schollz/progressbar/v3" "github.com/spf13/viper" "net/http" "os" - "strings" ) type K8sGPTServer struct { @@ -20,90 +17,92 @@ type K8sGPTServer struct { Backend string Key string Token string + Output string } type Result struct { - Analysis []analyzer.Analysis `json:"analysis"` + Analysis []analysis.Analysis `json:"analysis"` } func (s *K8sGPTServer) analyzeHandler(w http.ResponseWriter, r *http.Request) { namespace := r.URL.Query().Get("namespace") - ex := r.URL.Query().Get("explain") + explain := getBoolParam(r.URL.Query().Get("explain")) + anonymize := getBoolParam(r.URL.Query().Get("anonymize")) + nocache := getBoolParam(r.URL.Query().Get("nocache")) + language := r.URL.Query().Get("language") + + // get ai configuration + var configAI ai.AIConfiguration + err := viper.UnmarshalKey("ai", &configAI) + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } - explain := false + if len(configAI.Providers) == 0 { + color.Red("Error: AI provider not specified in configuration. Please run k8sgpt auth") + os.Exit(1) + } - if ex == "true" { - explain = true + var aiProvider ai.AIProvider + for _, provider := range configAI.Providers { + if s.Backend == provider.Name { + aiProvider = provider + break + } } - output := Result{} + if aiProvider.Name == "" { + color.Red("Error: AI provider %s not specified in configuration. Please run k8sgpt auth", s.Backend) + os.Exit(1) + } - var aiClient ai.IAI - switch s.Backend { - case "openai": - aiClient = &ai.OpenAIClient{} - if err := aiClient.Configure(s.Token, "english"); err != nil { - color.Red("Error: %v", err) - os.Exit(1) - } - default: - color.Red("Backend not supported") + aiClient := ai.NewClient(aiProvider.Name) + if err := aiClient.Configure(aiProvider.Password, aiProvider.Model, language); err != nil { + color.Red("Error: %v", err) os.Exit(1) } ctx := context.Background() // Get kubernetes client from viper - client := viper.Get("kubernetesClient").(*kubernetes.Client) - // Analysis configuration - config := &analyzer.AnalysisConfiguration{ + + kubecontext := viper.GetString("kubecontext") + kubeconfig := viper.GetString("kubeconfig") + client, err := kubernetes.NewClient(kubecontext, kubeconfig) + if err != nil { + color.Red("Error initialising kubernetes client: %v", err) + os.Exit(1) + } + + config := &analysis.Analysis{ Namespace: namespace, Explain: explain, + AIClient: aiClient, + Client: client, + Context: ctx, + NoCache: nocache, } - var analysisResults *[]analyzer.Analysis = &[]analyzer.Analysis{} - if err := analyzer.RunAnalysis(ctx, []string{}, config, client, - aiClient, analysisResults); err != nil { + err = config.RunAnalysis() + if err != nil { color.Red("Error: %v", err) + os.Exit(1) } - fmt.Println(analysisResults) - if len(*analysisResults) == 0 { - fmt.Fprintf(w, "{ \"status\": \"OK\" }") - } - - var bar = progressbar.Default(int64(len(*analysisResults))) - if !explain { - bar.Clear() - } - var printOutput []analyzer.Analysis - - for _, analysis := range *analysisResults { - - if explain { - parsedText, err := analyzer.ParseViaAI(ctx, config, aiClient, analysis.Error) - if err != nil { - // Check for exhaustion - if strings.Contains(err.Error(), "status code: 429") { - fmt.Fprintf(w, "Exhausted API quota. Please try again later") - os.Exit(1) - } - color.Red("Error: %v", err) - continue - } - analysis.Details = parsedText - bar.Add(1) + if explain { + err := config.GetAIResults(s.Output, anonymize) + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) } - printOutput = append(printOutput, analysis) - - analysis.Error = analysis.Error[0:] - output.Analysis = append(output.Analysis, analysis) } - j, err := json.MarshalIndent(output, "", " ") + + output, err := config.JsonOutput() if err != nil { color.Red("Error: %v", err) os.Exit(1) } - fmt.Fprintf(w, "%s", j) + fmt.Fprintf(w, string(output)) } @@ -116,3 +115,10 @@ func (s *K8sGPTServer) Serve() error { } return nil } + +func getBoolParam(param string) bool { + if param == "true" { + return true + } + return false +} From 9157d4dd1312bf75b336beb0e097422b303d22f1 Mon Sep 17 00:00:00 2001 From: Thomas Schuetz Date: Thu, 13 Apr 2023 08:11:56 +0200 Subject: [PATCH 3/7] feat: unified cmd and api Signed-off-by: Thomas Schuetz --- cmd/analyze/analyze.go | 55 ++------------------------------------- cmd/serve/serve.go | 2 -- pkg/ai/noopai.go | 6 +++-- pkg/ai/openai.go | 10 ++++--- pkg/analysis/analysis.go | 56 +++++++++++++++++++++++++++++++++++++++- pkg/server/main.go | 56 +++------------------------------------- pkg/util/util.go | 4 +++ 7 files changed, 74 insertions(+), 115 deletions(-) diff --git a/cmd/analyze/analyze.go b/cmd/analyze/analyze.go index a1b371a549..39ced28bd1 100644 --- a/cmd/analyze/analyze.go +++ b/cmd/analyze/analyze.go @@ -1,16 +1,12 @@ package analyze import ( - "context" "fmt" "os" "github.com/fatih/color" - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/analysis" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var ( @@ -33,60 +29,13 @@ var AnalyzeCmd = &cobra.Command{ provide you with a list of issues that need to be resolved`, Run: func(cmd *cobra.Command, args []string) { - // get ai configuration - var configAI ai.AIConfiguration - err := viper.UnmarshalKey("ai", &configAI) + // AnalysisResult configuration + config, err := analysis.NewAnalysis(backend, language, filters, namespace, nocache, explain) if err != nil { color.Red("Error: %v", err) os.Exit(1) } - if len(configAI.Providers) == 0 { - color.Red("Error: AI provider not specified in configuration. Please run k8sgpt auth") - os.Exit(1) - } - - var aiProvider ai.AIProvider - for _, provider := range configAI.Providers { - if backend == provider.Name { - aiProvider = provider - break - } - } - - if aiProvider.Name == "" { - color.Red("Error: AI provider %s not specified in configuration. Please run k8sgpt auth", backend) - os.Exit(1) - } - - aiClient := ai.NewClient(aiProvider.Name) - if err := aiClient.Configure(aiProvider.Password, aiProvider.Model, language); err != nil { - color.Red("Error: %v", err) - os.Exit(1) - } - - ctx := context.Background() - // Get kubernetes client from viper - - kubecontext := viper.GetString("kubecontext") - kubeconfig := viper.GetString("kubeconfig") - client, err := kubernetes.NewClient(kubecontext, kubeconfig) - if err != nil { - color.Red("Error initialising kubernetes client: %v", err) - os.Exit(1) - } - - // AnalysisResult configuration - config := &analysis.Analysis{ - Namespace: namespace, - NoCache: nocache, - Filters: filters, - Explain: explain, - AIClient: aiClient, - Client: client, - Context: ctx, - } - err = config.RunAnalysis() if err != nil { color.Red("Error: %v", err) diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go index c00aefe70a..e6350902e3 100644 --- a/cmd/serve/serve.go +++ b/cmd/serve/serve.go @@ -44,8 +44,6 @@ var ServeCmd = &cobra.Command{ Token: token, } - fmt.Println(server) - err := server.Serve() if err != nil { color.Red("Error: %v", err) diff --git a/pkg/ai/noopai.go b/pkg/ai/noopai.go index b544109033..8ea24d8516 100644 --- a/pkg/ai/noopai.go +++ b/pkg/ai/noopai.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "fmt" "github.com/fatih/color" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/spf13/viper" "strings" ) @@ -33,6 +34,7 @@ func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, nocache bool) inputKey := strings.Join(prompt, " ") // Check for cached data sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey)) + cacheKey := util.GetCacheKey(a.GetName(), sEnc) response, err := a.GetCompletion(ctx, inputKey) if err != nil { @@ -40,8 +42,8 @@ func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, nocache bool) return "", err } - if !viper.IsSet(sEnc) { - viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response))) + if !viper.IsSet(cacheKey) { + viper.Set(cacheKey, base64.StdEncoding.EncodeToString([]byte(response))) if err := viper.WriteConfig(); err != nil { color.Red("error writing config: %v", err) return "", nil diff --git a/pkg/ai/openai.go b/pkg/ai/openai.go index 91a70c474e..4ffca36506 100644 --- a/pkg/ai/openai.go +++ b/pkg/ai/openai.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "errors" "fmt" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" "strings" "github.com/fatih/color" @@ -58,10 +59,11 @@ func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, nocache bool) inputKey := strings.Join(prompt, " ") // Check for cached data sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey)) + cacheKey := util.GetCacheKey(a.GetName(), sEnc) // find in viper cache - if viper.IsSet(sEnc) && !nocache { + if viper.IsSet(cacheKey) && !nocache { // retrieve data from cache - response := viper.GetString(sEnc) + response := viper.GetString(cacheKey) if response == "" { color.Red("error retrieving cached data") return "", nil @@ -79,8 +81,8 @@ func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, nocache bool) return "", err } - if !viper.IsSet(sEnc) { - viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response))) + if !viper.IsSet(cacheKey) || nocache { + viper.Set(cacheKey, base64.StdEncoding.EncodeToString([]byte(response))) if err := viper.WriteConfig(); err != nil { color.Red("error writing config: %v", err) return "", nil diff --git a/pkg/analysis/analysis.go b/pkg/analysis/analysis.go index f2ac1a7a4e..f7948aa66f 100644 --- a/pkg/analysis/analysis.go +++ b/pkg/analysis/analysis.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "os" "strings" "github.com/fatih/color" @@ -41,8 +42,61 @@ type JsonOutput struct { Results []common.Result `json:"results"` } -func (a *Analysis) RunAnalysis() error { +func NewAnalysis(backend string, language string, filters []string, namespace string, noCache bool, explain bool) (*Analysis, error) { + var configAI ai.AIConfiguration + err := viper.UnmarshalKey("ai", &configAI) + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + + if len(configAI.Providers) == 0 { + color.Red("Error: AI provider not specified in configuration. Please run k8sgpt auth") + os.Exit(1) + } + + var aiProvider ai.AIProvider + for _, provider := range configAI.Providers { + if backend == provider.Name { + aiProvider = provider + break + } + } + + if aiProvider.Name == "" { + color.Red("Error: AI provider %s not specified in configuration. Please run k8sgpt auth", backend) + return nil, errors.New("AI provider not specified in configuration") + } + aiClient := ai.NewClient(aiProvider.Name) + if err := aiClient.Configure(aiProvider.Password, aiProvider.Model, language); err != nil { + color.Red("Error: %v", err) + return nil, err + } + + ctx := context.Background() + // Get kubernetes client from viper + + kubecontext := viper.GetString("kubecontext") + kubeconfig := viper.GetString("kubeconfig") + client, err := kubernetes.NewClient(kubecontext, kubeconfig) + if err != nil { + color.Red("Error initialising kubernetes client: %v", err) + return nil, err + } + + return &Analysis{ + Context: ctx, + Filters: filters, + Client: client, + AIClient: aiClient, + Namespace: namespace, + NoCache: noCache, + Explain: explain, + }, nil +} + +func (a *Analysis) RunAnalysis() error { activeFilters := viper.GetStringSlice("active_filters") analyzerMap := analyzer.GetAnalyzerMap() diff --git a/pkg/server/main.go b/pkg/server/main.go index ade7012bb6..bfb4a1d865 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -1,13 +1,9 @@ package server import ( - "context" "fmt" "github.com/fatih/color" - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/analysis" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" - "github.com/spf13/viper" "net/http" "os" ) @@ -31,56 +27,9 @@ func (s *K8sGPTServer) analyzeHandler(w http.ResponseWriter, r *http.Request) { nocache := getBoolParam(r.URL.Query().Get("nocache")) language := r.URL.Query().Get("language") - // get ai configuration - var configAI ai.AIConfiguration - err := viper.UnmarshalKey("ai", &configAI) + config, err := analysis.NewAnalysis(s.Backend, language, []string{}, namespace, nocache, explain) if err != nil { - color.Red("Error: %v", err) - os.Exit(1) - } - - if len(configAI.Providers) == 0 { - color.Red("Error: AI provider not specified in configuration. Please run k8sgpt auth") - os.Exit(1) - } - - var aiProvider ai.AIProvider - for _, provider := range configAI.Providers { - if s.Backend == provider.Name { - aiProvider = provider - break - } - } - - if aiProvider.Name == "" { - color.Red("Error: AI provider %s not specified in configuration. Please run k8sgpt auth", s.Backend) - os.Exit(1) - } - - aiClient := ai.NewClient(aiProvider.Name) - if err := aiClient.Configure(aiProvider.Password, aiProvider.Model, language); err != nil { - color.Red("Error: %v", err) - os.Exit(1) - } - - ctx := context.Background() - // Get kubernetes client from viper - - kubecontext := viper.GetString("kubecontext") - kubeconfig := viper.GetString("kubeconfig") - client, err := kubernetes.NewClient(kubecontext, kubeconfig) - if err != nil { - color.Red("Error initialising kubernetes client: %v", err) - os.Exit(1) - } - - config := &analysis.Analysis{ - Namespace: namespace, - Explain: explain, - AIClient: aiClient, - Client: client, - Context: ctx, - NoCache: nocache, + fmt.Fprintf(w, err.Error()) } err = config.RunAnalysis() @@ -108,6 +57,7 @@ func (s *K8sGPTServer) analyzeHandler(w http.ResponseWriter, r *http.Request) { func (s *K8sGPTServer) Serve() error { http.HandleFunc("/analyze", s.analyzeHandler) + color.Green("Starting server on port " + s.Port) err := http.ListenAndServe(":"+s.Port, nil) if err != nil { fmt.Printf("error starting server: %s\n", err) diff --git a/pkg/util/util.go b/pkg/util/util.go index 37f504c733..8cf273cbde 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -129,3 +129,7 @@ func ReplaceIfMatch(text string, pattern string, replacement string) string { } return text } + +func GetCacheKey(provider string, sEnc string) string { + return fmt.Sprintf("%s-%s", provider, sEnc) +} From 159b3851ec54e93a447b0f13aa4ceb7b8b8f62db Mon Sep 17 00:00:00 2001 From: Thomas Schuetz Date: Thu, 13 Apr 2023 09:00:14 +0200 Subject: [PATCH 4/7] fix: naming Signed-off-by: Thomas Schuetz --- cmd/serve/serve.go | 4 ++-- pkg/server/main.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go index e6350902e3..7e182e349b 100644 --- a/cmd/serve/serve.go +++ b/cmd/serve/serve.go @@ -3,7 +3,7 @@ package serve import ( "fmt" "github.com/fatih/color" - server2 "github.com/k8sgpt-ai/k8sgpt/pkg/server" + k8sgptserver "github.com/k8sgpt-ai/k8sgpt/pkg/server" "github.com/spf13/cobra" "github.com/spf13/viper" "os" @@ -38,7 +38,7 @@ var ServeCmd = &cobra.Command{ os.Exit(1) } - server := server2.K8sGPTServer{ + server := k8sgptserver.Config{ Backend: backend, Port: port, Token: token, diff --git a/pkg/server/main.go b/pkg/server/main.go index bfb4a1d865..0f7e7a40f3 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -8,7 +8,7 @@ import ( "os" ) -type K8sGPTServer struct { +type Config struct { Port string Backend string Key string @@ -20,7 +20,7 @@ type Result struct { Analysis []analysis.Analysis `json:"analysis"` } -func (s *K8sGPTServer) analyzeHandler(w http.ResponseWriter, r *http.Request) { +func (s *Config) analyzeHandler(w http.ResponseWriter, r *http.Request) { namespace := r.URL.Query().Get("namespace") explain := getBoolParam(r.URL.Query().Get("explain")) anonymize := getBoolParam(r.URL.Query().Get("anonymize")) @@ -55,7 +55,7 @@ func (s *K8sGPTServer) analyzeHandler(w http.ResponseWriter, r *http.Request) { } -func (s *K8sGPTServer) Serve() error { +func (s *Config) Serve() error { http.HandleFunc("/analyze", s.analyzeHandler) color.Green("Starting server on port " + s.Port) err := http.ListenAndServe(":"+s.Port, nil) From 6b630275eb64b799c50e3074cb22a3b41bb893de Mon Sep 17 00:00:00 2001 From: Thomas Schuetz Date: Thu, 13 Apr 2023 13:11:39 +0200 Subject: [PATCH 5/7] fix: start message Signed-off-by: Thomas Schuetz --- pkg/server/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/server/main.go b/pkg/server/main.go index 0f7e7a40f3..1186093845 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -57,7 +57,7 @@ func (s *Config) analyzeHandler(w http.ResponseWriter, r *http.Request) { func (s *Config) Serve() error { http.HandleFunc("/analyze", s.analyzeHandler) - color.Green("Starting server on port " + s.Port) + color.Green("Starting server on port %d", s.Port) err := http.ListenAndServe(":"+s.Port, nil) if err != nil { fmt.Printf("error starting server: %s\n", err) From 336ec2a42693d0df325b95cbebd9545b19e27725 Mon Sep 17 00:00:00 2001 From: Thomas Schuetz Date: Thu, 13 Apr 2023 13:13:45 +0200 Subject: [PATCH 6/7] fix: bool conversion Signed-off-by: Thomas Schuetz --- pkg/server/main.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/server/main.go b/pkg/server/main.go index 1186093845..b5e43047dd 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -6,6 +6,8 @@ import ( "github.com/k8sgpt-ai/k8sgpt/pkg/analysis" "net/http" "os" + "strconv" + "strings" ) type Config struct { @@ -67,8 +69,10 @@ func (s *Config) Serve() error { } func getBoolParam(param string) bool { - if param == "true" { - return true + b, err := strconv.ParseBool(strings.ToLower(param)) + if err != nil { + // Handle error if conversion fails + return false } - return false + return b } From 26c0cb2eed75695220007e6d6f7b492c2641a149 Mon Sep 17 00:00:00 2001 From: Thomas Schuetz Date: Thu, 13 Apr 2023 13:29:19 +0200 Subject: [PATCH 7/7] feat: add simple health endpoint Signed-off-by: Thomas Schuetz --- pkg/server/main.go | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/pkg/server/main.go b/pkg/server/main.go index b5e43047dd..8dab23d571 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -1,11 +1,11 @@ package server import ( + json "encoding/json" "fmt" "github.com/fatih/color" "github.com/k8sgpt-ai/k8sgpt/pkg/analysis" "net/http" - "os" "strconv" "strings" ) @@ -18,6 +18,18 @@ type Config struct { Output string } +type Health struct { + Status string `json:"status"` + Success int `json:"success"` + Failure int `json:"failure"` +} + +var health = Health{ + Status: "ok", + Success: 0, + Failure: 0, +} + type Result struct { Analysis []analysis.Analysis `json:"analysis"` } @@ -31,35 +43,40 @@ func (s *Config) analyzeHandler(w http.ResponseWriter, r *http.Request) { config, err := analysis.NewAnalysis(s.Backend, language, []string{}, namespace, nocache, explain) if err != nil { + health.Failure++ fmt.Fprintf(w, err.Error()) } err = config.RunAnalysis() if err != nil { color.Red("Error: %v", err) - os.Exit(1) + health.Failure++ + fmt.Fprintf(w, err.Error()) } if explain { err := config.GetAIResults(s.Output, anonymize) if err != nil { color.Red("Error: %v", err) - os.Exit(1) + health.Failure++ + fmt.Fprintf(w, err.Error()) } } output, err := config.JsonOutput() if err != nil { color.Red("Error: %v", err) - os.Exit(1) + health.Failure++ + fmt.Fprintf(w, err.Error()) } + health.Success++ fmt.Fprintf(w, string(output)) - } func (s *Config) Serve() error { http.HandleFunc("/analyze", s.analyzeHandler) - color.Green("Starting server on port %d", s.Port) + http.HandleFunc("/healthz", s.healthzHandler) + color.Green("Starting server on port %s", s.Port) err := http.ListenAndServe(":"+s.Port, nil) if err != nil { fmt.Printf("error starting server: %s\n", err) @@ -68,6 +85,15 @@ func (s *Config) Serve() error { return nil } +func (s *Config) healthzHandler(w http.ResponseWriter, r *http.Request) { + js, err := json.MarshalIndent(health, "", " ") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Fprintf(w, string(js)) +} + func getBoolParam(param string) bool { b, err := strconv.ParseBool(strings.ToLower(param)) if err != nil {