diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 9ea9e7c4..8a56eabe 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -352,7 +352,7 @@ }, { "ImportPath": "k8s.io/apimachinery", - "Rev": "591a38b7a7b7" + "Rev": "4c6179225362" }, { "ImportPath": "k8s.io/gengo", diff --git a/go.mod b/go.mod index bec8df72..71e3796b 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( golang.org/x/time v0.0.0-20191024005414-555d28b269f0 google.golang.org/appengine v1.5.0 // indirect k8s.io/api v0.0.0-20200403220253-fa879b399cd0 - k8s.io/apimachinery v0.0.0-20200407101112-591a38b7a7b7 + k8s.io/apimachinery v0.0.0-20200408061145-4c6179225362 k8s.io/klog v1.0.0 k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 sigs.k8s.io/yaml v1.2.0 @@ -39,5 +39,5 @@ replace ( golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // pinned to release-branch.go1.13 golang.org/x/tools => golang.org/x/tools v0.0.0-20190821162956-65e3620a7ae7 // pinned to release-branch.go1.13 k8s.io/api => k8s.io/api v0.0.0-20200403220253-fa879b399cd0 - k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200407101112-591a38b7a7b7 + k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200408061145-4c6179225362 ) diff --git a/go.sum b/go.sum index fad76fb5..c83363c0 100644 --- a/go.sum +++ b/go.sum @@ -188,7 +188,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.0.0-20200403220253-fa879b399cd0/go.mod h1:IHUHI0RKOjAF2/LmT+5g7hlTufmlzdZqccff7saac3A= -k8s.io/apimachinery v0.0.0-20200407101112-591a38b7a7b7/go.mod h1:plsINh5MKcZh761kXB2H+kXnD8vwFIAy7bitct1VPNU= +k8s.io/apimachinery v0.0.0-20200408061145-4c6179225362/go.mod h1:plsINh5MKcZh761kXB2H+kXnD8vwFIAy7bitct1VPNU= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= diff --git a/util/jsonpath/jsonpath.go b/util/jsonpath/jsonpath.go index 78b6b678..2118688c 100644 --- a/util/jsonpath/jsonpath.go +++ b/util/jsonpath/jsonpath.go @@ -29,12 +29,12 @@ import ( type JSONPath struct { name string parser *Parser - stack [][]reflect.Value // push and pop values in different scopes - cur []reflect.Value // current scope values beginRange int inRange int endRange int + lastEndNode *Node + allowMissingKeys bool } @@ -81,12 +81,12 @@ func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) { return nil, fmt.Errorf("%s is an incomplete jsonpath template", j.name) } - j.cur = []reflect.Value{reflect.ValueOf(data)} + cur := []reflect.Value{reflect.ValueOf(data)} nodes := j.parser.Root.Nodes fullResult := [][]reflect.Value{} for i := 0; i < len(nodes); i++ { node := nodes[i] - results, err := j.walk(j.cur, node) + results, err := j.walk(cur, node) if err != nil { return nil, err } @@ -94,24 +94,31 @@ func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) { // encounter an end node, break the current block if j.endRange > 0 && j.endRange <= j.inRange { j.endRange-- + j.lastEndNode = &nodes[i] break } // encounter a range node, start a range loop if j.beginRange > 0 { j.beginRange-- j.inRange++ - for k, value := range results { + for _, value := range results { j.parser.Root.Nodes = nodes[i+1:] - if k == len(results)-1 { - j.inRange-- - } nextResults, err := j.FindResults(value.Interface()) if err != nil { return nil, err } fullResult = append(fullResult, nextResults...) } - break + j.inRange-- + + // Fast forward to resume processing after the most recent end node that was encountered + for k := i + 1; k < len(nodes); k++ { + if &nodes[k] == j.lastEndNode { + i = k + break + } + } + continue } fullResult = append(fullResult, results) } @@ -212,17 +219,11 @@ func (j *JSONPath) evalIdentifier(input []reflect.Value, node *IdentifierNode) ( results := []reflect.Value{} switch node.Name { case "range": - j.stack = append(j.stack, j.cur) j.beginRange++ results = input case "end": - if j.endRange < j.inRange { // inside a loop, break the current block + if j.inRange > 0 { j.endRange++ - break - } - // the loop is about to end, pop value and continue the following execution - if len(j.stack) > 0 { - j.cur, j.stack = j.stack[len(j.stack)-1], j.stack[:len(j.stack)-1] } else { return results, fmt.Errorf("not in range, nothing to end") } diff --git a/util/jsonpath/jsonpath_test.go b/util/jsonpath/jsonpath_test.go index 1513e5e7..7679ccd9 100644 --- a/util/jsonpath/jsonpath_test.go +++ b/util/jsonpath/jsonpath_test.go @@ -298,6 +298,133 @@ func TestKubernetes(t *testing.T) { testJSONPathSortOutput(randomPrintOrderTests, t) } +func TestNestedRanges(t *testing.T) { + var input = []byte(`{ + "items": [ + { + "metadata": { + "name": "pod1" + }, + "spec": { + "containers": [ + { + "name": "foo", + "another": [ + { "name": "value1" }, + { "name": "value2" } + ] + }, + { + "name": "bar", + "another": [ + { "name": "value1" }, + { "name": "value2" } + ] + } + ] + } + }, + { + "metadata": { + "name": "pod2" + }, + "spec": { + "containers": [ + { + "name": "baz", + "another": [ + { "name": "value1" }, + { "name": "value2" } + ] + } + ] + } + } + ] + }`) + var data interface{} + err := json.Unmarshal(input, &data) + if err != nil { + t.Error(err) + } + + testJSONPath( + []jsonpathTest{ + { + "nested range with a trailing newline", + `{range .items[*]}` + + `{.metadata.name}` + + `{":"}` + + `{range @.spec.containers[*]}` + + `{.name}` + + `{","}` + + `{end}` + + `{"+"}` + + `{end}`, + data, + "pod1:foo,bar,+pod2:baz,+", + false, + }, + }, + false, + t, + ) + + testJSONPath( + []jsonpathTest{ + { + "nested range with a trailing character within another nested range with a trailing newline", + `{range .items[*]}` + + `{.metadata.name}` + + `{"~"}` + + `{range @.spec.containers[*]}` + + `{.name}` + + `{":"}` + + `{range @.another[*]}` + + `{.name}` + + `{","}` + + `{end}` + + `{"+"}` + + `{end}` + + `{"#"}` + + `{end}`, + data, + "pod1~foo:value1,value2,+bar:value1,value2,+#pod2~baz:value1,value2,+#", + false, + }, + }, + false, + t, + ) + + testJSONPath( + []jsonpathTest{ + { + "two nested ranges at the same level with a trailing newline", + `{range .items[*]}` + + `{.metadata.name}` + + `{"\t"}` + + `{range @.spec.containers[*]}` + + `{.name}` + + `{" "}` + + `{end}` + + `{"\t"}` + + `{range @.spec.containers[*]}` + + `{.name}` + + `{" "}` + + `{end}` + + `{"\n"}` + + `{end}`, + data, + "pod1\tfoo bar \tfoo bar \npod2\tbaz \tbaz \n", + false, + }, + }, + false, + t, + ) +} + func TestFilterPartialMatchesSometimesMissingAnnotations(t *testing.T) { // for https://issues.k8s.io/45546 var input = []byte(`{