-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat(scheduler-targets): EcsRunTask scheduler target #33697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
65e6293
ecs runtask wip
gracelu0 7604171
Add integration tests and readme
gracelu0 a60e30c
fix README
gracelu0 863b23d
address comments
gracelu0 6519994
allow awsvpc config for ec2 tasks and add fargate platform version type
gracelu0 08fc170
make EcsRunTask classes public and add unit tests
gracelu0 66721c4
Merge branch 'main' into ecs-run-task-scheduler-target
gracelu0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
334 changes: 334 additions & 0 deletions
334
packages/@aws-cdk/aws-scheduler-targets-alpha/lib/ecs-run-task.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
gracelu0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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, | ||
| }, | ||
| }, | ||
| }), | ||
| }, | ||
| }; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.