diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/PITR-Integ-Test.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/PITR-Integ-Test.assets.json new file mode 100644 index 0000000000000..32a37675f6a3a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/PITR-Integ-Test.assets.json @@ -0,0 +1,20 @@ +{ + "version": "39.0.0", + "files": { + "0418629b0628f97e666451fa00925d057fd7dff0620b45116b1776e029f336f8": { + "source": { + "path": "PITR-Integ-Test.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-eu-west-1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-eu-west-1", + "objectKey": "0418629b0628f97e666451fa00925d057fd7dff0620b45116b1776e029f336f8.json", + "region": "eu-west-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-eu-west-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/PITR-Integ-Test.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/PITR-Integ-Test.template.json new file mode 100644 index 0000000000000..850a785e84ef0 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/PITR-Integ-Test.template.json @@ -0,0 +1,77 @@ +{ + "Resources": { + "TableTestV29E695762": { + "Type": "AWS::DynamoDB::GlobalTable", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "Replicas": [ + { + "PointInTimeRecoverySpecification": { + "PointInTimeRecoveryEnabled": true, + "RecoveryPeriodInDays": 5 + }, + "Region": "eu-west-2" + }, + { + "PointInTimeRecoverySpecification": { + "PointInTimeRecoveryEnabled": true, + "RecoveryPeriodInDays": 10 + }, + "Region": "eu-west-1" + } + ], + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + } + }, + "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.dynamodb-v2.pitr.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/cdk.out new file mode 100644 index 0000000000000..91e1a8b9901d5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"39.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/integ.json new file mode 100644 index 0000000000000..cf29ce8408c6c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "39.0.0", + "testCases": { + "table-v2-pitr-test/DefaultTest": { + "stacks": [ + "PITR-Integ-Test" + ], + "assertionStack": "table-v2-pitr-test/DefaultTest/DeployAssert", + "assertionStackName": "tablev2pitrtestDefaultTestDeployAssertCC1E91C6" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/manifest.json new file mode 100644 index 0000000000000..ad9652cbd1613 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/manifest.json @@ -0,0 +1,113 @@ +{ + "version": "39.0.0", + "artifacts": { + "PITR-Integ-Test.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "PITR-Integ-Test.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "PITR-Integ-Test": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/eu-west-1", + "properties": { + "templateFile": "PITR-Integ-Test.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-eu-west-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-eu-west-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-eu-west-1/0418629b0628f97e666451fa00925d057fd7dff0620b45116b1776e029f336f8.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "PITR-Integ-Test.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-eu-west-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "PITR-Integ-Test.assets" + ], + "metadata": { + "/PITR-Integ-Test/TableTestV2/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TableTestV29E695762" + } + ], + "/PITR-Integ-Test/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/PITR-Integ-Test/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "PITR-Integ-Test" + }, + "tablev2pitrtestDefaultTestDeployAssertCC1E91C6.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "tablev2pitrtestDefaultTestDeployAssertCC1E91C6.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "tablev2pitrtestDefaultTestDeployAssertCC1E91C6": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "tablev2pitrtestDefaultTestDeployAssertCC1E91C6.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": [ + "tablev2pitrtestDefaultTestDeployAssertCC1E91C6.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": [ + "tablev2pitrtestDefaultTestDeployAssertCC1E91C6.assets" + ], + "metadata": { + "/table-v2-pitr-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/table-v2-pitr-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "table-v2-pitr-test/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/tablev2pitrtestDefaultTestDeployAssertCC1E91C6.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/tablev2pitrtestDefaultTestDeployAssertCC1E91C6.assets.json new file mode 100644 index 0000000000000..95d05f202d190 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/tablev2pitrtestDefaultTestDeployAssertCC1E91C6.assets.json @@ -0,0 +1,19 @@ +{ + "version": "39.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "tablev2pitrtestDefaultTestDeployAssertCC1E91C6.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.dynamodb-v2.pitr.js.snapshot/tablev2pitrtestDefaultTestDeployAssertCC1E91C6.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/tablev2pitrtestDefaultTestDeployAssertCC1E91C6.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/tablev2pitrtestDefaultTestDeployAssertCC1E91C6.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.dynamodb-v2.pitr.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/tree.json new file mode 100644 index 0000000000000..3fd29dbd7026e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.js.snapshot/tree.json @@ -0,0 +1,156 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "PITR-Integ-Test": { + "id": "PITR-Integ-Test", + "path": "PITR-Integ-Test", + "children": { + "TableTestV2": { + "id": "TableTestV2", + "path": "PITR-Integ-Test/TableTestV2", + "children": { + "Resource": { + "id": "Resource", + "path": "PITR-Integ-Test/TableTestV2/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::DynamoDB::GlobalTable", + "aws:cdk:cloudformation:props": { + "attributeDefinitions": [ + { + "attributeName": "id", + "attributeType": "S" + } + ], + "billingMode": "PAY_PER_REQUEST", + "keySchema": [ + { + "attributeName": "id", + "keyType": "HASH" + } + ], + "replicas": [ + { + "region": "eu-west-2", + "pointInTimeRecoverySpecification": { + "pointInTimeRecoveryEnabled": true, + "recoveryPeriodInDays": 5 + } + }, + { + "region": "eu-west-1", + "pointInTimeRecoverySpecification": { + "pointInTimeRecoveryEnabled": true, + "recoveryPeriodInDays": 10 + } + } + ], + "streamSpecification": { + "streamViewType": "NEW_AND_OLD_IMAGES" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.CfnGlobalTable", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.TableBaseV2", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "PITR-Integ-Test/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "PITR-Integ-Test/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "table-v2-pitr-test": { + "id": "table-v2-pitr-test", + "path": "table-v2-pitr-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "table-v2-pitr-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "table-v2-pitr-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "table-v2-pitr-test/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "table-v2-pitr-test/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "table-v2-pitr-test/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.ts new file mode 100644 index 0000000000000..ede25477da770 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb-v2.pitr.ts @@ -0,0 +1,37 @@ +import { App, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +const app = new App(); + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + new dynamodb.TableV2(this, 'TableTestV2', { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING, + }, + removalPolicy: RemovalPolicy.DESTROY, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 10, + }, + replicas: [{ + region: 'eu-west-2', + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 5, + }, + }], + }); + } +} + +const stack = new TestStack(app, 'PITR-Integ-Test', { env: { region: 'eu-west-1' } }); + +new IntegTest(app, 'table-v2-pitr-test', { + testCases: [stack], +}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/cdk.out new file mode 100644 index 0000000000000..91e1a8b9901d5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"39.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/integ.json new file mode 100644 index 0000000000000..2e1cdc1d48f48 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "39.0.0", + "testCases": { + "table-pitr-test/DefaultTest": { + "stacks": [ + "table-pitr" + ], + "assertionStack": "table-pitr-test/DefaultTest/DeployAssert", + "assertionStackName": "tablepitrtestDefaultTestDeployAssert8B21EF95" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/manifest.json new file mode 100644 index 0000000000000..8deec5ae81321 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/manifest.json @@ -0,0 +1,113 @@ +{ + "version": "39.0.0", + "artifacts": { + "table-pitr.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "table-pitr.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "table-pitr": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/eu-west-1", + "properties": { + "templateFile": "table-pitr.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-eu-west-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-eu-west-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-eu-west-1/17a0b584180fa10180759083942df1c4452233aab6a49f626e1e35cb91bbf5a2.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "table-pitr.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-eu-west-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "table-pitr.assets" + ], + "metadata": { + "/table-pitr/TableTest/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TableTest40FC03A7" + } + ], + "/table-pitr/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/table-pitr/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "table-pitr" + }, + "tablepitrtestDefaultTestDeployAssert8B21EF95.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "tablepitrtestDefaultTestDeployAssert8B21EF95.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "tablepitrtestDefaultTestDeployAssert8B21EF95": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "tablepitrtestDefaultTestDeployAssert8B21EF95.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": [ + "tablepitrtestDefaultTestDeployAssert8B21EF95.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": [ + "tablepitrtestDefaultTestDeployAssert8B21EF95.assets" + ], + "metadata": { + "/table-pitr-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/table-pitr-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "table-pitr-test/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/table-pitr.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/table-pitr.assets.json new file mode 100644 index 0000000000000..11b1e6f3ff0b2 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/table-pitr.assets.json @@ -0,0 +1,20 @@ +{ + "version": "39.0.0", + "files": { + "17a0b584180fa10180759083942df1c4452233aab6a49f626e1e35cb91bbf5a2": { + "source": { + "path": "table-pitr.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-eu-west-1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-eu-west-1", + "objectKey": "17a0b584180fa10180759083942df1c4452233aab6a49f626e1e35cb91bbf5a2.json", + "region": "eu-west-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-eu-west-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/table-pitr.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/table-pitr.template.json new file mode 100644 index 0000000000000..7fe1d17cf233d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/table-pitr.template.json @@ -0,0 +1,65 @@ +{ + "Resources": { + "TableTest40FC03A7": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "PointInTimeRecoverySpecification": { + "PointInTimeRecoveryEnabled": true, + "RecoveryPeriodInDays": 10 + }, + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + }, + "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.dynamodb.pitr.js.snapshot/tablepitrtestDefaultTestDeployAssert8B21EF95.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/tablepitrtestDefaultTestDeployAssert8B21EF95.assets.json new file mode 100644 index 0000000000000..0dc1140c3bcf6 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/tablepitrtestDefaultTestDeployAssert8B21EF95.assets.json @@ -0,0 +1,19 @@ +{ + "version": "39.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "tablepitrtestDefaultTestDeployAssert8B21EF95.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.dynamodb.pitr.js.snapshot/tablepitrtestDefaultTestDeployAssert8B21EF95.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/tablepitrtestDefaultTestDeployAssert8B21EF95.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/tablepitrtestDefaultTestDeployAssert8B21EF95.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.dynamodb.pitr.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/tree.json new file mode 100644 index 0000000000000..e7e990c7e686b --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.js.snapshot/tree.json @@ -0,0 +1,152 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "table-pitr": { + "id": "table-pitr", + "path": "table-pitr", + "children": { + "TableTest": { + "id": "TableTest", + "path": "table-pitr/TableTest", + "children": { + "Resource": { + "id": "Resource", + "path": "table-pitr/TableTest/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::DynamoDB::Table", + "aws:cdk:cloudformation:props": { + "attributeDefinitions": [ + { + "attributeName": "id", + "attributeType": "S" + } + ], + "keySchema": [ + { + "attributeName": "id", + "keyType": "HASH" + } + ], + "pointInTimeRecoverySpecification": { + "pointInTimeRecoveryEnabled": true, + "recoveryPeriodInDays": 10 + }, + "provisionedThroughput": { + "readCapacityUnits": 5, + "writeCapacityUnits": 5 + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.CfnTable", + "version": "0.0.0" + } + }, + "ScalingRole": { + "id": "ScalingRole", + "path": "table-pitr/TableTest/ScalingRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.Table", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "table-pitr/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "table-pitr/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "table-pitr-test": { + "id": "table-pitr-test", + "path": "table-pitr-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "table-pitr-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "table-pitr-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "table-pitr-test/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "table-pitr-test/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "table-pitr-test/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.ts new file mode 100644 index 0000000000000..e7d0b0bbeeec0 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.dynamodb.pitr.ts @@ -0,0 +1,30 @@ +import { App, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +const app = new App(); + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + new dynamodb.Table(this, 'TableTest', { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING, + }, + removalPolicy: RemovalPolicy.DESTROY, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 10, + }, + }); + } +} + +const stack = new TestStack(app, 'table-pitr', { env: { region: 'eu-west-1' } }); + +new IntegTest(app, 'table-pitr-test', { + testCases: [stack], +}); diff --git a/packages/aws-cdk-lib/aws-dynamodb/README.md b/packages/aws-cdk-lib/aws-dynamodb/README.md index 5c0a1488d0fff..20be04c86613c 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/README.md +++ b/packages/aws-cdk-lib/aws-dynamodb/README.md @@ -19,7 +19,9 @@ const table = new dynamodb.TableV2(this, 'Table', { partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING }, contributorInsights: true, tableClass: dynamodb.TableClass.STANDARD_INFREQUENT_ACCESS, - pointInTimeRecovery: true, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + }, }); ``` @@ -66,7 +68,7 @@ globalTable.addReplica({ region: 'us-east-2', deletionProtection: true }); The following properties are configurable on a per-replica basis, but will be inherited from the `TableV2` properties if not specified: * contributorInsights * deletionProtection -* pointInTimeRecovery +* pointInTimeRecoverySpecification * tableClass * readCapacity (only configurable if the `TableV2` billing mode is `PROVISIONED`) * globalSecondaryIndexes (only `contributorInsights` and `readCapacity`) @@ -82,12 +84,16 @@ const stack = new cdk.Stack(app, 'Stack', { env: { region: 'us-west-2' } }); const globalTable = new dynamodb.TableV2(stack, 'GlobalTable', { partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING }, contributorInsights: true, - pointInTimeRecovery: true, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + }, replicas: [ { region: 'us-east-1', tableClass: dynamodb.TableClass.STANDARD_INFREQUENT_ACCESS, - pointInTimeRecovery: false, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: false, + }, }, { region: 'us-east-2', @@ -622,12 +628,17 @@ const globalTable = new dynamodb.TableV2(stack, 'GlobalTable', { ## Point-in-Time Recovery -`pointInTimeRecovery` provides automatic backups of your DynamoDB table data which helps protect your tables from accidental write or delete operations. +`pointInTimeRecoverySpecifcation` provides automatic backups of your DynamoDB table data which helps protect your tables from accidental write or delete operations. + +You can also choose to set `recoveryPeriodInDays` to a value between `1` and `35` which dictates how many days of recoverable data is stored. If no value is provided, the recovery period defaults to `35` days. ```ts const table = new dynamodb.TableV2(this, 'Table', { partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING }, - pointInTimeRecovery: true, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 4, + }, }); ``` diff --git a/packages/aws-cdk-lib/aws-dynamodb/TABLE_V1_API.md b/packages/aws-cdk-lib/aws-dynamodb/TABLE_V1_API.md index e316b314d184a..a85e72f2d8833 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/TABLE_V1_API.md +++ b/packages/aws-cdk-lib/aws-dynamodb/TABLE_V1_API.md @@ -294,4 +294,23 @@ new dynamodb.Table(this, 'MyTable', { }); ``` -If you have a global table replica, note that it does not support the addition of a resource-based policy. \ No newline at end of file +If you have a global table replica, note that it does not support the addition of a resource-based policy. + +## Point-in-Time Recovery + +`pointInTimeRecoverySpecifcation` provides automatic backups of your DynamoDB table data which helps protect your tables from accidental write or delete operations. + +You can also choose to set `recoveryPeriodInDays` to a value between `1` and `35` which dictates how many days of recoverable data is stored. If no value is provided, the recovery period defaults to `35` days. + +```ts +const table = new dynamodb.Table(this, 'Table', { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING, + }, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 4, + }, +}); +``` \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-dynamodb/lib/shared.ts b/packages/aws-cdk-lib/aws-dynamodb/lib/shared.ts index d73540650d99d..5e2d44243f297 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/lib/shared.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/lib/shared.ts @@ -101,6 +101,25 @@ export interface WarmThroughput { readonly writeUnitsPerSecond?: number; } +/** + * Reference to PointInTimeRecovey Specification + * for continuous backups + */ +export interface PointInTimeRecoverySpecification { + /** + * Indicates whether point in time recovery is enabled (true) or disabled (false) on the table. + * @default false + */ + readonly pointInTimeRecoveryEnabled: boolean; + /** + * The number of preceding days for which continuous backups are taken and maintained. + * Your table data is only recoverable to any point-in-time from within the configured recovery period. + * If no value is provided, the value will default to 35. + * @default 35 + */ + readonly recoveryPeriodInDays?: number; +} + /** * Data types for attributes within a 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 66fd85e68cbb8..9ba74232dab95 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/lib/table-v2.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/lib/table-v2.ts @@ -1,8 +1,10 @@ import { Construct } from 'constructs'; + import { Billing } from './billing'; import { Capacity } from './capacity'; import { CfnGlobalTable } from './dynamodb.generated'; import { TableEncryptionV2 } from './encryption'; + import { Attribute, BillingMode, @@ -10,6 +12,7 @@ import { ProjectionType, SecondaryIndexProps, StreamViewType, + PointInTimeRecoverySpecification, TableClass, WarmThroughput, } from './shared'; @@ -29,6 +32,7 @@ import { TagType, Token, } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; import * as cxapi from '../../cx-api'; const HASH_KEY_TYPE = 'HASH'; @@ -148,11 +152,19 @@ export interface TableOptionsV2 { /** * Whether point-in-time recovery is enabled. - * - * @default false + * @deprecated use `pointInTimeRecoverySpecification` instead + * @default false - point in time recovery is not enabled. */ readonly pointInTimeRecovery?: boolean; + /** + * Whether point-in-time recovery is enabled + * and recoveryPeriodInDays is set. + * + * @default - point in time recovery is not enabled. + */ + readonly pointInTimeRecoverySpecification?: PointInTimeRecoverySpecification; + /** * The table class. * @@ -559,6 +571,8 @@ export class TableV2 extends TableBaseV2 { this.addKey(props.sortKey, RANGE_KEY_TYPE); } + this.validatePitr(props); + if (props.billing?.mode === BillingMode.PAY_PER_REQUEST || props.billing?.mode === undefined) { this.maxReadRequestUnits = props.billing?._renderReadCapacity(); this.maxWriteRequestUnits = props.billing?._renderWriteCapacity(); @@ -697,9 +711,23 @@ export class TableV2 extends TableBaseV2 { } private configureReplicaTable(props: ReplicaTableProps): CfnGlobalTable.ReplicaSpecificationProperty { - const pointInTimeRecovery = props.pointInTimeRecovery ?? this.tableOptions.pointInTimeRecovery; const contributorInsights = props.contributorInsights ?? this.tableOptions.contributorInsights; + // Determine if Point-In-Time Recovery (PITR) is enabled based on the provided property or table options (deprecated options). + const pointInTimeRecovery = props.pointInTimeRecovery ?? this.tableOptions.pointInTimeRecovery; + + /* Construct the PointInTimeRecoverySpecification object to configure PITR settings. + * 1. Explicitly provided specification via props.pointInTimeRecoverySpecification. + * 2. Fallback to default specification from tableOptions.pointInTimeRecoverySpecification. + * 3. Derive the specification based on pointInTimeRecovery if it's defined. + */ + const pointInTimeRecoverySpecification: PointInTimeRecoverySpecification | undefined = + props.pointInTimeRecoverySpecification ?? + this.tableOptions.pointInTimeRecoverySpecification ?? + (pointInTimeRecovery !== undefined + ? { pointInTimeRecoveryEnabled: pointInTimeRecovery } + : undefined); + /* * Feature flag set as the following may be a breaking change. * @see https://github.com/aws/aws-cdk/pull/31097 @@ -730,9 +758,7 @@ export class TableV2 extends TableBaseV2 { contributorInsightsSpecification: contributorInsights !== undefined ? { enabled: contributorInsights } : undefined, - pointInTimeRecoverySpecification: pointInTimeRecovery !== undefined - ? { pointInTimeRecoveryEnabled: pointInTimeRecovery } - : undefined, + pointInTimeRecoverySpecification: pointInTimeRecoverySpecification, readProvisionedThroughputSettings: props.readCapacity ? props.readCapacity._renderReadCapacity() : this.readProvisioning, @@ -992,4 +1018,20 @@ export class TableV2 extends TableBaseV2 { throw new Error(`You may not provide more than ${MAX_LSI_COUNT} local secondary indexes`); } } + + private validatePitr(props: TablePropsV2) { + const recoveryPeriodInDays = props.pointInTimeRecoverySpecification?.recoveryPeriodInDays; + + if (props.pointInTimeRecovery !== undefined && props.pointInTimeRecoverySpecification !== undefined) { + throw new ValidationError('`pointInTimeRecoverySpecification` and `pointInTimeRecovery` are set. Use `pointInTimeRecoverySpecification` only.', this); + } + + if (!props.pointInTimeRecoverySpecification?.pointInTimeRecoveryEnabled && recoveryPeriodInDays) { + throw new ValidationError('Cannot set `recoveryPeriodInDays` while `pointInTimeRecoveryEnabled` is set to false.', this); + } + + if (recoveryPeriodInDays !== undefined && (recoveryPeriodInDays < 1 || recoveryPeriodInDays > 35 )) { + throw new ValidationError('`recoveryPeriodInDays` must be a value between `1` and `35`.', this); + } + } } diff --git a/packages/aws-cdk-lib/aws-dynamodb/lib/table.ts b/packages/aws-cdk-lib/aws-dynamodb/lib/table.ts index 9287fcd026b6b..433ebf0e03347 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/lib/table.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/lib/table.ts @@ -8,7 +8,7 @@ import { ScalableTableAttribute } from './scalable-table-attribute'; import { Operation, OperationsMetricOptions, SystemErrorsForOperationsMetricOptions, Attribute, BillingMode, ProjectionType, ITable, SecondaryIndexProps, TableClass, - LocalSecondaryIndexProps, TableEncryption, StreamViewType, WarmThroughput, + LocalSecondaryIndexProps, TableEncryption, StreamViewType, WarmThroughput, PointInTimeRecoverySpecification, } from './shared'; import * as appscaling from '../../aws-applicationautoscaling'; import * as cloudwatch from '../../aws-cloudwatch'; @@ -21,6 +21,7 @@ import { Aws, CfnCondition, CfnCustomResource, CfnResource, Duration, Fn, Lazy, Names, RemovalPolicy, Stack, Token, CustomResource, } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; const HASH_KEY_TYPE = 'HASH'; const RANGE_KEY_TYPE = 'RANGE'; @@ -286,10 +287,19 @@ export interface TableOptions extends SchemaOptions { /** * Whether point-in-time recovery is enabled. - * @default - point-in-time recovery is disabled + * @deprecated use `pointInTimeRecoverySpecification` instead + * @default false - point in time recovery is not enabled. */ readonly pointInTimeRecovery?: boolean; + /** + * Whether point-in-time recovery is enabled + * and recoveryPeriodInDays is set. + * + * @default - point in time recovery is not enabled. + */ + readonly pointInTimeRecoverySpecification?: PointInTimeRecoverySpecification; + /** * Whether server-side encryption with an AWS managed customer master key is enabled. * @@ -1176,6 +1186,8 @@ export class Table extends TableBase { const { sseSpecification, encryptionKey } = this.parseEncryption(props); + const pointInTimeRecoverySpecification = this.validatePitr(props); + let streamSpecification: CfnTable.StreamSpecificationProperty | undefined; if (props.replicationRegions) { if (props.stream && props.stream !== StreamViewType.NEW_AND_OLD_IMAGES) { @@ -1205,7 +1217,7 @@ export class Table extends TableBase { attributeDefinitions: this.attributeDefinitions, globalSecondaryIndexes: Lazy.any({ produce: () => this.globalSecondaryIndexes }, { omitEmptyArray: true }), localSecondaryIndexes: Lazy.any({ produce: () => this.localSecondaryIndexes }, { omitEmptyArray: true }), - pointInTimeRecoverySpecification: props.pointInTimeRecovery != null ? { pointInTimeRecoveryEnabled: props.pointInTimeRecovery } : undefined, + pointInTimeRecoverySpecification: pointInTimeRecoverySpecification, billingMode: this.billingMode === BillingMode.PAY_PER_REQUEST ? this.billingMode : undefined, provisionedThroughput: this.billingMode === BillingMode.PAY_PER_REQUEST ? undefined : { readCapacityUnits: props.readCapacity || 5, @@ -1520,6 +1532,27 @@ export class Table extends TableBase { nonKeyAttributes.forEach(att => this.nonKeyAttributes.add(att)); } + private validatePitr (props: TableProps): PointInTimeRecoverySpecification | undefined { + if (props.pointInTimeRecoverySpecification !==undefined && props.pointInTimeRecovery !== undefined) { + throw new ValidationError('`pointInTimeRecoverySpecification` and `pointInTimeRecovery` are set. Use `pointInTimeRecoverySpecification` only.', this); + } + + const recoveryPeriodInDays = props.pointInTimeRecoverySpecification?.recoveryPeriodInDays; + + if (!props.pointInTimeRecoverySpecification?.pointInTimeRecoveryEnabled && recoveryPeriodInDays) { + throw new ValidationError('Cannot set `recoveryPeriodInDays` while `pointInTimeRecoveryEnabled` is set to false.', this); + } + + if (recoveryPeriodInDays !== undefined && (recoveryPeriodInDays < 1 || recoveryPeriodInDays > 35 )) { + throw new ValidationError('`recoveryPeriodInDays` must be a value between `1` and `35`.', this); + } + + return props.pointInTimeRecoverySpecification ?? + (props.pointInTimeRecovery !== undefined + ? { pointInTimeRecoveryEnabled: props.pointInTimeRecovery } + : undefined); + } + private buildIndexKeySchema(partitionKey: Attribute, sortKey?: Attribute): CfnTable.KeySchemaProperty[] { this.registerAttribute(partitionKey); const indexKeySchema: CfnTable.KeySchemaProperty[] = [ diff --git a/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts b/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts index 71cb2763033da..1a936794b45c4 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts @@ -172,6 +172,71 @@ describe('default properties', () => { ); }); + test('point-in-time-recovery-specification enabled', () => { + new Table(stack, CONSTRUCT_NAME, { + partitionKey: TABLE_PARTITION_KEY, + sortKey: TABLE_SORT_KEY, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 5, + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', + { + AttributeDefinitions: [ + { AttributeName: 'hashKey', AttributeType: 'S' }, + { AttributeName: 'sortKey', AttributeType: 'N' }, + ], + KeySchema: [ + { AttributeName: 'hashKey', KeyType: 'HASH' }, + { AttributeName: 'sortKey', KeyType: 'RANGE' }, + ], + ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 }, + PointInTimeRecoverySpecification: { + PointInTimeRecoveryEnabled: true, + RecoveryPeriodInDays: 5, + }, + }, + ); + }); + + test('both point-in-time-recovery-specification and point-in-time-recovery set', () => { + expect(() => new Table(stack, CONSTRUCT_NAME, { + partitionKey: TABLE_PARTITION_KEY, + sortKey: TABLE_SORT_KEY, + pointInTimeRecovery: true, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 5, + }, + })).toThrow('`pointInTimeRecoverySpecification` and `pointInTimeRecovery` are set. Use `pointInTimeRecoverySpecification` only.'); + }); + + test('recoveryPeriodInDays set out of bounds', () => { + expect(() => { + new Table(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 36, + }, + }); + }).toThrow('`recoveryPeriodInDays` must be a value between `1` and `35`.'); + }); + + test('recoveryPeriodInDays set but pitr disabled', () => { + expect(() => { + new Table(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: false, + recoveryPeriodInDays: 35, + }, + }); + }).toThrow('Cannot set `recoveryPeriodInDays` while `pointInTimeRecoveryEnabled` is set to false.'); + }); + test('server-side encryption is not enabled', () => { new Table(stack, CONSTRUCT_NAME, { partitionKey: TABLE_PARTITION_KEY, 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 0c68f85ef0f2d..be8454f09ba3f 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 @@ -153,6 +153,75 @@ describe('table', () => { }); }); + test('with point-in-time-recovery-specification', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 4, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::GlobalTable', { + Replicas: [ + { + Region: { + Ref: 'AWS::Region', + }, + PointInTimeRecoverySpecification: { + PointInTimeRecoveryEnabled: true, + RecoveryPeriodInDays: 4, + }, + }, + ], + }); + }); + + test('both point-in-time-recovery-specification and point-in-time-recovery set', () => { + const stack = new Stack(undefined, 'Stack'); + expect(() => { + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + pointInTimeRecovery: true, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 5, + }, + }); + }).toThrow('`pointInTimeRecoverySpecification` and `pointInTimeRecovery` are set. Use `pointInTimeRecoverySpecification` only.'); + }); + + test('recoveryPeriodInDays set out of bounds', () => { + const stack = new Stack(undefined, 'Stack'); + expect(() => { + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true, + recoveryPeriodInDays: 36, + }, + }); + }).toThrow('`recoveryPeriodInDays` must be a value between `1` and `35`.'); + }); + + test('recoveryPeriodInDays set but pitr disabled', () => { + const stack = new Stack(undefined, 'Stack'); + expect(() => { + new TableV2(stack, 'Table', { + partitionKey: { name: 'pk', type: AttributeType.STRING }, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: false, + recoveryPeriodInDays: 35, + }, + }); + }).toThrow('Cannot set `recoveryPeriodInDays` while `pointInTimeRecoveryEnabled` is set to false.'); + }); + test('with STANDARD table class', () => { // GIVEN const stack = new Stack(undefined, 'Stack');