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(backup): throw ValidationError instead of untyped Errors #33387

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -16,6 +16,7 @@ baseConfig.rules['import/no-extraneous-dependencies'] = [

// no-throw-default-error
const enableNoThrowDefaultErrorIn = [
'aws-backup',
'aws-amplify',
'aws-amplifyuibuilder',
'aws-apigateway',
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk-lib/aws-backup/lib/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CfnBackupPlan } from './backup.generated';
import { BackupPlanCopyActionProps, BackupPlanRule } from './rule';
import { BackupSelection, BackupSelectionOptions } from './selection';
import { BackupVault, IBackupVault } from './vault';
import { IResource, Lazy, Resource } from '../../core';
import { IResource, Lazy, Resource, ValidationError } from '../../core';
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';

/**
Expand Down Expand Up @@ -217,7 +217,7 @@ export class BackupPlan extends Resource implements IBackupPlan {
public get backupVault(): IBackupVault {
if (!this._backupVault) {
// This cannot happen but is here to make TypeScript happy
throw new Error('No backup vault!');
throw new ValidationError('No backup vault!', this);
}

return this._backupVault;
Expand Down
12 changes: 6 additions & 6 deletions packages/aws-cdk-lib/aws-backup/lib/rule.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IBackupVault } from './vault';
import * as events from '../../aws-events';
import { Duration, Token } from '../../core';
import { Duration, Token, UnscopedValidationError } from '../../core';

/**
* Properties for a BackupPlanRule
Expand Down Expand Up @@ -206,30 +206,30 @@ export class BackupPlanRule {
constructor(props: BackupPlanRuleProps) {
if (props.deleteAfter && props.moveToColdStorageAfter &&
props.deleteAfter.toDays() < props.moveToColdStorageAfter.toDays()) {
throw new Error('`deleteAfter` must be greater than `moveToColdStorageAfter`');
throw new UnscopedValidationError('`deleteAfter` must be greater than `moveToColdStorageAfter`');
}

if (props.scheduleExpression && !/^cron/.test(props.scheduleExpression.expressionString)) {
throw new Error('`scheduleExpression` must be of type `cron`');
throw new UnscopedValidationError('`scheduleExpression` must be of type `cron`');
}

const deleteAfter = (props.enableContinuousBackup && !props.deleteAfter) ? Duration.days(35) : props.deleteAfter;

if (props.enableContinuousBackup && props.moveToColdStorageAfter) {
throw new Error('`moveToColdStorageAfter` must not be specified if `enableContinuousBackup` is enabled');
throw new UnscopedValidationError('`moveToColdStorageAfter` must not be specified if `enableContinuousBackup` is enabled');
}

if (props.enableContinuousBackup && props.deleteAfter &&
(props.deleteAfter?.toDays() < 1 || props.deleteAfter?.toDays() > 35)) {
throw new Error(`'deleteAfter' must be between 1 and 35 days if 'enableContinuousBackup' is enabled, but got ${props.deleteAfter.toHumanString()}`);
throw new UnscopedValidationError(`'deleteAfter' must be between 1 and 35 days if 'enableContinuousBackup' is enabled, but got ${props.deleteAfter.toHumanString()}`);
}

if (props.copyActions && props.copyActions.length > 0) {
props.copyActions.forEach(copyAction => {
if (copyAction.deleteAfter && !Token.isUnresolved(copyAction.deleteAfter) &&
copyAction.moveToColdStorageAfter && !Token.isUnresolved(copyAction.moveToColdStorageAfter) &&
copyAction.deleteAfter.toDays() < copyAction.moveToColdStorageAfter.toDays() + 90) {
throw new Error([
throw new UnscopedValidationError([
'\'deleteAfter\' must at least 90 days later than corresponding \'moveToColdStorageAfter\'',
`received 'deleteAfter: ${copyAction.deleteAfter.toDays()}' and 'moveToColdStorageAfter: ${copyAction.moveToColdStorageAfter.toDays()}'`,
].join('\n'));
Expand Down
22 changes: 11 additions & 11 deletions packages/aws-cdk-lib/aws-backup/lib/vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CfnBackupVault } from './backup.generated';
import * as iam from '../../aws-iam';
import * as kms from '../../aws-kms';
import * as sns from '../../aws-sns';
import { ArnFormat, Duration, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token } from '../../core';
import { ArnFormat, Duration, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token, ValidationError } from '../../core';
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';

/**
Expand Down Expand Up @@ -209,7 +209,7 @@ abstract class BackupVaultBase extends Resource implements IBackupVault {
public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
for (const action of actions) {
if (action.indexOf('*') >= 0) {
throw new Error("AWS Backup access policies don't support a wildcard in the Action key.");
throw new ValidationError("AWS Backup access policies don't support a wildcard in the Action key.", this);
}
}

Expand Down Expand Up @@ -246,10 +246,10 @@ export class BackupVault extends BackupVaultBase {
const parsedArn = Stack.of(scope).splitArn(backupVaultArn, ArnFormat.COLON_RESOURCE_NAME);

if (parsedArn.arnFormat !== ArnFormat.COLON_RESOURCE_NAME) {
throw new Error(`Backup Vault Arn ${backupVaultArn} has the wrong format, expected ${ArnFormat.COLON_RESOURCE_NAME}.`);
throw new ValidationError(`Backup Vault Arn ${backupVaultArn} has the wrong format, expected ${ArnFormat.COLON_RESOURCE_NAME}.`, scope);
}
if (!parsedArn.resourceName) {
throw new Error(`Backup Vault Arn ${backupVaultArn} does not have a resource name.`);
throw new ValidationError(`Backup Vault Arn ${backupVaultArn} does not have a resource name.`, scope);
}

class Import extends BackupVaultBase {
Expand All @@ -274,7 +274,7 @@ export class BackupVault extends BackupVaultBase {
addConstructMetadata(this, props);

if (props.backupVaultName && !Token.isUnresolved(props.backupVaultName) && !/^[a-zA-Z0-9\-_]{2,50}$/.test(props.backupVaultName)) {
throw new Error('Expected vault name to match pattern `^[a-zA-Z0-9\-_]{2,50}$`');
throw new ValidationError('Expected vault name to match pattern `^[a-zA-Z0-9\-_]{2,50}$`', this);
}

let notifications: CfnBackupVault.NotificationObjectTypeProperty | undefined;
Expand All @@ -296,7 +296,7 @@ export class BackupVault extends BackupVaultBase {
accessPolicy: Lazy.any({ produce: () => this.accessPolicy.toJSON() }),
encryptionKeyArn: props.encryptionKey && props.encryptionKey.keyArn,
notifications,
lockConfiguration: renderLockConfiguration(props.lockConfiguration),
lockConfiguration: renderLockConfiguration(this, props.lockConfiguration),
});
vault.applyRemovalPolicy(props.removalPolicy);

Expand Down Expand Up @@ -336,26 +336,26 @@ export class BackupVault extends BackupVaultBase {
}
}

function renderLockConfiguration(config?: LockConfiguration): CfnBackupVault.LockConfigurationTypeProperty | undefined {
function renderLockConfiguration(scope: Construct, config?: LockConfiguration): CfnBackupVault.LockConfigurationTypeProperty | undefined {
if (!config) {
return undefined;
}

if (config.changeableFor && config.changeableFor.toHours() < 72) {
throw new Error(`AWS Backup enforces a 72-hour cooling-off period before Vault Lock takes effect and becomes immutable, got ${config.changeableFor.toHours()} hours`);
throw new ValidationError(`AWS Backup enforces a 72-hour cooling-off period before Vault Lock takes effect and becomes immutable, got ${config.changeableFor.toHours()} hours`, scope);
}

if (config.maxRetention) {
if (config.maxRetention.toDays() > 36500) {
throw new Error(`The longest maximum retention period you can specify is 36500 days, got ${config.maxRetention.toDays()} days`);
throw new ValidationError(`The longest maximum retention period you can specify is 36500 days, got ${config.maxRetention.toDays()} days`, scope);
}
if (config.maxRetention.toDays() <= config.minRetention.toDays()) {
throw new Error(`The maximum retention period (${config.maxRetention.toDays()} days) must be greater than the minimum retention period (${config.minRetention.toDays()} days)`);
throw new ValidationError(`The maximum retention period (${config.maxRetention.toDays()} days) must be greater than the minimum retention period (${config.minRetention.toDays()} days)`, scope);
}
}

if (config.minRetention.toHours() < 24) {
throw new Error(`The shortest minimum retention period you can specify is 1 day, got ${config.minRetention.toHours()} hours`);
throw new ValidationError(`The shortest minimum retention period you can specify is 1 day, got ${config.minRetention.toHours()} hours`, scope);
}

return {
Expand Down