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"
"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") {

View File

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

View File

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

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
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)

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"`