Skip to content
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

feat(batch): throw ValidationError instead of untyped Errors #33389

Merged
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
1 change: 1 addition & 0 deletions packages/aws-cdk-lib/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const enableNoThrowDefaultErrorIn = [
'aws-applicationautoscaling',
'aws-appsync',
'aws-appmesh',
'aws-batch',
'aws-cognito',
'aws-elasticloadbalancing',
'aws-elasticloadbalancingv2',
Expand Down
10 changes: 5 additions & 5 deletions packages/aws-cdk-lib/aws-batch/lib/ecs-container-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -695,7 +695,7 @@ abstract class EcsContainerDefinitionBase extends Construct implements IEcsConta
};
}

throw new Error('unsupported Volume encountered');
throw new ValidationError('unsupported Volume encountered', this);
});
},
}),
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk-lib/aws-batch/lib/eks-job-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -204,7 +204,7 @@ export class EksJobDefinition extends JobDefinitionBase implements IEksJobDefini
};
}

throw new Error('unknown volume type');
throw new ValidationError('unknown volume type', this);
});
},
}),
Expand Down
9 changes: 5 additions & 4 deletions packages/aws-cdk-lib/aws-batch/lib/job-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk-lib/aws-batch/lib/linux-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
44 changes: 22 additions & 22 deletions packages/aws-cdk-lib/aws-batch/lib/managed-compute-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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 ?? (
Expand All @@ -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);
Expand All @@ -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', {
Expand Down Expand Up @@ -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 ?? [];
Expand All @@ -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', {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
}

Expand Down