From 961fb6c555f59f1276531f462739b76b1508830e Mon Sep 17 00:00:00 2001 From: Thomas Schuetz Date: Sat, 25 Mar 2023 22:53:14 +0100 Subject: [PATCH] feat: add service analysis Signed-off-by: Thomas Schuetz --- pkg/analyzer/analysis.go | 1 + pkg/analyzer/analyzer.go | 5 ++ pkg/analyzer/serviceAnalyzer.go | 121 ++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 pkg/analyzer/serviceAnalyzer.go diff --git a/pkg/analyzer/analysis.go b/pkg/analyzer/analysis.go index 7a35075e29..11ddc7217c 100644 --- a/pkg/analyzer/analysis.go +++ b/pkg/analyzer/analysis.go @@ -9,6 +9,7 @@ type PreAnalysis struct { Pod v1.Pod FailureDetails []string ReplicaSet appsv1.ReplicaSet + Endpoint v1.Endpoints } type Analysis struct { diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 595a27f051..db4df4b9e4 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -18,5 +18,10 @@ func RunAnalysis(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI if err != nil { return err } + + err = AnalyzeEndpoints(ctx, client, aiClient, explain, analysisResults) + if err != nil { + return err + } return nil } diff --git a/pkg/analyzer/serviceAnalyzer.go b/pkg/analyzer/serviceAnalyzer.go new file mode 100644 index 0000000000..085bb8ffc9 --- /dev/null +++ b/pkg/analyzer/serviceAnalyzer.go @@ -0,0 +1,121 @@ +package analyzer + +import ( + "context" + "encoding/base64" + "fmt" + "strings" + "time" + + "github.com/briandowns/spinner" + "github.com/fatih/color" + "github.com/k8sgpt-ai/k8sgpt/pkg/ai" + "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" + "github.com/spf13/viper" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func AnalyzeEndpoints(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, explain bool, analysisResults *[]Analysis) error { + + // search all namespaces for pods that are not running + list, err := client.GetClient().CoreV1().Endpoints("").List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + + var preAnalysis = map[string]PreAnalysis{} + + for _, ep := range list.Items { + var failures []string + + // Check for empty service + if len(ep.Subsets) == 0 { + svc, err := client.GetClient().CoreV1().Services(ep.Namespace).Get(ctx, ep.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + for k, v := range svc.Spec.Selector { + failures = append(failures, fmt.Sprintf("Service has no endpoints, expected label %s=%s", k, v)) + } + } else { + count := 0 + pods := []string{} + + // Check through container status to check for crashes + for _, epSubset := range ep.Subsets { + if len(epSubset.NotReadyAddresses) > 0 { + for _, addresses := range epSubset.NotReadyAddresses { + count++ + pods = append(pods, addresses.TargetRef.Kind+"/"+addresses.TargetRef.Name) + } + failures = append(failures, fmt.Sprintf("Service has not ready endpoints, pods: %s, expected %d", pods, count)) + } + } + } + + if len(failures) > 0 { + preAnalysis[fmt.Sprintf("%s/%s", ep.Namespace, ep.Name)] = PreAnalysis{ + Endpoint: ep, + FailureDetails: failures, + } + } + } + + for key, value := range preAnalysis { + var currentAnalysis = Analysis{ + Kind: "Service", + Name: key, + Error: value.FailureDetails[0], + } + + parent, _ := getParent(client, value.Endpoint.ObjectMeta) + + if explain { + s := spinner.New(spinner.CharSets[35], 100*time.Millisecond) // Build our new spinner + s.Start() + + inputValue := strings.Join(value.FailureDetails, " ") + + // Check for cached data + sEnc := base64.StdEncoding.EncodeToString([]byte(inputValue)) + // find in viper cache + if viper.IsSet(sEnc) { + s.Stop() + // retrieve data from cache + response := viper.GetString(sEnc) + if response == "" { + color.Red("error retrieving cached data") + continue + } + output, err := base64.StdEncoding.DecodeString(response) + if err != nil { + color.Red("error decoding cached data: %v", err) + continue + } + currentAnalysis.Details = string(output) + currentAnalysis.ParentObject = parent + *analysisResults = append(*analysisResults, currentAnalysis) + continue + } + + response, err := aiClient.GetCompletion(ctx, inputValue) + s.Stop() + if err != nil { + color.Red("error getting completion: %v", err) + continue + } + + if !viper.IsSet(sEnc) { + viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response))) + if err := viper.WriteConfig(); err != nil { + return err + } + } + currentAnalysis.Details = response + } + currentAnalysis.ParentObject = parent + *analysisResults = append(*analysisResults, currentAnalysis) + } + return nil +}