diff --git a/internal/pkg/cli/deploy/workload.go b/internal/pkg/cli/deploy/workload.go index 8ca10485727..bf757f0ff0c 100644 --- a/internal/pkg/cli/deploy/workload.go +++ b/internal/pkg/cli/deploy/workload.go @@ -593,6 +593,7 @@ func buildArgsPerContainer(name, workspacePath string, img ContainerImageIdentif CacheFrom: buildArgs.CacheFrom, Target: aws.StringValue(buildArgs.Target), Platform: mf.ContainerPlatform(), + Engine: aws.StringValue(buildArgs.Engine), Tags: tags, Labels: labels, } diff --git a/internal/pkg/docker/dockerengine/dockerengine.go b/internal/pkg/docker/dockerengine/dockerengine.go index 0ad7aa6fd6d..b9148c44815 100644 --- a/internal/pkg/docker/dockerengine/dockerengine.go +++ b/internal/pkg/docker/dockerengine/dockerengine.go @@ -90,6 +90,7 @@ type BuildArguments struct { Platform string // Optional. OS/Arch to pass to `docker build`. Args map[string]string // Optional. Build args to pass via `--build-arg` flags. Equivalent to ARG directives in dockerfile. Labels map[string]string // Required. Set metadata for an image. + Engine string // Optional. Currently supported options are "docker" and "podman". Defaults to "docker". } // RunOptions holds the options for running a Docker container. @@ -232,12 +233,22 @@ func (c DockerCmdClient) Exec(ctx context.Context, container string, out io.Writ } // Push pushes the images with the specified tags and ecr repository URI, and returns the image digest on success. -func (c DockerCmdClient) Push(ctx context.Context, uri string, w io.Writer, tags ...string) (digest string, err error) { +func (c DockerCmdClient) Push(ctx context.Context, uri string, engine string, w io.Writer, tags ...string) (digest string, err error) { images := []string{} for _, tag := range tags { images = append(images, imageName(uri, tag)) } var args []string + // Podman image digests are based on the compressed image, so need to be gathered from podman. + var digestFile *os.File + if engine == "podman" { + digestFile, err = os.CreateTemp("", "copilot-digest") + if err != nil { + return "", fmt.Errorf("create temp file for digest: %w", err) + } + defer os.Remove(digestFile.Name()) + args = append(args, "--digestfile", digestFile.Name()) + } if ci, _ := c.lookupEnv("CI"); ci == "true" { args = append(args, "--quiet") } @@ -247,6 +258,14 @@ func (c DockerCmdClient) Push(ctx context.Context, uri string, w io.Writer, tags return "", fmt.Errorf("docker push %s: %w", img, err) } } + if engine == "podman" { + digest, err := os.ReadFile(digestFile.Name()) + if err != nil { + return "", fmt.Errorf("read digest file: %w", err) + } + return string(digest), nil + } + buf := new(strings.Builder) // The container image will have the same digest regardless of the associated tag. // Pick the first tag and get the image's digest. diff --git a/internal/pkg/docker/dockerengine/dockerengine_test.go b/internal/pkg/docker/dockerengine/dockerengine_test.go index 10d6c342379..e5a940e0955 100644 --- a/internal/pkg/docker/dockerengine/dockerengine_test.go +++ b/internal/pkg/docker/dockerengine/dockerengine_test.go @@ -45,6 +45,7 @@ func TestDockerCommand_Build(t *testing.T) { args map[string]string target string cacheFrom []string + engine string envVars map[string]string labels map[string]string setupMocks func(controller *gomock.Controller) @@ -313,7 +314,7 @@ func TestDockerCommand_Push(t *testing.T) { lookupEnv: emptyLookupEnv, } buf := new(strings.Builder) - digest, err := cmd.Push(ctx, "aws_account_id.dkr.ecr.region.amazonaws.com/my-web-app", buf, "latest", "g123bfc") + digest, err := cmd.Push(ctx, "aws_account_id.dkr.ecr.region.amazonaws.com/my-web-app", "docker", buf, "latest", "g123bfc") // THEN require.NoError(t, err) @@ -343,7 +344,7 @@ func TestDockerCommand_Push(t *testing.T) { }, } buf := new(strings.Builder) - digest, err := cmd.Push(context.Background(), "aws_account_id.dkr.ecr.region.amazonaws.com/my-web-app", buf, "latest") + digest, err := cmd.Push(context.Background(), "aws_account_id.dkr.ecr.region.amazonaws.com/my-web-app", "docker", buf, "latest") // THEN require.NoError(t, err) @@ -362,7 +363,7 @@ func TestDockerCommand_Push(t *testing.T) { lookupEnv: emptyLookupEnv, } buf := new(strings.Builder) - _, err := cmd.Push(ctx, "uri", buf, "latest") + _, err := cmd.Push(ctx, "uri", "docker", buf, "latest") // THEN require.EqualError(t, err, "docker push uri:latest: some error") @@ -381,7 +382,7 @@ func TestDockerCommand_Push(t *testing.T) { lookupEnv: emptyLookupEnv, } buf := new(strings.Builder) - _, err := cmd.Push(ctx, "uri", buf, "latest") + _, err := cmd.Push(ctx, "uri", "docker", buf, "latest") // THEN require.EqualError(t, err, "inspect image digest for uri: some error") @@ -406,7 +407,7 @@ func TestDockerCommand_Push(t *testing.T) { lookupEnv: emptyLookupEnv, } buf := new(strings.Builder) - _, err := cmd.Push(ctx, "aws_account_id.dkr.ecr.region.amazonaws.com/my-web-app", buf, "latest", "g123bfc") + _, err := cmd.Push(ctx, "aws_account_id.dkr.ecr.region.amazonaws.com/my-web-app", "docker", buf, "latest", "g123bfc") // THEN require.EqualError(t, err, "parse the digest from the repo digest ''") diff --git a/internal/pkg/manifest/workload.go b/internal/pkg/manifest/workload.go index 28d7d713305..8f1fc93030c 100644 --- a/internal/pkg/manifest/workload.go +++ b/internal/pkg/manifest/workload.go @@ -178,6 +178,7 @@ func (i *ImageLocationOrBuild) BuildConfig(rootDirectory string) *DockerBuildArg Args: i.args(), Target: i.target(), CacheFrom: i.cacheFrom(), + Engine: i.engine(), } } @@ -238,6 +239,14 @@ func (i *ImageLocationOrBuild) cacheFrom() []string { return i.Build.BuildArgs.CacheFrom } +// engine returns the engine to use for building the image if it exists, otherwise "docker" +func (i *ImageLocationOrBuild) engine() *string { + if i.Build.BuildArgs.Engine != nil { + return i.Build.BuildArgs.Engine + } + return aws.String("docker") +} + // ImageOverride holds fields that override Dockerfile image defaults. type ImageOverride struct { EntryPoint EntryPointOverride `yaml:"entrypoint"` @@ -398,6 +407,7 @@ type DockerBuildArgs struct { Args map[string]string `yaml:"args,omitempty"` Target *string `yaml:"target,omitempty"` CacheFrom []string `yaml:"cache_from,omitempty"` + Engine *string `yaml:"engine,omitempty"` } func (b *DockerBuildArgs) isEmpty() bool { diff --git a/internal/pkg/repository/repository.go b/internal/pkg/repository/repository.go index 6dc264a302a..560037d937b 100644 --- a/internal/pkg/repository/repository.go +++ b/internal/pkg/repository/repository.go @@ -18,7 +18,7 @@ import ( type ContainerLoginBuildPusher interface { Build(ctx context.Context, args *dockerengine.BuildArguments, w io.Writer) error Login(uri, username, password string) error - Push(ctx context.Context, uri string, w io.Writer, tags ...string) (digest string, err error) + Push(ctx context.Context, uri string, engine string, w io.Writer, tags ...string) (digest string, err error) IsEcrCredentialHelperEnabled(uri string) bool } @@ -77,10 +77,11 @@ func (r *Repository) BuildAndPush(ctx context.Context, args *dockerengine.BuildA return "", fmt.Errorf("build Dockerfile at %s: %w", args.Dockerfile, err) } - digest, err = r.docker.Push(ctx, args.URI, w, args.Tags...) + digest, err = r.docker.Push(ctx, args.URI, args.Engine, w, args.Tags...) if err != nil { return "", fmt.Errorf("push to repo %s: %w", r.name, err) } + return digest, nil }