diff --git a/cmd/input.go b/cmd/input.go index c348ef3acf5..239b0405be9 100644 --- a/cmd/input.go +++ b/cmd/input.go @@ -14,6 +14,7 @@ type Input struct { autodetectEvent bool eventPath string reuseContainers bool + uniqueContainerNames bool bindWorkdir bool secrets []string vars []string diff --git a/cmd/root.go b/cmd/root.go index 1fbb55bcfe1..779de9d9851 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -81,6 +81,7 @@ func createRootCommand(ctx context.Context, input *Input, version string) *cobra rootCmd.Flags().StringArrayVarP(&input.inputs, "input", "", []string{}, "action input to make available to actions (e.g. --input myinput=foo)") rootCmd.Flags().StringArrayVarP(&input.platforms, "platform", "P", []string{}, "custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)") rootCmd.Flags().BoolVarP(&input.reuseContainers, "reuse", "r", false, "don't remove container(s) on successfully completed workflow(s) to maintain state between runs") + rootCmd.Flags().BoolVarP(&input.uniqueContainerNames, "unique-container-names", "", false, "container names will include a (quasi-)unique (random) component to avoid conflicts with other instances of act running against the same Docker server (disabled if --reuse is present)") rootCmd.Flags().BoolVarP(&input.bindWorkdir, "bind", "b", false, "bind working directory to container, rather than copy") rootCmd.Flags().BoolVarP(&input.forcePull, "pull", "p", true, "pull docker image(s) even if already present") rootCmd.Flags().BoolVarP(&input.forceRebuild, "rebuild", "", true, "rebuild local action docker image(s) even if already present") @@ -611,6 +612,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str ForcePull: !input.actionOfflineMode && input.forcePull, ForceRebuild: input.forceRebuild, ReuseContainers: input.reuseContainers, + UniqueContainerNames: input.uniqueContainerNames, Workdir: input.Workdir(), ActionCacheDir: input.actionCachePath, ActionOfflineMode: input.actionOfflineMode, diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index 5d4277123ed..57a32559334 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -12,6 +12,7 @@ import ( "errors" "fmt" "io" + "math/big" "os" "path/filepath" "regexp" @@ -28,6 +29,8 @@ import ( "github.com/opencontainers/selinux/go-selinux" ) +var uniqueIdentifier = "" + // RunContext contains info about current job type RunContext struct { Name string @@ -55,6 +58,38 @@ type RunContext struct { nodeToolFullPath string } +func init() { + var charset = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + + // To allow multiple concurrent instances of ACT, we need to use + // a unique identifier that will differentiate all containers + // spawned by this instance from those spawned by others. If we + // don't do that, then weird conflicts will arise due to possible + // container name collsions. We use a secure random string of + // alphanumeric characters for this purpose. + // + // We don't use a UUID due to its length - we don't want too long + // a string ... just long enough to provide sufficient randomness + // + // This function will try to generate a secure random string. If + // that fails for some reason, it will use the PID as its + // "unique identifier" + length64 := int64(len(charset)) + target := make([]rune, 8) + for i := range target { + p, err := rand.Int(rand.Reader, big.NewInt(length64)) + if err != nil { + // TODO: Should we log this error? How (without a lot of hoops)? + // If we had some sort of issue with the randomness, + // let's just use the PID as the "randomness" + uniqueIdentifier = fmt.Sprintf("%08X", os.Getpid()) + return + } + target[i] = charset[p.Int64()] + } + uniqueIdentifier = string(target) +} + func (rc *RunContext) AddMask(mask string) { rc.Masks = append(rc.Masks, mask) } @@ -90,6 +125,13 @@ func (rc *RunContext) GetEnv() map[string]string { } func (rc *RunContext) jobContainerName() string { + // Assume we want to use the consistent, constant identifier + if rc.Config.UniqueContainerNames && !rc.Config.ReuseContainers { + // If we're not going to reuse the containers, and we want to be + // able to run multiple instances of act concurrently, then we + // must use the instance-specific (random) identifier + return createContainerName("act", uniqueIdentifier, rc.String()) + } return createContainerName("act", rc.String()) } diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index b72bbc69746..fddc371669a 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -29,6 +29,7 @@ type Config struct { EventPath string // path to JSON file to use for event.json in containers DefaultBranch string // name of the main branch for this repository ReuseContainers bool // reuse containers to maintain state + UniqueContainerNames bool // make container names for this instance unique ForcePull bool // force pulling of the image, even if already present ForceRebuild bool // force rebuilding local docker image action LogOutput bool // log the output from docker run