Http comperators (#1753)

* Allowing more comperators for the http analyzer

* test

* Update pkg/analyze/host_http.go

Co-authored-by: Andrew Lavery <laverya@umich.edu>

---------

Co-authored-by: Andrew Lavery <laverya@umich.edu>
This commit is contained in:
Johannes Tuchscherer
2025-03-06 14:40:47 -07:00
committed by GitHub
parent c770c32c2e
commit 3665d25abf
4 changed files with 217 additions and 17 deletions

1
go.mod
View File

@@ -9,6 +9,7 @@ require (
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
github.com/apparentlymart/go-cidr v1.1.0
github.com/blang/semver/v4 v4.0.0
github.com/casbin/govaluate v1.3.0
github.com/cilium/ebpf v0.17.3
github.com/containerd/cgroups/v3 v3.0.5
github.com/containers/image/v5 v5.34.1

2
go.sum
View File

@@ -707,6 +707,8 @@ github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuP
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/c9s/goprocinfo v0.0.0-20170724085704-0010a05ce49f h1:tRk+aBit+q3oqnj/1mF5HHhP2yxJM2lSa0afOJxQ3nE=
github.com/c9s/goprocinfo v0.0.0-20170724085704-0010a05ce49f/go.mod h1:uEyr4WpAH4hio6LFriaPkL938XnrvLpNPmQHBdrmbIE=
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

View File

@@ -3,9 +3,9 @@ package analyzer
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/casbin/govaluate"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
@@ -64,28 +64,33 @@ func compareHostHTTPConditionalToActual(conditional string, result *httpResult)
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))
}
conditional = strings.ReplaceAll(conditional, " = ", " == ")
conditional = strings.ReplaceAll(conditional, " === ", " == ")
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])
expression, err := govaluate.NewEvaluableExpression(conditional)
if err != nil {
return false, err
return false, errors.Wrap(err, "failed to create evaluable expression")
}
if result.Response == nil {
return false, err
return false, nil
}
return result.Response.Status == i, nil
parameters := make(map[string]interface{}, 8)
parameters["statusCode"] = result.Response.Status
comparisonResult, err := expression.Evaluate(parameters)
if err != nil {
return false, errors.Wrap(err, "failed to evaluate expression")
}
boolResult, ok := comparisonResult.(bool)
if !ok {
return false, fmt.Errorf("expression did not evaluate to a boolean value")
}
return boolResult, nil
}
func analyzeHTTPResult(analyzer *troubleshootv1beta2.HTTPAnalyze, fileName string, getCollectedFileContents getCollectedFileContents, title string) ([]*AnalyzeResult, error) {

View File

@@ -113,6 +113,124 @@ func TestAnalyzeHostHTTP(t *testing.T) {
},
},
},
{
name: "invalid compare operator",
expectErr: true,
httpResult: &httpResult{
Response: &collect.HTTPResponse{
Status: 200,
},
},
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
CollectorName: "collector",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "statusCode #$ 200",
Message: "passed",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
Message: "default",
},
},
},
},
},
{
name: "!= compare operator",
httpResult: &httpResult{
Response: &collect.HTTPResponse{
Status: 201,
},
},
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
CollectorName: "collector",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "statusCode != 200",
Message: "passed",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
Message: "default",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "HTTP Request",
IsPass: true,
Message: "passed",
},
},
},
{
name: "Looking for 2xx status codes",
httpResult: &httpResult{
Response: &collect.HTTPResponse{
Status: 201,
},
},
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
CollectorName: "collector",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "statusCode >= 300 || statusCode < 200",
Message: "expected 2xx status code",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "default",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "HTTP Request",
IsPass: true,
Message: "default",
},
},
},
{
name: "Looking for 2xx status codes does not match",
httpResult: &httpResult{
Response: &collect.HTTPResponse{
Status: 300,
},
},
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
CollectorName: "collector",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "statusCode >= 300 || statusCode < 200",
Message: "expected 2xx status code",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "default",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "HTTP Request",
IsFail: true,
Message: "expected 2xx status code",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -137,3 +255,77 @@ func TestAnalyzeHostHTTP(t *testing.T) {
})
}
}
func TestAnalyzeHostHTTPHTTPCodesAndCompareOperators(t *testing.T) {
httpResult := &httpResult{
Response: &collect.HTTPResponse{
Status: 200,
},
}
tests := []struct {
name string
}{
{
name: "statusCode == 200",
},
{
name: "statusCode === 200",
},
{
name: "statusCode = 200",
},
{
name: "statusCode != 201",
},
{
name: "statusCode >= 200",
},
{
name: "statusCode > 199",
},
{
name: "statusCode <= 200",
},
{
name: "statusCode <= 201",
},
{
name: "statusCode < 201",
},
{
name: "statusCode < 201 && statusCode > 199",
},
{
name: "statusCode < 201 || statusCode > 199 && statusCode == 200",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
hostAnalyzer := &troubleshootv1beta2.HTTPAnalyze{
CollectorName: "registry",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: test.name},
},
},
}
req := require.New(t)
b, err := json.Marshal(httpResult)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := (&AnalyzeHostHTTP{hostAnalyzer}).Analyze(getCollectedFileContents, nil)
req.NoError(err)
req.Len(result, 1)
req.Equal(true, result[0].IsPass)
})
}
}