@@ -13,6 +13,7 @@ import {
13
13
} from 'aws-cdk-lib' ;
14
14
import { Construct } from 'constructs' ;
15
15
import { DeleteOldFunction } from './delete-old-function' ;
16
+ import { FindSnapshotFunction } from './find-snapshot-function' ;
16
17
import { ParametersFunction } from './parameters-function' ;
17
18
import { WaitFunction } from './wait-function' ;
18
19
@@ -88,6 +89,15 @@ export interface IRdsSanitizedSnapshotter {
88
89
*/
89
90
readonly snapshotPrefix ?: string ;
90
91
92
+ /**
93
+ * Use the latest available snapshot instead of taking a new one. This can be used to shorten the process at the cost of using a possibly older snapshot.
94
+ *
95
+ * This will use the latest snapshot whether it's an automatic system snapshot or a manual snapshot.
96
+ *
97
+ * @default false
98
+ */
99
+ readonly useExistingSnapshot ?: boolean ;
100
+
91
101
/**
92
102
* Prefix for all temporary snapshots and databases. The step function execution id will be added to it.
93
103
*
@@ -130,6 +140,7 @@ export class RdsSanitizedSnapshotter extends Construct {
130
140
private readonly fargateCluster : ecs . ICluster ;
131
141
private readonly sqlScript : string ;
132
142
private readonly reencrypt : boolean ;
143
+ private readonly useExistingSnapshot : boolean ;
133
144
134
145
private readonly generalTags : { Key : string ; Value : string } [ ] ;
135
146
private readonly finalSnapshotTags : { Key : string ; Value : string } [ ] ;
@@ -188,6 +199,7 @@ export class RdsSanitizedSnapshotter extends Construct {
188
199
this . snapshotPrefix = props . snapshotPrefix ?? this . databaseIdentifier ;
189
200
190
201
this . reencrypt = props . snapshotKey !== undefined ;
202
+ this . useExistingSnapshot = props . useExistingSnapshot ?? false ;
191
203
192
204
this . dbClusterArn = cdk . Stack . of ( this ) . formatArn ( {
193
205
service : 'rds' ,
@@ -204,7 +216,7 @@ export class RdsSanitizedSnapshotter extends Construct {
204
216
this . tempSnapshotArn = cdk . Stack . of ( this ) . formatArn ( {
205
217
service : 'rds' ,
206
218
resource : this . isCluster ? 'cluster-snapshot' : 'snapshot' ,
207
- resourceName : `${ this . tempPrefix } -*` ,
219
+ resourceName : this . useExistingSnapshot ? '*' : `${ this . tempPrefix } -*` ,
208
220
arnFormat : cdk . ArnFormat . COLON_RESOURCE_NAME ,
209
221
} ) ;
210
222
this . tempDbClusterArn = cdk . Stack . of ( this ) . formatArn ( {
@@ -242,14 +254,20 @@ export class RdsSanitizedSnapshotter extends Construct {
242
254
243
255
let c : stepfunctions . IChainable ;
244
256
let s : stepfunctions . INextable ;
245
- s = c = this . createSnapshot ( 'Create Temporary Snapshot' , '$.databaseIdentifier' , '$.tempSnapshotId' , this . generalTags ) ;
246
- s = s . next ( this . waitForOperation ( 'Wait for Snapshot' , 'snapshot' , '$.databaseIdentifier' , '$.tempSnapshotId' ) ) ;
257
+ let originalSnapshotLocation = '$.tempSnapshotId' ;
258
+ if ( this . useExistingSnapshot ) {
259
+ originalSnapshotLocation = '$.latestSnapshot.id' ;
260
+ s = c = this . findLatestSnapshot ( 'Find Latest Snapshot' , '$.databaseIdentifier' ) ;
261
+ } else {
262
+ s = c = this . createSnapshot ( 'Create Temporary Snapshot' , '$.databaseIdentifier' , originalSnapshotLocation , this . generalTags ) ;
263
+ s = s . next ( this . waitForOperation ( 'Wait for Snapshot' , 'snapshot' , '$.databaseIdentifier' , originalSnapshotLocation ) ) ;
264
+ }
247
265
if ( props . snapshotKey ) {
248
- s = s . next ( this . reencryptSnapshot ( props . snapshotKey ) ) ;
266
+ s = s . next ( this . reencryptSnapshot ( originalSnapshotLocation , props . snapshotKey ) ) ;
249
267
s = s . next ( this . waitForOperation ( 'Wait for Re-encrypt' , 'snapshot' , '$.databaseIdentifier' , '$.tempEncSnapshotId' ) ) ;
250
268
s = s . next ( this . createTemporaryDatabase ( '$.tempEncSnapshotId' ) ) ;
251
269
} else {
252
- s = s . next ( this . createTemporaryDatabase ( '$.tempSnapshotId' ) ) ;
270
+ s = s . next ( this . createTemporaryDatabase ( originalSnapshotLocation ) ) ;
253
271
}
254
272
s = s . next ( this . waitForOperation ( 'Wait for Temporary Database' , this . isCluster ? 'cluster' : 'instance' , '$.tempDbId' ) ) ;
255
273
s = s . next ( this . setPassword ( ) ) ;
@@ -342,6 +360,30 @@ export class RdsSanitizedSnapshotter extends Construct {
342
360
return parametersState ;
343
361
}
344
362
363
+ private findLatestSnapshot ( id : string , databaseId : string ) {
364
+ const findFn = new FindSnapshotFunction ( this , 'find-snapshot' , {
365
+ logRetention : logs . RetentionDays . ONE_MONTH ,
366
+ initialPolicy : [
367
+ new iam . PolicyStatement ( {
368
+ actions : [ 'rds:DescribeDBClusterSnapshots' , 'rds:DescribeDBSnapshots' ] ,
369
+ resources : [ this . dbClusterArn , this . dbInstanceArn ] ,
370
+ } ) ,
371
+ ] ,
372
+ } ) ;
373
+
374
+ let payload = {
375
+ databaseIdentifier : stepfunctions . JsonPath . stringAt ( databaseId ) ,
376
+ isCluster : this . isCluster ,
377
+ } ;
378
+
379
+ return new stepfunctions_tasks . LambdaInvoke ( this , id , {
380
+ lambdaFunction : findFn ,
381
+ payloadResponseOnly : true ,
382
+ payload : stepfunctions . TaskInput . fromObject ( payload ) ,
383
+ resultPath : stepfunctions . JsonPath . stringAt ( '$.latestSnapshot' ) ,
384
+ } ) ;
385
+ }
386
+
345
387
private createSnapshot ( id : string , databaseId : string , snapshotId : string , tags : { Key : string ; Value : string } [ ] ) {
346
388
return new stepfunctions_tasks . CallAwsService ( this , id , {
347
389
service : 'rds' ,
@@ -393,14 +435,14 @@ export class RdsSanitizedSnapshotter extends Construct {
393
435
} ) ;
394
436
}
395
437
396
- private reencryptSnapshot ( key : kms . IKey ) {
438
+ private reencryptSnapshot ( snapshot : string , key : kms . IKey ) {
397
439
return new stepfunctions_tasks . CallAwsService ( this , 'Re-encrypt Snapshot' , {
398
440
service : 'rds' ,
399
441
action : this . isCluster ? 'copyDBClusterSnapshot' : 'copyDBSnapshot' ,
400
442
parameters : {
401
- SourceDBClusterSnapshotIdentifier : this . isCluster ? stepfunctions . JsonPath . stringAt ( '$.tempSnapshotId' ) : undefined ,
443
+ SourceDBClusterSnapshotIdentifier : this . isCluster ? stepfunctions . JsonPath . stringAt ( snapshot ) : undefined ,
402
444
TargetDBClusterSnapshotIdentifier : this . isCluster ? stepfunctions . JsonPath . stringAt ( '$.tempEncSnapshotId' ) : undefined ,
403
- SourceDBSnapshotIdentifier : this . isCluster ? undefined : stepfunctions . JsonPath . stringAt ( '$.tempSnapshotId' ) ,
445
+ SourceDBSnapshotIdentifier : this . isCluster ? undefined : stepfunctions . JsonPath . stringAt ( snapshot ) ,
404
446
TargetDBSnapshotIdentifier : this . isCluster ? undefined : stepfunctions . JsonPath . stringAt ( '$.tempEncSnapshotId' ) ,
405
447
KmsKeyId : key . keyId ,
406
448
CopyTags : false ,
@@ -711,18 +753,23 @@ export class RdsSanitizedSnapshotter extends Construct {
711
753
// We retry everything because when any branch fails, all other branches are cancelled.
712
754
// Retrying gives other branches an opportunity to start and hopefully at least run.
713
755
const p = new stepfunctions . Parallel ( this , 'Cleanup' , { resultPath : stepfunctions . JsonPath . DISCARD } ) ;
714
- p . branch (
715
- new stepfunctions_tasks . CallAwsService ( this , 'Temporary Snapshot' , {
716
- service : 'rds' ,
717
- action : this . isCluster ? 'deleteDBClusterSnapshot' : 'deleteDBSnapshot' ,
718
- parameters : {
719
- DbClusterSnapshotIdentifier : this . isCluster ? stepfunctions . JsonPath . stringAt ( '$.tempSnapshotId' ) : undefined ,
720
- DbSnapshotIdentifier : this . isCluster ? undefined : stepfunctions . JsonPath . stringAt ( '$.tempSnapshotId' ) ,
721
- } ,
722
- iamResources : [ this . tempSnapshotArn ] ,
723
- resultPath : stepfunctions . JsonPath . DISCARD ,
724
- } ) . addRetry ( { maxAttempts : 5 , interval : cdk . Duration . seconds ( 10 ) } ) ,
725
- ) ;
756
+ if ( ! this . useExistingSnapshot ) {
757
+ p . branch (
758
+ new stepfunctions_tasks . CallAwsService ( this , 'Temporary Snapshot' , {
759
+ service : 'rds' ,
760
+ action : this . isCluster ? 'deleteDBClusterSnapshot' : 'deleteDBSnapshot' ,
761
+ parameters : {
762
+ DbClusterSnapshotIdentifier : this . isCluster ? stepfunctions . JsonPath . stringAt ( '$.tempSnapshotId' ) : undefined ,
763
+ DbSnapshotIdentifier : this . isCluster ? undefined : stepfunctions . JsonPath . stringAt ( '$.tempSnapshotId' ) ,
764
+ } ,
765
+ iamResources : [ this . tempSnapshotArn ] ,
766
+ resultPath : stepfunctions . JsonPath . DISCARD ,
767
+ } ) . addRetry ( {
768
+ maxAttempts : 5 ,
769
+ interval : cdk . Duration . seconds ( 10 ) ,
770
+ } ) ,
771
+ ) ;
772
+ }
726
773
if ( this . reencrypt ) {
727
774
p . branch (
728
775
new stepfunctions_tasks . CallAwsService ( this , 'Re-encrypted Snapshot' , {
0 commit comments