mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2025-05-10 17:16:06 +00:00
feat: add anonymization flag
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
This commit is contained in:
parent
9423b53c1d
commit
d2a84ea2b5
@ -21,6 +21,7 @@ var (
|
||||
language string
|
||||
nocache bool
|
||||
namespace string
|
||||
anonymize bool
|
||||
)
|
||||
|
||||
// AnalyzeCmd represents the problems command
|
||||
@ -85,7 +86,7 @@ var AnalyzeCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if explain {
|
||||
err := config.GetAIResults(output)
|
||||
err := config.GetAIResults(output, anonymize)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
@ -113,6 +114,8 @@ func init() {
|
||||
AnalyzeCmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace to analyze")
|
||||
// no cache flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&nocache, "no-cache", "c", false, "Do not use cached data")
|
||||
// anonymize flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&anonymize, "anonymize", "a", false, "Anonymize data")
|
||||
// array of strings flag
|
||||
AnalyzeCmd.Flags().StringSliceVarP(&filters, "filter", "f", []string{}, "Filter for these analyzers (e.g. Pod, PersistentVolumeClaim, Service, ReplicaSet)")
|
||||
// explain flag
|
||||
|
@ -55,7 +55,6 @@ func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string
|
||||
}
|
||||
|
||||
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))
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"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/util"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@ -124,13 +125,13 @@ func (a *Analysis) PrintOutput() {
|
||||
fmt.Printf("%s %s(%s)\n", color.CyanString("%d", n),
|
||||
color.YellowString(result.Name), color.CyanString(result.ParentObject))
|
||||
for _, err := range result.Error {
|
||||
fmt.Printf("- %s %s\n", color.RedString("Error:"), color.RedString(err))
|
||||
fmt.Printf("- %s %s\n", color.RedString("Error:"), color.RedString(err.Text))
|
||||
}
|
||||
fmt.Println(color.GreenString(result.Details + "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Analysis) GetAIResults(output string) error {
|
||||
func (a *Analysis) GetAIResults(output string, anonymize bool) error {
|
||||
if len(a.Results) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -141,7 +142,17 @@ func (a *Analysis) GetAIResults(output string) error {
|
||||
}
|
||||
|
||||
for index, analysis := range a.Results {
|
||||
parsedText, err := a.AIClient.Parse(a.Context, analysis.Error, a.NoCache)
|
||||
var texts []string
|
||||
|
||||
for _, failure := range analysis.Error {
|
||||
for _, s := range failure.Sensitive {
|
||||
if anonymize {
|
||||
failure.Text = util.ReplaceIfMatch(failure.Text, s.Unmasked, s.Masked)
|
||||
}
|
||||
}
|
||||
texts = append(texts, failure.Text)
|
||||
}
|
||||
parsedText, err := a.AIClient.Parse(a.Context, texts, a.NoCache)
|
||||
if err != nil {
|
||||
// Check for exhaustion
|
||||
if strings.Contains(err.Error(), "status code: 429") {
|
||||
@ -151,6 +162,15 @@ func (a *Analysis) GetAIResults(output string) error {
|
||||
color.Red("Error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if anonymize {
|
||||
for _, failure := range analysis.Error {
|
||||
for _, s := range failure.Sensitive {
|
||||
parsedText = strings.ReplaceAll(parsedText, s.Masked, s.Unmasked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
analysis.Details = parsedText
|
||||
if output != "json" {
|
||||
bar.Add(1)
|
||||
|
@ -3,9 +3,10 @@ package analysis
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
|
||||
@ -42,11 +43,16 @@ func TestAnalysis_ProblemJsonOutput(t *testing.T) {
|
||||
analysis := Analysis{
|
||||
Results: []analyzer.Result{
|
||||
{
|
||||
"Deployment",
|
||||
"test-deployment",
|
||||
[]string{"test-problem"},
|
||||
"test-solution",
|
||||
"parent-resource"},
|
||||
Kind: "Deployment",
|
||||
Name: "test-deployment",
|
||||
Error: []analyzer.Failure{
|
||||
{
|
||||
Text: "test-problem",
|
||||
Sensitive: []analyzer.Sensitive{},
|
||||
},
|
||||
},
|
||||
Details: "test-solution",
|
||||
ParentObject: "parent-resource"},
|
||||
},
|
||||
Namespace: "default",
|
||||
}
|
||||
@ -55,11 +61,17 @@ func TestAnalysis_ProblemJsonOutput(t *testing.T) {
|
||||
Status: StateProblemDetected,
|
||||
Problems: 1,
|
||||
Results: []analyzer.Result{
|
||||
{"Deployment",
|
||||
"test-deployment",
|
||||
[]string{"test-problem"},
|
||||
"test-solution",
|
||||
"parent-resource"},
|
||||
{
|
||||
Kind: "Deployment",
|
||||
Name: "test-deployment",
|
||||
Error: []analyzer.Failure{
|
||||
{
|
||||
Text: "test-problem",
|
||||
Sensitive: []analyzer.Sensitive{},
|
||||
},
|
||||
},
|
||||
Details: "test-solution",
|
||||
ParentObject: "parent-resource"},
|
||||
},
|
||||
}
|
||||
|
||||
@ -84,11 +96,20 @@ func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
|
||||
analysis := Analysis{
|
||||
Results: []analyzer.Result{
|
||||
{
|
||||
"Deployment",
|
||||
"test-deployment",
|
||||
[]string{"test-problem", "another-test-problem"},
|
||||
"test-solution",
|
||||
"parent-resource"},
|
||||
Kind: "Deployment",
|
||||
Name: "test-deployment",
|
||||
Error: []analyzer.Failure{
|
||||
{
|
||||
Text: "test-problem",
|
||||
Sensitive: []analyzer.Sensitive{},
|
||||
},
|
||||
{
|
||||
Text: "another-test-problem",
|
||||
Sensitive: []analyzer.Sensitive{},
|
||||
},
|
||||
},
|
||||
Details: "test-solution",
|
||||
ParentObject: "parent-resource"},
|
||||
},
|
||||
Namespace: "default",
|
||||
}
|
||||
@ -97,11 +118,21 @@ func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
|
||||
Status: StateProblemDetected,
|
||||
Problems: 2,
|
||||
Results: []analyzer.Result{
|
||||
{"Deployment",
|
||||
"test-deployment",
|
||||
[]string{"test-problem", "another-test-problem"},
|
||||
"test-solution",
|
||||
"parent-resource"},
|
||||
{
|
||||
Kind: "Deployment",
|
||||
Name: "test-deployment",
|
||||
Error: []analyzer.Failure{
|
||||
{
|
||||
Text: "test-problem",
|
||||
Sensitive: []analyzer.Sensitive{},
|
||||
},
|
||||
{
|
||||
Text: "another-test-problem",
|
||||
Sensitive: []analyzer.Sensitive{},
|
||||
},
|
||||
},
|
||||
Details: "test-solution",
|
||||
ParentObject: "parent-resource"},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@ -18,7 +19,7 @@ func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
var preAnalysis = map[string]PreAnalysis{}
|
||||
|
||||
for _, hpa := range list.Items {
|
||||
var failures []string
|
||||
var failures []Failure
|
||||
|
||||
// check ScaleTargetRef exist
|
||||
scaleTargetRef := hpa.Spec.ScaleTargetRef
|
||||
@ -46,11 +47,22 @@ func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
scaleTargetRefNotFound = true
|
||||
}
|
||||
default:
|
||||
failures = append(failures, fmt.Sprintf("HorizontalPodAutoscaler uses %s as ScaleTargetRef which does not possible option.", scaleTargetRef.Kind))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("HorizontalPodAutoscaler uses %s as ScaleTargetRef which does not possible option.", scaleTargetRef.Kind),
|
||||
Sensitive: []Sensitive{},
|
||||
})
|
||||
}
|
||||
|
||||
if scaleTargetRefNotFound {
|
||||
failures = append(failures, fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name),
|
||||
Sensitive: []Sensitive{
|
||||
Sensitive{
|
||||
Unmasked: scaleTargetRef.Name,
|
||||
Masked: util.MaskString(scaleTargetRef.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
|
@ -101,7 +101,7 @@ func TestHPAAnalyzerWithUnsuportedScaleTargetRef(t *testing.T) {
|
||||
var errorFound bool
|
||||
for _, analysis := range analysisResults {
|
||||
for _, err := range analysis.Error {
|
||||
if strings.Contains(err, "does not possible option.") {
|
||||
if strings.Contains(err.Text, "does not possible option.") {
|
||||
errorFound = true
|
||||
break
|
||||
}
|
||||
@ -148,7 +148,7 @@ func TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {
|
||||
var errorFound bool
|
||||
for _, analysis := range analysisResults {
|
||||
for _, err := range analysis.Error {
|
||||
if strings.Contains(err, "does not exist.") {
|
||||
if strings.Contains(err.Text, "does not exist.") {
|
||||
errorFound = true
|
||||
break
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@ -18,14 +19,26 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
var preAnalysis = map[string]PreAnalysis{}
|
||||
|
||||
for _, ing := range list.Items {
|
||||
var failures []string
|
||||
var failures []Failure
|
||||
|
||||
// get ingressClassName
|
||||
ingressClassName := ing.Spec.IngressClassName
|
||||
if ingressClassName == nil {
|
||||
ingClassValue := ing.Annotations["kubernetes.io/ingress.class"]
|
||||
if ingClassValue == "" {
|
||||
failures = append(failures, fmt.Sprintf("Ingress %s/%s does not specify an Ingress class.", ing.Namespace, ing.Name))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("Ingress %s/%s does not specify an Ingress class.", ing.Namespace, ing.Name),
|
||||
Sensitive: []Sensitive{
|
||||
{
|
||||
Unmasked: ing.Namespace,
|
||||
Masked: util.MaskString(ing.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: ing.Name,
|
||||
Masked: util.MaskString(ing.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
ingressClassName = &ingClassValue
|
||||
}
|
||||
@ -35,7 +48,15 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
if ingressClassName != nil {
|
||||
_, 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))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("Ingress uses the ingress class %s which does not exist.", *ingressClassName),
|
||||
Sensitive: []Sensitive{
|
||||
{
|
||||
Unmasked: *ingressClassName,
|
||||
Masked: util.MaskString(*ingressClassName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +66,19 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
_, 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))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("Ingress uses the service %s/%s which does not exist.", ing.Namespace, path.Backend.Service.Name),
|
||||
Sensitive: []Sensitive{
|
||||
{
|
||||
Unmasked: ing.Namespace,
|
||||
Masked: util.MaskString(ing.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: path.Backend.Service.Name,
|
||||
Masked: util.MaskString(path.Backend.Service.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +86,19 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
for _, tls := range ing.Spec.TLS {
|
||||
_, 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))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("Ingress uses the secret %s/%s as a TLS certificate which does not exist.", ing.Namespace, tls.SecretName),
|
||||
Sensitive: []Sensitive{
|
||||
{
|
||||
Unmasked: ing.Namespace,
|
||||
Masked: util.MaskString(ing.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: tls.SecretName,
|
||||
Masked: util.MaskString(tls.SecretName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
|
@ -99,7 +99,7 @@ func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
|
||||
var errorFound bool
|
||||
for _, analysis := range analysisResults {
|
||||
for _, err := range analysis.Error {
|
||||
if strings.Contains(err, "does not specify an Ingress class") {
|
||||
if strings.Contains(err.Text, "does not specify an Ingress class") {
|
||||
errorFound = true
|
||||
break
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@ -18,7 +19,7 @@ func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
var preAnalysis = map[string]PreAnalysis{}
|
||||
|
||||
for _, pdb := range list.Items {
|
||||
var failures []string
|
||||
var failures []Failure
|
||||
|
||||
evt, err := FetchLatestEvent(a.Context, a.Client, pdb.Namespace, pdb.Name)
|
||||
if err != nil || evt == nil {
|
||||
@ -28,13 +29,31 @@ func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
if evt.Reason == "NoPods" && evt.Message != "" {
|
||||
if pdb.Spec.Selector != nil {
|
||||
for k, v := range pdb.Spec.Selector.MatchLabels {
|
||||
failures = append(failures, fmt.Sprintf("%s, expected label %s=%s", evt.Message, k, v))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("%s, expected label %s=%s", evt.Message, k, v),
|
||||
Sensitive: []Sensitive{
|
||||
Sensitive{
|
||||
Unmasked: k,
|
||||
Masked: util.MaskString(k),
|
||||
},
|
||||
Sensitive{
|
||||
Unmasked: v,
|
||||
Masked: util.MaskString(v),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
for _, v := range pdb.Spec.Selector.MatchExpressions {
|
||||
failures = append(failures, fmt.Sprintf("%s, expected expression %s", evt.Message, v))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("%s, expected expression %s=%s", evt.Message, v),
|
||||
Sensitive: []Sensitive{},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
failures = append(failures, fmt.Sprintf("%s, selector is nil", evt.Message))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("%s, selector is nil", evt.Message),
|
||||
Sensitive: []Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@ -18,7 +19,7 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
var preAnalysis = map[string]PreAnalysis{}
|
||||
|
||||
for _, pod := range list.Items {
|
||||
var failures []string
|
||||
var failures []Failure
|
||||
// Check for pending pods
|
||||
if pod.Status.Phase == "Pending" {
|
||||
|
||||
@ -26,7 +27,10 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
for _, containerStatus := range pod.Status.Conditions {
|
||||
if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" {
|
||||
if containerStatus.Message != "" {
|
||||
failures = []string{containerStatus.Message}
|
||||
failures = append(failures, Failure{
|
||||
Text: containerStatus.Message,
|
||||
Sensitive: []Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,7 +41,10 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
if containerStatus.State.Waiting != nil {
|
||||
if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" || containerStatus.State.Waiting.Reason == "ImagePullBackOff" {
|
||||
if containerStatus.State.Waiting.Message != "" {
|
||||
failures = append(failures, containerStatus.State.Waiting.Message)
|
||||
failures = append(failures, Failure{
|
||||
Text: containerStatus.State.Waiting.Message,
|
||||
Sensitive: []Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
|
||||
@ -49,7 +56,10 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
continue
|
||||
}
|
||||
if evt.Reason == "FailedCreatePodSandBox" && evt.Message != "" {
|
||||
failures = append(failures, evt.Message)
|
||||
failures = append(failures, Failure{
|
||||
Text: evt.Message,
|
||||
Sensitive: []Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@ -19,7 +20,7 @@ func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
var preAnalysis = map[string]PreAnalysis{}
|
||||
|
||||
for _, pvc := range list.Items {
|
||||
var failures []string
|
||||
var failures []Failure
|
||||
|
||||
// Check for empty rs
|
||||
if pvc.Status.Phase == "Pending" {
|
||||
@ -30,7 +31,10 @@ func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
continue
|
||||
}
|
||||
if evt.Reason == "ProvisioningFailed" && evt.Message != "" {
|
||||
failures = append(failures, evt.Message)
|
||||
failures = append(failures, Failure{
|
||||
Text: evt.Message,
|
||||
Sensitive: []Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
|
@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@ -19,7 +20,7 @@ func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
var preAnalysis = map[string]PreAnalysis{}
|
||||
|
||||
for _, rs := range list.Items {
|
||||
var failures []string
|
||||
var failures []Failure
|
||||
|
||||
// Check for empty rs
|
||||
if rs.Status.Replicas == 0 {
|
||||
@ -27,7 +28,11 @@ func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
// Check through container status to check for crashes
|
||||
for _, rsStatus := range rs.Status.Conditions {
|
||||
if rsStatus.Type == "ReplicaFailure" && rsStatus.Reason == "FailedCreate" {
|
||||
failures = []string{rsStatus.Message}
|
||||
failures = append(failures, Failure{
|
||||
Text: rsStatus.Message,
|
||||
Sensitive: []Sensitive{},
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -20,7 +21,7 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
var preAnalysis = map[string]PreAnalysis{}
|
||||
|
||||
for _, ep := range list.Items {
|
||||
var failures []string
|
||||
var failures []Failure
|
||||
|
||||
// Check for empty service
|
||||
if len(ep.Subsets) == 0 {
|
||||
@ -31,7 +32,19 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
}
|
||||
|
||||
for k, v := range svc.Spec.Selector {
|
||||
failures = append(failures, fmt.Sprintf("Service has no endpoints, expected label %s=%s", k, v))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("Service has no endpoints, expected label %s=%s", k, v),
|
||||
Sensitive: []Sensitive{
|
||||
Sensitive{
|
||||
Unmasked: k,
|
||||
Masked: util.MaskString(k),
|
||||
},
|
||||
Sensitive{
|
||||
Unmasked: v,
|
||||
Masked: util.MaskString(v),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
count := 0
|
||||
@ -44,7 +57,10 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
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))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("Service has not ready endpoints, pods: %s, expected %d", pods, count),
|
||||
Sensitive: []Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,20 +17,40 @@ func (StatefulSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
|
||||
var preAnalysis = map[string]PreAnalysis{}
|
||||
|
||||
for _, sts := range list.Items {
|
||||
var failures []string
|
||||
var failures []Failure
|
||||
|
||||
// get serviceName
|
||||
serviceName := sts.Spec.ServiceName
|
||||
_, err := a.Client.GetClient().CoreV1().Services(sts.Namespace).Get(a.Context, serviceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
failures = append(failures, fmt.Sprintf("StatefulSet uses the service %s/%s which does not exist.", sts.Namespace, serviceName))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("StatefulSet uses the service %s/%s which does not exist.", sts.Namespace, serviceName),
|
||||
Sensitive: []Sensitive{
|
||||
Sensitive{
|
||||
Unmasked: sts.Namespace,
|
||||
Masked: util.MaskString(sts.Namespace),
|
||||
},
|
||||
Sensitive{
|
||||
Unmasked: serviceName,
|
||||
Masked: util.MaskString(serviceName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(sts.Spec.VolumeClaimTemplates) > 0 {
|
||||
for _, volumeClaimTemplate := range sts.Spec.VolumeClaimTemplates {
|
||||
if volumeClaimTemplate.Spec.StorageClassName != nil {
|
||||
_, err := a.Client.GetClient().StorageV1().StorageClasses().Get(a.Context, *volumeClaimTemplate.Spec.StorageClassName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
failures = append(failures, fmt.Sprintf("StatefulSet uses the storage class %s which does not exist.", *volumeClaimTemplate.Spec.StorageClassName))
|
||||
failures = append(failures, Failure{
|
||||
Text: fmt.Sprintf("StatefulSet uses the storage class %s which does not exist.", *volumeClaimTemplate.Spec.StorageClassName),
|
||||
Sensitive: []Sensitive{
|
||||
Sensitive{
|
||||
Unmasked: *volumeClaimTemplate.Spec.StorageClassName,
|
||||
Masked: util.MaskString(*volumeClaimTemplate.Spec.StorageClassName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ func TestStatefulSetAnalyzerWithoutService(t *testing.T) {
|
||||
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
@ -131,7 +131,7 @@ func TestStatefulSetAnalyzerMissingStorageClass(t *testing.T) {
|
||||
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
@ -22,7 +23,7 @@ type Analyzer struct {
|
||||
|
||||
type PreAnalysis struct {
|
||||
Pod v1.Pod
|
||||
FailureDetails []string
|
||||
FailureDetails []Failure
|
||||
ReplicaSet appsv1.ReplicaSet
|
||||
PersistentVolumeClaim v1.PersistentVolumeClaim
|
||||
Endpoint v1.Endpoints
|
||||
@ -33,9 +34,19 @@ type PreAnalysis struct {
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Error []string `json:"error"`
|
||||
Details string `json:"details"`
|
||||
ParentObject string `json:"parentObject"`
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Error []Failure `json:"error"`
|
||||
Details string `json:"details"`
|
||||
ParentObject string `json:"parentObject"`
|
||||
}
|
||||
|
||||
type Failure struct {
|
||||
Text string
|
||||
Sensitive []Sensitive
|
||||
}
|
||||
|
||||
type Sensitive struct {
|
||||
Unmasked string
|
||||
Masked string
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -96,3 +99,20 @@ func SliceDiff(source, dest []string) []string {
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func MaskString(input string) string {
|
||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
result := make([]rune, len(input))
|
||||
for i := range result {
|
||||
result[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func ReplaceIfMatch(text string, pattern string, replacement string) string {
|
||||
re := regexp.MustCompile(fmt.Sprintf(`%s(\b\s)`, pattern))
|
||||
if re.MatchString(text) {
|
||||
text = re.ReplaceAllString(text, replacement+" ")
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user