From b94577a0acb0aa76e257fb15267faf6c0954700a Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 4 Feb 2020 14:48:16 +0200 Subject: [PATCH 1/2] feat(ecs): ContainerImage.fromDockerImageAsset Allow using an existing `DockerImageAsset` object as a container image in order to enable direct access to `DockerImageAsset`s API such as accessing the ECR repository, the source hash or granting permissions. The reason this could not have been exposed through the normal `fromImageAsset` is that `ContainerImage` can be used multiple times (i.e. be bound to multiple container definitions), so there is no reliable way to allow users to access the asset. Related to #5791 and #5983 --- .../aws-ecr-assets/lib/image-asset.ts | 20 ++-- packages/@aws-cdk/aws-ecr-assets/package.json | 7 +- .../@aws-cdk/aws-ecs/lib/container-image.ts | 22 ++++- .../aws-ecs/lib/images/asset-image.ts | 30 +----- .../aws-ecs/test/test.container-definition.ts | 99 ++++++++++++++++++- 5 files changed, 138 insertions(+), 40 deletions(-) diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts index 6ace07dd1871f..47afea0986af6 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -5,12 +5,10 @@ import * as fs from 'fs'; import * as minimatch from 'minimatch'; import * as path from 'path'; -export interface DockerImageAssetProps extends assets.FingerprintOptions { - /** - * The directory where the Dockerfile is stored - */ - readonly directory: string; - +/** + * Options for DockerImageAsset + */ +export interface DockerImageAssetOptions extends assets.FingerprintOptions { /** * ECR repository name * @@ -51,6 +49,16 @@ export interface DockerImageAssetProps extends assets.FingerprintOptions { readonly file?: string; } +/** + * Props for DockerImageAssets + */ +export interface DockerImageAssetProps extends DockerImageAssetOptions { + /** + * The directory where the Dockerfile is stored + */ + readonly directory: string; +} + /** * An asset that represents a Docker image. * diff --git a/packages/@aws-cdk/aws-ecr-assets/package.json b/packages/@aws-cdk/aws-ecr-assets/package.json index 14c817f4c176c..d16f9dfdaa38c 100644 --- a/packages/@aws-cdk/aws-ecr-assets/package.json +++ b/packages/@aws-cdk/aws-ecr-assets/package.json @@ -100,10 +100,5 @@ "bundledDependencies": [ "minimatch" ], - "stability": "experimental", - "awslint": { - "exclude": [ - "docs-public-apis:@aws-cdk/aws-ecr-assets.DockerImageAssetProps" - ] - } + "stability": "experimental" } diff --git a/packages/@aws-cdk/aws-ecs/lib/container-image.ts b/packages/@aws-cdk/aws-ecs/lib/container-image.ts index d74d5b97130e6..aa9719c02a3a8 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-image.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-image.ts @@ -22,7 +22,10 @@ export abstract class ContainerImage { } /** - * Reference an image that's constructed directly from sources on disk + * Reference an image that's constructed directly from sources on disk. + * + * If you already have a `DockerImageAsset` instance, you can use the + * `ContainerImage.fromDockerImageAsset` method instead. * * @param directory The directory containing the Dockerfile */ @@ -30,6 +33,22 @@ export abstract class ContainerImage { return new AssetImage(directory, props); } + /** + * Use an existing `DockerImageAsset` for this container image. + * + * @param asset The `DockerImageAsset` to use for this container definition. + */ + public static fromDockerImageAsset(asset: DockerImageAsset): ContainerImage { + return { + bind(_scope: cdk.Construct, containerDefinition: ContainerDefinition): ContainerImageConfig { + asset.repository.grantPull(containerDefinition.taskDefinition.obtainExecutionRole()); + return { + imageName: asset.imageUri + }; + } + }; + } + /** * Called when the image is used by a ContainerDefinition */ @@ -51,6 +70,7 @@ export interface ContainerImageConfig { readonly repositoryCredentials?: CfnTaskDefinition.RepositoryCredentialsProperty; } +import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets'; import { AssetImage, AssetImageProps } from './images/asset-image'; import { EcrImage } from './images/ecr'; import { RepositoryImage, RepositoryImageProps } from './images/repository'; diff --git a/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts b/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts index 541a92b77a1b1..0495a57e30d80 100644 --- a/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts +++ b/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts @@ -1,4 +1,4 @@ -import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets'; +import { DockerImageAsset, DockerImageAssetOptions } from '@aws-cdk/aws-ecr-assets'; import * as cdk from '@aws-cdk/core'; import { ContainerDefinition } from '../container-definition'; import { ContainerImage, ContainerImageConfig } from '../container-image'; @@ -6,28 +6,7 @@ import { ContainerImage, ContainerImageConfig } from '../container-image'; /** * The properties for building an AssetImage. */ -export interface AssetImageProps { - /** - * The arguments to pass to the `docker build` command - * - * @default none - */ - readonly buildArgs?: { [key: string]: string }; - - /** - * Docker target to build to - * - * @default none - */ - readonly target?: string; - - /** - * Path to the Dockerfile (relative to the directory). - * - * @default 'Dockerfile' - */ - readonly file?: string; - +export interface AssetImageProps extends DockerImageAssetOptions { } /** @@ -46,10 +25,9 @@ export class AssetImage extends ContainerImage { public bind(scope: cdk.Construct, containerDefinition: ContainerDefinition): ContainerImageConfig { const asset = new DockerImageAsset(scope, 'AssetImage', { directory: this.directory, - buildArgs: this.props.buildArgs, - target: this.props.target, - file: this.props.file, + ...this.props, }); + asset.repository.grantPull(containerDefinition.taskDefinition.obtainExecutionRole()); return { diff --git a/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts b/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts index f143d75e5fa4e..f6743a309c36c 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts @@ -1,8 +1,10 @@ import { expect, haveResource, haveResourceLike, InspectionFailure } from '@aws-cdk/assert'; +import * as ecr_assets from '@aws-cdk/aws-ecr-assets'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as ssm from '@aws-cdk/aws-ssm'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; +import * as path from 'path'; import * as ecs from '../lib'; export = { @@ -1293,5 +1295,100 @@ export = { test.done(); } }, - // render extra hosts test + + 'can use a DockerImageAsset directly for a container image'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + const asset = new ecr_assets.DockerImageAsset(stack, 'MyDockerImage', { + directory: path.join(__dirname, 'demo-image') + }); + + // WHEN + taskDefinition.addContainer('default', { + image: ecs.ContainerImage.fromDockerImageAsset(asset), + memoryLimitMiB: 1024 + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Essential: true, + Image: { + "Fn::Join": [ + "", + [ + { Ref: "AWS::AccountId" }, + ".dkr.ecr.", + { Ref: "AWS::Region" }, + ".", + { Ref: "AWS::URLSuffix" }, + "/aws-cdk/assets:baa2d6eb2a17c75424df631c8c70ff39f2d5f3bee8b9e1a109ee24ca17300540" + ] + ] + }, + Memory: 1024, + Name: "default" + } + ] + })); + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + Effect: "Allow", + Resource: { + "Fn::Join": [ + "", + [ "arn:", { Ref: "AWS::Partition" }, ":ecr:", { Ref: "AWS::Region" }, ":", { Ref: "AWS::AccountId" }, ":repository/aws-cdk/assets" ] + ] + } + }, + { + Action: "ecr:GetAuthorizationToken", + Effect: "Allow", + Resource: "*" + } + ], + Version: "2012-10-17" + } + })); + test.done(); + }, + + 'docker image asset options can be used when using container image'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + taskDefinition.addContainer('default', { + memoryLimitMiB: 1024, + image: ecs.ContainerImage.fromAsset(path.join(__dirname, 'demo-image'), { + file: 'index.py', // just because it's there already + target: 'build-target' + }) + }); + + // THEN + const asm = app.synth(); + test.deepEqual(asm.getStackArtifact(stack.artifactId).assets[0], { + repositoryName: 'aws-cdk/assets', + imageTag: 'f9014d1df7c8f5a5e7abaf18eb5bc895e82f8b06eeed6f75a40cf1bc2a78955a', + id: 'f9014d1df7c8f5a5e7abaf18eb5bc895e82f8b06eeed6f75a40cf1bc2a78955a', + packaging: 'container-image', + path: 'asset.f9014d1df7c8f5a5e7abaf18eb5bc895e82f8b06eeed6f75a40cf1bc2a78955a', + sourceHash: 'f9014d1df7c8f5a5e7abaf18eb5bc895e82f8b06eeed6f75a40cf1bc2a78955a', + target: 'build-target', + file: 'index.py' + }); + test.done(); + } }; From 425d822dd4b0b2f11436849b761fd34f46c84993 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 4 Feb 2020 15:59:38 +0200 Subject: [PATCH 2/2] update readme --- packages/@aws-cdk/aws-ecs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 4137b2ac4c260..44386b2bcbaf6 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -227,6 +227,8 @@ obtained from either DockerHub or from ECR repositories, or built directly from to start. If no tag 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 + `@aws-cdk/aws-ecr-assets.DockerImageAsset` as a container image. ### Environment variables