Skip to content

Commit 37f1eb0

Browse files
authored
feat(events-targets): support assignPublicIp flag to EcsTask (#25660)
This feature supports `assignPublicIp` to `EcsTask`. It specifies whether the task's elastic network interface receives a public IP address. You can enable it only when `LaunchType` is `FARGATE`. In this commit, the choice logic of the `LaunchType` keeps the backwards compatibility. Closes #9233 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 2d74a46 commit 37f1eb0

File tree

8 files changed

+282
-7
lines changed

8 files changed

+282
-7
lines changed

packages/@aws-cdk-testing/framework-integ/test/aws-events-targets/test/ecs/integ.event-fargate-task.js.snapshot/aws-ecs-integ-fargate.assets.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"version": "31.0.0",
33
"files": {
4-
"4eef78557096850d7999e5b8dea53c92d3dfecb6360d19bff549c574432580e1": {
4+
"f54d2ab12d74af5412f68984953da28f5db17e8b02e7df30a6e02393dbdc1da1": {
55
"source": {
66
"path": "aws-ecs-integ-fargate.template.json",
77
"packaging": "file"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "4eef78557096850d7999e5b8dea53c92d3dfecb6360d19bff549c574432580e1.json",
12+
"objectKey": "f54d2ab12d74af5412f68984953da28f5db17e8b02e7df30a6e02393dbdc1da1.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}

packages/@aws-cdk-testing/framework-integ/test/aws-events-targets/test/ecs/integ.event-fargate-task.js.snapshot/aws-ecs-integ-fargate.template.json

+41
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,47 @@
556556
"Arn"
557557
]
558558
}
559+
},
560+
{
561+
"Arn": {
562+
"Fn::GetAtt": [
563+
"EcsCluster97242B84",
564+
"Arn"
565+
]
566+
},
567+
"EcsParameters": {
568+
"LaunchType": "FARGATE",
569+
"NetworkConfiguration": {
570+
"AwsVpcConfiguration": {
571+
"AssignPublicIp": "ENABLED",
572+
"SecurityGroups": [
573+
{
574+
"Fn::GetAtt": [
575+
"TaskDefSecurityGroupD50E7CF0",
576+
"GroupId"
577+
]
578+
}
579+
],
580+
"Subnets": [
581+
{
582+
"Ref": "VpcPublicSubnet1Subnet5C2D37C4"
583+
}
584+
]
585+
}
586+
},
587+
"TaskCount": 1,
588+
"TaskDefinitionArn": {
589+
"Ref": "TaskDef54694570"
590+
}
591+
},
592+
"Id": "Target1",
593+
"Input": "{}",
594+
"RoleArn": {
595+
"Fn::GetAtt": [
596+
"TaskDefEventsRoleFB3B67B8",
597+
"Arn"
598+
]
599+
}
559600
}
560601
]
561602
}

packages/@aws-cdk-testing/framework-integ/test/aws-events-targets/test/ecs/integ.event-fargate-task.js.snapshot/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"validateOnSynth": false,
1818
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
1919
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
20-
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4eef78557096850d7999e5b8dea53c92d3dfecb6360d19bff549c574432580e1.json",
20+
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/f54d2ab12d74af5412f68984953da28f5db17e8b02e7df30a6e02393dbdc1da1.json",
2121
"requiresBootstrapStackVersion": 6,
2222
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
2323
"additionalDependencies": [

packages/@aws-cdk-testing/framework-integ/test/aws-events-targets/test/ecs/integ.event-fargate-task.js.snapshot/tree.json

+41
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,47 @@
971971
}
972972
},
973973
"input": "{\"containerOverrides\":[{\"name\":\"TheContainer\",\"environment\":[{\"name\":\"I_WAS_TRIGGERED\",\"value\":\"From CloudWatch Events\"}]}]}"
974+
},
975+
{
976+
"id": "Target1",
977+
"arn": {
978+
"Fn::GetAtt": [
979+
"EcsCluster97242B84",
980+
"Arn"
981+
]
982+
},
983+
"roleArn": {
984+
"Fn::GetAtt": [
985+
"TaskDefEventsRoleFB3B67B8",
986+
"Arn"
987+
]
988+
},
989+
"ecsParameters": {
990+
"taskCount": 1,
991+
"taskDefinitionArn": {
992+
"Ref": "TaskDef54694570"
993+
},
994+
"launchType": "FARGATE",
995+
"networkConfiguration": {
996+
"awsVpcConfiguration": {
997+
"subnets": [
998+
{
999+
"Ref": "VpcPublicSubnet1Subnet5C2D37C4"
1000+
}
1001+
],
1002+
"assignPublicIp": "ENABLED",
1003+
"securityGroups": [
1004+
{
1005+
"Fn::GetAtt": [
1006+
"TaskDefSecurityGroupD50E7CF0",
1007+
"GroupId"
1008+
]
1009+
}
1010+
]
1011+
}
1012+
}
1013+
},
1014+
"input": "{}"
9741015
}
9751016
]
9761017
}

packages/@aws-cdk-testing/framework-integ/test/aws-events-targets/test/ecs/integ.event-fargate-task.ts

+10
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ rule.addTarget(new targets.EcsTask({
5151
],
5252
}));
5353

54+
// add public EcsTask as the target of the Rule
55+
rule.addTarget(
56+
new targets.EcsTask({
57+
cluster,
58+
taskDefinition,
59+
assignPublicIp: true,
60+
subnetSelection: { subnetType: ec2.SubnetType.PUBLIC },
61+
}),
62+
);
63+
5464
new integ.IntegTest(app, 'EcsFargateTest', {
5565
testCases: [stack],
5666
});

packages/aws-cdk-lib/aws-events-targets/README.md

+31-2
Original file line numberDiff line numberDiff line change
@@ -388,15 +388,44 @@ rule.addTarget(
388388
);
389389
```
390390

391-
### enable Amazon ECS Exec for ECS Task
391+
### Assign public IP addresses to tasks
392392

393-
If you use Amazon ECS Exec, you can run commands in or get a shell to a container running on an Amazon EC2 instance or on AWS Fargate.
393+
You can set the `assignPublicIp` flag to assign public IP addresses to tasks.
394+
If you want to detach the public IP address from the task, you have to set the flag `false`.
395+
You can specify the flag `true` only when the launch type is set to FARGATE.
394396

395397
```ts
396398
import * as ecs from "aws-cdk-lib/aws-ecs"
397399
declare const cluster: ecs.ICluster
398400
declare const taskDefinition: ecs.TaskDefinition
401+
402+
const rule = new events.Rule(this, 'Rule', {
403+
schedule: events.Schedule.rate(cdk.Duration.hours(1)),
404+
});
405+
406+
rule.addTarget(
407+
new targets.EcsTask({
408+
cluster,
409+
taskDefinition,
410+
assignPublicIp: true,
411+
subnetSelection: { subnetType: ec2.SubnetType.PUBLIC },
412+
}),
413+
);
399414
declare const rule: events.Rule
415+
```
416+
417+
### enable Amazon ECS Exec for ECS Task
418+
419+
If you use Amazon ECS Exec, you can run commands in or get a shell to a container running on an Amazon EC2 instance or on AWS Fargate.
420+
421+
```ts
422+
import * as ecs from "aws-cdk-lib/aws-ecs"
423+
declare const cluster: ecs.ICluster
424+
declare const taskDefinition: ecs.TaskDefinition
425+
426+
const rule = new events.Rule(this, 'Rule', {
427+
schedule: events.Schedule.rate(cdk.Duration.hours(1)),
428+
});
400429

401430
rule.addTarget(new targets.EcsTask({
402431
cluster,

packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ export interface EcsTaskProps extends TargetBaseProps {
9696
*/
9797
readonly platformVersion?: ecs.FargatePlatformVersion;
9898

99+
/**
100+
* Specifies whether the task's elastic network interface receives a public IP address.
101+
* You can specify true only when LaunchType is set to FARGATE.
102+
*
103+
* @default - true if the subnet type is PUBLIC, otherwise false
104+
*/
105+
readonly assignPublicIp?: boolean;
106+
99107
/**
100108
* Specifies whether to propagate the tags from the task definition to the task. If no value is specified, the tags are not propagated.
101109
*
@@ -144,6 +152,7 @@ export class EcsTask implements events.IRuleTarget {
144152
private readonly taskCount: number;
145153
private readonly role: iam.IRole;
146154
private readonly platformVersion?: ecs.FargatePlatformVersion;
155+
private readonly assignPublicIp?: boolean;
147156
private readonly propagateTags?: ecs.PropagatedTagSource;
148157
private readonly tags?: Tag[];
149158
private readonly enableExecuteCommand?: boolean;
@@ -157,6 +166,7 @@ export class EcsTask implements events.IRuleTarget {
157166
this.taskDefinition = props.taskDefinition;
158167
this.taskCount = props.taskCount ?? 1;
159168
this.platformVersion = props.platformVersion;
169+
this.assignPublicIp = props.assignPublicIp;
160170
this.enableExecuteCommand = props.enableExecuteCommand;
161171

162172
const propagateTagsValidValues = [ecs.PropagatedTagSource.TASK_DEFINITION, ecs.PropagatedTagSource.NONE];
@@ -212,14 +222,24 @@ export class EcsTask implements events.IRuleTarget {
212222
const enableExecuteCommand = this.enableExecuteCommand;
213223

214224
const subnetSelection = this.props.subnetSelection || { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS };
215-
const assignPublicIp = subnetSelection.subnetType === ec2.SubnetType.PUBLIC ? 'ENABLED' : 'DISABLED';
225+
226+
// throw an error if assignPublicIp is true and the subnet type is not PUBLIC
227+
if (this.assignPublicIp && subnetSelection.subnetType !== ec2.SubnetType.PUBLIC) {
228+
throw new Error('assignPublicIp should be set to true only for PUBLIC subnets');
229+
}
230+
231+
const assignPublicIp = (this.assignPublicIp ?? subnetSelection.subnetType === ec2.SubnetType.PUBLIC) ? 'ENABLED' : 'DISABLED';
232+
const launchType = this.taskDefinition.isEc2Compatible ? 'EC2' : 'FARGATE';
233+
if (assignPublicIp === 'ENABLED' && launchType !== 'FARGATE') {
234+
throw new Error('assignPublicIp is only supported for FARGATE tasks');
235+
};
216236

217237
const baseEcsParameters = { taskCount, taskDefinitionArn, propagateTags, tagList, enableExecuteCommand };
218238

219239
const ecsParameters: events.CfnRule.EcsParametersProperty = this.taskDefinition.networkMode === ecs.NetworkMode.AWS_VPC
220240
? {
221241
...baseEcsParameters,
222-
launchType: this.taskDefinition.isEc2Compatible ? 'EC2' : 'FARGATE',
242+
launchType,
223243
platformVersion: this.platformVersion,
224244
networkConfiguration: {
225245
awsVpcConfiguration: {

packages/aws-cdk-lib/aws-events-targets/test/ecs/event-rule-target.test.ts

+134
Original file line numberDiff line numberDiff line change
@@ -920,3 +920,137 @@ test('sets tag lists', () => {
920920
],
921921
});
922922
});
923+
924+
test('enable assignPublicIp for public subnet', () => {
925+
// GIVEN
926+
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef');
927+
taskDefinition.addContainer('TheContainer', {
928+
image: ecs.ContainerImage.fromRegistry('henk'),
929+
});
930+
931+
const rule = new events.Rule(stack, 'Rule', {
932+
schedule: events.Schedule.expression('rate(1 min)'),
933+
});
934+
935+
// WHEN
936+
rule.addTarget(new targets.EcsTask({
937+
cluster,
938+
taskDefinition,
939+
taskCount: 1,
940+
containerOverrides: [{
941+
containerName: 'TheContainer',
942+
command: ['echo', events.EventField.fromPath('$.detail.event')],
943+
}],
944+
subnetSelection: { subnetType: ec2.SubnetType.PUBLIC },
945+
assignPublicIp: true,
946+
}));
947+
948+
// THEN
949+
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
950+
Targets: [
951+
{
952+
EcsParameters: {
953+
NetworkConfiguration: {
954+
AwsVpcConfiguration: {
955+
AssignPublicIp: 'ENABLED',
956+
},
957+
},
958+
},
959+
},
960+
],
961+
});
962+
});
963+
964+
test('DISABLE is set when disable assignPublicIp in a public subnet', () => {
965+
// GIVEN
966+
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef');
967+
taskDefinition.addContainer('TheContainer', {
968+
image: ecs.ContainerImage.fromRegistry('henk'),
969+
});
970+
971+
const rule = new events.Rule(stack, 'Rule', {
972+
schedule: events.Schedule.expression('rate(1 min)'),
973+
});
974+
975+
// WHEN
976+
rule.addTarget(new targets.EcsTask({
977+
cluster,
978+
taskDefinition,
979+
taskCount: 1,
980+
containerOverrides: [{
981+
containerName: 'TheContainer',
982+
command: ['echo', events.EventField.fromPath('$.detail.event')],
983+
}],
984+
subnetSelection: { subnetType: ec2.SubnetType.PUBLIC },
985+
assignPublicIp: false,
986+
}));
987+
988+
// THEN
989+
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
990+
Targets: [
991+
{
992+
EcsParameters: {
993+
NetworkConfiguration: {
994+
AwsVpcConfiguration: {
995+
AssignPublicIp: 'DISABLED',
996+
},
997+
},
998+
},
999+
},
1000+
],
1001+
});
1002+
});
1003+
1004+
test('throw error when enable assignPublicIp for non-Fargate task', () => {
1005+
// GIVEN
1006+
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef', {});
1007+
taskDefinition.addContainer('TheContainer', {
1008+
image: ecs.ContainerImage.fromRegistry('henk'),
1009+
});
1010+
1011+
const rule = new events.Rule(stack, 'Rule', {
1012+
schedule: events.Schedule.expression('rate(1 min)'),
1013+
});
1014+
1015+
// THEN
1016+
expect(() => {
1017+
rule.addTarget(new targets.EcsTask({
1018+
cluster,
1019+
taskDefinition,
1020+
taskCount: 1,
1021+
containerOverrides: [{
1022+
containerName: 'TheContainer',
1023+
command: ['echo', events.EventField.fromPath('$.detail.event')],
1024+
}],
1025+
subnetSelection: { subnetType: ec2.SubnetType.PUBLIC },
1026+
assignPublicIp: true,
1027+
}));
1028+
}).toThrowError('assignPublicIp is only supported for FARGATE tasks');
1029+
});
1030+
1031+
test('throw an error when assignPublicIp is set to true for private subnets', () => {
1032+
// GIVEN
1033+
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef');
1034+
taskDefinition.addContainer('TheContainer', {
1035+
image: ecs.ContainerImage.fromRegistry('henk'),
1036+
});
1037+
1038+
const rule = new events.Rule(stack, 'Rule', {
1039+
schedule: events.Schedule.expression('rate(1 min)'),
1040+
});
1041+
1042+
// THEN
1043+
expect(() => {
1044+
rule.addTarget(new targets.EcsTask({
1045+
cluster,
1046+
taskDefinition,
1047+
taskCount: 1,
1048+
containerOverrides: [{
1049+
containerName: 'TheContainer',
1050+
command: ['echo', events.EventField.fromPath('$.detail.event')],
1051+
}],
1052+
subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
1053+
assignPublicIp: true,
1054+
}));
1055+
}).toThrowError('assignPublicIp should be set to true only for PUBLIC subnets');
1056+
});

0 commit comments

Comments
 (0)