diff --git a/pkg/analyze/common_status.go b/pkg/analyze/common_status.go new file mode 100644 index 00000000..58b70d0f --- /dev/null +++ b/pkg/analyze/common_status.go @@ -0,0 +1,120 @@ +package analyzer + +import ( + "strconv" + "strings" + + "github.com/pkg/errors" + troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1" +) + +func commonStatus(outcomes []*troubleshootv1beta1.Outcome, readyReplicas int) (*AnalyzeResult, error) { + result := &AnalyzeResult{} + + // ordering from the spec is important, the first one that matches returns + for _, outcome := range outcomes { + if outcome.Fail != nil { + if outcome.Fail.When == "" { + result.IsFail = true + result.Message = outcome.Fail.Message + result.URI = outcome.Fail.URI + + return result, nil + } + + match, err := compareActualToWhen(outcome.Fail.When, readyReplicas) + if err != nil { + return nil, errors.Wrap(err, "failed to parse fail range") + } + + if match { + result.IsFail = true + result.Message = outcome.Fail.Message + result.URI = outcome.Fail.URI + + return result, nil + } + } else if outcome.Warn != nil { + if outcome.Warn.When == "" { + result.IsWarn = true + result.Message = outcome.Warn.Message + result.URI = outcome.Warn.URI + + return result, nil + } + + match, err := compareActualToWhen(outcome.Warn.When, readyReplicas) + if err != nil { + return nil, errors.Wrap(err, "failed to parse warn range") + } + + if match { + result.IsWarn = true + result.Message = outcome.Warn.Message + result.URI = outcome.Warn.URI + + return result, nil + } + } else if outcome.Pass != nil { + if outcome.Pass.When == "" { + result.IsPass = true + result.Message = outcome.Pass.Message + result.URI = outcome.Pass.URI + + return result, nil + } + + match, err := compareActualToWhen(outcome.Pass.When, readyReplicas) + if err != nil { + return nil, errors.Wrap(err, "failed to parse pass range") + } + + if match { + result.IsPass = true + result.Message = outcome.Pass.Message + result.URI = outcome.Pass.URI + + return result, nil + } + } + } + + return result, nil +} + +func compareActualToWhen(when string, actual int) (bool, error) { + parts := strings.Split(strings.TrimSpace(when), " ") + + // we can make this a lot more flexible + if len(parts) != 2 { + return false, errors.New("unable to parse when range") + } + + value, err := strconv.Atoi(parts[1]) + if err != nil { + return false, errors.New("unable to parse when value") + } + + switch parts[0] { + case "=": + fallthrough + case "==": + fallthrough + case "===": + return actual == value, nil + + case "<": + return actual < value, nil + + case ">": + return actual > value, nil + + case "<=": + return actual <= value, nil + + case ">=": + return actual >= value, nil + } + + return false, errors.Errorf("unknown comparator: %q", parts[0]) +} diff --git a/pkg/analyze/deployment_status.go b/pkg/analyze/deployment_status.go index a5e931f3..ab4bc0d0 100644 --- a/pkg/analyze/deployment_status.go +++ b/pkg/analyze/deployment_status.go @@ -4,8 +4,6 @@ import ( "encoding/json" "fmt" "path" - "strconv" - "strings" "github.com/pkg/errors" troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1" @@ -24,14 +22,10 @@ func deploymentStatus(analyzer *troubleshootv1beta1.DeploymentStatus, getCollect } var status *appsv1.DeploymentStatus - if analyzer.Name != "" { - for _, deployment := range deployments { - if deployment.Name == analyzer.Name { - status = &deployment.Status - } + for _, deployment := range deployments { + if deployment.Name == analyzer.Name { + status = &deployment.Status } - } else if analyzer.Selector != nil { - } if status == nil { @@ -42,112 +36,5 @@ func deploymentStatus(analyzer *troubleshootv1beta1.DeploymentStatus, getCollect }, nil } - result := &AnalyzeResult{} - - // ordering from the spec is important, the first one that matches returns - for _, outcome := range analyzer.Outcomes { - if outcome.Fail != nil { - if outcome.Fail.When == "" { - result.IsFail = true - result.Message = outcome.Fail.Message - result.URI = outcome.Fail.URI - - return result, nil - } - - match, err := compareActualToWhen(outcome.Fail.When, int(status.ReadyReplicas)) - if err != nil { - return nil, errors.Wrap(err, "failed to parse fail range") - } - - if match { - result.IsFail = true - result.Message = outcome.Fail.Message - result.URI = outcome.Fail.URI - - return result, nil - } - } else if outcome.Warn != nil { - if outcome.Warn.When == "" { - result.IsWarn = true - result.Message = outcome.Warn.Message - result.URI = outcome.Warn.URI - - return result, nil - } - - match, err := compareActualToWhen(outcome.Warn.When, int(status.ReadyReplicas)) - if err != nil { - return nil, errors.Wrap(err, "failed to parse warn range") - } - - if match { - result.IsWarn = true - result.Message = outcome.Warn.Message - result.URI = outcome.Warn.URI - - return result, nil - } - } else if outcome.Pass != nil { - if outcome.Pass.When == "" { - result.IsPass = true - result.Message = outcome.Pass.Message - result.URI = outcome.Pass.URI - - return result, nil - } - - match, err := compareActualToWhen(outcome.Pass.When, int(status.ReadyReplicas)) - if err != nil { - return nil, errors.Wrap(err, "failed to parse pass range") - } - - if match { - result.IsPass = true - result.Message = outcome.Pass.Message - result.URI = outcome.Pass.URI - - return result, nil - } - } - } - - return result, nil -} - -func compareActualToWhen(when string, actual int) (bool, error) { - parts := strings.Split(strings.TrimSpace(when), " ") - - // we can make this a lot more flexible - if len(parts) != 2 { - return false, errors.New("unable to parse when range") - } - - value, err := strconv.Atoi(parts[1]) - if err != nil { - return false, errors.New("unable to parse when value") - } - - switch parts[0] { - case "=": - fallthrough - case "==": - fallthrough - case "===": - return actual == value, nil - - case "<": - return actual < value, nil - - case ">": - return actual > value, nil - - case "<=": - return actual <= value, nil - - case ">=": - return actual >= value, nil - } - - return false, errors.Errorf("unknown comparator: %q", parts[0]) + return commonStatus(analyzer.Outcomes, int(status.ReadyReplicas)) } diff --git a/pkg/analyze/statefulset_status.go b/pkg/analyze/statefulset_status.go index d7bb7869..3ec3bc04 100644 --- a/pkg/analyze/statefulset_status.go +++ b/pkg/analyze/statefulset_status.go @@ -1,11 +1,40 @@ package analyzer import ( + "encoding/json" + "fmt" + "path" + "github.com/pkg/errors" troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1" + appsv1 "k8s.io/api/apps/v1" ) func statefulsetStatus(analyzer *troubleshootv1beta1.StatefulsetStatus, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) { + collected, err := getCollectedFileContents(path.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace))) + if err != nil { + return nil, errors.Wrap(err, "failed to read collected deployments from namespace") + } - return nil, errors.New("not implemented") + var statefulsets []appsv1.StatefulSet + if err := json.Unmarshal(collected, &statefulsets); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal statefulset list") + } + + var status *appsv1.StatefulSetStatus + for _, statefulset := range statefulsets { + if statefulset.Name == analyzer.Name { + status = &statefulset.Status + } + } + + if status == nil { + // there's not an error, but maybe the requested statefulset is not even deployed + return &AnalyzeResult{ + IsFail: true, + Message: "not found", + }, nil + } + + return commonStatus(analyzer.Outcomes, int(status.ReadyReplicas)) } diff --git a/pkg/apis/troubleshoot/v1beta1/analyzer_shared.go b/pkg/apis/troubleshoot/v1beta1/analyzer_shared.go index 71bdb090..c9d28d3a 100644 --- a/pkg/apis/troubleshoot/v1beta1/analyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta1/analyzer_shared.go @@ -54,16 +54,14 @@ type DeploymentStatus struct { AnalyzeMeta `json:",inline" yaml:",inline"` Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` Namespace string `json:"namespace" yaml:"namespace"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Selector []string `json:"selector,omitempty" yaml:"selector,omitempty"` + Name string `json:"name" yaml:"name"` } type StatefulsetStatus struct { AnalyzeMeta `json:",inline" yaml:",inline"` Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` Namespace string `json:"namespace" yaml:"namespace"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Selector []string `json:"selector,omitempty" yaml:"selector,omitempty"` + Name string `json:"name" yaml:"name"` } type AnalyzeMeta struct { diff --git a/pkg/apis/troubleshoot/v1beta1/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta1/zz_generated.deepcopy.go index 97c320f7..537f1041 100644 --- a/pkg/apis/troubleshoot/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta1/zz_generated.deepcopy.go @@ -756,11 +756,6 @@ func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) { } } } - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = make([]string, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentStatus. @@ -1364,11 +1359,6 @@ func (in *StatefulsetStatus) DeepCopyInto(out *StatefulsetStatus) { } } } - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = make([]string, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulsetStatus.