GitHub env file support (#426)

* Upgrade to the official golangci-lint action and fix some issues it found

* Update deps

* Remove a shadow warning

* Initialize the splitPattern only once

* Initial attempt at supporting $GITHUB_ENV

Needs some polishing and tests

* Now it's actually working

* Replace golang.org/x/crypto/ssh/terminal with golang.org/x/term

* Disable the issue-228 test again

* The linter is picky

* Discovered that the workflow/envs.txt had to exist in certain cases

* Fix small linter issue
This commit is contained in:
Torbjørn Vatn
2021-01-12 07:39:43 +01:00
committed by GitHub
parent 8887daa3e7
commit 15eaa15a0e
16 changed files with 492 additions and 107 deletions

View File

@@ -13,7 +13,7 @@ var commandPatternADO *regexp.Regexp
func init() {
commandPatternGA = regexp.MustCompile("^::([^ ]+)( (.+))?::([^\r\n]*)[\r\n]+$")
commandPatternADO = regexp.MustCompile("^##\\[([^ ]+)( (.+))?\\]([^\r\n]*)[\r\n]+$")
commandPatternADO = regexp.MustCompile("^##\\[([^ ]+)( (.+))?]([^\r\n]*)[\r\n]+$")
}
func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler {
@@ -103,7 +103,7 @@ func unescapeCommandData(arg string) string {
"%0A": "\n",
}
for k, v := range escapeMap {
arg = strings.Replace(arg, k, v, -1)
arg = strings.ReplaceAll(arg, k, v)
}
return arg
}
@@ -116,7 +116,7 @@ func unescapeCommandProperty(arg string) string {
"%2C": ",",
}
for k, v := range escapeMap {
arg = strings.Replace(arg, k, v, -1)
arg = strings.ReplaceAll(arg, k, v)
}
return arg
}

View File

@@ -8,17 +8,17 @@ import (
)
func TestSetEnv(t *testing.T) {
assert := assert.New(t)
a := assert.New(t)
ctx := context.Background()
rc := new(RunContext)
handler := rc.commandHandler(ctx)
handler("::set-env name=x::valz\n")
assert.Equal("valz", rc.Env["x"])
a.Equal("valz", rc.Env["x"])
}
func TestSetOutput(t *testing.T) {
assert := assert.New(t)
a := assert.New(t)
ctx := context.Background()
rc := new(RunContext)
rc.StepResults = make(map[string]*stepResult)
@@ -29,62 +29,62 @@ func TestSetOutput(t *testing.T) {
Outputs: make(map[string]string),
}
handler("::set-output name=x::valz\n")
assert.Equal("valz", rc.StepResults["my-step"].Outputs["x"])
a.Equal("valz", rc.StepResults["my-step"].Outputs["x"])
handler("::set-output name=x::percent2%25\n")
assert.Equal("percent2%", rc.StepResults["my-step"].Outputs["x"])
a.Equal("percent2%", rc.StepResults["my-step"].Outputs["x"])
handler("::set-output name=x::percent2%25%0Atest\n")
assert.Equal("percent2%\ntest", rc.StepResults["my-step"].Outputs["x"])
a.Equal("percent2%\ntest", rc.StepResults["my-step"].Outputs["x"])
handler("::set-output name=x::percent2%25%0Atest another3%25test\n")
assert.Equal("percent2%\ntest another3%test", rc.StepResults["my-step"].Outputs["x"])
a.Equal("percent2%\ntest another3%test", rc.StepResults["my-step"].Outputs["x"])
handler("::set-output name=x%3A::percent2%25%0Atest\n")
assert.Equal("percent2%\ntest", rc.StepResults["my-step"].Outputs["x:"])
a.Equal("percent2%\ntest", rc.StepResults["my-step"].Outputs["x:"])
handler("::set-output name=x%3A%2C%0A%25%0D%3A::percent2%25%0Atest\n")
assert.Equal("percent2%\ntest", rc.StepResults["my-step"].Outputs["x:,\n%\r:"])
a.Equal("percent2%\ntest", rc.StepResults["my-step"].Outputs["x:,\n%\r:"])
}
func TestAddpath(t *testing.T) {
assert := assert.New(t)
a := assert.New(t)
ctx := context.Background()
rc := new(RunContext)
handler := rc.commandHandler(ctx)
handler("::add-path::/zoo\n")
assert.Equal("/zoo", rc.ExtraPath[0])
a.Equal("/zoo", rc.ExtraPath[0])
handler("::add-path::/boo\n")
assert.Equal("/boo", rc.ExtraPath[1])
a.Equal("/boo", rc.ExtraPath[1])
}
func TestStopCommands(t *testing.T) {
assert := assert.New(t)
a := assert.New(t)
ctx := context.Background()
rc := new(RunContext)
handler := rc.commandHandler(ctx)
handler("::set-env name=x::valz\n")
assert.Equal("valz", rc.Env["x"])
a.Equal("valz", rc.Env["x"])
handler("::stop-commands::my-end-token\n")
handler("::set-env name=x::abcd\n")
assert.Equal("valz", rc.Env["x"])
a.Equal("valz", rc.Env["x"])
handler("::my-end-token::\n")
handler("::set-env name=x::abcd\n")
assert.Equal("abcd", rc.Env["x"])
a.Equal("abcd", rc.Env["x"])
}
func TestAddpathADO(t *testing.T) {
assert := assert.New(t)
a := assert.New(t)
ctx := context.Background()
rc := new(RunContext)
handler := rc.commandHandler(ctx)
handler("##[add-path]/zoo\n")
assert.Equal("/zoo", rc.ExtraPath[0])
a.Equal("/zoo", rc.ExtraPath[0])
handler("##[add-path]/boo\n")
assert.Equal("/boo", rc.ExtraPath[1])
a.Equal("/boo", rc.ExtraPath[1])
}

View File

@@ -185,7 +185,6 @@ func TestInterpolate(t *testing.T) {
func updateTestExpressionWorkflow(t *testing.T, tables []struct {
in string
out string
//wantErr bool
}, rc *RunContext) {
var envs string

View File

@@ -12,11 +12,11 @@ import (
"github.com/nektos/act/pkg/common"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/term"
)
const (
//nocolor = 0
// nocolor = 0
red = 31
green = 32
yellow = 33
@@ -126,7 +126,7 @@ func (f *stepLogFormatter) isColored(entry *logrus.Entry) bool {
func checkIfTerminal(w io.Writer) bool {
switch v := w.(type) {
case *os.File:
return terminal.IsTerminal(int(v.Fd()))
return term.IsTerminal(int(v.Fd()))
default:
return false
}

View File

@@ -125,6 +125,10 @@ func (rc *RunContext) startJobContainer() common.Executor {
Name: "workflow/event.json",
Mode: 0644,
Body: rc.EventJSON,
}, &container.FileEntry{
Name: "workflow/envs.txt",
Mode: 0644,
Body: "",
}, &container.FileEntry{
Name: "home/.act",
Mode: 0644,
@@ -199,6 +203,13 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
}
_ = sc.setupEnv()(ctx)
if sc.Env != nil {
err := rc.JobContainer.UpdateFromGithubEnv(&sc.Env)(ctx)
if err != nil {
return err
}
}
rc.ExprEval = sc.NewExpressionEvaluator()
runStep, err := rc.EvalBool(sc.Step.If)
@@ -267,14 +278,17 @@ func (rc *RunContext) isEnabled(ctx context.Context) bool {
return true
}
var splitPattern *regexp.Regexp
// EvalBool evaluates an expression against current run context
func (rc *RunContext) EvalBool(expr string) (bool, error) {
if splitPattern == nil {
splitPattern = regexp.MustCompile(fmt.Sprintf(`%s|%s|\S+`, expressionPattern.String(), operatorPattern.String()))
}
if strings.HasPrefix(strings.TrimSpace(expr), "!") {
return false, errors.New("expressions starting with ! must be wrapped in ${{ }}")
}
if expr != "" {
splitPattern := regexp.MustCompile(fmt.Sprintf(`%s|%s|\S+`, expressionPattern.String(), operatorPattern.String()))
parts := splitPattern.FindAllString(expr, -1)
var evaluatedParts []string
for i, part := range parts {
@@ -550,6 +564,8 @@ func (rc *RunContext) withGithubEnv(env map[string]string) map[string]string {
github := rc.getGithubContext()
env["CI"] = "true"
env["HOME"] = "/github/home"
env["GITHUB_ENV"] = "/github/workflow/envs.txt"
env["GITHUB_WORKFLOW"] = github.Workflow
env["GITHUB_RUN_ID"] = github.RunID
env["GITHUB_RUN_NUMBER"] = github.RunNumber

View File

@@ -92,7 +92,7 @@ func TestRunEvent(t *testing.T) {
{"testdata", "matrix-include-exclude", "push", "", platforms},
{"testdata", "commands", "push", "", platforms},
{"testdata", "workdir", "push", "", platforms},
//{"testdata", "issue-228", "push", "", platforms}, // TODO [igni]: Remove this once everything passes
// {"testdata", "issue-228", "push", "", platforms}, // TODO [igni]: Remove this once everything passes
{"testdata", "defaults-run", "push", "", platforms},
}
log.SetLevel(log.DebugLevel)