diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml index c0425b7a..7256fe2c 100644 --- a/config/crds/troubleshoot.sh_analyzers.yaml +++ b/config/crds/troubleshoot.sh_analyzers.yaml @@ -732,6 +732,8 @@ spec: type: BoolString fileName: type: string + jsonPath: + type: string outcomes: items: properties: diff --git a/config/crds/troubleshoot.sh_preflights.yaml b/config/crds/troubleshoot.sh_preflights.yaml index ac66e104..c9dbcfc1 100644 --- a/config/crds/troubleshoot.sh_preflights.yaml +++ b/config/crds/troubleshoot.sh_preflights.yaml @@ -732,6 +732,8 @@ spec: type: BoolString fileName: type: string + jsonPath: + type: string outcomes: items: properties: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 50273871..c3d94aa5 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -763,6 +763,8 @@ spec: type: BoolString fileName: type: string + jsonPath: + type: string outcomes: items: properties: diff --git a/pkg/analyze/json_compare.go b/pkg/analyze/json_compare.go index 242d8cfe..4e60b44b 100644 --- a/pkg/analyze/json_compare.go +++ b/pkg/analyze/json_compare.go @@ -1,6 +1,7 @@ package analyzer import ( + "bytes" "encoding/json" "path/filepath" "reflect" @@ -9,6 +10,7 @@ import ( "github.com/pkg/errors" troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" iutils "github.com/replicatedhq/troubleshoot/pkg/interfaceutils" + "k8s.io/client-go/util/jsonpath" ) type AnalyzeJsonCompare struct { @@ -55,6 +57,32 @@ func (a *AnalyzeJsonCompare) analyzeJsonCompare(analyzer *troubleshootv1beta2.Js if err != nil { return nil, errors.Wrapf(err, "failed to get object at path: %s", analyzer.Path) } + } else if analyzer.JsonPath != "" { + jsp := jsonpath.New(analyzer.CheckName) + jsp.AllowMissingKeys(true).EnableJSONOutput(true) + err = jsp.Parse(analyzer.JsonPath) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse jsonpath: %s", analyzer.JsonPath) + } + + var data bytes.Buffer + err = jsp.Execute(&data, actual) + if err != nil { + return nil, errors.Wrap(err, "failed to execute jsonpath") + } + + err = json.NewDecoder(&data).Decode(&actual) + if err != nil { + return nil, errors.Wrap(err, "failed to decode jsonpath result") + } + + // If we get back a single result in a slice unwrap it. + // Technically this doesn't strictly follow jsonpath, but it makes + // things easier downstream. Basically we don't want to require + // users to wrap a single result with []. + if a, ok := actual.([]interface{}); ok && len(a) == 1 { + actual = a[0] + } } var expected interface{} diff --git a/pkg/analyze/json_compare_test.go b/pkg/analyze/json_compare_test.go index 5c7d6f77..be272547 100644 --- a/pkg/analyze/json_compare_test.go +++ b/pkg/analyze/json_compare_test.go @@ -541,6 +541,144 @@ func Test_jsonCompare(t *testing.T) { }, fileContents: []byte(``), }, + { + name: "jsonpath comparison", + analyzer: troubleshootv1beta2.JsonCompare{ + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + Message: "pass", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + Message: "fail", + }, + }, + }, + CollectorName: "jsonpath-compare-1", + FileName: "jsonpath-compare-1.json", + JsonPath: "{$.morestuff[0]}", + Value: `{ + "foo": { + "bar": 123 + } + }`, + }, + expectResult: AnalyzeResult{ + IsPass: true, + IsWarn: false, + IsFail: false, + Title: "jsonpath-compare-1", + Message: "pass", + IconKey: "kubernetes_text_analyze", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg", + }, + fileContents: []byte(`{ + "foo": "bar", + "stuff": { + "foo": "bar", + "bar": true + }, + "morestuff": [ + { + "foo": { + "bar": 123 + } + } + ] + }`), + }, + { + name: "jsonpath comparison, but fail on match", + analyzer: troubleshootv1beta2.JsonCompare{ + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + Message: "pass", + When: "false", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + Message: "fail", + When: "true", + }, + }, + }, + CollectorName: "jsonpath-compare-1-1", + FileName: "jsonpath-compare-1-1.json", + JsonPath: "{$.morestuff[0].foo.bar}", + Value: `123`, + }, + expectResult: AnalyzeResult{ + IsPass: false, + IsWarn: false, + IsFail: true, + Title: "jsonpath-compare-1-1", + Message: "fail", + IconKey: "kubernetes_text_analyze", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg", + }, + fileContents: []byte(`{ + "foo": "bar", + "stuff": { + "foo": "bar", + "bar": true + }, + "morestuff": [ + { + "foo": { + "bar": 123 + } + } + ] + }`), + }, + { + name: "jsonpath comparison, multiple values", + analyzer: troubleshootv1beta2.JsonCompare{ + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + Message: "pass", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + Message: "fail", + }, + }, + }, + CollectorName: "jsonpath-compare-2", + FileName: "jsonpath-compare-2.json", + JsonPath: "{$..bar}", + Value: `[true, 123]`, + }, + expectResult: AnalyzeResult{ + IsPass: true, + IsWarn: false, + IsFail: false, + Title: "jsonpath-compare-2", + Message: "pass", + IconKey: "kubernetes_text_analyze", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg", + }, + fileContents: []byte(`{ + "foo": "bar", + "stuff": { + "foo": "bar", + "bar": true + }, + "morestuff": [ + { + "foo": { + "bar": 123 + } + } + ] + }`), + }, } for _, test := range tests { diff --git a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go index 1a9278de..4cb171d3 100644 --- a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go @@ -161,6 +161,7 @@ type JsonCompare struct { CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"` FileName string `json:"fileName,omitempty" yaml:"fileName,omitempty"` Path string `json:"path,omitempty" yaml:"path,omitempty"` + JsonPath string `json:"jsonPath,omitempty" yaml:"jsonPath,omitempty"` Value string `json:"value,omitempty" yaml:"value,omitempty"` Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` } diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json index b934fd2d..1f1eaff9 100644 --- a/schemas/analyzer-troubleshoot-v1beta2.json +++ b/schemas/analyzer-troubleshoot-v1beta2.json @@ -1092,6 +1092,9 @@ "fileName": { "type": "string" }, + "jsonPath": { + "type": "string" + }, "outcomes": { "type": "array", "items": { diff --git a/schemas/preflight-troubleshoot-v1beta2.json b/schemas/preflight-troubleshoot-v1beta2.json index bf6d466d..8074b71f 100644 --- a/schemas/preflight-troubleshoot-v1beta2.json +++ b/schemas/preflight-troubleshoot-v1beta2.json @@ -1092,6 +1092,9 @@ "fileName": { "type": "string" }, + "jsonPath": { + "type": "string" + }, "outcomes": { "type": "array", "items": { diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index de206ffa..436b4a8d 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -1138,6 +1138,9 @@ "fileName": { "type": "string" }, + "jsonPath": { + "type": "string" + }, "outcomes": { "type": "array", "items": {