add support for 'reuse' mode to allow act to be used for a fast local task runner

This commit is contained in:
Casey Lee 2019-01-17 00:45:37 -08:00
parent 317a305f51
commit 8793c8a6a4
No known key found for this signature in database
GPG key ID: 4CC378651BF9C168
11 changed files with 107 additions and 72 deletions

View file

@ -1,8 +1,4 @@
FROM golang:1.11.4-stretch
RUN go get -u honnef.co/go/tools/cmd/staticcheck
RUN go get -u golang.org/x/lint/golint
RUN go get -u github.com/fzipp/gocyclo
FROM golangci/golangci-lint:v1.12.5
COPY "entrypoint.sh" "/entrypoint.sh"
RUN chmod +x /entrypoint.sh

View file

@ -1,10 +1,4 @@
#!/bin/sh
#GOPATH=/go
#PATH=${GOPATH}/bin:/usr/local/go/bin:${PATH}
go vet ./...
golint -set_exit_status ./...
staticcheck ./...
gocyclo -over 10 .
golangci-lint run
go test -cover ./...

View file

@ -19,3 +19,9 @@ action "check" {
args = "release"
secrets = ["GITHUB_TOKEN"]
}
action "build" {
uses = "docker://goreleaser/goreleaser:v0.97"
args = "--snapshot --rm-dist"
secrets = ["SNAPSHOT_VERSION"]
}

View file

@ -15,7 +15,6 @@ linters:
- gosec
- unconvert
- dupl
- maligned
- nakedret
- prealloc
- scopelint

View file

@ -12,20 +12,19 @@ endif
IS_SNAPSHOT = $(if $(findstring -, $(VERSION)),true,false)
TAG_VERSION = v$(VERSION)
ACT ?= go run main.go
default: check
deps:
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $$(go env GOPATH)/bin v1.12.5
check:
golangci-lint run
go test -cover ./...
$(ACT) -ra check
build: deps check
@GO111MODULE=off go get github.com/goreleaser/goreleaser
build: check
$(eval export SNAPSHOT_VERSION=$(VERSION))
@goreleaser --snapshot --rm-dist
$(ACT) -ra build
release:
$(ACT) -ra release
install: build
@cp dist/$(shell go env GOOS)_$(shell go env GOARCH)/act /usr/local/bin/act
@ -36,7 +35,6 @@ installer:
@GO111MODULE=off go get github.com/goreleaser/godownloader
godownloader -r nektos/act -o install.sh
promote:
@echo "VERSION:$(VERSION) IS_SNAPSHOT:$(IS_SNAPSHOT) LATEST_VERSION:$(LATEST_VERSION)"
ifeq (false,$(IS_SNAPSHOT))

View file

@ -41,6 +41,9 @@ act -a test
# Run in dry-run mode:
act -n
# Run in reuse mode to save state:
act -r
```
# Support

View file

@ -42,6 +42,7 @@ type RunnerConfig struct {
WorkflowPath string // path to load main.workflow file, relative to WorkingDir
EventName string // name of event to run
EventPath string // path to JSON file to use for event.json in containers, relative to WorkingDir
ReuseContainers bool // reuse containers to maintain state
}
type environmentApplier interface {

View file

@ -5,7 +5,6 @@ import (
"bytes"
"fmt"
"io"
"math/rand"
"os"
"path/filepath"
"regexp"
@ -78,11 +77,6 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
if err != nil {
return common.NewErrorExecutor(err)
}
randSuffix := randString(6)
containerName := regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-")
if len(containerName)+len(randSuffix)+1 > 30 {
containerName = containerName[:(30 - (len(randSuffix) + 1))]
}
envList := make([]string, 0)
for k, v := range env {
@ -95,13 +89,14 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
Image: image,
WorkingDir: "/github/workspace",
Env: envList,
Name: fmt.Sprintf("%s-%s", containerName, randSuffix),
Name: runner.createContainerName(actionName),
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 common.NewPipelineExecutor(executors...)
@ -174,12 +169,18 @@ func (runner *runnerImpl) createGithubTarball() (io.Reader, error) {
}
const letterBytes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
func (runner *runnerImpl) createContainerName(actionName string) string {
containerName := regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-")
func randString(slen int) string {
b := make([]byte, slen)
for i := range b {
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))]
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)
}
return string(b)
func trimToLen(s string, l int) string {
if len(s) > l {
return s[:l]
}
return s
}

View file

@ -25,6 +25,7 @@ func Execute(ctx context.Context, version string) {
}
rootCmd.Flags().BoolP("list", "l", false, "list actions")
rootCmd.Flags().StringP("action", "a", "", "run action")
rootCmd.Flags().BoolVarP(&runnerConfig.ReuseContainers, "reuse", "r", false, "reuse action containers to maintain state")
rootCmd.Flags().StringVarP(&runnerConfig.EventPath, "event", "e", "", "path to event JSON file")
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().BoolVarP(&runnerConfig.Dryrun, "dryrun", "n", false, "dryrun mode")

View file

@ -25,6 +25,7 @@ type NewDockerRunExecutorInput struct {
Content map[string]io.Reader
Volumes []string
Name string
ReuseContainers bool
}
// NewDockerRunExecutor function to create a run executor for the container
@ -41,29 +42,44 @@ func NewDockerRunExecutor(input NewDockerRunExecutorInput) common.Executor {
return err
}
containerID, err := createContainer(input, cli)
// check if container exists
containerID, err := findContainer(input, cli, input.Name)
if err != nil {
return err
}
// if we have an old container and we aren't reusing, remove it!
if !input.ReuseContainers && containerID != "" {
input.Logger.Debugf("Found existing container for %s...removing", input.Name)
removeContainer(input, cli, containerID)
containerID = ""
}
// create a new container if we don't have one to reuse
if containerID == "" {
containerID, err = createContainer(input, cli)
if err != nil {
return err
}
}
// be sure to cleanup container if we aren't reusing
if !input.ReuseContainers {
defer removeContainer(input, cli, containerID)
err = copyContentToContainer(input, cli, containerID)
if err != nil {
return err
}
err = attachContainer(input, cli, containerID)
if err != nil {
return err
}
err = startContainer(input, cli, containerID)
if err != nil {
return err
}
executor := common.NewPipelineExecutor(
func() error {
return copyContentToContainer(input, cli, containerID)
}, func() error {
return attachContainer(input, cli, containerID)
}, func() error {
return startContainer(input, cli, containerID)
}, func() error {
return waitContainer(input, cli, containerID)
},
)
return executor()
}
}
@ -99,6 +115,25 @@ func createContainer(input NewDockerRunExecutorInput, cli *client.Client) (strin
return resp.ID, nil
}
func findContainer(input NewDockerRunExecutorInput, cli *client.Client, containerName string) (string, error) {
containers, err := cli.ContainerList(input.Ctx, types.ContainerListOptions{
All: true,
})
if err != nil {
return "", err
}
for _, container := range containers {
for _, name := range container.Names {
if name[1:] == containerName {
return container.ID, nil
}
}
}
return "", nil
}
func removeContainer(input NewDockerRunExecutorInput, cli *client.Client, containerID string) {
err := cli.ContainerRemove(context.Background(), containerID, types.ContainerRemoveOptions{
RemoveVolumes: true,

1
go.sum
View file

@ -19,6 +19,7 @@ github.com/docker/distribution v2.7.0+incompatible h1:neUDAlf3wX6Ml4HdqTrbcOHXtf
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
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/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=