diff --git a/packages/aws-cdk-lib/aws-efs/lib/efs-file-system.ts b/packages/aws-cdk-lib/aws-efs/lib/efs-file-system.ts index 95bd3db05dda6..c5b30f17881e9 100644 --- a/packages/aws-cdk-lib/aws-efs/lib/efs-file-system.ts +++ b/packages/aws-cdk-lib/aws-efs/lib/efs-file-system.ts @@ -330,7 +330,7 @@ export interface FileSystemProps { * * @default - no replication */ - readonly replicationConfiguration?: ReplicationConfiguration; + readonly replicationConfiguration?: ReplicationConfiguration[]; } /** @@ -599,23 +599,27 @@ export class FileSystem extends FileSystemBase { throw new Error('ThroughputMode ELASTIC is not supported for file systems with performanceMode MAX_IO'); } - const { destinationFileSystem, region, availabilityZone, kmsKey } = props.replicationConfiguration ?? {}; if (props.replicationConfiguration) { if (props.replicationOverwriteProtection === ReplicationOverwriteProtection.DISABLED) { throw new Error('Cannot configure `replicationConfiguration` when `replicationOverwriteProtection` is set to `DISABLED`'); } - - if (destinationFileSystem && (region || availabilityZone || kmsKey)) { - throw new Error('Cannot configure `replicationConfiguration.region`, `replicationConfiguration.az` or `replicationConfiguration.kmsKey` when `replicationConfiguration.destinationFileSystem` is set'); - } - - if (region && !Token.isUnresolved(region) && !/^[a-z]{2}-((iso[a-z]{0,1}-)|(gov-)){0,1}[a-z]+-{0,1}[0-9]{0,1}$/.test(region)) { - throw new Error('`replicationConfiguration.region` is invalid.'); + if (props.replicationConfiguration.length !== 1) { + throw new Error('`replicationConfiguration` must contain exactly one destination'); } - if (availabilityZone && !Token.isUnresolved(availabilityZone) && !region) { - throw new Error('`replicationConfiguration.availabilityZone` cannot be specified without `replicationConfiguration.region`'); - } + props.replicationConfiguration.forEach((config) => { + const { destinationFileSystem, region, availabilityZone, kmsKey } = config; + + if (destinationFileSystem && (region || availabilityZone || kmsKey)) { + throw new Error('Cannot configure `replicationConfiguration.region`, `replicationConfiguration.az` or `replicationConfiguration.kmsKey` when `replicationConfiguration.destinationFileSystem` is set'); + } + if (region && !Token.isUnresolved(region) && !/^[a-z]{2}-((iso[a-z]{0,1}-)|(gov-)){0,1}[a-z]+-{0,1}[0-9]{0,1}$/.test(region)) { + throw new Error('`replicationConfiguration.region` is invalid.'); + } + if (availabilityZone && !Token.isUnresolved(availabilityZone) && !region) { + throw new Error('`replicationConfiguration.availabilityZone` cannot be specified without `replicationConfiguration.region`'); + } + }); } // we explictly use 'undefined' to represent 'false' to maintain backwards compatibility since @@ -645,14 +649,13 @@ export class FileSystem extends FileSystemBase { } : undefined; const replicationConfiguration = props.replicationConfiguration ? { - destinations: [ - { - fileSystemId: destinationFileSystem?.fileSystemId, - kmsKeyId: kmsKey?.keyArn, - region: destinationFileSystem ? destinationFileSystem.env.region : (region ?? Stack.of(this).region), - availabilityZoneName: availabilityZone, - }, - ], + destinations: props.replicationConfiguration.map( + (config) => ({ + fileSystemId: config.destinationFileSystem?.fileSystemId, + kmsKeyId: config.kmsKey?.keyArn, + region: config.destinationFileSystem ? config.destinationFileSystem.env.region : (config.region ?? Stack.of(this).region), + availabilityZoneName: config.availabilityZone, + })), } : undefined; this._resource = new CfnFileSystem(this, 'Resource', { diff --git a/packages/aws-cdk-lib/aws-efs/test/efs-file-system.test.ts b/packages/aws-cdk-lib/aws-efs/test/efs-file-system.test.ts index 2d65149667a18..2db8944c9070e 100644 --- a/packages/aws-cdk-lib/aws-efs/test/efs-file-system.test.ts +++ b/packages/aws-cdk-lib/aws-efs/test/efs-file-system.test.ts @@ -4,7 +4,7 @@ import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; import { App, RemovalPolicy, Size, Stack, Tags } from '../../core'; import * as cxapi from '../../cx-api'; -import { FileSystem, LifecyclePolicy, PerformanceMode, ThroughputMode, OutOfInfrequentAccessPolicy, ReplicationOverwriteProtection } from '../lib'; +import { FileSystem, LifecyclePolicy, PerformanceMode, ThroughputMode, OutOfInfrequentAccessPolicy, ReplicationOverwriteProtection, ReplicationConfiguration } from '../lib'; let stack = new Stack(); let vpc = new ec2.Vpc(stack, 'VPC'); @@ -964,7 +964,7 @@ describe('replication configuration', () => { // WHEN new FileSystem(stack, 'EfsFileSystem', { vpc, - replicationConfiguration: {}, + replicationConfiguration: [{}], }); // THEN @@ -989,9 +989,9 @@ describe('replication configuration', () => { }); new FileSystem(stack, 'EfsFileSystem', { vpc, - replicationConfiguration: { + replicationConfiguration: [{ destinationFileSystem: destination, - }, + }], }); // THEN @@ -1012,11 +1012,11 @@ describe('replication configuration', () => { // WHEN new FileSystem(stack, 'EfsFileSystem', { vpc, - replicationConfiguration: { + replicationConfiguration: [{ kmsKey: new kms.Key(stack, 'customKey'), region: 'us-east-1', availabilityZone: 'us-east-1a', - }, + }], }); // THEN @@ -1043,9 +1043,9 @@ describe('replication configuration', () => { expect(() => { new FileSystem(stack, 'EfsFileSystem', { vpc, - replicationConfiguration: { + replicationConfiguration: [{ region: 'us-east-1', - }, + }], replicationOverwriteProtection: ReplicationOverwriteProtection.DISABLED, }); }).toThrow('Cannot configure `replicationConfiguration` when `replicationOverwriteProtection` is set to `DISABLED`'); @@ -1065,10 +1065,10 @@ describe('replication configuration', () => { expect(() => { new FileSystem(stack, 'EfsFileSystem', { vpc, - replicationConfiguration: { + replicationConfiguration: [{ destinationFileSystem: destination, ...config, - }, + }], }); }).toThrow('Cannot configure `replicationConfiguration.region`, `replicationConfiguration.az` or `replicationConfiguration.kmsKey` when `replicationConfiguration.destinationFileSystem` is set'); }); @@ -1084,10 +1084,10 @@ describe('replication configuration', () => { expect(() => { new FileSystem(stack, 'EfsFileSystem', { vpc, - replicationConfiguration: { + replicationConfiguration: [{ destinationFileSystem: destination, kmsKey: new kms.Key(stack, 'customKey'), - }, + }], }); }).toThrow('Cannot configure `replicationConfiguration.region`, `replicationConfiguration.az` or `replicationConfiguration.kmsKey` when `replicationConfiguration.destinationFileSystem` is set'); }); @@ -1097,10 +1097,9 @@ describe('replication configuration', () => { expect(() => { new FileSystem(stack, 'EfsFileSystem', { vpc, - replicationConfiguration: { - enable: true, + replicationConfiguration: [{ region: 'invalid-region', - }, + }], }); }).toThrow('`replicationConfiguration.region` is invalid.'); }); @@ -1110,10 +1109,22 @@ describe('replication configuration', () => { expect(() => { new FileSystem(stack, 'EfsFileSystem', { vpc, - replicationConfiguration: { + replicationConfiguration: [{ availabilityZone: 'us-east-1a', - }, + }], }); }).toThrow('`replicationConfiguration.availabilityZone` cannot be specified without `replicationConfiguration.region`'); }); + + test.each([ + [[]], [[{ region: 'us-east-1' }, { region: 'ap-northeast-1' }]], + ])('throw error for invalid length of replicationConfiguration', (replicationConfiguration) => { + // THEN + expect(() => { + new FileSystem(stack, 'EfsFileSystem', { + vpc, + replicationConfiguration, + }); + }).toThrow('`replicationConfiguration` must contain exactly one destination'); + }); });