add support for 'reuse' mode to allow act to be used for a fast local task runner
This commit is contained in:
parent
317a305f51
commit
8793c8a6a4
11 changed files with 107 additions and 72 deletions
6
.github/actions/check/Dockerfile
vendored
6
.github/actions/check/Dockerfile
vendored
|
@ -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
|
||||||
|
|
8
.github/actions/check/entrypoint.sh
vendored
8
.github/actions/check/entrypoint.sh
vendored
|
@ -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
14
.github/main.workflow
vendored
|
@ -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"]
|
||||||
|
}
|
|
@ -15,7 +15,6 @@ linters:
|
||||||
- gosec
|
- gosec
|
||||||
- unconvert
|
- unconvert
|
||||||
- dupl
|
- dupl
|
||||||
- maligned
|
|
||||||
- nakedret
|
- nakedret
|
||||||
- prealloc
|
- prealloc
|
||||||
- scopelint
|
- scopelint
|
||||||
|
|
18
Makefile
18
Makefile
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
1
go.sum
|
@ -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=
|
||||||
|
|
Loading…
Reference in a new issue