diff options
-rw-r--r-- | .github/actions/check/Dockerfile | 8 | ||||
-rw-r--r-- | .github/actions/check/entrypoint.sh | 8 | ||||
-rw-r--r-- | .github/main.workflow | 14 | ||||
-rw-r--r-- | .golangci.yml | 1 | ||||
-rw-r--r-- | Makefile | 18 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | actions/api.go | 13 | ||||
-rw-r--r-- | actions/runner_exec.go | 29 | ||||
-rw-r--r-- | cmd/root.go | 1 | ||||
-rw-r--r-- | container/docker_run.go | 79 | ||||
-rw-r--r-- | go.sum | 1 |
11 files changed, 105 insertions, 70 deletions
diff --git a/.github/actions/check/Dockerfile b/.github/actions/check/Dockerfile index 4f299fa..12f9d8d 100644 --- a/.github/actions/check/Dockerfile +++ b/.github/actions/check/Dockerfile @@ -1,10 +1,6 @@ -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 -ENTRYPOINT ["/entrypoint.sh"] +ENTRYPOINT ["/entrypoint.sh"]
\ No newline at end of file diff --git a/.github/actions/check/entrypoint.sh b/.github/actions/check/entrypoint.sh index 2489661..be59dc9 100644 --- a/.github/actions/check/entrypoint.sh +++ b/.github/actions/check/entrypoint.sh @@ -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 ./...
\ No newline at end of file diff --git a/.github/main.workflow b/.github/main.workflow index b6d13a9..afc96ae 100644 --- a/.github/main.workflow +++ b/.github/main.workflow @@ -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"] - }
\ No newline at end of file +} + +action "build" { + uses = "docker://goreleaser/goreleaser:v0.97" + args = "--snapshot --rm-dist" + secrets = ["SNAPSHOT_VERSION"] +}
\ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index 085539b..8b00dd9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,7 +15,6 @@ linters: - gosec - unconvert - dupl - - maligned - nakedret - prealloc - scopelint @@ -12,20 +12,19 @@ endif IS_SNAPSHOT = $(if $(findstring -, $(VERSION)),true,false) TAG_VERSION = v$(VERSION) -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 +ACT ?= go run main.go +default: check 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 diff --git a/actions/api.go b/actions/api.go index 0680165..6836596 100644 --- a/actions/api.go +++ b/actions/api.go @@ -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 { diff --git a/actions/runner_exec.go b/actions/runner_exec.go index 2e5e2d4..b6be9a3 100644 --- a/actions/runner_exec.go +++ b/actions/runner_exec.go @@ -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, "-") + + 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 randString(slen int) string { - b := make([]byte, slen) - for i := range b { - b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] +func trimToLen(s string, l int) string { + if len(s) > l { + return s[:l] } - return string(b) + return s } diff --git a/cmd/root.go b/cmd/root.go index 932689f..7bfe13d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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") diff --git a/container/docker_run.go b/container/docker_run.go index 18cda31..b078d60 100644 --- a/container/docker_run.go +++ b/container/docker_run.go @@ -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) + // check if container exists + containerID, err := findContainer(input, cli, input.Name) if err != nil { return err } - defer removeContainer(input, cli, containerID) - err = copyContentToContainer(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 = attachContainer(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 + } } - err = startContainer(input, cli, containerID) - if err != nil { - return err + // be sure to cleanup container if we aren't reusing + if !input.ReuseContainers { + defer removeContainer(input, cli, containerID) } - return waitContainer(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, @@ -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= |