Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion packages/@aws-cdk/aws-scheduler-targets-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ The following targets are supported:
9. `targets.FirehosePutRecord`: [Put a record to an Amazon Data Firehose](#put-a-record-to-an-amazon-data-firehose)
10. `targets.CodePipelineStartPipelineExecution`: [Start a CodePipeline execution](#start-a-codepipeline-execution)
11. `targets.SageMakerStartPipelineExecution`: [Start a SageMaker pipeline execution](#start-a-sagemaker-pipeline-execution)
12. `targets.Universal`: [Invoke a wider set of AWS API](#invoke-a-wider-set-of-aws-api)
12. `targets.EcsRunTask`: [Start a new ECS task](#schedule-an-ecs-task-run)
13. `targets.Universal`: [Invoke a wider set of AWS API](#invoke-a-wider-set-of-aws-api)

## Invoke a Lambda function

Expand Down Expand Up @@ -316,6 +317,42 @@ new Schedule(this, 'Schedule', {
});
```

## Schedule an ECS task run

Use the `EcsRunTask` target to schedule an ECS task run for a cluster.

The code snippet below creates an event rule with a Fargate task definition and cluster as the target which is called every hour by EventBridge Scheduler.

```ts
import * as ecs from 'aws-cdk-lib/aws-ecs';

declare const cluster: ecs.ICluster;
declare const taskDefinition: ecs.FargateTaskDefinition;

new Schedule(this, 'Schedule', {
schedule: ScheduleExpression.rate(cdk.Duration.minutes(60)),
target: new targets.EcsRunFargateTask(cluster, {
taskDefinition,
}),
});
```

The code snippet below creates an event rule with a EC2 task definition and cluster as the target which is called every hour by EventBridge Scheduler.

```ts
import * as ecs from 'aws-cdk-lib/aws-ecs';

declare const cluster: ecs.ICluster;
declare const taskDefinition: ecs.Ec2TaskDefinition;

new Schedule(this, 'Schedule', {
schedule: ScheduleExpression.rate(cdk.Duration.minutes(60)),
target: new targets.EcsRunEc2Task(cluster, {
taskDefinition,
}),
});
```

## Invoke a wider set of AWS API

Use the `Universal` target to invoke AWS API. See <https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html>
Expand Down
334 changes: 334 additions & 0 deletions packages/@aws-cdk/aws-scheduler-targets-alpha/lib/ecs-run-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
import { ISchedule, IScheduleTarget, ScheduleTargetConfig } from '@aws-cdk/aws-scheduler-alpha';
import { Lazy, ValidationError } from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import { IRole, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target';

/**
* Metadata that you apply to a resource to help categorize and organize the resource. Each tag consists of a key and an optional value, both of which you define.
*/
export interface Tag {
/**
* Key is the name of the tag
*/
readonly key: string;
/**
* Value is the metadata contents of the tag
*/
readonly value: string;
}

/**
* Parameters for scheduling ECS Run Task (common to EC2 and Fargate launch types).
*/
export interface EcsRunTaskBaseProps extends ScheduleTargetBaseProps {
/**
* The task definition to use for scheduled tasks.
*
* Note: this must be TaskDefinition, and not ITaskDefinition,
* as it requires properties that are not known for imported task definitions
* If you want to run a RunTask with an imported task definition,
* consider using a Universal target.
*/
readonly taskDefinition: ecs.TaskDefinition;

/**
* The capacity provider strategy to use for the task.
*
* @default - No capacity provider strategy
*/
readonly capacityProviderStrategies?: ecs.CapacityProviderStrategy[];

/**
* The subnets associated with the task. These subnets must all be in the same VPC.
* The task will be launched in these subnets.
*
* @default - all private subnets of the VPC are selected.
*/
readonly vpcSubnets?: ec2.SubnetSelection;

/**
* The security groups associated with the task. These security groups must all be in the same VPC.
* Controls inbound and outbound network access for the task.
*
* @default - The security group for the VPC is used.
*/
readonly securityGroups?: ec2.ISecurityGroup[];

/**
* Specifies whether to enable Amazon ECS managed tags for the task.
* @default - false
*/
readonly enableEcsManagedTags?: boolean;

/**
* Whether to enable execute command functionality for the containers in this task.
* If true, this enables execute command functionality on all containers in the task.
*
* @default - false
*/
readonly enableExecuteCommand?: boolean;

/**
* Specifies an ECS task group for the task.
*
* @default - No group
*/
readonly group?: string;

/**
* Specifies whether to propagate the tags from the task definition to the task.
* If no value is specified, the tags are not propagated.
*
* @default - No tag propagation
*/
readonly propagateTags?: boolean;

/**
* The reference ID to use for the task.
*
* @default - No reference ID.
*/
readonly referenceId?: string;

/**
* The metadata that you apply to the task to help you categorize and organize them.
* Each tag consists of a key and an optional value, both of which you define.
*
* @default - No tags
*/
readonly tags?: Tag[];

/**
* The number of tasks to create based on TaskDefinition.
*
* @default 1
*/
readonly taskCount?: number;

}

/**
* Properties for scheduling an ECS Fargate Task.
*/
export interface FargateTaskProps extends EcsRunTaskBaseProps {
/**
* Specifies whether the task's elastic network interface receives a public IP address.
* If true, the task will receive a public IP address and be accessible from the internet.
* Should only be set to true when using public subnets.
*
* @default - true if the subnet type is PUBLIC, otherwise false
*/
readonly assignPublicIp?: boolean;

/**
* Specifies the platform version for the task.
* Specify only the numeric portion of the platform version, such as 1.1.0.
* Platform versions determine the underlying runtime environment for the task.
*
* @default - LATEST
*/
readonly platformVersion?: ecs.FargatePlatformVersion;
}

/**
* Properties for scheduling an ECS Task on EC2.
*/
export interface Ec2TaskProps extends EcsRunTaskBaseProps {
/**
* The rules that must be met in order to place a task on a container instance.
*
* @default - No placement constraints.
*/
readonly placementConstraints?: ecs.PlacementConstraint[];

/**
* The algorithm for selecting container instances for task placement.
*
* @default - No placement strategies.
*/
readonly placementStrategies?: ecs.PlacementStrategy[];
}

/**
* Schedule an ECS Task using AWS EventBridge Scheduler.
*/
export abstract class EcsRunTask extends ScheduleTargetBase implements IScheduleTarget {
constructor(
protected readonly cluster: ecs.ICluster,
protected readonly props: EcsRunTaskBaseProps,
) {
super(props, cluster.clusterArn);
}

protected addTargetActionToRole(role: IRole): void {
// grantRun already adds the necessary PassRole permissions for both task role and execution role
this.props.taskDefinition.grantRun(role);

// Add permissions for tagging if needed
if (this.props.propagateTags === true || this.props.tags) {
role.addToPrincipalPolicy(new PolicyStatement({
actions: ['ecs:TagResource'],
resources: [`arn:${this.cluster.stack.partition}:ecs:${this.cluster.env.region}:${this.props.taskDefinition.env.account}:task/${this.cluster.clusterName}/*`],
}));
}
}

protected bindBaseTargetConfig(_schedule: ISchedule): ScheduleTargetConfig {
return {
...super.bindBaseTargetConfig(_schedule),
ecsParameters: {
taskDefinitionArn: this.props.taskDefinition.taskDefinitionArn,
capacityProviderStrategy: this.props.capacityProviderStrategies,
taskCount: this.props.taskCount,
tags: this.props.tags,
propagateTags: this.props.propagateTags ? ecs.PropagatedTagSource.TASK_DEFINITION : undefined,
enableEcsManagedTags: this.props.enableEcsManagedTags,
enableExecuteCommand: this.props.enableExecuteCommand,
group: this.props.group,
referenceId: this.props.referenceId,
},
};
}
}

/**
* Schedule an ECS Task on Fargate using AWS EventBridge Scheduler.
*/
export class EcsRunFargateTask extends EcsRunTask {
private readonly subnetSelection?: ec2.SubnetSelection;
private readonly assignPublicIp?: boolean;
private readonly platformVersion?: string;
private readonly capacityProviderStrategies?: ecs.CapacityProviderStrategy[];

constructor(
cluster: ecs.ICluster,
props: FargateTaskProps,
) {
super(cluster, props);
this.subnetSelection = props.vpcSubnets;
this.assignPublicIp = props.assignPublicIp;
this.platformVersion = props.platformVersion;
this.capacityProviderStrategies = props.capacityProviderStrategies;
}

protected bindBaseTargetConfig(_schedule: ISchedule): ScheduleTargetConfig {
if (!this.props.taskDefinition.isFargateCompatible) {
throw new ValidationError('TaskDefinition is not compatible with Fargate launch type.', _schedule);
}

const subnetSelection = this.subnetSelection || { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS };

// Throw an error if assignPublicIp is true and the subnet type is not public
if (this.assignPublicIp && subnetSelection.subnetType !== ec2.SubnetType.PUBLIC) {
throw new ValidationError('assignPublicIp should be set to true only for public subnets', _schedule);
}

const assignPublicIp = this.assignPublicIp !== undefined
? (this.assignPublicIp ? 'ENABLED' : 'DISABLED')
: (subnetSelection.subnetType === ec2.SubnetType.PUBLIC ? 'ENABLED' : 'DISABLED');

// Only one of capacityProviderStrategies or launchType can be set
// See https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html#ECS-RunTask-request-launchType
const launchType = this.capacityProviderStrategies ? undefined : ecs.LaunchType.FARGATE;

const bindBaseTargetConfigParameters = super.bindBaseTargetConfig(_schedule).ecsParameters!;

return {
...super.bindBaseTargetConfig(_schedule),
ecsParameters: {
...bindBaseTargetConfigParameters,
launchType,
platformVersion: this.platformVersion,
networkConfiguration: {
awsvpcConfiguration: {
assignPublicIp,
subnets: this.cluster.vpc.selectSubnets(subnetSelection).subnetIds,
securityGroups: (this.props.securityGroups && this.props.securityGroups.length > 0)
?
this.props.securityGroups?.map((sg) => sg.securityGroupId)
: undefined,
},
},
},
};
}
}

/**
* Schedule an ECS Task on EC2 using AWS EventBridge Scheduler.
*/
export class EcsRunEc2Task extends EcsRunTask {
private readonly capacityProviderStrategies?: ecs.CapacityProviderStrategy[];
private readonly placementConstraints?: ecs.PlacementConstraint[];
private readonly placementStrategies?: ecs.PlacementStrategy[];

constructor(
cluster: ecs.ICluster,
props: Ec2TaskProps,
) {
super(cluster, props);
this.placementConstraints = props.placementConstraints;
this.placementStrategies = props.placementStrategies;
this.capacityProviderStrategies = props.capacityProviderStrategies;
}

protected bindBaseTargetConfig(_schedule: ISchedule): ScheduleTargetConfig {
if (this.props.taskDefinition.compatibility === ecs.Compatibility.FARGATE) {
throw new ValidationError('TaskDefinition is not compatible with EC2 launch type', _schedule);
}

// Only one of capacityProviderStrategy or launchType can be set
// See https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html#ECS-RunTask-request-launchType
const launchType = this.capacityProviderStrategies ? undefined : ecs.LaunchType.EC2;

const taskDefinitionUsesAwsVpc = this.props.taskDefinition.networkMode === ecs.NetworkMode.AWS_VPC;

// Security groups are only configurable with the "awsvpc" network mode.
// See https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html#ECS-RunTask-request-networkConfiguration
if (!taskDefinitionUsesAwsVpc && (this.props.securityGroups || this.props.vpcSubnets)) {
throw new ValidationError('Security groups and subnets can only be used with awsvpc network mode', _schedule);
}

const subnetSelection =
taskDefinitionUsesAwsVpc ? this.props.vpcSubnets || { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }
: undefined;

const bindBaseTargetConfigParameters = super.bindBaseTargetConfig(_schedule).ecsParameters!;

return {
...super.bindBaseTargetConfig(_schedule),
ecsParameters: {
...bindBaseTargetConfigParameters,
launchType,
placementConstraints: Lazy.any({
produce: () => {
// Only map if placementConstraints is defined and has items
return this.placementConstraints?.length
? this.placementConstraints?.map((constraint) => constraint.toJson()).flat()
: undefined;
},
}),
placementStrategy: Lazy.any({
produce: () => {
return this.placementStrategies?.length
? this.placementStrategies?.map((strategy) => strategy.toJson()).flat()
: undefined;
},
}, { omitEmptyArray: true }),
... (taskDefinitionUsesAwsVpc && {
networkConfiguration: {
awsvpcConfiguration: {
subnets: this.cluster.vpc.selectSubnets(subnetSelection).subnetIds,
securityGroups: (this.props.securityGroups && this.props.securityGroups.length > 0)
?
this.props.securityGroups.map((sg) => sg.securityGroupId)
: undefined,
},
},
}),
},
};
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-scheduler-targets-alpha/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './codebuild-start-build';
export * from './codepipeline-start-pipeline-execution';
export * from './event-bridge-put-events';
export * from './ecs-run-task';
export * from './inspector-start-assessment-run';
export * from './firehose-put-record';
export * from './kinesis-stream-put-record';
Expand Down
Loading