diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 55c4d0cc..9d46e43f 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -18,6 +18,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 - uses: actions/setup-go@v1 with: go-version: 1.14 diff --git a/act/container/docker_build.go b/act/container/docker_build.go index 1c840606..f9107790 100644 --- a/act/container/docker_build.go +++ b/act/container/docker_build.go @@ -7,24 +7,28 @@ import ( "path/filepath" "github.com/docker/docker/api/types" - "github.com/docker/docker/builder/dockerignore" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" - "github.com/nektos/act/pkg/common" + + // github.com/docker/docker/builder/dockerignore is deprecated + "github.com/moby/buildkit/frontend/dockerfile/dockerignore" log "github.com/sirupsen/logrus" + + "github.com/nektos/act/pkg/common" ) // NewDockerBuildExecutorInput the input for the NewDockerBuildExecutor function type NewDockerBuildExecutorInput struct { ContextDir string ImageTag string + Platform string } // NewDockerBuildExecutor function to create a run executor for the container func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor { return func(ctx context.Context) error { logger := common.Logger(ctx) - logger.Infof("%sdocker build -t %s %s", logPrefix, input.ImageTag, input.ContextDir) + logger.Infof("%sdocker build -t %s --platform %s %s", logPrefix, input.ImageTag, input.Platform, input.ContextDir) if common.Dryrun(ctx) { return nil } @@ -38,8 +42,9 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor { tags := []string{input.ImageTag} options := types.ImageBuildOptions{ - Tags: tags, - Remove: true, + Tags: tags, + Remove: true, + Platform: input.Platform, } buildContext, err := createBuildContext(input.ContextDir, "Dockerfile") @@ -49,7 +54,7 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor { defer buildContext.Close() - logger.Debugf("Creating image from context dir '%s' with tag '%s'", input.ContextDir, input.ImageTag) + logger.Debugf("Creating image from context dir '%s' with tag '%s' and platform '%s'", input.ContextDir, input.ImageTag, input.Platform) resp, err := cli.ImageBuild(ctx, buildContext, options) err = logDockerResponse(logger, resp.Body, err != nil) diff --git a/act/container/docker_images.go b/act/container/docker_images.go index efe267ac..37eaec59 100644 --- a/act/container/docker_images.go +++ b/act/container/docker_images.go @@ -2,14 +2,15 @@ package container import ( "context" + "fmt" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" ) // ImageExistsLocally returns a boolean indicating if an image with the -// requested name (and tag) exist in the local docker image store -func ImageExistsLocally(ctx context.Context, imageName string) (bool, error) { +// requested name, tag and architecture exists in the local docker image store +func ImageExistsLocally(ctx context.Context, imageName string, platform string) (bool, error) { cli, err := GetDockerClient(ctx) if err != nil { return false, err @@ -27,5 +28,61 @@ func ImageExistsLocally(ctx context.Context, imageName string) (bool, error) { return false, err } - return len(images) > 0, nil + if len(images) > 0 { + if platform == "any" { + return true, nil + } + for _, v := range images { + inspectImage, _, err := cli.ImageInspectWithRaw(ctx, v.ID) + if err != nil { + return false, err + } + + if fmt.Sprintf("%s/%s", inspectImage.Os, inspectImage.Architecture) == platform { + return true, nil + } + } + return false, nil + } + + return false, nil +} + +// DeleteImage removes image from local store, the function is used to run different +// container image architectures +func DeleteImage(ctx context.Context, imageName string) (bool, error) { + if exists, err := ImageExistsLocally(ctx, imageName, "any"); !exists { + return false, err + } + + cli, err := GetDockerClient(ctx) + if err != nil { + return false, err + } + + filters := filters.NewArgs() + filters.Add("reference", imageName) + + imageListOptions := types.ImageListOptions{ + Filters: filters, + } + + images, err := cli.ImageList(ctx, imageListOptions) + if err != nil { + return false, err + } + + if len(images) > 0 { + for _, v := range images { + if _, err = cli.ImageRemove(ctx, v.ID, types.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }); err != nil { + return false, err + } + } + return true, nil + } + + return false, nil } diff --git a/act/container/docker_images_test.go b/act/container/docker_images_test.go index aebb0667..ee1b763a 100644 --- a/act/container/docker_images_test.go +++ b/act/container/docker_images_test.go @@ -23,9 +23,15 @@ func TestImageExistsLocally(t *testing.T) { // to help make this test reliable and not flaky, we need to have // an image that will exist, and onew that won't exist - exists, err := ImageExistsLocally(ctx, "library/alpine:this-random-tag-will-never-exist") + // Test if image exists with specific tag + invalidImageTag, err := ImageExistsLocally(ctx, "library/alpine:this-random-tag-will-never-exist", "linux/amd64") assert.Nil(t, err) - assert.Equal(t, false, exists) + assert.Equal(t, false, invalidImageTag) + + // Test if image exists with specific architecture (image platform) + invalidImagePlatform, err := ImageExistsLocally(ctx, "alpine:latest", "windows/amd64") + assert.Nil(t, err) + assert.Equal(t, false, invalidImagePlatform) // pull an image cli, err := client.NewClientWithOpts(client.FromEnv) @@ -34,13 +40,28 @@ func TestImageExistsLocally(t *testing.T) { // Chose alpine latest because it's so small // maybe we should build an image instead so that tests aren't reliable on dockerhub - reader, err := cli.ImagePull(ctx, "alpine:latest", types.ImagePullOptions{}) + readerDefault, err := cli.ImagePull(ctx, "alpine:latest", types.ImagePullOptions{ + Platform: "linux/amd64", + }) assert.Nil(t, err) - defer reader.Close() - _, err = ioutil.ReadAll(reader) + defer readerDefault.Close() + _, err = ioutil.ReadAll(readerDefault) assert.Nil(t, err) - exists, err = ImageExistsLocally(ctx, "alpine:latest") + imageDefaultArchExists, err := ImageExistsLocally(ctx, "alpine:latest", "linux/amd64") assert.Nil(t, err) - assert.Equal(t, true, exists) + assert.Equal(t, true, imageDefaultArchExists) + + // Validate if another architecture platform can be pulled + readerArm64, err := cli.ImagePull(ctx, "alpine:latest", types.ImagePullOptions{ + Platform: "linux/arm64", + }) + assert.Nil(t, err) + defer readerArm64.Close() + _, err = ioutil.ReadAll(readerArm64) + assert.Nil(t, err) + + imageArm64Exists, err := ImageExistsLocally(ctx, "alpine:latest", "linux/arm64") + assert.Nil(t, err) + assert.Equal(t, true, imageArm64Exists) } diff --git a/act/container/docker_pull.go b/act/container/docker_pull.go index 6a01856e..ee8e0dbe 100644 --- a/act/container/docker_pull.go +++ b/act/container/docker_pull.go @@ -6,15 +6,17 @@ import ( "strings" "github.com/docker/docker/api/types" - "github.com/nektos/act/pkg/common" "github.com/pkg/errors" log "github.com/sirupsen/logrus" + + "github.com/nektos/act/pkg/common" ) // NewDockerPullExecutorInput the input for the NewDockerPullExecutor function type NewDockerPullExecutorInput struct { Image string ForcePull bool + Platform string } // NewDockerPullExecutor function to create a run executor for the container @@ -29,10 +31,10 @@ func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor { pull := input.ForcePull if !pull { - imageExists, err := ImageExistsLocally(ctx, input.Image) + imageExists, err := ImageExistsLocally(ctx, input.Image, input.Platform) log.Debugf("Image exists? %v", imageExists) if err != nil { - return errors.WithMessagef(err, "unable to determine if image already exists for image %q", input.Image) + return errors.WithMessagef(err, "unable to determine if image already exists for image %q (%s)", input.Image, input.Platform) } if !imageExists { @@ -45,14 +47,16 @@ func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor { } imageRef := cleanImage(input.Image) - logger.Debugf("pulling image '%v'", imageRef) + logger.Debugf("pulling image '%v' (%s)", imageRef, input.Platform) cli, err := GetDockerClient(ctx) if err != nil { return err } - reader, err := cli.ImagePull(ctx, imageRef, types.ImagePullOptions{}) + reader, err := cli.ImagePull(ctx, imageRef, types.ImagePullOptions{ + Platform: input.Platform, + }) _ = logDockerResponse(logger, reader, err != nil) if err != nil { return err diff --git a/act/container/docker_run.go b/act/container/docker_run.go index 047bd079..68340eff 100644 --- a/act/container/docker_run.go +++ b/act/container/docker_run.go @@ -24,10 +24,13 @@ import ( "github.com/docker/docker/api/types/mount" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" - "github.com/nektos/act/pkg/common" + specs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "golang.org/x/term" + + "github.com/nektos/act/pkg/common" ) // NewContainerInput the input for the New function @@ -45,6 +48,7 @@ type NewContainerInput struct { NetworkMode string Privileged bool UsernsMode string + Platform string } // FileEntry is a file to copy to a container @@ -75,7 +79,7 @@ func NewContainer(input *NewContainerInput) Container { func (cr *containerReference) Create() common.Executor { return common. - NewDebugExecutor("%sdocker create image=%s entrypoint=%+q cmd=%+q", logPrefix, cr.input.Image, cr.input.Entrypoint, cr.input.Cmd). + NewDebugExecutor("%sdocker create image=%s platform=%s entrypoint=%+q cmd=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd). Then( common.NewPipelineExecutor( cr.connect(), @@ -86,7 +90,7 @@ func (cr *containerReference) Create() common.Executor { } func (cr *containerReference) Start(attach bool) common.Executor { return common. - NewInfoExecutor("%sdocker run image=%s entrypoint=%+q cmd=%+q", logPrefix, cr.input.Image, cr.input.Entrypoint, cr.input.Cmd). + NewInfoExecutor("%sdocker run image=%s platform=%s entrypoint=%+q cmd=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd). Then( common.NewPipelineExecutor( cr.connect(), @@ -101,6 +105,7 @@ func (cr *containerReference) Pull(forcePull bool) common.Executor { return NewDockerPullExecutor(NewDockerPullExecutorInput{ Image: cr.input.Image, ForcePull: forcePull, + Platform: cr.input.Platform, }) } func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common.Executor { @@ -267,17 +272,26 @@ func (cr *containerReference) create() common.Executor { }) } + desiredPlatform := strings.SplitN(cr.input.Platform, `/`, 2) + + if len(desiredPlatform) != 2 { + logger.Panicf("Incorrect container platform option. %s is not a valid platform.", cr.input.Platform) + } + resp, err := cr.cli.ContainerCreate(ctx, config, &container.HostConfig{ Binds: input.Binds, Mounts: mounts, NetworkMode: container.NetworkMode(input.NetworkMode), Privileged: input.Privileged, UsernsMode: container.UsernsMode(input.UsernsMode), - }, nil, input.Name) + }, nil, &specs.Platform{ + Architecture: desiredPlatform[1], + OS: desiredPlatform[0], + }, input.Name) if err != nil { return errors.WithStack(err) } - logger.Debugf("Created container name=%s id=%v from image %v", input.Name, resp.ID, input.Image) + logger.Debugf("Created container name=%s id=%v from image %v (platform: %s)", input.Name, resp.ID, input.Image, input.Platform) logger.Debugf("ENV ==> %v", input.Env) cr.id = resp.ID diff --git a/act/runner/run_context.go b/act/runner/run_context.go index 82aae319..90dba18d 100644 --- a/act/runner/run_context.go +++ b/act/runner/run_context.go @@ -11,11 +11,11 @@ import ( "runtime" "strings" - "github.com/nektos/act/pkg/container" + log "github.com/sirupsen/logrus" "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/model" - log "github.com/sirupsen/logrus" ) // RunContext contains info about current job @@ -89,6 +89,10 @@ func (rc *RunContext) startJobContainer() common.Executor { binds = append(binds, fmt.Sprintf("%s:%s%s", rc.Config.Workdir, "/github/workspace", bindModifiers)) } + if rc.Config.ContainerArchitecture == "" { + rc.Config.ContainerArchitecture = fmt.Sprintf("%s/%s", "linux", runtime.GOARCH) + } + rc.JobContainer = container.NewContainer(&container.NewContainerInput{ Cmd: nil, Entrypoint: []string{"/usr/bin/tail", "-f", "/dev/null"}, @@ -107,6 +111,7 @@ func (rc *RunContext) startJobContainer() common.Executor { Stderr: logWriter, Privileged: rc.Config.Privileged, UsernsMode: rc.Config.UsernsMode, + Platform: rc.Config.ContainerArchitecture, }) var copyWorkspace bool diff --git a/act/runner/runner.go b/act/runner/runner.go index f03de4de..531e6c07 100644 --- a/act/runner/runner.go +++ b/act/runner/runner.go @@ -17,21 +17,22 @@ type Runner interface { // Config contains the config for a new runner type Config struct { - Actor string // the user that triggered the event - Workdir string // path to working directory - BindWorkdir bool // bind the workdir to the job container - EventName string // name of event to run - 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 - ForcePull bool // force pulling of the image, if already present - LogOutput bool // log the output from docker run - Env map[string]string // env for containers - Secrets map[string]string // list of secrets - InsecureSecrets bool // switch hiding output when printing to terminal - Platforms map[string]string // list of platforms - Privileged bool // use privileged mode - UsernsMode string // user namespace to use + Actor string // the user that triggered the event + Workdir string // path to working directory + BindWorkdir bool // bind the workdir to the job container + EventName string // name of event to run + 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 + ForcePull bool // force pulling of the image, if already present + LogOutput bool // log the output from docker run + Env map[string]string // env for containers + Secrets map[string]string // list of secrets + InsecureSecrets bool // switch hiding output when printing to terminal + Platforms map[string]string // list of platforms + Privileged bool // use privileged mode + UsernsMode string // user namespace to use + ContainerArchitecture string // Desired OS/architecture platform for running containers } type runnerImpl struct { diff --git a/act/runner/runner_test.go b/act/runner/runner_test.go index 4199e19e..5cfb34c9 100644 --- a/act/runner/runner_test.go +++ b/act/runner/runner_test.go @@ -7,10 +7,10 @@ import ( "testing" "github.com/joho/godotenv" - "github.com/nektos/act/pkg/model" - log "github.com/sirupsen/logrus" "gotest.tools/v3/assert" + + "github.com/nektos/act/pkg/model" ) func TestGraphEvent(t *testing.T) { @@ -32,11 +32,12 @@ func TestGraphEvent(t *testing.T) { } type TestJobFileInfo struct { - workdir string - workflowPath string - eventName string - errorMessage string - platforms map[string]string + workdir string + workflowPath string + eventName string + errorMessage string + platforms map[string]string + containerArchitecture string } func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) { @@ -45,11 +46,12 @@ func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) { assert.NilError(t, err, workdir) fullWorkflowPath := filepath.Join(workdir, tjfi.workflowPath) runnerConfig := &Config{ - Workdir: workdir, - BindWorkdir: true, - EventName: tjfi.eventName, - Platforms: tjfi.platforms, - ReuseContainers: false, + Workdir: workdir, + BindWorkdir: true, + EventName: tjfi.eventName, + Platforms: tjfi.platforms, + ReuseContainers: false, + ContainerArchitecture: tjfi.containerArchitecture, } runner, err := New(runnerConfig) assert.NilError(t, err, tjfi.workflowPath) @@ -77,23 +79,42 @@ func TestRunEvent(t *testing.T) { "ubuntu-latest": "node:12.20.1-buster-slim", } tables := []TestJobFileInfo{ - {"testdata", "basic", "push", "", platforms}, - {"testdata", "fail", "push", "exit with `FAILURE`: 1", platforms}, - {"testdata", "runs-on", "push", "", platforms}, - {"testdata", "job-container", "push", "", platforms}, - {"testdata", "job-container-non-root", "push", "", platforms}, - {"testdata", "uses-docker-url", "push", "", platforms}, - {"testdata", "remote-action-docker", "push", "", platforms}, - {"testdata", "remote-action-js", "push", "", platforms}, - {"testdata", "local-action-docker-url", "push", "", platforms}, - {"testdata", "local-action-dockerfile", "push", "", platforms}, - {"testdata", "local-action-js", "push", "", platforms}, - {"testdata", "matrix", "push", "", platforms}, - {"testdata", "matrix-include-exclude", "push", "", platforms}, - {"testdata", "commands", "push", "", platforms}, - {"testdata", "workdir", "push", "", platforms}, - // {"testdata", "issue-228", "push", "", platforms}, // TODO [igni]: Remove this once everything passes - {"testdata", "defaults-run", "push", "", platforms}, + {"testdata", "basic", "push", "", platforms, "linux/amd64"}, + {"testdata", "fail", "push", "exit with `FAILURE`: 1", platforms, "linux/amd64"}, + {"testdata", "runs-on", "push", "", platforms, "linux/amd64"}, + {"testdata", "job-container", "push", "", platforms, "linux/amd64"}, + {"testdata", "job-container-non-root", "push", "", platforms, "linux/amd64"}, + {"testdata", "uses-docker-url", "push", "", platforms, "linux/amd64"}, + {"testdata", "remote-action-docker", "push", "", platforms, "linux/amd64"}, + {"testdata", "remote-action-js", "push", "", platforms, "linux/amd64"}, + {"testdata", "local-action-docker-url", "push", "", platforms, "linux/amd64"}, + {"testdata", "local-action-dockerfile", "push", "", platforms, "linux/amd64"}, + {"testdata", "local-action-js", "push", "", platforms, "linux/amd64"}, + {"testdata", "matrix", "push", "", platforms, "linux/amd64"}, + {"testdata", "matrix-include-exclude", "push", "", platforms, "linux/amd64"}, + {"testdata", "commands", "push", "", platforms, "linux/amd64"}, + {"testdata", "workdir", "push", "", platforms, "linux/amd64"}, + // {"testdata", "issue-228", "push", "", platforms, "linux/amd64"}, // TODO [igni]: Remove this once everything passes + {"testdata", "defaults-run", "push", "", platforms, "linux/amd64"}, + + // linux/arm64 + {"testdata", "basic", "push", "", platforms, "linux/arm64"}, + {"testdata", "fail", "push", "exit with `FAILURE`: 1", platforms, "linux/arm64"}, + {"testdata", "runs-on", "push", "", platforms, "linux/arm64"}, + {"testdata", "job-container", "push", "", platforms, "linux/arm64"}, + {"testdata", "job-container-non-root", "push", "", platforms, "linux/arm64"}, + {"testdata", "uses-docker-url", "push", "", platforms, "linux/arm64"}, + {"testdata", "remote-action-docker", "push", "", platforms, "linux/arm64"}, + {"testdata", "remote-action-js", "push", "", platforms, "linux/arm64"}, + {"testdata", "local-action-docker-url", "push", "", platforms, "linux/arm64"}, + {"testdata", "local-action-dockerfile", "push", "", platforms, "linux/arm64"}, + {"testdata", "local-action-js", "push", "", platforms, "linux/arm64"}, + {"testdata", "matrix", "push", "", platforms, "linux/arm64"}, + {"testdata", "matrix-include-exclude", "push", "", platforms, "linux/arm64"}, + {"testdata", "commands", "push", "", platforms, "linux/arm64"}, + {"testdata", "workdir", "push", "", platforms, "linux/arm64"}, + // {"testdata", "issue-228", "push", "", platforms, "linux/arm64"}, // TODO [igni]: Remove this once everything passes + {"testdata", "defaults-run", "push", "", platforms, "linux/arm64"}, } log.SetLevel(log.DebugLevel) diff --git a/act/runner/step_context.go b/act/runner/step_context.go index 09a211f0..137d384e 100644 --- a/act/runner/step_context.go +++ b/act/runner/step_context.go @@ -10,8 +10,8 @@ import ( "runtime" "strings" - log "github.com/sirupsen/logrus" "github.com/kballard/go-shellquote" + log "github.com/sirupsen/logrus" "github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/container" @@ -217,6 +217,10 @@ func (sc *StepContext) newStepContainer(ctx context.Context, image string, cmd [ binds = append(binds, fmt.Sprintf("%s:%s%s", rc.Config.Workdir, "/github/workspace", bindModifiers)) } + if rc.Config.ContainerArchitecture == "" { + rc.Config.ContainerArchitecture = fmt.Sprintf("%s/%s", "linux", runtime.GOARCH) + } + stepContainer := container.NewContainer(&container.NewContainerInput{ Cmd: cmd, Entrypoint: entrypoint, @@ -235,6 +239,7 @@ func (sc *StepContext) newStepContainer(ctx context.Context, image string, cmd [ Stderr: logWriter, Privileged: rc.Config.Privileged, UsernsMode: rc.Config.UsernsMode, + Platform: rc.Config.ContainerArchitecture, }) return stepContainer } @@ -354,10 +359,35 @@ func (sc *StepContext) runAction(actionDir string, actionPath string) common.Exe image = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-")) image = strings.ToLower(image) contextDir := filepath.Join(actionDir, actionPath, action.Runs.Main) + + exists, err := container.ImageExistsLocally(ctx, image, "any") + if err != nil { + return err + } + + if exists { + wasRemoved, err := container.DeleteImage(ctx, image) + if err != nil { + return err + } + if !wasRemoved { + return fmt.Errorf("failed to delete image '%s'", image) + } + } + prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{ ContextDir: contextDir, ImageTag: image, + Platform: rc.Config.ContainerArchitecture, }) + exists, err = container.ImageExistsLocally(ctx, image, rc.Config.ContainerArchitecture) + if err != nil { + return err + } + + if !exists { + return err + } } cmd, err := shellquote.Split(step.With["args"]) diff --git a/act/runner/step_context_test.go b/act/runner/step_context_test.go index 527da675..84a1b66a 100644 --- a/act/runner/step_context_test.go +++ b/act/runner/step_context_test.go @@ -12,10 +12,14 @@ func TestStepContextExecutor(t *testing.T) { "ubuntu-latest": "node:12.20.1-buster-slim", } tables := []TestJobFileInfo{ - {"testdata", "uses-github-empty", "push", "Expected format {org}/{repo}[/path]@ref", platforms}, - {"testdata", "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms}, - {"testdata", "uses-github-root", "push", "", platforms}, - {"testdata", "uses-github-path", "push", "", platforms}, + {"testdata", "uses-github-empty", "push", "Expected format {org}/{repo}[/path]@ref", platforms, "linux/amd64"}, + {"testdata", "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms, "linux/amd64"}, + {"testdata", "uses-github-root", "push", "", platforms, "linux/amd64"}, + {"testdata", "uses-github-path", "push", "", platforms, "linux/amd64"}, + {"testdata", "uses-github-empty", "push", "Expected format {org}/{repo}[/path]@ref", platforms, "linux/arm64"}, + {"testdata", "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms, "linux/arm64"}, + {"testdata", "uses-github-root", "push", "", platforms, "linux/arm64"}, + {"testdata", "uses-github-path", "push", "", platforms, "linux/arm64"}, } // These tests are sufficient to only check syntax. ctx := common.WithDryrun(context.Background(), true) diff --git a/cmd/input.go b/cmd/input.go index aa1cecdd..b54bc681 100644 --- a/cmd/input.go +++ b/cmd/input.go @@ -7,25 +7,26 @@ import ( // Input contains the input for the root command type Input struct { - actor string - workdir string - workflowsPath string - autodetectEvent bool - eventPath string - reuseContainers bool - bindWorkdir bool - secrets []string - envs []string - platforms []string - dryrun bool - forcePull bool - noOutput bool - envfile string - secretfile string - insecureSecrets bool - defaultBranch string - privileged bool - usernsMode string + actor string + workdir string + workflowsPath string + autodetectEvent bool + eventPath string + reuseContainers bool + bindWorkdir bool + secrets []string + envs []string + platforms []string + dryrun bool + forcePull bool + noOutput bool + envfile string + secretfile string + insecureSecrets bool + defaultBranch string + privileged bool + usernsMode string + containerArchitecture string } func (i *Input) resolve(path string) string { diff --git a/cmd/root.go b/cmd/root.go index 826c7837..dc3073e5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "strings" "github.com/nektos/act/pkg/common" @@ -57,6 +58,7 @@ func Execute(ctx context.Context, version string) { rootCmd.PersistentFlags().StringVarP(&input.secretfile, "secret-file", "", ".secrets", "file with list of secrets to read from (e.g. --secret-file .secrets)") rootCmd.PersistentFlags().BoolVarP(&input.insecureSecrets, "insecure-secrets", "", false, "NOT RECOMMENDED! Doesn't hide secrets while printing logs.") rootCmd.PersistentFlags().StringVarP(&input.envfile, "env-file", "", ".env", "environment file to read and use as env in the containers") + rootCmd.PersistentFlags().StringVarP(&input.containerArchitecture, "container-architecture", "", "", "Architecture which should be used to run containers, e.g.: linux/amd64. Defaults to linux/ [linux/"+runtime.GOARCH+"]") rootCmd.SetArgs(args()) if err := rootCmd.Execute(); err != nil { @@ -247,21 +249,22 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str // run the plan config := &runner.Config{ - Actor: input.actor, - EventName: eventName, - EventPath: input.EventPath(), - DefaultBranch: defaultbranch, - ForcePull: input.forcePull, - ReuseContainers: input.reuseContainers, - Workdir: input.Workdir(), - BindWorkdir: input.bindWorkdir, - LogOutput: !input.noOutput, - Env: envs, - Secrets: secrets, - InsecureSecrets: input.insecureSecrets, - Platforms: input.newPlatforms(), - Privileged: input.privileged, - UsernsMode: input.usernsMode, + Actor: input.actor, + EventName: eventName, + EventPath: input.EventPath(), + DefaultBranch: defaultbranch, + ForcePull: input.forcePull, + ReuseContainers: input.reuseContainers, + Workdir: input.Workdir(), + BindWorkdir: input.bindWorkdir, + LogOutput: !input.noOutput, + Env: envs, + Secrets: secrets, + InsecureSecrets: input.insecureSecrets, + Platforms: input.newPlatforms(), + Privileged: input.privileged, + UsernsMode: input.usernsMode, + ContainerArchitecture: input.containerArchitecture, } r, err := runner.New(config) if err != nil {