diff --git a/packages/aws-cdk-lib/.eslintrc.js b/packages/aws-cdk-lib/.eslintrc.js index 7f8f4a9f3e0ba..bd396867c73d3 100644 --- a/packages/aws-cdk-lib/.eslintrc.js +++ b/packages/aws-cdk-lib/.eslintrc.js @@ -41,6 +41,7 @@ const enableNoThrowDefaultErrorIn = [ 'aws-cloudtrail', 'aws-cloudwatch', 'aws-cloudwatch-actions', + 'aws-ecr', 'aws-elasticloadbalancing', 'aws-elasticloadbalancingv2', 'aws-elasticloadbalancingv2-actions', diff --git a/packages/aws-cdk-lib/aws-ecr/lib/repository.ts b/packages/aws-cdk-lib/aws-ecr/lib/repository.ts index c2e647d3dd4ac..713d14ee8c800 100644 --- a/packages/aws-cdk-lib/aws-ecr/lib/repository.ts +++ b/packages/aws-cdk-lib/aws-ecr/lib/repository.ts @@ -18,6 +18,8 @@ import { TokenComparison, CustomResource, Aws, + ValidationError, + UnscopedValidationError, } from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { AutoDeleteImagesProvider } from '../../custom-resource-handlers/dist/aws-ecr/auto-delete-images-provider.generated'; @@ -630,7 +632,7 @@ export class Repository extends RepositoryBase { // repository names can include "/" (e.g. foo/bar/myrepo) and it is impossible to // parse the name from an ARN using CloudFormation's split/select. if (Token.isUnresolved(repositoryArn)) { - throw new Error('"repositoryArn" is a late-bound value, and therefore "repositoryName" is required. Use `fromRepositoryAttributes` instead'); + throw new UnscopedValidationError('"repositoryArn" is a late-bound value, and therefore "repositoryName" is required. Use `fromRepositoryAttributes` instead'); } validateRepositoryArn(); @@ -655,7 +657,7 @@ export class Repository extends RepositoryBase { const splitArn = repositoryArn.split(':'); if (!splitArn[splitArn.length - 1].startsWith('repository/')) { - throw new Error(`Repository arn should be in the format 'arn::ecr:::repository/', got ${repositoryArn}.`); + throw new UnscopedValidationError(`Repository arn should be in the format 'arn::ecr:::repository/', got ${repositoryArn}.`); } } } @@ -707,7 +709,7 @@ export class Repository extends RepositoryBase { } if (errors.length > 0) { - throw new Error(`Invalid ECR repository name (value: ${repositoryName})${EOL}${errors.join(EOL)}`); + throw new UnscopedValidationError(`Invalid ECR repository name (value: ${repositoryName})${EOL}${errors.join(EOL)}`); } } @@ -754,10 +756,10 @@ export class Repository extends RepositoryBase { }); if (props.emptyOnDelete && props.removalPolicy !== RemovalPolicy.DESTROY) { - throw new Error('Cannot use \'emptyOnDelete\' property on a repository without setting removal policy to \'DESTROY\'.'); + throw new ValidationError('Cannot use \'emptyOnDelete\' property on a repository without setting removal policy to \'DESTROY\'.', this); } else if (props.emptyOnDelete == undefined && props.autoDeleteImages) { if (props.removalPolicy !== RemovalPolicy.DESTROY) { - throw new Error('Cannot use \'autoDeleteImages\' property on a repository without setting removal policy to \'DESTROY\'.'); + throw new ValidationError('Cannot use \'autoDeleteImages\' property on a repository without setting removal policy to \'DESTROY\'.', this); } this.enableAutoDeleteImages(); } @@ -801,28 +803,28 @@ export class Repository extends RepositoryBase { && (rule.tagPrefixList === undefined || rule.tagPrefixList.length === 0) && (rule.tagPatternList === undefined || rule.tagPatternList.length === 0) ) { - throw new Error('TagStatus.Tagged requires the specification of a tagPrefixList or a tagPatternList'); + throw new ValidationError('TagStatus.Tagged requires the specification of a tagPrefixList or a tagPatternList', this); } if (rule.tagStatus !== TagStatus.TAGGED && (rule.tagPrefixList !== undefined || rule.tagPatternList !== undefined)) { - throw new Error('tagPrefixList and tagPatternList can only be specified when tagStatus is set to Tagged'); + throw new ValidationError('tagPrefixList and tagPatternList can only be specified when tagStatus is set to Tagged', this); } if (rule.tagPrefixList !== undefined && rule.tagPatternList !== undefined) { - throw new Error('Both tagPrefixList and tagPatternList cannot be specified together in a rule'); + throw new ValidationError('Both tagPrefixList and tagPatternList cannot be specified together in a rule', this); } if (rule.tagPatternList !== undefined) { rule.tagPatternList.forEach((pattern) => { const splitPatternLength = pattern.split('*').length; if (splitPatternLength > 5) { - throw new Error(`A tag pattern cannot contain more than four wildcard characters (*), pattern: ${pattern}, counts: ${splitPatternLength - 1}`); + throw new ValidationError(`A tag pattern cannot contain more than four wildcard characters (*), pattern: ${pattern}, counts: ${splitPatternLength - 1}`, this); } }); } if ((rule.maxImageAge !== undefined) === (rule.maxImageCount !== undefined)) { - throw new Error(`Life cycle rule must contain exactly one of 'maxImageAge' and 'maxImageCount', got: ${JSON.stringify(rule)}`); + throw new ValidationError(`Life cycle rule must contain exactly one of 'maxImageAge' and 'maxImageCount', got: ${JSON.stringify(rule)}`, this); } if (rule.tagStatus === TagStatus.ANY && this.lifecycleRules.filter(r => r.tagStatus === TagStatus.ANY).length > 0) { - throw new Error('Life cycle can only have one TagStatus.Any rule'); + throw new ValidationError('Life cycle can only have one TagStatus.Any rule', this); } this.lifecycleRules.push({ ...rule }); @@ -862,7 +864,7 @@ export class Repository extends RepositoryBase { const anyRules = this.lifecycleRules.filter(r => r.tagStatus === TagStatus.ANY); if (anyRules.length > 0 && anyRules[0].rulePriority !== undefined && autoPrioritizedRules.length > 0) { // Supporting this is too complex for very little value. We just prohibit it. - throw new Error("Cannot combine prioritized TagStatus.Any rule with unprioritized rules. Remove rulePriority from the 'Any' rule."); + throw new ValidationError("Cannot combine prioritized TagStatus.Any rule with unprioritized rules. Remove rulePriority from the 'Any' rule.", this); } const prios = prioritizedRules.map(r => r.rulePriority!); @@ -891,7 +893,7 @@ export class Repository extends RepositoryBase { // if encryption key is set, encryption must be set to KMS. if (encryptionType !== RepositoryEncryption.KMS && props.encryptionKey) { - throw new Error(`encryptionKey is specified, so 'encryption' must be set to KMS (value: ${encryptionType.value})`); + throw new ValidationError(`encryptionKey is specified, so 'encryption' must be set to KMS (value: ${encryptionType.value})`, this); } if (encryptionType === RepositoryEncryption.AES_256) { @@ -905,7 +907,7 @@ export class Repository extends RepositoryBase { }; } - throw new Error(`Unexpected 'encryptionType': ${encryptionType}`); + throw new ValidationError(`Unexpected 'encryptionType': ${encryptionType}`, this); } private enableAutoDeleteImages() { @@ -958,7 +960,7 @@ function validateAnyRuleLast(rules: LifecycleRule[]) { if (anyRules.length === 1) { const maxPrio = Math.max(...rules.map(r => r.rulePriority!)); if (anyRules[0].rulePriority !== maxPrio) { - throw new Error(`TagStatus.Any rule must have highest priority, has ${anyRules[0].rulePriority} which is smaller than ${maxPrio}`); + throw new UnscopedValidationError(`TagStatus.Any rule must have highest priority, has ${anyRules[0].rulePriority} which is smaller than ${maxPrio}`); } } }