diff --git a/cmd/analyze/analyze.go b/cmd/analyze/analyze.go index 33309b0232..2a3f176c1a 100644 --- a/cmd/analyze/analyze.go +++ b/cmd/analyze/analyze.go @@ -4,12 +4,13 @@ import ( "context" "encoding/json" "fmt" + "github.com/k8sgpt-ai/k8sgpt/pkg/analysis" + "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" "os" "strings" "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/cobra" @@ -53,50 +54,44 @@ var AnalyzeCmd = &cobra.Command{ os.Exit(1) } - var aiClient ai.IAI - switch backendType { - case "openai": - aiClient = &ai.OpenAIClient{} - if err := aiClient.Configure(token, language); err != nil { - color.Red("Error: %v", err) - os.Exit(1) - } - default: - color.Red("Backend not supported") + aiClient := ai.NewClient(backendType) + if err := aiClient.Configure(token, 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{ + // AnalysisResult configuration + config := &analysis.Analysis{ Namespace: namespace, NoCache: nocache, Explain: explain, + AIClient: aiClient, + Client: client, + Context: ctx, } - var analysisResults *[]analyzer.Analysis = &[]analyzer.Analysis{} - if err := analyzer.RunAnalysis(ctx, filters, config, client, - aiClient, analysisResults); err != nil { + err := config.RunAnalysis() + if err != nil { color.Red("Error: %v", err) os.Exit(1) } - if len(*analysisResults) == 0 { + if len(config.Results) == 0 { color.Green("{ \"status\": \"OK\" }") os.Exit(0) } - var bar = progressbar.Default(int64(len(*analysisResults))) + var bar = progressbar.Default(int64(len(config.Results))) if !explain { bar.Clear() } - var printOutput []analyzer.Analysis - - for _, analysis := range *analysisResults { + var printOutput []analyzer.Result + for _, analysis := range config.Results { if explain { - parsedText, err := analyzer.ParseViaAI(ctx, config, aiClient, analysis.Error) + parsedText, err := aiClient.Parse(ctx, analysis.Error, nocache) if err != nil { // Check for exhaustion if strings.Contains(err.Error(), "status code: 429") { diff --git a/pkg/ai/ai.go b/pkg/ai/ai.go index 5d54e2c2fb..9bf0997be5 100644 --- a/pkg/ai/ai.go +++ b/pkg/ai/ai.go @@ -2,8 +2,12 @@ package ai import ( "context" + "encoding/base64" "errors" "fmt" + "github.com/fatih/color" + "github.com/spf13/viper" + "strings" "github.com/sashabaranov/go-openai" ) @@ -46,3 +50,40 @@ func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string } return resp.Choices[0].Message.Content, nil } + +func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, nocache bool) (string, error) { + // parse the text with the AI backend + inputKey := strings.Join(prompt, " ") + // Check for cached data + sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey)) + // find in viper cache + if viper.IsSet(sEnc) && !nocache { + // retrieve data from cache + response := viper.GetString(sEnc) + if response == "" { + color.Red("error retrieving cached data") + return "", nil + } + output, err := base64.StdEncoding.DecodeString(response) + if err != nil { + color.Red("error decoding cached data: %v", err) + return "", nil + } + return string(output), nil + } + + response, err := a.GetCompletion(ctx, inputKey) + if err != nil { + color.Red("error getting completion: %v", err) + return "", err + } + + if !viper.IsSet(sEnc) { + viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response))) + if err := viper.WriteConfig(); err != nil { + color.Red("error writing config: %v", err) + return "", nil + } + } + return response, nil +} diff --git a/pkg/ai/iai.go b/pkg/ai/iai.go index 6c61d0a215..cb12efa7ca 100644 --- a/pkg/ai/iai.go +++ b/pkg/ai/iai.go @@ -1,8 +1,20 @@ package ai -import "context" +import ( + "context" +) type IAI interface { Configure(token string, language string) error GetCompletion(ctx context.Context, prompt string) (string, error) + Parse(ctx context.Context, prompt []string, nocache bool) (string, error) +} + +func NewClient(provider string) IAI { + switch provider { + case "openai": + return &OpenAIClient{} + default: + return &OpenAIClient{} + } } diff --git a/pkg/analysis/analysis.go b/pkg/analysis/analysis.go new file mode 100644 index 0000000000..f2dd345132 --- /dev/null +++ b/pkg/analysis/analysis.go @@ -0,0 +1,72 @@ +package analysis + +import ( + "context" + "github.com/k8sgpt-ai/k8sgpt/pkg/ai" + "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" + "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" + "github.com/spf13/viper" +) + +type Analysis struct { + Context context.Context + Filters []string + Client *kubernetes.Client + AIClient ai.IAI + Results []analyzer.Result + Namespace string + NoCache bool + Explain bool +} + +func (a *Analysis) RunAnalysis() error { + + activeFilters := viper.GetStringSlice("active_filters") + + analyzerMap := analyzer.GetAnalyzerMap() + + analyzerConfig := analyzer.Analyzer{ + Client: a.Client, + Context: a.Context, + Namespace: a.Namespace, + AIClient: a.AIClient, + } + + // if there are no filters selected and no active_filters then run all of them + if len(a.Filters) == 0 && len(activeFilters) == 0 { + for _, analyzer := range analyzerMap { + results, err := analyzer.Analyze(analyzerConfig) + if err != nil { + return err + } + a.Results = append(a.Results, results...) + } + return nil + } + + // if the filters flag is specified + if len(a.Filters) != 0 { + for _, filter := range a.Filters { + if analyzer, ok := analyzerMap[filter]; ok { + results, err := analyzer.Analyze(analyzerConfig) + if err != nil { + return err + } + a.Results = append(a.Results, results...) + } + } + return nil + } + + // use active_filters + for _, filter := range activeFilters { + if analyzer, ok := analyzerMap[filter]; ok { + results, err := analyzer.Analyze(analyzerConfig) + if err != nil { + return err + } + a.Results = append(a.Results, results...) + } + } + return nil +} diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index b57228d8d2..0db2b96c0a 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -1,15 +1,8 @@ package analyzer -import ( - "context" - "encoding/base64" - "strings" - - "github.com/fatih/color" - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" - "github.com/spf13/viper" -) +type IAnalyzer interface { + Analyze(analysis Analyzer) ([]Result, error) +} var coreAnalyzerMap = map[string]IAnalyzer{ "Pod": PodAnalyzer{}, @@ -24,85 +17,6 @@ var additionalAnalyzerMap = map[string]IAnalyzer{ "PodDisruptionBudget": PdbAnalyzer{}, } -func RunAnalysis(ctx context.Context, filters []string, config *AnalysisConfiguration, - client *kubernetes.Client, - aiClient ai.IAI, analysisResults *[]Analysis) error { - - activeFilters := viper.GetStringSlice("active_filters") - - analyzerMap := getAnalyzerMap() - - // if there are no filters selected and no active_filters then run all of them - if len(filters) == 0 && len(activeFilters) == 0 { - for _, analyzer := range analyzerMap { - if err := analyzer.RunAnalysis(ctx, config, client, aiClient, analysisResults); err != nil { - return err - } - } - return nil - } - - // if the filters flag is specified - if len(filters) != 0 { - for _, filter := range filters { - if analyzer, ok := analyzerMap[filter]; ok { - if err := analyzer.RunAnalysis(ctx, config, client, aiClient, analysisResults); err != nil { - return err - } - } - } - return nil - } - - // use active_filters - for _, filter := range activeFilters { - if analyzer, ok := analyzerMap[filter]; ok { - if err := analyzer.RunAnalysis(ctx, config, client, aiClient, analysisResults); err != nil { - return err - } - } - } - return nil -} - -func ParseViaAI(ctx context.Context, config *AnalysisConfiguration, - aiClient ai.IAI, prompt []string) (string, error) { - // parse the text with the AI backend - inputKey := strings.Join(prompt, " ") - // Check for cached data - sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey)) - // find in viper cache - if viper.IsSet(sEnc) && !config.NoCache { - // retrieve data from cache - response := viper.GetString(sEnc) - if response == "" { - color.Red("error retrieving cached data") - return "", nil - } - output, err := base64.StdEncoding.DecodeString(response) - if err != nil { - color.Red("error decoding cached data: %v", err) - return "", nil - } - return string(output), nil - } - - response, err := aiClient.GetCompletion(ctx, inputKey) - if err != nil { - color.Red("error getting completion: %v", err) - return "", err - } - - if !viper.IsSet(sEnc) { - viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response))) - if err := viper.WriteConfig(); err != nil { - color.Red("error writing config: %v", err) - return "", nil - } - } - return response, nil -} - func ListFilters() ([]string, []string) { coreKeys := make([]string, 0, len(coreAnalyzerMap)) for k := range coreAnalyzerMap { @@ -116,7 +30,7 @@ func ListFilters() ([]string, []string) { return coreKeys, additionalKeys } -func getAnalyzerMap() map[string]IAnalyzer { +func GetAnalyzerMap() map[string]IAnalyzer { mergedMap := make(map[string]IAnalyzer) diff --git a/pkg/analyzer/events.go b/pkg/analyzer/events.go index 6392e24d2f..b593567757 100644 --- a/pkg/analyzer/events.go +++ b/pkg/analyzer/events.go @@ -2,7 +2,6 @@ package analyzer import ( "context" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/analyzer/hpaAnalyzer.go b/pkg/analyzer/hpa.go similarity index 57% rename from pkg/analyzer/hpaAnalyzer.go rename to pkg/analyzer/hpa.go index 8ca77d5fba..6f22a44283 100644 --- a/pkg/analyzer/hpaAnalyzer.go +++ b/pkg/analyzer/hpa.go @@ -1,23 +1,18 @@ package analyzer import ( - "context" "fmt" - - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type HpaAnalyzer struct{} -func (HpaAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, - analysisResults *[]Analysis) error { +func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) { - list, err := client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(config.Namespace).List(ctx, metav1.ListOptions{}) + list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{}) if err != nil { - return err + return nil, err } var preAnalysis = map[string]PreAnalysis{} @@ -31,22 +26,22 @@ func (HpaAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio switch scaleTargetRef.Kind { case "Deployment": - _, err := client.GetClient().AppsV1().Deployments(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{}) + _, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{}) if err != nil { scaleTargetRefNotFound = true } case "ReplicationController": - _, err := client.GetClient().CoreV1().ReplicationControllers(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{}) + _, err := a.Client.GetClient().CoreV1().ReplicationControllers(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{}) if err != nil { scaleTargetRefNotFound = true } case "ReplicaSet": - _, err := client.GetClient().AppsV1().ReplicaSets(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{}) + _, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{}) if err != nil { scaleTargetRefNotFound = true } case "StatefulSet": - _, err := client.GetClient().AppsV1().StatefulSets(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{}) + _, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{}) if err != nil { scaleTargetRefNotFound = true } @@ -68,16 +63,16 @@ func (HpaAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio } for key, value := range preAnalysis { - var currentAnalysis = Analysis{ + var currentAnalysis = Result{ Kind: "HorizontalPodAutoscaler", Name: key, Error: value.FailureDetails, } - parent, _ := util.GetParent(client, value.HorizontalPodAutoscalers.ObjectMeta) + parent, _ := util.GetParent(a.Client, value.HorizontalPodAutoscalers.ObjectMeta) currentAnalysis.ParentObject = parent - *analysisResults = append(*analysisResults, currentAnalysis) + a.Results = append(a.Results, currentAnalysis) } - return nil + return a.Results, nil } diff --git a/pkg/analyzer/ianalyzer.go b/pkg/analyzer/ianalyzer.go deleted file mode 100644 index 8cb7a9135f..0000000000 --- a/pkg/analyzer/ianalyzer.go +++ /dev/null @@ -1,13 +0,0 @@ -package analyzer - -import ( - "context" - - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" -) - -type IAnalyzer interface { - RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, - analysisResults *[]Analysis) error -} diff --git a/pkg/analyzer/ingressAnalyzer.go b/pkg/analyzer/ingress.go similarity index 65% rename from pkg/analyzer/ingressAnalyzer.go rename to pkg/analyzer/ingress.go index 1897f6b9be..7919701ca0 100644 --- a/pkg/analyzer/ingressAnalyzer.go +++ b/pkg/analyzer/ingress.go @@ -1,23 +1,18 @@ package analyzer import ( - "context" "fmt" - - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type IngressAnalyzer struct{} -func (IngressAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, - analysisResults *[]Analysis) error { +func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) { - list, err := client.GetClient().NetworkingV1().Ingresses(config.Namespace).List(ctx, metav1.ListOptions{}) + list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{}) if err != nil { - return err + return nil, err } var preAnalysis = map[string]PreAnalysis{} @@ -38,7 +33,7 @@ func (IngressAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfigur // check if ingressclass exist if ingressClassName != nil { - _, err := client.GetClient().NetworkingV1().IngressClasses().Get(ctx, *ingressClassName, metav1.GetOptions{}) + _, err := a.Client.GetClient().NetworkingV1().IngressClasses().Get(a.Context, *ingressClassName, metav1.GetOptions{}) if err != nil { failures = append(failures, fmt.Sprintf("Ingress uses the ingress class %s which does not exist.", *ingressClassName)) } @@ -48,7 +43,7 @@ func (IngressAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfigur for _, rule := range ing.Spec.Rules { // loop over paths for _, path := range rule.HTTP.Paths { - _, err := client.GetClient().CoreV1().Services(ing.Namespace).Get(ctx, path.Backend.Service.Name, metav1.GetOptions{}) + _, err := a.Client.GetClient().CoreV1().Services(ing.Namespace).Get(a.Context, path.Backend.Service.Name, metav1.GetOptions{}) if err != nil { failures = append(failures, fmt.Sprintf("Ingress uses the service %s/%s which does not exist.", ing.Namespace, path.Backend.Service.Name)) } @@ -56,7 +51,7 @@ func (IngressAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfigur } for _, tls := range ing.Spec.TLS { - _, err := client.GetClient().CoreV1().Secrets(ing.Namespace).Get(ctx, tls.SecretName, metav1.GetOptions{}) + _, err := a.Client.GetClient().CoreV1().Secrets(ing.Namespace).Get(a.Context, tls.SecretName, metav1.GetOptions{}) if err != nil { failures = append(failures, fmt.Sprintf("Ingress uses the secret %s/%s as a TLS certificate which does not exist.", ing.Namespace, tls.SecretName)) } @@ -71,16 +66,16 @@ func (IngressAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfigur } for key, value := range preAnalysis { - var currentAnalysis = Analysis{ + var currentAnalysis = Result{ Kind: "Ingress", Name: key, Error: value.FailureDetails, } - parent, _ := util.GetParent(client, value.Ingress.ObjectMeta) + parent, _ := util.GetParent(a.Client, value.Ingress.ObjectMeta) currentAnalysis.ParentObject = parent - *analysisResults = append(*analysisResults, currentAnalysis) + a.Results = append(a.Results, currentAnalysis) } - return nil + return a.Results, nil } diff --git a/pkg/analyzer/ingressAnalyzer_test.go b/pkg/analyzer/ingress_test.go similarity index 73% rename from pkg/analyzer/ingressAnalyzer_test.go rename to pkg/analyzer/ingress_test.go index b3546804e8..bb1eef9fde 100644 --- a/pkg/analyzer/ingressAnalyzer_test.go +++ b/pkg/analyzer/ingress_test.go @@ -22,16 +22,17 @@ func TestIngressAnalyzer(t *testing.T) { }, }) ingressAnalyzer := IngressAnalyzer{} - var analysisResults []Analysis - err := ingressAnalyzer.RunAnalysis(context.Background(), - &AnalysisConfiguration{ - Namespace: "default", - }, - &kubernetes.Client{ + + config := Analyzer{ + Client: &kubernetes.Client{ Client: clientset, - }, nil, &analysisResults) + }, + Context: context.Background(), + Namespace: "default", + } + analysisResults, err := ingressAnalyzer.Analyze(config) if err != nil { - t.Fatalf("unexpected error: %v", err) + t.Error(err) } assert.Equal(t, len(analysisResults), 1) } @@ -54,16 +55,18 @@ func TestIngressAnalyzerWithMultipleIngresses(t *testing.T) { }, ) ingressAnalyzer := IngressAnalyzer{} - var analysisResults []Analysis - err := ingressAnalyzer.RunAnalysis(context.Background(), - &AnalysisConfiguration{ - Namespace: "default", - }, - &kubernetes.Client{ + + config := Analyzer{ + Client: &kubernetes.Client{ Client: clientset, - }, nil, &analysisResults) + }, + Context: context.Background(), + Namespace: "default", + } + + analysisResults, err := ingressAnalyzer.Analyze(config) if err != nil { - t.Fatalf("unexpected error: %v", err) + t.Error(err) } assert.Equal(t, len(analysisResults), 2) } @@ -80,16 +83,17 @@ func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) { }) ingressAnalyzer := IngressAnalyzer{} - var analysisResults []Analysis - err := ingressAnalyzer.RunAnalysis(context.Background(), - &AnalysisConfiguration{ - Namespace: "default", - }, - &kubernetes.Client{ + config := Analyzer{ + Client: &kubernetes.Client{ Client: clientset, - }, nil, &analysisResults) + }, + Context: context.Background(), + Namespace: "default", + } + + analysisResults, err := ingressAnalyzer.Analyze(config) if err != nil { - t.Fatalf("unexpected error: %v", err) + t.Error(err) } var errorFound bool diff --git a/pkg/analyzer/pdbAnalyzer.go b/pkg/analyzer/pdb.go similarity index 64% rename from pkg/analyzer/pdbAnalyzer.go rename to pkg/analyzer/pdb.go index f28a74d9b1..c6dc854545 100644 --- a/pkg/analyzer/pdbAnalyzer.go +++ b/pkg/analyzer/pdb.go @@ -1,23 +1,18 @@ package analyzer import ( - "context" "fmt" - - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type PdbAnalyzer struct{} -func (PdbAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, - analysisResults *[]Analysis) error { +func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) { - list, err := client.GetClient().PolicyV1().PodDisruptionBudgets(config.Namespace).List(ctx, metav1.ListOptions{}) + list, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{}) if err != nil { - return err + return nil, err } var preAnalysis = map[string]PreAnalysis{} @@ -25,7 +20,7 @@ func (PdbAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio for _, pdb := range list.Items { var failures []string - evt, err := FetchLatestEvent(ctx, client, pdb.Namespace, pdb.Name) + evt, err := FetchLatestEvent(a.Context, a.Client, pdb.Namespace, pdb.Name) if err != nil || evt == nil { continue } @@ -52,16 +47,16 @@ func (PdbAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio } for key, value := range preAnalysis { - var currentAnalysis = Analysis{ + var currentAnalysis = Result{ Kind: "PodDisruptionBudget", Name: key, Error: value.FailureDetails, } - parent, _ := util.GetParent(client, value.PodDisruptionBudget.ObjectMeta) + parent, _ := util.GetParent(a.Client, value.PodDisruptionBudget.ObjectMeta) currentAnalysis.ParentObject = parent - *analysisResults = append(*analysisResults, currentAnalysis) + a.Results = append(a.Results, currentAnalysis) } - return nil + return a.Results, err } diff --git a/pkg/analyzer/podAnalyzer.go b/pkg/analyzer/pod.go similarity index 74% rename from pkg/analyzer/podAnalyzer.go rename to pkg/analyzer/pod.go index 9dc172aa93..797f649814 100644 --- a/pkg/analyzer/podAnalyzer.go +++ b/pkg/analyzer/pod.go @@ -1,24 +1,19 @@ package analyzer import ( - "context" "fmt" - - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type PodAnalyzer struct{} - -func (PodAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, - client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error { +type PodAnalyzer struct { +} +func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) { // search all namespaces for pods that are not running - list, err := client.GetClient().CoreV1().Pods(config.Namespace).List(ctx, metav1.ListOptions{}) + list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{}) if err != nil { - return err + return nil, err } var preAnalysis = map[string]PreAnalysis{} @@ -49,7 +44,7 @@ func (PodAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" { // parse the event log and append details - evt, err := FetchLatestEvent(ctx, client, pod.Namespace, pod.Name) + evt, err := FetchLatestEvent(a.Context, a.Client, pod.Namespace, pod.Name) if err != nil || evt == nil { continue } @@ -68,16 +63,16 @@ func (PodAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio } for key, value := range preAnalysis { - var currentAnalysis = Analysis{ + var currentAnalysis = Result{ Kind: "Pod", Name: key, Error: value.FailureDetails, } - parent, _ := util.GetParent(client, value.Pod.ObjectMeta) + parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta) currentAnalysis.ParentObject = parent - *analysisResults = append(*analysisResults, currentAnalysis) + a.Results = append(a.Results, currentAnalysis) } - return nil + return a.Results, nil } diff --git a/pkg/analyzer/podAnalyzer_test.go b/pkg/analyzer/pod_test.go similarity index 76% rename from pkg/analyzer/podAnalyzer_test.go rename to pkg/analyzer/pod_test.go index 0baf4b4935..609541fed8 100644 --- a/pkg/analyzer/podAnalyzer_test.go +++ b/pkg/analyzer/pod_test.go @@ -11,7 +11,7 @@ import ( "k8s.io/client-go/kubernetes/fake" ) -func TestPodAnalzyer(t *testing.T) { +func TestPodAnalyzer(t *testing.T) { clientset := fake.NewSimpleClientset(&v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -31,17 +31,18 @@ func TestPodAnalzyer(t *testing.T) { }, }) - podAnalyzer := PodAnalyzer{} - var analysisResults []Analysis - err := podAnalyzer.RunAnalysis(context.Background(), - &AnalysisConfiguration{ - Namespace: "default", - }, - &kubernetes.Client{ + config := Analyzer{ + Client: &kubernetes.Client{ Client: clientset, - }, nil, &analysisResults) + }, + Context: context.Background(), + Namespace: "default", + } + podAnalyzer := PodAnalyzer{} + var analysisResults []Result + analysisResults, err := podAnalyzer.Analyze(config) if err != nil { - t.Fatalf("unexpected error: %v", err) + t.Error(err) } assert.Equal(t, len(analysisResults), 1) } diff --git a/pkg/analyzer/pvcAnalyzer.go b/pkg/analyzer/pvc.go similarity index 59% rename from pkg/analyzer/pvcAnalyzer.go rename to pkg/analyzer/pvc.go index 60e5cd2676..8491014b15 100644 --- a/pkg/analyzer/pvcAnalyzer.go +++ b/pkg/analyzer/pvc.go @@ -1,23 +1,19 @@ package analyzer import ( - "context" "fmt" - - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type PvcAnalyzer struct{} -func (PvcAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error { +func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) { // search all namespaces for pods that are not running - list, err := client.GetClient().CoreV1().PersistentVolumeClaims(config.Namespace).List(ctx, metav1.ListOptions{}) + list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{}) if err != nil { - return err + return nil, err } var preAnalysis = map[string]PreAnalysis{} @@ -29,7 +25,7 @@ func (PvcAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio if pvc.Status.Phase == "Pending" { // parse the event log and append details - evt, err := FetchLatestEvent(ctx, client, pvc.Namespace, pvc.Name) + evt, err := FetchLatestEvent(a.Context, a.Client, pvc.Namespace, pvc.Name) if err != nil || evt == nil { continue } @@ -46,16 +42,16 @@ func (PvcAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio } for key, value := range preAnalysis { - var currentAnalysis = Analysis{ + var currentAnalysis = Result{ Kind: "PersistentVolumeClaim", Name: key, Error: value.FailureDetails, } - parent, _ := util.GetParent(client, value.PersistentVolumeClaim.ObjectMeta) + parent, _ := util.GetParent(a.Client, value.PersistentVolumeClaim.ObjectMeta) currentAnalysis.ParentObject = parent - *analysisResults = append(*analysisResults, currentAnalysis) + a.Results = append(a.Results, currentAnalysis) } - return nil + return a.Results, nil } diff --git a/pkg/analyzer/rsAnalyzer.go b/pkg/analyzer/rs.go similarity index 63% rename from pkg/analyzer/rsAnalyzer.go rename to pkg/analyzer/rs.go index 1f0222cd74..0d3d41592a 100644 --- a/pkg/analyzer/rsAnalyzer.go +++ b/pkg/analyzer/rs.go @@ -1,24 +1,19 @@ package analyzer import ( - "context" "fmt" - - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ReplicaSetAnalyzer struct{} -func (ReplicaSetAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, - client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error { +func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) { // search all namespaces for pods that are not running - list, err := client.GetClient().AppsV1().ReplicaSets(config.Namespace).List(ctx, metav1.ListOptions{}) + list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{}) if err != nil { - return err + return nil, err } var preAnalysis = map[string]PreAnalysis{} @@ -45,16 +40,15 @@ func (ReplicaSetAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfi } for key, value := range preAnalysis { - var currentAnalysis = Analysis{ + var currentAnalysis = Result{ Kind: "ReplicaSet", Name: key, Error: value.FailureDetails, } - parent, _ := util.GetParent(client, value.ReplicaSet.ObjectMeta) + parent, _ := util.GetParent(a.Client, value.ReplicaSet.ObjectMeta) currentAnalysis.ParentObject = parent - *analysisResults = append(*analysisResults, currentAnalysis) + a.Results = append(a.Results, currentAnalysis) } - - return nil + return a.Results, nil } diff --git a/pkg/analyzer/serviceAnalyzer.go b/pkg/analyzer/service.go similarity index 69% rename from pkg/analyzer/serviceAnalyzer.go rename to pkg/analyzer/service.go index 59a5ed444b..02fc1cef28 100644 --- a/pkg/analyzer/serviceAnalyzer.go +++ b/pkg/analyzer/service.go @@ -1,25 +1,20 @@ package analyzer import ( - "context" "fmt" - "github.com/fatih/color" - "github.com/k8sgpt-ai/k8sgpt/pkg/ai" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ServiceAnalyzer struct{} -func (ServiceAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, - analysisResults *[]Analysis) error { +func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) { // search all namespaces for pods that are not running - list, err := client.GetClient().CoreV1().Endpoints(config.Namespace).List(ctx, metav1.ListOptions{}) + list, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{}) if err != nil { - return err + return nil, err } var preAnalysis = map[string]PreAnalysis{} @@ -29,7 +24,7 @@ func (ServiceAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfigur // Check for empty service if len(ep.Subsets) == 0 { - svc, err := client.GetClient().CoreV1().Services(ep.Namespace).Get(ctx, ep.Name, metav1.GetOptions{}) + svc, err := a.Client.GetClient().CoreV1().Services(ep.Namespace).Get(a.Context, ep.Name, metav1.GetOptions{}) if err != nil { color.Yellow("Service %s/%s does not exist", ep.Namespace, ep.Name) continue @@ -63,15 +58,15 @@ func (ServiceAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfigur } for key, value := range preAnalysis { - var currentAnalysis = Analysis{ + var currentAnalysis = Result{ Kind: "Service", Name: key, Error: value.FailureDetails, } - parent, _ := util.GetParent(client, value.Endpoint.ObjectMeta) + parent, _ := util.GetParent(a.Client, value.Endpoint.ObjectMeta) currentAnalysis.ParentObject = parent - *analysisResults = append(*analysisResults, currentAnalysis) + a.Results = append(a.Results, currentAnalysis) } - return nil + return a.Results, nil } diff --git a/pkg/analyzer/serviceAnalyzer_test.go b/pkg/analyzer/service_test.go similarity index 74% rename from pkg/analyzer/serviceAnalyzer_test.go rename to pkg/analyzer/service_test.go index aebdebf700..b7d2dace5a 100644 --- a/pkg/analyzer/serviceAnalyzer_test.go +++ b/pkg/analyzer/service_test.go @@ -11,7 +11,7 @@ import ( "k8s.io/client-go/kubernetes/fake" ) -func TestServiceAnalzyer(t *testing.T) { +func TestServiceAnalyzer(t *testing.T) { clientset := fake.NewSimpleClientset(&v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ @@ -32,17 +32,18 @@ func TestServiceAnalzyer(t *testing.T) { }, }}) - serviceAnalyzer := ServiceAnalyzer{} - var analysisResults []Analysis - err := serviceAnalyzer.RunAnalysis(context.Background(), - &AnalysisConfiguration{ - Namespace: "default", - }, - &kubernetes.Client{ + config := Analyzer{ + Client: &kubernetes.Client{ Client: clientset, - }, nil, &analysisResults) + }, + Context: context.Background(), + Namespace: "default", + } + + serviceAnalyzer := ServiceAnalyzer{} + analysisResults, err := serviceAnalyzer.Analyze(config) if err != nil { - t.Fatalf("unexpected error: %v", err) + t.Error(err) } assert.Equal(t, len(analysisResults), 1) } diff --git a/pkg/analyzer/analysis.go b/pkg/analyzer/types.go similarity index 56% rename from pkg/analyzer/analysis.go rename to pkg/analyzer/types.go index 67c93af36b..42e5e0ab81 100644 --- a/pkg/analyzer/analysis.go +++ b/pkg/analyzer/types.go @@ -1,17 +1,23 @@ package analyzer import ( + "context" + "github.com/k8sgpt-ai/k8sgpt/pkg/ai" + "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" appsv1 "k8s.io/api/apps/v1" - autoscalingv1 "k8s.io/api/autoscaling/v1" + autov1 "k8s.io/api/autoscaling/v1" v1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" + networkv1 "k8s.io/api/networking/v1" policyv1 "k8s.io/api/policy/v1" ) -type AnalysisConfiguration struct { - Namespace string - NoCache bool - Explain bool +type Analyzer struct { + Client *kubernetes.Client + Context context.Context + Namespace string + AIClient ai.IAI + PreAnalysis map[string]PreAnalysis + Results []Result } type PreAnalysis struct { @@ -20,12 +26,12 @@ type PreAnalysis struct { ReplicaSet appsv1.ReplicaSet PersistentVolumeClaim v1.PersistentVolumeClaim Endpoint v1.Endpoints - Ingress networkingv1.Ingress - HorizontalPodAutoscalers autoscalingv1.HorizontalPodAutoscaler + Ingress networkv1.Ingress + HorizontalPodAutoscalers autov1.HorizontalPodAutoscaler PodDisruptionBudget policyv1.PodDisruptionBudget } -type Analysis struct { +type Result struct { Kind string `json:"kind"` Name string `json:"name"` Error []string `json:"error"`