From edfa01c5c4cf0fabb92d288777a520a0f0fd0fb7 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 19 Jun 2024 12:14:37 +1000 Subject: [PATCH] feat: [sc-106625] http analyzer for in-cluster (#1566) * http analzyer for in-cluster * make check-schemas --- config/crds/troubleshoot.sh_analyzers.yaml | 49 ++++++++++++ config/crds/troubleshoot.sh_preflights.yaml | 49 ++++++++++++ .../crds/troubleshoot.sh_supportbundles.yaml | 49 ++++++++++++ pkg/analyze/analyzer.go | 2 + pkg/analyze/host_http.go | 69 +++++++++-------- pkg/analyze/http_analyze.go | 30 ++++++++ .../troubleshoot/v1beta2/analyzer_shared.go | 1 + .../v1beta2/zz_generated.deepcopy.go | 5 ++ schemas/analyzer-troubleshoot-v1beta2.json | 76 +++++++++++++++++++ schemas/preflight-troubleshoot-v1beta2.json | 76 +++++++++++++++++++ .../supportbundle-troubleshoot-v1beta2.json | 76 +++++++++++++++++++ 11 files changed, 450 insertions(+), 32 deletions(-) create mode 100644 pkg/analyze/http_analyze.go diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml index 4b49cf09f..96f71386b 100644 --- a/config/crds/troubleshoot.sh_analyzers.yaml +++ b/config/crds/troubleshoot.sh_analyzers.yaml @@ -673,6 +673,55 @@ spec: required: - collectorName type: object + http: + properties: + annotations: + additionalProperties: + type: string + type: object + checkName: + type: string + collectorName: + type: string + exclude: + type: BoolString + outcomes: + items: + properties: + fail: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + pass: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + warn: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + type: object + type: array + strict: + type: BoolString + required: + - outcomes + type: object imagePullSecret: properties: annotations: diff --git a/config/crds/troubleshoot.sh_preflights.yaml b/config/crds/troubleshoot.sh_preflights.yaml index 2474e2442..fc6f271a7 100644 --- a/config/crds/troubleshoot.sh_preflights.yaml +++ b/config/crds/troubleshoot.sh_preflights.yaml @@ -673,6 +673,55 @@ spec: required: - collectorName type: object + http: + properties: + annotations: + additionalProperties: + type: string + type: object + checkName: + type: string + collectorName: + type: string + exclude: + type: BoolString + outcomes: + items: + properties: + fail: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + pass: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + warn: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + type: object + type: array + strict: + type: BoolString + required: + - outcomes + type: object imagePullSecret: properties: annotations: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index a32ab7c15..f8a0fab02 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -704,6 +704,55 @@ spec: required: - collectorName type: object + http: + properties: + annotations: + additionalProperties: + type: string + type: object + checkName: + type: string + collectorName: + type: string + exclude: + type: BoolString + outcomes: + items: + properties: + fail: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + pass: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + warn: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + type: object + type: array + strict: + type: BoolString + required: + - outcomes + type: object imagePullSecret: properties: annotations: diff --git a/pkg/analyze/analyzer.go b/pkg/analyze/analyzer.go index 01bf8790d..42fff4143 100644 --- a/pkg/analyze/analyzer.go +++ b/pkg/analyze/analyzer.go @@ -250,6 +250,8 @@ func getAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer { return &AnalyzeEvent{analyzer: analyzer.Event} case analyzer.NodeMetrics != nil: return &AnalyzeNodeMetrics{analyzer: analyzer.NodeMetrics} + case analyzer.HTTP != nil: + return &AnalyzeHTTPAnalyze{analyzer: analyzer.HTTP} default: return nil } diff --git a/pkg/analyze/host_http.go b/pkg/analyze/host_http.go index fd2f39543..caccec94c 100644 --- a/pkg/analyze/host_http.go +++ b/pkg/analyze/host_http.go @@ -38,7 +38,41 @@ func (a *AnalyzeHostHTTP) Analyze( if hostAnalyzer.CollectorName != "" { name = filepath.Join("host-collectors/http", hostAnalyzer.CollectorName+".json") } - contents, err := getCollectedFileContents(name) + + return analyzeHTTPResult(hostAnalyzer, name, getCollectedFileContents, a.Title()) +} + +func compareHostHTTPConditionalToActual(conditional string, result *httpResult) (res bool, err error) { + if conditional == "error" { + return result.Error != nil, nil + } + + parts := strings.Split(conditional, " ") + if len(parts) != 3 { + return false, fmt.Errorf("Failed to parse conditional: got %d parts", len(parts)) + } + + if parts[0] != "statusCode" { + return false, errors.New(`Conditional must begin with keyword "statusCode"`) + } + + if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" { + return false, errors.New(`Only supported operator is "=="`) + } + + i, err := strconv.Atoi(parts[2]) + if err != nil { + return false, err + } + + if result.Response == nil { + return false, err + } + return result.Response.Status == i, nil +} + +func analyzeHTTPResult(analyzer *troubleshootv1beta2.HTTPAnalyze, fileName string, getCollectedFileContents getCollectedFileContents, title string) ([]*AnalyzeResult, error) { + contents, err := getCollectedFileContents(fileName) if err != nil { return nil, errors.Wrap(err, "failed to get collected file") } @@ -49,10 +83,10 @@ func (a *AnalyzeHostHTTP) Analyze( } result := &AnalyzeResult{ - Title: a.Title(), + Title: title, } - for _, outcome := range hostAnalyzer.Outcomes { + for _, outcome := range analyzer.Outcomes { if outcome.Fail != nil { if outcome.Fail.When == "" { result.IsFail = true @@ -122,32 +156,3 @@ func (a *AnalyzeHostHTTP) Analyze( return []*AnalyzeResult{result}, nil } - -func compareHostHTTPConditionalToActual(conditional string, result *httpResult) (res bool, err error) { - if conditional == "error" { - return result.Error != nil, nil - } - - parts := strings.Split(conditional, " ") - if len(parts) != 3 { - return false, fmt.Errorf("Failed to parse conditional: got %d parts", len(parts)) - } - - if parts[0] != "statusCode" { - return false, errors.New(`Conditional must begin with keyword "statusCode"`) - } - - if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" { - return false, errors.New(`Only supported operator is "=="`) - } - - i, err := strconv.Atoi(parts[2]) - if err != nil { - return false, err - } - - if result.Response == nil { - return false, err - } - return result.Response.Status == i, nil -} diff --git a/pkg/analyze/http_analyze.go b/pkg/analyze/http_analyze.go new file mode 100644 index 000000000..d792d1f5a --- /dev/null +++ b/pkg/analyze/http_analyze.go @@ -0,0 +1,30 @@ +package analyzer + +import ( + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" +) + +type AnalyzeHTTPAnalyze struct { + analyzer *troubleshootv1beta2.HTTPAnalyze +} + +func (a *AnalyzeHTTPAnalyze) Title() string { + checkName := a.analyzer.CheckName + if checkName == "" { + checkName = a.analyzer.CollectorName + } + + return checkName +} + +func (a *AnalyzeHTTPAnalyze) IsExcluded() (bool, error) { + return isExcluded(a.analyzer.Exclude) +} + +func (a *AnalyzeHTTPAnalyze) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) { + fileName := "result.json" + if a.analyzer.CollectorName != "" { + fileName = a.analyzer.CollectorName + ".json" + } + return analyzeHTTPResult(a.analyzer, fileName, getFile, a.Title()) +} diff --git a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go index 76aba6367..216e45532 100644 --- a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go @@ -292,4 +292,5 @@ type Analyze struct { Goldpinger *GoldpingerAnalyze `json:"goldpinger,omitempty" yaml:"goldpinger,omitempty"` Event *EventAnalyze `json:"event,omitempty" yaml:"event,omitempty"` NodeMetrics *NodeMetricsAnalyze `json:"nodeMetrics,omitempty" yaml:"nodeMetrics,omitempty"` + HTTP *HTTPAnalyze `json:"http,omitempty" yaml:"http,omitempty"` } diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index 3f397a6eb..8e2f99c23 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -218,6 +218,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) { *out = new(NodeMetricsAnalyze) (*in).DeepCopyInto(*out) } + if in.HTTP != nil { + in, out := &in.HTTP, &out.HTTP + *out = new(HTTPAnalyze) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze. diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json index 50ddc5026..852b8c3be 100644 --- a/schemas/analyzer-troubleshoot-v1beta2.json +++ b/schemas/analyzer-troubleshoot-v1beta2.json @@ -993,6 +993,82 @@ } } }, + "http": { + "type": "object", + "required": [ + "outcomes" + ], + "properties": { + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "checkName": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "outcomes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fail": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "pass": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "warn": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + } + } + } + }, + "strict": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + } + } + }, "imagePullSecret": { "type": "object", "required": [ diff --git a/schemas/preflight-troubleshoot-v1beta2.json b/schemas/preflight-troubleshoot-v1beta2.json index 6f2c97572..cd0c711c7 100644 --- a/schemas/preflight-troubleshoot-v1beta2.json +++ b/schemas/preflight-troubleshoot-v1beta2.json @@ -993,6 +993,82 @@ } } }, + "http": { + "type": "object", + "required": [ + "outcomes" + ], + "properties": { + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "checkName": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "outcomes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fail": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "pass": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "warn": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + } + } + } + }, + "strict": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + } + } + }, "imagePullSecret": { "type": "object", "required": [ diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index 1bc4f9654..337b37a79 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -1039,6 +1039,82 @@ } } }, + "http": { + "type": "object", + "required": [ + "outcomes" + ], + "properties": { + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "checkName": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "outcomes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fail": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "pass": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "warn": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + } + } + } + }, + "strict": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + } + } + }, "imagePullSecret": { "type": "object", "required": [