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
|
||||
|
||||
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
|
||||
|
|
8
.github/actions/check/entrypoint.sh
vendored
8
.github/actions/check/entrypoint.sh
vendored
|
@ -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 ./...
|
14
.github/main.workflow
vendored
14
.github/main.workflow
vendored
|
@ -7,15 +7,21 @@ action "check" {
|
|||
uses = "./.github/actions/check"
|
||||
}
|
||||
|
||||
action "branch-filter" {
|
||||
action "branch-filter" {
|
||||
needs = ["check"]
|
||||
uses = "actions/bin/filter@master"
|
||||
args = "tag v*"
|
||||
}
|
||||
}
|
||||
|
||||
action "release" {
|
||||
action "release" {
|
||||
needs = ["branch-filter"]
|
||||
uses = "docker://goreleaser/goreleaser:v0.97"
|
||||
args = "release"
|
||||
secrets = ["GITHUB_TOKEN"]
|
||||
}
|
||||
}
|
||||
|
||||
action "build" {
|
||||
uses = "docker://goreleaser/goreleaser:v0.97"
|
||||
args = "--snapshot --rm-dist"
|
||||
secrets = ["SNAPSHOT_VERSION"]
|
||||
}
|
|
@ -15,7 +15,6 @@ linters:
|
|||
- gosec
|
||||
- unconvert
|
||||
- dupl
|
||||
- maligned
|
||||
- nakedret
|
||||
- prealloc
|
||||
- scopelint
|
||||
|
|
18
Makefile
18
Makefile
|
@ -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))
|
||||
|
|
|
@ -41,6 +41,9 @@ act -a test
|
|||
|
||||
# Run in dry-run mode:
|
||||
act -n
|
||||
|
||||
# Run in reuse mode to save state:
|
||||
act -r
|
||||
```
|
||||
|
||||
# Support
|
||||
|
|
|
@ -36,12 +36,13 @@ type ActionRunner interface {
|
|||
|
||||
// RunnerConfig contains the config for a new runner
|
||||
type RunnerConfig struct {
|
||||
Ctx context.Context // context to use for the run
|
||||
Dryrun bool // don't start any of the containers
|
||||
WorkingDir string // base directory to use
|
||||
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
|
||||
Ctx context.Context // context to use for the run
|
||||
Dryrun bool // don't start any of the containers
|
||||
WorkingDir string // base directory to use
|
||||
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 {
|
||||
|
|
|
@ -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},
|
||||
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))]
|
||||
}
|
||||
return string(b)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -16,15 +16,16 @@ import (
|
|||
// NewDockerRunExecutorInput the input for the NewDockerRunExecutor function
|
||||
type NewDockerRunExecutorInput struct {
|
||||
DockerExecutorInput
|
||||
Image string
|
||||
Entrypoint []string
|
||||
Cmd []string
|
||||
WorkingDir string
|
||||
Env []string
|
||||
Binds []string
|
||||
Content map[string]io.Reader
|
||||
Volumes []string
|
||||
Name string
|
||||
Image string
|
||||
Entrypoint []string
|
||||
Cmd []string
|
||||
WorkingDir string
|
||||
Env []string
|
||||
Binds []string
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer removeContainer(input, cli, containerID)
|
||||
|
||||
err = copyContentToContainer(input, cli, containerID)
|
||||
// check if container exists
|
||||
containerID, err := findContainer(input, cli, input.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = attachContainer(input, cli, containerID)
|
||||
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 = ""
|
||||
}
|
||||
|
||||
err = startContainer(input, cli, containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
// create a new container if we don't have one to reuse
|
||||
if containerID == "" {
|
||||
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
|
||||
}
|
||||
|
||||
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
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/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=
|
||||
|
|
Loading…
Reference in a new issue