Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dynamodb): replicas not created on table replacement #13300

Merged
merged 9 commits into from
Mar 5, 2021
43 changes: 25 additions & 18 deletions packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,34 @@ import { DynamoDB } from 'aws-sdk'; // eslint-disable-line import/no-extraneous-
export async function onEventHandler(event: OnEventRequest): Promise<OnEventResponse> {
console.log('Event: %j', event);

/**
* Process only Create and Delete requests. We shouldn't receive any
* update request and in case we do there is nothing to update.
*/
const dynamodb = new DynamoDB();

let updateTableAction: 'Create' | 'Update' | 'Delete';
if (event.RequestType === 'Create' || event.RequestType === 'Delete') {
const dynamodb = new DynamoDB();

const data = await dynamodb.updateTable({
TableName: event.ResourceProperties.TableName,
ReplicaUpdates: [
{
[event.RequestType]: {
RegionName: event.ResourceProperties.Region,
},
},
],
}).promise();
console.log('Update table: %j', data);
updateTableAction = event.RequestType;
} else { // Update
// This can only be a table replacement so we create a replica
// in the new table. The replica for the "old" table will be
// deleted when CF issues a Delete event on the old physical
// resource id.
updateTableAction = 'Create';
}

return { PhysicalResourceId: event.ResourceProperties.Region };
const data = await dynamodb.updateTable({
TableName: event.ResourceProperties.TableName,
ReplicaUpdates: [
{
[updateTableAction]: {
RegionName: event.ResourceProperties.Region,
},
},
],
}).promise();
console.log('Update table: %j', data);

return event.RequestType === 'Create' || event.RequestType === 'Update'
? { PhysicalResourceId: `${event.ResourceProperties.TableName}-${event.ResourceProperties.Region}` }
: {};
}

export async function isCompleteHandler(event: IsCompleteRequest): Promise<IsCompleteResponse> {
Expand Down
17 changes: 12 additions & 5 deletions packages/@aws-cdk/aws-dynamodb/lib/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1670,12 +1670,19 @@ interface ScalableAttributePair {
*/
class SourceTableAttachedPolicy extends CoreConstruct implements iam.IGrantable {
public readonly grantPrincipal: iam.IPrincipal;
public readonly policy: iam.IPolicy;
public readonly policy: iam.IManagedPolicy;

public constructor(sourceTable: Table, role: iam.IRole) {
super(sourceTable, `SourceTableAttachedPolicy-${Names.nodeUniqueId(role.node)}`);

const policy = new iam.Policy(this, 'Resource', { roles: [role] });
super(sourceTable, `SourceTableAttachedManagedPolicy-${Names.nodeUniqueId(role.node)}`);

const policy = new iam.ManagedPolicy(this, 'Resource', {
// A CF update of the description property of a managed policy requires
// a replacement. Use the table name in the description to force a managed
// policy replacement when the table name changes. This way we preserve permissions
// to delete old replicas in case of a table replacement.
description: `DynamoDB replication managed policy for table ${sourceTable.tableName}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an awesome trick! I'll have to remember this one to use in other Custom Resources.

I'm just a little worried about the delete order - since this depends on the Table, won't it be deleted first, before the replica?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine because it depends on the role used by onEventHandler and isCompleteHandler, see also #8224 for more context on this SourceTableAttachedPolicy

See also my test #13300 (comment).

roles: [role],
});
this.policy = policy;
this.grantPrincipal = new SourceTableAttachedPrincipal(role, policy);
}
Expand All @@ -1686,7 +1693,7 @@ class SourceTableAttachedPolicy extends CoreConstruct implements iam.IGrantable
* `SourceTableAttachedPolicy` class so it can act as an `IGrantable`.
*/
class SourceTableAttachedPrincipal extends iam.PrincipalBase {
public constructor(private readonly role: iam.IRole, private readonly policy: iam.Policy) {
public constructor(private readonly role: iam.IRole, private readonly policy: iam.ManagedPolicy) {
super();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B77945CD5DF": {
"Type": "AWS::IAM::Policy",
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B771F8F2CCB": {
"Type": "AWS::IAM::ManagedPolicy",
"Properties": {
"PolicyDocument": {
"Statement": [
Expand Down Expand Up @@ -93,7 +93,18 @@
],
"Version": "2012-10-17"
},
"PolicyName": "leAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B77945CD5DF",
"Description": {
"Fn::Join": [
"",
[
"DynamoDB replication managed policy for table ",
{
"Ref": "TableCD117FA1"
}
]
]
},
"Path": "/",
"Roles": [
{
"Fn::GetAtt": [
Expand All @@ -104,8 +115,8 @@
]
}
},
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1AE3D3CF6D": {
"Type": "AWS::IAM::Policy",
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1A5DC546D2": {
"Type": "AWS::IAM::ManagedPolicy",
"Properties": {
"PolicyDocument": {
"Statement": [
Expand All @@ -127,7 +138,18 @@
],
"Version": "2012-10-17"
},
"PolicyName": "ttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1AE3D3CF6D",
"Description": {
"Fn::Join": [
"",
[
"DynamoDB replication managed policy for table ",
{
"Ref": "TableCD117FA1"
}
]
]
},
"Path": "/",
"Roles": [
{
"Fn::GetAtt": [
Expand All @@ -153,8 +175,8 @@
"Region": "us-east-2"
},
"DependsOn": [
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1AE3D3CF6D",
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B77945CD5DF",
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1A5DC546D2",
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B771F8F2CCB",
"TableWriteScalingTargetE5669214",
"TableWriteScalingTargetTrackingD78DCCD8"
],
Expand All @@ -178,8 +200,8 @@
},
"DependsOn": [
"TableReplicauseast28A15C236",
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1AE3D3CF6D",
"TableSourceTableAttachedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B77945CD5DF",
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1A5DC546D2",
"TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B771F8F2CCB",
"TableWriteScalingTargetE5669214",
"TableWriteScalingTargetTrackingD78DCCD8"
],
Expand Down Expand Up @@ -256,7 +278,7 @@
},
"/",
{
"Ref": "AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3BucketEDAACFE7"
"Ref": "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3Bucket806FEB2C"
},
"/",
{
Expand All @@ -266,7 +288,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3VersionKey6FF3D50F"
"Ref": "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3VersionKey81C7BC5B"
}
]
}
Expand All @@ -279,7 +301,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3VersionKey6FF3D50F"
"Ref": "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3VersionKey81C7BC5B"
}
]
}
Expand All @@ -289,11 +311,11 @@
]
},
"Parameters": {
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3Bucket50997EC4Ref": {
"Ref": "AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3Bucket1C6779E0"
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3BucketD1258B42Ref": {
"Ref": "AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3BucketDEBF01E6"
},
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3VersionKey0F47C425Ref": {
"Ref": "AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3VersionKey5C1D9275"
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3VersionKey0F5C355ERef": {
"Ref": "AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3VersionKey42EBA2AE"
},
"referencetoawscdkdynamodbglobalreplicasprovisionedAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3Bucket6C51C355Ref": {
"Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1"
Expand Down Expand Up @@ -334,17 +356,17 @@
}
},
"Parameters": {
"AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3Bucket1C6779E0": {
"AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3BucketDEBF01E6": {
"Type": "String",
"Description": "S3 bucket for asset \"f13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714\""
"Description": "S3 bucket for asset \"dd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776\""
},
"AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714S3VersionKey5C1D9275": {
"AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776S3VersionKey42EBA2AE": {
"Type": "String",
"Description": "S3 key for asset version \"f13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714\""
"Description": "S3 key for asset version \"dd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776\""
},
"AssetParametersf13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714ArtifactHash477AAEA7": {
"AssetParametersdd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776ArtifactHash692B4CCE": {
"Type": "String",
"Description": "Artifact hash for asset \"f13d472270faaa08099009152a8848a0e7434b14773f3c3f94acca6f6c3ae714\""
"Description": "Artifact hash for asset \"dd0a4ac30ffa331e472caec08a7784ac440d122a6f924b1bea7d48dc85f8f776\""
},
"AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1": {
"Type": "String",
Expand All @@ -358,17 +380,17 @@
"Type": "String",
"Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\""
},
"AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3BucketEDAACFE7": {
"AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3Bucket806FEB2C": {
"Type": "String",
"Description": "S3 bucket for asset \"e31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fd\""
"Description": "S3 bucket for asset \"d56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aad\""
},
"AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdS3VersionKey6FF3D50F": {
"AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3VersionKey81C7BC5B": {
"Type": "String",
"Description": "S3 key for asset version \"e31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fd\""
"Description": "S3 key for asset version \"d56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aad\""
},
"AssetParameterse31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fdArtifactHash898696F1": {
"AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadArtifactHashD0230F6F": {
"Type": "String",
"Description": "Artifact hash for asset \"e31d108faccc52dcd9a9d86276a05e6ad861311925fe6931eadc31d0fe17e1fd\""
"Description": "Artifact hash for asset \"d56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aad\""
}
}
}
Loading