Skip to content

Commit 12f804c

Browse files
committed
fix(dynamodb): use concrete ARN account/region when resourceName is tokenized
The previous broad `Token.isUnresolved(tableArn)` guard in `validateMultiAccountReplica` skipped splitArn extraction whenever any part of the source table ARN was tokenized, even if just the resourceName was a token with concrete account and region components. This caused `sourceAccount` and `sourceRegion` to fall back to the stack that owns the imported resource (often the same as the replica stack), which falsely triggered the `MultiAccountReplicaMustBeDifferentAccount` and `MultiAccountReplicaMustBeDifferentRegion` errors for imported tables whose ARN had a tokenized table name. Now `splitArn` is always called and the extracted account/region are used whenever they are concrete, falling back to the stack's values only when the ARN's corresponding part is itself a token. Closes #38354 🤖🤖🤖 Generated with AI - this code was authored with the help of an AI agent
1 parent be92dbb commit 12f804c

2 files changed

Lines changed: 41 additions & 5 deletions

File tree

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,11 +1603,21 @@ export class TableV2MultiAccountReplica extends TableBaseV2 {
16031603
let sourceAccount = sourceStack.account;
16041604
let sourceRegion = sourceStack.region;
16051605

1606-
// For imported tables, extract account/region from ARN instead of stack
1607-
if (!Token.isUnresolved(props.replicaSourceTable!.tableArn)) {
1608-
const arnParts = this.stack.splitArn(props.replicaSourceTable!.tableArn, ArnFormat.SLASH_RESOURCE_NAME);
1609-
if (arnParts.account) sourceAccount = arnParts.account;
1610-
if (arnParts.region) sourceRegion = arnParts.region;
1606+
// For imported tables, extract account/region from ARN even when the ARN is
1607+
// only partially tokenized (e.g., the resourceName is a token but the
1608+
// account and region are concrete). `splitArn` returns concrete values for
1609+
// parts that are not tokens and undefined for parts that are tokenized, so
1610+
// the per-field `Token.isUnresolved` checks below handle the latter case.
1611+
// The previous broad `Token.isUnresolved(tableArn)` guard caused the
1612+
// validation to fall back to the stack that owns the imported resource,
1613+
// leading to false-positive "must be in a different account/region" errors
1614+
// when the source and replica stacks happened to share an environment.
1615+
const arnParts = this.stack.splitArn(props.replicaSourceTable!.tableArn, ArnFormat.SLASH_RESOURCE_NAME);
1616+
if (arnParts.account && !Token.isUnresolved(arnParts.account)) {
1617+
sourceAccount = arnParts.account;
1618+
}
1619+
if (arnParts.region && !Token.isUnresolved(arnParts.region)) {
1620+
sourceRegion = arnParts.region;
16111621
}
16121622

16131623
// Validate different account (skip if token)

packages/aws-cdk-lib/aws-dynamodb/test/table-v2.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4116,6 +4116,32 @@ test('TableV2MultiAccountReplica on imported table does not throw', () => {
41164116
expect(warnings[0].entry.data).toContain('manually configure multi-account replication permissions');
41174117
});
41184118

4119+
test('TableV2MultiAccountReplica does not throw with partially tokenized source ARN', () => {
4120+
const app = new App();
4121+
const replicaStack = new Stack(app, 'ReplicaStack', { env: { account: '222222222222', region: 'us-east-1' } });
4122+
4123+
// Source ARN has concrete account (`111111111111`) and region (`us-east-2`),
4124+
// but a tokenized table name. `Token.isUnresolved(tableArn)` returns true for
4125+
// the whole ARN (because the resourceName is a token), but `splitArn`
4126+
// correctly extracts the concrete account/region components. The
4127+
// validation must use those extracted components, not fall back to the
4128+
// replica stack's own account/region — that fallback would falsely trigger
4129+
// "must be in a different account" because the fallback equals
4130+
// `this.stack.account`.
4131+
const dynamicTableName = Lazy.string({ produce: () => 'dynamic-source-table' });
4132+
const importedTable = TableV2.fromTableArn(
4133+
replicaStack,
4134+
'ImportedTable',
4135+
`arn:aws:dynamodb:us-east-2:111111111111:table/${dynamicTableName}`,
4136+
);
4137+
4138+
expect(() => {
4139+
new TableV2MultiAccountReplica(replicaStack, 'ReplicaTable', {
4140+
replicaSourceTable: importedTable,
4141+
});
4142+
}).not.toThrow();
4143+
});
4144+
41194145
test('TableV2MultiAccountReplica works with fromTableArn without key schema', () => {
41204146
const app = new App();
41214147
const replicaStack = new Stack(app, 'ReplicaStack', { env: { account: '222222222222', region: 'us-east-1' } });

0 commit comments

Comments
 (0)