From 380774edd5f8c42294651ead3541eebcf029251c Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Thu, 14 Apr 2022 09:26:58 +1000 Subject: [PATCH] feat(aws-ecr): make it easy to reference image tag or digest, use everywhere (#19799) This generalises the fix of #13299 by creating a `IRepository.repositoryUriForTagOrDigest` function that detects whether something looks like a digest (starts with `sha256:`) or is a tag, and formats the URI with `@` or `:` as appropriate. This function is then used in most places that previously called `repositoryUriForTag`, meaning they can use image digests in addition to tags. The one remain real call is in aws-ecs's `TagParameterContainerImage`. This includes aws-lambda's `EcrImageCode`, and thus closes #15333. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `cdk-integ` to deploy the infrastructure and generate the snapshot (i.e. `cdk-integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-apprunner/README.md | 2 +- .../@aws-cdk/aws-apprunner/lib/service.ts | 10 ++++++- .../lib/linux-arm-build-image.ts | 6 ++-- .../@aws-cdk/aws-codebuild/lib/project.ts | 12 ++++---- packages/@aws-cdk/aws-ecr/lib/repository.ts | 28 ++++++++++++++++++- packages/@aws-cdk/aws-ecs/README.md | 4 +-- packages/@aws-cdk/aws-ecs/lib/images/ecr.ts | 6 +--- packages/@aws-cdk/aws-lambda/README.md | 2 +- packages/@aws-cdk/aws-lambda/lib/code.ts | 9 +++++- .../@aws-cdk/aws-lambda/test/code.test.ts | 25 ++++++++++++++++- .../lib/sagemaker/base-types.ts | 6 ++-- 11 files changed, 85 insertions(+), 25 deletions(-) diff --git a/packages/@aws-cdk/aws-apprunner/README.md b/packages/@aws-cdk/aws-apprunner/README.md index 658c46fc9ecb6..0af5e1bf09f5a 100644 --- a/packages/@aws-cdk/aws-apprunner/README.md +++ b/packages/@aws-cdk/aws-apprunner/README.md @@ -65,7 +65,7 @@ new apprunner.Service(this, 'Service', { source: apprunner.Source.fromEcr({ imageConfiguration: { port: 80 }, repository: ecr.Repository.fromRepositoryName(this, 'NginxRepository', 'nginx'), - tag: 'latest', + tagOrDigest: 'latest', }), }); ``` diff --git a/packages/@aws-cdk/aws-apprunner/lib/service.ts b/packages/@aws-cdk/aws-apprunner/lib/service.ts index e2551eb37732c..90309845f3fdf 100644 --- a/packages/@aws-cdk/aws-apprunner/lib/service.ts +++ b/packages/@aws-cdk/aws-apprunner/lib/service.ts @@ -219,8 +219,14 @@ export interface EcrProps { /** * Image tag. * @default - 'latest' + * @deprecated use `tagOrDigest` */ readonly tag?: string; + /** + * Image tag or digest (digests must start with `sha256:`). + * @default - 'latest' + */ + readonly tagOrDigest?: string; } /** @@ -313,7 +319,9 @@ export class EcrSource extends Source { return { imageRepository: { imageConfiguration: this.props.imageConfiguration, - imageIdentifier: this.props.repository.repositoryUriForTag(this.props.tag || 'latest'), + imageIdentifier: this.props.repository.repositoryUriForTagOrDigest( + this.props.tagOrDigest || this.props.tag || 'latest', + ), imageRepositoryType: ImageRepositoryType.ECR, }, ecrRepository: this.props.repository, diff --git a/packages/@aws-cdk/aws-codebuild/lib/linux-arm-build-image.ts b/packages/@aws-cdk/aws-codebuild/lib/linux-arm-build-image.ts index 12a7d2e54daf6..14115ee22ad6b 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/linux-arm-build-image.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/linux-arm-build-image.ts @@ -42,12 +42,12 @@ export class LinuxArmBuildImage implements IBuildImage { * @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-ecr.html * * @param repository The ECR repository - * @param tag Image tag (default "latest") + * @param tagOrDigest Image tag or digest (default "latest", digests must start with `sha256:`) * @returns An aarch64 Linux build image from an ECR repository. */ - public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest'): IBuildImage { + public static fromEcrRepository(repository: ecr.IRepository, tagOrDigest: string = 'latest'): IBuildImage { return new LinuxArmBuildImage({ - imageId: repository.repositoryUriForTag(tag), + imageId: repository.repositoryUriForTagOrDigest(tagOrDigest), imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, repository, }); diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index b026f9e842007..d39e30bd45c61 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -1790,11 +1790,11 @@ export class LinuxBuildImage implements IBuildImage { * @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-ecr.html * * @param repository The ECR repository - * @param tag Image tag (default "latest") + * @param tagOrDigest Image tag or digest (default "latest", digests must start with `sha256:`) */ - public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest'): IBuildImage { + public static fromEcrRepository(repository: ecr.IRepository, tagOrDigest: string = 'latest'): IBuildImage { return new LinuxBuildImage({ - imageId: repository.repositoryUriForTag(tag), + imageId: repository.repositoryUriForTagOrDigest(tagOrDigest), imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, repository, }); @@ -1951,15 +1951,15 @@ export class WindowsBuildImage implements IBuildImage { * @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-ecr.html * * @param repository The ECR repository - * @param tag Image tag (default "latest") + * @param tagOrDigest Image tag or digest (default "latest", digests must start with `sha256:`) */ public static fromEcrRepository( repository: ecr.IRepository, - tag: string = 'latest', + tagOrDigest: string = 'latest', imageType: WindowsImageType = WindowsImageType.STANDARD): IBuildImage { return new WindowsBuildImage({ - imageId: repository.repositoryUriForTag(tag), + imageId: repository.repositoryUriForTagOrDigest(tagOrDigest), imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, imageType, repository, diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index f73c8990dd95f..12098e4e2e12b 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -42,7 +42,7 @@ export interface IRepository extends IResource { repositoryUriForTag(tag?: string): string; /** - * Returns the URI of the repository for a certain tag. Can be used in `docker push/pull`. + * Returns the URI of the repository for a certain digest. Can be used in `docker push/pull`. * * ACCOUNT.dkr.ecr.REGION.amazonaws.com/REPOSITORY[@DIGEST] * @@ -50,6 +50,16 @@ export interface IRepository extends IResource { */ repositoryUriForDigest(digest?: string): string; + /** + * Returns the URI of the repository for a certain tag or digest, inferring based on the syntax of the tag. Can be used in `docker push/pull`. + * + * ACCOUNT.dkr.ecr.REGION.amazonaws.com/REPOSITORY[:TAG] + * ACCOUNT.dkr.ecr.REGION.amazonaws.com/REPOSITORY[@DIGEST] + * + * @param tagOrDigest Image tag or digest to use (tools usually default to the image with the "latest" tag if omitted) + */ + repositoryUriForTagOrDigest(tagOrDigest?: string): string; + /** * Add a policy statement to the repository's resource policy */ @@ -162,6 +172,22 @@ export abstract class RepositoryBase extends Resource implements IRepository { return this.repositoryUriWithSuffix(digestSuffix); } + /** + * Returns the URL of the repository. Can be used in `docker push/pull`. + * + * ACCOUNT.dkr.ecr.REGION.amazonaws.com/REPOSITORY[:TAG] + * ACCOUNT.dkr.ecr.REGION.amazonaws.com/REPOSITORY[@DIGEST] + * + * @param tagOrDigest Optional image tag or digest (digests must start with `sha256:`) + */ + public repositoryUriForTagOrDigest(tagOrDigest?: string): string { + if (tagOrDigest?.startsWith('sha256:')) { + return this.repositoryUriForDigest(tagOrDigest); + } else { + return this.repositoryUriForTag(tagOrDigest); + } + } + /** * Returns the repository URI, with an appended suffix, if provided. * @param suffix An image tag or an image digest. diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index d0d091eda612e..901989b45c448 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -393,8 +393,8 @@ obtained from either DockerHub or from ECR repositories, built directly from a l - `ecs.ContainerImage.fromRegistry(imageName)`: use a public image. - `ecs.ContainerImage.fromRegistry(imageName, { credentials: mySecret })`: use a private image that requires credentials. -- `ecs.ContainerImage.fromEcrRepository(repo, tag)`: use the given ECR repository as the image - to start. If no tag is provided, "latest" is assumed. +- `ecs.ContainerImage.fromEcrRepository(repo, tagOrDigest)`: use the given ECR repository as the image + to start. If no tag or digest is provided, "latest" is assumed. - `ecs.ContainerImage.fromAsset('./image')`: build and upload an image directly from a `Dockerfile` in your source directory. - `ecs.ContainerImage.fromDockerImageAsset(asset)`: uses an existing diff --git a/packages/@aws-cdk/aws-ecs/lib/images/ecr.ts b/packages/@aws-cdk/aws-ecs/lib/images/ecr.ts index b9786f13e2816..eef0396aa3b4e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/images/ecr.ts +++ b/packages/@aws-cdk/aws-ecs/lib/images/ecr.ts @@ -25,11 +25,7 @@ export class EcrImage extends ContainerImage { constructor(private readonly repository: ecr.IRepository, private readonly tagOrDigest: string) { super(); - if (tagOrDigest?.startsWith('sha256:')) { - this.imageName = this.repository.repositoryUriForDigest(this.tagOrDigest); - } else { - this.imageName = this.repository.repositoryUriForTag(this.tagOrDigest); - } + this.imageName = this.repository.repositoryUriForTagOrDigest(this.tagOrDigest); } public bind(_scope: CoreConstruct, containerDefinition: ContainerDefinition): ContainerImageConfig { diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 1738f905855f4..573d469bfccd6 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -79,7 +79,7 @@ new lambda.DockerImageFunction(this, 'ECRFunction', { ``` The props for these docker image resources allow overriding the image's `CMD`, `ENTRYPOINT`, and `WORKDIR` -configurations. See their docs for more information. +configurations as well as choosing a specific tag or digest. See their docs for more information. ## Execution Role diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index f51e91de9bfb7..40bbd218fd780 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -454,8 +454,15 @@ export interface EcrImageCodeProps { /** * The image tag to use when pulling the image from ECR. * @default 'latest' + * @deprecated use `tagOrDigest` */ readonly tag?: string; + + /** + * The image tag or digest to use when pulling the image from ECR (digests must start with `sha256:`). + * @default 'latest' + */ + readonly tagOrDigest?: string; } /** @@ -473,7 +480,7 @@ export class EcrImageCode extends Code { return { image: { - imageUri: this.repository.repositoryUriForTag(this.props?.tag ?? 'latest'), + imageUri: this.repository.repositoryUriForTagOrDigest(this.props?.tagOrDigest ?? this.props?.tag ?? 'latest'), cmd: this.props.cmd, entrypoint: this.props.entrypoint, workingDirectory: this.props.workingDirectory, diff --git a/packages/@aws-cdk/aws-lambda/test/code.test.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts index 9bd03c3082ae8..3c0d31f6931e9 100644 --- a/packages/@aws-cdk/aws-lambda/test/code.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -225,7 +225,7 @@ describe('code', () => { code: lambda.Code.fromEcrImage(repo, { cmd: ['cmd', 'param1'], entrypoint: ['entrypoint', 'param2'], - tag: 'mytag', + tagOrDigest: 'mytag', workingDirectory: '/some/path', }), handler: lambda.Handler.FROM_IMAGE, @@ -245,6 +245,29 @@ describe('code', () => { }); }); + test('digests are interpreted correctly', () => { + // given + const stack = new cdk.Stack(); + const repo = new ecr.Repository(stack, 'Repo'); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromEcrImage(repo, { + tagOrDigest: 'sha256:afc607424cc02c92d4d6af5184a4fef46a69548e465a320808c6ff358b6a3a8d', + }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + // then + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { + Code: { + ImageUri: stack.resolve(repo.repositoryUriForDigest('sha256:afc607424cc02c92d4d6af5184a4fef46a69548e465a320808c6ff358b6a3a8d')), + }, + ImageConfig: Match.absent(), + }); + }); + test('permission grants', () => { // given const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts index e11a04c111856..460e0f53506e8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts @@ -353,10 +353,10 @@ export abstract class DockerImage { * Reference a Docker image stored in an ECR repository. * * @param repository the ECR repository where the image is hosted. - * @param tag an optional `tag` + * @param tagOrDigest an optional tag or digest (digests must start with `sha256:`) */ - public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest'): DockerImage { - return new StandardDockerImage({ repository, imageUri: repository.repositoryUriForTag(tag) }); + public static fromEcrRepository(repository: ecr.IRepository, tagOrDigest: string = 'latest'): DockerImage { + return new StandardDockerImage({ repository, imageUri: repository.repositoryUriForTagOrDigest(tagOrDigest) }); } /**