From 3eef7bc6e0a91f005dfc49eeee36dd28b19d8140 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Fri, 7 Feb 2025 16:16:13 +0000 Subject: [PATCH 1/2] feat(batch): throw `ValidationError` instead of untyped Errors --- .../aws-batch/lib/ecs-container-definition.ts | 10 ++--- .../aws-batch/lib/eks-job-definition.ts | 4 +- .../aws-cdk-lib/aws-batch/lib/job-queue.ts | 9 ++-- .../aws-batch/lib/linux-parameters.ts | 2 +- .../lib/managed-compute-environment.ts | 44 +++++++++---------- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/packages/aws-cdk-lib/aws-batch/lib/ecs-container-definition.ts b/packages/aws-cdk-lib/aws-batch/lib/ecs-container-definition.ts index 5ead1cb782c35..a45d1d86b0669 100644 --- a/packages/aws-cdk-lib/aws-batch/lib/ecs-container-definition.ts +++ b/packages/aws-cdk-lib/aws-batch/lib/ecs-container-definition.ts @@ -7,7 +7,7 @@ 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 } from '../../core'; +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'); @@ -695,7 +695,7 @@ abstract class EcsContainerDefinitionBase extends Construct implements IEcsConta }; } - throw new Error('unsupported Volume encountered'); + throw new ValidationError('unsupported Volume encountered', this); }); }, }), @@ -1059,15 +1059,15 @@ export class EcsFargateContainerDefinition extends EcsContainerDefinitionBase im if (this.fargateOperatingSystemFamily?.isWindows() && this.readonlyRootFilesystem) { // see https://kubernetes.io/docs/concepts/windows/intro/ - throw new Error('Readonly root filesystem is not possible on Windows; write access is required for registry & system processes to run inside the container'); + 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 Error(`ECS Fargate container '${id}' specifies 'ephemeralStorageSize' at ${props.ephemeralStorageSize.toGibibytes()} > 200 GB`); + throw new ValidationError(`ECS Fargate container '${id}' specifies 'ephemeralStorageSize' at ${props.ephemeralStorageSize.toGibibytes()} > 200 GB`, this); } else if (props.ephemeralStorageSize.toGibibytes() < 21) { - throw new Error(`ECS Fargate container '${id}' specifies 'ephemeralStorageSize' at ${props.ephemeralStorageSize.toGibibytes()} < 21 GB`); + throw new ValidationError(`ECS Fargate container '${id}' specifies 'ephemeralStorageSize' at ${props.ephemeralStorageSize.toGibibytes()} < 21 GB`, this); } } } diff --git a/packages/aws-cdk-lib/aws-batch/lib/eks-job-definition.ts b/packages/aws-cdk-lib/aws-batch/lib/eks-job-definition.ts index 145ae0cffd03c..c683626594cc9 100644 --- a/packages/aws-cdk-lib/aws-batch/lib/eks-job-definition.ts +++ b/packages/aws-cdk-lib/aws-batch/lib/eks-job-definition.ts @@ -2,7 +2,7 @@ import { Construct } from 'constructs'; import { CfnJobDefinition } from './batch.generated'; import { EksContainerDefinition, EmptyDirVolume, HostPathVolume, SecretPathVolume } from './eks-container-definition'; import { baseJobDefinitionProperties, IJobDefinition, JobDefinitionBase, JobDefinitionProps } from './job-definition-base'; -import { ArnFormat, Lazy, Stack } from '../../core'; +import { ArnFormat, Lazy, Stack, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; /** @@ -204,7 +204,7 @@ export class EksJobDefinition extends JobDefinitionBase implements IEksJobDefini }; } - throw new Error('unknown volume type'); + throw new ValidationError('unknown volume type', this); }); }, }), diff --git a/packages/aws-cdk-lib/aws-batch/lib/job-queue.ts b/packages/aws-cdk-lib/aws-batch/lib/job-queue.ts index 69ee4aa51e99d..424a46d46e15b 100644 --- a/packages/aws-cdk-lib/aws-batch/lib/job-queue.ts +++ b/packages/aws-cdk-lib/aws-batch/lib/job-queue.ts @@ -2,7 +2,7 @@ import { Construct } from 'constructs'; import { CfnJobQueue } from './batch.generated'; import { IComputeEnvironment } from './compute-environment-base'; import { ISchedulingPolicy } from './scheduling-policy'; -import { ArnFormat, Duration, IResource, Lazy, Resource, Stack } from '../../core'; +import { ArnFormat, Duration, IResource, Lazy, Resource, Stack, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; /** @@ -241,7 +241,7 @@ export class JobQueue extends Resource implements IJobQueue { public readonly jobQueueName = stack.splitArn(jobQueueArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; public addComputeEnvironment(_computeEnvironment: IComputeEnvironment, _order: number): void { - throw new Error(`cannot add ComputeEnvironments to imported JobQueue '${id}'`); + throw new ValidationError(`cannot add ComputeEnvironments to imported JobQueue '${id}'`, this); } } @@ -308,16 +308,17 @@ export class JobQueue extends Resource implements IJobQueue { return; } - return jobStateTimeLimitActions.map((action, index) => renderJobStateTimeLimitAction(action, index)); + return jobStateTimeLimitActions.map((action, index) => renderJobStateTimeLimitAction(this, action, index)); function renderJobStateTimeLimitAction( + scope: Construct, jobStateTimeLimitAction: JobStateTimeLimitAction, index: number, ): CfnJobQueue.JobStateTimeLimitActionProperty { const maxTimeSeconds = jobStateTimeLimitAction.maxTime.toSeconds(); if (maxTimeSeconds < 600 || maxTimeSeconds > 86400) { - throw new Error(`maxTime must be between 600 and 86400 seconds, got ${maxTimeSeconds} seconds at jobStateTimeLimitActions[${index}]`); + throw new ValidationError(`maxTime must be between 600 and 86400 seconds, got ${maxTimeSeconds} seconds at jobStateTimeLimitActions[${index}]`, scope); } return { diff --git a/packages/aws-cdk-lib/aws-batch/lib/linux-parameters.ts b/packages/aws-cdk-lib/aws-batch/lib/linux-parameters.ts index d525df6b963b2..81dc8590ad18a 100644 --- a/packages/aws-cdk-lib/aws-batch/lib/linux-parameters.ts +++ b/packages/aws-cdk-lib/aws-batch/lib/linux-parameters.ts @@ -101,7 +101,7 @@ export class LinuxParameters extends Construct { props.swappiness !== undefined && (!Number.isInteger(props.swappiness) || props.swappiness < 0 || props.swappiness > 100) ) { - throw new Error(`swappiness: Must be an integer between 0 and 100; received ${props.swappiness}.`); + throw new cdk.ValidationError(`swappiness: Must be an integer between 0 and 100; received ${props.swappiness}.`, this); } } diff --git a/packages/aws-cdk-lib/aws-batch/lib/managed-compute-environment.ts b/packages/aws-cdk-lib/aws-batch/lib/managed-compute-environment.ts index 1e15c8f1cbb12..82b81d4fdf5e4 100644 --- a/packages/aws-cdk-lib/aws-batch/lib/managed-compute-environment.ts +++ b/packages/aws-cdk-lib/aws-batch/lib/managed-compute-environment.ts @@ -5,7 +5,7 @@ import * as ec2 from '../../aws-ec2'; import * as eks from '../../aws-eks'; import * as iam from '../../aws-iam'; import { IRole } from '../../aws-iam'; -import { ArnFormat, Duration, ITaggable, Lazy, Resource, Stack, TagManager, TagType, Token } from '../../core'; +import { ArnFormat, Duration, ITaggable, Lazy, Resource, Stack, TagManager, TagType, Token, ValidationError } from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; /** @@ -619,10 +619,10 @@ export class ManagedEc2EcsComputeEnvironment extends ManagedComputeEnvironmentBa public readonly tags: TagManager = new TagManager(TagType.MAP, 'AWS::Batch::ComputeEnvironment'); public addInstanceClass(_instanceClass: ec2.InstanceClass): void { - throw new Error(`cannot add instance class to imported ComputeEnvironment '${id}'`); + throw new ValidationError(`cannot add instance class to imported ComputeEnvironment '${id}'`, this); } public addInstanceType(_instanceType: ec2.InstanceType): void { - throw new Error(`cannot add instance type to imported ComputeEnvironment '${id}'`); + throw new ValidationError(`cannot add instance type to imported ComputeEnvironment '${id}'`, this); } } @@ -650,7 +650,7 @@ export class ManagedEc2EcsComputeEnvironment extends ManagedComputeEnvironmentBa addConstructMetadata(this, props); this.images = props.images; - this.allocationStrategy = determineAllocationStrategy(id, props.allocationStrategy, this.spot); + this.allocationStrategy = determineAllocationStrategy(this, props.allocationStrategy, this.spot); this.spotBidPercentage = props.spotBidPercentage; this.spotFleetRole = props.spotFleetRole ?? ( @@ -665,7 +665,7 @@ export class ManagedEc2EcsComputeEnvironment extends ManagedComputeEnvironmentBa (this.instanceClasses.includes(ec2.InstanceClass.A1) || this.instanceTypes.find(instanceType => instanceType.sameInstanceClassAs(ec2.InstanceType.of(ec2.InstanceClass.A1, ec2.InstanceSize.LARGE)))) ) { - throw new Error('Amazon Linux 2023 does not support A1 instances.'); + throw new ValidationError('Amazon Linux 2023 does not support A1 instances.', this); } const { instanceRole, instanceProfile } = createInstanceRoleAndProfile(this, props.instanceRole); @@ -676,8 +676,8 @@ export class ManagedEc2EcsComputeEnvironment extends ManagedComputeEnvironmentBa this.minvCpus = props.minvCpus ?? DEFAULT_MIN_VCPUS; this.placementGroup = props.placementGroup; - validateVCpus(id, this.minvCpus, this.maxvCpus); - validateSpotConfig(id, this.spot, this.spotBidPercentage, this.spotFleetRole); + validateVCpus(this, this.minvCpus, this.maxvCpus); + validateSpotConfig(this, this.spot, this.spotBidPercentage, this.spotFleetRole); const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); const resource = new CfnComputeEnvironment(this, 'Resource', { @@ -1010,9 +1010,9 @@ export class ManagedEc2EksComputeEnvironment extends ManagedComputeEnvironmentBa this.eksCluster = props.eksCluster; this.images = props.images; - this.allocationStrategy = determineAllocationStrategy(id, props.allocationStrategy, this.spot); + this.allocationStrategy = determineAllocationStrategy(this, props.allocationStrategy, this.spot); if (this.allocationStrategy === AllocationStrategy.BEST_FIT) { - throw new Error(`ManagedEc2EksComputeEnvironment '${id}' uses invalid allocation strategy 'AllocationStrategy.BEST_FIT'`); + throw new ValidationError(`ManagedEc2EksComputeEnvironment '${id}' uses invalid allocation strategy 'AllocationStrategy.BEST_FIT'`, this); } this.spotBidPercentage = props.spotBidPercentage; this.instanceTypes = props.instanceTypes ?? []; @@ -1026,8 +1026,8 @@ export class ManagedEc2EksComputeEnvironment extends ManagedComputeEnvironmentBa this.minvCpus = props.minvCpus ?? DEFAULT_MIN_VCPUS; this.placementGroup = props.placementGroup; - validateVCpus(id, this.minvCpus, this.maxvCpus); - validateSpotConfig(id, this.spot, this.spotBidPercentage); + validateVCpus(this, this.minvCpus, this.maxvCpus); + validateSpotConfig(this, this.spot, this.spotBidPercentage); const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); const resource = new CfnComputeEnvironment(this, 'Resource', { @@ -1179,14 +1179,14 @@ function createSpotFleetRole(scope: Construct): IRole { }); } -function determineAllocationStrategy(id: string, allocationStrategy?: AllocationStrategy, spot?: boolean): AllocationStrategy | undefined { +function determineAllocationStrategy(scope: Construct, allocationStrategy?: AllocationStrategy, spot?: boolean): AllocationStrategy | undefined { let result = allocationStrategy; if (!allocationStrategy) { result = spot ? AllocationStrategy.SPOT_PRICE_CAPACITY_OPTIMIZED : AllocationStrategy.BEST_FIT_PROGRESSIVE; } else if (allocationStrategy === AllocationStrategy.SPOT_PRICE_CAPACITY_OPTIMIZED && !spot) { - throw new Error(`Managed ComputeEnvironment '${id}' specifies 'AllocationStrategy.SPOT_PRICE_CAPACITY_OPTIMIZED' without using spot instances`); + throw new ValidationError(`Managed ComputeEnvironment '${scope.node.id}' specifies 'AllocationStrategy.SPOT_PRICE_CAPACITY_OPTIMIZED' without using spot instances`, scope); } else if (allocationStrategy === AllocationStrategy.SPOT_CAPACITY_OPTIMIZED && !spot) { - throw new Error(`Managed ComputeEnvironment '${id}' specifies 'AllocationStrategy.SPOT_CAPACITY_OPTIMIZED' without using spot instances`); + throw new ValidationError(`Managed ComputeEnvironment '${scope.node.id}' specifies 'AllocationStrategy.SPOT_CAPACITY_OPTIMIZED' without using spot instances`, scope); } return result; @@ -1200,34 +1200,34 @@ function validateInstances(types?: ec2.InstanceType[], classes?: ec2.InstanceCla return []; } -function validateSpotConfig(id: string, spot?: boolean, spotBidPercentage?: number, spotFleetRole?: iam.IRole): void { +function validateSpotConfig(scope: Construct, spot?: boolean, spotBidPercentage?: number, spotFleetRole?: iam.IRole): void { if (spotBidPercentage) { if (!spot) { - throw new Error(`Managed ComputeEnvironment '${id}' specifies 'spotBidPercentage' without specifying 'spot'`); + throw new ValidationError(`Managed ComputeEnvironment '${scope.node.id}' specifies 'spotBidPercentage' without specifying 'spot'`, scope); } if (!Token.isUnresolved(spotBidPercentage)) { if (spotBidPercentage > 100) { - throw new Error(`Managed ComputeEnvironment '${id}' specifies 'spotBidPercentage' > 100`); + throw new ValidationError(`Managed ComputeEnvironment '${scope.node.id}' specifies 'spotBidPercentage' > 100`, scope); } else if (spotBidPercentage < 0) { - throw new Error(`Managed ComputeEnvironment '${id}' specifies 'spotBidPercentage' < 0`); + throw new ValidationError(`Managed ComputeEnvironment '${scope.node.id}' specifies 'spotBidPercentage' < 0`, scope); } } } if (spotFleetRole) { if (!spot) { - throw new Error(`Managed ComputeEnvironment '${id}' specifies 'spotFleetRole' without specifying 'spot'`); + throw new ValidationError(`Managed ComputeEnvironment '${scope.node.id}' specifies 'spotFleetRole' without specifying 'spot'`, scope); } } } -function validateVCpus(id: string, minvCpus: number, maxvCpus: number): void { +function validateVCpus(scope: Construct, minvCpus: number, maxvCpus: number): void { if (!Token.isUnresolved(minvCpus) && minvCpus < 0) { - throw new Error(`Managed ComputeEnvironment '${id}' has 'minvCpus' = ${minvCpus} < 0; 'minvCpus' cannot be less than zero`); + throw new ValidationError(`Managed ComputeEnvironment '${scope.node.id}' has 'minvCpus' = ${minvCpus} < 0; 'minvCpus' cannot be less than zero`, scope); } if (!Token.isUnresolved(minvCpus) && !Token.isUnresolved(maxvCpus) && minvCpus > maxvCpus) { - throw new Error(`Managed ComputeEnvironment '${id}' has 'minvCpus' = ${minvCpus} > 'maxvCpus' = ${maxvCpus}; 'minvCpus' cannot be greater than 'maxvCpus'`); + throw new ValidationError(`Managed ComputeEnvironment '${scope.node.id}' has 'minvCpus' = ${minvCpus} > 'maxvCpus' = ${maxvCpus}; 'minvCpus' cannot be greater than 'maxvCpus'`, scope); } } From 0e6a482ab0568f9542c7e4f138bc65819ec47075 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Tue, 11 Feb 2025 11:32:10 +0000 Subject: [PATCH 2/2] fixup --- packages/aws-cdk-lib/.eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/aws-cdk-lib/.eslintrc.js b/packages/aws-cdk-lib/.eslintrc.js index 46e5780301ecd..ff6099b4b0bc2 100644 --- a/packages/aws-cdk-lib/.eslintrc.js +++ b/packages/aws-cdk-lib/.eslintrc.js @@ -25,6 +25,7 @@ const enableNoThrowDefaultErrorIn = [ 'aws-applicationautoscaling', 'aws-appsync', 'aws-appmesh', + 'aws-batch', 'aws-cognito', 'aws-elasticloadbalancing', 'aws-elasticloadbalancingv2',