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 FROM golangci/golangci-lint:v1.12.5
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
COPY "entrypoint.sh" "/entrypoint.sh" COPY "entrypoint.sh" "/entrypoint.sh"
RUN chmod +x /entrypoint.sh RUN chmod +x /entrypoint.sh

View file

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

14
.github/main.workflow vendored
View file

@ -7,15 +7,21 @@ action "check" {
uses = "./.github/actions/check" uses = "./.github/actions/check"
} }
action "branch-filter" { action "branch-filter" {
needs = ["check"] needs = ["check"]
uses = "actions/bin/filter@master" uses = "actions/bin/filter@master"
args = "tag v*" args = "tag v*"
} }
action "release" { action "release" {
needs = ["branch-filter"] needs = ["branch-filter"]
uses = "docker://goreleaser/goreleaser:v0.97" uses = "docker://goreleaser/goreleaser:v0.97"
args = "release" args = "release"
secrets = ["GITHUB_TOKEN"] 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 - gosec
- unconvert - unconvert
- dupl - dupl
- maligned
- nakedret - nakedret
- prealloc - prealloc
- scopelint - scopelint

View file

@ -12,20 +12,19 @@ endif
IS_SNAPSHOT = $(if $(findstring -, $(VERSION)),true,false) IS_SNAPSHOT = $(if $(findstring -, $(VERSION)),true,false)
TAG_VERSION = v$(VERSION) TAG_VERSION = v$(VERSION)
ACT ?= go run main.go
default: check 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: check:
golangci-lint run $(ACT) -ra check
go test -cover ./...
build: deps check build: check
@GO111MODULE=off go get github.com/goreleaser/goreleaser
$(eval export SNAPSHOT_VERSION=$(VERSION)) $(eval export SNAPSHOT_VERSION=$(VERSION))
@goreleaser --snapshot --rm-dist $(ACT) -ra build
release:
$(ACT) -ra release
install: build install: build
@cp dist/$(shell go env GOOS)_$(shell go env GOARCH)/act /usr/local/bin/act @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 @GO111MODULE=off go get github.com/goreleaser/godownloader
godownloader -r nektos/act -o install.sh godownloader -r nektos/act -o install.sh
promote: promote:
@echo "VERSION:$(VERSION) IS_SNAPSHOT:$(IS_SNAPSHOT) LATEST_VERSION:$(LATEST_VERSION)" @echo "VERSION:$(VERSION) IS_SNAPSHOT:$(IS_SNAPSHOT) LATEST_VERSION:$(LATEST_VERSION)"
ifeq (false,$(IS_SNAPSHOT)) ifeq (false,$(IS_SNAPSHOT))

View file

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

View file

@ -36,12 +36,13 @@ type ActionRunner interface {
// RunnerConfig contains the config for a new runner // RunnerConfig contains the config for a new runner
type RunnerConfig struct { type RunnerConfig struct {
Ctx context.Context // context to use for the run Ctx context.Context // context to use for the run
Dryrun bool // don't start any of the containers Dryrun bool // don't start any of the containers
WorkingDir string // base directory to use WorkingDir string // base directory to use
WorkflowPath string // path to load main.workflow file, relative to WorkingDir WorkflowPath string // path to load main.workflow file, relative to WorkingDir
EventName string // name of event to run EventName string // name of event to run
EventPath string // path to JSON file to use for event.json in containers, relative to WorkingDir 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 { type environmentApplier interface {

View file

@ -5,7 +5,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"math/rand"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -78,11 +77,6 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
if err != nil { if err != nil {
return common.NewErrorExecutor(err) 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) envList := make([]string, 0)
for k, v := range env { for k, v := range env {
@ -95,13 +89,14 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
Image: image, Image: image,
WorkingDir: "/github/workspace", WorkingDir: "/github/workspace",
Env: envList, Env: envList,
Name: fmt.Sprintf("%s-%s", containerName, randSuffix), Name: runner.createContainerName(actionName),
Binds: []string{ Binds: []string{
fmt.Sprintf("%s:%s", runner.config.WorkingDir, "/github/workspace"), fmt.Sprintf("%s:%s", runner.config.WorkingDir, "/github/workspace"),
fmt.Sprintf("%s:%s", runner.tempDir, "/github/home"), fmt.Sprintf("%s:%s", runner.tempDir, "/github/home"),
fmt.Sprintf("%s:%s", "/var/run/docker.sock", "/var/run/docker.sock"), fmt.Sprintf("%s:%s", "/var/run/docker.sock", "/var/run/docker.sock"),
}, },
Content: map[string]io.Reader{"/github": ghReader}, Content: map[string]io.Reader{"/github": ghReader},
ReuseContainers: runner.config.ReuseContainers,
})) }))
return common.NewPipelineExecutor(executors...) 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 { prefix := fmt.Sprintf("%s-", trimToLen(filepath.Base(runner.config.WorkingDir), 10))
b := make([]byte, slen) suffix := ""
for i := range b { containerName = trimToLen(containerName, 30-(len(prefix)+len(suffix)))
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] 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().BoolP("list", "l", false, "list actions")
rootCmd.Flags().StringP("action", "a", "", "run action") 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.Flags().StringVarP(&runnerConfig.EventPath, "event", "e", "", "path to event JSON file")
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output") rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().BoolVarP(&runnerConfig.Dryrun, "dryrun", "n", false, "dryrun mode") rootCmd.PersistentFlags().BoolVarP(&runnerConfig.Dryrun, "dryrun", "n", false, "dryrun mode")

View file

@ -16,15 +16,16 @@ import (
// NewDockerRunExecutorInput the input for the NewDockerRunExecutor function // NewDockerRunExecutorInput the input for the NewDockerRunExecutor function
type NewDockerRunExecutorInput struct { type NewDockerRunExecutorInput struct {
DockerExecutorInput DockerExecutorInput
Image string Image string
Entrypoint []string Entrypoint []string
Cmd []string Cmd []string
WorkingDir string WorkingDir string
Env []string Env []string
Binds []string Binds []string
Content map[string]io.Reader Content map[string]io.Reader
Volumes []string Volumes []string
Name string Name string
ReuseContainers bool
} }
// NewDockerRunExecutor function to create a run executor for the container // NewDockerRunExecutor function to create a run executor for the container
@ -41,29 +42,44 @@ func NewDockerRunExecutor(input NewDockerRunExecutorInput) common.Executor {
return err return err
} }
containerID, err := createContainer(input, cli) // check if container exists
if err != nil { containerID, err := findContainer(input, cli, input.Name)
return err
}
defer removeContainer(input, cli, containerID)
err = copyContentToContainer(input, cli, containerID)
if err != nil { if err != nil {
return err return err
} }
err = attachContainer(input, cli, containerID) // if we have an old container and we aren't reusing, remove it!
if err != nil { if !input.ReuseContainers && containerID != "" {
return err input.Logger.Debugf("Found existing container for %s...removing", input.Name)
removeContainer(input, cli, containerID)
containerID = ""
} }
err = startContainer(input, cli, containerID) // create a new container if we don't have one to reuse
if err != nil { if containerID == "" {
return err containerID, err = createContainer(input, cli)
if err != nil {
return err
}
} }
return waitContainer(input, cli, containerID) // be sure to cleanup container if we aren't reusing
if !input.ReuseContainers {
defer removeContainer(input, cli, containerID)
}
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 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) { func removeContainer(input NewDockerRunExecutorInput, cli *client.Client, containerID string) {
err := cli.ContainerRemove(context.Background(), containerID, types.ContainerRemoveOptions{ err := cli.ContainerRemove(context.Background(), containerID, types.ContainerRemoveOptions{
RemoveVolumes: true, 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/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 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 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/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 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=