Skip to content

Commit

Permalink
feat!: Add DevX Backups support to RDS instance construct (#2276)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

Users of the GuDatabaseInstance class now need to explicitly opt-in/out of
DevX Backups via the devXBackups prop.
  • Loading branch information
jacobwinch authored Apr 17, 2024
1 parent 5e59797 commit 7cc8591
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 33 deletions.
8 changes: 8 additions & 0 deletions .changeset/six-shrimps-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@guardian/cdk": major
---

BREAKING CHANGE:

Users of the GuDatabaseInstance class now need to explicitly opt-in/out of
DevX Backups via the devXBackups prop.
111 changes: 80 additions & 31 deletions src/constructs/rds/instance.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Stack } from "aws-cdk-lib";
import { Duration, Stack } from "aws-cdk-lib";
import { Match, Template } from "aws-cdk-lib/assertions";
import { Vpc } from "aws-cdk-lib/aws-ec2";
import { DatabaseInstanceEngine, ParameterGroup, PostgresEngineVersion } from "aws-cdk-lib/aws-rds";
import { DatabaseInstanceEngine, PostgresEngineVersion } from "aws-cdk-lib/aws-rds";
import { GuTemplate, simpleGuStackForTesting } from "../../utils/test";
import { GuDatabaseInstance } from "./instance";

Expand All @@ -26,86 +26,135 @@ describe("The GuDatabaseInstance class", () => {
version: PostgresEngineVersion.VER_11_8,
}),
app: "testing",
devXBackups: { enabled: true },
});

Template.fromStack(stack).hasResourceProperties("AWS::RDS::DBInstance", {
DBInstanceClass: "db.t3.small",
});
});

it("sets the parameter group if passed in", () => {
test("sets the deletion protection value to true by default", () => {
const stack = simpleGuStackForTesting();

const parameterGroup = new ParameterGroup(stack, "MyParameterGroup", {
parameters: { max_connections: "100" },
new GuDatabaseInstance(stack, "DatabaseInstance", {
vpc,
instanceType: "t3.small",
engine: DatabaseInstanceEngine.postgres({
version: PostgresEngineVersion.VER_11_8,
}),
app: "testing",
devXBackups: { enabled: true },
});

Template.fromStack(stack).hasResourceProperties("AWS::RDS::DBInstance", {
DeletionProtection: true,
});
});

test("sets DeleteAutomatedBackups to false by default", () => {
const stack = simpleGuStackForTesting();
new GuDatabaseInstance(stack, "DatabaseInstance", {
vpc,
instanceType: "t3.small",
engine: DatabaseInstanceEngine.postgres({
version: PostgresEngineVersion.VER_11_8,
version: PostgresEngineVersion.VER_16,
}),
parameterGroup,
app: "testing",
devXBackups: { enabled: true },
});

Template.fromStack(stack).hasResourceProperties("AWS::RDS::DBInstance", {
DBParameterGroupName: {
Ref: Match.stringLikeRegexp("MyParameterGroup[A-Z0-9]+"),
},
DeleteAutomatedBackups: false,
});
});

it("creates a new parameter group if parameters are passed in", () => {
test("adds the correct tag if the user opts-in to DevX Backups", () => {
const stack = simpleGuStackForTesting();
new GuDatabaseInstance(stack, "DatabaseInstance", {
vpc,
instanceType: "t3.small",
engine: DatabaseInstanceEngine.postgres({
version: PostgresEngineVersion.VER_11_8,
version: PostgresEngineVersion.VER_16,
}),
parameters: { max_connections: "100" },
app: "testing",
devXBackups: { enabled: true },
});

const template = Template.fromStack(stack);

template.hasResourceProperties("AWS::RDS::DBInstance", {
DBParameterGroupName: {
Ref: Match.stringLikeRegexp("DatabaseInstanceTestingParameterGroup[A-Z0-9]+"),
},
console.log(JSON.stringify(stack.tags.tagValues()));
GuTemplate.fromStack(stack).hasGuTaggedResource("AWS::RDS::DBInstance", {
appIdentity: { app: "testing" },
additionalTags: [
{
Key: "devx-backup-enabled",
Value: "true",
},
],
});
});

template.hasResourceProperties("AWS::RDS::DBParameterGroup", {
Description: "Parameter group for postgres11",
Family: "postgres11",
Parameters: {
max_connections: "100",
test("adds the correct tag if the user opts-out of DevX Backups", () => {
const stack = simpleGuStackForTesting();
new GuDatabaseInstance(stack, "DatabaseInstance", {
vpc,
instanceType: "t3.small",
engine: DatabaseInstanceEngine.postgres({
version: PostgresEngineVersion.VER_16,
}),
app: "testing",
devXBackups: {
enabled: false,
optOutReason: "This DB is never created in AWS, so it does not need backups.",
backupRetention: Duration.days(30),
preferredBackupWindow: "00:00-02:00",
},
});

GuTemplate.fromStack(stack).hasGuTaggedResource("AWS::RDS::DBParameterGroup", {
GuTemplate.fromStack(stack).hasGuTaggedResource("AWS::RDS::DBInstance", {
appIdentity: { app: "testing" },
additionalTags: [
{
Key: "devx-backup-enabled",
Value: "false",
},
],
});
});

test("sets the deletion protection value to true by default", () => {
test("omits native RDS backup properties if the user opts-in to DevX Backups", () => {
const stack = simpleGuStackForTesting();
new GuDatabaseInstance(stack, "DatabaseInstance", {
vpc,
instanceType: "t3.small",
engine: DatabaseInstanceEngine.postgres({
version: PostgresEngineVersion.VER_11_8,
version: PostgresEngineVersion.VER_16,
}),
app: "testing",
devXBackups: { enabled: true },
});
Template.fromStack(stack).hasResourceProperties("AWS::RDS::DBInstance", {
// DevX Backups (AWS Backup) manages these properties, so they should always be omitted from CFN to avoid conflicts
BackupRetentionPeriod: Match.absent(),
PreferredBackupWindow: Match.absent(),
});
});

test("correctly wires up native RDS backup properties if the user opts-out of DevX Backups", () => {
const stack = simpleGuStackForTesting();
new GuDatabaseInstance(stack, "DatabaseInstance", {
vpc,
instanceType: "t3.small",
engine: DatabaseInstanceEngine.postgres({
version: PostgresEngineVersion.VER_16,
}),
app: "testing",
devXBackups: {
enabled: false,
optOutReason: "This DB is never created in AWS, so it does not need backups.",
backupRetention: Duration.days(30),
preferredBackupWindow: "00:00-02:00",
},
});
Template.fromStack(stack).hasResourceProperties("AWS::RDS::DBInstance", {
DeletionProtection: true,
BackupRetentionPeriod: 30,
PreferredBackupWindow: "00:00-02:00",
});
});
});
30 changes: 28 additions & 2 deletions src/constructs/rds/instance.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
import { Fn } from "aws-cdk-lib";
import { Fn, Tags } from "aws-cdk-lib";
import { InstanceType } from "aws-cdk-lib/aws-ec2";
import { DatabaseInstance } from "aws-cdk-lib/aws-rds";
import type { DatabaseInstanceProps } from "aws-cdk-lib/aws-rds";
import { GuAppAwareConstruct } from "../../utils/mixin/app-aware-construct";
import type { AppIdentity, GuStack } from "../core";

export interface GuDatabaseInstanceProps extends Omit<DatabaseInstanceProps, "instanceType">, AppIdentity {
export interface OptIn {
enabled: true;
}

// Using Pick here means that we get the AWS documentation for these properties rather than needing to define it ourselves
export interface OptOut extends Pick<DatabaseInstanceProps, "backupRetention" | "preferredBackupWindow"> {
enabled: false;
/**
* We recommend using DevX Backups where possible. If it is not suitable for your use-case please document
* this here so that we can understand why this is switched off when performing security audits.
*/
optOutReason: string;
}

export interface GuDatabaseInstanceProps
extends Omit<DatabaseInstanceProps, "instanceType" | "backupRetention" | "preferredBackupWindow">,
AppIdentity {
instanceType: string;
/**
* We recommend using DevX Backups to protect your RDS instance's backups.
* For more details on this feature, see the
* [documentation](https://docs.google.com/document/d/1VDCSxYFlWs4R6g0Waa6OmmfytV60AROyHxfIGho7cLA/edit#heading=h.vwt7syo8ng40).
*/
devXBackups: OptIn | OptOut;
}

export class GuDatabaseInstance extends GuAppAwareConstruct(DatabaseInstance) {
Expand All @@ -18,8 +40,12 @@ export class GuDatabaseInstance extends GuAppAwareConstruct(DatabaseInstance) {

super(scope, id, {
deletionProtection: true,
deleteAutomatedBackups: false,
backupRetention: props.devXBackups.enabled ? undefined : props.devXBackups.backupRetention,
preferredBackupWindow: props.devXBackups.enabled ? undefined : props.devXBackups.preferredBackupWindow,
...props,
instanceType,
});
Tags.of(this).add("devx-backup-enabled", String(props.devXBackups.enabled));
}
}

0 comments on commit 7cc8591

Please sign in to comment.