From 829de1e6b17d0360acf790edd8e757ba011ab974 Mon Sep 17 00:00:00 2001 From: Rui Li Date: Thu, 25 Feb 2021 11:13:23 -0800 Subject: [PATCH 1/2] fix(aws-events-targets): Allow creating EcsTask from an imported task definition (#12811) Fixes #12811 This change allows creating EcsTask from an imported task definition. It does so by changing the taskDefinition type in EcsTaskProps from concrete class TaskDefinition to interface ITaskDefinition. --- .../aws-ecs/lib/base/task-definition.ts | 86 ++++++++++- .../aws-ecs/lib/ec2/ec2-task-definition.ts | 51 ++++++- .../lib/fargate/fargate-task-definition.ts | 48 +++++- .../test/ec2/ec2-task-definition.test.ts | 75 ++++++++++ .../fargate/fargate-task-definition.test.ts | 86 ++++++++++- .../aws-ecs/test/task-definition.test.ts | 141 +++++++++++++++--- .../aws-events-targets/lib/ecs-task.ts | 8 +- 7 files changed, 466 insertions(+), 29 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index f7b0f05c92800..afe119bad6ff9 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -38,6 +38,16 @@ export interface ITaskDefinition extends IResource { * Return true if the task definition can be run on a Fargate cluster */ readonly isFargateCompatible: boolean; + + /** + * The networking mode to use for the containers in the task. + */ + readonly networkMode: NetworkMode; + + /** + * The name of the IAM role that grants containers in the task permission to call AWS APIs on your behalf. + */ + readonly taskRole: iam.IRole; } /** @@ -175,10 +185,55 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { readonly pidMode?: PidMode; } +/** + * The common task definition attributes used across all types of task definitions. + */ +export interface CommonTaskDefinitionAttributes { + /** + * The arn of the task definition + */ + readonly taskDefinitionArn: string; + + /** + * The networking mode to use for the containers in the task. + * + * @default NetworkMode.BRIDGE + */ + readonly networkMode?: NetworkMode; + + /** + * The name of the IAM role that grants containers in the task permission to call AWS APIs on your behalf. + * + * @default undefined. + */ + readonly taskRole?: iam.IRole; +} + +/** + * A reference to an existing task definition + */ +export interface TaskDefinitionAttributes extends CommonTaskDefinitionAttributes { + /** + * Execution role for this task definition + * + * @default: undefined + */ + readonly executionRole?: iam.IRole; + + /** + * What launch types this task definition should be compatible with. + * + * @default Compatibility.EC2_AND_FARGATE + */ + readonly compatibility?: Compatibility; +} + abstract class TaskDefinitionBase extends Resource implements ITaskDefinition { public abstract readonly compatibility: Compatibility; + public abstract readonly networkMode: NetworkMode; public abstract readonly taskDefinitionArn: string; + public abstract readonly taskRole: iam.IRole; public abstract readonly executionRole?: iam.IRole; /** @@ -207,10 +262,33 @@ export class TaskDefinition extends TaskDefinitionBase { * The task will have a compatibility of EC2+Fargate. */ public static fromTaskDefinitionArn(scope: Construct, id: string, taskDefinitionArn: string): ITaskDefinition { + return TaskDefinition.fromTaskDefinitionAttributes(scope, id, { taskDefinitionArn: taskDefinitionArn }); + } + + /** + * Create a task definition from a task definition reference + */ + public static fromTaskDefinitionAttributes(scope: Construct, id: string, attrs: TaskDefinitionAttributes): ITaskDefinition { class Import extends TaskDefinitionBase { - public readonly taskDefinitionArn = taskDefinitionArn; - public readonly compatibility = Compatibility.EC2_AND_FARGATE; - public readonly executionRole?: iam.IRole = undefined; + public readonly taskDefinitionArn = attrs.taskDefinitionArn; + public readonly compatibility = attrs.compatibility ?? Compatibility.EC2_AND_FARGATE; + public readonly executionRole = attrs.executionRole; + + public get networkMode(): NetworkMode { + if (attrs.networkMode == undefined) { + throw new Error('NetworkMode is available only if it is given when importing the TaskDefinition.'); + } else { + return attrs.networkMode; + } + } + + public get taskRole(): iam.IRole { + if (attrs.taskRole == undefined) { + throw new Error('TaskRole is available only if it is given when importing the TaskDefinition.'); + } else { + return attrs.taskRole; + } + } } return new Import(scope, id); @@ -248,7 +326,7 @@ export class TaskDefinition extends TaskDefinitionBase { public defaultContainer?: ContainerDefinition; /** - * The task launch type compatiblity requirement. + * The task launch type compatibility requirement. */ public readonly compatibility: Compatibility; diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts index 67d096830b9c7..1a5c444a2cb04 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts @@ -1,6 +1,16 @@ +import * as iam from '@aws-cdk/aws-iam'; import { Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { CommonTaskDefinitionProps, Compatibility, IpcMode, ITaskDefinition, NetworkMode, PidMode, TaskDefinition } from '../base/task-definition'; +import { + CommonTaskDefinitionAttributes, + CommonTaskDefinitionProps, + Compatibility, + IpcMode, + ITaskDefinition, + NetworkMode, + PidMode, + TaskDefinition, +} from '../base/task-definition'; import { PlacementConstraint } from '../placement'; /** @@ -51,6 +61,13 @@ export interface IEc2TaskDefinition extends ITaskDefinition { } +/** + * Attributes used to import an existing EC2 task definition + */ +export interface Ec2TaskDefinitionAttributes extends CommonTaskDefinitionAttributes { + +} + /** * The details of a task definition run on an EC2 cluster. * @@ -62,12 +79,42 @@ export class Ec2TaskDefinition extends TaskDefinition implements IEc2TaskDefinit * Imports a task definition from the specified task definition ARN. */ public static fromEc2TaskDefinitionArn(scope: Construct, id: string, ec2TaskDefinitionArn: string): IEc2TaskDefinition { + return Ec2TaskDefinition.fromEc2TaskDefinitionAttributes( + scope, id, { taskDefinitionArn: ec2TaskDefinitionArn }, + ); + } + + /** + * Imports an existing Ec2 task definition from its attributes + */ + public static fromEc2TaskDefinitionAttributes( + scope: Construct, + id: string, + attrs: Ec2TaskDefinitionAttributes, + ): IEc2TaskDefinition { class Import extends Resource implements IEc2TaskDefinition { - public readonly taskDefinitionArn = ec2TaskDefinitionArn; + public readonly taskDefinitionArn = attrs.taskDefinitionArn; public readonly compatibility = Compatibility.EC2; public readonly isEc2Compatible = true; public readonly isFargateCompatible = false; + + public get networkMode(): NetworkMode { + if (attrs.networkMode == undefined) { + throw new Error('NetworkMode is available only if it is given when importing the Ec2 TaskDefinition.'); + } else { + return attrs.networkMode; + } + } + + public get taskRole(): iam.IRole { + if (attrs.taskRole == undefined) { + throw new Error('TaskRole is available only if it is given when importing the Ec2 TaskDefinition.'); + } else { + return attrs.taskRole; + } + } } + return new Import(scope, id); } diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts index eba4ac4371ee8..c605950f11c4f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts @@ -1,6 +1,14 @@ +import * as iam from '@aws-cdk/aws-iam'; import { Resource, Tokenization } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { CommonTaskDefinitionProps, Compatibility, ITaskDefinition, NetworkMode, TaskDefinition } from '../base/task-definition'; +import { + CommonTaskDefinitionAttributes, + CommonTaskDefinitionProps, + Compatibility, + ITaskDefinition, + NetworkMode, + TaskDefinition, +} from '../base/task-definition'; /** * The properties for a task definition. @@ -51,6 +59,13 @@ export interface IFargateTaskDefinition extends ITaskDefinition { } +/** + * Attributes used to import an existing Fargate task definition + */ +export interface FargateTaskDefinitionAttributes extends CommonTaskDefinitionAttributes { + +} + /** * The details of a task definition run on a Fargate cluster. * @@ -62,11 +77,40 @@ export class FargateTaskDefinition extends TaskDefinition implements IFargateTas * Imports a task definition from the specified task definition ARN. */ public static fromFargateTaskDefinitionArn(scope: Construct, id: string, fargateTaskDefinitionArn: string): IFargateTaskDefinition { + return FargateTaskDefinition.fromFargateTaskDefinitionAttributes( + scope, id, { taskDefinitionArn: fargateTaskDefinitionArn }, + ); + } + + /** + * Import an existing Fargate task definition from its attributes + */ + public static fromFargateTaskDefinitionAttributes( + scope: Construct, + id: string, + attrs: FargateTaskDefinitionAttributes, + ): IFargateTaskDefinition { class Import extends Resource implements IFargateTaskDefinition { - public readonly taskDefinitionArn = fargateTaskDefinitionArn; + public readonly taskDefinitionArn = attrs.taskDefinitionArn; public readonly compatibility = Compatibility.FARGATE; public readonly isEc2Compatible = false; public readonly isFargateCompatible = true; + + public get networkMode(): NetworkMode { + if (attrs.networkMode == undefined) { + throw new Error('NetworkMode is available only if it is given when importing the Fargate TaskDefinition.'); + } else { + return attrs.networkMode; + } + } + + public get taskRole(): iam.IRole { + if (attrs.taskRole == undefined) { + throw new Error('TaskRole is available only if it is given when importing the Fargate TaskDefinition.'); + } else { + return attrs.taskRole; + } + } } return new Import(scope, id); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts index 60c39d2103daf..2cf3dc385838a 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts @@ -1059,6 +1059,81 @@ describe('ec2 task definition', () => { }); }); + describe('When importing from an existing Ec2 TaskDefinition', () => { + test('can succeed using TaskDefinition Arn', () => { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + + // WHEN + const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionArn(stack, 'EC2_TD_ID', expectTaskDefinitionArn); + + // THEN + expect(taskDefinition.taskDefinitionArn).toBe(expectTaskDefinitionArn); + }); + }); + + describe('When importing from an existing Ec2 TaskDefinition using attributes', () => { + test('can set the imported task attribuets successfully', () => { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + const expectNetworkMode = ecs.NetworkMode.AWS_VPC; + const expectTaskRole = new iam.Role(stack, 'TaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + + // WHEN + const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionAttributes(stack, 'TD_ID', { + taskDefinitionArn: expectTaskDefinitionArn, + networkMode: expectNetworkMode, + taskRole: expectTaskRole, + }); + + // THEN + expect(taskDefinition.taskDefinitionArn).toBe(expectTaskDefinitionArn); + expect(taskDefinition.compatibility).toBe(ecs.Compatibility.EC2); + expect(taskDefinition.isEc2Compatible).toBeTruthy(); + expect(taskDefinition.isFargateCompatible).toBeFalsy(); + expect(taskDefinition.networkMode).toBe(expectNetworkMode); + expect(taskDefinition.taskRole).toBe(expectTaskRole); + }); + + test('returns an Ec2 TaskDefinition that will throw an error when trying to access its yet to defined networkMode', () => { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + const expectTaskRole = new iam.Role(stack, 'TaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + + // WHEN + const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionAttributes(stack, 'TD_ID', { + taskDefinitionArn: expectTaskDefinitionArn, + taskRole: expectTaskRole, + }); + + // THEN + expect(() => taskDefinition.networkMode).toThrow(/NetworkMode is available only if it is given when importing the Ec2 TaskDefinition./); + }); + + test('returns an Ec2 TaskDefinition that will throw an error when trying to access its yet to defined taskRole', () => { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + const expectNetworkMode = ecs.NetworkMode.AWS_VPC; + + // WHEN + const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionAttributes(stack, 'TD_ID', { + taskDefinitionArn: expectTaskDefinitionArn, + networkMode: expectNetworkMode, + }); + + // THEN + expect(() => taskDefinition.taskRole).toThrow(/TaskRole is available only if it is given when importing the Ec2 TaskDefinition./); + }); + }); + test('throws when setting proxyConfiguration without networkMode AWS_VPC', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts index 00ec0028ef1a3..d60883d5b74ab 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts @@ -5,7 +5,7 @@ import { nodeunitShim, Test } from 'nodeunit-shim'; import * as ecs from '../../lib'; nodeunitShim({ - 'When creating an Fargate TaskDefinition': { + 'When creating a Fargate TaskDefinition': { 'with only required properties set, it correctly sets default properties'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -114,4 +114,88 @@ nodeunitShim({ test.done(); }, }, + + 'When importing from an existing Fargate TaskDefinition': { + 'can succeed using TaskDefinition Arn'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + + // WHEN + const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionArn(stack, 'FARGATE_TD_ID', expectTaskDefinitionArn); + + // THEN + test.equal(taskDefinition.taskDefinitionArn, expectTaskDefinitionArn); + test.done(); + }, + + 'can succeed using attributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + const expectNetworkMode = ecs.NetworkMode.AWS_VPC; + const expectTaskRole = new iam.Role(stack, 'TaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + + // WHEN + const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionAttributes(stack, 'TD_ID', { + taskDefinitionArn: expectTaskDefinitionArn, + networkMode: expectNetworkMode, + taskRole: expectTaskRole, + }); + + // THEN + test.equal(taskDefinition.taskDefinitionArn, expectTaskDefinitionArn); + test.equal(taskDefinition.compatibility, ecs.Compatibility.FARGATE); + test.ok(taskDefinition.isFargateCompatible); + test.equal(taskDefinition.isEc2Compatible, false); + test.equal(taskDefinition.networkMode, expectNetworkMode); + test.equal(taskDefinition.taskRole, expectTaskRole); + + test.done(); + }, + + 'returns a Fargate TaskDefinition that will throw an error when trying to access its networkMode but its networkMode is undefined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + const expectTaskRole = new iam.Role(stack, 'TaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + + // WHEN + const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionAttributes(stack, 'TD_ID', { + taskDefinitionArn: expectTaskDefinitionArn, + taskRole: expectTaskRole, + }); + + // THEN + test.throws(() => { + taskDefinition.networkMode; + }, /NetworkMode is available only if it is given when importing the Fargate TaskDefinition./); + + test.done(); + }, + + 'returns a Fargate TaskDefinition that will throw an error when trying to access its taskRole but its taskRole is undefined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + const expectNetworkMode = ecs.NetworkMode.AWS_VPC; + + // WHEN + const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionAttributes(stack, 'TD_ID', { + taskDefinitionArn: expectTaskDefinitionArn, + networkMode: expectNetworkMode, + }); + + // THEN + test.throws(() => { + taskDefinition.taskRole; + }, /TaskRole is available only if it is given when importing the Fargate TaskDefinition./); + + test.done(); + }, + }, }); diff --git a/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts index 34a7306106d66..41728ad90d8ff 100644 --- a/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts @@ -2,24 +2,131 @@ import { expect, haveResource } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; import * as ecs from '../lib'; +import * as iam from '@aws-cdk/aws-iam'; nodeunitShim({ - 'A task definition with both compatibilities defaults to networkmode AwsVpc'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecs.TaskDefinition(stack, 'TD', { - cpu: '512', - memoryMiB: '512', - compatibility: ecs.Compatibility.EC2_AND_FARGATE, - }); - - // THEN - expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { - NetworkMode: 'awsvpc', - })); - - test.done(); + 'When creating a new TaskDefinition': { + 'A task definition with both compatibilities defaults to networkmode AwsVpc'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecs.TaskDefinition(stack, 'TD', { + cpu: '512', + memoryMiB: '512', + compatibility: ecs.Compatibility.EC2_AND_FARGATE, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + NetworkMode: 'awsvpc', + })); + + test.done(); + }, + }, + + 'When importing from an existing Task definition': { + 'can import using a task definition arn'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinitionArn = 'TDArn'; + + // WHEN + const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionArn(stack, 'TD_ID', taskDefinitionArn); + + // THEN + test.equal(taskDefinition.taskDefinitionArn, taskDefinitionArn); + test.equal(taskDefinition.compatibility, ecs.Compatibility.EC2_AND_FARGATE); + test.equal(taskDefinition.executionRole, undefined); + + test.done(); + }, + + 'can import a Task Definition using attributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + const expectCompatibility = ecs.Compatibility.EC2; + const expectExecutionRole = new iam.Role(stack, 'ExecutionRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + const expectNetworkMode = ecs.NetworkMode.AWS_VPC; + const expectTaskRole = new iam.Role(stack, 'TaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + + // WHEN + const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionAttributes(stack, 'TD_ID', { + taskDefinitionArn: expectTaskDefinitionArn, + compatibility: expectCompatibility, + executionRole: expectExecutionRole, + networkMode: expectNetworkMode, + taskRole: expectTaskRole, + }); + + // THEN + test.equal(taskDefinition.taskDefinitionArn, expectTaskDefinitionArn); + test.equal(taskDefinition.compatibility, expectCompatibility); + test.equal(taskDefinition.executionRole, expectExecutionRole); + test.equal(taskDefinition.networkMode, expectNetworkMode); + test.equal(taskDefinition.taskRole, expectTaskRole); + + test.done(); + }, + + 'returns an imported TaskDefinition that will throw an error when trying to access its yet to defined networkMode'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + const expectCompatibility = ecs.Compatibility.EC2; + const expectExecutionRole = new iam.Role(stack, 'ExecutionRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + const expectTaskRole = new iam.Role(stack, 'TaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + + // WHEN + const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionAttributes(stack, 'TD_ID', { + taskDefinitionArn: expectTaskDefinitionArn, + compatibility: expectCompatibility, + executionRole: expectExecutionRole, + taskRole: expectTaskRole, + }); + + // THEN + test.throws(() => { + taskDefinition.networkMode; + }, /NetworkMode is available only if it is given when importing the TaskDefinition./); + + test.done(); + }, + + 'returns an imported TaskDefinition that will throw an error when trying to access its yet to defined taskRole'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const expectTaskDefinitionArn = 'TD_ARN'; + const expectCompatibility = ecs.Compatibility.EC2; + const expectNetworkMode = ecs.NetworkMode.AWS_VPC; + const expectExecutionRole = new iam.Role(stack, 'ExecutionRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + + // WHEN + const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionAttributes(stack, 'TD_ID', { + taskDefinitionArn: expectTaskDefinitionArn, + compatibility: expectCompatibility, + executionRole: expectExecutionRole, + networkMode: expectNetworkMode, + }); + + // THEN + test.throws(() => { + taskDefinition.taskRole; + }, /TaskRole is available only if it is given when importing the TaskDefinition./); + + test.done(); + }, }, }); diff --git a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts index baff4c76cde66..1c75344ee2d8d 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -5,6 +5,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { ContainerOverride } from './ecs-task-properties'; import { singletonEventRole } from './util'; +import { Construct } from 'constructs'; /** * Properties to define an ECS Event Task @@ -18,7 +19,7 @@ export interface EcsTaskProps { /** * Task Definition of the task that should be started */ - readonly taskDefinition: ecs.TaskDefinition; + readonly taskDefinition: ecs.ITaskDefinition; /** * How many tasks should be started when this event is triggered @@ -103,7 +104,7 @@ export class EcsTask implements events.IRuleTarget { */ public readonly securityGroups?: ec2.ISecurityGroup[]; private readonly cluster: ecs.ICluster; - private readonly taskDefinition: ecs.TaskDefinition; + private readonly taskDefinition: ecs.ITaskDefinition; private readonly taskCount: number; private readonly role: iam.IRole; private readonly platformVersion?: ecs.FargatePlatformVersion; @@ -137,8 +138,9 @@ export class EcsTask implements events.IRuleTarget { this.securityGroups = props.securityGroups; return; } + let securityGroup = props.securityGroup || this.taskDefinition.node.tryFindChild('SecurityGroup') as ec2.ISecurityGroup; - securityGroup = securityGroup || new ec2.SecurityGroup(this.taskDefinition, 'SecurityGroup', { vpc: this.props.cluster.vpc }); + securityGroup = securityGroup || new ec2.SecurityGroup(this.taskDefinition as unknown as Construct, 'SecurityGroup', { vpc: this.props.cluster.vpc }); this.securityGroup = securityGroup; // Maintain backwards-compatibility for customers that read the generated security group. this.securityGroups = [securityGroup]; } From 0766d75e4b4291097e17bb9a539577c2eeaa3e0b Mon Sep 17 00:00:00 2001 From: Rui Li Date: Sat, 27 Feb 2021 20:58:16 -0800 Subject: [PATCH 2/2] Address comments to 829de1e6b Addressed comments to 829de1e6b17d0360acf790edd8e757ba011ab974 * Tests for creating ecs event target from imported task definitions * Replace spooky casting unknown to type validation * Drop executionRole from the task definition attributes * Provide more meaningful documentation and error prompt --- .../lib/base/_imported-task-definition.ts | 108 ++++++++ .../aws-ecs/lib/base/task-definition.ts | 47 +--- .../aws-ecs/lib/ec2/ec2-task-definition.ts | 39 +-- .../lib/fargate/fargate-task-definition.ts | 38 +-- .../test/ec2/ec2-task-definition.test.ts | 10 +- .../fargate/fargate-task-definition.test.ts | 6 +- .../aws-ecs/test/task-definition.test.ts | 20 +- .../aws-events-targets/lib/ecs-task.ts | 9 +- .../test/ecs/event-rule-target.test.ts | 243 ++++++++++++++++++ 9 files changed, 404 insertions(+), 116 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecs/lib/base/_imported-task-definition.ts diff --git a/packages/@aws-cdk/aws-ecs/lib/base/_imported-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/_imported-task-definition.ts new file mode 100644 index 0000000000000..3c9c583b96de0 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/base/_imported-task-definition.ts @@ -0,0 +1,108 @@ +import { IRole } from '@aws-cdk/aws-iam'; +import { Construct } from 'constructs'; +import { IEc2TaskDefinition } from '../ec2/ec2-task-definition'; +import { IFargateTaskDefinition } from '../fargate/fargate-task-definition'; +import { Compatibility, NetworkMode, isEc2Compatible, isFargateCompatible } from './task-definition'; +import { Resource } from '@aws-cdk/core'; + +/** + * The properties of ImportedTaskDefinition + */ +export interface ImportedTaskDefinitionProps { + /** + * The arn of the task definition + */ + readonly taskDefinitionArn: string; + + /** + * What launch types this task definition should be compatible with. + * + * @default Compatibility.EC2_AND_FARGATE + */ + readonly compatibility?: Compatibility; + + /** + * The networking mode to use for the containers in the task. + * + * @default Network mode cannot be provided to the imported task. + */ + readonly networkMode?: NetworkMode; + + /** + * The name of the IAM role that grants containers in the task permission to call AWS APIs on your behalf. + * + * @default Permissions cannot be granted to the imported task. + */ + readonly taskRole?: IRole; +} + +/** + * Task definition reference of an imported task + */ +export class ImportedTaskDefinition extends Resource implements IEc2TaskDefinition, IFargateTaskDefinition { + /** + * What launch types this task definition should be compatible with. + */ + readonly compatibility: Compatibility; + + /** + * ARN of this task definition + */ + readonly taskDefinitionArn: string; + + /** + * Execution role for this task definition + */ + readonly executionRole?: IRole = undefined; + + /** + * The networking mode to use for the containers in the task. + */ + readonly _networkMode?: NetworkMode; + + /** + * The name of the IAM role that grants containers in the task permission to call AWS APIs on your behalf. + */ + readonly _taskRole?: IRole; + + constructor(scope: Construct, id: string, props: ImportedTaskDefinitionProps) { + super(scope, id); + + this.compatibility = props.compatibility ?? Compatibility.EC2_AND_FARGATE; + this.taskDefinitionArn = props.taskDefinitionArn; + this._taskRole = props.taskRole; + this._networkMode = props.networkMode; + } + + public get networkMode(): NetworkMode { + if (this._networkMode == undefined) { + throw new Error('This operation requires the networkMode in ImportedTaskDefinition to be defined. ' + + 'Add the \'networkMode\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); + } else { + return this._networkMode; + } + } + + public get taskRole(): IRole { + if (this._taskRole == undefined) { + throw new Error('This operation requires the taskRole in ImportedTaskDefinition to be defined. ' + + 'Add the \'taskRole\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); + } else { + return this._taskRole; + } + } + + /** + * Return true if the task definition can be run on an EC2 cluster + */ + public get isEc2Compatible(): boolean { + return isEc2Compatible(this.compatibility); + } + + /** + * Return true if the task definition can be run on a Fargate cluster + */ + public get isFargateCompatible(): boolean { + return isFargateCompatible(this.compatibility); + } +} diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index afe119bad6ff9..338fd59b39ea7 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -8,6 +8,7 @@ import { FirelensLogRouter, FirelensLogRouterDefinitionOptions, FirelensLogRoute import { AwsLogDriver } from '../log-drivers/aws-log-driver'; import { PlacementConstraint } from '../placement'; import { ProxyConfiguration } from '../proxy-configuration/proxy-configuration'; +import { ImportedTaskDefinition } from './_imported-task-definition'; /** * The interface for all task definitions. @@ -197,14 +198,14 @@ export interface CommonTaskDefinitionAttributes { /** * The networking mode to use for the containers in the task. * - * @default NetworkMode.BRIDGE + * @default Network mode cannot be provided to the imported task. */ readonly networkMode?: NetworkMode; /** * The name of the IAM role that grants containers in the task permission to call AWS APIs on your behalf. * - * @default undefined. + * @default Permissions cannot be granted to the imported task. */ readonly taskRole?: iam.IRole; } @@ -213,13 +214,6 @@ export interface CommonTaskDefinitionAttributes { * A reference to an existing task definition */ export interface TaskDefinitionAttributes extends CommonTaskDefinitionAttributes { - /** - * Execution role for this task definition - * - * @default: undefined - */ - readonly executionRole?: iam.IRole; - /** * What launch types this task definition should be compatible with. * @@ -262,36 +256,19 @@ export class TaskDefinition extends TaskDefinitionBase { * The task will have a compatibility of EC2+Fargate. */ public static fromTaskDefinitionArn(scope: Construct, id: string, taskDefinitionArn: string): ITaskDefinition { - return TaskDefinition.fromTaskDefinitionAttributes(scope, id, { taskDefinitionArn: taskDefinitionArn }); + return new ImportedTaskDefinition(scope, id, { taskDefinitionArn: taskDefinitionArn }); } /** * Create a task definition from a task definition reference */ public static fromTaskDefinitionAttributes(scope: Construct, id: string, attrs: TaskDefinitionAttributes): ITaskDefinition { - class Import extends TaskDefinitionBase { - public readonly taskDefinitionArn = attrs.taskDefinitionArn; - public readonly compatibility = attrs.compatibility ?? Compatibility.EC2_AND_FARGATE; - public readonly executionRole = attrs.executionRole; - - public get networkMode(): NetworkMode { - if (attrs.networkMode == undefined) { - throw new Error('NetworkMode is available only if it is given when importing the TaskDefinition.'); - } else { - return attrs.networkMode; - } - } - - public get taskRole(): iam.IRole { - if (attrs.taskRole == undefined) { - throw new Error('TaskRole is available only if it is given when importing the TaskDefinition.'); - } else { - return attrs.taskRole; - } - } - } - - return new Import(scope, id); + return new ImportedTaskDefinition(scope, id, { + taskDefinitionArn: attrs.taskDefinitionArn, + compatibility: attrs.compatibility, + networkMode: attrs.networkMode, + taskRole: attrs.taskRole, + }); } /** @@ -968,13 +945,13 @@ export interface ITaskDefinitionExtension { /** * Return true if the given task definition can be run on an EC2 cluster */ -function isEc2Compatible(compatibility: Compatibility): boolean { +export function isEc2Compatible(compatibility: Compatibility): boolean { return [Compatibility.EC2, Compatibility.EC2_AND_FARGATE].includes(compatibility); } /** * Return true if the given task definition can be run on a Fargate cluster */ -function isFargateCompatible(compatibility: Compatibility): boolean { +export function isFargateCompatible(compatibility: Compatibility): boolean { return [Compatibility.FARGATE, Compatibility.EC2_AND_FARGATE].includes(compatibility); } diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts index 1a5c444a2cb04..ff571c884b73e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts @@ -1,5 +1,3 @@ -import * as iam from '@aws-cdk/aws-iam'; -import { Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CommonTaskDefinitionAttributes, @@ -12,6 +10,7 @@ import { TaskDefinition, } from '../base/task-definition'; import { PlacementConstraint } from '../placement'; +import { ImportedTaskDefinition } from '../base/_imported-task-definition'; /** * The properties for a task definition run on an EC2 cluster. @@ -79,9 +78,9 @@ export class Ec2TaskDefinition extends TaskDefinition implements IEc2TaskDefinit * Imports a task definition from the specified task definition ARN. */ public static fromEc2TaskDefinitionArn(scope: Construct, id: string, ec2TaskDefinitionArn: string): IEc2TaskDefinition { - return Ec2TaskDefinition.fromEc2TaskDefinitionAttributes( - scope, id, { taskDefinitionArn: ec2TaskDefinitionArn }, - ); + return new ImportedTaskDefinition(scope, id, { + taskDefinitionArn: ec2TaskDefinitionArn, + }); } /** @@ -92,30 +91,12 @@ export class Ec2TaskDefinition extends TaskDefinition implements IEc2TaskDefinit id: string, attrs: Ec2TaskDefinitionAttributes, ): IEc2TaskDefinition { - class Import extends Resource implements IEc2TaskDefinition { - public readonly taskDefinitionArn = attrs.taskDefinitionArn; - public readonly compatibility = Compatibility.EC2; - public readonly isEc2Compatible = true; - public readonly isFargateCompatible = false; - - public get networkMode(): NetworkMode { - if (attrs.networkMode == undefined) { - throw new Error('NetworkMode is available only if it is given when importing the Ec2 TaskDefinition.'); - } else { - return attrs.networkMode; - } - } - - public get taskRole(): iam.IRole { - if (attrs.taskRole == undefined) { - throw new Error('TaskRole is available only if it is given when importing the Ec2 TaskDefinition.'); - } else { - return attrs.taskRole; - } - } - } - - return new Import(scope, id); + return new ImportedTaskDefinition(scope, id, { + taskDefinitionArn: attrs.taskDefinitionArn, + compatibility: Compatibility.EC2, + networkMode: attrs.networkMode, + taskRole: attrs.taskRole, + }); } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts index c605950f11c4f..3d8d113886709 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts @@ -1,5 +1,4 @@ -import * as iam from '@aws-cdk/aws-iam'; -import { Resource, Tokenization } from '@aws-cdk/core'; +import { Tokenization } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CommonTaskDefinitionAttributes, @@ -9,6 +8,7 @@ import { NetworkMode, TaskDefinition, } from '../base/task-definition'; +import { ImportedTaskDefinition } from '../base/_imported-task-definition'; /** * The properties for a task definition. @@ -77,9 +77,7 @@ export class FargateTaskDefinition extends TaskDefinition implements IFargateTas * Imports a task definition from the specified task definition ARN. */ public static fromFargateTaskDefinitionArn(scope: Construct, id: string, fargateTaskDefinitionArn: string): IFargateTaskDefinition { - return FargateTaskDefinition.fromFargateTaskDefinitionAttributes( - scope, id, { taskDefinitionArn: fargateTaskDefinitionArn }, - ); + return new ImportedTaskDefinition(scope, id, { taskDefinitionArn: fargateTaskDefinitionArn }); } /** @@ -90,30 +88,12 @@ export class FargateTaskDefinition extends TaskDefinition implements IFargateTas id: string, attrs: FargateTaskDefinitionAttributes, ): IFargateTaskDefinition { - class Import extends Resource implements IFargateTaskDefinition { - public readonly taskDefinitionArn = attrs.taskDefinitionArn; - public readonly compatibility = Compatibility.FARGATE; - public readonly isEc2Compatible = false; - public readonly isFargateCompatible = true; - - public get networkMode(): NetworkMode { - if (attrs.networkMode == undefined) { - throw new Error('NetworkMode is available only if it is given when importing the Fargate TaskDefinition.'); - } else { - return attrs.networkMode; - } - } - - public get taskRole(): iam.IRole { - if (attrs.taskRole == undefined) { - throw new Error('TaskRole is available only if it is given when importing the Fargate TaskDefinition.'); - } else { - return attrs.taskRole; - } - } - } - - return new Import(scope, id); + return new ImportedTaskDefinition(scope, id, { + taskDefinitionArn: attrs.taskDefinitionArn, + compatibility: Compatibility.FARGATE, + networkMode: attrs.networkMode, + taskRole: attrs.taskRole, + }); } /** diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts index 2cf3dc385838a..16128a96c6ed1 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts @@ -1114,7 +1114,9 @@ describe('ec2 task definition', () => { }); // THEN - expect(() => taskDefinition.networkMode).toThrow(/NetworkMode is available only if it is given when importing the Ec2 TaskDefinition./); + expect(() => taskDefinition.networkMode).toThrow( + 'This operation requires the networkMode in ImportedTaskDefinition to be defined. ' + + 'Add the \'networkMode\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); }); test('returns an Ec2 TaskDefinition that will throw an error when trying to access its yet to defined taskRole', () => { @@ -1130,7 +1132,9 @@ describe('ec2 task definition', () => { }); // THEN - expect(() => taskDefinition.taskRole).toThrow(/TaskRole is available only if it is given when importing the Ec2 TaskDefinition./); + expect(() => { taskDefinition.taskRole; }).toThrow( + 'This operation requires the taskRole in ImportedTaskDefinition to be defined. ' + + 'Add the \'taskRole\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); }); }); @@ -1153,7 +1157,5 @@ describe('ec2 task definition', () => { expect(() => { new ecs.Ec2TaskDefinition(stack, 'TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, proxyConfiguration }); }).toThrow(/ProxyConfiguration can only be used with AwsVpc network mode, got: bridge/); - - }); }); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts index d60883d5b74ab..b8fad1bc9316f 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts @@ -173,7 +173,8 @@ nodeunitShim({ // THEN test.throws(() => { taskDefinition.networkMode; - }, /NetworkMode is available only if it is given when importing the Fargate TaskDefinition./); + }, 'This operation requires the networkMode in ImportedTaskDefinition to be defined. ' + + 'Add the \'networkMode\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); test.done(); }, @@ -193,7 +194,8 @@ nodeunitShim({ // THEN test.throws(() => { taskDefinition.taskRole; - }, /TaskRole is available only if it is given when importing the Fargate TaskDefinition./); + }, 'This operation requires the taskRole in ImportedTaskDefinition to be defined. ' + + 'Add the \'taskRole\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); test.done(); }, diff --git a/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts index 41728ad90d8ff..709d3abd86026 100644 --- a/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts @@ -48,9 +48,6 @@ nodeunitShim({ const stack = new cdk.Stack(); const expectTaskDefinitionArn = 'TD_ARN'; const expectCompatibility = ecs.Compatibility.EC2; - const expectExecutionRole = new iam.Role(stack, 'ExecutionRole', { - assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), - }); const expectNetworkMode = ecs.NetworkMode.AWS_VPC; const expectTaskRole = new iam.Role(stack, 'TaskRole', { assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), @@ -60,7 +57,6 @@ nodeunitShim({ const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionAttributes(stack, 'TD_ID', { taskDefinitionArn: expectTaskDefinitionArn, compatibility: expectCompatibility, - executionRole: expectExecutionRole, networkMode: expectNetworkMode, taskRole: expectTaskRole, }); @@ -68,7 +64,7 @@ nodeunitShim({ // THEN test.equal(taskDefinition.taskDefinitionArn, expectTaskDefinitionArn); test.equal(taskDefinition.compatibility, expectCompatibility); - test.equal(taskDefinition.executionRole, expectExecutionRole); + test.equal(taskDefinition.executionRole, undefined); test.equal(taskDefinition.networkMode, expectNetworkMode); test.equal(taskDefinition.taskRole, expectTaskRole); @@ -80,9 +76,6 @@ nodeunitShim({ const stack = new cdk.Stack(); const expectTaskDefinitionArn = 'TD_ARN'; const expectCompatibility = ecs.Compatibility.EC2; - const expectExecutionRole = new iam.Role(stack, 'ExecutionRole', { - assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), - }); const expectTaskRole = new iam.Role(stack, 'TaskRole', { assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), }); @@ -91,14 +84,14 @@ nodeunitShim({ const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionAttributes(stack, 'TD_ID', { taskDefinitionArn: expectTaskDefinitionArn, compatibility: expectCompatibility, - executionRole: expectExecutionRole, taskRole: expectTaskRole, }); // THEN test.throws(() => { taskDefinition.networkMode; - }, /NetworkMode is available only if it is given when importing the TaskDefinition./); + }, 'This operation requires the networkMode in ImportedTaskDefinition to be defined. ' + + 'Add the \'networkMode\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); test.done(); }, @@ -109,22 +102,19 @@ nodeunitShim({ const expectTaskDefinitionArn = 'TD_ARN'; const expectCompatibility = ecs.Compatibility.EC2; const expectNetworkMode = ecs.NetworkMode.AWS_VPC; - const expectExecutionRole = new iam.Role(stack, 'ExecutionRole', { - assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), - }); // WHEN const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionAttributes(stack, 'TD_ID', { taskDefinitionArn: expectTaskDefinitionArn, compatibility: expectCompatibility, - executionRole: expectExecutionRole, networkMode: expectNetworkMode, }); // THEN test.throws(() => { taskDefinition.taskRole; - }, /TaskRole is available only if it is given when importing the TaskDefinition./); + }, 'This operation requires the taskRole in ImportedTaskDefinition to be defined. ' + + 'Add the \'taskRole\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); test.done(); }, diff --git a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts index 1c75344ee2d8d..a31fc0b68ca5c 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -5,7 +5,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { ContainerOverride } from './ecs-task-properties'; import { singletonEventRole } from './util'; -import { Construct } from 'constructs'; /** * Properties to define an ECS Event Task @@ -139,8 +138,14 @@ export class EcsTask implements events.IRuleTarget { return; } + if (!cdk.Construct.isConstruct(this.taskDefinition)) { + throw new Error('Cannot create a security group for ECS task. ' + + 'The task definition in ECS task is not a Construct. ' + + 'Please pass a taskDefinition as a Construct in EcsTaskProps.'); + } + let securityGroup = props.securityGroup || this.taskDefinition.node.tryFindChild('SecurityGroup') as ec2.ISecurityGroup; - securityGroup = securityGroup || new ec2.SecurityGroup(this.taskDefinition as unknown as Construct, 'SecurityGroup', { vpc: this.props.cluster.vpc }); + securityGroup = securityGroup || new ec2.SecurityGroup(this.taskDefinition, 'SecurityGroup', { vpc: this.props.cluster.vpc }); this.securityGroup = securityGroup; // Maintain backwards-compatibility for customers that read the generated security group. this.securityGroups = [securityGroup]; } diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts b/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts index 4ac8347d02ec3..f0ab7770fadbd 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts @@ -58,6 +58,249 @@ test('Can use EC2 taskdef as EventRule target', () => { }); }); +test('Throws error for lacking of taskRole ' + + 'when importing from an EC2 task definition just from a task definition arn as EventRule target', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionArn(stack, 'TaskDef', 'importedTaskDefArn'); + + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 min)'), + }); + + // THEN + expect(() => { + rule.addTarget(new targets.EcsTask({ + cluster, + taskDefinition, + taskCount: 1, + containerOverrides: [{ + containerName: 'TheContainer', + command: ['echo', events.EventField.fromPath('$.detail.event')], + }], + })); + }).toThrow('This operation requires the taskRole in ImportedTaskDefinition to be defined. ' + + 'Add the \'taskRole\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); +}); + +test('Can import an EC2 task definition from task definition attributes as EventRule target', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionAttributes(stack, 'TaskDef', { + taskDefinitionArn: 'importedTaskDefArn', + networkMode: ecs.NetworkMode.BRIDGE, + taskRole: new iam.Role(stack, 'TaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }), + }); + + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 min)'), + }); + + // WHEN + rule.addTarget(new targets.EcsTask({ + cluster, + taskDefinition, + taskCount: 1, + containerOverrides: [{ + containerName: 'TheContainer', + command: ['echo', events.EventField.fromPath('$.detail.event')], + }], + })); + + // THEN + expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Targets: [ + { + Arn: { 'Fn::GetAtt': ['EcsCluster97242B84', 'Arn'] }, + EcsParameters: { + TaskCount: 1, + TaskDefinitionArn: 'importedTaskDefArn', + }, + InputTransformer: { + InputPathsMap: { + 'detail-event': '$.detail.event', + }, + InputTemplate: '{"containerOverrides":[{"name":"TheContainer","command":["echo",]}]}', + }, + RoleArn: { 'Fn::GetAtt': ['TaskDefEventsRoleFB3B67B8', 'Arn'] }, + Id: 'Target0', + }, + ], + }); +}); + +test('Throws error for lacking of taskRole ' + + 'when importing from a Fargate task definition just from a task definition arn as EventRule target', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + + const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionArn(stack, 'TaskDef', 'ImportedTaskDefArn'); + + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 min)'), + }); + + // THEN + expect(() => { + rule.addTarget(new targets.EcsTask({ + cluster, + taskDefinition, + taskCount: 1, + containerOverrides: [{ + containerName: 'TheContainer', + command: ['echo', events.EventField.fromPath('$.detail.event')], + }], + })); + }).toThrow('This operation requires the taskRole in ImportedTaskDefinition to be defined. ' + + 'Add the \'taskRole\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); +}); + +test('Can import a Fargate task definition from task definition attributes as EventRule target', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + + const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionAttributes(stack, 'TaskDef', { + taskDefinitionArn: 'importedTaskDefArn', + networkMode: ecs.NetworkMode.AWS_VPC, + taskRole: new iam.Role(stack, 'TaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }), + }); + + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 min)'), + }); + + // WHEN + rule.addTarget(new targets.EcsTask({ + cluster, + taskDefinition, + taskCount: 1, + containerOverrides: [{ + containerName: 'TheContainer', + command: ['echo', events.EventField.fromPath('$.detail.event')], + }], + })); + + // THEN + expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Targets: [ + { + Arn: { 'Fn::GetAtt': ['EcsCluster97242B84', 'Arn'] }, + EcsParameters: { + TaskCount: 1, + TaskDefinitionArn: 'importedTaskDefArn', + }, + InputTransformer: { + InputPathsMap: { + 'detail-event': '$.detail.event', + }, + InputTemplate: '{"containerOverrides":[{"name":"TheContainer","command":["echo",]}]}', + }, + RoleArn: { 'Fn::GetAtt': ['TaskDefEventsRoleFB3B67B8', 'Arn'] }, + Id: 'Target0', + }, + ], + }); +}); + +test('Throws error for lacking of taskRole ' + + 'when importing from a task definition just from a task definition arn as EventRule target', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + + const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionArn(stack, 'TaskDef', 'ImportedTaskDefArn'); + + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 min)'), + }); + + // THEN + expect(() => { + rule.addTarget(new targets.EcsTask({ + cluster, + taskDefinition, + taskCount: 1, + containerOverrides: [{ + containerName: 'TheContainer', + command: ['echo', events.EventField.fromPath('$.detail.event')], + }], + })); + }).toThrow('This operation requires the taskRole in ImportedTaskDefinition to be defined. ' + + 'Add the \'taskRole\' in ImportedTaskDefinitionProps to instantiate ImportedTaskDefinition'); +}); + +test('Can import a Task definition from task definition attributes as EventRule target', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + + const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionAttributes(stack, 'TaskDef', { + taskDefinitionArn: 'importedTaskDefArn', + networkMode: ecs.NetworkMode.AWS_VPC, + taskRole: new iam.Role(stack, 'TaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }), + }); + + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 min)'), + }); + + // WHEN + rule.addTarget(new targets.EcsTask({ + cluster, + taskDefinition, + taskCount: 1, + containerOverrides: [{ + containerName: 'TheContainer', + command: ['echo', events.EventField.fromPath('$.detail.event')], + }], + })); + + // THEN + expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Targets: [ + { + Arn: { 'Fn::GetAtt': ['EcsCluster97242B84', 'Arn'] }, + EcsParameters: { + TaskCount: 1, + TaskDefinitionArn: 'importedTaskDefArn', + }, + InputTransformer: { + InputPathsMap: { + 'detail-event': '$.detail.event', + }, + InputTemplate: '{"containerOverrides":[{"name":"TheContainer","command":["echo",]}]}', + }, + RoleArn: { 'Fn::GetAtt': ['TaskDefEventsRoleFB3B67B8', 'Arn'] }, + Id: 'Target0', + }, + ], + }); +}); + test('Can use Fargate taskdef as EventRule target', () => { // GIVEN const stack = new cdk.Stack();