add commands support
Signed-off-by: Casey Lee <cplee@nektos.com>
This commit is contained in:
parent
033168228b
commit
f7252cbcf9
12 changed files with 303 additions and 33 deletions
2
.github/workflows/push.yml
vendored
2
.github/workflows/push.yml
vendored
|
@ -6,5 +6,5 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/workflows/check
|
||||
#- uses: ./.github/workflows/check
|
||||
- uses: ./.github/workflows/integration
|
||||
|
|
47
pkg/common/line_writer.go
Normal file
47
pkg/common/line_writer.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// LineHandler is a callback function for handling a line
|
||||
type LineHandler func(line string)
|
||||
|
||||
type lineWriter struct {
|
||||
buffer bytes.Buffer
|
||||
handlers []LineHandler
|
||||
}
|
||||
|
||||
// NewLineWriter creates a new instance of a line writer
|
||||
func NewLineWriter(handlers ...LineHandler) io.Writer {
|
||||
w := new(lineWriter)
|
||||
w.handlers = handlers
|
||||
return w
|
||||
}
|
||||
|
||||
func (lw *lineWriter) Write(p []byte) (n int, err error) {
|
||||
pBuf := bytes.NewBuffer(p)
|
||||
written := 0
|
||||
for {
|
||||
line, err := pBuf.ReadString('\n')
|
||||
w, _ := lw.buffer.WriteString(line)
|
||||
written += w
|
||||
if err == nil {
|
||||
lw.handleLine(lw.buffer.String())
|
||||
lw.buffer.Reset()
|
||||
} else if err == io.EOF {
|
||||
break
|
||||
} else {
|
||||
return written, err
|
||||
}
|
||||
}
|
||||
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func (lw *lineWriter) handleLine(line string) {
|
||||
for _, h := range lw.handlers {
|
||||
h(line)
|
||||
}
|
||||
}
|
36
pkg/common/line_writer_test.go
Normal file
36
pkg/common/line_writer_test.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLineWriter(t *testing.T) {
|
||||
lines := make([]string, 0)
|
||||
lineHandler := func(s string) {
|
||||
lines = append(lines, s)
|
||||
}
|
||||
|
||||
lineWriter := NewLineWriter(lineHandler)
|
||||
|
||||
assert := assert.New(t)
|
||||
write := func(s string) {
|
||||
n, err := lineWriter.Write([]byte(s))
|
||||
assert.NoError(err)
|
||||
assert.Equal(len(s), n, s)
|
||||
}
|
||||
|
||||
write("hello")
|
||||
write(" ")
|
||||
write("world!!\nextra")
|
||||
write(" line\n and another\nlast")
|
||||
write(" line\n")
|
||||
write("no newline here...")
|
||||
|
||||
assert.Len(lines, 4)
|
||||
assert.Equal("hello world!!\n", lines[0])
|
||||
assert.Equal("extra line\n", lines[1])
|
||||
assert.Equal(" and another\n", lines[2])
|
||||
assert.Equal("last line\n", lines[3])
|
||||
}
|
|
@ -2,15 +2,11 @@ package container
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/nektos/act/pkg/common"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
)
|
||||
|
||||
type dockerMessage struct {
|
||||
|
@ -26,6 +22,7 @@ type dockerMessage struct {
|
|||
|
||||
const logPrefix = " \U0001F433 "
|
||||
|
||||
/*
|
||||
func logDockerOutput(ctx context.Context, dockerResponse io.Reader) {
|
||||
logger := common.Logger(ctx)
|
||||
if entry, ok := logger.(*logrus.Entry); ok {
|
||||
|
@ -44,7 +41,9 @@ func logDockerOutput(ctx context.Context, dockerResponse io.Reader) {
|
|||
logrus.Errorf("Unable to get writer from logger (type=%T)", logger)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
func streamDockerOutput(ctx context.Context, dockerResponse io.Reader) {
|
||||
/*
|
||||
out := os.Stdout
|
||||
|
@ -74,6 +73,7 @@ func streamDockerOutput(ctx context.Context, dockerResponse io.Reader) {
|
|||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
func logDockerResponse(logger logrus.FieldLogger, dockerResponse io.ReadCloser, isError bool) error {
|
||||
if dockerResponse == nil {
|
||||
|
|
91
pkg/runner/command.go
Normal file
91
pkg/runner/command.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/nektos/act/pkg/common"
|
||||
)
|
||||
|
||||
var commandPattern *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
commandPattern = regexp.MustCompile("^::([^ ]+)( (.+))?::([^\r\n]*)[\r\n]+$")
|
||||
}
|
||||
|
||||
func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler {
|
||||
logger := common.Logger(ctx)
|
||||
resumeCommand := ""
|
||||
return func(line string) {
|
||||
if m := commandPattern.FindStringSubmatch(line); m != nil {
|
||||
command := m[1]
|
||||
kvPairs := parseKeyValuePairs(m[3])
|
||||
arg := m[4]
|
||||
|
||||
if resumeCommand != "" && command != resumeCommand {
|
||||
return
|
||||
}
|
||||
|
||||
switch command {
|
||||
case "set-env":
|
||||
rc.setEnv(ctx, kvPairs, arg)
|
||||
case "set-output":
|
||||
rc.setOutput(ctx, kvPairs, arg)
|
||||
case "add-path":
|
||||
rc.addPath(ctx, arg)
|
||||
case "debug":
|
||||
logger.Infof(" \U0001F4AC %s", line)
|
||||
case "warning":
|
||||
logger.Infof(" \U0001F6A7 %s", line)
|
||||
case "error":
|
||||
logger.Infof(" \U00002757 %s", line)
|
||||
case "add-mask":
|
||||
logger.Infof(" \U00002699 %s", line)
|
||||
case "stop-commands":
|
||||
resumeCommand = arg
|
||||
logger.Infof(" \U00002699 %s", line)
|
||||
case resumeCommand:
|
||||
resumeCommand = ""
|
||||
logger.Infof(" \U00002699 %s", line)
|
||||
default:
|
||||
logger.Infof(" \U00002753 %s", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *RunContext) setEnv(ctx context.Context, kvPairs map[string]string, arg string) {
|
||||
common.Logger(ctx).Infof(" \U00002699 ::set-env:: %s=%s", kvPairs["name"], arg)
|
||||
if rc.Env == nil {
|
||||
rc.Env = make(map[string]string)
|
||||
}
|
||||
rc.Env[kvPairs["name"]] = arg
|
||||
}
|
||||
func (rc *RunContext) setOutput(ctx context.Context, kvPairs map[string]string, arg string) {
|
||||
common.Logger(ctx).Infof(" \U00002699 ::set-output:: %s=%s", kvPairs["name"], arg)
|
||||
if rc.Outputs == nil {
|
||||
rc.Outputs = make(map[string]string)
|
||||
}
|
||||
rc.Outputs[kvPairs["name"]] = arg
|
||||
}
|
||||
func (rc *RunContext) addPath(ctx context.Context, arg string) {
|
||||
common.Logger(ctx).Infof(" \U00002699 ::add-path:: %s", arg)
|
||||
if rc.Env == nil {
|
||||
rc.Env = make(map[string]string)
|
||||
}
|
||||
rc.Env["PATH"] = fmt.Sprintf("%s:%s", arg, rc.Env["PATH"])
|
||||
}
|
||||
|
||||
func parseKeyValuePairs(kvPairs string) map[string]string {
|
||||
rtn := make(map[string]string)
|
||||
kvPairList := strings.Split(kvPairs, ",")
|
||||
for _, kvPair := range kvPairList {
|
||||
kv := strings.Split(kvPair, "=")
|
||||
if len(kv) == 2 {
|
||||
rtn[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
return rtn
|
||||
}
|
57
pkg/runner/command_test.go
Normal file
57
pkg/runner/command_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetEnv(t *testing.T) {
|
||||
assert := 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"])
|
||||
}
|
||||
|
||||
func TestSetOutput(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ctx := context.Background()
|
||||
rc := new(RunContext)
|
||||
handler := rc.commandHandler(ctx)
|
||||
|
||||
handler("::set-output name=x::valz\n")
|
||||
assert.Equal("valz", rc.Outputs["x"])
|
||||
}
|
||||
|
||||
func TestAddpath(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ctx := context.Background()
|
||||
rc := new(RunContext)
|
||||
handler := rc.commandHandler(ctx)
|
||||
|
||||
handler("::add-path::/zoo")
|
||||
assert.Equal("/zoo:", rc.Env["PATH"])
|
||||
|
||||
handler("::add-path::/booo")
|
||||
assert.Equal("/booo:/zoo:", rc.Env["PATH"])
|
||||
}
|
||||
|
||||
func TestStopCommands(t *testing.T) {
|
||||
assert := 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"])
|
||||
handler("::stop-commands::my-end-token\n")
|
||||
handler("::set-env name=x::abcd\n")
|
||||
assert.Equal("valz", rc.Env["x"])
|
||||
handler("::my-end-token::\n")
|
||||
handler("::set-env name=x::abcd\n")
|
||||
assert.Equal("abcd", rc.Env["x"])
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/nektos/act/pkg/common"
|
||||
|
||||
|
@ -27,6 +28,7 @@ const (
|
|||
|
||||
var colors []int
|
||||
var nextColor int
|
||||
var mux sync.Mutex
|
||||
|
||||
func init() {
|
||||
nextColor = 0
|
||||
|
@ -37,9 +39,11 @@ func init() {
|
|||
|
||||
// WithJobLogger attaches a new logger to context that is aware of steps
|
||||
func WithJobLogger(ctx context.Context, jobName string) context.Context {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
formatter := new(stepLogFormatter)
|
||||
formatter.color = colors[nextColor%len(colors)]
|
||||
nextColor = nextColor + 1
|
||||
nextColor++
|
||||
|
||||
logger := logrus.New()
|
||||
logger.SetFormatter(formatter)
|
||||
|
@ -71,7 +75,9 @@ func (f *stepLogFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry) {
|
|||
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
||||
jobName := entry.Data["job"]
|
||||
|
||||
if entry.Data["dryrun"] == true {
|
||||
if entry.Data["raw_output"] == true {
|
||||
fmt.Fprintf(b, "\x1b[%dm|\x1b[0m %s", f.color, entry.Message)
|
||||
} else if entry.Data["dryrun"] == true {
|
||||
fmt.Fprintf(b, "\x1b[1m\x1b[%dm\x1b[7m*DRYRUN*\x1b[0m \x1b[%dm[%s] \x1b[0m%s", gray, f.color, jobName, entry.Message)
|
||||
} else {
|
||||
fmt.Fprintf(b, "\x1b[%dm[%s] \x1b[0m%s", f.color, jobName, entry.Message)
|
||||
|
@ -82,7 +88,9 @@ func (f *stepLogFormatter) print(b *bytes.Buffer, entry *logrus.Entry) {
|
|||
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
||||
jobName := entry.Data["job"]
|
||||
|
||||
if entry.Data["dryrun"] == true {
|
||||
if entry.Data["raw_output"] == true {
|
||||
fmt.Fprintf(b, "[%s] | %s", jobName, entry.Message)
|
||||
} else if entry.Data["dryrun"] == true {
|
||||
fmt.Fprintf(b, "*DRYRUN* [%s] %s", jobName, entry.Message)
|
||||
} else {
|
||||
fmt.Fprintf(b, "[%s] %s", jobName, entry.Message)
|
||||
|
|
|
@ -57,9 +57,7 @@ func (rc *RunContext) Executor() common.Executor {
|
|||
}
|
||||
s := step
|
||||
steps = append(steps, func(ctx context.Context) error {
|
||||
//common.Logger(ctx).Infof("\U0001F680 Begin %s", step)
|
||||
//common.Logger(ctx).Infof("\u2728 Begin - %s", step)
|
||||
common.Logger(ctx).Infof("\u2B50 Begin - %s", s)
|
||||
common.Logger(ctx).Infof("\u2B50 Run %s", s)
|
||||
err := rc.newStepExecutor(s)(ctx)
|
||||
if err == nil {
|
||||
common.Logger(ctx).Infof(" \u2705 Success - %s", s)
|
||||
|
@ -128,16 +126,10 @@ func (rc *RunContext) runContainer(containerSpec *model.ContainerSpec) common.Ex
|
|||
entrypoint = strings.Fields(containerSpec.Entrypoint)
|
||||
}
|
||||
|
||||
var logWriter io.Writer
|
||||
logger := common.Logger(ctx)
|
||||
if entry, ok := logger.(*log.Entry); ok {
|
||||
logWriter = entry.Writer()
|
||||
} else if lgr, ok := logger.(*log.Logger); ok {
|
||||
logWriter = lgr.Writer()
|
||||
} else {
|
||||
logger.Errorf("Unable to get writer from logger (type=%T)", logger)
|
||||
}
|
||||
logWriter = os.Stdout
|
||||
rawLogger := common.Logger(ctx).WithField("raw_output", true)
|
||||
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) {
|
||||
rawLogger.Debugf(s)
|
||||
})
|
||||
|
||||
return container.NewDockerRunExecutor(container.NewDockerRunExecutorInput{
|
||||
Cmd: cmd,
|
||||
|
|
|
@ -37,6 +37,8 @@ func TestRunEvent(t *testing.T) {
|
|||
eventName string
|
||||
errorMessage string
|
||||
}{
|
||||
{"runs-on", "push", ""},
|
||||
/*
|
||||
{"basic", "push", ""},
|
||||
{"fail", "push", "exit with `FAILURE`: 1"},
|
||||
{"runs-on", "push", ""},
|
||||
|
@ -46,6 +48,7 @@ func TestRunEvent(t *testing.T) {
|
|||
{"remote-action-js", "push", ""},
|
||||
{"local-action-docker-url", "push", ""},
|
||||
{"local-action-dockerfile", "push", ""},
|
||||
*/
|
||||
}
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
|
@ -57,6 +60,7 @@ func TestRunEvent(t *testing.T) {
|
|||
runnerConfig := &Config{
|
||||
Workdir: "testdata",
|
||||
EventName: table.eventName,
|
||||
ReuseContainers: true,
|
||||
}
|
||||
runner, err := New(runnerConfig)
|
||||
assert.NilError(t, err, table.workflowPath)
|
||||
|
|
33
pkg/runner/testdata/commands/push.yml
vendored
Normal file
33
pkg/runner/testdata/commands/push.yml
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: basic
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: TEST set-env
|
||||
run: echo "::set-env name=foo::bar"
|
||||
- name: TEST set-env (cont.)
|
||||
run: echo $foo | grep bar
|
||||
|
||||
- name: TEST set-output
|
||||
run: echo "::set-output name=zoo::zar"
|
||||
|
||||
#- run: echo "::add-path::/zip"
|
||||
#- run: echo $PATH | grep /zip
|
||||
|
||||
- name: TEST debug, warning, error
|
||||
run: |
|
||||
echo "::debug file=app.js,line=100,col=20::Hello debug!"
|
||||
echo "::warning file=app.js,line=100,col=20::Hello warning!"
|
||||
echo "::error file=app.js,line=100,col=30::Hello error!"
|
||||
|
||||
- name: TEST stop-commands
|
||||
run: |
|
||||
echo "::stop-commands::my-end-token"
|
||||
echo "::set-env name=foo::baz"
|
||||
echo $foo | grep bar
|
||||
echo "::my-end-token::"
|
||||
echo "::set-env name=foo::baz"
|
||||
- name: TEST stop-commands (cont.)
|
||||
run: echo $foo | grep baz
|
2
pkg/runner/testdata/parallel/push.yml
vendored
2
pkg/runner/testdata/parallel/push.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
args: echo ${GITHUB_REF} | grep nektos/act
|
||||
- uses: ./actions/docker-url
|
||||
with:
|
||||
args: npm install angular-cli
|
||||
args: npm install -g qs
|
||||
test2:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
|
|
2
pkg/runner/testdata/runs-on/push.yml
vendored
2
pkg/runner/testdata/runs-on/push.yml
vendored
|
@ -5,4 +5,6 @@ jobs:
|
|||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: env
|
||||
- run: echo ${GITHUB_ACTOR}
|
||||
- run: echo ${GITHUB_ACTOR} | grep nektos/act
|
||||
|
|
Loading…
Reference in a new issue