diff --git a/pkg/exprparser/functions_test.go b/pkg/exprparser/functions_test.go index ea51a2bc8ba..7241ddf82e6 100644 --- a/pkg/exprparser/functions_test.go +++ b/pkg/exprparser/functions_test.go @@ -31,6 +31,13 @@ func TestFunctionContains(t *testing.T) { {`contains(fromJSON('[3.14,"second"]'), 3.14) }}`, true, "contains-item-number-number"}, {`contains(fromJSON('["","second"]'), fromJSON('[]')) }}`, false, "contains-item-str-arr"}, {`contains(fromJSON('["","second"]'), fromJSON('{}')) }}`, false, "contains-item-str-obj"}, + {`contains(fromJSON('[{ "first": { "result": "success" }},{ "second": { "result": "success" }}]').first.result, 'success') }}`, true, "multiple-contains-item"}, + {`contains(fromJSON('[{ "result": "success" },{ "result": "failure" }]').*.result, 'failure') }}`, true, "multiple-contains-dereferenced-failure-item"}, + {`contains(fromJSON('[{ "result": "failure" },{ "result": "success" }]').*.result, 'success') }}`, true, "multiple-contains-dereferenced-success-item"}, + {`contains(fromJSON('[{ "result": "failure" },{ "result": "success" }]').*.result, 'notthere') }}`, false, "multiple-contains-dereferenced-missing-item"}, + {`contains(fromJSON('[{ "result": "failure", "outputs": { "key": "val1" } },{ "result": "success", "outputs": { "key": "val2" } }]').*.outputs.key, 'val1') }}`, true, "multiple-contains-dereferenced-output-item"}, + {`contains(fromJSON('[{ "result": "failure", "outputs": { "key": "val1" } },{ "result": "success", "outputs": { "key": "val2" } }]').*.outputs.key, 'val2') }}`, true, "multiple-contains-dereferenced-output-item-2"}, + {`contains(fromJSON('[{ "result": "failure", "outputs": { "key": "val1" } },{ "result": "success", "outputs": { "key": "val2" } }]').*.outputs.key, 'missing') }}`, false, "multiple-contains-dereferenced-output-misssing-item"}, } env := &EvaluationEnvironment{} @@ -249,3 +256,23 @@ func TestFunctionFormat(t *testing.T) { }) } } + +func TestMapContains(t *testing.T) { + env := &EvaluationEnvironment{ + Needs: map[string]Needs{ + "first-job": { + Outputs: map[string]string{}, + Result: "success", + }, + "second-job": { + Outputs: map[string]string{}, + Result: "failure", + }, + }, + } + + output, err := NewInterpeter(env, Config{}).Evaluate("contains(needs.*.result, 'failure')", DefaultStatusCheckNone) + assert.Nil(t, err) + + assert.Equal(t, true, output) +} diff --git a/pkg/exprparser/interpreter.go b/pkg/exprparser/interpreter.go index 7630854f28e..8d50913d9d3 100644 --- a/pkg/exprparser/interpreter.go +++ b/pkg/exprparser/interpreter.go @@ -229,6 +229,10 @@ func (impl *interperterImpl) evaluateObjectDeref(objectDerefNode *actionlint.Obj return nil, err } + _, receiverIsDeref := objectDerefNode.Receiver.(*actionlint.ArrayDerefNode) + if receiverIsDeref { + return impl.getPropertyValueDereferenced(reflect.ValueOf(left), objectDerefNode.Property) + } return impl.getPropertyValue(reflect.ValueOf(left), objectDerefNode.Property) } @@ -312,6 +316,34 @@ func (impl *interperterImpl) getPropertyValue(left reflect.Value, property strin return nil, nil } +func (impl *interperterImpl) getPropertyValueDereferenced(left reflect.Value, property string) (value interface{}, err error) { + switch left.Kind() { + case reflect.Ptr: + return impl.getPropertyValue(left, property) + + case reflect.Struct: + return impl.getPropertyValue(left, property) + case reflect.Map: + iter := left.MapRange() + + var values []interface{} + for iter.Next() { + value, err := impl.getPropertyValue(iter.Value(), property) + if err != nil { + return nil, err + } + + values = append(values, value) + } + + return values, nil + case reflect.Slice: + return impl.getPropertyValue(left, property) + } + + return nil, nil +} + func (impl *interperterImpl) getMapValue(value reflect.Value) (interface{}, error) { if value.Kind() == reflect.Ptr { return impl.getMapValue(value.Elem()) diff --git a/pkg/exprparser/interpreter_test.go b/pkg/exprparser/interpreter_test.go index f45851d1f44..8019ace1ef0 100644 --- a/pkg/exprparser/interpreter_test.go +++ b/pkg/exprparser/interpreter_test.go @@ -562,6 +562,8 @@ func TestContexts(t *testing.T) { {"matrix.os", "Linux", "matrix-context"}, {"needs.job-id.outputs.output-name", "value", "needs-context"}, {"needs.job-id.result", "success", "needs-context"}, + {"contains(needs.*.result, 'success')", true, "needs-wildcard-context-contains-success"}, + {"contains(needs.*.result, 'failure')", false, "needs-wildcard-context-contains-failure"}, {"inputs.name", "value", "inputs-context"}, } @@ -610,6 +612,12 @@ func TestContexts(t *testing.T) { }, Result: "success", }, + "another-job-id": { + Outputs: map[string]string{ + "output-name": "value", + }, + Result: "success", + }, }, Inputs: map[string]interface{}{ "name": "value",