mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-03-02 01:40:19 +00:00
feat: allow ctx overlay + case sensitive env ctx (#99)
* switch to fork of actionlint
This commit is contained in:
@@ -2,6 +2,7 @@ package exprparser
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
@@ -25,8 +26,12 @@ type EvaluationEnvironment struct {
|
||||
Needs map[string]Needs
|
||||
Inputs map[string]interface{}
|
||||
HashFiles func([]reflect.Value) (interface{}, error)
|
||||
EnvCS bool
|
||||
CtxData map[string]interface{}
|
||||
}
|
||||
|
||||
type CaseSensitiveDict map[string]string
|
||||
|
||||
type Needs struct {
|
||||
Outputs map[string]string `json:"outputs"`
|
||||
Result string `json:"result"`
|
||||
@@ -151,10 +156,17 @@ func (impl *interperterImpl) evaluateNode(exprNode actionlint.ExprNode) (interfa
|
||||
}
|
||||
|
||||
func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableNode) (interface{}, error) {
|
||||
switch strings.ToLower(variableNode.Name) {
|
||||
lowerName := strings.ToLower(variableNode.Name)
|
||||
if result, err := impl.evaluateOverriddenVariable(lowerName); result != nil || err != nil {
|
||||
return result, err
|
||||
}
|
||||
switch lowerName {
|
||||
case "github":
|
||||
return impl.env.Github, nil
|
||||
case "env":
|
||||
if impl.env.EnvCS {
|
||||
return CaseSensitiveDict(impl.env.Env), nil
|
||||
}
|
||||
return impl.env.Env, nil
|
||||
case "job":
|
||||
return impl.env.Job, nil
|
||||
@@ -188,6 +200,33 @@ func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableN
|
||||
}
|
||||
}
|
||||
|
||||
func (impl *interperterImpl) evaluateOverriddenVariable(lowerName string) (interface{}, error) {
|
||||
if cd, ok := impl.env.CtxData[lowerName]; ok {
|
||||
if serverPayload, ok := cd.(map[string]interface{}); ok {
|
||||
if lowerName == "github" {
|
||||
var out map[string]interface{}
|
||||
content, err := json.Marshal(impl.env.Github)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(content, &out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range serverPayload {
|
||||
// skip empty values, because github.workspace was set by Gitea Actions to an empty string
|
||||
if _, ok := out[k]; !ok || v != "" && v != nil {
|
||||
out[k] = v
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
return cd, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (impl *interperterImpl) evaluateIndexAccess(indexAccessNode *actionlint.IndexAccessNode) (interface{}, error) {
|
||||
left, err := impl.evaluateNode(indexAccessNode.Operand)
|
||||
if err != nil {
|
||||
@@ -280,6 +319,11 @@ func (impl *interperterImpl) getPropertyValue(left reflect.Value, property strin
|
||||
return i, nil
|
||||
|
||||
case reflect.Map:
|
||||
cd, ok := left.Interface().(CaseSensitiveDict)
|
||||
if ok {
|
||||
return cd[property], nil
|
||||
}
|
||||
|
||||
iter := left.MapRange()
|
||||
|
||||
for iter.Next() {
|
||||
|
||||
@@ -528,46 +528,58 @@ func TestOperatorsBooleanEvaluation(t *testing.T) {
|
||||
|
||||
func TestContexts(t *testing.T) {
|
||||
table := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
name string
|
||||
input string
|
||||
expected interface{}
|
||||
name string
|
||||
caseSensitiveEnv bool
|
||||
ctxdata map[string]interface{}
|
||||
}{
|
||||
{"github.action", "push", "github-context"},
|
||||
{"github.event.commits[0].message", nil, "github-context-noexist-prop"},
|
||||
{"fromjson('{\"commits\":[]}').commits[0].message", nil, "github-context-noexist-prop"},
|
||||
{"github.event.pull_request.labels.*.name", nil, "github-context-noexist-prop"},
|
||||
{"env.TEST", "value", "env-context"},
|
||||
{"job.status", "success", "job-context"},
|
||||
{"steps.step-id.outputs.name", "value", "steps-context"},
|
||||
{"steps.step-id.conclusion", "success", "steps-context-conclusion"},
|
||||
{"steps.step-id.conclusion && true", true, "steps-context-conclusion"},
|
||||
{"steps.step-id2.conclusion", "skipped", "steps-context-conclusion"},
|
||||
{"steps.step-id2.conclusion && true", true, "steps-context-conclusion"},
|
||||
{"steps.step-id.outcome", "success", "steps-context-outcome"},
|
||||
{"steps.step-id['outcome']", "success", "steps-context-outcome"},
|
||||
{"steps.step-id.outcome == 'success'", true, "steps-context-outcome"},
|
||||
{"steps.step-id['outcome'] == 'success'", true, "steps-context-outcome"},
|
||||
{"steps.step-id.outcome && true", true, "steps-context-outcome"},
|
||||
{"steps['step-id']['outcome'] && true", true, "steps-context-outcome"},
|
||||
{"steps.step-id2.outcome", "failure", "steps-context-outcome"},
|
||||
{"steps.step-id2.outcome && true", true, "steps-context-outcome"},
|
||||
{input: "github.action", expected: "push", name: "github-context"},
|
||||
{input: "github.action", expected: "push", name: "github-context", ctxdata: map[string]interface{}{"github": map[string]interface{}{"ref": "refs/heads/test-data"}}},
|
||||
{input: "github.ref", expected: "refs/heads/test-data", name: "github-context", ctxdata: map[string]interface{}{"github": map[string]interface{}{"ref": "refs/heads/test-data"}}},
|
||||
{input: "github.custom-field", expected: "custom-value", name: "github-context", ctxdata: map[string]interface{}{"github": map[string]interface{}{"custom-field": "custom-value"}}},
|
||||
{input: "github.event.commits[0].message", expected: nil, name: "github-context-noexist-prop"},
|
||||
{input: "fromjson('{\"commits\":[]}').commits[0].message", expected: nil, name: "github-context-noexist-prop"},
|
||||
{input: "github.event.pull_request.labels.*.name", expected: nil, name: "github-context-noexist-prop"},
|
||||
{input: "env.TEST", expected: "value", name: "env-context"},
|
||||
{input: "env.TEST", expected: "value", name: "env-context", caseSensitiveEnv: true},
|
||||
{input: "env.test", expected: "", name: "env-context", caseSensitiveEnv: true},
|
||||
{input: "env['TEST']", expected: "value", name: "env-context", caseSensitiveEnv: true},
|
||||
{input: "env['test']", expected: "", name: "env-context", caseSensitiveEnv: true},
|
||||
{input: "env.test", expected: "value", name: "env-context"},
|
||||
{input: "job.status", expected: "success", name: "job-context"},
|
||||
{input: "steps.step-id.outputs.name", expected: "value", name: "steps-context"},
|
||||
{input: "steps.step-id.conclusion", expected: "success", name: "steps-context-conclusion"},
|
||||
{input: "steps.step-id.conclusion && true", expected: true, name: "steps-context-conclusion"},
|
||||
{input: "steps.step-id2.conclusion", expected: "skipped", name: "steps-context-conclusion"},
|
||||
{input: "steps.step-id2.conclusion && true", expected: true, name: "steps-context-conclusion"},
|
||||
{input: "steps.step-id.outcome", expected: "success", name: "steps-context-outcome"},
|
||||
{input: "steps.step-id['outcome']", expected: "success", name: "steps-context-outcome"},
|
||||
{input: "steps.step-id.outcome == 'success'", expected: true, name: "steps-context-outcome"},
|
||||
{input: "steps.step-id['outcome'] == 'success'", expected: true, name: "steps-context-outcome"},
|
||||
{input: "steps.step-id.outcome && true", expected: true, name: "steps-context-outcome"},
|
||||
{input: "steps['step-id']['outcome'] && true", expected: true, name: "steps-context-outcome"},
|
||||
{input: "steps.step-id2.outcome", expected: "failure", name: "steps-context-outcome"},
|
||||
{input: "steps.step-id2.outcome && true", expected: true, name: "steps-context-outcome"},
|
||||
// Disabled, since the interpreter is still too broken
|
||||
// {"contains(steps.*.outcome, 'success')", true, "steps-context-array-outcome"},
|
||||
// {"contains(steps.*.outcome, 'failure')", true, "steps-context-array-outcome"},
|
||||
// {"contains(steps.*.outputs.name, 'value')", true, "steps-context-array-outputs"},
|
||||
{"runner.os", "Linux", "runner-context"},
|
||||
{"secrets.name", "value", "secrets-context"},
|
||||
{"vars.name", "value", "vars-context"},
|
||||
{"strategy.fail-fast", true, "strategy-context"},
|
||||
{"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"},
|
||||
{input: "runner.os", expected: "Linux", name: "runner-context"},
|
||||
{input: "secrets.name", expected: "value", name: "secrets-context"},
|
||||
{input: "vars.name", expected: "value", name: "vars-context"},
|
||||
{input: "strategy.fail-fast", expected: true, name: "strategy-context"},
|
||||
{input: "matrix.os", expected: "Linux", name: "matrix-context"},
|
||||
{input: "needs.job-id.outputs.output-name", expected: "value", name: "needs-context"},
|
||||
{input: "needs.job-id.result", expected: "success", name: "needs-context"},
|
||||
{input: "contains(needs.*.result, 'success')", expected: true, name: "needs-wildcard-context-contains-success"},
|
||||
{input: "contains(needs.*.result, 'failure')", expected: false, name: "needs-wildcard-context-contains-failure"},
|
||||
{input: "inputs.name", expected: "value", name: "inputs-context"},
|
||||
{input: "vars.MY_VAR", expected: "refs/heads/test-data", name: "vars-context", ctxdata: map[string]interface{}{"vars": map[string]interface{}{"MY_VAR": "refs/heads/test-data"}}},
|
||||
{input: "vars.MY_VAR", expected: "refs/heads/test-data", name: "vars-context", ctxdata: map[string]interface{}{"vars": map[string]interface{}{"my_var": "refs/heads/test-data"}}},
|
||||
}
|
||||
|
||||
env := &EvaluationEnvironment{
|
||||
env := EvaluationEnvironment{
|
||||
Github: &model.GithubContext{
|
||||
Action: "push",
|
||||
},
|
||||
@@ -626,7 +638,10 @@ func TestContexts(t *testing.T) {
|
||||
|
||||
for _, tt := range table {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
|
||||
tenv := env
|
||||
tenv.EnvCS = tt.caseSensitiveEnv
|
||||
tenv.CtxData = tt.ctxdata
|
||||
output, err := NewInterpeter(&tenv, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, tt.expected, output)
|
||||
|
||||
Reference in New Issue
Block a user