Skip to content

Commit 3ff5501

Browse files
authored
feat(codepipeline): throw ValidationErrors instead of untyped Errors (#33855)
### Issue # (if applicable) Relates to #32569 ### Reason for this change untyped Errors are not recommended ### Description of changes ValidationErrors 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 f28eae2 commit 3ff5501

File tree

20 files changed

+116
-92
lines changed

20 files changed

+116
-92
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const enableNoThrowDefaultErrorIn = [
4343
'aws-codebuild',
4444
'aws-codecommit',
4545
'aws-codedeploy',
46+
'aws-codepipeline',
47+
'aws-codepipeline-actions',
4648
'aws-cognito',
4749
'aws-ecr',
4850
'aws-elasticloadbalancing',

packages/aws-cdk-lib/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ abstract class CloudFormationDeployAction extends CloudFormationAction {
349349
if (this._deploymentRole) {
350350
return this._deploymentRole;
351351
} else {
352-
throw new Error(`Cannot use the ${member} before the Action has been added to a Pipeline`);
352+
throw new cdk.UnscopedValidationError(`Cannot use the ${member} before the Action has been added to a Pipeline`);
353353
}
354354
}
355355
}

packages/aws-cdk-lib/aws-codepipeline-actions/lib/cloudformation/stackset-types.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export abstract class StackInstances {
132132
*/
133133
public static fromArtifactPath(artifactPath: codepipeline.ArtifactPath, regions: string[]): StackInstances {
134134
if (regions.length === 0) {
135-
throw new Error("'regions' may not be an empty list");
135+
throw new cdk.UnscopedValidationError("'regions' may not be an empty list");
136136
}
137137

138138
return new class extends StackInstances {
@@ -159,11 +159,11 @@ export abstract class StackInstances {
159159
*/
160160
private static fromList(targets: string[], regions: string[]): StackInstances {
161161
if (targets.length === 0) {
162-
throw new Error("'targets' may not be an empty list");
162+
throw new cdk.UnscopedValidationError("'targets' may not be an empty list");
163163
}
164164

165165
if (regions.length === 0) {
166-
throw new Error("'regions' may not be an empty list");
166+
throw new cdk.UnscopedValidationError("'regions' may not be an empty list");
167167
}
168168

169169
return new class extends StackInstances {

packages/aws-cdk-lib/aws-codepipeline-actions/lib/codebuild/build-action.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,12 @@ export class CodeBuildAction extends Action {
153153
const pipelineStack = cdk.Stack.of(scope);
154154
const projectStack = cdk.Stack.of(this.props.project);
155155
if (pipelineStack.account !== projectStack.account) {
156-
throw new Error('A cross-account CodeBuild action cannot have outputs. ' +
156+
throw new cdk.ValidationError(
157+
'A cross-account CodeBuild action cannot have outputs. ' +
157158
'This is a known CodeBuild limitation. ' +
158-
'See https://github.com/aws/aws-cdk/issues/4169 for details');
159+
'See https://github.com/aws/aws-cdk/issues/4169 for details',
160+
scope,
161+
);
159162
}
160163
}
161164

packages/aws-cdk-lib/aws-codepipeline-actions/lib/codecommit/source-action.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as codepipeline from '../../../aws-codepipeline';
44
import { EventPattern, IRuleTarget } from '../../../aws-events';
55
import * as targets from '../../../aws-events-targets';
66
import * as iam from '../../../aws-iam';
7-
import { FeatureFlags, Names, Stack, Token, TokenComparison } from '../../../core';
7+
import { FeatureFlags, Names, Stack, Token, TokenComparison, UnscopedValidationError } from '../../../core';
88
import { CODECOMMIT_SOURCE_ACTION_DEFAULT_BRANCH_NAME } from '../../../cx-api';
99
import { Action } from '../action';
1010
import { sourceArtifactBounds } from '../common';
@@ -169,7 +169,7 @@ export class CodeCommitSourceAction extends Action {
169169
constructor(props: CodeCommitSourceActionProps) {
170170
const branch = props.branch ?? CodeCommitSourceAction.OLD_DEFAULT_BRANCH_NAME;
171171
if (!branch) {
172-
throw new Error("'branch' parameter cannot be an empty string");
172+
throw new UnscopedValidationError("'branch' parameter cannot be an empty string");
173173
}
174174

175175
if (props.codeBuildCloneOutput === true) {

packages/aws-cdk-lib/aws-codepipeline-actions/lib/codedeploy/ecs-deploy-action.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
22
import * as codedeploy from '../../../aws-codedeploy';
33
import * as codepipeline from '../../../aws-codepipeline';
44
import * as iam from '../../../aws-iam';
5-
import { Lazy } from '../../../core';
5+
import { Lazy, UnscopedValidationError } from '../../../core';
66
import { Action } from '../action';
77
import { forceSupportStackDependency } from '../private/stack-dependency';
88

@@ -117,7 +117,7 @@ export class CodeDeployEcsDeployAction extends Action {
117117

118118
if (props.containerImageInputs) {
119119
if (props.containerImageInputs.length > 4) {
120-
throw new Error(`Action cannot have more than 4 container image inputs, got: ${props.containerImageInputs.length}`);
120+
throw new UnscopedValidationError(`Action cannot have more than 4 container image inputs, got: ${props.containerImageInputs.length}`);
121121
}
122122

123123
for (const imageInput of props.containerImageInputs) {
@@ -215,26 +215,26 @@ export class CodeDeployEcsDeployAction extends Action {
215215

216216
function determineTaskDefinitionArtifact(props: CodeDeployEcsDeployActionProps): codepipeline.Artifact {
217217
if (props.taskDefinitionTemplateFile && props.taskDefinitionTemplateInput) {
218-
throw new Error("Exactly one of 'taskDefinitionTemplateInput' or 'taskDefinitionTemplateFile' can be provided in the ECS CodeDeploy Action");
218+
throw new UnscopedValidationError("Exactly one of 'taskDefinitionTemplateInput' or 'taskDefinitionTemplateFile' can be provided in the ECS CodeDeploy Action");
219219
}
220220
if (props.taskDefinitionTemplateFile) {
221221
return props.taskDefinitionTemplateFile.artifact;
222222
}
223223
if (props.taskDefinitionTemplateInput) {
224224
return props.taskDefinitionTemplateInput;
225225
}
226-
throw new Error("Specifying one of 'taskDefinitionTemplateInput' or 'taskDefinitionTemplateFile' is required for the ECS CodeDeploy Action");
226+
throw new UnscopedValidationError("Specifying one of 'taskDefinitionTemplateInput' or 'taskDefinitionTemplateFile' is required for the ECS CodeDeploy Action");
227227
}
228228

229229
function determineAppSpecArtifact(props: CodeDeployEcsDeployActionProps): codepipeline.Artifact {
230230
if (props.appSpecTemplateFile && props.appSpecTemplateInput) {
231-
throw new Error("Exactly one of 'appSpecTemplateInput' or 'appSpecTemplateFile' can be provided in the ECS CodeDeploy Action");
231+
throw new UnscopedValidationError("Exactly one of 'appSpecTemplateInput' or 'appSpecTemplateFile' can be provided in the ECS CodeDeploy Action");
232232
}
233233
if (props.appSpecTemplateFile) {
234234
return props.appSpecTemplateFile.artifact;
235235
}
236236
if (props.appSpecTemplateInput) {
237237
return props.appSpecTemplateInput;
238238
}
239-
throw new Error("Specifying one of 'appSpecTemplateInput' or 'appSpecTemplateFile' is required for the ECS CodeDeploy Action");
239+
throw new UnscopedValidationError("Specifying one of 'appSpecTemplateInput' or 'appSpecTemplateFile' is required for the ECS CodeDeploy Action");
240240
}

packages/aws-cdk-lib/aws-codepipeline-actions/lib/commands/commands-action.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ export class CommandsAction extends Action {
7171
});
7272

7373
if (props.commands.length < 1 || props.commands.length > 50) {
74-
throw new Error(`The length of the commands array must be between 1 and 50, got: ${props.commands.length}`);
74+
throw new cdk.UnscopedValidationError(`The length of the commands array must be between 1 and 50, got: ${props.commands.length}`);
7575
}
7676

7777
if (props.outputVariables !== undefined && (props.outputVariables.length < 1 || props.outputVariables.length > 15)) {
78-
throw new Error(`The length of the outputVariables array must be between 1 and 15, got: ${props.outputVariables.length}`);
78+
throw new cdk.UnscopedValidationError(`The length of the outputVariables array must be between 1 and 15, got: ${props.outputVariables.length}`);
7979
}
8080

8181
this.outputVariables = props.outputVariables || [];
@@ -89,7 +89,7 @@ export class CommandsAction extends Action {
8989
*/
9090
public variable(variableName: string): string {
9191
if (!this.outputVariables.includes(variableName)) {
92-
throw new Error(`Variable '${variableName}' is not exported by \`outputVariables\`, exported variables: ${this.outputVariables.join(', ')}`);
92+
throw new cdk.UnscopedValidationError(`Variable '${variableName}' is not exported by \`outputVariables\`, exported variables: ${this.outputVariables.join(', ')}`);
9393
}
9494
return this.variableExpression(variableName);
9595
}

packages/aws-cdk-lib/aws-codepipeline-actions/lib/common.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as codepipeline from '../../aws-codepipeline';
2-
import { Token } from '../../core';
2+
import { Token, UnscopedValidationError } from '../../core';
33

44
/**
55
* The ArtifactBounds that make sense for source Actions -
@@ -33,6 +33,6 @@ export function validatePercentage(name: string, value?: number) {
3333
}
3434

3535
if (value < 0 || value > 100 || !Number.isInteger(value)) {
36-
throw new Error(`'${name}': must be a whole number between 0 and 100, got: ${value}`);
36+
throw new UnscopedValidationError(`'${name}': must be a whole number between 0 and 100, got: ${value}`);
3737
}
3838
}

packages/aws-cdk-lib/aws-codepipeline-actions/lib/ecs/deploy-action.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
22
import * as codepipeline from '../../../aws-codepipeline';
33
import * as ecs from '../../../aws-ecs';
44
import * as iam from '../../../aws-iam';
5-
import { Duration } from '../../../core';
5+
import { Duration, UnscopedValidationError } from '../../../core';
66
import { Action } from '../action';
77
import { deployArtifactBounds } from '../common';
88

@@ -70,7 +70,7 @@ export class EcsDeployAction extends Action {
7070

7171
const deploymentTimeout = props.deploymentTimeout?.toMinutes({ integral: true });
7272
if (deploymentTimeout !== undefined && (deploymentTimeout < 1 || deploymentTimeout > 60)) {
73-
throw new Error(`Deployment timeout must be between 1 and 60 minutes, got: ${deploymentTimeout}`);
73+
throw new UnscopedValidationError(`Deployment timeout must be between 1 and 60 minutes, got: ${deploymentTimeout}`);
7474
}
7575

7676
this.props = props;
@@ -122,13 +122,13 @@ export class EcsDeployAction extends Action {
122122

123123
function determineInputArtifact(props: EcsDeployActionProps): codepipeline.Artifact {
124124
if (props.imageFile && props.input) {
125-
throw new Error("Exactly one of 'input' or 'imageFile' can be provided in the ECS deploy Action");
125+
throw new UnscopedValidationError("Exactly one of 'input' or 'imageFile' can be provided in the ECS deploy Action");
126126
}
127127
if (props.imageFile) {
128128
return props.imageFile.artifact;
129129
}
130130
if (props.input) {
131131
return props.input;
132132
}
133-
throw new Error("Specifying one of 'input' or 'imageFile' is required for the ECS deploy Action");
133+
throw new UnscopedValidationError("Specifying one of 'input' or 'imageFile' is required for the ECS deploy Action");
134134
}

packages/aws-cdk-lib/aws-codepipeline-actions/lib/lambda/invoke-action.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
22
import * as codepipeline from '../../../aws-codepipeline';
33
import * as iam from '../../../aws-iam';
44
import * as lambda from '../../../aws-lambda';
5-
import { Stack } from '../../../core';
5+
import { Stack, UnscopedValidationError } from '../../../core';
66
import { Action } from '../action';
77

88
/**
@@ -84,7 +84,7 @@ export class LambdaInvokeAction extends Action {
8484
this.props = props;
8585

8686
if (props.userParameters && props.userParametersString) {
87-
throw new Error('Only one of userParameters or userParametersString can be specified');
87+
throw new UnscopedValidationError('Only one of userParameters or userParametersString can be specified');
8888
}
8989
}
9090

packages/aws-cdk-lib/aws-codepipeline-actions/lib/manual-approval-action.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as codepipeline from '../../aws-codepipeline';
44
import * as iam from '../../aws-iam';
55
import * as sns from '../../aws-sns';
66
import * as subs from '../../aws-sns-subscriptions';
7-
import { Duration } from '../../core';
7+
import { Duration, UnscopedValidationError } from '../../core';
88

99
/**
1010
* Construction properties of the `ManualApprovalAction`.
@@ -67,7 +67,7 @@ export class ManualApprovalAction extends Action {
6767
});
6868

6969
if (props.timeout && (props.timeout.toMinutes() < 5 || props.timeout.toMinutes() > 86400)) {
70-
throw new Error(`timeout must be between 5 minutes and 86400 minutes (60 days), got ${props.timeout.toMinutes()} minutes`);
70+
throw new UnscopedValidationError(`timeout must be between 5 minutes and 86400 minutes (60 days), got ${props.timeout.toMinutes()} minutes`);
7171
}
7272

7373
this.props = props;
@@ -87,7 +87,7 @@ export class ManualApprovalAction extends Action {
8787
*/
8888
public grantManualApproval(grantable: iam.IGrantable): void {
8989
if (!this.stage) {
90-
throw new Error('Cannot grant permissions before binding action to a stage');
90+
throw new UnscopedValidationError('Cannot grant permissions before binding action to a stage');
9191
}
9292
grantable.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({
9393
actions: ['codepipeline:ListPipelines'],

packages/aws-cdk-lib/aws-codepipeline-actions/lib/s3/source-action.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
22
import * as codepipeline from '../../../aws-codepipeline';
33
import * as targets from '../../../aws-events-targets';
44
import * as s3 from '../../../aws-s3';
5-
import { Names, Token } from '../../../core';
5+
import { Names, Token, UnscopedValidationError } from '../../../core';
66
import { Action } from '../action';
77
import { sourceArtifactBounds } from '../common';
88

@@ -97,7 +97,7 @@ export class S3SourceAction extends Action {
9797
});
9898

9999
if (props.bucketKey.length === 0) {
100-
throw new Error('Property bucketKey cannot be an empty string');
100+
throw new UnscopedValidationError('Property bucketKey cannot be an empty string');
101101
}
102102

103103
this.props = props;
@@ -159,7 +159,7 @@ export class S3SourceAction extends Action {
159159
ret = baseId + this.props.bucketKey;
160160
if (this.props.bucket.node.tryFindChild(ret)) {
161161
// this means a duplicate path for the same bucket - error out
162-
throw new Error(`S3 source action with path '${this.props.bucketKey}' is already present in the pipeline for this source bucket`);
162+
throw new UnscopedValidationError(`S3 source action with path '${this.props.bucketKey}' is already present in the pipeline for this source bucket`);
163163
}
164164
}
165165

packages/aws-cdk-lib/aws-codepipeline/lib/action.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as notifications from '../../aws-codestarnotifications';
44
import * as events from '../../aws-events';
55
import * as iam from '../../aws-iam';
66
import * as s3 from '../../aws-s3';
7-
import { Duration, IResource, Lazy } from '../../core';
7+
import { Duration, IResource, Lazy, UnscopedValidationError } from '../../core';
88

99
export enum ActionCategory {
1010
SOURCE = 'Source',
@@ -398,8 +398,7 @@ export abstract class Action implements IAction {
398398
produce: () => {
399399
// make sure the action was bound (= added to a pipeline)
400400
if (this._actualNamespace === undefined) {
401-
throw new Error(`Cannot reference variables of action '${this.actionProperties.actionName}', ` +
402-
'as that action was never added to a pipeline');
401+
throw new UnscopedValidationError(`Cannot reference variables of action '${this.actionProperties.actionName}', as that action was never added to a pipeline`);
403402
} else {
404403
return this._customerProvidedNamespace !== undefined
405404
// if a customer passed a namespace explicitly, always use that
@@ -467,15 +466,15 @@ export abstract class Action implements IAction {
467466
if (this.__pipeline) {
468467
return this.__pipeline;
469468
} else {
470-
throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange');
469+
throw new UnscopedValidationError('Action must be added to a stage that is part of a pipeline before using onStateChange');
471470
}
472471
}
473472

474473
private get _stage(): IStage {
475474
if (this.__stage) {
476475
return this.__stage;
477476
} else {
478-
throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange');
477+
throw new UnscopedValidationError('Action must be added to a stage that is part of a pipeline before using onStateChange');
479478
}
480479
}
481480

@@ -488,7 +487,7 @@ export abstract class Action implements IAction {
488487
if (this.__scope) {
489488
return this.__scope;
490489
} else {
491-
throw new Error('Action must be added to a stage that is part of a pipeline first');
490+
throw new UnscopedValidationError('Action must be added to a stage that is part of a pipeline first');
492491
}
493492
}
494493
}

packages/aws-cdk-lib/aws-codepipeline/lib/artifact.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as validation from './private/validation';
22
import * as s3 from '../../aws-s3';
3-
import { Lazy, Token } from '../../core';
3+
import { Lazy, Token, UnscopedValidationError } from '../../core';
44

55
/**
66
* An output artifact of an action. Artifacts can be used as input by some actions.
@@ -35,7 +35,7 @@ export class Artifact {
3535
validation.validateArtifactName(artifactName);
3636

3737
if (artifactFiles !== undefined && (artifactFiles.length < 1 || artifactFiles.length > 10)) {
38-
throw new Error(`The length of the artifactFiles array must be between 1 and 10, got: ${artifactFiles.length}`);
38+
throw new UnscopedValidationError(`The length of the artifactFiles array must be between 1 and 10, got: ${artifactFiles.length}`);
3939
}
4040

4141
this._artifactName = artifactName;
@@ -133,7 +133,7 @@ export class Artifact {
133133
/** @internal */
134134
protected _setName(name: string) {
135135
if (this._artifactName) {
136-
throw new Error(`Artifact already has name '${this._artifactName}', cannot override it`);
136+
throw new UnscopedValidationError(`Artifact already has name '${this._artifactName}', cannot override it`);
137137
} else {
138138
this._artifactName = name;
139139
}

0 commit comments

Comments
 (0)