diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/aws-cdk-global-table-mrsc.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/aws-cdk-global-table-mrsc.assets.json new file mode 100644 index 0000000000000..74b4a846e3a03 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/aws-cdk-global-table-mrsc.assets.json @@ -0,0 +1,21 @@ +{ + "version": "45.0.0", + "files": { + "defd68cca50d06f5e4108d848be1f6f970a9c3d6f3fc29de03ddfc7ab8ddc805": { + "displayName": "aws-cdk-global-table-mrsc Template", + "source": { + "path": "aws-cdk-global-table-mrsc.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "defd68cca50d06f5e4108d848be1f6f970a9c3d6f3fc29de03ddfc7ab8ddc805.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/aws-cdk-global-table-mrsc.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/aws-cdk-global-table-mrsc.template.json new file mode 100644 index 0000000000000..fbda2b6469df0 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/aws-cdk-global-table-mrsc.template.json @@ -0,0 +1,84 @@ +{ + "Resources": { + "GlobalTable89F068B2": { + "Type": "AWS::DynamoDB::GlobalTable", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "pk", + "AttributeType": "S" + }, + { + "AttributeName": "sk", + "AttributeType": "N" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "GlobalTableWitnesses": [ + { + "Region": "us-west-2" + } + ], + "KeySchema": [ + { + "AttributeName": "pk", + "KeyType": "HASH" + }, + { + "AttributeName": "sk", + "KeyType": "RANGE" + } + ], + "MultiRegionConsistency": "STRONG", + "Replicas": [ + { + "Region": "us-east-2" + }, + { + "Region": "us-east-1" + } + ], + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "TableName": "my-global-table" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.assets.json new file mode 100644 index 0000000000000..c214491106f61 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.assets.json @@ -0,0 +1,20 @@ +{ + "version": "45.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F Template", + "source": { + "path": "awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/cdk.out new file mode 100644 index 0000000000000..3704a1b682acf --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"45.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/integ.json new file mode 100644 index 0000000000000..69096ec4f929a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/integ.json @@ -0,0 +1,17 @@ +{ + "version": "45.0.0", + "testCases": { + "aws-cdk-global-table-integ/DefaultTest": { + "stacks": [ + "aws-cdk-global-table-mrsc" + ], + "regions": [ + "us-east-1" + ], + "stackUpdateWorkflow": false, + "assertionStack": "aws-cdk-global-table-integ/DefaultTest/DeployAssert", + "assertionStackName": "awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F" + } + }, + "minimumCliVersion": "2.1020.1" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/manifest.json new file mode 100644 index 0000000000000..21e5bc521e312 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/manifest.json @@ -0,0 +1,594 @@ +{ + "version": "45.0.0", + "artifacts": { + "aws-cdk-global-table-mrsc.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-global-table-mrsc.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-global-table-mrsc": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/us-east-1", + "properties": { + "templateFile": "aws-cdk-global-table-mrsc.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-us-east-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-us-east-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1/defd68cca50d06f5e4108d848be1f6f970a9c3d6f3fc29de03ddfc7ab8ddc805.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-global-table-mrsc.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-us-east-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-global-table-mrsc.assets" + ], + "metadata": { + "/aws-cdk-global-table-mrsc/GlobalTable": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + }, + { + "type": "aws:cdk:hasPhysicalName", + "data": { + "Ref": "GlobalTable89F068B2" + } + } + ], + "/aws-cdk-global-table-mrsc/GlobalTable/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GlobalTable89F068B2" + } + ], + "/aws-cdk-global-table-mrsc/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-global-table-mrsc/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-global-table-mrsc" + }, + "awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "awscdkglobaltableintegDefaultTestDeployAssertA2A9E81F.assets" + ], + "metadata": { + "/aws-cdk-global-table-integ/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-global-table-integ/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-global-table-integ/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/core:enableStackNameDuplicates": { + "recommendedValue": true, + "explanation": "Allow multiple stacks with the same name" + }, + "aws-cdk:enableDiffNoFail": { + "recommendedValue": true, + "explanation": "Make `cdk diff` not fail when there are differences" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD" + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path" + }, + "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": { + "recommendedValue": true, + "explanation": "DockerImageAsset properly supports `.dockerignore` files by default" + }, + "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": { + "recommendedValue": true, + "explanation": "Fix the referencing of SecretsManager names from ARNs" + }, + "@aws-cdk/aws-kms:defaultKeyPolicies": { + "recommendedValue": true, + "explanation": "Tighten default KMS key policies" + }, + "@aws-cdk/aws-s3:grantWriteWithoutAcl": { + "recommendedValue": true, + "explanation": "Remove `PutObjectAcl` from Bucket.grantWrite" + }, + "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": { + "recommendedValue": true, + "explanation": "Do not specify a default DesiredCount for ECS services" + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK" + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently" + }, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have elastic file systems encrypted at rest by default." + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default." + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to by default create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy" + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model." + }, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": { + "userValue": false, + "recommendedValue": false, + "explanation": "When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated commands will be added to UserData to block container accessing IMDS. **Applicable to Linux only. IMPORTANT: See [details.](#aws-cdkaws-ecsenableImdsBlockingDeprecatedFeature)**" + }, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, CDK synth will throw exception if canContainersAccessInstanceRole is false. **IMPORTANT: See [details.](#aws-cdkaws-ecsdisableEcsImdsBlocking)**" + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis." + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy" + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role" + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + } + } + } + } + }, + "minimumCliVersion": "2.1020.1" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/tree.json new file mode 100644 index 0000000000000..9f8d264d8cc13 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.js.snapshot/tree.json @@ -0,0 +1 @@ +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-cdk-global-table-mrsc":{"id":"aws-cdk-global-table-mrsc","path":"aws-cdk-global-table-mrsc","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"GlobalTable":{"id":"GlobalTable","path":"aws-cdk-global-table-mrsc/GlobalTable","constructInfo":{"fqn":"aws-cdk-lib.aws_dynamodb.TableBaseV2","version":"0.0.0","metadata":["*","*"]},"children":{"Resource":{"id":"Resource","path":"aws-cdk-global-table-mrsc/GlobalTable/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_dynamodb.CfnGlobalTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::DynamoDB::GlobalTable","aws:cdk:cloudformation:props":{"attributeDefinitions":[{"attributeName":"pk","attributeType":"S"},{"attributeName":"sk","attributeType":"N"}],"billingMode":"PAY_PER_REQUEST","globalTableWitnesses":[{"region":"us-west-2"}],"keySchema":[{"attributeName":"pk","keyType":"HASH"},{"attributeName":"sk","keyType":"RANGE"}],"multiRegionConsistency":"STRONG","replicas":[{"region":"us-east-2"},{"region":"us-east-1"}],"streamSpecification":{"streamViewType":"NEW_AND_OLD_IMAGES"},"tableName":"my-global-table"}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-cdk-global-table-mrsc/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-cdk-global-table-mrsc/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"aws-cdk-global-table-integ":{"id":"aws-cdk-global-table-integ","path":"aws-cdk-global-table-integ","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"aws-cdk-global-table-integ/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"aws-cdk-global-table-integ/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"aws-cdk-global-table-integ/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-cdk-global-table-integ/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-cdk-global-table-integ/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.ts new file mode 100644 index 0000000000000..21713c9b98c59 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-v2-mrsc.ts @@ -0,0 +1,31 @@ +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { App, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'; +import { AttributeType, MultiRegionConsistency, TableV2 } from 'aws-cdk-lib/aws-dynamodb'; +import { Construct } from 'constructs'; + +class TestStack extends Stack { + public constructor(scope: Construct, id: string, props: StackProps) { + super(scope, id, props); + + new TableV2(this, 'GlobalTable', { + tableName: 'my-global-table', + partitionKey: { name: 'pk', type: AttributeType.STRING }, + sortKey: { name: 'sk', type: AttributeType.NUMBER }, + removalPolicy: RemovalPolicy.DESTROY, + multiRegionConsistency: MultiRegionConsistency.STRONG, + witnessRegion: 'us-west-2', + replicas: [ + { + region: 'us-east-2', + }, + ], + }); + } +} + +const app = new App(); +new IntegTest(app, 'aws-cdk-global-table-integ', { + testCases: [new TestStack(app, 'aws-cdk-global-table-mrsc', { env: { region: 'us-east-1' } })], + regions: ['us-east-1'], + stackUpdateWorkflow: false, +}); diff --git a/packages/aws-cdk-lib/aws-dynamodb/README.md b/packages/aws-cdk-lib/aws-dynamodb/README.md index 20be04c86613c..d0d6a99aa5f53 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/README.md +++ b/packages/aws-cdk-lib/aws-dynamodb/README.md @@ -150,6 +150,68 @@ const barStack = new BarStack(app, 'BarStack', { Note: You can create an instance of the `TableV2` construct with as many `replicas` as needed as long as there is only one replica per region. After table creation you can add or remove `replicas`, but you can only add or remove a single replica in each update. +## Multi-Region Strong Consistency (MRSC) + +By default, DynamoDB global tables provide eventual consistency across regions. For applications requiring strong consistency across regions, you can configure Multi-Region Strong Consistency (MRSC) using the `multiRegionConsistency` property. + +MRSC global tables can be configured in two ways: +* **Three replicas**: Deploy your table across three regions within the same region set +* **Two replicas + one witness**: Deploy your table across two regions with a witness region for consensus + +### Region Sets + +MRSC global tables must be deployed within the same region set. The supported region sets are: + +* **US Region set**: `us-east-1`, `us-east-2`, `us-west-2` +* **EU Region set**: `eu-west-1`, `eu-west-2`, `eu-west-3`, `eu-central-1` +* **AP Region set**: `ap-northeast-1`, `ap-northeast-2`, `ap-northeast-3` + +### Three Replicas Configuration + +```ts +import * as cdk from 'aws-cdk-lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'Stack', { env: { region: 'us-west-2' } }); + +const mrscTable = new dynamodb.TableV2(stack, 'MRSCTable', { + partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING }, + multiRegionConsistency: dynamodb.MultiRegionConsistency.STRONG, + replicas: [ + { region: 'us-east-1' }, + { region: 'us-east-2' }, + ], +}); +``` + +### Two Replicas + Witness Configuration + +```ts +import * as cdk from 'aws-cdk-lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'Stack', { env: { region: 'us-west-2' } }); + +const mrscTable = new dynamodb.TableV2(stack, 'MRSCTable', { + partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING }, + multiRegionConsistency: dynamodb.MultiRegionConsistency.STRONG, + replicas: [ + { region: 'us-east-1' }, + ], + witnessRegion: 'us-east-2', +}); +``` + +### Important Considerations + +* **Witness regions** can only be used with `MultiRegionConsistency.STRONG`. Attempting to specify a witness region with eventual consistency will result in a validation error. +* **Region validation**: All regions (primary, replicas, and witness) must be within the same region set. +* **Replica count**: When using a witness region, you must have exactly 2 replicas (including the primary). Without a witness region, you must have exactly 3 replicas. +* **Performance**: MRSC provides strong consistency but may have higher latency compared to eventual consistency. + +Further reading: +https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html#V2globaltables_HowItWorks.consistency-modes-mrsc + ## Billing The `TableV2` construct can be configured with on-demand or provisioned billing: diff --git a/packages/aws-cdk-lib/aws-dynamodb/lib/shared.ts b/packages/aws-cdk-lib/aws-dynamodb/lib/shared.ts index a14d1c73fe76d..579999426640d 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/lib/shared.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/lib/shared.ts @@ -198,6 +198,23 @@ export enum TableClass { STANDARD_INFREQUENT_ACCESS = 'STANDARD_INFREQUENT_ACCESS', } +/** + * Global table multi-region consistency mode. + * + * @see https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html#V2globaltables_HowItWorks.consistency-modes-mrsc + */ +export enum MultiRegionConsistency { + /** + * Default consistency mode for Global Tables. + * Multi-region eventual consistency. + */ + EVENTUAL = 'EVENTUAL', + /** + * Multi-region strong consistency. + */ + STRONG = 'STRONG', +} + /** * What kind of server-side encryption to apply to this table. */ diff --git a/packages/aws-cdk-lib/aws-dynamodb/lib/table-v2.ts b/packages/aws-cdk-lib/aws-dynamodb/lib/table-v2.ts index 2e459b397fc94..4b6f168148e2f 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/lib/table-v2.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/lib/table-v2.ts @@ -15,6 +15,7 @@ import { PointInTimeRecoverySpecification, TableClass, WarmThroughput, + MultiRegionConsistency, } from './shared'; import { ITableV2, TableBaseV2 } from './table-v2-base'; import { PolicyDocument } from '../../aws-iam'; @@ -296,6 +297,24 @@ export interface TablePropsV2 extends TableOptionsV2 { */ readonly replicas?: ReplicaTableProps[]; + /** + * The witness Region for the MRSC global table. + * A MRSC global table can be configured with either three replicas, or with two replicas and one witness. + * + * Note: Witness region cannot be specified for a Multi-Region Eventual Consistency (MREC) Global Table. + * Witness regions are only supported for Multi-Region Strong Consistency (MRSC) Global Tables. + * + * @default - no witness region + */ + readonly witnessRegion?: string; + + /** + * Specifies the consistency mode for a new global table. + * + * @default MultiRegionConsistency.EVENTUAL + */ + readonly multiRegionConsistency?: MultiRegionConsistency; + /** * Global secondary indexes. * @@ -596,11 +615,19 @@ export class TableV2 extends TableBaseV2 { props.globalSecondaryIndexes?.forEach(gsi => this.addGlobalSecondaryIndex(gsi)); props.localSecondaryIndexes?.forEach(lsi => this.addLocalSecondaryIndex(lsi)); + if (props.multiRegionConsistency === MultiRegionConsistency.STRONG) { + this.validateMrscConfiguration(props); + } else if (props.witnessRegion) { + throw new ValidationError('Witness region cannot be specified for a Multi-Region Eventual Consistency (MREC) Global Table - Witness regions are only supported for Multi-Region Strong Consistency (MRSC) Global Tables.', this); + } + const resource = new CfnGlobalTable(this, 'Resource', { tableName: this.physicalName, keySchema: this.keySchema, attributeDefinitions: Lazy.any({ produce: () => this.attributeDefinitions }), replicas: Lazy.any({ produce: () => this.renderReplicaTables() }), + globalTableWitnesses: props.witnessRegion? [{ region: props.witnessRegion }] : undefined, + multiRegionConsistency: props.multiRegionConsistency ? props.multiRegionConsistency : undefined, globalSecondaryIndexes: Lazy.any({ produce: () => this.renderGlobalIndexes() }, { omitEmptyArray: true }), localSecondaryIndexes: Lazy.any({ produce: () => this.renderLocalIndexes() }, { omitEmptyArray: true }), billingMode: this.billingMode, @@ -1048,4 +1075,63 @@ export class TableV2 extends TableBaseV2 { throw new ValidationError('`recoveryPeriodInDays` must be a value between `1` and `35`.', this); } } + + private validateMrscConfiguration(props: TablePropsV2) { + const regionSets = { + US: ['us-east-1', 'us-east-2', 'us-west-2'], + EU: ['eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-central-1'], + AP: ['ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3'], + }; + + const primaryRegion = this.stack.region; + const replicaRegions = (props.replicas || []).map(replica => replica.region); + const witnessRegion = props.witnessRegion; + + if (Token.isUnresolved(primaryRegion)) { + throw new ValidationError('MRSC global tables with STRONG consistency are not supported in a region agnostic stack', this); + } + + const allRegions = [primaryRegion, ...replicaRegions]; + if (witnessRegion) { + allRegions.push(witnessRegion); + } + + for (const region of allRegions) { + if (Token.isUnresolved(region)) { + throw new ValidationError('MRSC global tables with STRONG consistency do not support token-based regions', this); + } + } + + let regionSet: string[] | undefined; + let regionSetName: string | undefined; + + for (const [setName, regions] of Object.entries(regionSets)) { + if (regions.includes(primaryRegion)) { + regionSet = regions; + regionSetName = setName; + break; + } + } + + if (!regionSet || !regionSetName) { + throw new ValidationError(`Primary region '${primaryRegion}' is not supported for MRSC global tables with STRONG consistency. Supported regions: ${Object.values(regionSets).flat().join(', ')}`, this); + } + + for (const region of allRegions) { + if (!regionSet.includes(region)) { + throw new ValidationError(`Region '${region}' is not in the same region set (${regionSetName}) as the primary region '${primaryRegion}'. All regions must be within the same region set for MRSC global tables with STRONG consistency. Supported ${regionSetName} regions: ${regionSet.join(', ')}`, this); + } + } + + const totalReplicas = replicaRegions.length + 1; + if (witnessRegion) { + if (totalReplicas !== 2) { + throw new ValidationError(`MRSC global table with witness region must have exactly 2 replicas (including primary), but found ${totalReplicas}. Current configuration: primary region '${primaryRegion}', replica regions [${replicaRegions.join(', ')}], witness region '${witnessRegion}'`, this); + } + } else { + if (totalReplicas !== 3) { + throw new ValidationError(`MRSC global table without witness region must have exactly 3 replicas (including primary), but found ${totalReplicas}. Current configuration: primary region '${primaryRegion}', replica regions [${replicaRegions.join(', ')}]`, this); + } + } + } } diff --git a/packages/aws-cdk-lib/aws-dynamodb/test/table-v2.test.ts b/packages/aws-cdk-lib/aws-dynamodb/test/table-v2.test.ts index 82eb348fed071..d83b484cb79b1 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/test/table-v2.test.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/test/table-v2.test.ts @@ -6,6 +6,7 @@ import { CfnDeletionPolicy, Lazy, RemovalPolicy, Stack, Tags } from '../../core' import { AttributeType, Billing, Capacity, GlobalSecondaryIndexPropsV2, TableV2, LocalSecondaryIndexProps, ProjectionType, StreamViewType, TableClass, TableEncryptionV2, + MultiRegionConsistency, } from '../lib'; describe('table', () => { @@ -3239,3 +3240,173 @@ test('Warm Throughput test on-demand', () => { ], }); }); + +describe('MRSC global tables', () => { + test('with witness region', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack', { env: { region: 'us-west-2' } }); + + // WHEN + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + replicas: [{ region: 'us-east-1' }], + witnessRegion: 'us-east-2', + multiRegionConsistency: MultiRegionConsistency.STRONG, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::GlobalTable', { + Replicas: [ + { Region: 'us-east-1' }, + { Region: 'us-west-2' }, + ], + GlobalTableWitnesses: [ + { Region: 'us-east-2' }, + ], + }); + }); + + test('without witness region should not have GlobalTableWitnesses property', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack', { env: { region: 'us-west-2' } }); + + // WHEN + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + replicas: [{ region: 'us-east-1' }], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::GlobalTable', { + Replicas: [ + { Region: 'us-east-1' }, + { Region: 'us-west-2' }, + ], + }); + // Verify that GlobalTableWitnesses is not present in the template + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::GlobalTable', + Match.not(Match.objectLike({ + GlobalTableWitnesses: Match.anyValue(), + })), + ); + }); + + test('with witness region and strong consistency requirements', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack', { env: { region: 'us-west-2' } }); + + // WHEN + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + sortKey: { name: 'sk', type: AttributeType.STRING }, + replicas: [{ region: 'us-east-1' }], + witnessRegion: 'us-east-2', + multiRegionConsistency: MultiRegionConsistency.STRONG, + billing: Billing.provisioned({ + readCapacity: Capacity.fixed(10), + writeCapacity: Capacity.autoscaled({ minCapacity: 10, maxCapacity: 100 }), + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::GlobalTable', { + KeySchema: [ + { AttributeName: 'pk', KeyType: 'HASH' }, + { AttributeName: 'sk', KeyType: 'RANGE' }, + ], + AttributeDefinitions: [ + { AttributeName: 'pk', AttributeType: 'S' }, + { AttributeName: 'sk', AttributeType: 'S' }, + ], + BillingMode: 'PROVISIONED', + Replicas: [ + { + Region: 'us-east-1', + ReadProvisionedThroughputSettings: { + ReadCapacityUnits: 10, + }, + }, + { + Region: 'us-west-2', + ReadProvisionedThroughputSettings: { + ReadCapacityUnits: 10, + }, + }, + ], + GlobalTableWitnesses: [ + { Region: 'us-east-2' }, + ], + }); + }); +}); +describe('MRSC global tables validation', () => { + test('throws when witness region is used with eventual consistency', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack', { env: { region: 'us-west-2' } }); + + // WHEN / THEN - Error should be thrown during construction + expect(() => { + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + replicas: [{ region: 'us-east-1' }], + witnessRegion: 'us-east-2', + // multiRegionConsistency defaults to EVENTUAL + }); + }).toThrow('Witness region cannot be specified for a Multi-Region Eventual Consistency (MREC) Global Table - Witness regions are only supported for Multi-Region Strong Consistency (MRSC) Global Tables.'); + }); + + test('validates regions are in same region set for STRONG consistency', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack', { env: { region: 'us-west-2' } }); + + // WHEN / THEN - Error should be thrown during construction + expect(() => { + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + replicas: [{ region: 'eu-west-1' }], + witnessRegion: 'us-east-2', + multiRegionConsistency: MultiRegionConsistency.STRONG, + }); + }).toThrow("Region 'eu-west-1' is not in the same region set (US) as the primary region 'us-west-2'. All regions must be within the same region set for MRSC global tables with STRONG consistency. Supported US regions: us-east-1, us-east-2, us-west-2"); + }); + + test('validates exactly 2 replicas with witness for STRONG consistency', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack', { env: { region: 'eu-west-1' } }); + + // WHEN / THEN - Error should be thrown during construction + expect(() => { + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + replicas: [{ region: 'eu-west-2' }, { region: 'eu-west-3' }], // Too many replicas + witnessRegion: 'eu-central-1', // Use same region set + multiRegionConsistency: MultiRegionConsistency.STRONG, + }); + }).toThrow("MRSC global table with witness region must have exactly 2 replicas (including primary), but found 3. Current configuration: primary region 'eu-west-1', replica regions [eu-west-2, eu-west-3], witness region 'eu-central-1'"); + }); + + test('allows valid STRONG consistency configuration with witness', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack', { env: { region: 'us-west-2' } }); + + // WHEN + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + replicas: [{ region: 'us-east-1' }], + witnessRegion: 'us-east-2', + multiRegionConsistency: MultiRegionConsistency.STRONG, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::GlobalTable', { + MultiRegionConsistency: 'STRONG', + Replicas: [ + { Region: 'us-east-1' }, + { Region: 'us-west-2' }, + ], + GlobalTableWitnesses: [ + { Region: 'us-east-2' }, + ], + }); + }); +});