diff --git a/cmd/analyze/analyze.go b/cmd/analyze/analyze.go index cc159d157c..c46953fc17 100644 --- a/cmd/analyze/analyze.go +++ b/cmd/analyze/analyze.go @@ -79,8 +79,7 @@ var AnalyzeCmd = &cobra.Command{ } fmt.Println(string(j)) default: - fmt.Printf("%s %s: %s \n%s\n", color.CyanString("%d", n), color.YellowString(analysis.Name), color.RedString(analysis.Error), color.GreenString(analysis.Details)) - + fmt.Printf("%s %s(%s): %s \n%s\n", color.CyanString("%d", n), color.YellowString(analysis.Name), color.CyanString(analysis.ParentObject), color.RedString(analysis.Error), color.GreenString(analysis.Details)) } } diff --git a/pkg/analyzer/analysis.go b/pkg/analyzer/analysis.go index b62792591c..7a35075e29 100644 --- a/pkg/analyzer/analysis.go +++ b/pkg/analyzer/analysis.go @@ -1,8 +1,20 @@ package analyzer +import ( + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" +) + +type PreAnalysis struct { + Pod v1.Pod + FailureDetails []string + ReplicaSet appsv1.ReplicaSet +} + type Analysis struct { - Kind string - Name string - Error string - Details string + Kind string `json:"kind"` + Name string `json:"name"` + Error string `json:"error"` + Details string `json:"details"` + ParentObject string `json:"parentObject"` } diff --git a/pkg/analyzer/podAnalyzer.go b/pkg/analyzer/podAnalyzer.go index 354677f319..5b5b839c12 100644 --- a/pkg/analyzer/podAnalyzer.go +++ b/pkg/analyzer/podAnalyzer.go @@ -22,30 +22,30 @@ func AnalyzePod(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, if err != nil { return err } - - var brokenPods = map[string][]string{} + var preAnalysis = map[string]PreAnalysis{} for _, pod := range list.Items { - + var failures []string // Check for pending pods if pod.Status.Phase == "Pending" { // Check through container status to check for crashes for _, containerStatus := range pod.Status.Conditions { if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" { - brokenPods[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = []string{containerStatus.Message} + if containerStatus.Message != "" { + failures = []string{containerStatus.Message} + } } } } // Check through container status to check for crashes - var failureDetails = []string{} for _, containerStatus := range pod.Status.ContainerStatuses { if containerStatus.State.Waiting != nil { if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" || containerStatus.State.Waiting.Reason == "ImagePullBackOff" { - - failureDetails = append(failureDetails, containerStatus.State.Waiting.Message) - brokenPods[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = failureDetails + if containerStatus.State.Waiting.Message != "" { + failures = append(failures, containerStatus.State.Waiting.Message) + } } // This represents a container that is still being created or blocked due to conditions such as OOMKilled if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" { @@ -55,24 +55,30 @@ func AnalyzePod(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, if err != nil || evt == nil { continue } - if evt.Reason == "FailedCreatePodSandBox" { - failureDetails = append(failureDetails, evt.Message) - brokenPods[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = failureDetails + if evt.Reason == "FailedCreatePodSandBox" && evt.Message != "" { + failures = append(failures, evt.Message) } } - } } - + if len(failures) > 0 { + preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = PreAnalysis{ + Pod: pod, + FailureDetails: failures, + } + } } - for key, value := range brokenPods { - inputValue := strings.Join(value, " ") + for key, value := range preAnalysis { + inputValue := strings.Join(value.FailureDetails, " ") var currentAnalysis = Analysis{ Kind: "Pod", Name: key, - Error: value[0], + Error: value.FailureDetails[0], } + + parent, _ := getParent(client, value.Pod.ObjectMeta) + if explain { s := spinner.New(spinner.CharSets[35], 100*time.Millisecond) // Build our new spinner s.Start() @@ -94,6 +100,7 @@ func AnalyzePod(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, continue } currentAnalysis.Details = string(output) + currentAnalysis.ParentObject = parent *analysisResults = append(*analysisResults, currentAnalysis) continue } @@ -112,10 +119,59 @@ func AnalyzePod(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, } } currentAnalysis.Details = response - } + currentAnalysis.ParentObject = parent *analysisResults = append(*analysisResults, currentAnalysis) } return nil } + +func getParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool) { + if meta.OwnerReferences != nil { + for _, owner := range meta.OwnerReferences { + switch owner.Kind { + case "ReplicaSet": + rs, err := client.GetClient().AppsV1().ReplicaSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{}) + if err != nil { + return "", false + } + if rs.OwnerReferences != nil { + return getParent(client, rs.ObjectMeta) + } + return "ReplicaSet/" + rs.Name, false + + case "Deployment": + dep, err := client.GetClient().AppsV1().Deployments(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{}) + if err != nil { + return "", false + } + if dep.OwnerReferences != nil { + return getParent(client, dep.ObjectMeta) + } + return "Deployment/" + dep.Name, false + + case "StatefulSet": + sts, err := client.GetClient().AppsV1().StatefulSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{}) + if err != nil { + return "", false + } + if sts.OwnerReferences != nil { + return getParent(client, sts.ObjectMeta) + } + return "StatefulSet/" + sts.Name, false + + case "DaemonSet": + ds, err := client.GetClient().AppsV1().DaemonSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{}) + if err != nil { + return "", false + } + if ds.OwnerReferences != nil { + return getParent(client, ds.ObjectMeta) + } + return "DaemonSet/" + ds.Name, false + } + } + } + return meta.Name, false +} diff --git a/pkg/analyzer/rsAnalyzer.go b/pkg/analyzer/rsAnalyzer.go index bebde099f7..94e5bcea56 100644 --- a/pkg/analyzer/rsAnalyzer.go +++ b/pkg/analyzer/rsAnalyzer.go @@ -23,9 +23,10 @@ func AnalyzeReplicaSet(ctx context.Context, client *kubernetes.Client, aiClient return err } - var brokenRS = map[string][]string{} + var preAnalysis = map[string]PreAnalysis{} for _, rs := range list.Items { + var failures []string // Check for empty rs if rs.Status.Replicas == 0 { @@ -33,24 +34,32 @@ func AnalyzeReplicaSet(ctx context.Context, client *kubernetes.Client, aiClient // Check through container status to check for crashes for _, rsStatus := range rs.Status.Conditions { if rsStatus.Type == "ReplicaFailure" && rsStatus.Reason == "FailedCreate" { - brokenRS[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = []string{rsStatus.Message} + failures = []string{rsStatus.Message} } } } + if len(failures) > 0 { + preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = PreAnalysis{ + ReplicaSet: rs, + FailureDetails: failures, + } + } } - for key, value := range brokenRS { + for key, value := range preAnalysis { var currentAnalysis = Analysis{ Kind: "ReplicaSet", Name: key, - Error: value[0], + Error: value.FailureDetails[0], } + parent, _ := getParent(client, value.ReplicaSet.ObjectMeta) + if explain { s := spinner.New(spinner.CharSets[35], 100*time.Millisecond) // Build our new spinner s.Start() - inputValue := strings.Join(value, " ") + inputValue := strings.Join(value.FailureDetails, " ") // Check for cached data sEnc := base64.StdEncoding.EncodeToString([]byte(inputValue)) @@ -69,6 +78,7 @@ func AnalyzeReplicaSet(ctx context.Context, client *kubernetes.Client, aiClient continue } currentAnalysis.Details = string(output) + currentAnalysis.ParentObject = parent *analysisResults = append(*analysisResults, currentAnalysis) continue } @@ -88,6 +98,7 @@ func AnalyzeReplicaSet(ctx context.Context, client *kubernetes.Client, aiClient } currentAnalysis.Details = response } + currentAnalysis.ParentObject = parent *analysisResults = append(*analysisResults, currentAnalysis) }