packages/aws-cdk-lib/aws-scheduler-targets/lib/ecs-run-task.ts (172 lines of code) (raw):

import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target'; import * as ec2 from '../../aws-ec2'; import * as ecs from '../../aws-ecs'; import { IRole, PolicyStatement } from '../../aws-iam'; import { ISchedule, IScheduleTarget, ScheduleTargetConfig } from '../../aws-scheduler'; import { Lazy, ValidationError } from '../../core'; /** * 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, }, }, }), }, }; } }