Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* eslint-disable no-console */
/// <reference path="../../../../../../../node_modules/aws-cdk-lib/custom-resources/lib/provider-framework/types.d.ts" />
import { RDS } from '@aws-sdk/client-rds'; // eslint-disable-line import/no-extraneous-dependencies

export async function onEventHandler(event: AWSCDKAsyncCustomResource.OnEventRequest): Promise<AWSCDKAsyncCustomResource.OnEventResponse> {
console.log('Event: %j', event);

const rds = new RDS();

const physicalResourceId = `${event.ResourceProperties.DBInstanceIdentifier}-${event.ResourceProperties.DBInstanceIdentifier}`;

if (event.RequestType === 'Create' || event.RequestType === 'Update') {
const data = await rds.createDBSnapshot({
DBInstanceIdentifier: event.ResourceProperties.DBInstanceIdentifier,
DBSnapshotIdentifier: event.ResourceProperties.DBSnapshotIdentifier,
});
return {
PhysicalResourceId: physicalResourceId,
Data: {
DBSnapshotArn: data.DBSnapshot?.DBSnapshotArn,
},
};
}

if (event.RequestType === 'Delete') {
await rds.deleteDBSnapshot({
DBSnapshotIdentifier: event.ResourceProperties.DBSnapshotIdentifier,
});
}

return {
PhysicalResourceId: `${event.ResourceProperties.DBInstanceIdentifier}-${event.ResourceProperties.DBInstanceIdentifier}`,
};
}

export async function isCompleteHandler(event: AWSCDKAsyncCustomResource.IsCompleteRequest): Promise<AWSCDKAsyncCustomResource.IsCompleteResponse> {
console.log('Event: %j', event);

const snapshotStatus = await tryGetSnapshotStatus(event.ResourceProperties.DBSnapshotIdentifier);

switch (event.RequestType) {
case 'Create':
case 'Update':
return { IsComplete: snapshotStatus === 'available' };
case 'Delete':
return { IsComplete: snapshotStatus === undefined };
}
}

async function tryGetSnapshotStatus(identifier: string): Promise<string | undefined> {
try {
const rds = new RDS();
const data = await rds.describeDBSnapshots({
DBSnapshotIdentifier: identifier,
});
return data.DBSnapshots?.[0].Status;
} catch (err: any) {
if (err.name === 'DBSnapshotNotFoundFault') {
return undefined;
}
throw err;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@
"Statement": [
{
"Action": [
"rds:AddTagsToResource",
"rds:CreateDBClusterSnapshot",
"rds:DeleteDBClusterSnapshot"
],
Expand Down Expand Up @@ -625,7 +626,7 @@
"Arn"
]
},
"Runtime": "nodejs18.x"
"Runtime": "nodejs22.x"
},
"DependsOn": [
"SnapshoterOnEventHandlerServiceRoleDefaultPolicyAF0DFD57",
Expand Down Expand Up @@ -744,7 +745,7 @@
"Arn"
]
},
"Runtime": "nodejs18.x"
"Runtime": "nodejs22.x"
},
"DependsOn": [
"SnapshoterIsCompleteHandlerServiceRoleDefaultPolicyA43EB222",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import * as path from 'path';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { App, ArnFormat, CustomResource, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import * as cr from 'aws-cdk-lib/custom-resources';
import { App, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as rds from 'aws-cdk-lib/aws-rds';
import { ClusterInstance } from 'aws-cdk-lib/aws-rds';
import { IntegTest } from '@aws-cdk/integ-tests-alpha';
import { STANDARD_NODEJS_RUNTIME } from '../../config';
import { ClusterSnapshoter } from './snapshoter';

class TestStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
Expand All @@ -34,7 +30,7 @@ class TestStack extends Stack {
removalPolicy: RemovalPolicy.DESTROY,
});

const snapshoter = new Snapshoter(this, 'Snapshoter', {
const snapshoter = new ClusterSnapshoter(this, 'Snapshoter', {
cluster,
snapshotIdentifier: 'cdk-integ-cluster-snapshot',
});
Expand All @@ -58,69 +54,6 @@ class TestStack extends Stack {
}
}

interface SnapshoterProps {
readonly cluster: rds.IDatabaseCluster;
readonly snapshotIdentifier: string;
}

class Snapshoter extends Construct {
public readonly snapshotArn: string;

constructor(scope: Construct, id: string, props: SnapshoterProps) {
super(scope, id);

const clusterArn = Stack.of(this).formatArn({
service: 'rds',
resource: 'cluster',
resourceName: props.cluster.clusterIdentifier,
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
});

const snapshotArn = Stack.of(this).formatArn({
service: 'rds',
resource: 'cluster-snapshot',
resourceName: props.snapshotIdentifier,
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
});

const code = lambda.Code.fromAsset(path.join(__dirname, 'snapshot-handler'), { exclude: ['*.ts'] });
const onEventHandler = new lambda.Function(this, 'OnEventHandler', {
code,
runtime: STANDARD_NODEJS_RUNTIME,
handler: 'index.onEventHandler',
});
onEventHandler.addToRolePolicy(new iam.PolicyStatement({
actions: ['rds:CreateDBClusterSnapshot', 'rds:DeleteDBClusterSnapshot'],
resources: [clusterArn, snapshotArn],
}));

const isCompleteHandler = new lambda.Function(this, 'IsCompleteHandler', {
code,
runtime: STANDARD_NODEJS_RUNTIME,
handler: 'index.isCompleteHandler',
});
isCompleteHandler.addToRolePolicy(new iam.PolicyStatement({
actions: ['rds:DescribeDBClusterSnapshots'],
resources: [clusterArn, snapshotArn],
}));

const provider = new cr.Provider(this, 'SnapshotProvider', {
onEventHandler,
isCompleteHandler,
});

const customResource = new CustomResource(this, 'Snapshot', {
resourceType: 'Custom::Snapshoter',
serviceToken: provider.serviceToken,
properties: {
DBClusterIdentifier: props.cluster.clusterIdentifier,
DBClusterSnapshotIdentifier: props.snapshotIdentifier,
},
});
this.snapshotArn = customResource.getAttString('DBClusterSnapshotArn');
}
}

const app = new App({
postCliContext: {
'@aws-cdk/aws-lambda:useCdkManagedLogGroup': false,
Expand All @@ -133,4 +66,3 @@ new IntegTest(app, 'ClusterSnapshotInteg', {
testCases: [stack],
diffAssets: true,
});
app.synth();
Loading
Loading