Skip to content

Commit 2e7c55b

Browse files
authored
feat(eks): throw ValidationErrors instead of untyped errors (#34428)
### Issue Relates to #32569 ### Reason for this change untyped Errors are not recommended ### Description of changes `ValidationError`s everywhere ### Describe any new or updated permissions being added None ### Description of how you validated changes Existing tests. Exemptions granted as this is a refactor of existing code. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 5cd82f5 commit 2e7c55b

File tree

8 files changed

+59
-60
lines changed

8 files changed

+59
-60
lines changed

packages/aws-cdk-lib/.eslintrc.js

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ baseConfig.rules['@cdklabs/no-throw-default-error'] = ['error'];
1919
const noThrowDefaultErrorNotYetSupported = [
2020
'aws-ecs-patterns',
2121
'aws-ecs',
22-
'aws-eks',
2322
'aws-elasticsearch',
2423
'aws-events-targets',
2524
'aws-globalaccelerator',

packages/aws-cdk-lib/aws-eks/lib/aws-auth.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AwsAuthMapping } from './aws-auth-mapping';
33
import { Cluster, AuthenticationMode } from './cluster';
44
import { KubernetesManifest } from './k8s-manifest';
55
import * as iam from '../../aws-iam';
6-
import { Lazy, Stack } from '../../core';
6+
import { Lazy, Stack, ValidationError } from '../../core';
77

88
/**
99
* Configuration props for the AwsAuth construct.
@@ -45,7 +45,7 @@ export class AwsAuth extends Construct {
4545
const supportConfigMap = props.cluster.authenticationMode !== AuthenticationMode.API ? true : false;
4646

4747
if (!supportConfigMap) {
48-
throw new Error('ConfigMap not supported in the AuthenticationMode');
48+
throw new ValidationError('ConfigMap not supported in the AuthenticationMode', this);
4949
}
5050

5151
this.stack = Stack.of(this);
@@ -123,7 +123,7 @@ export class AwsAuth extends Construct {
123123
// a dependency on the cluster, allowing those resources to be in a different stack,
124124
// will create a circular dependency. granted, it won't always be the case,
125125
// but we opted for the more cautious and restrictive approach for now.
126-
throw new Error(`${construct.node.path} should be defined in the scope of the ${thisStack.stackName} stack to prevent circular dependencies`);
126+
throw new ValidationError(`${construct.node.path} should be defined in the scope of the ${thisStack.stackName} stack to prevent circular dependencies`, this);
127127
}
128128
}
129129

packages/aws-cdk-lib/aws-eks/lib/cluster-resource.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as ec2 from '../../aws-ec2';
66
import * as iam from '../../aws-iam';
77
import * as kms from '../../aws-kms';
88
import * as lambda from '../../aws-lambda';
9-
import { ArnComponents, CustomResource, Token, Stack, Lazy } from '../../core';
9+
import { ArnComponents, CustomResource, Token, Stack, Lazy, ValidationError } from '../../core';
1010

1111
export interface ClusterResourceProps {
1212
readonly resourcesVpcConfig: CfnCluster.ResourcesVpcConfigProperty;
@@ -56,7 +56,7 @@ export class ClusterResource extends Construct {
5656
super(scope, id);
5757

5858
if (!props.roleArn) {
59-
throw new Error('"roleArn" is required');
59+
throw new ValidationError('"roleArn" is required', this);
6060
}
6161

6262
const provider = ClusterResourceProvider.getOrCreate(this, {

packages/aws-cdk-lib/aws-eks/lib/cluster.ts

+27-27
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import * as iam from '../../aws-iam';
2626
import * as kms from '../../aws-kms';
2727
import * as lambda from '../../aws-lambda';
2828
import * as ssm from '../../aws-ssm';
29-
import { Annotations, CfnOutput, CfnResource, IResource, Resource, Stack, Tags, Token, Duration, Size } from '../../core';
29+
import { Annotations, CfnOutput, CfnResource, IResource, Resource, Stack, Tags, Token, Duration, Size, ValidationError, UnscopedValidationError } from '../../core';
3030
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';
3131

3232
// defaults are based on https://eksctl.io
@@ -782,7 +782,7 @@ export class EndpointAccess {
782782
*/
783783
public readonly _config: EndpointAccessConfig) {
784784
if (!_config.publicAccess && _config.publicCidrs && _config.publicCidrs.length > 0) {
785-
throw new Error('CIDR blocks can only be configured when public access is enabled');
785+
throw new UnscopedValidationError('CIDR blocks can only be configured when public access is enabled');
786786
}
787787
}
788788

@@ -796,7 +796,7 @@ export class EndpointAccess {
796796
if (!this._config.privateAccess) {
797797
// when private access is disabled, we can't restric public
798798
// access since it will render the kubectl provider unusable.
799-
throw new Error('Cannot restric public access to endpoint when private access is disabled. Use PUBLIC_AND_PRIVATE.onlyFrom() instead.');
799+
throw new UnscopedValidationError('Cannot restric public access to endpoint when private access is disabled. Use PUBLIC_AND_PRIVATE.onlyFrom() instead.');
800800
}
801801
return new EndpointAccess({
802802
...this._config,
@@ -1156,7 +1156,7 @@ abstract class ClusterBase extends Resource implements ICluster {
11561156

11571157
// see https://github.com/awslabs/cdk8s/blob/master/packages/cdk8s/src/chart.ts#L84
11581158
if (typeof cdk8sChart.toJson !== 'function') {
1159-
throw new Error(`Invalid cdk8s chart. Must contain a 'toJson' method, but found ${typeof cdk8sChart.toJson}`);
1159+
throw new ValidationError(`Invalid cdk8s chart. Must contain a 'toJson' method, but found ${typeof cdk8sChart.toJson}`, this);
11601160
}
11611161

11621162
const manifest = new KubernetesManifest(this, id, {
@@ -1238,7 +1238,7 @@ abstract class ClusterBase extends Resource implements ICluster {
12381238

12391239
const bootstrapEnabled = options.bootstrapEnabled ?? true;
12401240
if (options.bootstrapOptions && !bootstrapEnabled) {
1241-
throw new Error('Cannot specify "bootstrapOptions" if "bootstrapEnabled" is false');
1241+
throw new ValidationError('Cannot specify "bootstrapOptions" if "bootstrapEnabled" is false', this);
12421242
}
12431243

12441244
if (bootstrapEnabled) {
@@ -1626,7 +1626,7 @@ export class Cluster extends ClusterBase {
16261626

16271627
const selectedSubnetIdsPerGroup = this.vpcSubnets.map(s => this.vpc.selectSubnets(s).subnetIds);
16281628
if (selectedSubnetIdsPerGroup.some(Token.isUnresolved) && selectedSubnetIdsPerGroup.length > 1) {
1629-
throw new Error('eks.Cluster: cannot select multiple subnet groups from a VPC imported from list tokens with unknown length. Select only one subnet group, pass a length to Fn.split, or switch to Vpc.fromLookup.');
1629+
throw new ValidationError('eks.Cluster: cannot select multiple subnet groups from a VPC imported from list tokens with unknown length. Select only one subnet group, pass a length to Fn.split, or switch to Vpc.fromLookup.', this);
16301630
}
16311631

16321632
// Get subnetIds for all selected subnets
@@ -1665,29 +1665,29 @@ export class Cluster extends ClusterBase {
16651665
if (!hasPendingLookup) {
16661666
if (privateSubnets.length === 0 && publicAccessDisabled) {
16671667
// no private subnets and no public access at all, no good.
1668-
throw new Error('Vpc must contain private subnets when public endpoint access is disabled');
1668+
throw new ValidationError('Vpc must contain private subnets when public endpoint access is disabled', this);
16691669
}
16701670

16711671
if (privateSubnets.length === 0 && publicAccessRestricted) {
16721672
// no private subnets and public access is restricted, no good.
1673-
throw new Error('Vpc must contain private subnets when public endpoint access is restricted');
1673+
throw new ValidationError('Vpc must contain private subnets when public endpoint access is restricted', this);
16741674
}
16751675
}
16761676

16771677
const placeClusterHandlerInVpc = props.placeClusterHandlerInVpc ?? false;
16781678

16791679
if (!hasPendingLookup) {
16801680
if (placeClusterHandlerInVpc && privateSubnets.length === 0) {
1681-
throw new Error('Cannot place cluster handler in the VPC since no private subnets could be selected');
1681+
throw new ValidationError('Cannot place cluster handler in the VPC since no private subnets could be selected', this);
16821682
}
16831683
}
16841684

16851685
if (props.clusterHandlerSecurityGroup && !placeClusterHandlerInVpc) {
1686-
throw new Error('Cannot specify clusterHandlerSecurityGroup without placeClusterHandlerInVpc set to true');
1686+
throw new ValidationError('Cannot specify clusterHandlerSecurityGroup without placeClusterHandlerInVpc set to true', this);
16871687
}
16881688

16891689
if (props.serviceIpv4Cidr && props.ipFamily == IpFamily.IP_V6) {
1690-
throw new Error('Cannot specify serviceIpv4Cidr with ipFamily equal to IpFamily.IP_V6');
1690+
throw new ValidationError('Cannot specify serviceIpv4Cidr with ipFamily equal to IpFamily.IP_V6', this);
16911691
}
16921692

16931693
this.validateRemoteNetworkConfig(props);
@@ -1745,7 +1745,7 @@ export class Cluster extends ClusterBase {
17451745

17461746
// validate VPC properties according to: https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html
17471747
if (this.vpc instanceof ec2.Vpc && !(this.vpc.dnsHostnamesEnabled && this.vpc.dnsSupportEnabled)) {
1748-
throw new Error('Private endpoint access requires the VPC to have DNS support and DNS hostnames enabled. Use `enableDnsHostnames: true` and `enableDnsSupport: true` when creating the VPC.');
1748+
throw new ValidationError('Private endpoint access requires the VPC to have DNS support and DNS hostnames enabled. Use `enableDnsHostnames: true` and `enableDnsSupport: true` when creating the VPC.', this);
17491749
}
17501750

17511751
this.kubectlPrivateSubnets = privateSubnets;
@@ -1934,7 +1934,7 @@ export class Cluster extends ClusterBase {
19341934
@MethodMetadata()
19351935
public addAutoScalingGroupCapacity(id: string, options: AutoScalingGroupCapacityOptions): autoscaling.AutoScalingGroup {
19361936
if (options.machineImageType === MachineImageType.BOTTLEROCKET && options.bootstrapOptions !== undefined) {
1937-
throw new Error('bootstrapOptions is not supported for Bottlerocket');
1937+
throw new ValidationError('bootstrapOptions is not supported for Bottlerocket', this);
19381938
}
19391939
const asg = new autoscaling.AutoScalingGroup(this, id, {
19401940
...options,
@@ -2143,7 +2143,7 @@ export class Cluster extends ClusterBase {
21432143
// wanted to avoid resource tear down, we decided for now that we will only
21442144
// support a single EKS cluster per CFN stack.
21452145
if (this.stack.node.tryFindChild(uid)) {
2146-
throw new Error('Only a single EKS cluster can be defined within a CloudFormation stack');
2146+
throw new ValidationError('Only a single EKS cluster can be defined within a CloudFormation stack', this);
21472147
}
21482148

21492149
return new KubectlProvider(this.stack, uid, { cluster: this });
@@ -2264,7 +2264,7 @@ export class Cluster extends ClusterBase {
22642264
if (cidrs.length > 1) {
22652265
cidrs.forEach((cidr1, j) => {
22662266
if (cidrs.slice(j + 1).some(cidr2 => validateCidrPairOverlap(cidr1, cidr2))) {
2267-
throw new Error(`CIDR ${cidr1} should not overlap with another CIDR in remote node network #${index + 1}`);
2267+
throw new ValidationError(`CIDR ${cidr1} should not overlap with another CIDR in remote node network #${index + 1}`, this);
22682268
}
22692269
});
22702270
}
@@ -2275,7 +2275,7 @@ export class Cluster extends ClusterBase {
22752275
props.remoteNodeNetworks!.slice(i + 1).forEach((network2, j) => {
22762276
const [overlap, remoteNodeCidr1, remoteNodeCidr2] = validateCidrBlocksOverlap(network1.cidrs, network2.cidrs);
22772277
if (overlap) {
2278-
throw new Error(`CIDR block ${remoteNodeCidr1} in remote node network #${i + 1} should not overlap with CIDR block ${remoteNodeCidr2} in remote node network #${i + j + 2}`);
2278+
throw new ValidationError(`CIDR block ${remoteNodeCidr1} in remote node network #${i + 1} should not overlap with CIDR block ${remoteNodeCidr2} in remote node network #${i + j + 2}`, this);
22792279
}
22802280
});
22812281
});
@@ -2287,7 +2287,7 @@ export class Cluster extends ClusterBase {
22872287
if (cidrs.length > 1) {
22882288
cidrs.forEach((cidr1, j) => {
22892289
if (cidrs.slice(j + 1).some(cidr2 => validateCidrPairOverlap(cidr1, cidr2))) {
2290-
throw new Error(`CIDR ${cidr1} should not overlap with another CIDR in remote pod network #${index + 1}`);
2290+
throw new ValidationError(`CIDR ${cidr1} should not overlap with another CIDR in remote pod network #${index + 1}`, this);
22912291
}
22922292
});
22932293
}
@@ -2298,7 +2298,7 @@ export class Cluster extends ClusterBase {
22982298
props.remotePodNetworks!.slice(i + 1).forEach((network2, j) => {
22992299
const [overlap, remotePodCidr1, remotePodCidr2] = validateCidrBlocksOverlap(network1.cidrs, network2.cidrs);
23002300
if (overlap) {
2301-
throw new Error(`CIDR block ${remotePodCidr1} in remote pod network #${i + 1} should not overlap with CIDR block ${remotePodCidr2} in remote pod network #${i + j + 2}`);
2301+
throw new ValidationError(`CIDR block ${remotePodCidr1} in remote pod network #${i + 1} should not overlap with CIDR block ${remotePodCidr2} in remote pod network #${i + j + 2}`, this);
23022302
}
23032303
});
23042304
});
@@ -2308,7 +2308,7 @@ export class Cluster extends ClusterBase {
23082308
for (const podNetwork of props.remotePodNetworks) {
23092309
const [overlap, remoteNodeCidr, remotePodCidr] = validateCidrBlocksOverlap(nodeNetwork.cidrs, podNetwork.cidrs);
23102310
if (overlap) {
2311-
throw new Error(`Remote node network CIDR block ${remoteNodeCidr} should not overlap with remote pod network CIDR block ${remotePodCidr}`);
2311+
throw new ValidationError(`Remote node network CIDR block ${remoteNodeCidr} should not overlap with remote pod network CIDR block ${remotePodCidr}`, this);
23122312
}
23132313
}
23142314
}
@@ -2563,55 +2563,55 @@ class ImportedCluster extends ClusterBase {
25632563

25642564
public get vpc() {
25652565
if (!this.props.vpc) {
2566-
throw new Error('"vpc" is not defined for this imported cluster');
2566+
throw new ValidationError('"vpc" is not defined for this imported cluster', this);
25672567
}
25682568
return this.props.vpc;
25692569
}
25702570

25712571
public get clusterSecurityGroup(): ec2.ISecurityGroup {
25722572
if (!this._clusterSecurityGroup) {
2573-
throw new Error('"clusterSecurityGroup" is not defined for this imported cluster');
2573+
throw new ValidationError('"clusterSecurityGroup" is not defined for this imported cluster', this);
25742574
}
25752575
return this._clusterSecurityGroup;
25762576
}
25772577

25782578
public get clusterSecurityGroupId(): string {
25792579
if (!this.props.clusterSecurityGroupId) {
2580-
throw new Error('"clusterSecurityGroupId" is not defined for this imported cluster');
2580+
throw new ValidationError('"clusterSecurityGroupId" is not defined for this imported cluster', this);
25812581
}
25822582
return this.props.clusterSecurityGroupId;
25832583
}
25842584

25852585
public get clusterEndpoint(): string {
25862586
if (!this.props.clusterEndpoint) {
2587-
throw new Error('"clusterEndpoint" is not defined for this imported cluster');
2587+
throw new ValidationError('"clusterEndpoint" is not defined for this imported cluster', this);
25882588
}
25892589
return this.props.clusterEndpoint;
25902590
}
25912591

25922592
public get clusterCertificateAuthorityData(): string {
25932593
if (!this.props.clusterCertificateAuthorityData) {
2594-
throw new Error('"clusterCertificateAuthorityData" is not defined for this imported cluster');
2594+
throw new ValidationError('"clusterCertificateAuthorityData" is not defined for this imported cluster', this);
25952595
}
25962596
return this.props.clusterCertificateAuthorityData;
25972597
}
25982598

25992599
public get clusterEncryptionConfigKeyArn(): string {
26002600
if (!this.props.clusterEncryptionConfigKeyArn) {
2601-
throw new Error('"clusterEncryptionConfigKeyArn" is not defined for this imported cluster');
2601+
throw new ValidationError('"clusterEncryptionConfigKeyArn" is not defined for this imported cluster', this);
26022602
}
26032603
return this.props.clusterEncryptionConfigKeyArn;
26042604
}
26052605

26062606
public get openIdConnectProvider(): iam.IOpenIdConnectProvider {
26072607
if (!this.props.openIdConnectProvider) {
2608-
throw new Error('"openIdConnectProvider" is not defined for this imported cluster');
2608+
throw new ValidationError('"openIdConnectProvider" is not defined for this imported cluster', this);
26092609
}
26102610
return this.props.openIdConnectProvider;
26112611
}
26122612

26132613
public get awsAuth(): AwsAuth {
2614-
throw new Error('"awsAuth" is not supported on imported clusters');
2614+
throw new ValidationError('"awsAuth" is not supported on imported clusters', this);
26152615
}
26162616
}
26172617

packages/aws-cdk-lib/aws-eks/lib/fargate-profile.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { FARGATE_PROFILE_RESOURCE_TYPE } from './cluster-resource-handler/consts
44
import { ClusterResourceProvider } from './cluster-resource-provider';
55
import * as ec2 from '../../aws-ec2';
66
import * as iam from '../../aws-iam';
7-
import { Annotations, CustomResource, ITaggable, Lazy, TagManager, TagType } from '../../core';
7+
import { Annotations, CustomResource, ITaggable, Lazy, TagManager, TagType, ValidationError } from '../../core';
88

99
/**
1010
* Options for defining EKS Fargate Profiles.
@@ -164,11 +164,11 @@ export class FargateProfile extends Construct implements ITaggable {
164164
}
165165

166166
if (props.selectors.length < 1) {
167-
throw new Error('Fargate profile requires at least one selector');
167+
throw new ValidationError('Fargate profile requires at least one selector', this);
168168
}
169169

170170
if (props.selectors.length > 5) {
171-
throw new Error('Fargate profile supports up to five selectors');
171+
throw new ValidationError('Fargate profile supports up to five selectors', this);
172172
}
173173

174174
this.tags = new TagManager(TagType.MAP, 'AWS::EKS::FargateProfile');

packages/aws-cdk-lib/aws-eks/lib/helm-chart.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
22
import { ICluster } from './cluster';
33
import { KubectlProvider } from './kubectl-provider';
44
import { Asset } from '../../aws-s3-assets';
5-
import { CustomResource, Duration, Names, Stack } from '../../core';
5+
import { CustomResource, Duration, Names, Stack, ValidationError } from '../../core';
66

77
/**
88
* Helm Chart options.
@@ -137,16 +137,16 @@ export class HelmChart extends Construct {
137137

138138
const timeout = props.timeout?.toSeconds();
139139
if (timeout && timeout > 900) {
140-
throw new Error('Helm chart timeout cannot be higher than 15 minutes.');
140+
throw new ValidationError('Helm chart timeout cannot be higher than 15 minutes.', this);
141141
}
142142

143143
if (!this.chart && !this.chartAsset) {
144-
throw new Error("Either 'chart' or 'chartAsset' must be specified to install a helm chart");
144+
throw new ValidationError("Either 'chart' or 'chartAsset' must be specified to install a helm chart", this);
145145
}
146146

147147
if (this.chartAsset && (this.repository || this.version)) {
148-
throw new Error(
149-
"Neither 'repository' nor 'version' can be used when configuring 'chartAsset'",
148+
throw new ValidationError(
149+
"Neither 'repository' nor 'version' can be used when configuring 'chartAsset'", this,
150150
);
151151
}
152152

packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Construct, IConstruct } from 'constructs';
22
import { ICluster, Cluster } from './cluster';
33
import * as iam from '../../aws-iam';
4-
import { Duration, Stack, NestedStack, Names, CfnCondition, Fn, Aws } from '../../core';
4+
import { Duration, Stack, NestedStack, Names, CfnCondition, Fn, Aws, ValidationError } from '../../core';
55
import { KubectlFunction } from '../../custom-resource-handlers/dist/aws-eks/kubectl-provider.generated';
66
import * as cr from '../../custom-resources';
77
import { AwsCliLayer } from '../../lambda-layer-awscli';
@@ -121,11 +121,11 @@ export class KubectlProvider extends NestedStack implements IKubectlProvider {
121121
const cluster = props.cluster;
122122

123123
if (!cluster.kubectlRole) {
124-
throw new Error('"kubectlRole" is not defined, cannot issue kubectl commands against this cluster');
124+
throw new ValidationError('"kubectlRole" is not defined, cannot issue kubectl commands against this cluster', this);
125125
}
126126

127127
if (cluster.kubectlPrivateSubnets && !cluster.kubectlSecurityGroup) {
128-
throw new Error('"kubectlSecurityGroup" is required if "kubectlSubnets" is specified');
128+
throw new ValidationError('"kubectlSecurityGroup" is required if "kubectlSubnets" is specified', this);
129129
}
130130

131131
const memorySize = cluster.kubectlMemory ? cluster.kubectlMemory.toMebibytes() : 1024;

0 commit comments

Comments
 (0)