Skip to content

Commit

Permalink
feat(event-targets): add support for fargate/awsvpc tasks (#2707)
Browse files Browse the repository at this point in the history
The target is "enhanced" using an AwsCustomResource calling `CloudWatchEvents.putTargets`.

Fix wrong reference to container name in `containerOverrides` (must be `name`).

BREAKING CHANGE: `targets.EcsEc2Task` renamed to `targets.EcsTask`
  • Loading branch information
jogold authored and rix0rrr committed Jun 3, 2019
1 parent ae4a04f commit 2754dde
Show file tree
Hide file tree
Showing 13 changed files with 1,508 additions and 191 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class ScheduledEc2Task extends cdk.Construct {
});

// Use Ec2TaskEventRuleTarget as the target of the EventRule
const eventRuleTarget = new eventsTargets.EcsEc2Task( {
const eventRuleTarget = new eventsTargets.EcsTask( {
cluster: props.cluster,
taskDefinition,
taskCount: props.desiredTaskCount
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ you can configure on your instances.
## Integration with CloudWatch Events

To start an Amazon ECS task on an Amazon EC2-backed Cluster, instantiate an
`@aws-cdk/aws-events-targets.EcsEc2Task` instead of an `Ec2Service`:
`@aws-cdk/aws-events-targets.EcsTask` instead of an `Ec2Service`:

```ts
import targets = require('@aws-cdk/aws-events-targets');
Expand Down
125 changes: 0 additions & 125 deletions packages/@aws-cdk/aws-events-targets/lib/ecs-ec2-task.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@ export interface TaskEnvironmentVariable {
* Exactly one of `value` and `valuePath` must be specified.
*/
readonly value: string;
}
}
170 changes: 170 additions & 0 deletions packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import cloudformation = require('@aws-cdk/aws-cloudformation');
import ec2 = require('@aws-cdk/aws-ec2');
import ecs = require('@aws-cdk/aws-ecs');
import events = require ('@aws-cdk/aws-events');
import iam = require('@aws-cdk/aws-iam');
import { ContainerOverride } from './ecs-task-properties';
import { singletonEventRole } from './util';

/**
* Properties to define an ECS Event Task
*/
export interface EcsTaskProps {
/**
* Cluster where service will be deployed
*/
readonly cluster: ecs.ICluster;

/**
* Task Definition of the task that should be started
*/
readonly taskDefinition: ecs.TaskDefinition;

/**
* How many tasks should be started when this event is triggered
*
* @default 1
*/
readonly taskCount?: number;

/**
* Container setting overrides
*
* Key is the name of the container to override, value is the
* values you want to override.
*/
readonly containerOverrides?: ContainerOverride[];

/**
* In what subnets to place the task's ENIs
*
* (Only applicable in case the TaskDefinition is configured for AwsVpc networking)
*
* @default Private subnets
*/
readonly subnetSelection?: ec2.SubnetSelection;

/**
* Existing security group to use for the task's ENIs
*
* (Only applicable in case the TaskDefinition is configured for AwsVpc networking)
*
* @default A new security group is created
*/
readonly securityGroup?: ec2.ISecurityGroup;
}

/**
* Start a task on an ECS cluster
*/
export class EcsTask implements events.IRuleTarget {
public readonly securityGroup?: ec2.ISecurityGroup;
private readonly cluster: ecs.ICluster;
private readonly taskDefinition: ecs.TaskDefinition;
private readonly taskCount: number;

constructor(private readonly props: EcsTaskProps) {
this.cluster = props.cluster;
this.taskDefinition = props.taskDefinition;
this.taskCount = props.taskCount !== undefined ? props.taskCount : 1;

if (this.taskDefinition.networkMode === ecs.NetworkMode.AwsVpc) {
this.securityGroup = props.securityGroup || new ec2.SecurityGroup(this.taskDefinition, 'SecurityGroup', { vpc: this.props.cluster.vpc });
}
}

/**
* Allows using tasks as target of CloudWatch events
*/
public bind(rule: events.IRule): events.RuleTargetProperties {
const policyStatements = [new iam.PolicyStatement()
.addAction('ecs:RunTask')
.addResource(this.taskDefinition.taskDefinitionArn)
.addCondition('ArnEquals', { "ecs:cluster": this.cluster.clusterArn })
];

// If it so happens that a Task Execution Role was created for the TaskDefinition,
// then the CloudWatch Events Role must have permissions to pass it (otherwise it doesn't).
if (this.taskDefinition.executionRole !== undefined) {
policyStatements.push(new iam.PolicyStatement()
.addAction('iam:PassRole')
.addResource(this.taskDefinition.executionRole.roleArn));
}

// For Fargate task we need permission to pass the task role.
if (this.taskDefinition.isFargateCompatible) {
policyStatements.push(new iam.PolicyStatement()
.addAction('iam:PassRole')
.addResource(this.taskDefinition.taskRole.roleArn));
}

const id = this.taskDefinition.node.id + '-on-' + this.cluster.node.id;
const arn = this.cluster.clusterArn;
const role = singletonEventRole(this.taskDefinition, policyStatements);
const containerOverrides = this.props.containerOverrides && this.props.containerOverrides
.map(({ containerName, ...overrides }) => ({ name: containerName, ...overrides }));
const input = { containerOverrides };
const taskCount = this.taskCount;
const taskDefinitionArn = this.taskDefinition.taskDefinitionArn;

// Use a custom resource to "enhance" the target with network configuration
// when using awsvpc network mode.
if (this.taskDefinition.networkMode === ecs.NetworkMode.AwsVpc) {
const subnetSelection = this.props.subnetSelection || { subnetType: ec2.SubnetType.Private };
const assignPublicIp = subnetSelection.subnetType === ec2.SubnetType.Private ? 'DISABLED' : 'ENABLED';

new cloudformation.AwsCustomResource(this.taskDefinition, 'PutTargets', {
// `onCreate´ defaults to `onUpdate` and we don't need an `onDelete` here
// because the rule/target will be owned by CF anyway.
onUpdate: {
service: 'CloudWatchEvents',
apiVersion: '2015-10-07',
action: 'putTargets',
parameters: {
Rule: this.taskDefinition.node.stack.parseArn(rule.ruleArn).resourceName,
Targets: [
{
Arn: arn,
Id: id,
EcsParameters: {
TaskDefinitionArn: taskDefinitionArn,
LaunchType: this.taskDefinition.isEc2Compatible ? 'EC2' : 'FARGATE',
NetworkConfiguration: {
awsvpcConfiguration: {
Subnets: this.props.cluster.vpc.selectSubnets(subnetSelection).subnetIds,
AssignPublicIp: assignPublicIp,
SecurityGroups: this.securityGroup && [this.securityGroup.securityGroupId],
}
},
TaskCount: taskCount,
},
Input: JSON.stringify(input),
RoleArn: role.roleArn
}
]
},
physicalResourceId: id,
},
policyStatements: [ // Cannot use automatic policy statements because we need iam:PassRole
new iam.PolicyStatement()
.addAction('events:PutTargets')
.addResource(rule.ruleArn),
new iam.PolicyStatement()
.addAction('iam:PassRole')
.addResource(role.roleArn)
]
});
}

return {
id,
arn,
role,
ecsParameters: {
taskCount,
taskDefinitionArn
},
input: events.RuleTargetInput.fromObject(input)
};
}
}
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-events-targets/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ export * from './sns';
export * from './codebuild';
export * from './lambda';
export * from './ecs-task-properties';
export * from './ecs-ec2-task';
export * from './ecs-task';
export * from './state-machine';
4 changes: 3 additions & 1 deletion packages/@aws-cdk/aws-events-targets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"pkglint": "^0.33.0"
},
"dependencies": {
"@aws-cdk/aws-cloudformation": "^0.33.0",
"@aws-cdk/aws-codebuild": "^0.33.0",
"@aws-cdk/aws-codepipeline": "^0.33.0",
"@aws-cdk/aws-ec2": "^0.33.0",
Expand All @@ -93,6 +94,7 @@
},
"homepage": "https://github.com/awslabs/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-cloudformation": "^0.33.0",
"@aws-cdk/aws-codebuild": "^0.33.0",
"@aws-cdk/aws-codepipeline": "^0.33.0",
"@aws-cdk/aws-ec2": "^0.33.0",
Expand All @@ -107,4 +109,4 @@
"engines": {
"node": ">= 8.10.0"
}
}
}
Loading

0 comments on commit 2754dde

Please sign in to comment.