1
0
Fork 0
mirror of https://code.forgejo.org/forgejo/runner.git synced 2025-08-11 17:50:58 +00:00

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 f26a1a3f0c
commit ff392444a4
7 changed files with 95 additions and 61 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

@ -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,