From 318b403829599f3702984b90cd99b85237d5846f Mon Sep 17 00:00:00 2001 From: Martin Lee Date: Thu, 3 Aug 2023 11:17:33 +0100 Subject: [PATCH] Add support for podman image digests --- internal/pkg/cli/deploy/workload.go | 1 + .../pkg/docker/dockerengine/dockerengine.go | 21 ++++++++++++++++++- .../docker/dockerengine/dockerengine_test.go | 11 +++++----- internal/pkg/manifest/workload.go | 10 +++++++++ internal/pkg/repository/repository.go | 5 +++-- 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/internal/pkg/cli/deploy/workload.go b/internal/pkg/cli/deploy/workload.go index 1a243581cf8..2e3b1d467ec 100644 --- a/internal/pkg/cli/deploy/workload.go +++ b/internal/pkg/cli/deploy/workload.go @@ -512,6 +512,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 1e33d02c714..915ea5dc8c1 100644 --- a/internal/pkg/docker/dockerengine/dockerengine.go +++ b/internal/pkg/docker/dockerengine/dockerengine.go @@ -69,6 +69,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". } // GenerateDockerBuildArgs returns command line arguments to be passed to the Docker build command based on the provided BuildArguments. @@ -170,12 +171,22 @@ func (c DockerCmdClient) Login(uri, username, password string) error { } // 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") } @@ -185,6 +196,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 032d93e8e3d..4a7eee0dd28 100644 --- a/internal/pkg/docker/dockerengine/dockerengine_test.go +++ b/internal/pkg/docker/dockerengine/dockerengine_test.go @@ -42,6 +42,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) @@ -302,7 +303,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) @@ -332,7 +333,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) @@ -351,7 +352,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") @@ -370,7 +371,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") @@ -395,7 +396,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 3ea1134057a..bddbf371544 100644 --- a/internal/pkg/manifest/workload.go +++ b/internal/pkg/manifest/workload.go @@ -195,6 +195,7 @@ func (i *ImageLocationOrBuild) BuildConfig(rootDirectory string) *DockerBuildArg Args: i.args(), Target: i.target(), CacheFrom: i.cacheFrom(), + Engine: i.engine(), } } @@ -237,6 +238,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"` @@ -397,6 +406,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 08b354703f7..7b3fbc6261b 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 } @@ -68,10 +68,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 }