packages/aws-cdk-lib/aws-batch/lib/ecs-container-definition.ts (422 lines of code) (raw):
import { Construct, IConstruct } from 'constructs';
import { CfnJobDefinition } from './batch.generated';
import { LinuxParameters } from './linux-parameters';
import * as ecs from '../../aws-ecs';
import { IFileSystem } from '../../aws-efs';
import * as iam from '../../aws-iam';
import { LogGroup } from '../../aws-logs';
import * as secretsmanager from '../../aws-secretsmanager';
import * as ssm from '../../aws-ssm';
import { Lazy, PhysicalName, Size, ValidationError } from '../../core';
const EFS_VOLUME_SYMBOL = Symbol.for('aws-cdk-lib/aws-batch/lib/container-definition.EfsVolume');
const HOST_VOLUME_SYMBOL = Symbol.for('aws-cdk-lib/aws-batch/lib/container-definition.HostVolume');
/**
* Specify the secret's version id or version stage
*/
export interface SecretVersionInfo {
/**
* version id of the secret
*
* @default - use default version id
*/
readonly versionId?: string;
/**
* version stage of the secret
*
* @default - use default version stage
*/
readonly versionStage?: string;
}
/**
* A secret environment variable.
*/
export abstract class Secret {
/**
* Creates an environment variable value from a parameter stored in AWS
* Systems Manager Parameter Store.
*/
public static fromSsmParameter(parameter: ssm.IParameter): Secret {
return {
arn: parameter.parameterArn,
grantRead: grantee => parameter.grantRead(grantee),
};
}
/**
* Creates a environment variable value from a secret stored in AWS Secrets
* Manager.
*
* @param secret the secret stored in AWS Secrets Manager
* @param field the name of the field with the value that you want to set as
* the environment variable value. Only values in JSON format are supported.
* If you do not specify a JSON field, then the full content of the secret is
* used.
*/
public static fromSecretsManager(secret: secretsmanager.ISecret, field?: string): Secret {
return {
arn: field ? `${secret.secretArn}:${field}::` : secret.secretArn,
hasField: !!field,
grantRead: grantee => secret.grantRead(grantee),
};
}
/**
* Creates a environment variable value from a secret stored in AWS Secrets
* Manager.
*
* @param secret the secret stored in AWS Secrets Manager
* @param versionInfo the version information to reference the secret
* @param field the name of the field with the value that you want to set as
* the environment variable value. Only values in JSON format are supported.
* If you do not specify a JSON field, then the full content of the secret is
* used.
*/
public static fromSecretsManagerVersion(secret: secretsmanager.ISecret, versionInfo: SecretVersionInfo, field?: string): Secret {
return {
arn: `${secret.secretArn}:${field ?? ''}:${versionInfo.versionStage ?? ''}:${versionInfo.versionId ?? ''}`,
hasField: !!field,
grantRead: grantee => secret.grantRead(grantee),
};
}
/**
* The ARN of the secret
*/
public abstract readonly arn: string;
/**
* Whether this secret uses a specific JSON field
*/
public abstract readonly hasField?: boolean;
/**
* Grants reading the secret to a principal
*/
public abstract grantRead(grantee: iam.IGrantable): iam.Grant;
}
/**
* Options to configure an EcsVolume
*/
export interface EcsVolumeOptions {
/**
* the name of this volume
*/
readonly name: string;
/**
* the path on the container where this volume is mounted
*/
readonly containerPath: string;
/**
* if set, the container will have readonly access to the volume
*
* @default false
*/
readonly readonly?: boolean;
}
/**
* Represents a Volume that can be mounted to a container that uses ECS
*/
export abstract class EcsVolume {
/**
* Creates a Volume that uses an AWS Elastic File System (EFS); this volume can grow and shrink as needed
*
* @see https://docs.aws.amazon.com/batch/latest/userguide/efs-volumes.html
*/
static efs(options: EfsVolumeOptions) {
return new EfsVolume(options);
}
/**
* Creates a Host volume. This volume will persist on the host at the specified `hostPath`.
* If the `hostPath` is not specified, Docker will choose the host path. In this case,
* the data may not persist after the containers that use it stop running.
*/
static host(options: HostVolumeOptions) {
return new HostVolume(options);
}
/**
* The name of this volume
*/
public readonly name: string;
/**
* The path on the container that this volume will be mounted to
*/
public readonly containerPath: string;
/**
* Whether or not the container has readonly access to this volume
*
* @default false
*/
public readonly readonly?: boolean;
constructor(options: EcsVolumeOptions) {
this.name = options.name;
this.containerPath = options.containerPath;
this.readonly = options.readonly;
}
}
/**
* Options for configuring an EfsVolume
*/
export interface EfsVolumeOptions extends EcsVolumeOptions {
/**
* The EFS File System that supports this volume
*/
readonly fileSystem: IFileSystem;
/**
* The directory within the Amazon EFS file system to mount as the root directory inside the host.
* If this parameter is omitted, the root of the Amazon EFS volume is used instead.
* Specifying `/` has the same effect as omitting this parameter.
* The maximum length is 4,096 characters.
*
* @default - root of the EFS File System
*/
readonly rootDirectory?: string;
/**
* Enables encryption for Amazon EFS data in transit between the Amazon ECS host and the Amazon EFS server
*
* @see https://docs.aws.amazon.com/efs/latest/ug/encryption-in-transit.html
*
* @default false
*/
readonly enableTransitEncryption?: boolean;
/**
* The port to use when sending encrypted data between the Amazon ECS host and the Amazon EFS server.
* The value must be between 0 and 65,535.
*
* @see https://docs.aws.amazon.com/efs/latest/ug/efs-mount-helper.html
*
* @default - chosen by the EFS Mount Helper
*/
readonly transitEncryptionPort?: number;
/**
* The Amazon EFS access point ID to use.
* If an access point is specified, `rootDirectory` must either be omitted or set to `/`
* which enforces the path set on the EFS access point.
* If an access point is used, `enableTransitEncryption` must be `true`.
*
* @see https://docs.aws.amazon.com/efs/latest/ug/efs-access-points.html
*
* @default - no accessPointId
*/
readonly accessPointId?: string;
/**
* Whether or not to use the AWS Batch job IAM role defined in a job definition when mounting the Amazon EFS file system.
* If specified, `enableTransitEncryption` must be `true`.
*
* @see https://docs.aws.amazon.com/batch/latest/userguide/efs-volumes.html#efs-volume-accesspoints
*
* @default false
*/
readonly useJobRole?: boolean;
}
/**
* A Volume that uses an AWS Elastic File System (EFS); this volume can grow and shrink as needed
*/
export class EfsVolume extends EcsVolume {
/**
* Returns true if x is an EfsVolume, false otherwise
*/
public static isEfsVolume(x: any) : x is EfsVolume {
return x !== null && typeof(x) === 'object' && EFS_VOLUME_SYMBOL in x;
}
/**
* The EFS File System that supports this volume
*/
public readonly fileSystem: IFileSystem;
/**
* The directory within the Amazon EFS file system to mount as the root directory inside the host.
* If this parameter is omitted, the root of the Amazon EFS volume is used instead.
* Specifying `/` has the same effect as omitting this parameter.
* The maximum length is 4,096 characters.
*
* @default - root of the EFS File System
*/
public readonly rootDirectory?: string;
/**
* Enables encryption for Amazon EFS data in transit between the Amazon ECS host and the Amazon EFS server
*
* @see https://docs.aws.amazon.com/efs/latest/ug/encryption-in-transit.html
*
* @default false
*/
public readonly enableTransitEncryption?: boolean;
/**
* The port to use when sending encrypted data between the Amazon ECS host and the Amazon EFS server.
* The value must be between 0 and 65,535.
*
* @see https://docs.aws.amazon.com/efs/latest/ug/efs-mount-helper.html
*
* @default - chosen by the EFS Mount Helper
*/
public readonly transitEncryptionPort?: number;
/**
* The Amazon EFS access point ID to use.
* If an access point is specified, `rootDirectory` must either be omitted or set to `/`
* which enforces the path set on the EFS access point.
* If an access point is used, `enableTransitEncryption` must be `true`.
*
* @see https://docs.aws.amazon.com/efs/latest/ug/efs-access-points.html
*
* @default - no accessPointId
*/
public readonly accessPointId?: string;
/**
* Whether or not to use the AWS Batch job IAM role defined in a job definition when mounting the Amazon EFS file system.
* If specified, `enableTransitEncryption` must be `true`.
*
* @see https://docs.aws.amazon.com/batch/latest/userguide/efs-volumes.html#efs-volume-accesspoints
*
* @default false
*/
public readonly useJobRole?: boolean;
constructor(options: EfsVolumeOptions) {
super(options);
this.fileSystem = options.fileSystem;
this.rootDirectory = options.rootDirectory;
this.enableTransitEncryption = options.enableTransitEncryption;
this.transitEncryptionPort = options.transitEncryptionPort;
this.accessPointId = options.accessPointId;
this.useJobRole = options.useJobRole;
}
}
Object.defineProperty(EfsVolume.prototype, EFS_VOLUME_SYMBOL, {
value: true,
enumerable: false,
writable: false,
});
/**
* Options for configuring an ECS HostVolume
*/
export interface HostVolumeOptions extends EcsVolumeOptions {
/**
* The path on the host machine this container will have access to
*
* @default - Docker will choose the host path.
* The data may not persist after the containers that use it stop running.
*/
readonly hostPath?: string;
}
/**
* Creates a Host volume. This volume will persist on the host at the specified `hostPath`.
* If the `hostPath` is not specified, Docker will choose the host path. In this case,
* the data may not persist after the containers that use it stop running.
*/
export class HostVolume extends EcsVolume {
/**
* returns `true` if `x` is a `HostVolume`, `false` otherwise
*/
public static isHostVolume(x: any): x is HostVolume {
return x !== null && typeof (x) === 'object' && HOST_VOLUME_SYMBOL in x;
}
/**
* The path on the host machine this container will have access to
*/
public readonly hostPath?: string;
constructor(options: HostVolumeOptions) {
super(options);
this.hostPath = options.hostPath;
}
}
Object.defineProperty(HostVolume.prototype, HOST_VOLUME_SYMBOL, {
value: true,
enumerable: false,
writable: false,
});
/**
* A container that can be run with ECS orchestration
*/
export interface IEcsContainerDefinition extends IConstruct {
/**
* The image that this container will run
*/
readonly image: ecs.ContainerImage;
/**
* The number of vCPUs reserved for the container.
* Each vCPU is equivalent to 1,024 CPU shares.
* For containers running on EC2 resources, you must specify at least one vCPU.
*/
readonly cpu: number;
/**
* The memory hard limit present to the container.
* If your container attempts to exceed the memory specified, the container is terminated.
* You must specify at least 4 MiB of memory for a job.
*/
readonly memory: Size;
/**
* The command that's passed to the container
*
* @see https://docs.docker.com/engine/reference/builder/#cmd
*/
readonly command?: string[];
/**
* The environment variables to pass to a container.
* Cannot start with `AWS_BATCH`.
* We don't recommend using plaintext environment variables for sensitive information, such as credential data.
*
* @default - no environment variables
*/
readonly environment?: { [key:string]: string };
/**
* The role used by Amazon ECS container and AWS Fargate agents to make AWS API calls on your behalf.
*
* @see https://docs.aws.amazon.com/batch/latest/userguide/execution-IAM-role.html
*/
readonly executionRole: iam.IRole;
/**
* The role that the container can assume.
*
* @default - no jobRole
*
* @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
*/
readonly jobRole?: iam.IRole;
/**
* Linux-specific modifications that are applied to the container, such as details for device mappings.
*
* @default none
*/
readonly linuxParameters?: LinuxParameters;
/**
* The configuration of the log driver
*/
readonly logDriverConfig?: ecs.LogDriverConfig;
/**
* Gives the container readonly access to its root filesystem.
*
* @default false
*/
readonly readonlyRootFilesystem?: boolean;
/**
* A map from environment variable names to the secrets for the container. Allows your job definitions
* to reference the secret by the environment variable name defined in this property.
*
* @see https://docs.aws.amazon.com/batch/latest/userguide/specifying-sensitive-data.html
*
* @default - no secrets
*/
readonly secrets?: { [envVarName: string]: Secret };
/**
* The user name to use inside the container
*
* @default - no user
*/
readonly user?: string;
/**
* The volumes to mount to this container. Automatically added to the job definition.
*
* @default - no volumes
*/
readonly volumes: EcsVolume[];
/**
* Renders this container to CloudFormation
*
* @internal
*/
_renderContainerDefinition(): CfnJobDefinition.ContainerPropertiesProperty;
/**
* Add a Volume to this container
*/
addVolume(volume: EcsVolume): void;
}
/**
* Props to configure an EcsContainerDefinition
*/
export interface EcsContainerDefinitionProps {
/**
* The image that this container will run
*/
readonly image: ecs.ContainerImage;
/**
* The number of vCPUs reserved for the container.
* Each vCPU is equivalent to 1,024 CPU shares.
* For containers running on EC2 resources, you must specify at least one vCPU.
*/
readonly cpu: number;
/**
* The memory hard limit present to the container.
* If your container attempts to exceed the memory specified, the container is terminated.
* You must specify at least 4 MiB of memory for a job.
*/
readonly memory: Size;
/**
* The command that's passed to the container
*
* @see https://docs.docker.com/engine/reference/builder/#cmd
*
* @default - no command
*/
readonly command?: string[];
/**
* The environment variables to pass to a container.
* Cannot start with `AWS_BATCH`.
* We don't recommend using plaintext environment variables for sensitive information, such as credential data.
*
* @default - no environment variables
*/
readonly environment?: { [key:string]: string };
/**
* The role used by Amazon ECS container and AWS Fargate agents to make AWS API calls on your behalf.
*
* @see https://docs.aws.amazon.com/batch/latest/userguide/execution-IAM-role.html
*
* @default - a Role will be created
*/
readonly executionRole?: iam.IRole;
/**
* The role that the container can assume.
*
* @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
*
* @default - no job role
*/
readonly jobRole?: iam.IRole;
/**
* Linux-specific modifications that are applied to the container, such as details for device mappings.
*
* @default none
*/
readonly linuxParameters?: LinuxParameters;
/**
* The loging configuration for this Job
*
* @default - the log configuration of the Docker daemon
*/
readonly logging?: ecs.LogDriver;
/**
* Gives the container readonly access to its root filesystem.
*
* @default false
*/
readonly readonlyRootFilesystem?: boolean;
/**
* A map from environment variable names to the secrets for the container. Allows your job definitions
* to reference the secret by the environment variable name defined in this property.
*
* @see https://docs.aws.amazon.com/batch/latest/userguide/specifying-sensitive-data.html
*
* @default - no secrets
*/
readonly secrets?: { [envVarName: string]: Secret };
/**
* The user name to use inside the container
*
* @default - no user
*/
readonly user?: string;
/**
* The volumes to mount to this container. Automatically added to the job definition.
*
* @default - no volumes
*/
readonly volumes?: EcsVolume[];
}
/**
* Abstract base class for ECS Containers
*/
abstract class EcsContainerDefinitionBase extends Construct implements IEcsContainerDefinition {
public readonly image: ecs.ContainerImage;
public readonly cpu: number;
public readonly memory: Size;
public readonly command?: string[];
public readonly environment?: { [key:string]: string };
public readonly executionRole: iam.IRole;
public readonly jobRole?: iam.IRole;
public readonly linuxParameters?: LinuxParameters;
public readonly logDriverConfig?: ecs.LogDriverConfig;
public readonly readonlyRootFilesystem?: boolean;
public readonly secrets?: { [envVarName: string]: Secret };
public readonly user?: string;
public readonly volumes: EcsVolume[];
private readonly imageConfig: ecs.ContainerImageConfig;
constructor(scope: Construct, id: string, props: EcsContainerDefinitionProps) {
super(scope, id);
this.image = props.image;
this.cpu = props.cpu;
this.command = props.command;
this.environment = props.environment;
this.executionRole = props.executionRole ?? createExecutionRole(this, 'ExecutionRole', props.logging ? true : false);
this.jobRole = props.jobRole;
this.linuxParameters = props.linuxParameters;
this.memory = props.memory;
if (props.logging) {
this.logDriverConfig = props.logging.bind(this, {
...this as any,
// TS!
taskDefinition: {
obtainExecutionRole: () => this.executionRole,
},
});
}
this.readonlyRootFilesystem = props.readonlyRootFilesystem ?? false;
this.secrets = props.secrets;
this.user = props.user;
this.volumes = props.volumes ?? [];
this.imageConfig = props.image.bind(this, {
...this as any,
taskDefinition: {
obtainExecutionRole: () => this.executionRole,
},
});
}
/**
* @internal
*/
public _renderContainerDefinition(): CfnJobDefinition.ContainerPropertiesProperty {
return {
image: this.imageConfig.imageName,
command: this.command,
environment: Object.keys(this.environment ?? {}).map((envKey) => ({
name: envKey,
value: (this.environment ?? {})[envKey],
})),
jobRoleArn: this.jobRole?.roleArn,
executionRoleArn: this.executionRole?.roleArn,
linuxParameters: this.linuxParameters && this.linuxParameters.renderLinuxParameters(),
logConfiguration: this.logDriverConfig,
readonlyRootFilesystem: this.readonlyRootFilesystem,
resourceRequirements: this._renderResourceRequirements(),
secrets: this.secrets ? Object.entries(this.secrets).map(([name, secret]) => {
secret.grantRead(this.executionRole);
return {
name,
valueFrom: secret.arn,
};
}) : undefined,
mountPoints: Lazy.any({
produce: () => {
if (this.volumes.length === 0) {
return undefined;
}
return this.volumes.map((volume) => {
return {
containerPath: volume.containerPath,
readOnly: volume.readonly,
sourceVolume: volume.name,
};
});
},
}),
volumes: Lazy.any({
produce: () => {
if (this.volumes.length === 0) {
return undefined;
}
return this.volumes.map((volume) => {
if (EfsVolume.isEfsVolume(volume)) {
return {
name: volume.name,
efsVolumeConfiguration: {
fileSystemId: volume.fileSystem.fileSystemId,
rootDirectory: volume.rootDirectory,
transitEncryption: volume.enableTransitEncryption ? 'ENABLED' : (volume.enableTransitEncryption === false ? 'DISABLED' : undefined),
transitEncryptionPort: volume.transitEncryptionPort,
authorizationConfig: volume.accessPointId || volume.useJobRole ? {
accessPointId: volume.accessPointId,
iam: volume.useJobRole ? 'ENABLED' : (volume.useJobRole === false ? 'DISABLED' : undefined),
} : undefined,
},
};
} else if (HostVolume.isHostVolume(volume)) {
return {
name: volume.name,
host: {
sourcePath: volume.hostPath,
},
};
}
throw new ValidationError('unsupported Volume encountered', this);
});
},
}),
user: this.user,
};
}
public addVolume(volume: EcsVolume): void {
this.volumes.push(volume);
}
/**
* @internal
*/
protected _renderResourceRequirements() {
const resourceRequirements = [];
resourceRequirements.push({
type: 'MEMORY',
value: this.memory.toMebibytes().toString(),
});
resourceRequirements.push({
type: 'VCPU',
value: this.cpu.toString(),
});
return resourceRequirements;
}
}
/**
* Sets limits for a resource with `ulimit` on linux systems.
* Used by the Docker daemon.
*/
export interface Ulimit {
/**
* The hard limit for this resource. The container will
* be terminated if it exceeds this limit.
*/
readonly hardLimit: number;
/**
* The resource to limit
*/
readonly name: UlimitName;
/**
* The reservation for this resource. The container will
* not be terminated if it exceeds this limit.
*/
readonly softLimit: number;
}
/**
* The resources to be limited
*/
export enum UlimitName {
/**
* max core dump file size
*/
CORE = 'core',
/**
* max cpu time (seconds) for a process
*/
CPU = 'cpu',
/**
* max data segment size
*/
DATA = 'data',
/**
* max file size
*/
FSIZE = 'fsize',
/**
* max number of file locks
*/
LOCKS = 'locks',
/**
* max locked memory
*/
MEMLOCK = 'memlock',
/**
* max POSIX message queue size
*/
MSGQUEUE = 'msgqueue',
/**
* max nice value for any process this user is running
*/
NICE = 'nice',
/**
* maximum number of open file descriptors
*/
NOFILE = 'nofile',
/**
* maximum number of processes
*/
NPROC = 'nproc',
/**
* size of the process' resident set (in pages)
*/
RSS = 'rss',
/**
* max realtime priority
*/
RTPRIO = 'rtprio',
/**
* timeout for realtime tasks
*/
RTTIME = 'rttime',
/**
* max number of pending signals
*/
SIGPENDING = 'sigpending',
/**
* max stack size (in bytes)
*/
STACK = 'stack',
}
/**
* A container orchestrated by ECS that uses EC2 resources
*/
export interface IEcsEc2ContainerDefinition extends IEcsContainerDefinition {
/**
* When this parameter is true, the container is given elevated permissions on the host container instance (similar to the root user).
*
* @default false
*/
readonly privileged?: boolean;
/**
* Limits to set for the user this docker container will run as
*/
readonly ulimits: Ulimit[];
/**
* The number of physical GPUs to reserve for the container.
* Make sure that the number of GPUs reserved for all containers in a job doesn't exceed
* the number of available GPUs on the compute resource that the job is launched on.
*
* @default - no gpus
*/
readonly gpu?: number;
/**
* Add a ulimit to this container
*/
addUlimit(ulimit: Ulimit): void;
}
/**
* Props to configure an EcsEc2ContainerDefinition
*/
export interface EcsEc2ContainerDefinitionProps extends EcsContainerDefinitionProps {
/**
* When this parameter is true, the container is given elevated permissions on the host container instance (similar to the root user).
*
* @default false
*/
readonly privileged?: boolean;
/**
* Limits to set for the user this docker container will run as
*
* @default - no ulimits
*/
readonly ulimits?: Ulimit[];
/**
* The number of physical GPUs to reserve for the container.
* Make sure that the number of GPUs reserved for all containers in a job doesn't exceed
* the number of available GPUs on the compute resource that the job is launched on.
*
* @default - no gpus
*/
readonly gpu?: number;
}
/**
* A container orchestrated by ECS that uses EC2 resources
*/
export class EcsEc2ContainerDefinition extends EcsContainerDefinitionBase implements IEcsEc2ContainerDefinition {
public readonly privileged?: boolean;
public readonly ulimits: Ulimit[];
public readonly gpu?: number;
constructor(scope: Construct, id: string, props: EcsEc2ContainerDefinitionProps) {
super(scope, id, props);
this.privileged = props.privileged;
this.ulimits = props.ulimits ?? [];
this.gpu = props.gpu;
}
/**
* @internal
*/
public _renderContainerDefinition(): CfnJobDefinition.ContainerPropertiesProperty {
return {
...super._renderContainerDefinition(),
ulimits: Lazy.any({
produce: () => {
if (this.ulimits.length === 0) {
return undefined;
}
return this.ulimits.map((ulimit) => ({
hardLimit: ulimit.hardLimit,
name: ulimit.name,
softLimit: ulimit.softLimit,
}));
},
}),
privileged: this.privileged,
resourceRequirements: this._renderResourceRequirements(),
};
}
/**
* Add a ulimit to this container
*/
addUlimit(ulimit: Ulimit): void {
this.ulimits.push(ulimit);
}
/**
* @internal
*/
protected _renderResourceRequirements() {
const resourceRequirements = super._renderResourceRequirements();
if (this.gpu) {
resourceRequirements.push({
type: 'GPU',
value: this.gpu.toString(),
});
}
return resourceRequirements;
}
}
/**
* A container orchestrated by ECS that uses Fargate resources and is orchestrated by ECS
*/
export interface IEcsFargateContainerDefinition extends IEcsContainerDefinition {
/**
* Indicates whether the job has a public IP address.
* For a job that's running on Fargate resources in a private subnet to send outbound traffic to the internet
* (for example, to pull container images), the private subnet requires a NAT gateway be attached to route requests to the internet.
*
* @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html
*
* @default false
*/
readonly assignPublicIp?: boolean;
/**
* Which version of Fargate to use when running this container
*
* @default LATEST
*/
readonly fargatePlatformVersion?: ecs.FargatePlatformVersion;
/**
* The size for ephemeral storage.
*
* @default - 20 GiB
*/
readonly ephemeralStorageSize?: Size;
/**
* The vCPU architecture of Fargate Runtime.
*
* @default - X86_64
*/
readonly fargateCpuArchitecture?: ecs.CpuArchitecture;
/**
* The operating system for the compute environment.
*
* @default - LINUX
*/
readonly fargateOperatingSystemFamily?: ecs.OperatingSystemFamily;
}
/**
* Props to configure an EcsFargateContainerDefinition
*/
export interface EcsFargateContainerDefinitionProps extends EcsContainerDefinitionProps {
/**
* Indicates whether the job has a public IP address.
* For a job that's running on Fargate resources in a private subnet to send outbound traffic to the internet
* (for example, to pull container images), the private subnet requires a NAT gateway be attached to route requests to the internet.
*
* @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html
*
* @default false
*/
readonly assignPublicIp?: boolean;
/**
* Which version of Fargate to use when running this container
*
* @default LATEST
*/
readonly fargatePlatformVersion?: ecs.FargatePlatformVersion;
/**
* The size for ephemeral storage.
*
* @default - 20 GiB
*/
readonly ephemeralStorageSize?: Size;
/**
* The vCPU architecture of Fargate Runtime.
*
* @default - X86_64
*/
readonly fargateCpuArchitecture?: ecs.CpuArchitecture;
/**
* The operating system for the compute environment.
*
* @default - LINUX
*/
readonly fargateOperatingSystemFamily?: ecs.OperatingSystemFamily;
}
/**
* A container orchestrated by ECS that uses Fargate resources
*/
export class EcsFargateContainerDefinition extends EcsContainerDefinitionBase implements IEcsFargateContainerDefinition {
public readonly fargatePlatformVersion?: ecs.FargatePlatformVersion;
public readonly assignPublicIp?: boolean;
public readonly ephemeralStorageSize?: Size;
public readonly fargateCpuArchitecture?: ecs.CpuArchitecture;
public readonly fargateOperatingSystemFamily?: ecs.OperatingSystemFamily;
constructor(scope: Construct, id: string, props: EcsFargateContainerDefinitionProps) {
super(scope, id, props);
this.assignPublicIp = props.assignPublicIp;
this.fargatePlatformVersion = props.fargatePlatformVersion;
this.ephemeralStorageSize = props.ephemeralStorageSize;
this.fargateCpuArchitecture = props.fargateCpuArchitecture;
this.fargateOperatingSystemFamily = props.fargateOperatingSystemFamily;
if (this.fargateOperatingSystemFamily?.isWindows() && this.readonlyRootFilesystem) {
// see https://kubernetes.io/docs/concepts/windows/intro/
throw new ValidationError('Readonly root filesystem is not possible on Windows; write access is required for registry & system processes to run inside the container', this);
}
// validates ephemeralStorageSize is within limits
if (props.ephemeralStorageSize) {
if (props.ephemeralStorageSize.toGibibytes() > 200) {
throw new ValidationError(`ECS Fargate container '${id}' specifies 'ephemeralStorageSize' at ${props.ephemeralStorageSize.toGibibytes()} > 200 GB`, this);
} else if (props.ephemeralStorageSize.toGibibytes() < 21) {
throw new ValidationError(`ECS Fargate container '${id}' specifies 'ephemeralStorageSize' at ${props.ephemeralStorageSize.toGibibytes()} < 21 GB`, this);
}
}
}
/**
* @internal
*/
public _renderContainerDefinition(): CfnJobDefinition.ContainerPropertiesProperty {
let containerDef = {
...super._renderContainerDefinition(),
ephemeralStorage: this.ephemeralStorageSize? {
sizeInGiB: this.ephemeralStorageSize?.toGibibytes(),
} : undefined,
fargatePlatformConfiguration: {
platformVersion: this.fargatePlatformVersion?.toString(),
},
networkConfiguration: {
assignPublicIp: this.assignPublicIp ? 'ENABLED' : 'DISABLED',
},
runtimePlatform: {
cpuArchitecture: this.fargateCpuArchitecture?._cpuArchitecture,
operatingSystemFamily: this.fargateOperatingSystemFamily?._operatingSystemFamily,
},
};
// readonlyRootFilesystem isn't applicable to Windows, see https://kubernetes.io/docs/concepts/windows/intro/
if (this.fargateOperatingSystemFamily?.isWindows()) {
containerDef.readonlyRootFilesystem = undefined;
}
return containerDef;
}
}
function createExecutionRole(scope: Construct, id: string, logging: boolean): iam.IRole {
const execRole = new iam.Role(scope, id, {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
// needed for cross-account access with TagParameterContainerImage
roleName: PhysicalName.GENERATE_IF_NEEDED,
});
if (!logging) {
// all jobs will fail without this if they produce any output at all when no logging is specified
LogGroup.fromLogGroupName(scope, 'batchDefaultLogGroup', '/aws/batch/job').grantWrite(execRole);
}
return execRole;
}