diff --git a/pkg/engine/utils/foreach.go b/pkg/engine/utils/foreach.go index 25d7f9fbc7..cf65c213c4 100644 --- a/pkg/engine/utils/foreach.go +++ b/pkg/engine/utils/foreach.go @@ -9,12 +9,22 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) +// EvaluateList evaluates the context using the given JMESPath expression and returns a unified slice of interfaces. func EvaluateList(jmesPath string, ctx enginecontext.EvalInterface) ([]interface{}, error) { i, err := ctx.Query(jmesPath) if err != nil { return nil, err } + m, ok := i.([]map[string]interface{}) + if ok { + l := make([]interface{}, 0, len(m)) + for _, e := range m { + l = append(l, e) + } + return l, nil + } + l, ok := i.([]interface{}) if !ok { return []interface{}{i}, nil diff --git a/pkg/engine/utils/foreach_test.go b/pkg/engine/utils/foreach_test.go index 2d0010528f..0ad19a1680 100644 --- a/pkg/engine/utils/foreach_test.go +++ b/pkg/engine/utils/foreach_test.go @@ -3,6 +3,9 @@ package utils import ( "testing" + "github.com/kyverno/kyverno/pkg/config" + "github.com/kyverno/kyverno/pkg/engine/context" + "github.com/kyverno/kyverno/pkg/engine/jmespath" "github.com/stretchr/testify/assert" ) @@ -14,3 +17,74 @@ func Test_InvertElements(t *testing.T) { assert.Equal(t, "b", elemsInverted[1]) assert.Equal(t, "c", elemsInverted[0]) } + +func Test_EvaluateList(t *testing.T) { + entryName := "test_object" + cases := []struct { + name string + rawData []byte + jmesPath string + expected interface{} + }{ + { + name: "slice data", + rawData: []byte(`["test-value-1", "test-value-2"]`), + jmesPath: entryName, + expected: []interface{}{"test-value-1", "test-value-2"}, + }, + { + name: "map data", + rawData: []byte(` + { + "test-key-1": "test-value-1", + "test-key-2": "test-value-2" + } + `), + jmesPath: entryName + ".items(@, 'key', 'value')", + expected: []interface{}{ + map[string]interface{}{ + "key": "test-key-1", + "value": "test-value-1", + }, + map[string]interface{}{ + "key": "test-key-2", + "value": "test-value-2", + }, + }, + }, + { + name: "map data with custom fields", + rawData: []byte(` + { + "test-key-1": "test-value-1", + "test-key-2": "test-value-2" + } + `), + jmesPath: "test_object.items(@, 'another-key', 'another-value')", + expected: []interface{}{ + map[string]interface{}{ + "another-key": "test-key-1", + "another-value": "test-value-1", + }, + map[string]interface{}{ + "another-key": "test-key-2", + "another-value": "test-value-2", + }, + }, + }, + } + + cfg := config.NewDefaultConfiguration(false) + jp := jmespath.New(cfg) + + for _, item := range cases { + t.Run(item.name, func(t *testing.T) { + ctx := context.NewContext(jp) + assert.NoError(t, ctx.AddContextEntry(entryName, item.rawData)) + + list, err := EvaluateList(item.jmesPath, ctx) + assert.NoError(t, err) + assert.Equal(t, item.expected, list) + }) + } +}