Skip to content

Commit 852e661

Browse files
committed
Fixed bug where jsonpath expression with a nested range does not process subsequent nodes
1 parent ca23b07 commit 852e661

File tree

2 files changed

+144
-16
lines changed

2 files changed

+144
-16
lines changed

staging/src/k8s.io/client-go/util/jsonpath/jsonpath.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ import (
2929
type JSONPath struct {
3030
name string
3131
parser *Parser
32-
stack [][]reflect.Value // push and pop values in different scopes
33-
cur []reflect.Value // current scope values
3432
beginRange int
3533
inRange int
3634
endRange int
3735

36+
lastEndNode *Node
37+
3838
allowMissingKeys bool
3939
}
4040

@@ -81,37 +81,44 @@ func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) {
8181
return nil, fmt.Errorf("%s is an incomplete jsonpath template", j.name)
8282
}
8383

84-
j.cur = []reflect.Value{reflect.ValueOf(data)}
84+
cur := []reflect.Value{reflect.ValueOf(data)}
8585
nodes := j.parser.Root.Nodes
8686
fullResult := [][]reflect.Value{}
8787
for i := 0; i < len(nodes); i++ {
8888
node := nodes[i]
89-
results, err := j.walk(j.cur, node)
89+
results, err := j.walk(cur, node)
9090
if err != nil {
9191
return nil, err
9292
}
9393

9494
// encounter an end node, break the current block
9595
if j.endRange > 0 && j.endRange <= j.inRange {
9696
j.endRange--
97+
j.lastEndNode = &nodes[i]
9798
break
9899
}
99100
// encounter a range node, start a range loop
100101
if j.beginRange > 0 {
101102
j.beginRange--
102103
j.inRange++
103-
for k, value := range results {
104+
for _, value := range results {
104105
j.parser.Root.Nodes = nodes[i+1:]
105-
if k == len(results)-1 {
106-
j.inRange--
107-
}
108106
nextResults, err := j.FindResults(value.Interface())
109107
if err != nil {
110108
return nil, err
111109
}
112110
fullResult = append(fullResult, nextResults...)
113111
}
114-
break
112+
j.inRange--
113+
114+
// Fast forward to resume processing after the most recent end node that was encountered
115+
for k := i + 1; k < len(nodes); k++ {
116+
if &nodes[k] == j.lastEndNode {
117+
i = k
118+
break
119+
}
120+
}
121+
continue
115122
}
116123
fullResult = append(fullResult, results)
117124
}
@@ -212,17 +219,11 @@ func (j *JSONPath) evalIdentifier(input []reflect.Value, node *IdentifierNode) (
212219
results := []reflect.Value{}
213220
switch node.Name {
214221
case "range":
215-
j.stack = append(j.stack, j.cur)
216222
j.beginRange++
217223
results = input
218224
case "end":
219-
if j.endRange < j.inRange { // inside a loop, break the current block
225+
if j.inRange > 0 {
220226
j.endRange++
221-
break
222-
}
223-
// the loop is about to end, pop value and continue the following execution
224-
if len(j.stack) > 0 {
225-
j.cur, j.stack = j.stack[len(j.stack)-1], j.stack[:len(j.stack)-1]
226227
} else {
227228
return results, fmt.Errorf("not in range, nothing to end")
228229
}

staging/src/k8s.io/client-go/util/jsonpath/jsonpath_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,133 @@ func TestKubernetes(t *testing.T) {
298298
testJSONPathSortOutput(randomPrintOrderTests, t)
299299
}
300300

301+
func TestNestedRanges(t *testing.T) {
302+
var input = []byte(`{
303+
"items": [
304+
{
305+
"metadata": {
306+
"name": "pod1"
307+
},
308+
"spec": {
309+
"containers": [
310+
{
311+
"name": "foo",
312+
"another": [
313+
{ "name": "value1" },
314+
{ "name": "value2" }
315+
]
316+
},
317+
{
318+
"name": "bar",
319+
"another": [
320+
{ "name": "value1" },
321+
{ "name": "value2" }
322+
]
323+
}
324+
]
325+
}
326+
},
327+
{
328+
"metadata": {
329+
"name": "pod2"
330+
},
331+
"spec": {
332+
"containers": [
333+
{
334+
"name": "baz",
335+
"another": [
336+
{ "name": "value1" },
337+
{ "name": "value2" }
338+
]
339+
}
340+
]
341+
}
342+
}
343+
]
344+
}`)
345+
var data interface{}
346+
err := json.Unmarshal(input, &data)
347+
if err != nil {
348+
t.Error(err)
349+
}
350+
351+
testJSONPath(
352+
[]jsonpathTest{
353+
{
354+
"nested range with a trailing newline",
355+
`{range .items[*]}` +
356+
`{.metadata.name}` +
357+
`{":"}` +
358+
`{range @.spec.containers[*]}` +
359+
`{.name}` +
360+
`{","}` +
361+
`{end}` +
362+
`{"+"}` +
363+
`{end}`,
364+
data,
365+
"pod1:foo,bar,+pod2:baz,+",
366+
false,
367+
},
368+
},
369+
false,
370+
t,
371+
)
372+
373+
testJSONPath(
374+
[]jsonpathTest{
375+
{
376+
"nested range with a trailing character within another nested range with a trailing newline",
377+
`{range .items[*]}` +
378+
`{.metadata.name}` +
379+
`{"~"}` +
380+
`{range @.spec.containers[*]}` +
381+
`{.name}` +
382+
`{":"}` +
383+
`{range @.another[*]}` +
384+
`{.name}` +
385+
`{","}` +
386+
`{end}` +
387+
`{"+"}` +
388+
`{end}` +
389+
`{"#"}` +
390+
`{end}`,
391+
data,
392+
"pod1~foo:value1,value2,+bar:value1,value2,+#pod2~baz:value1,value2,+#",
393+
false,
394+
},
395+
},
396+
false,
397+
t,
398+
)
399+
400+
testJSONPath(
401+
[]jsonpathTest{
402+
{
403+
"two nested ranges at the same level with a trailing newline",
404+
`{range .items[*]}` +
405+
`{.metadata.name}` +
406+
`{"\t"}` +
407+
`{range @.spec.containers[*]}` +
408+
`{.name}` +
409+
`{" "}` +
410+
`{end}` +
411+
`{"\t"}` +
412+
`{range @.spec.containers[*]}` +
413+
`{.name}` +
414+
`{" "}` +
415+
`{end}` +
416+
`{"\n"}` +
417+
`{end}`,
418+
data,
419+
"pod1\tfoo bar \tfoo bar \npod2\tbaz \tbaz \n",
420+
false,
421+
},
422+
},
423+
false,
424+
t,
425+
)
426+
}
427+
301428
func TestFilterPartialMatchesSometimesMissingAnnotations(t *testing.T) {
302429
// for https://issues.k8s.io/45546
303430
var input = []byte(`{

0 commit comments

Comments
 (0)