[PR #287] [MERGED] Rewrite contexts before evaluating them #1402

Closed
opened 2026-03-01 21:51:01 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/nektos/act/pull/287
Author: @badouralix
Created: 6/21/2020
Status: Merged
Merged: 6/24/2020
Merged by: @cplee

Base: masterHead: rewrite-hyphens


📝 Commits (5)

  • 5a72c07 Rewrite contexts before evaluating them
  • a429c03 Precompile context and expression patterns
  • d7eec0a Test trim before rewrite
  • d27985c Merge branch 'master' into rewrite-hyphens
  • 364a344 Merge branch 'master' into rewrite-hyphens

📊 Changes

2 files changed (+101 additions, -14 deletions)

View changed files

📝 pkg/runner/expression.go (+36 -9)
📝 pkg/runner/expression_test.go (+65 -5)

📄 Description

Description

This PR aims to fix https://github.com/nektos/act/issues/104. In a nutshell, act uses a javascript virtual machine to interpret and evaluate expressions in github actions. It works pretty well, except for expressions like object.property-with-hyphens which are correctly handled by github action runners, but not by the javascript vm.

This solution is heavily inspired by https://github.com/nektos/act/issues/104#issuecomment-636213788 written by @Shadowfiend, although the implementation diverged a little bit from the initial proposal. The idea is to tweak just a little bit the expression and transform it into object['property-with-hyphens'] which is both github actions and javascript compliant.

To do so, we introduce the Rewrite method which job is exactly the one described above, and we call it just before evaluating the expression in the javascript vm.

Explanations

⚠️ The current implementation relies on a regex hackery ( see here to test it online ), which is fairly ugly and hard to maintain. In the future, we should invest some time to implement a proper lexer/parser and a proper term rewriter. But in the short-term, this regex works.

That being said, using regex for this rewriting is really hard to get right !

Here is a snapshot of what I tried...

^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_-]*|\[.+\])*$
^\w+(?:\[.+\])*(\.[\w-]+)?
^(\w+(\[.+\])*)(\.[\w-]+)?
^(\w+(?:\[.+\])*)(\.[\w-]+)?
^(\w+(?:\[.+\])*)(?:\.([\w-]+))?
^(\w+(?:\[.+\])*)(?:\.([\w-]+))?(.*)$
^(?P<object>\w+(\[.+\])*)(\.(?P<property>[\w-]+))?(?P<trailing>.*)$

One thing to keep in mind is that not all . nor not all - are bad. For instance we do not want to rewrite filenames with extension.
But also, we would need to identify strings, escaped characters, already-bracketed expressions, recursive expressions, etc.

So let's keep it simple for now. We choose to only rewrite contexts, which is a subset of expressions
https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts

The pattern defines three groups, as described below :

^(\w+(?:\[.+\])*)(?:\.([\w-]+))?(.*)$
  \            /       \    /    \/
   '-----.----'         \  /      Remaining characters
         |               \/
         |                First level of property using dots
         |
         Current object
         Potentially nested object, but using the bracket notation

Here is the execution of the method step by step with in := "ecole.centrale.paris". See also https://play.golang.org/p/bu2rO817ycU

  1. Before entering the loop

    Variable Value
    re "ecole.centrale.paris"
  2. First iteration

    Variable Value
    matches[0] "ecole.centrale.paris"
    matches[1] "ecole"
    matches[2] "centrale"
    matches[3] ".paris"
    re "ecole['centrale'].paris"
  3. Second iteration

    Variable Value
    matches[0] "ecole['centrale'].paris"
    matches[1] "ecole['centrale']"
    matches[2] "paris"
    matches[3] ""
    re "ecole['centrale']['paris']"
  4. Third iteration

    Variable Value
    matches[0] "ecole['centrale']['paris']"
    matches[1] "ecole['centrale']['paris']"
    matches[2] ""
    matches[3] ""
    re "ecole['centrale']['paris']"

    Here we trigger the break condition and the function returns

Testing

Before the fix :

$ act --version
act version 0.2.9

$ act -P ubuntu-latest=node:12.6-buster-slim -W pkg/runner/testdata/issue-104
[Test stuff/Testing Testing] 🚀  Start image=node:12.6-buster-slim
[Test stuff/Testing Testing]   🐳  docker run image=node:12.6-buster-slim entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Test stuff/Testing Testing] ⭐  Run hello
[Test stuff/Testing Testing]   ☁  git clone 'https://github.com/actions/hello-world-docker-action' # ref=master
ERRO[0001] Unable to interpolate string '${{ inputs.who-to-greet }}' - [ReferenceError: 'to' is not defined]
[Test stuff/Testing Testing]   🐳  docker build -t act-actions-hello-world-docker-action-master:latest /Users/ayaz.badouraly/.cache/act/actions-hello-world-docker-action@master
[Test stuff/Testing Testing]   🐳  docker run image=act-actions-hello-world-docker-action-master:latest entrypoint=[] cmd=[""]
| Hello
[Test stuff/Testing Testing]   ⚙  ::set-output:: time=Sun Jun 21 17:15:37 UTC 2020
[Test stuff/Testing Testing]   ✅  Success - hello

After the fix :

$ go run . -P ubuntu-latest=node:12.6-buster-slim -W pkg/runner/testdata/issue-104
[Test stuff/Testing Testing] 🚀  Start image=node:12.6-buster-slim
[Test stuff/Testing Testing]   🐳  docker run image=node:12.6-buster-slim entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Test stuff/Testing Testing] ⭐  Run hello
[Test stuff/Testing Testing]   ☁  git clone 'https://github.com/actions/hello-world-docker-action' # ref=master
[Test stuff/Testing Testing]   🐳  docker build -t act-actions-hello-world-docker-action-master:latest /Users/ayaz.badouraly/.cache/act/actions-hello-world-docker-action@master
[Test stuff/Testing Testing]   🐳  docker run image=act-actions-hello-world-docker-action-master:latest entrypoint=[] cmd=["World"]
| Hello World
[Test stuff/Testing Testing]   ⚙  ::set-output:: time=Sun Jun 21 17:16:33 UTC 2020
[Test stuff/Testing Testing]   ✅  Success - hello

References


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/nektos/act/pull/287 **Author:** [@badouralix](https://github.com/badouralix) **Created:** 6/21/2020 **Status:** ✅ Merged **Merged:** 6/24/2020 **Merged by:** [@cplee](https://github.com/cplee) **Base:** `master` ← **Head:** `rewrite-hyphens` --- ### 📝 Commits (5) - [`5a72c07`](https://github.com/nektos/act/commit/5a72c0794da4f1e0f782eb102dfb69f2e4873acc) Rewrite contexts before evaluating them - [`a429c03`](https://github.com/nektos/act/commit/a429c03bf3a0856c9479ce47d29657f9e559c844) Precompile context and expression patterns - [`d7eec0a`](https://github.com/nektos/act/commit/d7eec0ad298975be0af83c98a8a9707728d2d530) Test trim before rewrite - [`d27985c`](https://github.com/nektos/act/commit/d27985c5db18cb448372d7fe05a664f289681443) Merge branch 'master' into rewrite-hyphens - [`364a344`](https://github.com/nektos/act/commit/364a34458e68a173f2e3745b92f8d64fd54e32e0) Merge branch 'master' into rewrite-hyphens ### 📊 Changes **2 files changed** (+101 additions, -14 deletions) <details> <summary>View changed files</summary> 📝 `pkg/runner/expression.go` (+36 -9) 📝 `pkg/runner/expression_test.go` (+65 -5) </details> ### 📄 Description ## Description This PR aims to fix https://github.com/nektos/act/issues/104. In a nutshell, `act` uses a [javascript virtual machine](https://github.com/robertkrimen/otto) to interpret and evaluate expressions in github actions. It works pretty well, except for expressions like `object.property-with-hyphens` which are correctly handled by github action runners, but not by the javascript vm. This solution is heavily inspired by https://github.com/nektos/act/issues/104#issuecomment-636213788 written by @Shadowfiend, although the implementation diverged a little bit from the initial proposal. The idea is to tweak just a little bit the expression and transform it into `object['property-with-hyphens']` which is both github actions and javascript compliant. To do so, we introduce the `Rewrite` method which job is exactly the one described above, and we call it just before evaluating the expression in the javascript vm. ## Explanations ⚠️ The current implementation relies on a regex hackery ( see [here](https://regex101.com/r/Xm1Y0I) to test it online ), which is fairly ugly and hard to maintain. In the future, we should invest some time to implement a proper lexer/parser and a proper term rewriter. But in the short-term, this regex works. That being said, using regex for this rewriting is really hard to get right ! <details> <summary> Here is a snapshot of what I tried... </summary> <p> ```regex ^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_-]*|\[.+\])*$ ^\w+(?:\[.+\])*(\.[\w-]+)? ^(\w+(\[.+\])*)(\.[\w-]+)? ^(\w+(?:\[.+\])*)(\.[\w-]+)? ^(\w+(?:\[.+\])*)(?:\.([\w-]+))? ^(\w+(?:\[.+\])*)(?:\.([\w-]+))?(.*)$ ^(?P<object>\w+(\[.+\])*)(\.(?P<property>[\w-]+))?(?P<trailing>.*)$ ``` </p> </details> One thing to keep in mind is that not all `.` nor not all `-` are bad. For instance we do not want to rewrite filenames with extension. But also, we would need to identify strings, escaped characters, already-bracketed expressions, recursive expressions, etc. So let's keep it simple for now. We choose to only rewrite contexts, which is a subset of expressions https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts The pattern defines three groups, as described below : ```regex ^(\w+(?:\[.+\])*)(?:\.([\w-]+))?(.*)$ \ / \ / \/ '-----.----' \ / Remaining characters | \/ | First level of property using dots | Current object Potentially nested object, but using the bracket notation ``` Here is the execution of the method step by step with `in := "ecole.centrale.paris"`. See also <https://play.golang.org/p/bu2rO817ycU> 0. Before entering the loop | Variable | Value | |:---:|:---:| | `re` | `"ecole.centrale.paris"` | 1. First iteration | Variable | Value | |:---:|:---:| | `matches[0]` | `"ecole.centrale.paris"` | | `matches[1]` | `"ecole"` | | `matches[2]` | `"centrale"` | | `matches[3]` | `".paris"` | | `re` | `"ecole['centrale'].paris"` | 2. Second iteration | Variable | Value | |:---:|:---:| | `matches[0]` | `"ecole['centrale'].paris"` | | `matches[1]` | `"ecole['centrale']"` | | `matches[2]` | `"paris"` | | `matches[3]` | `""` | | `re` | `"ecole['centrale']['paris']"` | 3. Third iteration | Variable | Value | |:---:|:---:| | `matches[0]` | `"ecole['centrale']['paris']"` | | `matches[1]` | `"ecole['centrale']['paris']"` | | `matches[2]` | `""` | | `matches[3]` | `""` | | `re` | `"ecole['centrale']['paris']"` | Here we trigger the break condition and the function returns ## Testing Before the fix : ```bash $ act --version act version 0.2.9 $ act -P ubuntu-latest=node:12.6-buster-slim -W pkg/runner/testdata/issue-104 [Test stuff/Testing Testing] 🚀 Start image=node:12.6-buster-slim [Test stuff/Testing Testing] 🐳 docker run image=node:12.6-buster-slim entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[] [Test stuff/Testing Testing] ⭐ Run hello [Test stuff/Testing Testing] ☁ git clone 'https://github.com/actions/hello-world-docker-action' # ref=master ERRO[0001] Unable to interpolate string '${{ inputs.who-to-greet }}' - [ReferenceError: 'to' is not defined] [Test stuff/Testing Testing] 🐳 docker build -t act-actions-hello-world-docker-action-master:latest /Users/ayaz.badouraly/.cache/act/actions-hello-world-docker-action@master [Test stuff/Testing Testing] 🐳 docker run image=act-actions-hello-world-docker-action-master:latest entrypoint=[] cmd=[""] | Hello [Test stuff/Testing Testing] ⚙ ::set-output:: time=Sun Jun 21 17:15:37 UTC 2020 [Test stuff/Testing Testing] ✅ Success - hello ``` After the fix : ```bash $ go run . -P ubuntu-latest=node:12.6-buster-slim -W pkg/runner/testdata/issue-104 [Test stuff/Testing Testing] 🚀 Start image=node:12.6-buster-slim [Test stuff/Testing Testing] 🐳 docker run image=node:12.6-buster-slim entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[] [Test stuff/Testing Testing] ⭐ Run hello [Test stuff/Testing Testing] ☁ git clone 'https://github.com/actions/hello-world-docker-action' # ref=master [Test stuff/Testing Testing] 🐳 docker build -t act-actions-hello-world-docker-action-master:latest /Users/ayaz.badouraly/.cache/act/actions-hello-world-docker-action@master [Test stuff/Testing Testing] 🐳 docker run image=act-actions-hello-world-docker-action-master:latest entrypoint=[] cmd=["World"] | Hello World [Test stuff/Testing Testing] ⚙ ::set-output:: time=Sun Jun 21 17:16:33 UTC 2020 [Test stuff/Testing Testing] ✅ Success - hello ``` ## References - [Expressions in github actions](https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions) - [Dot notation vs. bracket notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#Description) - [Test the regex online](https://regex101.com/r/Xm1Y0I) - [Test the rewrite function online](https://play.golang.org/p/bu2rO817ycU) - [École Centrale Paris](https://en.wikipedia.org/wiki/%C3%89cole_Centrale_Paris) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
kerem 2026-03-01 21:51:01 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/act#1402
No description provided.