mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
Have all in-cluster analysers implement the same interface. This will help with the implementation of code that requires making calls to all analysers Fixes #995
152 lines
5.0 KiB
Go
152 lines
5.0 KiB
Go
package analyzer
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/pkg/errors"
|
|
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
|
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
)
|
|
|
|
type AnalyzeDeploymentStatus struct {
|
|
analyzer *troubleshootv1beta2.DeploymentStatus
|
|
}
|
|
|
|
func (a *AnalyzeDeploymentStatus) Title() string {
|
|
if a.analyzer.CheckName != "" {
|
|
return a.analyzer.CheckName
|
|
}
|
|
|
|
if a.analyzer.Name != "" && a.analyzer.Namespace != "" {
|
|
return fmt.Sprintf("%s/%s Deployment Status", a.analyzer.Name, a.analyzer.Name)
|
|
}
|
|
|
|
if a.analyzer.Name != "" {
|
|
return fmt.Sprintf("%s Deployment Status", a.analyzer.Name)
|
|
}
|
|
if a.analyzer.Namespace != "" {
|
|
return fmt.Sprintf("%s Deployment Status", a.analyzer.Namespace)
|
|
}
|
|
|
|
return "Deployment Status"
|
|
}
|
|
|
|
func (a *AnalyzeDeploymentStatus) IsExcluded() (bool, error) {
|
|
return isExcluded(a.analyzer.Exclude)
|
|
}
|
|
|
|
func (a *AnalyzeDeploymentStatus) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
|
results, err := analyzeDeploymentStatus(a.analyzer, findFiles)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i := range results {
|
|
results[i].Strict = a.analyzer.Strict.BoolOrDefaultFalse()
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func analyzeDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
|
if analyzer.Name == "" {
|
|
return analyzeAllDeploymentStatuses(analyzer, getFileContents)
|
|
} else {
|
|
return analyzeOneDeploymentStatus(analyzer, getFileContents)
|
|
}
|
|
}
|
|
|
|
func analyzeOneDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
|
excludeFiles := []string{}
|
|
files, err := getFileContents(filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_DEPLOYMENTS, fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to read collected deployments from namespace")
|
|
}
|
|
|
|
var result *AnalyzeResult
|
|
for _, collected := range files { // only 1 file here
|
|
var exists bool = true
|
|
var readyReplicas int
|
|
|
|
var deployments appsv1.DeploymentList
|
|
if err := json.Unmarshal(collected, &deployments); err != nil {
|
|
return nil, errors.Wrap(err, "failed to unmarshal deployment list")
|
|
}
|
|
|
|
var status *appsv1.DeploymentStatus
|
|
for _, deployment := range deployments.Items {
|
|
if deployment.Name == analyzer.Name {
|
|
status = deployment.Status.DeepCopy()
|
|
}
|
|
}
|
|
|
|
if status == nil {
|
|
exists = false
|
|
readyReplicas = 0
|
|
} else {
|
|
readyReplicas = int(status.ReadyReplicas)
|
|
}
|
|
|
|
result, err = commonStatus(analyzer.Outcomes, analyzer.Name, "kubernetes_deployment_status", "https://troubleshoot.sh/images/analyzer-icons/deployment-status.svg?w=17&h=17", readyReplicas, exists, "deployment")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to process status")
|
|
}
|
|
}
|
|
|
|
if result == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return []*AnalyzeResult{result}, nil
|
|
}
|
|
|
|
func analyzeAllDeploymentStatuses(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
|
excludeFiles := []string{}
|
|
fileNames := make([]string, 0)
|
|
if analyzer.Namespace != "" {
|
|
fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_DEPLOYMENTS, fmt.Sprintf("%s.json", analyzer.Namespace)))
|
|
}
|
|
for _, ns := range analyzer.Namespaces {
|
|
fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_DEPLOYMENTS, fmt.Sprintf("%s.json", ns)))
|
|
}
|
|
|
|
// no namespace specified, so we need to analyze all deployments
|
|
if len(fileNames) == 0 {
|
|
fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_DEPLOYMENTS, "*.json"))
|
|
}
|
|
|
|
results := []*AnalyzeResult{}
|
|
for _, fileName := range fileNames {
|
|
files, err := getFileContents(fileName, excludeFiles)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to read collected deployments from file")
|
|
}
|
|
|
|
for _, collected := range files {
|
|
var deployments appsv1.DeploymentList
|
|
if err := json.Unmarshal(collected, &deployments); err != nil {
|
|
return nil, errors.Wrap(err, "failed to unmarshal deployment list")
|
|
}
|
|
|
|
for _, deployment := range deployments.Items {
|
|
if deployment.Status.Replicas == deployment.Status.AvailableReplicas {
|
|
continue
|
|
}
|
|
|
|
result := &AnalyzeResult{
|
|
Title: fmt.Sprintf("%s/%s Deployment Status", deployment.Namespace, deployment.Name),
|
|
IconKey: "kubernetes_deployment_status",
|
|
IconURI: "https://troubleshoot.sh/images/analyzer-icons/deployment-status.svg?w=17&h=17",
|
|
IsFail: true,
|
|
Message: fmt.Sprintf("The deployment %s/%s has %d/%d replicas", deployment.Namespace, deployment.Name, deployment.Status.ReadyReplicas, deployment.Status.Replicas),
|
|
}
|
|
|
|
results = append(results, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|