chore: analyzer and ai interfacing (#200)

* chore: analyzer and ai interfacing

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: backend variable for aiProvider in cmd

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: changed folders for analyzers

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* chore: renamed analyzers

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: fixed ingress tests after rebase

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: fixed ingress tests after rebase

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

---------

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
This commit is contained in:
Thomas Schuetz 2023-04-05 08:40:12 +02:00 committed by GitHub
parent eeac731858
commit 0195bfab30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 274 additions and 277 deletions

View File

@ -4,12 +4,13 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"os" "os"
"strings" "strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/schollz/progressbar/v3" "github.com/schollz/progressbar/v3"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -53,50 +54,44 @@ var AnalyzeCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
var aiClient ai.IAI aiClient := ai.NewClient(backendType)
switch backendType {
case "openai":
aiClient = &ai.OpenAIClient{}
if err := aiClient.Configure(token, language); err != nil { if err := aiClient.Configure(token, language); err != nil {
color.Red("Error: %v", err) color.Red("Error: %v", err)
os.Exit(1) os.Exit(1)
} }
default:
color.Red("Backend not supported")
os.Exit(1)
}
ctx := context.Background() ctx := context.Background()
// Get kubernetes client from viper // Get kubernetes client from viper
client := viper.Get("kubernetesClient").(*kubernetes.Client) client := viper.Get("kubernetesClient").(*kubernetes.Client)
// Analysis configuration // AnalysisResult configuration
config := &analyzer.AnalysisConfiguration{ config := &analysis.Analysis{
Namespace: namespace, Namespace: namespace,
NoCache: nocache, NoCache: nocache,
Explain: explain, Explain: explain,
AIClient: aiClient,
Client: client,
Context: ctx,
} }
var analysisResults *[]analyzer.Analysis = &[]analyzer.Analysis{} err := config.RunAnalysis()
if err := analyzer.RunAnalysis(ctx, filters, config, client, if err != nil {
aiClient, analysisResults); err != nil {
color.Red("Error: %v", err) color.Red("Error: %v", err)
os.Exit(1) os.Exit(1)
} }
if len(*analysisResults) == 0 { if len(config.Results) == 0 {
color.Green("{ \"status\": \"OK\" }") color.Green("{ \"status\": \"OK\" }")
os.Exit(0) os.Exit(0)
} }
var bar = progressbar.Default(int64(len(*analysisResults))) var bar = progressbar.Default(int64(len(config.Results)))
if !explain { if !explain {
bar.Clear() bar.Clear()
} }
var printOutput []analyzer.Analysis var printOutput []analyzer.Result
for _, analysis := range *analysisResults {
for _, analysis := range config.Results {
if explain { if explain {
parsedText, err := analyzer.ParseViaAI(ctx, config, aiClient, analysis.Error) parsedText, err := aiClient.Parse(ctx, analysis.Error, nocache)
if err != nil { if err != nil {
// Check for exhaustion // Check for exhaustion
if strings.Contains(err.Error(), "status code: 429") { if strings.Contains(err.Error(), "status code: 429") {

View File

@ -2,8 +2,12 @@ package ai
import ( import (
"context" "context"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"github.com/fatih/color"
"github.com/spf13/viper"
"strings"
"github.com/sashabaranov/go-openai" "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 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
}

View File

@ -1,8 +1,20 @@
package ai package ai
import "context" import (
"context"
)
type IAI interface { type IAI interface {
Configure(token string, language string) error Configure(token string, language string) error
GetCompletion(ctx context.Context, prompt string) (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{}
}
} }

72
pkg/analysis/analysis.go Normal file
View File

@ -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
}

View File

@ -1,15 +1,8 @@
package analyzer package analyzer
import ( type IAnalyzer interface {
"context" Analyze(analysis Analyzer) ([]Result, error)
"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"
)
var coreAnalyzerMap = map[string]IAnalyzer{ var coreAnalyzerMap = map[string]IAnalyzer{
"Pod": PodAnalyzer{}, "Pod": PodAnalyzer{},
@ -24,85 +17,6 @@ var additionalAnalyzerMap = map[string]IAnalyzer{
"PodDisruptionBudget": PdbAnalyzer{}, "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) { func ListFilters() ([]string, []string) {
coreKeys := make([]string, 0, len(coreAnalyzerMap)) coreKeys := make([]string, 0, len(coreAnalyzerMap))
for k := range coreAnalyzerMap { for k := range coreAnalyzerMap {
@ -116,7 +30,7 @@ func ListFilters() ([]string, []string) {
return coreKeys, additionalKeys return coreKeys, additionalKeys
} }
func getAnalyzerMap() map[string]IAnalyzer { func GetAnalyzerMap() map[string]IAnalyzer {
mergedMap := make(map[string]IAnalyzer) mergedMap := make(map[string]IAnalyzer)

View File

@ -2,7 +2,6 @@ package analyzer
import ( import (
"context" "context"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

View File

@ -1,23 +1,18 @@
package analyzer package analyzer
import ( import (
"context"
"fmt" "fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type HpaAnalyzer struct{} type HpaAnalyzer struct{}
func (HpaAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) {
analysisResults *[]Analysis) 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 { if err != nil {
return err return nil, err
} }
var preAnalysis = map[string]PreAnalysis{} var preAnalysis = map[string]PreAnalysis{}
@ -31,22 +26,22 @@ func (HpaAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio
switch scaleTargetRef.Kind { switch scaleTargetRef.Kind {
case "Deployment": 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 { if err != nil {
scaleTargetRefNotFound = true scaleTargetRefNotFound = true
} }
case "ReplicationController": 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 { if err != nil {
scaleTargetRefNotFound = true scaleTargetRefNotFound = true
} }
case "ReplicaSet": 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 { if err != nil {
scaleTargetRefNotFound = true scaleTargetRefNotFound = true
} }
case "StatefulSet": 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 { if err != nil {
scaleTargetRefNotFound = true scaleTargetRefNotFound = true
} }
@ -68,16 +63,16 @@ func (HpaAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio
} }
for key, value := range preAnalysis { for key, value := range preAnalysis {
var currentAnalysis = Analysis{ var currentAnalysis = Result{
Kind: "HorizontalPodAutoscaler", Kind: "HorizontalPodAutoscaler",
Name: key, Name: key,
Error: value.FailureDetails, Error: value.FailureDetails,
} }
parent, _ := util.GetParent(client, value.HorizontalPodAutoscalers.ObjectMeta) parent, _ := util.GetParent(a.Client, value.HorizontalPodAutoscalers.ObjectMeta)
currentAnalysis.ParentObject = parent currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis) a.Results = append(a.Results, currentAnalysis)
} }
return nil return a.Results, nil
} }

View File

@ -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
}

View File

@ -1,23 +1,18 @@
package analyzer package analyzer
import ( import (
"context"
"fmt" "fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type IngressAnalyzer struct{} type IngressAnalyzer struct{}
func (IngressAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
analysisResults *[]Analysis) 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 { if err != nil {
return err return nil, err
} }
var preAnalysis = map[string]PreAnalysis{} var preAnalysis = map[string]PreAnalysis{}
@ -38,7 +33,7 @@ func (IngressAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfigur
// check if ingressclass exist // check if ingressclass exist
if ingressClassName != nil { 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 { if err != nil {
failures = append(failures, fmt.Sprintf("Ingress uses the ingress class %s which does not exist.", *ingressClassName)) 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 { for _, rule := range ing.Spec.Rules {
// loop over paths // loop over paths
for _, path := range rule.HTTP.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 { if err != nil {
failures = append(failures, fmt.Sprintf("Ingress uses the service %s/%s which does not exist.", ing.Namespace, path.Backend.Service.Name)) 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 { 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 { 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)) 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 { for key, value := range preAnalysis {
var currentAnalysis = Analysis{ var currentAnalysis = Result{
Kind: "Ingress", Kind: "Ingress",
Name: key, Name: key,
Error: value.FailureDetails, Error: value.FailureDetails,
} }
parent, _ := util.GetParent(client, value.Ingress.ObjectMeta) parent, _ := util.GetParent(a.Client, value.Ingress.ObjectMeta)
currentAnalysis.ParentObject = parent currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis) a.Results = append(a.Results, currentAnalysis)
} }
return nil return a.Results, nil
} }

View File

@ -22,16 +22,17 @@ func TestIngressAnalyzer(t *testing.T) {
}, },
}) })
ingressAnalyzer := IngressAnalyzer{} ingressAnalyzer := IngressAnalyzer{}
var analysisResults []Analysis
err := ingressAnalyzer.RunAnalysis(context.Background(), config := Analyzer{
&AnalysisConfiguration{ Client: &kubernetes.Client{
Namespace: "default",
},
&kubernetes.Client{
Client: clientset, Client: clientset,
}, nil, &analysisResults) },
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := ingressAnalyzer.Analyze(config)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Error(err)
} }
assert.Equal(t, len(analysisResults), 1) assert.Equal(t, len(analysisResults), 1)
} }
@ -54,16 +55,18 @@ func TestIngressAnalyzerWithMultipleIngresses(t *testing.T) {
}, },
) )
ingressAnalyzer := IngressAnalyzer{} ingressAnalyzer := IngressAnalyzer{}
var analysisResults []Analysis
err := ingressAnalyzer.RunAnalysis(context.Background(), config := Analyzer{
&AnalysisConfiguration{ Client: &kubernetes.Client{
Namespace: "default",
},
&kubernetes.Client{
Client: clientset, Client: clientset,
}, nil, &analysisResults) },
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := ingressAnalyzer.Analyze(config)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Error(err)
} }
assert.Equal(t, len(analysisResults), 2) assert.Equal(t, len(analysisResults), 2)
} }
@ -80,16 +83,17 @@ func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
}) })
ingressAnalyzer := IngressAnalyzer{} ingressAnalyzer := IngressAnalyzer{}
var analysisResults []Analysis config := Analyzer{
err := ingressAnalyzer.RunAnalysis(context.Background(), Client: &kubernetes.Client{
&AnalysisConfiguration{
Namespace: "default",
},
&kubernetes.Client{
Client: clientset, Client: clientset,
}, nil, &analysisResults) },
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := ingressAnalyzer.Analyze(config)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Error(err)
} }
var errorFound bool var errorFound bool

View File

@ -1,23 +1,18 @@
package analyzer package analyzer
import ( import (
"context"
"fmt" "fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type PdbAnalyzer struct{} type PdbAnalyzer struct{}
func (PdbAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) {
analysisResults *[]Analysis) 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 { if err != nil {
return err return nil, err
} }
var preAnalysis = map[string]PreAnalysis{} var preAnalysis = map[string]PreAnalysis{}
@ -25,7 +20,7 @@ func (PdbAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio
for _, pdb := range list.Items { for _, pdb := range list.Items {
var failures []string 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 { if err != nil || evt == nil {
continue continue
} }
@ -52,16 +47,16 @@ func (PdbAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio
} }
for key, value := range preAnalysis { for key, value := range preAnalysis {
var currentAnalysis = Analysis{ var currentAnalysis = Result{
Kind: "PodDisruptionBudget", Kind: "PodDisruptionBudget",
Name: key, Name: key,
Error: value.FailureDetails, Error: value.FailureDetails,
} }
parent, _ := util.GetParent(client, value.PodDisruptionBudget.ObjectMeta) parent, _ := util.GetParent(a.Client, value.PodDisruptionBudget.ObjectMeta)
currentAnalysis.ParentObject = parent currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis) a.Results = append(a.Results, currentAnalysis)
} }
return nil return a.Results, err
} }

View File

@ -1,24 +1,19 @@
package analyzer package analyzer
import ( import (
"context"
"fmt" "fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type PodAnalyzer struct{} type PodAnalyzer struct {
}
func (PodAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error {
func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
// search all namespaces for pods that are not running // 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 { if err != nil {
return err return nil, err
} }
var preAnalysis = map[string]PreAnalysis{} 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" { if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" {
// parse the event log and append details // 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 { if err != nil || evt == nil {
continue continue
} }
@ -68,16 +63,16 @@ func (PodAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio
} }
for key, value := range preAnalysis { for key, value := range preAnalysis {
var currentAnalysis = Analysis{ var currentAnalysis = Result{
Kind: "Pod", Kind: "Pod",
Name: key, Name: key,
Error: value.FailureDetails, Error: value.FailureDetails,
} }
parent, _ := util.GetParent(client, value.Pod.ObjectMeta) parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
currentAnalysis.ParentObject = parent currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis) a.Results = append(a.Results, currentAnalysis)
} }
return nil return a.Results, nil
} }

View File

@ -11,7 +11,7 @@ import (
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
) )
func TestPodAnalzyer(t *testing.T) { func TestPodAnalyzer(t *testing.T) {
clientset := fake.NewSimpleClientset(&v1.Pod{ clientset := fake.NewSimpleClientset(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -31,17 +31,18 @@ func TestPodAnalzyer(t *testing.T) {
}, },
}) })
podAnalyzer := PodAnalyzer{} config := Analyzer{
var analysisResults []Analysis Client: &kubernetes.Client{
err := podAnalyzer.RunAnalysis(context.Background(),
&AnalysisConfiguration{
Namespace: "default",
},
&kubernetes.Client{
Client: clientset, Client: clientset,
}, nil, &analysisResults) },
Context: context.Background(),
Namespace: "default",
}
podAnalyzer := PodAnalyzer{}
var analysisResults []Result
analysisResults, err := podAnalyzer.Analyze(config)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Error(err)
} }
assert.Equal(t, len(analysisResults), 1) assert.Equal(t, len(analysisResults), 1)
} }

View File

@ -1,23 +1,19 @@
package analyzer package analyzer
import ( import (
"context"
"fmt" "fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type PvcAnalyzer struct{} 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 // 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 { if err != nil {
return err return nil, err
} }
var preAnalysis = map[string]PreAnalysis{} var preAnalysis = map[string]PreAnalysis{}
@ -29,7 +25,7 @@ func (PvcAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio
if pvc.Status.Phase == "Pending" { if pvc.Status.Phase == "Pending" {
// parse the event log and append details // 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 { if err != nil || evt == nil {
continue continue
} }
@ -46,16 +42,16 @@ func (PvcAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguratio
} }
for key, value := range preAnalysis { for key, value := range preAnalysis {
var currentAnalysis = Analysis{ var currentAnalysis = Result{
Kind: "PersistentVolumeClaim", Kind: "PersistentVolumeClaim",
Name: key, Name: key,
Error: value.FailureDetails, Error: value.FailureDetails,
} }
parent, _ := util.GetParent(client, value.PersistentVolumeClaim.ObjectMeta) parent, _ := util.GetParent(a.Client, value.PersistentVolumeClaim.ObjectMeta)
currentAnalysis.ParentObject = parent currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis) a.Results = append(a.Results, currentAnalysis)
} }
return nil return a.Results, nil
} }

View File

@ -1,24 +1,19 @@
package analyzer package analyzer
import ( import (
"context"
"fmt" "fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type ReplicaSetAnalyzer struct{} type ReplicaSetAnalyzer struct{}
func (ReplicaSetAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running // 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 { if err != nil {
return err return nil, err
} }
var preAnalysis = map[string]PreAnalysis{} var preAnalysis = map[string]PreAnalysis{}
@ -45,16 +40,15 @@ func (ReplicaSetAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfi
} }
for key, value := range preAnalysis { for key, value := range preAnalysis {
var currentAnalysis = Analysis{ var currentAnalysis = Result{
Kind: "ReplicaSet", Kind: "ReplicaSet",
Name: key, Name: key,
Error: value.FailureDetails, Error: value.FailureDetails,
} }
parent, _ := util.GetParent(client, value.ReplicaSet.ObjectMeta) parent, _ := util.GetParent(a.Client, value.ReplicaSet.ObjectMeta)
currentAnalysis.ParentObject = parent currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis) a.Results = append(a.Results, currentAnalysis)
} }
return a.Results, nil
return nil
} }

View File

@ -1,25 +1,20 @@
package analyzer package analyzer
import ( import (
"context"
"fmt" "fmt"
"github.com/fatih/color" "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" "github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type ServiceAnalyzer struct{} type ServiceAnalyzer struct{}
func (ServiceAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running // 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 { if err != nil {
return err return nil, err
} }
var preAnalysis = map[string]PreAnalysis{} var preAnalysis = map[string]PreAnalysis{}
@ -29,7 +24,7 @@ func (ServiceAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfigur
// Check for empty service // Check for empty service
if len(ep.Subsets) == 0 { 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 { if err != nil {
color.Yellow("Service %s/%s does not exist", ep.Namespace, ep.Name) color.Yellow("Service %s/%s does not exist", ep.Namespace, ep.Name)
continue continue
@ -63,15 +58,15 @@ func (ServiceAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfigur
} }
for key, value := range preAnalysis { for key, value := range preAnalysis {
var currentAnalysis = Analysis{ var currentAnalysis = Result{
Kind: "Service", Kind: "Service",
Name: key, Name: key,
Error: value.FailureDetails, Error: value.FailureDetails,
} }
parent, _ := util.GetParent(client, value.Endpoint.ObjectMeta) parent, _ := util.GetParent(a.Client, value.Endpoint.ObjectMeta)
currentAnalysis.ParentObject = parent currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis) a.Results = append(a.Results, currentAnalysis)
} }
return nil return a.Results, nil
} }

View File

@ -11,7 +11,7 @@ import (
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
) )
func TestServiceAnalzyer(t *testing.T) { func TestServiceAnalyzer(t *testing.T) {
clientset := fake.NewSimpleClientset(&v1.Endpoints{ clientset := fake.NewSimpleClientset(&v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -32,17 +32,18 @@ func TestServiceAnalzyer(t *testing.T) {
}, },
}}) }})
serviceAnalyzer := ServiceAnalyzer{} config := Analyzer{
var analysisResults []Analysis Client: &kubernetes.Client{
err := serviceAnalyzer.RunAnalysis(context.Background(),
&AnalysisConfiguration{
Namespace: "default",
},
&kubernetes.Client{
Client: clientset, Client: clientset,
}, nil, &analysisResults) },
Context: context.Background(),
Namespace: "default",
}
serviceAnalyzer := ServiceAnalyzer{}
analysisResults, err := serviceAnalyzer.Analyze(config)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Error(err)
} }
assert.Equal(t, len(analysisResults), 1) assert.Equal(t, len(analysisResults), 1)
} }

View File

@ -1,17 +1,23 @@
package analyzer package analyzer
import ( import (
"context"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1" autov1 "k8s.io/api/autoscaling/v1"
v1 "k8s.io/api/core/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" policyv1 "k8s.io/api/policy/v1"
) )
type AnalysisConfiguration struct { type Analyzer struct {
Client *kubernetes.Client
Context context.Context
Namespace string Namespace string
NoCache bool AIClient ai.IAI
Explain bool PreAnalysis map[string]PreAnalysis
Results []Result
} }
type PreAnalysis struct { type PreAnalysis struct {
@ -20,12 +26,12 @@ type PreAnalysis struct {
ReplicaSet appsv1.ReplicaSet ReplicaSet appsv1.ReplicaSet
PersistentVolumeClaim v1.PersistentVolumeClaim PersistentVolumeClaim v1.PersistentVolumeClaim
Endpoint v1.Endpoints Endpoint v1.Endpoints
Ingress networkingv1.Ingress Ingress networkv1.Ingress
HorizontalPodAutoscalers autoscalingv1.HorizontalPodAutoscaler HorizontalPodAutoscalers autov1.HorizontalPodAutoscaler
PodDisruptionBudget policyv1.PodDisruptionBudget PodDisruptionBudget policyv1.PodDisruptionBudget
} }
type Analysis struct { type Result struct {
Kind string `json:"kind"` Kind string `json:"kind"`
Name string `json:"name"` Name string `json:"name"`
Error []string `json:"error"` Error []string `json:"error"`