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

Add option to run custom architecture (container platform) (#525)

* Add QEMU to run different architectures

* Update dependencies in `go.mod`

* Add `--container-architecture` flag to specify custom image architecture

Co-authored-by: Casey Lee <cplee@nektos.com>
This commit is contained in:
hackercat 2021-03-29 06:08:40 +02:00 committed by GitHub
parent 195e84f0fd
commit 391acea9e2
13 changed files with 279 additions and 111 deletions

View file

@ -18,6 +18,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- uses: actions/setup-go@v1 - uses: actions/setup-go@v1
with: with:
go-version: 1.14 go-version: 1.14

View file

@ -7,24 +7,28 @@ import (
"path/filepath" "path/filepath"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/builder/dockerignore"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils" "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" log "github.com/sirupsen/logrus"
"github.com/nektos/act/pkg/common"
) )
// NewDockerBuildExecutorInput the input for the NewDockerBuildExecutor function // NewDockerBuildExecutorInput the input for the NewDockerBuildExecutor function
type NewDockerBuildExecutorInput struct { type NewDockerBuildExecutorInput struct {
ContextDir string ContextDir string
ImageTag string ImageTag string
Platform string
} }
// NewDockerBuildExecutor function to create a run executor for the container // NewDockerBuildExecutor function to create a run executor for the container
func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor { func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) 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) { if common.Dryrun(ctx) {
return nil return nil
} }
@ -40,6 +44,7 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
options := types.ImageBuildOptions{ options := types.ImageBuildOptions{
Tags: tags, Tags: tags,
Remove: true, Remove: true,
Platform: input.Platform,
} }
buildContext, err := createBuildContext(input.ContextDir, "Dockerfile") buildContext, err := createBuildContext(input.ContextDir, "Dockerfile")
@ -49,7 +54,7 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
defer buildContext.Close() 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) resp, err := cli.ImageBuild(ctx, buildContext, options)
err = logDockerResponse(logger, resp.Body, err != nil) err = logDockerResponse(logger, resp.Body, err != nil)

View file

@ -2,14 +2,15 @@ package container
import ( import (
"context" "context"
"fmt"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
) )
// ImageExistsLocally returns a boolean indicating if an image with the // ImageExistsLocally returns a boolean indicating if an image with the
// requested name (and tag) exist in the local docker image store // requested name, tag and architecture exists in the local docker image store
func ImageExistsLocally(ctx context.Context, imageName string) (bool, error) { func ImageExistsLocally(ctx context.Context, imageName string, platform string) (bool, error) {
cli, err := GetDockerClient(ctx) cli, err := GetDockerClient(ctx)
if err != nil { if err != nil {
return false, err return false, err
@ -27,5 +28,61 @@ func ImageExistsLocally(ctx context.Context, imageName string) (bool, error) {
return false, err 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
} }

View file

@ -23,9 +23,15 @@ func TestImageExistsLocally(t *testing.T) {
// to help make this test reliable and not flaky, we need to have // to help make this test reliable and not flaky, we need to have
// an image that will exist, and onew that won't exist // 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.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 // pull an image
cli, err := client.NewClientWithOpts(client.FromEnv) cli, err := client.NewClientWithOpts(client.FromEnv)
@ -34,13 +40,28 @@ func TestImageExistsLocally(t *testing.T) {
// Chose alpine latest because it's so small // Chose alpine latest because it's so small
// maybe we should build an image instead so that tests aren't reliable on dockerhub // 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) assert.Nil(t, err)
defer reader.Close() defer readerDefault.Close()
_, err = ioutil.ReadAll(reader) _, err = ioutil.ReadAll(readerDefault)
assert.Nil(t, err) assert.Nil(t, err)
exists, err = ImageExistsLocally(ctx, "alpine:latest") imageDefaultArchExists, err := ImageExistsLocally(ctx, "alpine:latest", "linux/amd64")
assert.Nil(t, err) 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)
} }

View file

@ -6,15 +6,17 @@ import (
"strings" "strings"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/nektos/act/pkg/common"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/nektos/act/pkg/common"
) )
// NewDockerPullExecutorInput the input for the NewDockerPullExecutor function // NewDockerPullExecutorInput the input for the NewDockerPullExecutor function
type NewDockerPullExecutorInput struct { type NewDockerPullExecutorInput struct {
Image string Image string
ForcePull bool ForcePull bool
Platform string
} }
// NewDockerPullExecutor function to create a run executor for the container // NewDockerPullExecutor function to create a run executor for the container
@ -29,10 +31,10 @@ func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
pull := input.ForcePull pull := input.ForcePull
if !pull { if !pull {
imageExists, err := ImageExistsLocally(ctx, input.Image) imageExists, err := ImageExistsLocally(ctx, input.Image, input.Platform)
log.Debugf("Image exists? %v", imageExists) log.Debugf("Image exists? %v", imageExists)
if err != nil { 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 { if !imageExists {
@ -45,14 +47,16 @@ func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
} }
imageRef := cleanImage(input.Image) imageRef := cleanImage(input.Image)
logger.Debugf("pulling image '%v'", imageRef) logger.Debugf("pulling image '%v' (%s)", imageRef, input.Platform)
cli, err := GetDockerClient(ctx) cli, err := GetDockerClient(ctx)
if err != nil { if err != nil {
return err 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) _ = logDockerResponse(logger, reader, err != nil)
if err != nil { if err != nil {
return err return err

View file

@ -24,10 +24,13 @@ import (
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy" "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" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/term" "golang.org/x/term"
"github.com/nektos/act/pkg/common"
) )
// NewContainerInput the input for the New function // NewContainerInput the input for the New function
@ -45,6 +48,7 @@ type NewContainerInput struct {
NetworkMode string NetworkMode string
Privileged bool Privileged bool
UsernsMode string UsernsMode string
Platform string
} }
// FileEntry is a file to copy to a container // FileEntry is a file to copy to a container
@ -75,7 +79,7 @@ func NewContainer(input *NewContainerInput) Container {
func (cr *containerReference) Create() common.Executor { func (cr *containerReference) Create() common.Executor {
return common. 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( Then(
common.NewPipelineExecutor( common.NewPipelineExecutor(
cr.connect(), cr.connect(),
@ -86,7 +90,7 @@ func (cr *containerReference) Create() common.Executor {
} }
func (cr *containerReference) Start(attach bool) common.Executor { func (cr *containerReference) Start(attach bool) common.Executor {
return common. 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( Then(
common.NewPipelineExecutor( common.NewPipelineExecutor(
cr.connect(), cr.connect(),
@ -101,6 +105,7 @@ func (cr *containerReference) Pull(forcePull bool) common.Executor {
return NewDockerPullExecutor(NewDockerPullExecutorInput{ return NewDockerPullExecutor(NewDockerPullExecutorInput{
Image: cr.input.Image, Image: cr.input.Image,
ForcePull: forcePull, ForcePull: forcePull,
Platform: cr.input.Platform,
}) })
} }
func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common.Executor { 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{ resp, err := cr.cli.ContainerCreate(ctx, config, &container.HostConfig{
Binds: input.Binds, Binds: input.Binds,
Mounts: mounts, Mounts: mounts,
NetworkMode: container.NetworkMode(input.NetworkMode), NetworkMode: container.NetworkMode(input.NetworkMode),
Privileged: input.Privileged, Privileged: input.Privileged,
UsernsMode: container.UsernsMode(input.UsernsMode), UsernsMode: container.UsernsMode(input.UsernsMode),
}, nil, input.Name) }, nil, &specs.Platform{
Architecture: desiredPlatform[1],
OS: desiredPlatform[0],
}, input.Name)
if err != nil { if err != nil {
return errors.WithStack(err) 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) logger.Debugf("ENV ==> %v", input.Env)
cr.id = resp.ID cr.id = resp.ID

View file

@ -11,11 +11,11 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/nektos/act/pkg/container" log "github.com/sirupsen/logrus"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus"
) )
// RunContext contains info about current job // 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)) 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{ rc.JobContainer = container.NewContainer(&container.NewContainerInput{
Cmd: nil, Cmd: nil,
Entrypoint: []string{"/usr/bin/tail", "-f", "/dev/null"}, Entrypoint: []string{"/usr/bin/tail", "-f", "/dev/null"},
@ -107,6 +111,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
Stderr: logWriter, Stderr: logWriter,
Privileged: rc.Config.Privileged, Privileged: rc.Config.Privileged,
UsernsMode: rc.Config.UsernsMode, UsernsMode: rc.Config.UsernsMode,
Platform: rc.Config.ContainerArchitecture,
}) })
var copyWorkspace bool var copyWorkspace bool

View file

@ -32,6 +32,7 @@ type Config struct {
Platforms map[string]string // list of platforms Platforms map[string]string // list of platforms
Privileged bool // use privileged mode Privileged bool // use privileged mode
UsernsMode string // user namespace to use UsernsMode string // user namespace to use
ContainerArchitecture string // Desired OS/architecture platform for running containers
} }
type runnerImpl struct { type runnerImpl struct {

View file

@ -7,10 +7,10 @@ import (
"testing" "testing"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"github.com/nektos/act/pkg/model"
) )
func TestGraphEvent(t *testing.T) { func TestGraphEvent(t *testing.T) {
@ -37,6 +37,7 @@ type TestJobFileInfo struct {
eventName string eventName string
errorMessage string errorMessage string
platforms map[string]string platforms map[string]string
containerArchitecture string
} }
func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) { func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) {
@ -50,6 +51,7 @@ func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) {
EventName: tjfi.eventName, EventName: tjfi.eventName,
Platforms: tjfi.platforms, Platforms: tjfi.platforms,
ReuseContainers: false, ReuseContainers: false,
ContainerArchitecture: tjfi.containerArchitecture,
} }
runner, err := New(runnerConfig) runner, err := New(runnerConfig)
assert.NilError(t, err, tjfi.workflowPath) assert.NilError(t, err, tjfi.workflowPath)
@ -77,23 +79,42 @@ func TestRunEvent(t *testing.T) {
"ubuntu-latest": "node:12.20.1-buster-slim", "ubuntu-latest": "node:12.20.1-buster-slim",
} }
tables := []TestJobFileInfo{ tables := []TestJobFileInfo{
{"testdata", "basic", "push", "", platforms}, {"testdata", "basic", "push", "", platforms, "linux/amd64"},
{"testdata", "fail", "push", "exit with `FAILURE`: 1", platforms}, {"testdata", "fail", "push", "exit with `FAILURE`: 1", platforms, "linux/amd64"},
{"testdata", "runs-on", "push", "", platforms}, {"testdata", "runs-on", "push", "", platforms, "linux/amd64"},
{"testdata", "job-container", "push", "", platforms}, {"testdata", "job-container", "push", "", platforms, "linux/amd64"},
{"testdata", "job-container-non-root", "push", "", platforms}, {"testdata", "job-container-non-root", "push", "", platforms, "linux/amd64"},
{"testdata", "uses-docker-url", "push", "", platforms}, {"testdata", "uses-docker-url", "push", "", platforms, "linux/amd64"},
{"testdata", "remote-action-docker", "push", "", platforms}, {"testdata", "remote-action-docker", "push", "", platforms, "linux/amd64"},
{"testdata", "remote-action-js", "push", "", platforms}, {"testdata", "remote-action-js", "push", "", platforms, "linux/amd64"},
{"testdata", "local-action-docker-url", "push", "", platforms}, {"testdata", "local-action-docker-url", "push", "", platforms, "linux/amd64"},
{"testdata", "local-action-dockerfile", "push", "", platforms}, {"testdata", "local-action-dockerfile", "push", "", platforms, "linux/amd64"},
{"testdata", "local-action-js", "push", "", platforms}, {"testdata", "local-action-js", "push", "", platforms, "linux/amd64"},
{"testdata", "matrix", "push", "", platforms}, {"testdata", "matrix", "push", "", platforms, "linux/amd64"},
{"testdata", "matrix-include-exclude", "push", "", platforms}, {"testdata", "matrix-include-exclude", "push", "", platforms, "linux/amd64"},
{"testdata", "commands", "push", "", platforms}, {"testdata", "commands", "push", "", platforms, "linux/amd64"},
{"testdata", "workdir", "push", "", platforms}, {"testdata", "workdir", "push", "", platforms, "linux/amd64"},
// {"testdata", "issue-228", "push", "", platforms}, // TODO [igni]: Remove this once everything passes // {"testdata", "issue-228", "push", "", platforms, "linux/amd64"}, // TODO [igni]: Remove this once everything passes
{"testdata", "defaults-run", "push", "", platforms}, {"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) log.SetLevel(log.DebugLevel)

View file

@ -10,8 +10,8 @@ import (
"runtime" "runtime"
"strings" "strings"
log "github.com/sirupsen/logrus"
"github.com/kballard/go-shellquote" "github.com/kballard/go-shellquote"
log "github.com/sirupsen/logrus"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "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)) 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{ stepContainer := container.NewContainer(&container.NewContainerInput{
Cmd: cmd, Cmd: cmd,
Entrypoint: entrypoint, Entrypoint: entrypoint,
@ -235,6 +239,7 @@ func (sc *StepContext) newStepContainer(ctx context.Context, image string, cmd [
Stderr: logWriter, Stderr: logWriter,
Privileged: rc.Config.Privileged, Privileged: rc.Config.Privileged,
UsernsMode: rc.Config.UsernsMode, UsernsMode: rc.Config.UsernsMode,
Platform: rc.Config.ContainerArchitecture,
}) })
return stepContainer 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 = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-"))
image = strings.ToLower(image) image = strings.ToLower(image)
contextDir := filepath.Join(actionDir, actionPath, action.Runs.Main) 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{ prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
ContextDir: contextDir, ContextDir: contextDir,
ImageTag: image, 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"]) cmd, err := shellquote.Split(step.With["args"])

View file

@ -12,10 +12,14 @@ func TestStepContextExecutor(t *testing.T) {
"ubuntu-latest": "node:12.20.1-buster-slim", "ubuntu-latest": "node:12.20.1-buster-slim",
} }
tables := []TestJobFileInfo{ tables := []TestJobFileInfo{
{"testdata", "uses-github-empty", "push", "Expected format {org}/{repo}[/path]@ref", 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}, {"testdata", "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms, "linux/amd64"},
{"testdata", "uses-github-root", "push", "", platforms}, {"testdata", "uses-github-root", "push", "", platforms, "linux/amd64"},
{"testdata", "uses-github-path", "push", "", platforms}, {"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. // These tests are sufficient to only check syntax.
ctx := common.WithDryrun(context.Background(), true) ctx := common.WithDryrun(context.Background(), true)

View file

@ -26,6 +26,7 @@ type Input struct {
defaultBranch string defaultBranch string
privileged bool privileged bool
usernsMode string usernsMode string
containerArchitecture string
} }
func (i *Input) resolve(path string) string { func (i *Input) resolve(path string) string {

View file

@ -6,6 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime"
"strings" "strings"
"github.com/nektos/act/pkg/common" "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().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().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.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/<your machine architecture> [linux/"+runtime.GOARCH+"]")
rootCmd.SetArgs(args()) rootCmd.SetArgs(args())
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
@ -262,6 +264,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
Platforms: input.newPlatforms(), Platforms: input.newPlatforms(),
Privileged: input.privileged, Privileged: input.privileged,
UsernsMode: input.usernsMode, UsernsMode: input.usernsMode,
ContainerArchitecture: input.containerArchitecture,
} }
r, err := runner.New(config) r, err := runner.New(config)
if err != nil { if err != nil {