mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
JSONPath support for json compare analyzer (#1244)
This adds JSONPath support to the json compare analyzer using k8s.io/client-go/util/jsonpath implementation. To preserve backwards compatibility a new attribute, `JsonPath, is added to the compare analyzer as opposed to changing how `Path` works. Only one should be set, but preference is given to `Path`, again to maintain backwards compatibility. As a convience for users, if the result of running the JSONPath expression returns a single value, that value is unwrapped from its enclosing array and used as the comparison with `Value`. This isn't strictly compatible with how JSONPath works (all results are wrapped in an array), but it's easier for end users who are expecting a single result from their JSONPath expression.
This commit is contained in:
@@ -732,6 +732,8 @@ spec:
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
jsonPath:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
|
||||
@@ -732,6 +732,8 @@ spec:
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
jsonPath:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
|
||||
@@ -763,6 +763,8 @@ spec:
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
jsonPath:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -1092,6 +1092,9 @@
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"jsonPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
@@ -1092,6 +1092,9 @@
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"jsonPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"jsonPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
Reference in New Issue
Block a user