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:
@@ -43,7 +43,6 @@ type styleDef struct {
|
||||
|
||||
var styleDefs = []styleDef{
|
||||
{"\u2554", "\u2557", "\u255a", "\u255d", "\u2550", "\u2551"},
|
||||
//{"\u250c", "\u2510", "\u2514", "\u2518", "\u2500", "\u2502"},
|
||||
{"\u256d", "\u256e", "\u2570", "\u256f", "\u2500", "\u2502"},
|
||||
{"\u250c", "\u2510", "\u2514", "\u2518", "\u254c", "\u254e"},
|
||||
{" ", " ", " ", " ", " ", " "},
|
||||
|
@@ -157,7 +157,6 @@ func findGitDirectory(fromFile string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//log.Debugf("Searching for git directory in %s", absPath)
|
||||
fi, err := os.Stat(absPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@@ -2,6 +2,7 @@ package container
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-billy/v5/helper/polyfill"
|
||||
@@ -24,7 +26,7 @@ import (
|
||||
"github.com/nektos/act/pkg/common"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// NewContainerInput the input for the New function
|
||||
@@ -58,6 +60,7 @@ type Container interface {
|
||||
Pull(forcePull bool) common.Executor
|
||||
Start(attach bool) common.Executor
|
||||
Exec(command []string, env map[string]string) common.Executor
|
||||
UpdateFromGithubEnv(env *map[string]string) common.Executor
|
||||
Remove() common.Executor
|
||||
}
|
||||
|
||||
@@ -116,6 +119,10 @@ func (cr *containerReference) CopyDir(destPath string, srcPath string) common.Ex
|
||||
).IfNot(common.Dryrun)
|
||||
}
|
||||
|
||||
func (cr *containerReference) UpdateFromGithubEnv(env *map[string]string) common.Executor {
|
||||
return cr.extractGithubEnv(env).IfNot(common.Dryrun)
|
||||
}
|
||||
|
||||
func (cr *containerReference) Exec(command []string, env map[string]string) common.Executor {
|
||||
|
||||
return common.NewPipelineExecutor(
|
||||
@@ -196,10 +203,10 @@ func (cr *containerReference) find() common.Executor {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
for _, container := range containers {
|
||||
for _, name := range container.Names {
|
||||
for _, c := range containers {
|
||||
for _, name := range c.Names {
|
||||
if name[1:] == cr.input.Name {
|
||||
cr.id = container.ID
|
||||
cr.id = c.ID
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -237,7 +244,7 @@ func (cr *containerReference) create() common.Executor {
|
||||
return nil
|
||||
}
|
||||
logger := common.Logger(ctx)
|
||||
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
|
||||
|
||||
input := cr.input
|
||||
config := &container.Config{
|
||||
@@ -275,11 +282,59 @@ func (cr *containerReference) create() common.Executor {
|
||||
}
|
||||
}
|
||||
|
||||
var singleLineEnvPattern, mulitiLineEnvPattern *regexp.Regexp
|
||||
|
||||
func (cr *containerReference) extractGithubEnv(env *map[string]string) common.Executor {
|
||||
if singleLineEnvPattern == nil {
|
||||
singleLineEnvPattern = regexp.MustCompile("^([^=]+)=([^=]+)$")
|
||||
mulitiLineEnvPattern = regexp.MustCompile(`^([^<]+)<<(\w+)$`)
|
||||
}
|
||||
|
||||
localEnv := *env
|
||||
return func(ctx context.Context) error {
|
||||
githubEnvTar, _, err := cr.cli.CopyFromContainer(ctx, cr.id, localEnv["GITHUB_ENV"])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
reader := tar.NewReader(githubEnvTar)
|
||||
_, err = reader.Next()
|
||||
if err != nil && err != io.EOF {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
s := bufio.NewScanner(reader)
|
||||
multiLineEnvKey := ""
|
||||
multiLineEnvDelimiter := ""
|
||||
multiLineEnvContent := ""
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
if singleLineEnv := singleLineEnvPattern.FindStringSubmatch(line); singleLineEnv != nil {
|
||||
localEnv[singleLineEnv[1]] = singleLineEnv[2]
|
||||
}
|
||||
if line == multiLineEnvDelimiter {
|
||||
localEnv[multiLineEnvKey] = multiLineEnvContent
|
||||
multiLineEnvKey, multiLineEnvDelimiter, multiLineEnvContent = "", "", ""
|
||||
}
|
||||
if multiLineEnvKey != "" && multiLineEnvDelimiter != "" {
|
||||
if multiLineEnvContent != "" {
|
||||
multiLineEnvContent += "\n"
|
||||
}
|
||||
multiLineEnvContent += line
|
||||
}
|
||||
if mulitiLineEnvStart := mulitiLineEnvPattern.FindStringSubmatch(line); mulitiLineEnvStart != nil {
|
||||
multiLineEnvKey = mulitiLineEnvStart[1]
|
||||
multiLineEnvDelimiter = mulitiLineEnvStart[2]
|
||||
}
|
||||
}
|
||||
env = &localEnv
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *containerReference) exec(cmd []string, env map[string]string) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
logger.Debugf("Exec command '%s'", cmd)
|
||||
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
|
||||
envList := make([]string, 0)
|
||||
for k, v := range env {
|
||||
envList = append(envList, fmt.Sprintf("%s=%s", k, v))
|
||||
@@ -492,7 +547,7 @@ func (cr *containerReference) attach() common.Executor {
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
|
||||
|
||||
var outWriter io.Writer
|
||||
outWriter = cr.input.Stdout
|
||||
|
@@ -283,13 +283,13 @@ const (
|
||||
// StepTypeRun is all steps that have a `run` attribute
|
||||
StepTypeRun StepType = iota
|
||||
|
||||
//StepTypeUsesDockerURL is all steps that have a `uses` that is of the form `docker://...`
|
||||
// StepTypeUsesDockerURL is all steps that have a `uses` that is of the form `docker://...`
|
||||
StepTypeUsesDockerURL
|
||||
|
||||
//StepTypeUsesActionLocal is all steps that have a `uses` that is a local action in a subdirectory
|
||||
// StepTypeUsesActionLocal is all steps that have a `uses` that is a local action in a subdirectory
|
||||
StepTypeUsesActionLocal
|
||||
|
||||
//StepTypeUsesActionRemote is all steps that have a `uses` that is a reference to a github repo
|
||||
// StepTypeUsesActionRemote is all steps that have a `uses` that is a reference to a github repo
|
||||
StepTypeUsesActionRemote
|
||||
)
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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])
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user