From 11ed69186e80f3667e78f36f94e126a0fb8e9230 Mon Sep 17 00:00:00 2001 From: allisaurus <34254888+allisaurus@users.noreply.github.com> Date: Wed, 13 Mar 2019 11:08:27 -0700 Subject: [PATCH] feat(ecs): support private registry authentication (#1737) Non-ECR registries now accept a secretsmanager Secret with a username and password to use for registry authentication. Fixes #1698. BREAKING CHANGE: `ContainerImage.fromDockerHub` has been renamed to `ContainerImage.fromRegistry`. --- design/aws-ecs-priv-registry-support.md | 125 ++++++++++++++++++ packages/@aws-cdk/aws-ecs/README.md | 10 +- .../aws-ecs/lib/container-definition.ts | 2 +- .../@aws-cdk/aws-ecs/lib/container-image.ts | 15 ++- .../aws-ecs/lib/images/asset-image.ts | 6 + .../@aws-cdk/aws-ecs/lib/images/dockerhub.ts | 15 --- packages/@aws-cdk/aws-ecs/lib/images/ecr.ts | 5 + .../@aws-cdk/aws-ecs/lib/images/repository.ts | 39 ++++++ packages/@aws-cdk/aws-ecs/lib/index.ts | 2 +- .../load-balanced-fargate-service-applet.ts | 2 +- packages/@aws-cdk/aws-ecs/package.json | 2 + .../aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts | 2 +- .../aws-ecs/test/ec2/integ.lb-bridge-nw.ts | 2 +- .../test/ec2/test.ec2-event-rule-target.ts | 2 +- .../aws-ecs/test/ec2/test.ec2-service.ts | 32 ++--- .../test/ec2/test.ec2-task-definition.ts | 12 +- .../@aws-cdk/aws-ecs/test/fargate/integ.l3.ts | 2 +- .../test/fargate/integ.lb-awsvpc-nw.ts | 2 +- .../test/fargate/test.fargate-service.ts | 10 +- .../aws-ecs/test/test.container-definition.ts | 78 ++++++++--- packages/@aws-cdk/aws-ecs/test/test.l3s.ts | 10 +- .../test.load-balanced-fargate-service.ts | 4 +- packages/decdk/examples/ecs.json | 2 +- tools/cdk-build-tools/package-lock.json | 2 +- 24 files changed, 298 insertions(+), 85 deletions(-) create mode 100644 design/aws-ecs-priv-registry-support.md delete mode 100644 packages/@aws-cdk/aws-ecs/lib/images/dockerhub.ts create mode 100644 packages/@aws-cdk/aws-ecs/lib/images/repository.ts diff --git a/design/aws-ecs-priv-registry-support.md b/design/aws-ecs-priv-registry-support.md new file mode 100644 index 0000000000000..c49b423bdc57d --- /dev/null +++ b/design/aws-ecs-priv-registry-support.md @@ -0,0 +1,125 @@ +# AWS ECS - Support for Private Registry Authentication + +To address issue [#1698](https://github.com/awslabs/aws-cdk/issues/1698), the ECS construct library should provide a way for customers to specify [`repositoryCredentials`](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html#ECS-Type-ContainerDefinition-repositoryCredentials) on their container. + +Minimally, this would mean adding a new string field on `ContainerDefinition`, however this doesn't provide any added value in terms of logical grouping or resource creation. We can instead modify the existing ECS CDK construct [`ContainerImage`](https://github.com/awslabs/aws-cdk/blob/master/packages/%40aws-cdk/aws-ecs/lib/container-image.ts) so that repository credentials are specified along with the image they're meant to access. + +## General approach + +The [`ecs.ContainerImage`](https://github.com/awslabs/aws-cdk/blob/master/packages/%40aws-cdk/aws-ecs/lib/container-image.ts) class already includes constructs for 3 types of images: + +* DockerHubImage +* EcrImage +* AssetImage + +DockerHub images are assumed public, however DockerHub also provides private repositories and there's currently no way to specify credentials for them in the CDK. + +There's also no explicit way to specify images hosted outside of DockerHub, AWS, or your local machine. Customers hosting their own registries or using another registry host, like Quay.io or JFrog Artifactory, would need to be able to specify both the image URI and the registry credentials in order to pull their images down for ECS tasks. + +Fundamentally, specifying images hosted in DockerHub or elsewhere works the same, because when passed an image URI vs. a plain (or namespaced) image name + tag, the Docker daemon does the right thing and tries to pull the image from the specified registery. + +Therefore, we should rename the existing `DockerHubImage` type be more generic and add the ability to optionally specify credentials. + + +## Code changes + +Given the above, we should make the following changes to support private registry authentication: + +1. Define `RepositoryCredentials` interface & class, add to `IContainerImage` +2. Rename `DockerHubImage` construct to be more generic, optionally accept and set `RepositoryCreds` + + +# Part 1: How to define registry credentials + +For extensibility, we can define a new `IRepositoryCreds` interface to house the AWS Secrets Manager secret with the creds and a new `RepositoryCreds` class which satisfies it using specific constructs (e.g., "fromSecret"). + +```ts +export interface IRepositoryCreds { + readonly secret: secretsManager.Secret; +} + +export class RepositoryCreds { + public readonly secret: secretsManager.Secret; + + public static fromSecret(secret: secretsManager.Secret) { + this.secret = secret; + } + + public bind(containerDefinition: ContainerDefinition): void { + // grant the execution role read access so the secret can be read prior to image pull + this.secret.grantRead(containerDefinition.taskDefinition.obtainExecutionRole()); + } +} +``` + +The `IContainerImage` interface will be updated to include an optional `repositoryCredentials` property: +```ts +export interface IContainerImage { + // ... + readonly imageName: string; + + /** + * NEW: The credentials required to access the image + */ + readonly repositoryCredentials?: IRepositoryCreds; + + // ... + bind(containerDefinition: ContainerDefinition): void; +} +``` + + +# Part 2: Rename `DockerHubImage` class to `WebHostedImage`, add optional creds + +The `DockerHubImage` construct will be renamed to `WebHostedImage`, and augmented to take in optional "credentials" via keyword props: +```ts +// define props +export interface WebHostedImageProps { + credentials: RepositoryCreds; +} + +// "DockerHubImage" class --> "WebHostedImage" +export class WebHostedImage implements IContainerImage { + public readonly imageName: string; + public readonly credentials: IRepositoryCreds; + + // add credentials to constructor + constructor(imageName: string, props: WebHostedImageProps) { + this.imageName = imageName + this.credentials = props.credentials + } + + public bind(_containerDefinition: ContainerDefinition): void { + // bind repositoryCredentials to ContainerDefinition + this.repositoryCredentials.bind(); + } +} +``` + +We will also update the API on `ContainerImage` to match: +```ts +export class ContainerImage { + //... + public static fromInternet(imageName: string, props: WebHostedImageProps) { + return new WebHostedImage(imageName, props); + } + // ... + +} +``` + +Example use: +```ts +const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + +const secret = secretsManager.Secret.import(stack, 'myRepoSecret', { + secretArn: 'arn:aws:secretsmanager:.....' +}) + +taskDefinition.AddContainer('myPrivateContainer', { + image: ecs.ContainerImage.fromInternet('userx/test', { + credentials: ecs.RepositoryCreds.fromSecret(secret) + }); +}); + +``` \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 2e1a42a32a40a..81ada6f31abb1 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -30,7 +30,7 @@ cluster.addDefaultAutoScalingGroupCapacity('Capacity', { const ecsService = new ecs.LoadBalancedEc2Service(this, 'Service', { cluster, memoryLimitMiB: 512, - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), }); ``` @@ -134,7 +134,7 @@ To add containers to a task definition, call `addContainer()`: ```ts const container = fargateTaskDefinition.addContainer("WebContainer", { // Use an image from DockerHub - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), // ... other options here ... }); ``` @@ -148,7 +148,7 @@ const ec2TaskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef', { const container = ec2TaskDefinition.addContainer("WebContainer", { // Use an image from DockerHub - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 1024 // ... other options here ... }); @@ -183,8 +183,8 @@ const taskDefinition = new ecs.TaskDefinition(this, 'TaskDef', { Images supply the software that runs inside the container. Images can be obtained from either DockerHub or from ECR repositories, or built directly from a local Dockerfile. -* `ecs.ContainerImage.fromDockerHub(imageName)`: use a publicly available image from - DockerHub. +* `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.fromAsset(this, 'Image', { directory: './image' })`: build and upload an diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index 7789e02f87de6..9508737da1843 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -379,7 +379,7 @@ export class ContainerDefinition extends cdk.Construct { portMappings: this.portMappings.map(renderPortMapping), privileged: this.props.privileged, readonlyRootFilesystem: this.props.readonlyRootFilesystem, - repositoryCredentials: undefined, // FIXME + repositoryCredentials: this.props.image.toRepositoryCredentialsJson(), ulimits: this.ulimits.map(renderUlimit), user: this.props.user, volumesFrom: this.volumesFrom.map(renderVolumeFrom), diff --git a/packages/@aws-cdk/aws-ecs/lib/container-image.ts b/packages/@aws-cdk/aws-ecs/lib/container-image.ts index efdb3f07a3f65..9b7b9e11b02bc 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-image.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-image.ts @@ -1,17 +1,17 @@ import ecr = require('@aws-cdk/aws-ecr'); import cdk = require('@aws-cdk/cdk'); - import { ContainerDefinition } from './container-definition'; +import { CfnTaskDefinition } from './ecs.generated'; /** * Constructs for types of container images */ export abstract class ContainerImage { /** - * Reference an image on DockerHub + * Reference an image on DockerHub or another online registry */ - public static fromDockerHub(name: string) { - return new DockerHubImage(name); + public static fromRegistry(name: string, props: RepositoryImageProps = {}) { + return new RepositoryImage(name, props); } /** @@ -37,8 +37,13 @@ export abstract class ContainerImage { * Called when the image is used by a ContainerDefinition */ public abstract bind(containerDefinition: ContainerDefinition): void; + + /** + * Render the Repository credentials to the CloudFormation object + */ + public abstract toRepositoryCredentialsJson(): CfnTaskDefinition.RepositoryCredentialsProperty | undefined; } import { AssetImage, AssetImageProps } from './images/asset-image'; -import { DockerHubImage } from './images/dockerhub'; import { EcrImage } from './images/ecr'; +import { RepositoryImage, RepositoryImageProps } from './images/repository'; \ No newline at end of file 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 03ed2eb925914..003fba3222acd 100644 --- a/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts +++ b/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts @@ -2,6 +2,7 @@ import { DockerImageAsset } from '@aws-cdk/assets-docker'; import cdk = require('@aws-cdk/cdk'); import { ContainerDefinition } from '../container-definition'; import { ContainerImage } from '../container-image'; +import { CfnTaskDefinition } from '../ecs.generated'; export interface AssetImageProps { /** @@ -15,6 +16,7 @@ export interface AssetImageProps { */ export class AssetImage extends ContainerImage { private readonly asset: DockerImageAsset; + constructor(scope: cdk.Construct, id: string, props: AssetImageProps) { super(); this.asset = new DockerImageAsset(scope, id, { directory: props.directory }); @@ -24,6 +26,10 @@ export class AssetImage extends ContainerImage { this.asset.repository.grantPull(containerDefinition.taskDefinition.obtainExecutionRole()); } + public toRepositoryCredentialsJson(): CfnTaskDefinition.RepositoryCredentialsProperty | undefined { + return undefined; + } + public get imageName() { return this.asset.imageUri; } diff --git a/packages/@aws-cdk/aws-ecs/lib/images/dockerhub.ts b/packages/@aws-cdk/aws-ecs/lib/images/dockerhub.ts deleted file mode 100644 index 4157807e19504..0000000000000 --- a/packages/@aws-cdk/aws-ecs/lib/images/dockerhub.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ContainerDefinition } from "../container-definition"; -import { ContainerImage } from "../container-image"; - -/** - * A DockerHub image - */ -export class DockerHubImage extends ContainerImage { - constructor(public readonly imageName: string) { - super(); - } - - public bind(_containerDefinition: ContainerDefinition): void { - // Nothing to do - } -} diff --git a/packages/@aws-cdk/aws-ecs/lib/images/ecr.ts b/packages/@aws-cdk/aws-ecs/lib/images/ecr.ts index d28dc2dca05e5..0d2741d577d6b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/images/ecr.ts +++ b/packages/@aws-cdk/aws-ecs/lib/images/ecr.ts @@ -1,6 +1,7 @@ import ecr = require('@aws-cdk/aws-ecr'); import { ContainerDefinition } from '../container-definition'; import { ContainerImage } from '../container-image'; +import { CfnTaskDefinition } from '../ecs.generated'; /** * An image from an ECR repository @@ -18,4 +19,8 @@ export class EcrImage extends ContainerImage { public bind(containerDefinition: ContainerDefinition): void { this.repository.grantPull(containerDefinition.taskDefinition.obtainExecutionRole()); } + + public toRepositoryCredentialsJson(): CfnTaskDefinition.RepositoryCredentialsProperty | undefined { + return undefined; + } } diff --git a/packages/@aws-cdk/aws-ecs/lib/images/repository.ts b/packages/@aws-cdk/aws-ecs/lib/images/repository.ts new file mode 100644 index 0000000000000..4b21cc503d704 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/images/repository.ts @@ -0,0 +1,39 @@ +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); +import { ContainerDefinition } from "../container-definition"; +import { ContainerImage } from "../container-image"; +import { CfnTaskDefinition } from '../ecs.generated'; + +export interface RepositoryImageProps { + /** + * Optional secret that houses credentials for the image registry + */ + credentials?: secretsmanager.ISecret; +} + +/** + * A container image hosted on DockerHub or another online registry + */ +export class RepositoryImage extends ContainerImage { + public readonly imageName: string; + + private credentialsSecret?: secretsmanager.ISecret; + + constructor(imageName: string, props: RepositoryImageProps = {}) { + super(); + this.imageName = imageName; + this.credentialsSecret = props.credentials; + } + + public bind(containerDefinition: ContainerDefinition): void { + if (this.credentialsSecret) { + this.credentialsSecret.grantRead(containerDefinition.taskDefinition.obtainExecutionRole()); + } + } + + public toRepositoryCredentialsJson(): CfnTaskDefinition.RepositoryCredentialsProperty | undefined { + if (!this.credentialsSecret) { return undefined; } + return { + credentialsParameter: this.credentialsSecret.secretArn + }; + } +} diff --git a/packages/@aws-cdk/aws-ecs/lib/index.ts b/packages/@aws-cdk/aws-ecs/lib/index.ts index c1b0a0f6c98f9..2e575685d7bff 100644 --- a/packages/@aws-cdk/aws-ecs/lib/index.ts +++ b/packages/@aws-cdk/aws-ecs/lib/index.ts @@ -21,7 +21,7 @@ export * from './load-balanced-ecs-service'; export * from './load-balanced-fargate-service-applet'; export * from './images/asset-image'; -export * from './images/dockerhub'; +export * from './images/repository'; export * from './images/ecr'; export * from './log-drivers/aws-log-driver'; diff --git a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts index 17a93d266e823..374d546a78f68 100644 --- a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts +++ b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts @@ -132,7 +132,7 @@ export class LoadBalancedFargateServiceApplet extends cdk.Stack { memoryMiB: props.memoryMiB, publicLoadBalancer: props.publicLoadBalancer, publicTasks: props.publicTasks, - image: ContainerImage.fromDockerHub(props.image), + image: ContainerImage.fromRegistry(props.image), desiredCount: props.desiredCount, environment: props.environment, certificate, diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index b32dfd8e06f27..370f76cfadeb7 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -79,6 +79,7 @@ "@aws-cdk/aws-logs": "^0.25.3", "@aws-cdk/aws-route53": "^0.25.3", "@aws-cdk/aws-sns": "^0.25.3", + "@aws-cdk/aws-secretsmanager": "^0.25.3", "@aws-cdk/cdk": "^0.25.3", "@aws-cdk/cx-api": "^0.25.3" }, @@ -97,6 +98,7 @@ "@aws-cdk/aws-iam": "^0.25.3", "@aws-cdk/aws-logs": "^0.25.3", "@aws-cdk/aws-route53": "^0.25.3", + "@aws-cdk/aws-secretsmanager": "^0.25.3", "@aws-cdk/cdk": "^0.25.3" }, "engines": { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts index 9fe198e7ab1ad..d6617c5a923fc 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts @@ -19,7 +19,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef', { }); const container = taskDefinition.addContainer('web', { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 256, }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.ts index 830f0d18efdc3..93532431269b2 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.ts @@ -21,7 +21,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef', { }); const container = taskDefinition.addContainer('web', { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 256, }); container.addPortMappings({ diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-event-rule-target.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-event-rule-target.ts index 344e18f8407fb..f706a7a497735 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-event-rule-target.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-event-rule-target.ts @@ -17,7 +17,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromDockerHub('henk'), + image: ecs.ContainerImage.fromRegistry('henk'), memoryLimitMiB: 256 }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts index 3e400fc82b971..3712676bbcb36 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts @@ -17,7 +17,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -57,7 +57,7 @@ export = { cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('BaseContainer', { - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), memoryReservationMiB: 10, }); @@ -82,7 +82,7 @@ export = { cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('BaseContainer', { - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), memoryReservationMiB: 10, }); @@ -128,7 +128,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -158,7 +158,7 @@ export = { }); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -190,7 +190,7 @@ export = { }); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -241,7 +241,7 @@ export = { }); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -267,7 +267,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -296,7 +296,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -327,7 +327,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -358,7 +358,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -385,7 +385,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -415,7 +415,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -442,7 +442,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -473,7 +473,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -501,7 +501,7 @@ export = { cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.Host }); const container = taskDefinition.addContainer('web', { - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), memoryLimitMiB: 1024, }); container.addPortMappings({ containerPort: 808 }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts index e0c54a189e212..3ded27b87c6e4 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts @@ -48,7 +48,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); const container = taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 // add validation? }); @@ -104,7 +104,7 @@ export = { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); const container = taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -154,7 +154,7 @@ export = { }); const container = taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -200,7 +200,7 @@ export = { taskDefinition.addContainer("web", { memoryLimitMiB: 1024, - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample") + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample") }); // THEN @@ -226,7 +226,7 @@ export = { }); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); @@ -254,7 +254,7 @@ export = { }); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 512 }); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.l3.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.l3.ts index 677a3c3d2fdb0..c39e53cb8fe4e 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.l3.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.l3.ts @@ -13,7 +13,7 @@ new ecs.LoadBalancedFargateService(stack, 'L3', { cluster, memoryMiB: '1GB', cpu: '512', - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), }); app.run(); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.ts index 1c3c05fcbd121..8b5e793cb20dd 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.ts @@ -16,7 +16,7 @@ const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef', { }); const container = taskDefinition.addContainer('web', { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), }); container.addPortMappings({ diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts index 4bb53cc72c538..705a893c0b763 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts @@ -16,7 +16,7 @@ export = { const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), }); new ecs.FargateService(stack, "FargateService", { @@ -109,7 +109,7 @@ export = { const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), }); new ecs.FargateService(stack, "FargateService", { @@ -138,7 +138,7 @@ export = { const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer("web", { - image: ecs.ContainerImage.fromDockerHub("amazon/amazon-ecs-sample"), + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), }); new ecs.FargateService(stack, "FargateService", { @@ -166,7 +166,7 @@ export = { const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer('MainContainer', { - image: ContainerImage.fromDockerHub('hello'), + image: ContainerImage.fromRegistry('hello'), }); // WHEN @@ -193,7 +193,7 @@ export = { const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); const container = taskDefinition.addContainer('MainContainer', { - image: ContainerImage.fromDockerHub('hello'), + image: ContainerImage.fromRegistry('hello'), }); container.addPortMappings({ containerPort: 8000 }); const service = new ecs.FargateService(stack, 'Service', { cluster, taskDefinition }); 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 393613d1aa9ab..fc88979637a90 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts @@ -1,4 +1,5 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import ecs = require('../lib'); @@ -15,7 +16,7 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); @@ -38,7 +39,7 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); @@ -61,7 +62,7 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); @@ -84,7 +85,7 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); @@ -105,12 +106,12 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); const logger = taskDefinition.addContainer("LoggingContainer", { - image: ecs.ContainerImage.fromDockerHub("myLogger"), + image: ecs.ContainerImage.fromRegistry("myLogger"), memoryLimitMiB: 1024, }); @@ -132,12 +133,12 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); const logger = taskDefinition.addContainer("LoggingContainer", { - image: ecs.ContainerImage.fromDockerHub("myLogger"), + image: ecs.ContainerImage.fromRegistry("myLogger"), memoryLimitMiB: 1024, }); @@ -159,7 +160,7 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); @@ -184,7 +185,7 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); @@ -210,7 +211,7 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); @@ -235,7 +236,7 @@ export = { }); const container = taskDefinition.addContainer("Container", { - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app"), + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), memoryLimitMiB: 2048, }); @@ -260,7 +261,7 @@ export = { // WHEN taskDefinition.addContainer('cont', { - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), memoryLimitMiB: 1024, environment: { TEST_ENVIRONMENT_VARIABLE: "test environment variable value" @@ -291,7 +292,7 @@ export = { // WHEN taskDefinition.addContainer('cont', { - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), memoryLimitMiB: 1024, logging: new ecs.AwsLogDriver(stack, 'Logging', { streamPrefix: 'prefix' }) }); @@ -335,7 +336,7 @@ export = { // WHEN taskDefinition.addContainer('cont', { - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), memoryLimitMiB: 1024, healthCheck: { command: [hcCommand] @@ -367,7 +368,7 @@ export = { // WHEN taskDefinition.addContainer('cont', { - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), memoryLimitMiB: 1024, healthCheck: { command: [hcCommand], @@ -395,5 +396,50 @@ export = { test.done(); }, + 'can specify private registry credentials'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + const mySecretArn = 'arn:aws:secretsmanager:region:1234567890:secret:MyRepoSecret-6f8hj3'; + + const repoCreds = secretsmanager.Secret.import(stack, 'MyRepoSecret', { + secretArn: mySecretArn, + }); + + // WHEN + taskDefinition.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('user-x/my-app', { + credentials: repoCreds + }), + memoryLimitMiB: 2048, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: 'user-x/my-app', + RepositoryCredentials: { + CredentialsParameter: mySecretArn + }, + } + ] + })); + + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "secretsmanager:GetSecretValue", + Effect: "Allow", + Resource: mySecretArn + } + ] + } + })); + + test.done(); + }, + // render extra hosts test }; diff --git a/packages/@aws-cdk/aws-ecs/test/test.l3s.ts b/packages/@aws-cdk/aws-ecs/test/test.l3s.ts index 23a2190c267c8..cfc9318178ea2 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.l3s.ts @@ -18,7 +18,7 @@ export = { new ecs.LoadBalancedEc2Service(stack, 'Service', { cluster, memoryLimitMiB: 1024, - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), desiredCount: 2, environment: { TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", @@ -63,7 +63,7 @@ export = { // WHEN new ecs.LoadBalancedFargateService(stack, 'Service', { cluster, - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), desiredCount: 2, environment: { TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", @@ -119,7 +119,7 @@ export = { // WHEN new ecs.LoadBalancedFargateService(stack, 'Service', { cluster, - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), desiredCount: 2, createLogs: false, environment: { @@ -166,7 +166,7 @@ export = { // WHEN new ecs.LoadBalancedFargateService(stack, 'Service', { cluster, - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), domainName: 'api.example.com', domainZone: zone, certificate: Certificate.import(stack, 'Cert', { certificateArn: 'helloworld' }) @@ -212,7 +212,7 @@ export = { test.throws(() => { new ecs.LoadBalancedFargateService(stack, 'Service', { cluster, - image: ecs.ContainerImage.fromDockerHub('test'), + image: ecs.ContainerImage.fromRegistry('test'), domainName: 'api.example.com' }); }); diff --git a/packages/@aws-cdk/aws-ecs/test/test.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs/test/test.load-balanced-fargate-service.ts index cdb28604975be..8bd6678387dbb 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.load-balanced-fargate-service.ts @@ -19,7 +19,7 @@ export = { cluster, certificate: cert, loadBalancerType: ecs.LoadBalancerType.Network, - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app") + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app") }); }; @@ -38,7 +38,7 @@ export = { new ecs.LoadBalancedFargateService(stack, 'Service', { cluster, loadBalancerType: ecs.LoadBalancerType.Network, - image: ecs.ContainerImage.fromDockerHub("/aws/aws-example-app") + image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app") }); // THEN diff --git a/packages/decdk/examples/ecs.json b/packages/decdk/examples/ecs.json index 48cf44aabe5b1..da80045f8b21f 100644 --- a/packages/decdk/examples/ecs.json +++ b/packages/decdk/examples/ecs.json @@ -30,7 +30,7 @@ "essential": true, "memoryLimitMiB": 1024, "image": { - "fromDockerHub": { + "fromRegistry": { "name": "redis" } } diff --git a/tools/cdk-build-tools/package-lock.json b/tools/cdk-build-tools/package-lock.json index 542dc4398c0d7..18f5a70782ff0 100644 --- a/tools/cdk-build-tools/package-lock.json +++ b/tools/cdk-build-tools/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-build-tools", - "version": "0.25.2", + "version": "0.25.3", "lockfileVersion": 1, "requires": true, "dependencies": {