Skip to content

Commit c7c424f

Browse files
authored
fix(dynamodb): replicas not created on table replacement (#13300)
Process `Update` events resulting from table replacements. Include the table name in the physical resource id to receive a `Delete` event when the table is replaced. This allows to clean "old" replicas. Use a managed policy instead of an inline policy for the custom resource. An update of the description property of a managed policy requires a replacement. If we use the table name in the description it forces a managed policy replacement when the table name changes. This way we preserve permissions to delete old replicas in case of a table replacement: a new managed policy with permissions for the new table is created during the update phase and the old managed policy with permissions for the old table is removed only during the update clean up phase. The logical ID of the `SourceTableAttachedPolicy` needs to be updated because CF doesn't allow to change a resource type. Closes #12332 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 278fba5 commit c7c424f

File tree

5 files changed

+180
-85
lines changed

5 files changed

+180
-85
lines changed

packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts

+25-18
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,34 @@ import { DynamoDB } from 'aws-sdk'; // eslint-disable-line import/no-extraneous-
55
export async function onEventHandler(event: OnEventRequest): Promise<OnEventResponse> {
66
console.log('Event: %j', event);
77

8-
/**
9-
* Process only Create and Delete requests. We shouldn't receive any
10-
* update request and in case we do there is nothing to update.
11-
*/
8+
const dynamodb = new DynamoDB();
9+
10+
let updateTableAction: 'Create' | 'Update' | 'Delete';
1211
if (event.RequestType === 'Create' || event.RequestType === 'Delete') {
13-
const dynamodb = new DynamoDB();
14-
15-
const data = await dynamodb.updateTable({
16-
TableName: event.ResourceProperties.TableName,
17-
ReplicaUpdates: [
18-
{
19-
[event.RequestType]: {
20-
RegionName: event.ResourceProperties.Region,
21-
},
22-
},
23-
],
24-
}).promise();
25-
console.log('Update table: %j', data);
12+
updateTableAction = event.RequestType;
13+
} else { // Update
14+
// This can only be a table replacement so we create a replica
15+
// in the new table. The replica for the "old" table will be
16+
// deleted when CF issues a Delete event on the old physical
17+
// resource id.
18+
updateTableAction = 'Create';
2619
}
2720

28-
return { PhysicalResourceId: event.ResourceProperties.Region };
21+
const data = await dynamodb.updateTable({
22+
TableName: event.ResourceProperties.TableName,
23+
ReplicaUpdates: [
24+
{
25+
[updateTableAction]: {
26+
RegionName: event.ResourceProperties.Region,
27+
},
28+
},
29+
],
30+
}).promise();
31+
console.log('Update table: %j', data);
32+
33+
return event.RequestType === 'Create' || event.RequestType === 'Update'
34+
? { PhysicalResourceId: `${event.ResourceProperties.TableName}-${event.ResourceProperties.Region}` }
35+
: {};
2936
}
3037

3138
export async function isCompleteHandler(event: IsCompleteRequest): Promise<IsCompleteResponse> {

packages/@aws-cdk/aws-dynamodb/lib/table.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -1670,12 +1670,19 @@ interface ScalableAttributePair {
16701670
*/
16711671
class SourceTableAttachedPolicy extends CoreConstruct implements iam.IGrantable {
16721672
public readonly grantPrincipal: iam.IPrincipal;
1673-
public readonly policy: iam.IPolicy;
1673+
public readonly policy: iam.IManagedPolicy;
16741674

16751675
public constructor(sourceTable: Table, role: iam.IRole) {
1676-
super(sourceTable, `SourceTableAttachedPolicy-${Names.nodeUniqueId(role.node)}`);
1677-
1678-
const policy = new iam.Policy(this, 'Resource', { roles: [role] });
1676+
super(sourceTable, `SourceTableAttachedManagedPolicy-${Names.nodeUniqueId(role.node)}`);
1677+
1678+
const policy = new iam.ManagedPolicy(this, 'Resource', {
1679+
// A CF update of the description property of a managed policy requires
1680+
// a replacement. Use the table name in the description to force a managed
1681+
// policy replacement when the table name changes. This way we preserve permissions
1682+
// to delete old replicas in case of a table replacement.
1683+
description: `DynamoDB replication managed policy for table ${sourceTable.tableName}`,
1684+
roles: [role],
1685+
});
16791686
this.policy = policy;
16801687
this.grantPrincipal = new SourceTableAttachedPrincipal(role, policy);
16811688
}
@@ -1686,7 +1693,7 @@ class SourceTableAttachedPolicy extends CoreConstruct implements iam.IGrantable
16861693
* `SourceTableAttachedPolicy` class so it can act as an `IGrantable`.
16871694
*/
16881695
class SourceTableAttachedPrincipal extends iam.PrincipalBase {
1689-
public constructor(private readonly role: iam.IRole, private readonly policy: iam.Policy) {
1696+
public constructor(private readonly role: iam.IRole, private readonly policy: iam.ManagedPolicy) {
16901697
super();
16911698
}
16921699

packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.expected.json

+51-29
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
"UpdateReplacePolicy": "Delete",
2727
"DeletionPolicy": "Delete"
2828
},
29-
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B77945CD5DF": {
30-
"Type": "AWS::IAM::Policy",
29+
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B771F8F2CCB": {
30+
"Type": "AWS::IAM::ManagedPolicy",
3131
"Properties": {
3232
"PolicyDocument": {
3333
"Statement": [
@@ -93,7 +93,18 @@
9393
],
9494
"Version": "2012-10-17"
9595
},
96-
"PolicyName": "leAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B77945CD5DF",
96+
"Description": {
97+
"Fn::Join": [
98+
"",
99+
[
100+
"DynamoDB replication managed policy for table ",
101+
{
102+
"Ref": "TableCD117FA1"
103+
}
104+
]
105+
]
106+
},
107+
"Path": "/",
97108
"Roles": [
98109
{
99110
"Fn::GetAtt": [
@@ -104,8 +115,8 @@
104115
]
105116
}
106117
},
107-
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1AE3D3CF6D": {
108-
"Type": "AWS::IAM::Policy",
118+
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1A5DC546D2": {
119+
"Type": "AWS::IAM::ManagedPolicy",
109120
"Properties": {
110121
"PolicyDocument": {
111122
"Statement": [
@@ -127,7 +138,18 @@
127138
],
128139
"Version": "2012-10-17"
129140
},
130-
"PolicyName": "ttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1AE3D3CF6D",
141+
"Description": {
142+
"Fn::Join": [
143+
"",
144+
[
145+
"DynamoDB replication managed policy for table ",
146+
{
147+
"Ref": "TableCD117FA1"
148+
}
149+
]
150+
]
151+
},
152+
"Path": "/",
131153
"Roles": [
132154
{
133155
"Fn::GetAtt": [
@@ -153,8 +175,8 @@
153175
"Region": "us-east-2"
154176
},
155177
"DependsOn": [
156-
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1AE3D3CF6D",
157-
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B77945CD5DF",
178+
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1A5DC546D2",
179+
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B771F8F2CCB",
158180
"TableWriteScalingTargetE5669214",
159181
"TableWriteScalingTargetTrackingD78DCCD8"
160182
],
@@ -178,8 +200,8 @@
178200
},
179201
"DependsOn": [
180202
"TableReplicauseast28A15C236",
181-
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1AE3D3CF6D",
182-
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B77945CD5DF",
203+
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1A5DC546D2",
204+
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B771F8F2CCB",
183205
"TableWriteScalingTargetE5669214",
184206
"TableWriteScalingTargetTrackingD78DCCD8"
185207
],
@@ -256,7 +278,7 @@
256278
},
257279
"/",
258280
{
259-
"Ref": "AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3BucketEDAACFE7"
281+
"Ref": "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3Bucket806FEB2C"
260282
},
261283
"/",
262284
{
@@ -266,7 +288,7 @@
266288
"Fn::Split": [
267289
"||",
268290
{
269-
"Ref": "AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3VersionKey6FF3D50F"
291+
"Ref": "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3VersionKey81C7BC5B"
270292
}
271293
]
272294
}
@@ -279,7 +301,7 @@
279301
"Fn::Split": [
280302
"||",
281303
{
282-
"Ref": "AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3VersionKey6FF3D50F"
304+
"Ref": "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3VersionKey81C7BC5B"
283305
}
284306
]
285307
}
@@ -289,11 +311,11 @@
289311
]
290312
},
291313
"Parameters": {
292-
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3Bucket50997EC4Ref": {
293-
"Ref": "AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3Bucket1C6779E0"
314+
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3BucketD1258B42Ref": {
315+
"Ref": "AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3BucketDEBF01E6"
294316
},
295-
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3VersionKey0F47C425Ref": {
296-
"Ref": "AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3VersionKey5C1D9275"
317+
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3VersionKey0F5C355ERef": {
318+
"Ref": "AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3VersionKey42EBA2AE"
297319
},
298320
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3Bucket6C51C355Ref": {
299321
"Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1"
@@ -334,17 +356,17 @@
334356
}
335357
},
336358
"Parameters": {
337-
"AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3Bucket1C6779E0": {
359+
"AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3BucketDEBF01E6": {
338360
"Type": "String",
339-
"Description": "S3 bucket for asset \"f13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714\""
361+
"Description": "S3 bucket for asset \"dd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776\""
340362
},
341-
"AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3VersionKey5C1D9275": {
363+
"AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3VersionKey42EBA2AE": {
342364
"Type": "String",
343-
"Description": "S3 key for asset version \"f13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714\""
365+
"Description": "S3 key for asset version \"dd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776\""
344366
},
345-
"AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714ArtifactHash477AAEA7": {
367+
"AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776ArtifactHash692B4CCE": {
346368
"Type": "String",
347-
"Description": "Artifact hash for asset \"f13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714\""
369+
"Description": "Artifact hash for asset \"dd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776\""
348370
},
349371
"AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1": {
350372
"Type": "String",
@@ -358,17 +380,17 @@
358380
"Type": "String",
359381
"Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\""
360382
},
361-
"AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3BucketEDAACFE7": {
383+
"AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3Bucket806FEB2C": {
362384
"Type": "String",
363-
"Description": "S3 bucket for asset \"e31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fd\""
385+
"Description": "S3 bucket for asset \"d56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aad\""
364386
},
365-
"AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3VersionKey6FF3D50F": {
387+
"AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3VersionKey81C7BC5B": {
366388
"Type": "String",
367-
"Description": "S3 key for asset version \"e31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fd\""
389+
"Description": "S3 key for asset version \"d56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aad\""
368390
},
369-
"AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdArtifactHash898696F1": {
391+
"AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadArtifactHashD0230F6F": {
370392
"Type": "String",
371-
"Description": "Artifact hash for asset \"e31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fd\""
393+
"Description": "Artifact hash for asset \"d56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aad\""
372394
}
373395
}
374396
}

0 commit comments

Comments
 (0)