diff --git a/.gitignore b/.gitignore index b326214..f226e1f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ dist/ *.nupkg .vscode/ +.idea/ diff --git a/pkg/runner/expression.go b/pkg/runner/expression.go index 2d52c02..26c531e 100644 --- a/pkg/runner/expression.go +++ b/pkg/runner/expression.go @@ -119,6 +119,7 @@ func (rc *RunContext) newVM() *otto.Otto { rc.vmSecrets(), rc.vmStrategy(), rc.vmMatrix(), + rc.vmEnv(), } vm := otto.New() for _, configer := range configers { @@ -196,18 +197,18 @@ func (rc *RunContext) vmHashFiles() func(*otto.Otto) { _ = vm.Set("hashFiles", func(path string) string { files, _, err := glob.Glob([]string{filepath.Join(rc.Config.Workdir, path)}) if err != nil { - logrus.Error(err) + logrus.Errorf("Unable to glob.Glob: %v", err) return "" } hasher := sha256.New() for _, file := range files { f, err := os.Open(file.Path) if err != nil { - logrus.Error(err) + logrus.Errorf("Unable to os.Open: %v", err) } defer f.Close() if _, err := io.Copy(hasher, f); err != nil { - logrus.Error(err) + logrus.Errorf("Unable to io.Copy: %v", err) } } return hex.EncodeToString(hasher.Sum(nil)) @@ -251,6 +252,14 @@ func (rc *RunContext) vmGithub() func(*otto.Otto) { } } +func (rc *RunContext) vmEnv() func(*otto.Otto) { + return func(vm *otto.Otto) { + env := rc.GetEnv() + log.Debugf("context env => %v", env) + _ = vm.Set("env", env) + } +} + func (sc *StepContext) vmEnv() func(*otto.Otto) { return func(vm *otto.Otto) { log.Debugf("context env => %v", sc.Env) diff --git a/pkg/runner/expression_test.go b/pkg/runner/expression_test.go index 6c01fe8..c713a75 100644 --- a/pkg/runner/expression_test.go +++ b/pkg/runner/expression_test.go @@ -4,15 +4,18 @@ import ( "testing" "github.com/nektos/act/pkg/model" - "github.com/stretchr/testify/assert" + a "github.com/stretchr/testify/assert" ) func TestEvaluate(t *testing.T) { - assert := assert.New(t) + assert := a.New(t) rc := &RunContext{ Config: &Config{ Workdir: ".", }, + Env: map[string]string{ + "key": "value", + }, Run: &model.Run{ JobID: "job1", Workflow: &model.Workflow{ @@ -79,6 +82,8 @@ func TestEvaluate(t *testing.T) { {"runner.os", "Linux", ""}, {"matrix.os", "Linux", ""}, {"matrix.foo", "bar", ""}, + {"env.key", "value", ""}, + } for _, table := range tables { @@ -97,11 +102,14 @@ func TestEvaluate(t *testing.T) { } func TestInterpolate(t *testing.T) { - assert := assert.New(t) + assert := a.New(t) rc := &RunContext{ Config: &Config{ Workdir: ".", }, + Env: map[string]string{ + "key": "value", + }, Run: &model.Run{ JobID: "job1", Workflow: &model.Workflow{ @@ -113,8 +121,20 @@ func TestInterpolate(t *testing.T) { }, } ee := rc.NewExpressionEvaluator() + tables := []struct{ + in string + out string + }{ + {" ${{1}} to ${{2}} ", " 1 to 2 "}, + {" ${{ env.key }} ", " value "}, + {"${{ env.unknown }}", ""}, + } - out := ee.Interpolate(" ${{1}} to ${{2}} ") - - assert.Equal(" 1 to 2 ", out) + for _, table := range tables { + table := table + t.Run(table.in, func(t *testing.T) { + out := ee.Interpolate(table.in) + assert.Equal(table.out, out, table.in) + }) + } } diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index 5823d43..8d9b255 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -235,15 +235,15 @@ func (rc *RunContext) platformImage() string { func (rc *RunContext) isEnabled(ctx context.Context) bool { job := rc.Run.Job() - log := common.Logger(ctx) + l := common.Logger(ctx) if !rc.EvalBool(job.If) { - log.Debugf("Skipping job '%s' due to '%s'", job.Name, job.If) + l.Debugf("Skipping job '%s' due to '%s'", job.Name, job.If) return false } img := rc.platformImage() if img == "" { - log.Infof("\U0001F6A7 Skipping unsupported platform '%+v'", job.RunsOn()) + l.Infof("\U0001F6A7 Skipping unsupported platform '%+v'", job.RunsOn()) return false } return true @@ -252,11 +252,9 @@ func (rc *RunContext) isEnabled(ctx context.Context) bool { // EvalBool evaluates an expression against current run context func (rc *RunContext) EvalBool(expr string) bool { if expr != "" { - //v, err := rc.ExprEval.Evaluate(fmt.Sprintf("if (%s) { true } else { false }", expr)) - expr := fmt.Sprintf("Boolean(%s)", expr) + expr = fmt.Sprintf("Boolean(%s)", rc.ExprEval.Interpolate(expr)) v, err := rc.ExprEval.Evaluate(expr) if err != nil { - log.Errorf("Error evaluating expression '%s' - %v", expr, err) return false } log.Debugf("expression '%s' evaluated to '%s'", expr, v) @@ -386,9 +384,11 @@ func (rc *RunContext) getGithubContext() *githubContext { log.Debugf("using github ref: %s", ref) ghc.Ref = ref } - err = json.Unmarshal([]byte(rc.EventJSON), &ghc.Event) - if err != nil { - logrus.Error(err) + if rc.EventJSON != "" { + err = json.Unmarshal([]byte(rc.EventJSON), &ghc.Event) + if err != nil { + logrus.Errorf("Unable to Unmarshal event '%s': %v", rc.EventJSON, err) + } } if ghc.EventName == "pull_request" { diff --git a/pkg/runner/run_context_test.go b/pkg/runner/run_context_test.go new file mode 100644 index 0000000..af07b8e --- /dev/null +++ b/pkg/runner/run_context_test.go @@ -0,0 +1,107 @@ +package runner + +import ( + "github.com/nektos/act/pkg/model" + a "github.com/stretchr/testify/assert" + "testing" + + "github.com/sirupsen/logrus/hooks/test" +) + +func TestRunContext_EvalBool(t *testing.T) { + hook := test.NewGlobal() + assert := a.New(t) + rc := &RunContext{ + Config: &Config{ + Workdir: ".", + }, + Env: map[string]string{ + "TRUE": "true", + "FALSE": "false", + "SOME_TEXT": "text", + }, + Run: &model.Run{ + JobID: "job1", + Workflow: &model.Workflow{ + Name: "test-workflow", + Jobs: map[string]*model.Job{ + "job1": { + Strategy: &model.Strategy{ + Matrix: map[string][]interface{}{ + "os": {"Linux", "Windows"}, + "foo": {"bar", "baz"}, + }, + }, + }, + }, + }, + }, + Matrix: map[string]interface{}{ + "os": "Linux", + "foo": "bar", + }, + StepResults: map[string]*stepResult{ + "id1": { + Outputs: map[string]string{ + "foo": "bar", + }, + Success: true, + }, + }, + } + rc.ExprEval = rc.NewExpressionEvaluator() + + tables := []struct { + in string + out bool + }{ + // The basic ones + {"true", true}, + {"false", false}, + {"1 !== 0", true}, + {"1 !== 1", false}, + {"1 == 0", false}, + {"1 == 1", true}, + {"1 > 2", false}, + {"1 < 2", true}, + {"success()", true}, + {"failure()", false}, + // And or + {"true && false", false}, + {"true && 1 < 2", true}, + {"false || 1 < 2", true}, + {"false || false", false}, + // None boolable + {"env.SOME_TEXT", true}, + {"env.UNKNOWN == 'true'", false}, + {"env.UNKNOWN", false}, + // Inline expressions + {"env.TRUE == 'true'", true}, + {"env.FALSE == 'true'", false}, + {"${{env.TRUE == 'true'}}", true}, + {"${{env.FALSE == 'true'}}", false}, + {"${{env.FALSE == 'false'}}", true}, + // All together now + {"false || env.TRUE == 'true'", true}, + {"true || env.FALSE == 'true'", true}, + {"true && env.TRUE == 'true'", true}, + {"false && env.TRUE == 'true'", false}, + {"env.FALSE == 'true' && env.TRUE == 'true'", false}, + {"env.FALSE == 'true' && true", false}, + {"${{env.FALSE == 'true'}} && true", false}, + // Check github context + {"github.actor == 'nektos/act'", true}, + {"github.actor == 'unknown'", false}, + } + + for _, table := range tables { + table := table + t.Run(table.in, func(t *testing.T) { + defer hook.Reset() + b := rc.EvalBool(table.in) + + assert.Equal(table.out, b, table.in) + assert.Empty(hook.LastEntry(), table.in) + }) + } +}