unit tests

Signed-off-by: Casey Lee <[email protected]>
This commit is contained in:
Casey Lee committed 2020-02-20 21:05:31 -05:00
1 parent be75ee20b1
commit 0582306861
466 files changed
+94682 -62525

No files matched your search

-14
View File
@@ -1,14 +0,0 @@
FROM golangci/golangci-lint:v1.12.5
RUN apt-get install git
LABEL "com.github.actions.name"="Check"
LABEL "com.github.actions.description"="Run static analysis and unit tests"
LABEL "com.github.actions.icon"="check-circle"
LABEL "com.github.actions.color"="green"
COPY "entrypoint.sh" "/entrypoint.sh"
RUN chmod +x /entrypoint.sh
ENV GOFLAGS -mod=vendor
ENTRYPOINT ["/entrypoint.sh"]
-12
View File
@@ -1,12 +0,0 @@
FROM golangci/golangci-lint:v1.12.5
LABEL "com.github.actions.name"="Check"
LABEL "com.github.actions.description"="Run integration tests"
LABEL "com.github.actions.icon"="check-circle"
LABEL "com.github.actions.color"="green"
COPY "entrypoint.sh" "/entrypoint.sh"
RUN chmod +x /entrypoint.sh
ENV GOFLAGS -mod=vendor
ENTRYPOINT ["/entrypoint.sh"]
@@ -1,3 +0,0 @@
#!/bin/sh
set -e
go test -cover ./actions
-48
View File
@@ -1,48 +0,0 @@
workflow "check-and-release" {
on = "push"
resolves = ["release"]
}
action "check" {
uses = "./.github/actions/check"
}
action "release-filter" {
needs = ["check"]
uses = "actions/bin/filter@master"
args = "tag 'v*'"
}
# only release on `v*` tags
action "release" {
needs = ["release-filter"]
uses = "docker://goreleaser/goreleaser:v0.98"
args = "release"
secrets = ["GITHUB_TOKEN"]
}
# local action for `make build`
action "build" {
uses = "docker://goreleaser/goreleaser:v0.98"
args = "--snapshot --rm-dist"
secrets = ["SNAPSHOT_VERSION"]
}
# local action for `make vendor`
action "vendor" {
uses = "docker://golang:1.11.4"
args = "go mod vendor"
}
action "integration-tests" {
needs = ["check"]
uses = "./.github/actions/integration"
}
# local release
action "local-release" {
needs = ["integration-tests"]
uses = "docker://goreleaser/goreleaser:v0.98"
args = "release"
secrets = ["GITHUB_TOKEN"]
}
-31
View File
@@ -1,31 +0,0 @@
name: basic
on: push
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
build:
runs-on: ubuntu-latest
container:
image: node:10.16-jessie
env:
NODE_ENV: development
steps:
- run: env
test:
runs-on: ubuntu-latest
needs: [build]
steps:
- run: cp $GITHUB_EVENT_PATH $HOME/foo.json
- run: ls $HOME
- run: cat $HOME/foo.json
- uses: docker://alpine:3.8
with:
args: uname -a
local-action:
runs-on: ubuntu-latest
steps:
- uses: ./.github/workflows/docker-url
+9
View File
@@ -0,0 +1,9 @@
FROM golangci/golangci-lint:v1.23.6
RUN apt-get install git
COPY "entrypoint.sh" "/entrypoint.sh"
RUN chmod +x /entrypoint.sh
ENV GOFLAGS -mod=vendor
ENTRYPOINT ["/entrypoint.sh"]
+8
View File
@@ -0,0 +1,8 @@
name: Check
description: Run static analysis and unit tests
branding:
icon: check-circle
color: green
runs:
using: 'docker'
image: 'Dockerfile'
@@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
set -e set -e
golangci-lint run golangci-lint run
go test -cover -short ./... go test -cover -short ./...
+7
View File
@@ -0,0 +1,7 @@
FROM golangci/golangci-lint:v1.23.6
COPY "entrypoint.sh" "/entrypoint.sh"
RUN chmod +x /entrypoint.sh
ENV GOFLAGS -mod=vendor
ENTRYPOINT ["/entrypoint.sh"]
+8
View File
@@ -0,0 +1,8 @@
name: Check
description: Run integration tests
branding:
icon: check-circle
color: green
runs:
using: 'docker'
image: 'Dockerfile'
@@ -0,0 +1,3 @@
#!/bin/sh
set -e
go test -cover ./runner
+9
View File
@@ -0,0 +1,9 @@
name: push
on: push
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: ./.github/workflows/check
- uses: ./.github/workflows/integration
+20
View File
@@ -0,0 +1,20 @@
name: tag
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: ./.github/workflows/check
- uses: ./.github/workflows/integration
- uses: goreleaser/goreleaser-action@v1
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v1
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+2 -2
View File
@@ -21,7 +21,7 @@ test:
go test -cover -short ./... go test -cover -short ./...
check: check:
$(ACT) -ra check $(ACT) -rj ci
build: check build: check
$(eval export SNAPSHOT_VERSION=$(VERSION)) $(eval export SNAPSHOT_VERSION=$(VERSION))
@@ -56,4 +56,4 @@ endif
vendor: vendor:
go mod vendor go mod vendor
.PHONY: vendor .PHONY: vendor
-48
View File
@@ -1,48 +0,0 @@
package actions
import (
"fmt"
"log"
"os"
"github.com/howeyc/gopass"
)
var secretCache map[string]string
type actionEnvironmentApplier struct {
*Action
}
type Action struct{}
func newActionEnvironmentApplier(action *Action) environmentApplier {
return &actionEnvironmentApplier{action}
}
func (action *actionEnvironmentApplier) applyEnvironment(env map[string]string) {
for envKey, envValue := range action.Env {
env[envKey] = envValue
}
for _, secret := range action.Secrets {
if secretVal, ok := os.LookupEnv(secret); ok {
env[secret] = secretVal
} else {
if secretCache == nil {
secretCache = make(map[string]string)
}
if _, ok := secretCache[secret]; !ok {
fmt.Printf("Provide value for '%s': ", secret)
val, err := gopass.GetPasswdMasked()
if err != nil {
log.Fatal("abort")
}
secretCache[secret] = string(val)
}
env[secret] = secretCache[secret]
}
}
}
-255
View File
@@ -1,255 +0,0 @@
package runner
import (
"archive/tar"
"bytes"
"fmt"
"io"
"path/filepath"
"regexp"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
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 = []string{
"/bin/sh",
"-c",
action.Args,
}
}
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
}
-1
View File
@@ -1 +0,0 @@
FROM alpine:3.8
-21
View File
@@ -1,21 +0,0 @@
workflow "build-and-deploy" {
on = "push"
resolves = ["deploy"]
}
action "build" {
uses = "./action1"
args = "echo 'build'"
}
action "test" {
uses = "docker://ubuntu:18.04"
args = "env"
needs = ["build"]
}
action "deploy" {
uses = "./action2"
runs = ["/bin/sh", "-c", "cat $GITHUB_EVENT_PATH"]
needs = ["test"]
}
-2
View File
@@ -1,2 +0,0 @@
FROM alpine:3.8
COPY foobar /foo/bar
-8
View File
@@ -1,8 +0,0 @@
workflow "test" {
on = "push"
resolves = ["test-action"]
}
action "test-action" {
uses = "./buildfail-action"
}
-9
View File
@@ -1,9 +0,0 @@
workflow "detect-event" {
on = "pull_request"
resolves = ["build"]
}
action "build" {
uses = "./action1"
args = "echo 'build'"
}
-17
View File
@@ -1,17 +0,0 @@
workflow "test" {
on = "push"
resolves = [
"test-action-repo",
"test-action-ref",
]
}
action "test-action-repo" {
uses = "docker://alpine:3.9"
runs = ["sh", "-c", "echo $GITHUB_REPOSITORY | grep '^nektos/act$'"]
}
action "test-action-ref" {
uses = "docker://alpine:3.9"
runs = ["sh", "-c", "echo $GITHUB_REF | grep '^refs/'"]
}
-13
View File
@@ -1,13 +0,0 @@
workflow "test" {
on = "push"
resolves = ["test-action"]
}
action "test-action" {
uses = "docker://alpine:3.9"
runs = ["sh", "-c", "echo $IN | grep $OUT"]
env = {
IN = "foo"
OUT = "bar"
}
}
-14
View File
@@ -1,14 +0,0 @@
workflow "New workflow" {
on = "push"
resolves = ["branch-ref","commit-ref"]
}
action "branch-ref" {
uses = "actions/docker/cli@master"
args = "version"
}
action "commit-ref" {
uses = "actions/docker/cli@c08a5fc9e0286844156fefff2c141072048141f6"
args = "version"
}
-19
View File
@@ -1,19 +0,0 @@
workflow "buildwf" {
on = "push"
resolves = ["build"]
}
action "build" {
uses = "./action1"
args = "echo 'build'"
}
workflow "deploywf" {
on = "release"
resolves = ["deploy"]
}
action "deploy" {
uses = "./action2"
runs = ["/bin/sh", "-c", "cat $GITHUB_EVENT_PATH"]
}
-13
View File
@@ -1,13 +0,0 @@
workflow "test" {
on = "push"
resolves = ["test-action"]
}
action "test-action" {
uses = "docker://alpine:3.9"
runs = ["sh", "-c", "echo $IN | grep $OUT"]
env = {
IN = "foo"
OUT = "foo"
}
}
-9
View File
@@ -1,9 +0,0 @@
workflow "New workflow" {
on = "push"
resolves = ["filter-version-before-deploy"]
}
action "filter-version-before-deploy" {
uses = "actions/bin/filter@master"
args = "tag z?[0-9]+\\.[0-9]+\\.[0-9]+"
}
-1
View File
@@ -21,7 +21,6 @@ github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BU
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb h1:PyjxRdW1mqCmSoxy/6uP01P7CGbsD+woX+oOWbaUPwQ= github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb h1:PyjxRdW1mqCmSoxy/6uP01P7CGbsD+woX+oOWbaUPwQ=
github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY= github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
github.com/docker/engine v1.13.1 h1:Cks33UT9YBW5Xyc3MtGDq2IPgqfJtJ+qkFaxc2b0Euc=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
+26 -15
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@@ -182,20 +183,19 @@ func findGitDirectory(fromFile string) (string, error) {
// NewGitCloneExecutorInput the input for the NewGitCloneExecutor // NewGitCloneExecutorInput the input for the NewGitCloneExecutor
type NewGitCloneExecutorInput struct { type NewGitCloneExecutorInput struct {
URL string URL string
Ref string Ref string
Dir string Dir string
Logger *log.Entry
Dryrun bool
} }
// NewGitCloneExecutor creates an executor to clone git repos // NewGitCloneExecutor creates an executor to clone git repos
func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor { func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
input.Logger.Infof("git clone '%s' # ref=%s", input.URL, input.Ref) logger := Logger(ctx)
input.Logger.Debugf(" cloning %s to %s", input.URL, input.Dir) logger.Infof("git clone '%s' # ref=%s", input.URL, input.Ref)
logger.Debugf(" cloning %s to %s", input.URL, input.Dir)
if input.Dryrun { if Dryrun(ctx) {
return nil return nil
} }
@@ -206,15 +206,26 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
r, err := git.PlainOpen(input.Dir) r, err := git.PlainOpen(input.Dir)
if err != nil { if err != nil {
var progressWriter io.Writer
if entry, ok := logger.(*log.Entry); ok {
progressWriter = entry.WriterLevel(log.DebugLevel)
} else if lgr, ok := logger.(*log.Logger); ok {
progressWriter = lgr.WriterLevel(log.DebugLevel)
} else {
log.Errorf("Unable to get writer from logger (type=%T)", logger)
progressWriter = os.Stdout
}
r, err = git.PlainClone(input.Dir, false, &git.CloneOptions{ r, err = git.PlainClone(input.Dir, false, &git.CloneOptions{
URL: input.URL, URL: input.URL,
Progress: input.Logger.WriterLevel(log.DebugLevel), Progress: progressWriter,
//ReferenceName: refName, //ReferenceName: refName,
}) })
if err != nil { if err != nil {
input.Logger.Errorf("Unable to clone %v %s: %v", input.URL, refName, err) logger.Errorf("Unable to clone %v %s: %v", input.URL, refName, err)
return err return err
} }
os.Chmod(input.Dir, 0755)
} }
w, err := r.Worktree() w, err := r.Worktree()
@@ -227,13 +238,13 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
Force: true, Force: true,
}) })
if err != nil && err.Error() != "already up-to-date" { if err != nil && err.Error() != "already up-to-date" {
input.Logger.Errorf("Unable to pull %s: %v", refName, err) logger.Errorf("Unable to pull %s: %v", refName, err)
} }
input.Logger.Debugf("Cloned %s to %s", input.URL, input.Dir) logger.Debugf("Cloned %s to %s", input.URL, input.Dir)
hash, err := r.ResolveRevision(plumbing.Revision(input.Ref)) hash, err := r.ResolveRevision(plumbing.Revision(input.Ref))
if err != nil { if err != nil {
input.Logger.Errorf("Unable to resolve %s: %v", input.Ref, err) logger.Errorf("Unable to resolve %s: %v", input.Ref, err)
return err return err
} }
@@ -243,11 +254,11 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
Force: true, Force: true,
}) })
if err != nil { if err != nil {
input.Logger.Errorf("Unable to checkout %s: %v", refName, err) logger.Errorf("Unable to checkout %s: %v", refName, err)
return err return err
} }
input.Logger.Debugf("Checked out %s", input.Ref) logger.Debugf("Checked out %s", input.Ref)
return nil return nil
} }
} }
+2 -1
View File
@@ -40,7 +40,8 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
tags := []string{input.ImageTag} tags := []string{input.ImageTag}
options := types.ImageBuildOptions{ options := types.ImageBuildOptions{
Tags: tags, Tags: tags,
Remove: true,
} }
buildContext, err := createBuildContext(input.ContextDir, "Dockerfile") buildContext, err := createBuildContext(input.ContextDir, "Dockerfile")
+5 -4
View File
@@ -19,27 +19,28 @@ func TestImageExistsLocally(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
ctx := context.Background()
// to help make this test reliable and not flaky, we need to have // to help make this test reliable and not flaky, we need to have
// an image that will exist, and onew that won't exist // an image that will exist, and onew that won't exist
exists, err := ImageExistsLocally(context.TODO(), "library/alpine:this-random-tag-will-never-exist") exists, err := ImageExistsLocally(ctx, "library/alpine:this-random-tag-will-never-exist")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, false, exists) assert.Equal(t, false, exists)
// pull an image // pull an image
cli, err := client.NewClientWithOpts(client.FromEnv) cli, err := client.NewClientWithOpts(client.FromEnv)
assert.Nil(t, err) assert.Nil(t, err)
cli.NegotiateAPIVersion(context.TODO()) cli.NegotiateAPIVersion(context.Background())
// Chose alpine latest because it's so small // Chose alpine latest because it's so small
// maybe we should build an image instead so that tests aren't reliable on dockerhub // maybe we should build an image instead so that tests aren't reliable on dockerhub
reader, err := cli.ImagePull(context.TODO(), "alpine:latest", types.ImagePullOptions{}) reader, err := cli.ImagePull(ctx, "alpine:latest", types.ImagePullOptions{})
assert.Nil(t, err) assert.Nil(t, err)
defer reader.Close() defer reader.Close()
_, err = ioutil.ReadAll(reader) _, err = ioutil.ReadAll(reader)
assert.Nil(t, err) assert.Nil(t, err)
exists, err = ImageExistsLocally(context.TODO(), "alpine:latest") exists, err = ImageExistsLocally(ctx, "alpine:latest")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, true, exists) assert.Equal(t, true, exists)
} }
+3 -2
View File
@@ -39,6 +39,7 @@ type ContainerSpec struct {
Options string `yaml:"options"` Options string `yaml:"options"`
Entrypoint string Entrypoint string
Args string Args string
Name string
} }
// Step is the structure of one step in a job // Step is the structure of one step in a job
@@ -63,8 +64,8 @@ func (s *Step) GetEnv() map[string]string {
rtnEnv[k] = v rtnEnv[k] = v
} }
for k, v := range s.With { for k, v := range s.With {
envKey := fmt.Sprintf("INPUT_%s", strings.ToUpper(k)) envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(k), "_")
envKey = regexp.MustCompile("[^A-Z0-9]").ReplaceAllString(envKey, "_") envKey = fmt.Sprintf("INPUT_%s", strings.ToUpper(envKey))
rtnEnv[envKey] = v rtnEnv[envKey] = v
} }
return rtnEnv return rtnEnv
-5
View File
@@ -1,5 +0,0 @@
package runner
type environmentApplier interface {
applyEnvironment(map[string]string)
}
+18 -15
View File
@@ -45,10 +45,13 @@ func (rc *RunContext) Close(ctx context.Context) error {
// Executor returns a pipeline executor for all the steps in the job // Executor returns a pipeline executor for all the steps in the job
func (rc *RunContext) Executor() common.Executor { func (rc *RunContext) Executor() common.Executor {
rc.setupTempDir()
steps := make([]common.Executor, 0) steps := make([]common.Executor, 0)
steps = append(steps, rc.setupTempDir())
for _, step := range rc.Run.Job().Steps { for i, step := range rc.Run.Job().Steps {
if step.ID == "" {
step.ID = fmt.Sprintf("%d", i)
}
steps = append(steps, rc.newStepExecutor(step)) steps = append(steps, rc.newStepExecutor(step))
} }
return common.NewPipelineExecutor(steps...).Finally(rc.Close) return common.NewPipelineExecutor(steps...).Finally(rc.Close)
@@ -64,17 +67,16 @@ func mergeMaps(maps ...map[string]string) map[string]string {
return rtnMap return rtnMap
} }
func (rc *RunContext) setupTempDir() common.Executor { func (rc *RunContext) setupTempDir() error {
return func(ctx context.Context) error { var err error
var err error tempBase := ""
tempBase := "" if runtime.GOOS == "darwin" {
if runtime.GOOS == "darwin" { tempBase = "/tmp"
tempBase = "/tmp"
}
rc.Tempdir, err = ioutil.TempDir(tempBase, "act-")
log.Debugf("Setup tempdir %s", rc.Tempdir)
return err
} }
rc.Tempdir, err = ioutil.TempDir(tempBase, "act-")
os.Chmod(rc.Tempdir, 0755)
log.Debugf("Setup tempdir %s", rc.Tempdir)
return err
} }
func (rc *RunContext) pullImage(containerSpec *model.ContainerSpec) common.Executor { func (rc *RunContext) pullImage(containerSpec *model.ContainerSpec) common.Executor {
@@ -111,7 +113,7 @@ func (rc *RunContext) runContainer(containerSpec *model.ContainerSpec) common.Ex
Image: containerSpec.Image, Image: containerSpec.Image,
WorkingDir: "/github/workspace", WorkingDir: "/github/workspace",
Env: envList, Env: envList,
Name: rc.createContainerName(), Name: containerSpec.Name,
Binds: []string{ Binds: []string{
fmt.Sprintf("%s:%s", rc.Config.Workdir, "/github/workspace"), fmt.Sprintf("%s:%s", rc.Config.Workdir, "/github/workspace"),
fmt.Sprintf("%s:%s", rc.Tempdir, "/github/home"), fmt.Sprintf("%s:%s", rc.Tempdir, "/github/home"),
@@ -155,8 +157,9 @@ func (rc *RunContext) createGithubTarball() (io.Reader, error) {
} }
func (rc *RunContext) createContainerName() string { func (rc *RunContext) createContainerName(stepID string) string {
containerName := regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(rc.Run.String(), "-") containerName := fmt.Sprintf("%s-%s", stepID, rc.Tempdir)
containerName = regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(containerName, "-")
prefix := fmt.Sprintf("%s-", trimToLen(filepath.Base(rc.Config.Workdir), 10)) prefix := fmt.Sprintf("%s-", trimToLen(filepath.Base(rc.Config.Workdir), 10))
suffix := "" suffix := ""
+33 -26
View File
@@ -2,28 +2,29 @@ package runner
import ( import (
"context" "context"
"fmt"
"testing" "testing"
"github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gotest.tools/assert" "gotest.tools/assert"
) )
func TestGraphEvent(t *testing.T) { func TestGraphEvent(t *testing.T) {
runnerConfig := &Config{ planner, err := model.NewWorkflowPlanner("testdata/basic")
WorkflowPath: "multi.workflow",
WorkingDir: "testdata",
EventName: "push",
}
runner, err := NewRunner(runnerConfig)
assert.NilError(t, err) assert.NilError(t, err)
graph, err := runner.GraphEvent("push") plan := planner.PlanEvent("push")
assert.NilError(t, err) assert.NilError(t, err)
assert.DeepEqual(t, graph, [][]string{{"build"}}) assert.Equal(t, len(plan.Stages), 2, "stages")
assert.Equal(t, len(plan.Stages[0].Runs), 1, "stage0.runs")
assert.Equal(t, len(plan.Stages[1].Runs), 1, "stage1.runs")
assert.Equal(t, plan.Stages[0].Runs[0].JobID, "build", "jobid")
assert.Equal(t, plan.Stages[1].Runs[0].JobID, "test", "jobid")
graph, err = runner.GraphEvent("release") plan = planner.PlanEvent("release")
assert.NilError(t, err) assert.Equal(t, len(plan.Stages), 0, "stages")
assert.DeepEqual(t, graph, [][]string{{"deploy"}})
} }
func TestRunEvent(t *testing.T) { func TestRunEvent(t *testing.T) {
@@ -36,30 +37,36 @@ func TestRunEvent(t *testing.T) {
eventName string eventName string
errorMessage string errorMessage string
}{ }{
{"basic.workflow", "push", ""}, {"basic", "push", ""},
{"pipe.workflow", "push", ""}, {"fail", "push", "exit with `FAILURE`: 1"},
{"fail.workflow", "push", "exit with `FAILURE`: 1"}, {"runs-on", "push", ""},
{"buildfail.workflow", "push", "COPY failed"}, {"job-container", "push", ""},
{"regex.workflow", "push", "exit with `NEUTRAL`: 78"}, {"uses-docker-url", "push", ""},
{"gitref.workflow", "push", ""}, {"remote-action-docker", "push", ""},
{"env.workflow", "push", ""}, {"remote-action-js", "push", ""},
{"detect_event.workflow", "", ""}, {"local-action-docker-url", "push", ""},
{"local-action-dockerfile", "push", ""},
} }
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
ctx := context.Background()
for _, table := range tables { for _, table := range tables {
table := table table := table
t.Run(table.workflowPath, func(t *testing.T) { t.Run(table.workflowPath, func(t *testing.T) {
runnerConfig := &RunnerConfig{ runnerConfig := &Config{
Ctx: context.Background(), Workdir: "testdata",
WorkflowPath: table.workflowPath, EventName: table.eventName,
WorkingDir: "testdata",
EventName: table.eventName,
} }
runner, err := NewRunner(runnerConfig) runner, err := New(runnerConfig)
assert.NilError(t, err, table.workflowPath) assert.NilError(t, err, table.workflowPath)
err = runner.RunEvent() planner, err := model.NewWorkflowPlanner(fmt.Sprintf("testdata/%s", table.workflowPath))
assert.NilError(t, err, table.workflowPath)
plan := planner.PlanEvent(table.eventName)
err = runner.NewPlanExecutor(plan)(ctx)
if table.errorMessage == "" { if table.errorMessage == "" {
assert.NilError(t, err, table.workflowPath) assert.NilError(t, err, table.workflowPath)
} else { } else {
+54 -10
View File
@@ -10,6 +10,7 @@ import (
"strings" "strings"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -18,6 +19,7 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
job := rc.Run.Job() job := rc.Run.Job()
containerSpec := new(model.ContainerSpec) containerSpec := new(model.ContainerSpec)
containerSpec.Env = rc.StepEnv(step) containerSpec.Env = rc.StepEnv(step)
containerSpec.Name = rc.createContainerName(step.ID)
switch step.Type() { switch step.Type() {
case model.StepTypeRun: case model.StepTypeRun:
@@ -45,15 +47,26 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
) )
case model.StepTypeUsesActionLocal: case model.StepTypeUsesActionLocal:
containerSpec.Image = fmt.Sprintf("%s:%s", containerSpec.Name, "latest")
return common.NewPipelineExecutor( return common.NewPipelineExecutor(
rc.setupAction(containerSpec, filepath.Join(rc.Config.Workdir, step.Uses)), rc.setupAction(containerSpec, filepath.Join(rc.Config.Workdir, step.Uses)),
rc.pullImage(containerSpec), rc.pullImage(containerSpec),
rc.runContainer(containerSpec), rc.runContainer(containerSpec),
) )
case model.StepTypeUsesActionRemote: case model.StepTypeUsesActionRemote:
remoteAction := newRemoteAction(step.Uses)
cloneDir, err := ioutil.TempDir(rc.Tempdir, remoteAction.Repo)
if err != nil {
return common.NewErrorExecutor(err)
}
containerSpec.Image = fmt.Sprintf("%s:%s", remoteAction.Repo, remoteAction.Ref)
return common.NewPipelineExecutor( return common.NewPipelineExecutor(
rc.cloneAction(step.Uses), common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
rc.setupAction(containerSpec, step.Uses), URL: remoteAction.CloneURL(),
Ref: remoteAction.Ref,
Dir: cloneDir,
}),
rc.setupAction(containerSpec, filepath.Join(cloneDir, remoteAction.Path)),
rc.pullImage(containerSpec), rc.pullImage(containerSpec),
rc.runContainer(containerSpec), rc.runContainer(containerSpec),
) )
@@ -174,8 +187,8 @@ func (rc *RunContext) setupAction(containerSpec *model.ContainerSpec, actionDir
} }
for inputID, input := range action.Inputs { for inputID, input := range action.Inputs {
envKey := fmt.Sprintf("INPUT_%s", strings.ToUpper(inputID)) envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
envKey = regexp.MustCompile("[^A-Z0-9]").ReplaceAllString(envKey, "_") envKey = fmt.Sprintf("INPUT_%s", envKey)
if _, ok := containerSpec.Env[envKey]; !ok { if _, ok := containerSpec.Env[envKey]; !ok {
containerSpec.Env[envKey] = input.Default containerSpec.Env[envKey] = input.Default
} }
@@ -183,23 +196,54 @@ func (rc *RunContext) setupAction(containerSpec *model.ContainerSpec, actionDir
switch action.Runs.Using { switch action.Runs.Using {
case model.ActionRunsUsingNode12: case model.ActionRunsUsingNode12:
containerSpec.Image = "node:12" containerSpec.Image = "node:12-alpine"
containerSpec.Args = action.Runs.Main if strings.HasPrefix(actionDir, rc.Config.Workdir) {
containerSpec.Args = fmt.Sprintf("node /github/workspace/%s/%s", strings.TrimPrefix(actionDir, rc.Config.Workdir), action.Runs.Main)
} else if strings.HasPrefix(actionDir, rc.Tempdir) {
containerSpec.Args = fmt.Sprintf("node /github/home/%s/%s", strings.TrimPrefix(actionDir, rc.Tempdir), action.Runs.Main)
}
case model.ActionRunsUsingDocker: case model.ActionRunsUsingDocker:
if strings.HasPrefix(action.Runs.Image, "docker://") { if strings.HasPrefix(action.Runs.Image, "docker://") {
containerSpec.Image = strings.TrimPrefix(action.Runs.Image, "docker://") containerSpec.Image = strings.TrimPrefix(action.Runs.Image, "docker://")
containerSpec.Entrypoint = strings.Join(action.Runs.Entrypoint, " ") containerSpec.Entrypoint = strings.Join(action.Runs.Entrypoint, " ")
containerSpec.Args = strings.Join(action.Runs.Args, " ") containerSpec.Args = strings.Join(action.Runs.Args, " ")
} else { } else {
// TODO: docker build contextDir := filepath.Join(actionDir, action.Runs.Main)
return container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
ContextDir: contextDir,
ImageTag: containerSpec.Image,
})(ctx)
} }
} }
return nil return nil
} }
} }
func (rc *RunContext) cloneAction(action string) common.Executor { type remoteAction struct {
return func(ctx context.Context) error { Org string
return nil Repo string
Path string
Ref string
}
func (ra *remoteAction) CloneURL() string {
return fmt.Sprintf("https://github.com/%s/%s", ra.Org, ra.Repo)
}
func newRemoteAction(action string) *remoteAction {
r := regexp.MustCompile(`^([^/@]+)/([^/@]+)(/([^@]*))?(@(.*))?$`)
matches := r.FindStringSubmatch(action)
ra := new(remoteAction)
ra.Org = matches[1]
ra.Repo = matches[2]
ra.Path = ""
ra.Ref = "master"
if len(matches) >= 5 {
ra.Path = matches[4]
} }
if len(matches) >= 7 {
ra.Ref = matches[6]
}
return ra
} }
File renamed without changes.
+4
View File
@@ -0,0 +1,4 @@
name: 'action1'
runs:
using: 'docker'
image: 'Dockerfile'
+8
View File
@@ -0,0 +1,8 @@
# Container image that runs your code
FROM alpine:3.10
# Copies your code file from your action repository to the filesystem path `/` of the container
COPY entrypoint.sh /entrypoint.sh
# Code file to execute when the docker container starts up (`entrypoint.sh`)
ENTRYPOINT ["/entrypoint.sh"]
+15
View File
@@ -0,0 +1,15 @@
name: 'Hello World'
description: 'Greet someone and record the time'
inputs:
who-to-greet: # id of input
description: 'Who to greet'
required: true
default: 'World'
outputs:
time: # id of output
description: 'The time we greeted you'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.who-to-greet }}
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh -l
echo "Hello $1"
time=$(date)
echo ::set-output name=time::$time
@@ -8,9 +8,9 @@ inputs:
default: World default: World
runs: runs:
using: docker using: docker
image: docker://alpine:3.8 #image: docker://alpine:3.8
image: docker://node:12-alpine
env: env:
TEST: enabled TEST: enabled
args: args:
- echo - env
- ${INPUT_WHO_TO_GREET}
+17
View File
@@ -0,0 +1,17 @@
name: basic
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: ./actions/action1
with:
args: echo 'build'
test:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: docker://ubuntu:18.04
with:
args: echo ${GITHUB_REF} | grep nektos/act
+12
View File
@@ -0,0 +1,12 @@
name: fail
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:10.16-jessie
env:
TEST_ENV: test-value
steps:
- run: echo ${TEST_ENV} | grep bazooka
+12
View File
@@ -0,0 +1,12 @@
name: job-container
on: push
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:10.16-jessie
env:
TEST_ENV: test-value
steps:
- run: echo ${TEST_ENV} | grep test-value
+8
View File
@@ -0,0 +1,8 @@
name: local-action-docker-url
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: ./actions/docker-url
+10
View File
@@ -0,0 +1,10 @@
name: local-action-dockerfile
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: ./actions/docker-local
with:
who-to-greet: 'Mona the Octocat'
+10
View File
@@ -0,0 +1,10 @@
name: remote-action-docker
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/hello-world-docker-action@master
with:
who-to-greet: 'Mona the Octocat'
+10
View File
@@ -0,0 +1,10 @@
name: remote-action-js
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/hello-world-javascript-action@master
with:
who-to-greet: 'Mona the Octocat'
+8
View File
@@ -0,0 +1,8 @@
name: runs-on
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo ${GITHUB_ACTOR} | grep nektos/act
Loaded 50 of 466 files, more files were not shown because too many files have changed in this diff. Show more