From 7b12a28a6c0fb8cf581d755d10e1592226300954 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 31 Aug 2023 12:51:32 +0000 Subject: [PATCH 1/3] feat(backup): schedule expression timezone --- packages/aws-cdk-lib/aws-backup/lib/plan.ts | 1 + packages/aws-cdk-lib/aws-backup/lib/rule.ts | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/aws-cdk-lib/aws-backup/lib/plan.ts b/packages/aws-cdk-lib/aws-backup/lib/plan.ts index a7ce167ac92ea..99b102d5e09e6 100644 --- a/packages/aws-cdk-lib/aws-backup/lib/plan.ts +++ b/packages/aws-cdk-lib/aws-backup/lib/plan.ts @@ -188,6 +188,7 @@ export class BackupPlan extends Resource implements IBackupPlan { }, ruleName: rule.props.ruleName ?? `${this.node.id}Rule${this.rules.length}`, scheduleExpression: rule.props.scheduleExpression?.expressionString, + scheduleExpressionTimezone: rule.props.scheduleExpressionTimezone, startWindowMinutes: rule.props.startWindow?.toMinutes(), enableContinuousBackup: rule.props.enableContinuousBackup, targetBackupVault: vault.backupVaultName, diff --git a/packages/aws-cdk-lib/aws-backup/lib/rule.ts b/packages/aws-cdk-lib/aws-backup/lib/rule.ts index fe546619bddb3..effbc8fdc12e8 100644 --- a/packages/aws-cdk-lib/aws-backup/lib/rule.ts +++ b/packages/aws-cdk-lib/aws-backup/lib/rule.ts @@ -44,6 +44,17 @@ export interface BackupPlanRuleProps { */ readonly scheduleExpression?: events.Schedule; + /** + * The name of the time zone for the schedule expression, following the IANA time zone standard. + * + * @example America/Los_Angeles + * + * @see https://www.iana.org/time-zones + * + * @default - UTC + */ + readonly scheduleExpressionTimezone?: string; + /** * The duration after a backup is scheduled before a job is canceled if it doesn't start successfully. * From cba14712ca7a6fa3b9b1a63f6673efae3f613ac7 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 5 Sep 2023 13:30:55 +0000 Subject: [PATCH 2/3] tests --- .../cdk-backup.assets.json | 6 +- .../cdk-backup.template.json | 11 ++ .../test/integ.backup.js.snapshot/cdk.out | 2 +- .../test/integ.backup.js.snapshot/integ.json | 2 +- .../integ.backup.js.snapshot/manifest.json | 4 +- .../test/integ.backup.js.snapshot/tree.json | 101 ++++++++++-------- .../test/aws-backup/test/integ.backup.ts | 10 ++ packages/aws-cdk-lib/aws-backup/lib/rule.ts | 4 + .../aws-cdk-lib/aws-backup/test/plan.test.ts | 35 ++++++ 9 files changed, 123 insertions(+), 52 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.assets.json index 19ddf51ac30c3..0e8b4357eaa36 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.assets.json @@ -1,7 +1,7 @@ { - "version": "32.0.0", + "version": "34.0.0", "files": { - "0c52c355c71ac95690274d7987110017ff9cd1a1bc79fa4206fda2f55d6b62d5": { + "92fad8a62da258c4bcae7ecf5ebdde433e9614aec519088b7a03a22067623d5e": { "source": { "path": "cdk-backup.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "0c52c355c71ac95690274d7987110017ff9cd1a1bc79fa4206fda2f55d6b62d5.json", + "objectKey": "92fad8a62da258c4bcae7ecf5ebdde433e9614aec519088b7a03a22067623d5e.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.template.json index 2722b98da789d..e283f77f3a8f8 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.template.json @@ -142,6 +142,17 @@ "BackupVaultName" ] } + }, + { + "RuleName": "PlanRule4", + "ScheduleExpression": "cron(0 4 15 * ? *)", + "ScheduleExpressionTimezone": "Europe/Brussels", + "TargetBackupVault": { + "Fn::GetAtt": [ + "Vault23237E5B", + "BackupVaultName" + ] + } } ] } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk.out index f0b901e7c06e5..2313ab5436501 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"32.0.0"} \ No newline at end of file +{"version":"34.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/integ.json index 266124ac58c12..b044eefcaffbb 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "33.0.0", + "version": "34.0.0", "testCases": { "integ.backup": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/manifest.json index aa4d73d1faac8..0187382f487d1 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "33.0.0", + "version": "34.0.0", "artifacts": { "cdk-backup.assets": { "type": "cdk:asset-manifest", @@ -17,7 +17,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/0c52c355c71ac95690274d7987110017ff9cd1a1bc79fa4206fda2f55d6b62d5.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/92fad8a62da258c4bcae7ecf5ebdde433e9614aec519088b7a03a22067623d5e.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/tree.json index 3728b496ca8cf..eef9deb001c03 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/tree.json @@ -37,22 +37,22 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_dynamodb.CfnTable", + "version": "0.0.0" } }, "ScalingRole": { "id": "ScalingRole", "path": "cdk-backup/Table/ScalingRole", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_dynamodb.Table", + "version": "0.0.0" } }, "FileSystem": { @@ -63,8 +63,8 @@ "aws:cdk:cloudformation:props": {} }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_efs.CfnFileSystem", + "version": "0.0.0" } }, "Vault": { @@ -84,14 +84,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.CfnBackupVault", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.BackupVault", + "version": "0.0.0" } }, "SecondaryVault": { @@ -111,22 +111,22 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.CfnBackupVault", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.BackupVault", + "version": "0.0.0" } }, "Env": { "id": "Env", "path": "cdk-backup/Env", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" } }, "ThirdVault": { @@ -156,14 +156,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.CfnBackupVault", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.BackupVault", + "version": "0.0.0" } }, "Plan": { @@ -244,14 +244,25 @@ "recoveryPointTags": { "stage": "prod" } + }, + { + "ruleName": "PlanRule4", + "scheduleExpression": "cron(0 4 15 * ? *)", + "scheduleExpressionTimezone": "Europe/Brussels", + "targetBackupVault": { + "Fn::GetAtt": [ + "Vault23237E5B", + "BackupVaultName" + ] + } } ] } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.CfnBackupPlan", + "version": "0.0.0" } }, "Selection": { @@ -266,8 +277,8 @@ "id": "ImportRole", "path": "cdk-backup/Plan/Selection/Role/ImportRole", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" } }, "Resource": { @@ -305,14 +316,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" } }, "Resource": { @@ -394,42 +405,42 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.CfnBackupSelection", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.BackupSelection", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.aws_backup.BackupPlan", + "version": "0.0.0" } }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "cdk-backup/BootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" } }, "CheckBootstrapVersion": { "id": "CheckBootstrapVersion", "path": "cdk-backup/CheckBootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" } }, "Tree": { @@ -437,13 +448,13 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.2.69" + "version": "10.2.70" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.2.69" + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.ts index d5138ced8bc41..62a3bca47e1f4 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.ts @@ -1,5 +1,6 @@ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as efs from 'aws-cdk-lib/aws-efs'; +import * as events from 'aws-cdk-lib/aws-events'; import { App, Duration, RemovalPolicy, Stack, StackProps, CfnParameter } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as backup from 'aws-cdk-lib/aws-backup'; @@ -61,6 +62,15 @@ class TestStack extends Stack { stage: 'prod', }, })); + + plan.addRule(new backup.BackupPlanRule({ + scheduleExpression: events.Schedule.cron({ + day: '15', + hour: '4', + minute: '0', + }), + scheduleExpressionTimezone: 'Europe/Brussels', + })); } } diff --git a/packages/aws-cdk-lib/aws-backup/lib/rule.ts b/packages/aws-cdk-lib/aws-backup/lib/rule.ts index effbc8fdc12e8..663f48af6d10e 100644 --- a/packages/aws-cdk-lib/aws-backup/lib/rule.ts +++ b/packages/aws-cdk-lib/aws-backup/lib/rule.ts @@ -224,6 +224,10 @@ export class BackupPlanRule { throw new Error('`scheduleExpression` must be of type `cron`'); } + if (props.scheduleExpressionTimezone && !props.scheduleExpression) { + throw new Error('Cannot specify `scheduleExpressionTimezone` for a rule without a `scheduleExpression`'); + } + const deleteAfter = (props.enableContinuousBackup && !props.deleteAfter) ? Duration.days(35) : props.deleteAfter; if (props.enableContinuousBackup && props.moveToColdStorageAfter) { diff --git a/packages/aws-cdk-lib/aws-backup/test/plan.test.ts b/packages/aws-cdk-lib/aws-backup/test/plan.test.ts index 4d9c23700d651..ddbe3b0e3b535 100644 --- a/packages/aws-cdk-lib/aws-backup/test/plan.test.ts +++ b/packages/aws-cdk-lib/aws-backup/test/plan.test.ts @@ -373,6 +373,41 @@ test('create a plan and add rule with recoveryPointTags', () => { }); }); +test('specify timezone for schedule expression', () => { + // WHEN + new BackupPlan(stack, 'Plan', { + backupPlanRules: [ + new BackupPlanRule({ + scheduleExpression: events.Schedule.cron({ + hour: '6', + minute: '0', + }), + scheduleExpressionTimezone: 'America/Los_Angeles', + }), + ], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Backup::BackupPlan', { + BackupPlan: { + BackupPlanName: 'Plan', + BackupPlanRule: [ + { + RuleName: 'PlanRule0', + TargetBackupVault: { + 'Fn::GetAtt': [ + 'PlanVault0284B0C2', + 'BackupVaultName', + ], + }, + ScheduleExpression: 'cron(0 6 * * ? *)', + ScheduleExpressionTimezone: 'America/Los_Angeles', + }, + ], + }, + }); +}); + test('throws when deleteAfter is not greater than moveToColdStorageAfter', () => { expect(() => new BackupPlanRule({ deleteAfter: Duration.days(5), From 03c186d59045a718097a2098f9855f6efcc11c1f Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 5 Sep 2023 13:35:09 +0000 Subject: [PATCH 3/3] README --- packages/aws-cdk-lib/aws-backup/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/aws-cdk-lib/aws-backup/README.md b/packages/aws-cdk-lib/aws-backup/README.md index 27b2fcc3667f1..e78fd24c7c6b5 100644 --- a/packages/aws-cdk-lib/aws-backup/README.md +++ b/packages/aws-cdk-lib/aws-backup/README.md @@ -71,6 +71,7 @@ plan.addRule(new backup.BackupPlanRule({ hour: '3', minute: '30', }), + scheduleExpressionTimezone: 'Europe/Brussels', // Optional, defaults to UTC moveToColdStorageAfter: Duration.days(30), })); ```