initial load of yaml working

This commit is contained in:
Casey Lee
2020-02-04 16:38:41 -08:00
parent 500e9677f3
commit 8c49ba0cec
30 changed files with 560 additions and 415 deletions

5
pkg/runner/api.go Normal file
View File

@@ -0,0 +1,5 @@
package runner
type environmentApplier interface {
applyEnvironment(map[string]string)
}

88
pkg/runner/runner.go Normal file
View File

@@ -0,0 +1,88 @@
package runner
import (
"io"
"io/ioutil"
"os"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus"
)
// Runner provides capabilities to run GitHub actions
type Runner interface {
PlanRunner
io.Closer
}
// PlanRunner to run a specific actions
type PlanRunner interface {
RunPlan(plan *model.Plan) error
}
// Config contains the config for a new runner
type Config struct {
Dryrun bool // don't start any of the containers
EventName string // name of event to run
EventPath string // path to JSON file to use for event.json in containers
ReuseContainers bool // reuse containers to maintain state
ForcePull bool // force pulling of the image, if already present
}
type runnerImpl struct {
config *Config
tempDir string
eventJSON string
}
// NewRunner Creates a new Runner
func NewRunner(runnerConfig *Config) (Runner, error) {
runner := &runnerImpl{
config: runnerConfig,
}
init := common.NewPipelineExecutor(
runner.setupTempDir,
runner.setupEvent,
)
return runner, init()
}
func (runner *runnerImpl) setupTempDir() error {
var err error
runner.tempDir, err = ioutil.TempDir("", "act-")
return err
}
func (runner *runnerImpl) setupEvent() error {
runner.eventJSON = "{}"
if runner.config.EventPath != "" {
log.Debugf("Reading event.json from %s", runner.config.EventPath)
eventJSONBytes, err := ioutil.ReadFile(runner.config.EventPath)
if err != nil {
return err
}
runner.eventJSON = string(eventJSONBytes)
}
return nil
}
func (runner *runnerImpl) RunPlan(plan *model.Plan) error {
pipeline := make([]common.Executor, 0)
for _, stage := range plan.Stages {
stageExecutor := make([]common.Executor, 0)
for _, run := range stage.Runs {
stageExecutor = append(stageExecutor, runner.newRunExecutor(run))
}
pipeline = append(pipeline, common.NewParallelExecutor(stageExecutor...))
}
executor := common.NewPipelineExecutor(pipeline...)
return executor()
}
func (runner *runnerImpl) Close() error {
return os.RemoveAll(runner.tempDir)
}

254
pkg/runner/runner_exec.go Normal file
View File

@@ -0,0 +1,254 @@
package runner
import (
"archive/tar"
"bytes"
"fmt"
"io"
"path/filepath"
"regexp"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus"
)
func (runner *runnerImpl) newRunExecutor(run *model.Run) common.Executor {
action := runner.workflowConfig.GetAction(actionName)
if action == nil {
return common.NewErrorExecutor(fmt.Errorf("Unable to find action named '%s'", actionName))
}
executors := make([]common.Executor, 0)
image, err := runner.addImageExecutor(action, &executors)
if err != nil {
return common.NewErrorExecutor(err)
}
err = runner.addRunExecutor(action, image, &executors)
if err != nil {
return common.NewErrorExecutor(err)
}
return common.NewPipelineExecutor(executors...)
}
/*
func (runner *runnerImpl) addImageExecutor(action *Action, executors *[]common.Executor) (string, error) {
var image string
logger := newActionLogger(action.Identifier, runner.config.Dryrun)
log.Debugf("Using '%s' for action '%s'", action.Uses, action.Identifier)
in := container.DockerExecutorInput{
Ctx: runner.config.Ctx,
Logger: logger,
Dryrun: runner.config.Dryrun,
}
switch uses := action.Uses.(type) {
case *model.UsesDockerImage:
image = uses.Image
pull := runner.config.ForcePull
if !pull {
imageExists, err := container.ImageExistsLocally(runner.config.Ctx, image)
log.Debugf("Image exists? %v", imageExists)
if err != nil {
return "", fmt.Errorf("unable to determine if image already exists for image %q", image)
}
if !imageExists {
pull = true
}
}
if pull {
*executors = append(*executors, container.NewDockerPullExecutor(container.NewDockerPullExecutorInput{
DockerExecutorInput: in,
Image: image,
}))
}
case *model.UsesPath:
contextDir := filepath.Join(runner.config.WorkingDir, uses.String())
sha, _, err := common.FindGitRevision(contextDir)
if err != nil {
log.Warnf("Unable to determine git revision: %v", err)
sha = "latest"
}
image = fmt.Sprintf("%s:%s", filepath.Base(contextDir), sha)
*executors = append(*executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
DockerExecutorInput: in,
ContextDir: contextDir,
ImageTag: image,
}))
case *model.UsesRepository:
image = fmt.Sprintf("%s:%s", filepath.Base(uses.Repository), uses.Ref)
cloneURL := fmt.Sprintf("https://github.com/%s", uses.Repository)
cloneDir := filepath.Join(os.TempDir(), "act", action.Uses.String())
*executors = append(*executors, common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
URL: cloneURL,
Ref: uses.Ref,
Dir: cloneDir,
Logger: logger,
Dryrun: runner.config.Dryrun,
}))
contextDir := filepath.Join(cloneDir, uses.Path)
*executors = append(*executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
DockerExecutorInput: in,
ContextDir: contextDir,
ImageTag: image,
}))
default:
return "", fmt.Errorf("unable to determine executor type for image '%s'", action.Uses)
}
return image, nil
}
*/
func (runner *runnerImpl) addRunExecutor(action *Action, image string, executors *[]common.Executor) error {
logger := newActionLogger(action.Identifier, runner.config.Dryrun)
log.Debugf("Using '%s' for action '%s'", action.Uses, action.Identifier)
in := container.DockerExecutorInput{
Ctx: runner.config.Ctx,
Logger: logger,
Dryrun: runner.config.Dryrun,
}
env := make(map[string]string)
for _, applier := range []environmentApplier{newActionEnvironmentApplier(action), runner} {
applier.applyEnvironment(env)
}
env["GITHUB_ACTION"] = action.Identifier
ghReader, err := runner.createGithubTarball()
if err != nil {
return err
}
envList := make([]string, 0)
for k, v := range env {
envList = append(envList, fmt.Sprintf("%s=%s", k, v))
}
var cmd, entrypoint []string
if action.Args != nil {
cmd = action.Args.Split()
}
if action.Runs != nil {
entrypoint = action.Runs.Split()
}
*executors = append(*executors, container.NewDockerRunExecutor(container.NewDockerRunExecutorInput{
DockerExecutorInput: in,
Cmd: cmd,
Entrypoint: entrypoint,
Image: image,
WorkingDir: "/github/workspace",
Env: envList,
Name: runner.createContainerName(action.Identifier),
Binds: []string{
fmt.Sprintf("%s:%s", runner.config.WorkingDir, "/github/workspace"),
fmt.Sprintf("%s:%s", runner.tempDir, "/github/home"),
fmt.Sprintf("%s:%s", "/var/run/docker.sock", "/var/run/docker.sock"),
},
Content: map[string]io.Reader{"/github": ghReader},
ReuseContainers: runner.config.ReuseContainers,
}))
return nil
}
func (runner *runnerImpl) applyEnvironment(env map[string]string) {
repoPath := runner.config.WorkingDir
workflows := runner.workflowConfig.GetWorkflows(runner.config.EventName)
if len(workflows) == 0 {
return
}
workflowName := workflows[0].Identifier
env["HOME"] = "/github/home"
env["GITHUB_ACTOR"] = "nektos/act"
env["GITHUB_EVENT_PATH"] = "/github/workflow/event.json"
env["GITHUB_WORKSPACE"] = "/github/workspace"
env["GITHUB_WORKFLOW"] = workflowName
env["GITHUB_EVENT_NAME"] = runner.config.EventName
_, rev, err := common.FindGitRevision(repoPath)
if err != nil {
log.Warningf("unable to get git revision: %v", err)
} else {
env["GITHUB_SHA"] = rev
}
repo, err := common.FindGithubRepo(repoPath)
if err != nil {
log.Warningf("unable to get git repo: %v", err)
} else {
env["GITHUB_REPOSITORY"] = repo
}
ref, err := common.FindGitRef(repoPath)
if err != nil {
log.Warningf("unable to get git ref: %v", err)
} else {
log.Infof("using github ref: %s", ref)
env["GITHUB_REF"] = ref
}
}
func (runner *runnerImpl) createGithubTarball() (io.Reader, error) {
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
var files = []struct {
Name string
Mode int64
Body string
}{
{"workflow/event.json", 0644, runner.eventJSON},
}
for _, file := range files {
log.Debugf("Writing entry to tarball %s len:%d", file.Name, len(runner.eventJSON))
hdr := &tar.Header{
Name: file.Name,
Mode: file.Mode,
Size: int64(len(runner.eventJSON)),
}
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
if _, err := tw.Write([]byte(runner.eventJSON)); err != nil {
return nil, err
}
}
if err := tw.Close(); err != nil {
return nil, err
}
return &buf, nil
}
func (runner *runnerImpl) createContainerName(actionName string) string {
containerName := regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-")
prefix := fmt.Sprintf("%s-", trimToLen(filepath.Base(runner.config.WorkingDir), 10))
suffix := ""
containerName = trimToLen(containerName, 30-(len(prefix)+len(suffix)))
return fmt.Sprintf("%s%s%s", prefix, containerName, suffix)
}
func trimToLen(s string, l int) string {
if len(s) > l {
return s[:l]
}
return s
}

71
pkg/runner/runner_test.go Normal file
View File

@@ -0,0 +1,71 @@
package runner
import (
"context"
"testing"
log "github.com/sirupsen/logrus"
"gotest.tools/assert"
)
func TestGraphEvent(t *testing.T) {
runnerConfig := &RunnerConfig{
Ctx: context.Background(),
WorkflowPath: "multi.workflow",
WorkingDir: "testdata",
EventName: "push",
}
runner, err := NewRunner(runnerConfig)
assert.NilError(t, err)
graph, err := runner.GraphEvent("push")
assert.NilError(t, err)
assert.DeepEqual(t, graph, [][]string{{"build"}})
graph, err = runner.GraphEvent("release")
assert.NilError(t, err)
assert.DeepEqual(t, graph, [][]string{{"deploy"}})
}
func TestRunEvent(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
tables := []struct {
workflowPath string
eventName string
errorMessage string
}{
{"basic.workflow", "push", ""},
{"pipe.workflow", "push", ""},
{"fail.workflow", "push", "exit with `FAILURE`: 1"},
{"buildfail.workflow", "push", "COPY failed"},
{"regex.workflow", "push", "exit with `NEUTRAL`: 78"},
{"gitref.workflow", "push", ""},
{"env.workflow", "push", ""},
{"detect_event.workflow", "", ""},
}
log.SetLevel(log.DebugLevel)
for _, table := range tables {
table := table
t.Run(table.workflowPath, func(t *testing.T) {
runnerConfig := &RunnerConfig{
Ctx: context.Background(),
WorkflowPath: table.workflowPath,
WorkingDir: "testdata",
EventName: table.eventName,
}
runner, err := NewRunner(runnerConfig)
assert.NilError(t, err, table.workflowPath)
err = runner.RunEvent()
if table.errorMessage == "" {
assert.NilError(t, err, table.workflowPath)
} else {
assert.ErrorContains(t, err, table.errorMessage)
}
})
}
}