Skip to content

Commit 24c6ef5

Browse files
authored
feat(backup): support continuous backup and point-in-time restores (#17602)
Adds support for [continuous backup and point-in-time restores](https://docs.aws.amazon.com/aws-backup/latest/devguide/point-in-time-recovery.html). Implemented validations when continuous backup and point-in-time restores is enabled: - `deleteAfter` between 1 and 35 days. "The minimum retention period is 1 day. The maximum retention period is 35 days." (see [docs](https://docs.aws.amazon.com/aws-backup/latest/devguide/point-in-time-recovery.html#point-in-time-recovery-working-with)) - `deleteAfter` must be specified. Mandatory in AWS console. CloudFormation error if not specified: `Lifecycle must be specified for backup rule enabled continuous backup` - `moveToColdStorageAfter` is not supported. Field not available in AWS console. CloudFormation error if specified: `MoveToColdStorageAfterDays is unavailable` Closes #15922. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent ace37a2 commit 24c6ef5

File tree

4 files changed

+147
-2
lines changed

4 files changed

+147
-2
lines changed

packages/@aws-cdk/aws-backup/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ plan.addRule(new backup.BackupPlanRule({
6363
}));
6464
```
6565

66+
Continuous backup and point-in-time restores (PITR) can be configured.
67+
Property `deleteAfter` defines the retention period for the backup. It is mandatory if PITR is enabled.
68+
If no value is specified, the retention period is set to 35 days which is the maximum retention period supported by PITR.
69+
Property `moveToColdStorageAfter` must not be specified because PITR does not support this option.
70+
This example defines an AWS Backup rule with PITR and a retention period set to 14 days:
71+
72+
```ts fixture=with-plan
73+
plan.addRule(new backup.BackupPlanRule({
74+
enableContinuousBackup: true,
75+
deleteAfter: Duration.days(14),
76+
}));
77+
```
78+
6679
Ready-made rules are also available:
6780

6881
```ts fixture=with-plan

packages/@aws-cdk/aws-backup/lib/plan.ts

+1
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export class BackupPlan extends Resource implements IBackupPlan {
187187
ruleName: rule.props.ruleName ?? `${this.node.id}Rule${this.rules.length}`,
188188
scheduleExpression: rule.props.scheduleExpression?.expressionString,
189189
startWindowMinutes: rule.props.startWindow?.toMinutes(),
190+
enableContinuousBackup: rule.props.enableContinuousBackup,
190191
targetBackupVault: vault.backupVaultName,
191192
});
192193
}

packages/@aws-cdk/aws-backup/lib/rule.ts

+36-2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ export interface BackupPlanRuleProps {
5858
* common vault for the plan will be created
5959
*/
6060
readonly backupVault?: IBackupVault;
61+
62+
/**
63+
* Enables continuous backup and point-in-time restores (PITR).
64+
*
65+
* Property `deleteAfter` defines the retention period for the backup. It is mandatory if PITR is enabled.
66+
* If no value is specified, the retention period is set to 35 days which is the maximum retention period supported by PITR.
67+
*
68+
* Property `moveToColdStorageAfter` must not be specified because PITR does not support this option.
69+
*
70+
* @default false
71+
*/
72+
readonly enableContinuousBackup?: boolean;
6173
}
6274

6375
/**
@@ -146,15 +158,37 @@ export class BackupPlanRule {
146158
});
147159
}
148160

161+
/**
162+
* Properties of BackupPlanRule
163+
*/
164+
public readonly props: BackupPlanRuleProps
165+
149166
/** @param props Rule properties */
150-
constructor(public readonly props: BackupPlanRuleProps) {
167+
constructor(props: BackupPlanRuleProps) {
151168
if (props.deleteAfter && props.moveToColdStorageAfter &&
152-
props.deleteAfter.toSeconds() < props.moveToColdStorageAfter.toSeconds()) {
169+
props.deleteAfter.toDays() < props.moveToColdStorageAfter.toDays()) {
153170
throw new Error('`deleteAfter` must be greater than `moveToColdStorageAfter`');
154171
}
155172

156173
if (props.scheduleExpression && !/^cron/.test(props.scheduleExpression.expressionString)) {
157174
throw new Error('`scheduleExpression` must be of type `cron`');
158175
}
176+
177+
const deleteAfter = (props.enableContinuousBackup && !props.deleteAfter) ? Duration.days(35) : props.deleteAfter;
178+
179+
if (props.enableContinuousBackup && props.moveToColdStorageAfter) {
180+
throw new Error('`moveToColdStorageAfter` must not be specified if `enableContinuousBackup` is enabled');
181+
}
182+
183+
if (props.enableContinuousBackup && props.deleteAfter &&
184+
(props.deleteAfter?.toDays() < 1 || props.deleteAfter?.toDays() > 35)) {
185+
throw new Error(`'deleteAfter' must be between 1 and 35 days if 'enableContinuousBackup' is enabled, but got ${props.deleteAfter.toHumanString()}`);
186+
}
187+
188+
this.props = {
189+
...props,
190+
deleteAfter,
191+
};
192+
159193
}
160194
}

packages/@aws-cdk/aws-backup/test/plan.test.ts

+97
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,81 @@ test('create a plan and add rules', () => {
7070
});
7171
});
7272

73+
test('create a plan with continuous backup option', () => {
74+
// GIVEN
75+
const vault = new BackupVault(stack, 'Vault');
76+
77+
// WHEN
78+
new BackupPlan(stack, 'Plan', {
79+
backupVault: vault,
80+
backupPlanRules: [
81+
new BackupPlanRule({
82+
enableContinuousBackup: true,
83+
}),
84+
],
85+
});
86+
87+
// THEN
88+
Template.fromStack(stack).hasResourceProperties('AWS::Backup::BackupPlan', {
89+
BackupPlan: {
90+
BackupPlanName: 'Plan',
91+
BackupPlanRule: [
92+
{
93+
EnableContinuousBackup: true,
94+
Lifecycle: {
95+
DeleteAfterDays: 35,
96+
},
97+
RuleName: 'PlanRule0',
98+
TargetBackupVault: {
99+
'Fn::GetAtt': [
100+
'Vault23237E5B',
101+
'BackupVaultName',
102+
],
103+
},
104+
},
105+
],
106+
},
107+
});
108+
});
109+
110+
test('create a plan with continuous backup option and specify deleteAfter', () => {
111+
// GIVEN
112+
const vault = new BackupVault(stack, 'Vault');
113+
114+
// WHEN
115+
new BackupPlan(stack, 'Plan', {
116+
backupVault: vault,
117+
backupPlanRules: [
118+
new BackupPlanRule({
119+
enableContinuousBackup: true,
120+
deleteAfter: Duration.days(1),
121+
}),
122+
],
123+
});
124+
125+
// THEN
126+
Template.fromStack(stack).hasResourceProperties('AWS::Backup::BackupPlan', {
127+
BackupPlan: {
128+
BackupPlanName: 'Plan',
129+
BackupPlanRule: [
130+
{
131+
EnableContinuousBackup: true,
132+
Lifecycle: {
133+
DeleteAfterDays: 1,
134+
},
135+
RuleName: 'PlanRule0',
136+
TargetBackupVault: {
137+
'Fn::GetAtt': [
138+
'Vault23237E5B',
139+
'BackupVaultName',
140+
],
141+
},
142+
},
143+
],
144+
},
145+
});
146+
});
147+
73148
test('create a plan and add rules - add BackupPlan.AdvancedBackupSettings.BackupOptions', () => {
74149
const vault = new BackupVault(stack, 'Vault');
75150
const otherVault = new BackupVault(stack, 'OtherVault');
@@ -234,3 +309,25 @@ test('synth fails when plan has no rules', () => {
234309

235310
expect(() => app.synth()).toThrow(/A backup plan must have at least 1 rule/);
236311
});
312+
313+
test('throws when moveToColdStorageAfter is used with enableContinuousBackup', () => {
314+
expect(() => new BackupPlanRule({
315+
enableContinuousBackup: true,
316+
deleteAfter: Duration.days(30),
317+
moveToColdStorageAfter: Duration.days(10),
318+
})).toThrow(/`moveToColdStorageAfter` must not be specified if `enableContinuousBackup` is enabled/);
319+
});
320+
321+
test('throws when deleteAfter is less than 1 in combination with enableContinuousBackup', () => {
322+
expect(() => new BackupPlanRule({
323+
enableContinuousBackup: true,
324+
deleteAfter: Duration.days(0),
325+
})).toThrow(/'deleteAfter' must be between 1 and 35 days if 'enableContinuousBackup' is enabled, but got 0 days/);
326+
});
327+
328+
test('throws when deleteAfter is greater than 35 in combination with enableContinuousBackup', () => {
329+
expect(() => new BackupPlanRule({
330+
enableContinuousBackup: true,
331+
deleteAfter: Duration.days(36),
332+
})).toThrow(/'deleteAfter' must be between 1 and 35 days if 'enableContinuousBackup' is enabled, but got 36 days/);
333+
});

0 commit comments

Comments
 (0)