From 1894f2db54eed7d65c9f54cb07cee6dd84200ad7 Mon Sep 17 00:00:00 2001 From: Sebastien Date: Wed, 10 Apr 2024 14:42:23 -0700 Subject: [PATCH] feat(AppSync): addRdsDataSource support for DatabaseCluster (#29544) ### Issue # (if applicable) (aws-appsync): addRdsDataSource doesnt support taking a DatabaseCluster Closes #29302 ### Reason for this change AppSync CDK construct currently accept only IServerlessCluster for RDS source as cluster type. However, with Aurora V2, serverless aurora clusters such as postgres aurora v14 and above are construct using the DatabaseCluster construct and as such AppSync.addRdsDataSource() method need ability to support both type of cluster interfaces. ### Description of changes To support the change I created a second props to support IDatabaseCluster in addition to IServerlessCluster already supported and I overloaded the constructor to support both type of props. However, to make the change possible, some modification to aws-rds were also required: 1 - Need IDatabaseCluster interface to have grantDataApiAccess() method published as part of the interface (the method was there but not published in the interface, when IServerlessCLuster interface have it published. 2 - need DatabaseCluster.grantDataApiAccess() to follow the same IAM permission pattern than ServerlessCluster.grantDataApiAccess() to have consistency. ServerlessCluster.grantDataApiAccess() method is adding automatically the required permission to secret manager if Data API is enabled. However, DatabaseCluster.grantDataApiAccess() do not. As such without the change it will have been required for end users to add that IAM permission to secret manager as an extra line when they will have assigned a serverless V2 cluster to AppSync datasource. To keep the experience unified across the 2 type of RDS clusters, i updated the method to have that IAM permission embedded. ### Description of how you validated changes - Unit test created for the new feature - Unit test updated and validated for RDS changes - Integration test created for the new feature I run all unit test and the integration test successfully. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test/appsync.rds-serverlessv2.graphql | 25 + .../appsync-rds-serverlessV2.assets.json | 32 + .../appsync-rds-serverlessV2.template.json | 997 +++++++++++ .../__entrypoint__.js | 156 ++ .../index.js | 1 + .../cdk.out | 1 + .../integ.json | 12 + .../manifest.json | 353 ++++ ...efaultTestDeployAssert877504EC.assets.json | 19 + ...aultTestDeployAssert877504EC.template.json | 36 + .../tree.json | 1503 +++++++++++++++++ .../test/integ.graphql-rds-serverlessv2.ts | 133 ++ .../cluster-data-api.assets.json | 4 +- .../cluster-data-api.template.json | 20 +- .../manifest.json | 2 +- .../tree.json | 20 +- packages/aws-cdk-lib/aws-appsync/README.md | 70 + .../aws-appsync/lib/data-source.ts | 29 +- .../aws-appsync/lib/graphqlapi-base.ts | 44 +- .../aws-appsync/test/appsync-rds.test.ts | 353 +++- .../aws-cdk-lib/aws-rds/lib/cluster-ref.ts | 7 + packages/aws-cdk-lib/aws-rds/lib/cluster.ts | 21 +- .../aws-cdk-lib/aws-rds/test/cluster.test.ts | 20 +- 23 files changed, 3798 insertions(+), 60 deletions(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.rds-serverlessv2.graphql create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/appsync-rds-serverlessV2.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/appsync-rds-serverlessV2.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/asset.e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da/__entrypoint__.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/asset.e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da/index.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/rdsserverlessV2stackDefaultTestDeployAssert877504EC.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/rdsserverlessV2stackDefaultTestDeployAssert877504EC.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/tree.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.rds-serverlessv2.graphql b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.rds-serverlessv2.graphql new file mode 100644 index 0000000000000..089553dfc913b --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.rds-serverlessv2.graphql @@ -0,0 +1,25 @@ +schema { + query: Query + mutation: Mutation +} + +type Query { + getPost(id:ID!): Post + allPosts: [Post] +} + +type Mutation { + addPost(id: ID!, author: String!, title: String, content: String, url: String): Post! +} + +type Post { + id: ID! + author: String! + title: String + content: String + url: String + ups: Int + downs: Int + relatedPosts: [Post] + relatedPostsMaxBatchSize: [Post] +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/appsync-rds-serverlessV2.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/appsync-rds-serverlessV2.assets.json new file mode 100644 index 0000000000000..bb27531e90795 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/appsync-rds-serverlessV2.assets.json @@ -0,0 +1,32 @@ +{ + "version": "36.0.0", + "files": { + "e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da": { + "source": { + "path": "asset.e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "ca2ed65791b73fc811ec9fde0decd8143d09a6a557ab9cdfe42c12de95191462": { + "source": { + "path": "appsync-rds-serverlessV2.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "ca2ed65791b73fc811ec9fde0decd8143d09a6a557ab9cdfe42c12de95191462.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-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/appsync-rds-serverlessV2.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/appsync-rds-serverlessV2.template.json new file mode 100644 index 0000000000000..c7178aadb3c36 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/appsync-rds-serverlessV2.template.json @@ -0,0 +1,997 @@ +{ + "Resources": { + "IntegVPC2FF1AB0E": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC" + } + ] + } + }, + "IntegVPCPublicSubnet1SubnetE05F7E7D": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1" + } + ], + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "IntegVPCPublicSubnet1RouteTable622895C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1" + } + ], + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "IntegVPCPublicSubnet1RouteTableAssociation0E84800B": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "IntegVPCPublicSubnet1RouteTable622895C7" + }, + "SubnetId": { + "Ref": "IntegVPCPublicSubnet1SubnetE05F7E7D" + } + } + }, + "IntegVPCPublicSubnet1DefaultRouteE885D95E": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "IntegVPCIGW02FC78B6" + }, + "RouteTableId": { + "Ref": "IntegVPCPublicSubnet1RouteTable622895C7" + } + }, + "DependsOn": [ + "IntegVPCVPCGW4DD476C7" + ] + }, + "IntegVPCPublicSubnet1EIP1AC057E9": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1" + } + ] + } + }, + "IntegVPCPublicSubnet1NATGateway380AC0A0": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "IntegVPCPublicSubnet1EIP1AC057E9", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "IntegVPCPublicSubnet1SubnetE05F7E7D" + }, + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1" + } + ] + }, + "DependsOn": [ + "IntegVPCPublicSubnet1DefaultRouteE885D95E", + "IntegVPCPublicSubnet1RouteTableAssociation0E84800B" + ] + }, + "IntegVPCPublicSubnet2Subnet9648DE97": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2" + } + ], + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "IntegVPCPublicSubnet2RouteTableB79B3910": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2" + } + ], + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "IntegVPCPublicSubnet2RouteTableAssociation831EA0CC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "IntegVPCPublicSubnet2RouteTableB79B3910" + }, + "SubnetId": { + "Ref": "IntegVPCPublicSubnet2Subnet9648DE97" + } + } + }, + "IntegVPCPublicSubnet2DefaultRoute2FC4B163": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "IntegVPCIGW02FC78B6" + }, + "RouteTableId": { + "Ref": "IntegVPCPublicSubnet2RouteTableB79B3910" + } + }, + "DependsOn": [ + "IntegVPCVPCGW4DD476C7" + ] + }, + "IntegVPCPublicSubnet2EIPEA07DF99": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2" + } + ] + } + }, + "IntegVPCPublicSubnet2NATGateway912800A3": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "IntegVPCPublicSubnet2EIPEA07DF99", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "IntegVPCPublicSubnet2Subnet9648DE97" + }, + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2" + } + ] + }, + "DependsOn": [ + "IntegVPCPublicSubnet2DefaultRoute2FC4B163", + "IntegVPCPublicSubnet2RouteTableAssociation831EA0CC" + ] + }, + "IntegVPCPrivateSubnet1SubnetD5B61223": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1" + } + ], + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "IntegVPCPrivateSubnet1RouteTableF2678D77": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1" + } + ], + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "IntegVPCPrivateSubnet1RouteTableAssociationAD4B0EBF": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "IntegVPCPrivateSubnet1RouteTableF2678D77" + }, + "SubnetId": { + "Ref": "IntegVPCPrivateSubnet1SubnetD5B61223" + } + } + }, + "IntegVPCPrivateSubnet1DefaultRoute140D7A84": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "IntegVPCPublicSubnet1NATGateway380AC0A0" + }, + "RouteTableId": { + "Ref": "IntegVPCPrivateSubnet1RouteTableF2678D77" + } + } + }, + "IntegVPCPrivateSubnet2SubnetFCC4EF23": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.192.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2" + } + ], + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "IntegVPCPrivateSubnet2RouteTable4132D373": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2" + } + ], + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "IntegVPCPrivateSubnet2RouteTableAssociation9A15DAD6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "IntegVPCPrivateSubnet2RouteTable4132D373" + }, + "SubnetId": { + "Ref": "IntegVPCPrivateSubnet2SubnetFCC4EF23" + } + } + }, + "IntegVPCPrivateSubnet2DefaultRouteAE44E307": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "IntegVPCPublicSubnet2NATGateway912800A3" + }, + "RouteTableId": { + "Ref": "IntegVPCPrivateSubnet2RouteTable4132D373" + } + } + }, + "IntegVPCIGW02FC78B6": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "appsync-rds-serverlessV2/Integ-VPC" + } + ] + } + }, + "IntegVPCVPCGW4DD476C7": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "InternetGatewayId": { + "Ref": "IntegVPCIGW02FC78B6" + }, + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "IntegVPCRestrictDefaultSecurityGroupCustomResource42DF8AB1": { + "Type": "Custom::VpcRestrictDefaultSG", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E", + "Arn" + ] + }, + "DefaultSecurityGroupId": { + "Fn::GetAtt": [ + "IntegVPC2FF1AB0E", + "DefaultSecurityGroup" + ] + }, + "Account": { + "Ref": "AWS::AccountId" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "ec2:RevokeSecurityGroupEgress" + ], + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ec2:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":security-group/", + { + "Fn::GetAtt": [ + "IntegVPC2FF1AB0E", + "DefaultSecurityGroup" + ] + } + ] + ] + } + ] + } + ] + } + } + ] + } + }, + "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0", + "Arn" + ] + }, + "Runtime": "nodejs18.x", + "Description": "Lambda function for removing all inbound/outbound rules from the VPC default security group" + }, + "DependsOn": [ + "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0" + ] + }, + "IntegClusterSubnets629F72ED": { + "Type": "AWS::RDS::DBSubnetGroup", + "Properties": { + "DBSubnetGroupDescription": "Subnets for Integ-Cluster database", + "SubnetIds": [ + { + "Ref": "IntegVPCPrivateSubnet1SubnetD5B61223" + }, + { + "Ref": "IntegVPCPrivateSubnet2SubnetFCC4EF23" + } + ] + } + }, + "IntegClusterSecurityGroupECB0A218": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS security group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "appsyncrdsserverlessV2IntegClusterSecret660F89C53fdaad7efa858a3daf9490cf0a702aeb": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "Description": { + "Fn::Join": [ + "", + [ + "Generated by the CDK for stack: ", + { + "Ref": "AWS::StackName" + } + ] + ] + }, + "GenerateSecretString": { + "ExcludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", + "GenerateStringKey": "password", + "PasswordLength": 30, + "SecretStringTemplate": "{\"username\":\"clusteradmin\"}" + }, + "Name": "integ-secretName" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "IntegClusterSecretAttachmentC627C903": { + "Type": "AWS::SecretsManager::SecretTargetAttachment", + "Properties": { + "SecretId": { + "Ref": "appsyncrdsserverlessV2IntegClusterSecret660F89C53fdaad7efa858a3daf9490cf0a702aeb" + }, + "TargetId": { + "Ref": "IntegCluster4261F36F" + }, + "TargetType": "AWS::RDS::DBCluster" + } + }, + "IntegCluster4261F36F": { + "Type": "AWS::RDS::DBCluster", + "Properties": { + "CopyTagsToSnapshot": true, + "DBClusterParameterGroupName": "default.aurora-postgresql15", + "DBSubnetGroupName": { + "Ref": "IntegClusterSubnets629F72ED" + }, + "DatabaseName": "integdb", + "EnableHttpEndpoint": true, + "Engine": "aurora-postgresql", + "EngineVersion": "15.5", + "MasterUserPassword": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "appsyncrdsserverlessV2IntegClusterSecret660F89C53fdaad7efa858a3daf9490cf0a702aeb" + }, + ":SecretString:password::}}" + ] + ] + }, + "MasterUsername": "clusteradmin", + "Port": 5432, + "ServerlessV2ScalingConfiguration": { + "MaxCapacity": 2, + "MinCapacity": 0.5 + }, + "VpcSecurityGroupIds": [ + { + "Fn::GetAtt": [ + "IntegClusterSecurityGroupECB0A218", + "GroupId" + ] + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "IntegClusterwriter03032C94": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBClusterIdentifier": { + "Ref": "IntegCluster4261F36F" + }, + "DBInstanceClass": "db.serverless", + "Engine": "aurora-postgresql", + "PromotionTier": 0 + }, + "DependsOn": [ + "IntegVPCPrivateSubnet1DefaultRoute140D7A84", + "IntegVPCPrivateSubnet1RouteTableAssociationAD4B0EBF", + "IntegVPCPrivateSubnet2DefaultRouteAE44E307", + "IntegVPCPrivateSubnet2RouteTableAssociation9A15DAD6" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "RdsServerlessV2API9BB3798C": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "AuthenticationType": "API_KEY", + "Name": "RdsServerlessV2API" + } + }, + "RdsServerlessV2APISchema3A85CED2": { + "Type": "AWS::AppSync::GraphQLSchema", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "Definition": "schema {\n query: Query\n mutation: Mutation\n}\n\ntype Query {\n getPost(id:ID!): Post\n allPosts: [Post]\n}\n\ntype Mutation {\n addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!\n}\n\ntype Post {\n id: ID!\n author: String!\n title: String\n content: String\n url: String\n ups: Int\n downs: Int\n relatedPosts: [Post]\n relatedPostsMaxBatchSize: [Post]\n}" + } + }, + "RdsServerlessV2APIDefaultApiKeyF5675D80": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + } + }, + "DependsOn": [ + "RdsServerlessV2APISchema3A85CED2" + ] + }, + "RdsServerlessV2APIdsServiceRole31B5B0D1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "RdsServerlessV2APIdsServiceRoleDefaultPolicy387D3F05": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":secretsmanager:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":secret:integ-secretName-??????" + ] + ] + }, + { + "Ref": "IntegClusterSecretAttachmentC627C903" + } + ] + }, + { + "Action": [ + "rds-data:BatchExecuteStatement", + "rds-data:BeginTransaction", + "rds-data:CommitTransaction", + "rds-data:ExecuteStatement", + "rds-data:RollbackTransaction" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":cluster:", + { + "Ref": "IntegCluster4261F36F" + } + ] + ] + } + }, + { + "Action": [ + "rds-data:DeleteItems", + "rds-data:ExecuteSql", + "rds-data:GetItems", + "rds-data:InsertItems", + "rds-data:UpdateItems" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":cluster:", + { + "Ref": "IntegCluster4261F36F" + }, + ":*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":cluster:", + { + "Ref": "IntegCluster4261F36F" + } + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "RdsServerlessV2APIdsServiceRoleDefaultPolicy387D3F05", + "Roles": [ + { + "Ref": "RdsServerlessV2APIdsServiceRole31B5B0D1" + } + ] + } + }, + "RdsServerlessV2APIds151E4AA6": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "Name": "ds", + "RelationalDatabaseConfig": { + "RdsHttpEndpointConfig": { + "AwsRegion": { + "Ref": "AWS::Region" + }, + "AwsSecretStoreArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":secretsmanager:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":secret:integ-secretName" + ] + ] + }, + "DatabaseName": "integdb", + "DbClusterIdentifier": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":cluster:", + { + "Ref": "IntegCluster4261F36F" + } + ] + ] + } + }, + "RelationalDatabaseSourceType": "RDS_HTTP_ENDPOINT" + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "RdsServerlessV2APIdsServiceRole31B5B0D1", + "Arn" + ] + }, + "Type": "RELATIONAL_DATABASE" + } + }, + "RdsServerlessV2APIQueryGetallPostsResolver65741DC0": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "DataSourceName": "ds", + "FieldName": "allPosts", + "Kind": "UNIT", + "RequestMappingTemplate": "{\n \"version\": \"2018-05-29\",\n \"statements\": [\n \"SELECT * FROM integdb\"\n ]\n }", + "ResponseMappingTemplate": "\n #if($ctx.error)\n $utils.error($ctx.error.message, $ctx.error.type)\n #end\n $utils.toJson($utils.rds.toJsonObject($ctx.result)[0])", + "TypeName": "Query" + }, + "DependsOn": [ + "RdsServerlessV2APIds151E4AA6", + "RdsServerlessV2APISchema3A85CED2" + ] + }, + "RdsServerlessV2APIQueryGetPostResolver2FD6B46A": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "DataSourceName": "ds", + "FieldName": "getPost", + "Kind": "UNIT", + "RequestMappingTemplate": "{\n \"version\": \"2018-05-29\",\n \"statements\": [\n \"SELECT * FROM integdb WHERE id = :id\"\n ]\n }", + "ResponseMappingTemplate": "\n #if($ctx.error)\n $utils.error($ctx.error.message, $ctx.error.type)\n #end\n $utils.toJson($utils.rds.toJsonObject($ctx.result)[0])", + "TypeName": "Query" + }, + "DependsOn": [ + "RdsServerlessV2APIds151E4AA6", + "RdsServerlessV2APISchema3A85CED2" + ] + }, + "RdsServerlessV2APIMutationAddPostResolverA1BDAED2": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "DataSourceName": "ds", + "FieldName": "addPost", + "Kind": "UNIT", + "RequestMappingTemplate": "\n {\n \"version\": \"2018-05-29\",\n \"statements\": [\n \"INSERT INTO integdb VALUES (:id, :author, :title, :content, :url )\",\n \"SELECT * WHERE id = :id\"\n ],\n \"variableMap\": {\n \":id\": $util.toJson($util.autoId()),\n \":author\": $util.toJson($ctx.args.author)\n \":title\": $util.toJson($ctx.args.title)\n \":content\": $util.toJson($ctx.args.content)\n \":url\": $util.toJson($ctx.args.url)\n }\n }", + "ResponseMappingTemplate": "\n #if($ctx.error)\n $utils.error($ctx.error.message, $ctx.error.type)\n #end\n $utils.toJson($utils.rds.toJsonObject($ctx.result)[0])", + "TypeName": "Mutation" + }, + "DependsOn": [ + "RdsServerlessV2APIds151E4AA6", + "RdsServerlessV2APISchema3A85CED2" + ] + } + }, + "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-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/asset.e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da/__entrypoint__.js b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/asset.e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da/__entrypoint__.js new file mode 100644 index 0000000000000..9271364bb7e49 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/asset.e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da/__entrypoint__.js @@ -0,0 +1,156 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + const parsedUrl = url.parse(event.ResponseURL); + const loggingSafeUrl = `${parsedUrl.protocol}//${parsedUrl.hostname}/${parsedUrl.pathname}?***`; + exports.external.log('submit response to cloudformation', loggingSafeUrl, json); + const responseBody = JSON.stringify(json); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { + 'content-type': '', + 'content-length': Buffer.byteLength(responseBody, 'utf8'), + }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, requestBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, (response) => { + response.resume(); // Consume the response but don't care about it + if (!response.statusCode || response.statusCode >= 400) { + reject(new Error(`Unsuccessful HTTP response: ${response.statusCode}`)); + } + else { + resolve(); + } + }); + request.on('error', reject); + request.write(requestBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFMUQsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBTSxFQUFFO1FBQ2YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbkRELDBCQW1EQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRztJQUU3QyxzRUFBc0U7SUFDdEUsdUJBQXVCO0lBQ3ZCLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDLGtCQUFrQixJQUFJLFVBQVUsQ0FBQyxrQkFBa0IsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7SUFDekUsTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksTUFBTTtRQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSwwQkFBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixNQUFNLFNBQVMsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUMvQyxNQUFNLGNBQWMsR0FBRyxHQUFHLFNBQVMsQ0FBQyxRQUFRLEtBQUssU0FBUyxDQUFDLFFBQVEsSUFBSSxTQUFTLENBQUMsUUFBUSxNQUFNLENBQUM7SUFDaEcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsbUNBQW1DLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBRXhFLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDMUMsTUFBTSxHQUFHLEdBQUc7UUFDVixRQUFRLEVBQUUsU0FBUyxDQUFDLFFBQVE7UUFDNUIsSUFBSSxFQUFFLFNBQVMsQ0FBQyxJQUFJO1FBQ3BCLE1BQU0sRUFBRSxLQUFLO1FBQ2IsT0FBTyxFQUFFO1lBQ1AsY0FBYyxFQUFFLEVBQUU7WUFDbEIsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDLFVBQVUsQ0FBQyxZQUFZLEVBQUUsTUFBTSxDQUFDO1NBQzFEO0tBQ0YsQ0FBQztJQUVGLE1BQU0sWUFBWSxHQUFHO1FBQ25CLFFBQVEsRUFBRSxDQUFDO1FBQ1gsS0FBSyxFQUFFLElBQUk7S0FDWixDQUFDO0lBQ0YsTUFBTSxXQUFXLENBQUMsWUFBWSxFQUFFLGdCQUFRLENBQUMsZUFBZSxDQUFDLENBQUMsR0FBRyxFQUFFLFlBQVksQ0FBQyxDQUFDO0FBQy9FLENBQUM7QUFFRCxLQUFLLFVBQVUsc0JBQXNCLENBQUMsT0FBNkIsRUFBRSxXQUFtQjtJQUN0RixPQUFPLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1FBQzNDLElBQUk7WUFDRixNQUFNLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDLFFBQVEsRUFBRSxFQUFFO2dCQUNsRCxRQUFRLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQywrQ0FBK0M7Z0JBQ2xFLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxJQUFJLFFBQVEsQ0FBQyxVQUFVLElBQUksR0FBRyxFQUFFO29CQUN0RCxNQUFNLENBQUMsSUFBSSxLQUFLLENBQUMsK0JBQStCLFFBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUM7aUJBQ3pFO3FCQUFNO29CQUNMLE9BQU8sRUFBRSxDQUFDO2lCQUNYO1lBQ0gsQ0FBQyxDQUFDLENBQUM7WUFDSCxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO1lBQzNCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUNmO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDWDtJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVcsRUFBRSxHQUFHLE1BQWE7SUFDL0Msc0NBQXNDO0lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUM7QUFDOUIsQ0FBQztBQVNELFNBQWdCLFdBQVcsQ0FBMEIsT0FBcUIsRUFBRSxFQUE0QjtJQUN0RyxPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUssRUFBRSxFQUFFO1FBQ3hCLElBQUksUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDaEMsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUN2QixPQUFPLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsT0FBTyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ3hCO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUU7b0JBQ25CLE1BQU0sQ0FBQyxDQUFDO2lCQUNUO2dCQUNELE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDVDtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQWhCRCxrQ0FnQkM7QUFFRCxLQUFLLFVBQVUsS0FBSyxDQUFDLEVBQVU7SUFDN0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgKiBhcyB1cmwgZnJvbSAndXJsJztcblxuLy8gZm9yIHVuaXQgdGVzdHNcbmV4cG9ydCBjb25zdCBleHRlcm5hbCA9IHtcbiAgc2VuZEh0dHBSZXF1ZXN0OiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0LFxuICBsb2c6IGRlZmF1bHRMb2csXG4gIGluY2x1ZGVTdGFja1RyYWNlczogdHJ1ZSxcbiAgdXNlckhhbmRsZXJJbmRleDogJy4vaW5kZXgnLFxufTtcblxuY29uc3QgQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpDUkVBVEVfRkFJTEVEJztcbmNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCB0eXBlIFJlc3BvbnNlID0gQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIEhhbmRsZXJSZXNwb25zZTtcbmV4cG9ydCB0eXBlIEhhbmRsZXIgPSAoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50LCBjb250ZXh0OiBBV1NMYW1iZGEuQ29udGV4dCkge1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9O1xuICBleHRlcm5hbC5sb2coSlNPTi5zdHJpbmdpZnkoc2FuaXRpemVkRXZlbnQsIHVuZGVmaW5lZCwgMikpO1xuXG4gIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gIC8vIGluZGljYXRlcyB0aGF0IHRoaXMgREVMRVRFIGlzIGEgc3Vic2VxdWVudCBERUxFVEUgdG8gYSBmYWlsZWQgQ1JFQVRFXG4gIC8vIG9wZXJhdGlvbi5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgZXh0ZXJuYWwubG9nKCdpZ25vcmluZyBERUxFVEUgZXZlbnQgY2F1c2VkIGJ5IGEgZmFpbGVkIENSRUFURSBldmVudCcpO1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgZXZlbnQpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgLy8gaW52b2tlIHRoZSB1c2VyIGhhbmRsZXIuIHRoaXMgaXMgaW50ZW50aW9uYWxseSBpbnNpZGUgdGhlIHRyeS1jYXRjaCB0b1xuICAgIC8vIGVuc3VyZSB0aGF0IGlmIHRoZXJlIGlzIGFuIGVycm9yIGl0J3MgcmVwb3J0ZWQgYXMgYSBmYWlsdXJlIHRvXG4gICAgLy8gY2xvdWRmb3JtYXRpb24gKG90aGVyd2lzZSBjZm4gd2FpdHMpLlxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgY29uc3QgdXNlckhhbmRsZXI6IEhhbmRsZXIgPSByZXF1aXJlKGV4dGVybmFsLnVzZXJIYW5kbGVySW5kZXgpLmhhbmRsZXI7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXNlckhhbmRsZXIoc2FuaXRpemVkRXZlbnQsIGNvbnRleHQpO1xuXG4gICAgLy8gdmFsaWRhdGUgdXNlciByZXNwb25zZSBhbmQgY3JlYXRlIHRoZSBjb21iaW5lZCBldmVudFxuICAgIGNvbnN0IHJlc3BvbnNlRXZlbnQgPSByZW5kZXJSZXNwb25zZShldmVudCwgcmVzdWx0KTtcblxuICAgIC8vIHN1Ym1pdCB0byBjZm4gYXMgc3VjY2Vzc1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgcmVzcG9uc2VFdmVudCk7XG4gIH0gY2F0Y2ggKGU6IGFueSkge1xuICAgIGNvbnN0IHJlc3A6IFJlc3BvbnNlID0ge1xuICAgICAgLi4uZXZlbnQsXG4gICAgICBSZWFzb246IGV4dGVybmFsLmluY2x1ZGVTdGFja1RyYWNlcyA/IGUuc3RhY2sgOiBlLm1lc3NhZ2UsXG4gICAgfTtcblxuICAgIGlmICghcmVzcC5QaHlzaWNhbFJlc291cmNlSWQpIHtcbiAgICAgIC8vIHNwZWNpYWwgY2FzZTogaWYgQ1JFQVRFIGZhaWxzLCB3aGljaCB1c3VhbGx5IGltcGxpZXMsIHdlIHVzdWFsbHkgZG9uJ3RcbiAgICAgIC8vIGhhdmUgYSBwaHlzaWNhbCByZXNvdXJjZSBpZC4gaW4gdGhpcyBjYXNlLCB0aGUgc3Vic2VxdWVudCBERUxFVEVcbiAgICAgIC8vIG9wZXJhdGlvbiBkb2VzIG5vdCBoYXZlIGFueSBtZWFuaW5nLCBhbmQgd2lsbCBsaWtlbHkgZmFpbCBhcyB3ZWxsLiB0b1xuICAgICAgLy8gYWRkcmVzcyB0aGlzLCB3ZSB1c2UgYSBtYXJrZXIgc28gdGhlIHByb3ZpZGVyIGZyYW1ld29yayBjYW4gc2ltcGx5XG4gICAgICAvLyBpZ25vcmUgdGhlIHN1YnNlcXVlbnQgREVMRVRFLlxuICAgICAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnQ3JlYXRlJykge1xuICAgICAgICBleHRlcm5hbC5sb2coJ0NSRUFURSBmYWlsZWQsIHJlc3BvbmRpbmcgd2l0aCBhIG1hcmtlciBwaHlzaWNhbCByZXNvdXJjZSBpZCBzbyB0aGF0IHRoZSBzdWJzZXF1ZW50IERFTEVURSB3aWxsIGJlIGlnbm9yZWQnKTtcbiAgICAgICAgcmVzcC5QaHlzaWNhbFJlc291cmNlSWQgPSBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUjtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIG90aGVyd2lzZSwgaWYgUGh5c2ljYWxSZXNvdXJjZUlkIGlzIG5vdCBzcGVjaWZpZWQsIHNvbWV0aGluZyBpc1xuICAgICAgICAvLyB0ZXJyaWJseSB3cm9uZyBiZWNhdXNlIGFsbCBvdGhlciBldmVudHMgc2hvdWxkIGhhdmUgYW4gSUQuXG4gICAgICAgIGV4dGVybmFsLmxvZyhgRVJST1I6IE1hbGZvcm1lZCBldmVudC4gXCJQaHlzaWNhbFJlc291cmNlSWRcIiBpcyByZXF1aXJlZDogJHtKU09OLnN0cmluZ2lmeShldmVudCl9YCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gdGhpcyBpcyBhbiBhY3R1YWwgZXJyb3IsIGZhaWwgdGhlIGFjdGl2aXR5IGFsdG9nZXRoZXIgYW5kIGV4aXN0LlxuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdGQUlMRUQnLCByZXNwKTtcbiAgfVxufVxuXG5mdW5jdGlvbiByZW5kZXJSZXNwb25zZShcbiAgY2ZuUmVxdWVzdDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIHsgUGh5c2ljYWxSZXNvdXJjZUlkPzogc3RyaW5nIH0sXG4gIGhhbmRsZXJSZXNwb25zZTogdm9pZCB8IEhhbmRsZXJSZXNwb25zZSA9IHsgfSk6IFJlc3BvbnNlIHtcblxuICAvLyBpZiBwaHlzaWNhbCBJRCBpcyBub3QgcmV0dXJuZWQsIHdlIGhhdmUgc29tZSBkZWZhdWx0cyBmb3IgeW91IGJhc2VkXG4gIC8vIG9uIHRoZSByZXF1ZXN0IHR5cGUuXG4gIGNvbnN0IHBoeXNpY2FsUmVzb3VyY2VJZCA9IGhhbmRsZXJSZXNwb25zZS5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5SZXF1ZXN0SWQ7XG5cbiAgLy8gaWYgd2UgYXJlIGluIERFTEVURSBhbmQgcGh5c2ljYWwgSUQgd2FzIGNoYW5nZWQsIGl0J3MgYW4gZXJyb3IuXG4gIGlmIChjZm5SZXF1ZXN0LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBwaHlzaWNhbFJlc291cmNlSWQgIT09IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBERUxFVEU6IGNhbm5vdCBjaGFuZ2UgdGhlIHBoeXNpY2FsIHJlc291cmNlIElEIGZyb20gXCIke2NmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIHRvIFwiJHtoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIGR1cmluZyBkZWxldGlvbmApO1xuICB9XG5cbiAgLy8gbWVyZ2UgcmVxdWVzdCBldmVudCBhbmQgcmVzdWx0IGV2ZW50IChyZXN1bHQgcHJldmFpbHMpLlxuICByZXR1cm4ge1xuICAgIC4uLmNmblJlcXVlc3QsXG4gICAgLi4uaGFuZGxlclJlc3BvbnNlLFxuICAgIFBoeXNpY2FsUmVzb3VyY2VJZDogcGh5c2ljYWxSZXNvdXJjZUlkLFxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzdWJtaXRSZXNwb25zZShzdGF0dXM6ICdTVUNDRVNTJyB8ICdGQUlMRUQnLCBldmVudDogUmVzcG9uc2UpIHtcbiAgY29uc3QganNvbjogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VSZXNwb25zZSA9IHtcbiAgICBTdGF0dXM6IHN0YXR1cyxcbiAgICBSZWFzb246IGV2ZW50LlJlYXNvbiA/PyBzdGF0dXMsXG4gICAgU3RhY2tJZDogZXZlbnQuU3RhY2tJZCxcbiAgICBSZXF1ZXN0SWQ6IGV2ZW50LlJlcXVlc3RJZCxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCB8fCBNSVNTSU5HX1BIWVNJQ0FMX0lEX01BUktFUixcbiAgICBMb2dpY2FsUmVzb3VyY2VJZDogZXZlbnQuTG9naWNhbFJlc291cmNlSWQsXG4gICAgTm9FY2hvOiBldmVudC5Ob0VjaG8sXG4gICAgRGF0YTogZXZlbnQuRGF0YSxcbiAgfTtcblxuICBjb25zdCBwYXJzZWRVcmwgPSB1cmwucGFyc2UoZXZlbnQuUmVzcG9uc2VVUkwpO1xuICBjb25zdCBsb2dnaW5nU2FmZVVybCA9IGAke3BhcnNlZFVybC5wcm90b2NvbH0vLyR7cGFyc2VkVXJsLmhvc3RuYW1lfS8ke3BhcnNlZFVybC5wYXRobmFtZX0/KioqYDtcbiAgZXh0ZXJuYWwubG9nKCdzdWJtaXQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBsb2dnaW5nU2FmZVVybCwganNvbik7XG5cbiAgY29uc3QgcmVzcG9uc2VCb2R5ID0gSlNPTi5zdHJpbmdpZnkoanNvbik7XG4gIGNvbnN0IHJlcSA9IHtcbiAgICBob3N0bmFtZTogcGFyc2VkVXJsLmhvc3RuYW1lLFxuICAgIHBhdGg6IHBhcnNlZFVybC5wYXRoLFxuICAgIG1ldGhvZDogJ1BVVCcsXG4gICAgaGVhZGVyczoge1xuICAgICAgJ2NvbnRlbnQtdHlwZSc6ICcnLFxuICAgICAgJ2NvbnRlbnQtbGVuZ3RoJzogQnVmZmVyLmJ5dGVMZW5ndGgocmVzcG9uc2VCb2R5LCAndXRmOCcpLFxuICAgIH0sXG4gIH07XG5cbiAgY29uc3QgcmV0cnlPcHRpb25zID0ge1xuICAgIGF0dGVtcHRzOiA1LFxuICAgIHNsZWVwOiAxMDAwLFxuICB9O1xuICBhd2FpdCB3aXRoUmV0cmllcyhyZXRyeU9wdGlvbnMsIGV4dGVybmFsLnNlbmRIdHRwUmVxdWVzdCkocmVxLCByZXNwb25zZUJvZHkpO1xufVxuXG5hc3luYyBmdW5jdGlvbiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0KG9wdGlvbnM6IGh0dHBzLlJlcXVlc3RPcHRpb25zLCByZXF1ZXN0Qm9keTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZTx2b2lkPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlcXVlc3QgPSBodHRwcy5yZXF1ZXN0KG9wdGlvbnMsIChyZXNwb25zZSkgPT4ge1xuICAgICAgICByZXNwb25zZS5yZXN1bWUoKTsgLy8gQ29uc3VtZSB0aGUgcmVzcG9uc2UgYnV0IGRvbid0IGNhcmUgYWJvdXQgaXRcbiAgICAgICAgaWYgKCFyZXNwb25zZS5zdGF0dXNDb2RlIHx8IHJlc3BvbnNlLnN0YXR1c0NvZGUgPj0gNDAwKSB7XG4gICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgVW5zdWNjZXNzZnVsIEhUVFAgcmVzcG9uc2U6ICR7cmVzcG9uc2Uuc3RhdHVzQ29kZX1gKSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICAgIHJlcXVlc3Qub24oJ2Vycm9yJywgcmVqZWN0KTtcbiAgICAgIHJlcXVlc3Qud3JpdGUocmVxdWVzdEJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/asset.e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da/index.js b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/asset.e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da/index.js new file mode 100644 index 0000000000000..013bcaffd8fe5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/asset.e978ad4ad0dca7e1c6be5f49cbbd1c5a150ee050c24052fedfe5a42f835d55da/index.js @@ -0,0 +1 @@ +"use strict";var I=Object.create;var t=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var g=Object.getPrototypeOf,l=Object.prototype.hasOwnProperty;var G=(r,e)=>{for(var o in e)t(r,o,{get:e[o],enumerable:!0})},n=(r,e,o,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of P(e))!l.call(r,s)&&s!==o&&t(r,s,{get:()=>e[s],enumerable:!(i=y(e,s))||i.enumerable});return r};var R=(r,e,o)=>(o=r!=null?I(g(r)):{},n(e||!r||!r.__esModule?t(o,"default",{value:r,enumerable:!0}):o,r)),S=r=>n(t({},"__esModule",{value:!0}),r);var k={};G(k,{handler:()=>f});module.exports=S(k);var a=R(require("@aws-sdk/client-ec2")),u=new a.EC2({});function c(r,e){return{GroupId:r,IpPermissions:[{UserIdGroupPairs:[{GroupId:r,UserId:e}],IpProtocol:"-1"}]}}function d(r){return{GroupId:r,IpPermissions:[{IpRanges:[{CidrIp:"0.0.0.0/0"}],IpProtocol:"-1"}]}}async function f(r){let e=r.ResourceProperties.DefaultSecurityGroupId,o=r.ResourceProperties.Account;switch(r.RequestType){case"Create":return p(e,o);case"Update":return h(r);case"Delete":return m(e,o)}}async function h(r){let e=r.OldResourceProperties.DefaultSecurityGroupId,o=r.ResourceProperties.DefaultSecurityGroupId;e!==o&&(await m(e,r.ResourceProperties.Account),await p(o,r.ResourceProperties.Account))}async function p(r,e){try{await u.revokeSecurityGroupEgress(d(r))}catch(o){if(o.name!=="InvalidPermission.NotFound")throw o}try{await u.revokeSecurityGroupIngress(c(r,e))}catch(o){if(o.name!=="InvalidPermission.NotFound")throw o}}async function m(r,e){await u.authorizeSecurityGroupIngress(c(r,e)),await u.authorizeSecurityGroupEgress(d(r))}0&&(module.exports={handler}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/integ.json new file mode 100644 index 0000000000000..fa75e5fd8f225 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.0", + "testCases": { + "rds-serverlessV2-stack/DefaultTest": { + "stacks": [ + "appsync-rds-serverlessV2" + ], + "assertionStack": "rds-serverlessV2-stack/DefaultTest/DeployAssert", + "assertionStackName": "rdsserverlessV2stackDefaultTestDeployAssert877504EC" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/manifest.json new file mode 100644 index 0000000000000..8a5023bc2ec18 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/manifest.json @@ -0,0 +1,353 @@ +{ + "version": "36.0.0", + "artifacts": { + "appsync-rds-serverlessV2.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "appsync-rds-serverlessV2.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "appsync-rds-serverlessV2": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "appsync-rds-serverlessV2.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}/ca2ed65791b73fc811ec9fde0decd8143d09a6a557ab9cdfe42c12de95191462.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "appsync-rds-serverlessV2.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": [ + "appsync-rds-serverlessV2.assets" + ], + "metadata": { + "/appsync-rds-serverlessV2/Integ-VPC/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPC2FF1AB0E" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet1SubnetE05F7E7D" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet1RouteTable622895C7" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet1RouteTableAssociation0E84800B" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet1DefaultRouteE885D95E" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet1EIP1AC057E9" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet1NATGateway380AC0A0" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet2Subnet9648DE97" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet2RouteTableB79B3910" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet2RouteTableAssociation831EA0CC" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet2DefaultRoute2FC4B163" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet2EIPEA07DF99" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPublicSubnet2NATGateway912800A3" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPrivateSubnet1SubnetD5B61223" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPrivateSubnet1RouteTableF2678D77" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPrivateSubnet1RouteTableAssociationAD4B0EBF" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPrivateSubnet1DefaultRoute140D7A84" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPrivateSubnet2SubnetFCC4EF23" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPrivateSubnet2RouteTable4132D373" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPrivateSubnet2RouteTableAssociation9A15DAD6" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCPrivateSubnet2DefaultRouteAE44E307" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCIGW02FC78B6" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCVPCGW4DD476C7" + } + ], + "/appsync-rds-serverlessV2/Integ-VPC/RestrictDefaultSecurityGroupCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegVPCRestrictDefaultSecurityGroupCustomResource42DF8AB1" + } + ], + "/appsync-rds-serverlessV2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0" + } + ], + "/appsync-rds-serverlessV2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E" + } + ], + "/appsync-rds-serverlessV2/Integ-Cluster/Subnets/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegClusterSubnets629F72ED" + } + ], + "/appsync-rds-serverlessV2/Integ-Cluster/SecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegClusterSecurityGroupECB0A218" + } + ], + "/appsync-rds-serverlessV2/Integ-Cluster/Secret/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "appsyncrdsserverlessV2IntegClusterSecret660F89C53fdaad7efa858a3daf9490cf0a702aeb" + } + ], + "/appsync-rds-serverlessV2/Integ-Cluster/Secret/Attachment/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegClusterSecretAttachmentC627C903" + } + ], + "/appsync-rds-serverlessV2/Integ-Cluster/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegCluster4261F36F" + } + ], + "/appsync-rds-serverlessV2/Integ-Cluster/writer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegClusterwriter03032C94" + } + ], + "/appsync-rds-serverlessV2/RdsServerlessV2API/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RdsServerlessV2API9BB3798C" + } + ], + "/appsync-rds-serverlessV2/RdsServerlessV2API/Schema": [ + { + "type": "aws:cdk:logicalId", + "data": "RdsServerlessV2APISchema3A85CED2" + } + ], + "/appsync-rds-serverlessV2/RdsServerlessV2API/DefaultApiKey": [ + { + "type": "aws:cdk:logicalId", + "data": "RdsServerlessV2APIDefaultApiKeyF5675D80" + } + ], + "/appsync-rds-serverlessV2/RdsServerlessV2API/ds/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RdsServerlessV2APIdsServiceRole31B5B0D1" + } + ], + "/appsync-rds-serverlessV2/RdsServerlessV2API/ds/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RdsServerlessV2APIdsServiceRoleDefaultPolicy387D3F05" + } + ], + "/appsync-rds-serverlessV2/RdsServerlessV2API/ds/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RdsServerlessV2APIds151E4AA6" + } + ], + "/appsync-rds-serverlessV2/RdsServerlessV2API/QueryGetallPostsResolver/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RdsServerlessV2APIQueryGetallPostsResolver65741DC0" + } + ], + "/appsync-rds-serverlessV2/RdsServerlessV2API/QueryGetPostResolver/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RdsServerlessV2APIQueryGetPostResolver2FD6B46A" + } + ], + "/appsync-rds-serverlessV2/RdsServerlessV2API/MutationAddPostResolver/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RdsServerlessV2APIMutationAddPostResolverA1BDAED2" + } + ], + "/appsync-rds-serverlessV2/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/appsync-rds-serverlessV2/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "appsync-rds-serverlessV2" + }, + "rdsserverlessV2stackDefaultTestDeployAssert877504EC.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "rdsserverlessV2stackDefaultTestDeployAssert877504EC.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "rdsserverlessV2stackDefaultTestDeployAssert877504EC": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "rdsserverlessV2stackDefaultTestDeployAssert877504EC.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": [ + "rdsserverlessV2stackDefaultTestDeployAssert877504EC.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": [ + "rdsserverlessV2stackDefaultTestDeployAssert877504EC.assets" + ], + "metadata": { + "/rds-serverlessV2-stack/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/rds-serverlessV2-stack/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "rds-serverlessV2-stack/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-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/rdsserverlessV2stackDefaultTestDeployAssert877504EC.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/rdsserverlessV2stackDefaultTestDeployAssert877504EC.assets.json new file mode 100644 index 0000000000000..98339491a717f --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/rdsserverlessV2stackDefaultTestDeployAssert877504EC.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "rdsserverlessV2stackDefaultTestDeployAssert877504EC.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-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/rdsserverlessV2stackDefaultTestDeployAssert877504EC.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/rdsserverlessV2stackDefaultTestDeployAssert877504EC.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/rdsserverlessV2stackDefaultTestDeployAssert877504EC.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-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/tree.json new file mode 100644 index 0000000000000..f8cff48d9ebef --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.js.snapshot/tree.json @@ -0,0 +1,1503 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "appsync-rds-serverlessV2": { + "id": "appsync-rds-serverlessV2", + "path": "appsync-rds-serverlessV2", + "children": { + "Integ-VPC": { + "id": "Integ-VPC", + "path": "appsync-rds-serverlessV2/Integ-VPC", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/Integ-VPC/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPC", + "aws:cdk:cloudformation:props": { + "cidrBlock": "10.0.0.0/16", + "enableDnsHostnames": true, + "enableDnsSupport": true, + "instanceTenancy": "default", + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnVPC", + "version": "0.0.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1" + } + ], + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1" + } + ], + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "IntegVPCPublicSubnet1RouteTable622895C7" + }, + "subnetId": { + "Ref": "IntegVPCPublicSubnet1SubnetE05F7E7D" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "IntegVPCIGW02FC78B6" + }, + "routeTableId": { + "Ref": "IntegVPCPublicSubnet1RouteTable622895C7" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "allocationId": { + "Fn::GetAtt": [ + "IntegVPCPublicSubnet1EIP1AC057E9", + "AllocationId" + ] + }, + "subnetId": { + "Ref": "IntegVPCPublicSubnet1SubnetE05F7E7D" + }, + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PublicSubnet2": { + "id": "PublicSubnet2", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.64.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2" + } + ], + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/Acl", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2" + } + ], + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "IntegVPCPublicSubnet2RouteTableB79B3910" + }, + "subnetId": { + "Ref": "IntegVPCPublicSubnet2Subnet9648DE97" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "IntegVPCIGW02FC78B6" + }, + "routeTableId": { + "Ref": "IntegVPCPublicSubnet2RouteTableB79B3910" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "allocationId": { + "Fn::GetAtt": [ + "IntegVPCPublicSubnet2EIPEA07DF99", + "AllocationId" + ] + }, + "subnetId": { + "Ref": "IntegVPCPublicSubnet2Subnet9648DE97" + }, + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet1": { + "id": "PrivateSubnet1", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1" + } + ], + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1/Acl", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1" + } + ], + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "IntegVPCPrivateSubnet1RouteTableF2678D77" + }, + "subnetId": { + "Ref": "IntegVPCPrivateSubnet1SubnetD5B61223" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "IntegVPCPublicSubnet1NATGateway380AC0A0" + }, + "routeTableId": { + "Ref": "IntegVPCPrivateSubnet1RouteTableF2678D77" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet2": { + "id": "PrivateSubnet2", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.192.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2" + } + ], + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2/Acl", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2" + } + ], + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "IntegVPCPrivateSubnet2RouteTable4132D373" + }, + "subnetId": { + "Ref": "IntegVPCPrivateSubnet2SubnetFCC4EF23" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "appsync-rds-serverlessV2/Integ-VPC/PrivateSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "IntegVPCPublicSubnet2NATGateway912800A3" + }, + "routeTableId": { + "Ref": "IntegVPCPrivateSubnet2RouteTable4132D373" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "IGW": { + "id": "IGW", + "path": "appsync-rds-serverlessV2/Integ-VPC/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "appsync-rds-serverlessV2/Integ-VPC" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnInternetGateway", + "version": "0.0.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "appsync-rds-serverlessV2/Integ-VPC/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "internetGatewayId": { + "Ref": "IntegVPCIGW02FC78B6" + }, + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnVPCGatewayAttachment", + "version": "0.0.0" + } + }, + "RestrictDefaultSecurityGroupCustomResource": { + "id": "RestrictDefaultSecurityGroupCustomResource", + "path": "appsync-rds-serverlessV2/Integ-VPC/RestrictDefaultSecurityGroupCustomResource", + "children": { + "Default": { + "id": "Default", + "path": "appsync-rds-serverlessV2/Integ-VPC/RestrictDefaultSecurityGroupCustomResource/Default", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.Vpc", + "version": "0.0.0" + } + }, + "Custom::VpcRestrictDefaultSGCustomResourceProvider": { + "id": "Custom::VpcRestrictDefaultSGCustomResourceProvider", + "path": "appsync-rds-serverlessV2/Custom::VpcRestrictDefaultSGCustomResourceProvider", + "children": { + "Staging": { + "id": "Staging", + "path": "appsync-rds-serverlessV2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Staging", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "appsync-rds-serverlessV2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "appsync-rds-serverlessV2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.CustomResourceProviderBase", + "version": "0.0.0" + } + }, + "Integ-Cluster": { + "id": "Integ-Cluster", + "path": "appsync-rds-serverlessV2/Integ-Cluster", + "children": { + "Subnets": { + "id": "Subnets", + "path": "appsync-rds-serverlessV2/Integ-Cluster/Subnets", + "children": { + "Default": { + "id": "Default", + "path": "appsync-rds-serverlessV2/Integ-Cluster/Subnets/Default", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::RDS::DBSubnetGroup", + "aws:cdk:cloudformation:props": { + "dbSubnetGroupDescription": "Subnets for Integ-Cluster database", + "subnetIds": [ + { + "Ref": "IntegVPCPrivateSubnet1SubnetD5B61223" + }, + { + "Ref": "IntegVPCPrivateSubnet2SubnetFCC4EF23" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_rds.CfnDBSubnetGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_rds.SubnetGroup", + "version": "0.0.0" + } + }, + "SecurityGroup": { + "id": "SecurityGroup", + "path": "appsync-rds-serverlessV2/Integ-Cluster/SecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/Integ-Cluster/SecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "RDS security group", + "securityGroupEgress": [ + { + "cidrIp": "0.0.0.0/0", + "description": "Allow all outbound traffic by default", + "ipProtocol": "-1" + } + ], + "vpcId": { + "Ref": "IntegVPC2FF1AB0E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSecurityGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.SecurityGroup", + "version": "0.0.0" + } + }, + "AuroraPostgreSqlDatabaseClusterEngineDefaultParameterGroup": { + "id": "AuroraPostgreSqlDatabaseClusterEngineDefaultParameterGroup", + "path": "appsync-rds-serverlessV2/Integ-Cluster/AuroraPostgreSqlDatabaseClusterEngineDefaultParameterGroup", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Secret": { + "id": "Secret", + "path": "appsync-rds-serverlessV2/Integ-Cluster/Secret", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/Integ-Cluster/Secret/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SecretsManager::Secret", + "aws:cdk:cloudformation:props": { + "description": { + "Fn::Join": [ + "", + [ + "Generated by the CDK for stack: ", + { + "Ref": "AWS::StackName" + } + ] + ] + }, + "generateSecretString": { + "passwordLength": 30, + "secretStringTemplate": "{\"username\":\"clusteradmin\"}", + "generateStringKey": "password", + "excludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\" + }, + "name": "integ-secretName" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_secretsmanager.CfnSecret", + "version": "0.0.0" + } + }, + "Attachment": { + "id": "Attachment", + "path": "appsync-rds-serverlessV2/Integ-Cluster/Secret/Attachment", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/Integ-Cluster/Secret/Attachment/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SecretsManager::SecretTargetAttachment", + "aws:cdk:cloudformation:props": { + "secretId": { + "Ref": "appsyncrdsserverlessV2IntegClusterSecret660F89C53fdaad7efa858a3daf9490cf0a702aeb" + }, + "targetId": { + "Ref": "IntegCluster4261F36F" + }, + "targetType": "AWS::RDS::DBCluster" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_secretsmanager.CfnSecretTargetAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_secretsmanager.SecretTargetAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_rds.DatabaseSecret", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/Integ-Cluster/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::RDS::DBCluster", + "aws:cdk:cloudformation:props": { + "copyTagsToSnapshot": true, + "databaseName": "integdb", + "dbClusterParameterGroupName": "default.aurora-postgresql15", + "dbSubnetGroupName": { + "Ref": "IntegClusterSubnets629F72ED" + }, + "enableHttpEndpoint": true, + "engine": "aurora-postgresql", + "engineVersion": "15.5", + "masterUsername": "clusteradmin", + "masterUserPassword": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "appsyncrdsserverlessV2IntegClusterSecret660F89C53fdaad7efa858a3daf9490cf0a702aeb" + }, + ":SecretString:password::}}" + ] + ] + }, + "port": 5432, + "serverlessV2ScalingConfiguration": { + "minCapacity": 0.5, + "maxCapacity": 2 + }, + "vpcSecurityGroupIds": [ + { + "Fn::GetAtt": [ + "IntegClusterSecurityGroupECB0A218", + "GroupId" + ] + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_rds.CfnDBCluster", + "version": "0.0.0" + } + }, + "writer": { + "id": "writer", + "path": "appsync-rds-serverlessV2/Integ-Cluster/writer", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/Integ-Cluster/writer/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::RDS::DBInstance", + "aws:cdk:cloudformation:props": { + "dbClusterIdentifier": { + "Ref": "IntegCluster4261F36F" + }, + "dbInstanceClass": "db.serverless", + "engine": "aurora-postgresql", + "promotionTier": 0 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_rds.CfnDBInstance", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_rds.DatabaseCluster", + "version": "0.0.0" + } + }, + "Secret": { + "id": "Secret", + "path": "appsync-rds-serverlessV2/Secret", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "RdsServerlessV2API": { + "id": "RdsServerlessV2API", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::GraphQLApi", + "aws:cdk:cloudformation:props": { + "authenticationType": "API_KEY", + "name": "RdsServerlessV2API" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnGraphQLApi", + "version": "0.0.0" + } + }, + "Schema": { + "id": "Schema", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/Schema", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::GraphQLSchema", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "definition": "schema {\n query: Query\n mutation: Mutation\n}\n\ntype Query {\n getPost(id:ID!): Post\n allPosts: [Post]\n}\n\ntype Mutation {\n addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!\n}\n\ntype Post {\n id: ID!\n author: String!\n title: String\n content: String\n url: String\n ups: Int\n downs: Int\n relatedPosts: [Post]\n relatedPostsMaxBatchSize: [Post]\n}" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnGraphQLSchema", + "version": "0.0.0" + } + }, + "DefaultApiKey": { + "id": "DefaultApiKey", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/DefaultApiKey", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::ApiKey", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnApiKey", + "version": "0.0.0" + } + }, + "LogGroup": { + "id": "LogGroup", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/LogGroup", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "ds": { + "id": "ds", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/ds", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/ds/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/ds/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/ds/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/ds/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/ds/ServiceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":secretsmanager:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":secret:integ-secretName-??????" + ] + ] + }, + { + "Ref": "IntegClusterSecretAttachmentC627C903" + } + ] + }, + { + "Action": [ + "rds-data:BatchExecuteStatement", + "rds-data:BeginTransaction", + "rds-data:CommitTransaction", + "rds-data:ExecuteStatement", + "rds-data:RollbackTransaction" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":cluster:", + { + "Ref": "IntegCluster4261F36F" + } + ] + ] + } + }, + { + "Action": [ + "rds-data:DeleteItems", + "rds-data:ExecuteSql", + "rds-data:GetItems", + "rds-data:InsertItems", + "rds-data:UpdateItems" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":cluster:", + { + "Ref": "IntegCluster4261F36F" + }, + ":*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":cluster:", + { + "Ref": "IntegCluster4261F36F" + } + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "RdsServerlessV2APIdsServiceRoleDefaultPolicy387D3F05", + "roles": [ + { + "Ref": "RdsServerlessV2APIdsServiceRole31B5B0D1" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/ds/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::DataSource", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "name": "ds", + "relationalDatabaseConfig": { + "rdsHttpEndpointConfig": { + "awsRegion": { + "Ref": "AWS::Region" + }, + "dbClusterIdentifier": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":cluster:", + { + "Ref": "IntegCluster4261F36F" + } + ] + ] + }, + "awsSecretStoreArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":secretsmanager:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":secret:integ-secretName" + ] + ] + }, + "databaseName": "integdb" + }, + "relationalDatabaseSourceType": "RDS_HTTP_ENDPOINT" + }, + "serviceRoleArn": { + "Fn::GetAtt": [ + "RdsServerlessV2APIdsServiceRole31B5B0D1", + "Arn" + ] + }, + "type": "RELATIONAL_DATABASE" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnDataSource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.RdsDataSource", + "version": "0.0.0" + } + }, + "QueryGetallPostsResolver": { + "id": "QueryGetallPostsResolver", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/QueryGetallPostsResolver", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/QueryGetallPostsResolver/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::Resolver", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "dataSourceName": "ds", + "fieldName": "allPosts", + "kind": "UNIT", + "requestMappingTemplate": "{\n \"version\": \"2018-05-29\",\n \"statements\": [\n \"SELECT * FROM integdb\"\n ]\n }", + "responseMappingTemplate": "\n #if($ctx.error)\n $utils.error($ctx.error.message, $ctx.error.type)\n #end\n $utils.toJson($utils.rds.toJsonObject($ctx.result)[0])", + "typeName": "Query" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnResolver", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.Resolver", + "version": "0.0.0" + } + }, + "QueryGetPostResolver": { + "id": "QueryGetPostResolver", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/QueryGetPostResolver", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/QueryGetPostResolver/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::Resolver", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "dataSourceName": "ds", + "fieldName": "getPost", + "kind": "UNIT", + "requestMappingTemplate": "{\n \"version\": \"2018-05-29\",\n \"statements\": [\n \"SELECT * FROM integdb WHERE id = :id\"\n ]\n }", + "responseMappingTemplate": "\n #if($ctx.error)\n $utils.error($ctx.error.message, $ctx.error.type)\n #end\n $utils.toJson($utils.rds.toJsonObject($ctx.result)[0])", + "typeName": "Query" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnResolver", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.Resolver", + "version": "0.0.0" + } + }, + "MutationAddPostResolver": { + "id": "MutationAddPostResolver", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/MutationAddPostResolver", + "children": { + "Resource": { + "id": "Resource", + "path": "appsync-rds-serverlessV2/RdsServerlessV2API/MutationAddPostResolver/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::Resolver", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "RdsServerlessV2API9BB3798C", + "ApiId" + ] + }, + "dataSourceName": "ds", + "fieldName": "addPost", + "kind": "UNIT", + "requestMappingTemplate": "\n {\n \"version\": \"2018-05-29\",\n \"statements\": [\n \"INSERT INTO integdb VALUES (:id, :author, :title, :content, :url )\",\n \"SELECT * WHERE id = :id\"\n ],\n \"variableMap\": {\n \":id\": $util.toJson($util.autoId()),\n \":author\": $util.toJson($ctx.args.author)\n \":title\": $util.toJson($ctx.args.title)\n \":content\": $util.toJson($ctx.args.content)\n \":url\": $util.toJson($ctx.args.url)\n }\n }", + "responseMappingTemplate": "\n #if($ctx.error)\n $utils.error($ctx.error.message, $ctx.error.type)\n #end\n $utils.toJson($utils.rds.toJsonObject($ctx.result)[0])", + "typeName": "Mutation" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnResolver", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.Resolver", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.GraphqlApi", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "appsync-rds-serverlessV2/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "appsync-rds-serverlessV2/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "rds-serverlessV2-stack": { + "id": "rds-serverlessV2-stack", + "path": "rds-serverlessV2-stack", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "rds-serverlessV2-stack/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "rds-serverlessV2-stack/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "rds-serverlessV2-stack/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "rds-serverlessV2-stack/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "rds-serverlessV2-stack/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.3.0" + } + } + }, + "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-appsync/test/integ.graphql-rds-serverlessv2.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.ts new file mode 100644 index 0000000000000..c7d2ec66c788c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.graphql-rds-serverlessv2.ts @@ -0,0 +1,133 @@ +/// !cdk-integ * + +import * as path from 'path'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as cdk from 'aws-cdk-lib'; +import * as appsync from 'aws-cdk-lib/aws-appsync'; +import * as secretmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Vpc } from 'aws-cdk-lib/aws-ec2'; +import { Construct } from 'constructs'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +/* + * Creates an Appsync GraphQL API and schema with Aurora Serverless V2 as datasource + * + * - build RDS Aurora Serverless V2 construct + * - Build AppSync API + * - Add RDS as Datasource + * - Create query and mutation for RDS table + */ + +class TestStack extends cdk.Stack { + constructor(scope: Construct) { + super(scope, 'appsync-rds-serverlessV2'); + + const vpc = new Vpc(this, 'Integ-VPC'); + + const credentialsBaseOptions: rds.CredentialsBaseOptions = { + secretName: 'integ-secretName', + }; + + const cluster = new rds.DatabaseCluster(this, 'Integ-Cluster', { + engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_15_5 }), + writer: rds.ClusterInstance.serverlessV2('writer'), + removalPolicy: cdk.RemovalPolicy.DESTROY, + vpc, + credentials: rds.Credentials.fromGeneratedSecret('clusteradmin', credentialsBaseOptions), + defaultDatabaseName: 'integdb', + }); + + const secret = secretmanager.Secret.fromSecretNameV2(this, 'Secret', 'integ-secretName'); + + const api = new appsync.GraphqlApi(this, 'RdsServerlessV2API', { + name: 'RdsServerlessV2API', + schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'appsync.rds-serverlessv2.graphql')), + }); + + const serverlessV2DS = api.addRdsDataSourceV2('ds', cluster, secret, 'integdb'); + + const queryAllPostReqTemplate: string = `{ + "version": "2018-05-29", + "statements": [ + "SELECT * FROM integdb" + ] + }`; + + const queryAllPostsResolver: appsync.BaseResolverProps = { + typeName: 'Query', + fieldName: 'allPosts', + requestMappingTemplate: + appsync.MappingTemplate.fromString(queryAllPostReqTemplate), + responseMappingTemplate: appsync.MappingTemplate.fromString(` + #if($ctx.error) + $utils.error($ctx.error.message, $ctx.error.type) + #end + $utils.toJson($utils.rds.toJsonObject($ctx.result)[0])`, + ), + }; + + serverlessV2DS.createResolver('QueryGetallPostsResolver', queryAllPostsResolver); + + const queryPostReqTemplate: string = `{ + "version": "2018-05-29", + "statements": [ + "SELECT * FROM integdb WHERE id = :id" + ] + }`; + + const queryPostResolver: appsync.BaseResolverProps = { + typeName: 'Query', + fieldName: 'getPost', + requestMappingTemplate: + appsync.MappingTemplate.fromString(queryPostReqTemplate), + responseMappingTemplate: appsync.MappingTemplate.fromString(` + #if($ctx.error) + $utils.error($ctx.error.message, $ctx.error.type) + #end + $utils.toJson($utils.rds.toJsonObject($ctx.result)[0])`, + ), + }; + + serverlessV2DS.createResolver('QueryGetPostResolver', queryPostResolver); + + const mutationAddPostReqTemplate: string = ` + { + "version": "2018-05-29", + "statements": [ + "INSERT INTO integdb VALUES (:id, :author, :title, :content, :url )", + "SELECT * WHERE id = :id" + ], + "variableMap": { + ":id": $util.toJson($util.autoId()), + ":author": $util.toJson($ctx.args.author) + ":title": $util.toJson($ctx.args.title) + ":content": $util.toJson($ctx.args.content) + ":url": $util.toJson($ctx.args.url) + } + }`; + + const mutationAddPostResolver: appsync.BaseResolverProps = { + typeName: 'Mutation', + fieldName: 'addPost', + requestMappingTemplate: + appsync.MappingTemplate.fromString(mutationAddPostReqTemplate), + responseMappingTemplate: appsync.MappingTemplate.fromString(` + #if($ctx.error) + $utils.error($ctx.error.message, $ctx.error.type) + #end + $utils.toJson($utils.rds.toJsonObject($ctx.result)[0])`, + ), + }; + + serverlessV2DS.createResolver('MutationAddPostResolver', mutationAddPostResolver); + } +} + +const app = new cdk.App(); +const testCase = new TestStack(app); + +new IntegTest(app, 'rds-serverlessV2-stack', { + testCases: [testCase], +}); + +app.synth(); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/cluster-data-api.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/cluster-data-api.assets.json index b800063a0def3..798b6899d21d7 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/cluster-data-api.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/cluster-data-api.assets.json @@ -14,7 +14,7 @@ } } }, - "b2fb40833cc33946a3656aa5e2cdb19e4259b9a84dbabe7a6559ad319344359d": { + "7b01f2c8700d8548b7d912d20f8aabafa935805d43b94300b50ef08b267a9da6": { "source": { "path": "cluster-data-api.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "b2fb40833cc33946a3656aa5e2cdb19e4259b9a84dbabe7a6559ad319344359d.json", + "objectKey": "7b01f2c8700d8548b7d912d20f8aabafa935805d43b94300b50ef08b267a9da6.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/cluster-data-api.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/cluster-data-api.template.json index 76ea729cef991..d42d0a52a6498 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/cluster-data-api.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/cluster-data-api.template.json @@ -543,16 +543,6 @@ "Properties": { "PolicyDocument": { "Statement": [ - { - "Action": [ - "secretsmanager:DescribeSecret", - "secretsmanager:GetSecretValue" - ], - "Effect": "Allow", - "Resource": { - "Ref": "DatabaseSecretAttachmentE5D1B020" - } - }, { "Action": [ "rds-data:BatchExecuteStatement", @@ -585,6 +575,16 @@ ] ] } + }, + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": { + "Ref": "DatabaseSecretAttachmentE5D1B020" + } } ], "Version": "2012-10-17" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/manifest.json index 873f1cd7cb6c8..28fbad8e6e9c7 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "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}/b2fb40833cc33946a3656aa5e2cdb19e4259b9a84dbabe7a6559ad319344359d.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/7b01f2c8700d8548b7d912d20f8aabafa935805d43b94300b50ef08b267a9da6.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/tree.json index 5b1165f6b9b53..9447b1ea76ef0 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-data-api.js.snapshot/tree.json @@ -770,16 +770,6 @@ "aws:cdk:cloudformation:props": { "policyDocument": { "Statement": [ - { - "Action": [ - "secretsmanager:DescribeSecret", - "secretsmanager:GetSecretValue" - ], - "Effect": "Allow", - "Resource": { - "Ref": "DatabaseSecretAttachmentE5D1B020" - } - }, { "Action": [ "rds-data:BatchExecuteStatement", @@ -812,6 +802,16 @@ ] ] } + }, + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": { + "Ref": "DatabaseSecretAttachmentE5D1B020" + } } ], "Version": "2012-10-17" diff --git a/packages/aws-cdk-lib/aws-appsync/README.md b/packages/aws-cdk-lib/aws-appsync/README.md index c001eb67b121a..dfb8fca7ce144 100644 --- a/packages/aws-cdk-lib/aws-appsync/README.md +++ b/packages/aws-cdk-lib/aws-appsync/README.md @@ -94,6 +94,8 @@ AppSync provides a data source for executing SQL commands against Amazon Aurora Serverless clusters. You can use AppSync resolvers to execute SQL statements against the Data API with GraphQL queries, mutations, and subscriptions. +#### Aurora Serverless V1 Cluster + ```ts // Create username and password secret for DB Cluster const secret = new rds.DatabaseSecret(this, 'AuroraSecret', { @@ -156,6 +158,74 @@ rdsDS.createResolver('MutationAddDemoRdsResolver', { }); ``` +#### Aurora Serverless V2 Cluster + +```ts +// Create username and password secret for DB Cluster +const secret = new rds.DatabaseSecret(this, 'AuroraSecret', { + username: 'clusteradmin', +}); + +// The VPC to place the cluster in +const vpc = new ec2.Vpc(this, 'AuroraVpc'); + +// Create the serverless cluster, provide all values needed to customise the database. +const cluster = new rds.DatabaseCluster(this, 'AuroraClusterV2', { + engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_15_5 }), + credentials: { username: 'clusteradmin' }, + clusterIdentifier: 'db-endpoint-test', + writer: rds.ClusterInstance.serverlessV2('writer'), + serverlessV2MinCapacity: 2, + serverlessV2MaxCapacity: 10, + vpc, + defaultDatabaseName: 'demos', + enableDataApi: true, // has to be set to true to enable Data API as not enable by default + }); + +// Build a data source for AppSync to access the database. +declare const api: appsync.GraphqlApi; +const rdsDS = api.addRdsDataSourceV2('rds', cluster, secret, 'demos'); + +// Set up a resolver for an RDS query. +rdsDS.createResolver('QueryGetDemosRdsResolver', { + typeName: 'Query', + fieldName: 'getDemosRds', + requestMappingTemplate: appsync.MappingTemplate.fromString(` + { + "version": "2018-05-29", + "statements": [ + "SELECT * FROM demos" + ] + } + `), + responseMappingTemplate: appsync.MappingTemplate.fromString(` + $utils.toJson($utils.rds.toJsonObject($ctx.result)[0]) + `), +}); + +// Set up a resolver for an RDS mutation. +rdsDS.createResolver('MutationAddDemoRdsResolver', { + typeName: 'Mutation', + fieldName: 'addDemoRds', + requestMappingTemplate: appsync.MappingTemplate.fromString(` + { + "version": "2018-05-29", + "statements": [ + "INSERT INTO demos VALUES (:id, :version)", + "SELECT * WHERE id = :id" + ], + "variableMap": { + ":id": $util.toJson($util.autoId()), + ":version": $util.toJson($ctx.args.version) + } + } + `), + responseMappingTemplate: appsync.MappingTemplate.fromString(` + $utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0]) + `), +}); +``` + ### HTTP Endpoints GraphQL schema file `schema.graphql`: diff --git a/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts b/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts index 4c229f09d1a8e..44c6583268ac0 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts @@ -9,7 +9,7 @@ import { IEventBus } from '../../aws-events'; import { Grant, IGrantable, IPrincipal, IRole, Role, ServicePrincipal } from '../../aws-iam'; import { IFunction } from '../../aws-lambda'; import { IDomain as IOpenSearchDomain } from '../../aws-opensearchservice'; -import { IServerlessCluster } from '../../aws-rds'; +import { IServerlessCluster, IDatabaseCluster } from '../../aws-rds'; import { ISecret } from '../../aws-secretsmanager'; import { IResolvable, Lazy, Stack, Token } from '../../core'; @@ -340,7 +340,7 @@ export class LambdaDataSource extends BackedDataSource { } /** - * Properties for an AppSync RDS datasource + * Properties for an AppSync RDS datasource Aurora Serverless V1 */ export interface RdsDataSourceProps extends BackedDataSourceProps { /** @@ -359,11 +359,32 @@ export interface RdsDataSourceProps extends BackedDataSourceProps { readonly databaseName?: string; } +/** + * Properties for an AppSync RDS datasource Aurora Serverless V2 + */ +export interface RdsDataSourcePropsV2 extends BackedDataSourceProps { + /** + * The serverless cluster to call to interact with this data source + */ + readonly serverlessCluster: IDatabaseCluster; + /** + * The secret containing the credentials for the database + */ + readonly secretStore: ISecret; + /** + * The name of the database to use within the cluster + * + * @default - None + */ + readonly databaseName?: string; +} + /** * An AppSync datasource backed by RDS */ export class RdsDataSource extends BackedDataSource { - constructor(scope: Construct, id: string, props: RdsDataSourceProps) { + constructor(scope: Construct, id: string, props: RdsDataSourceProps) + constructor(scope: Construct, id: string, props: RdsDataSourcePropsV2) { super(scope, id, props, { type: 'RELATIONAL_DATABASE', relationalDatabaseConfig: { @@ -383,6 +404,7 @@ export class RdsDataSource extends BackedDataSource { relationalDatabaseSourceType: 'RDS_HTTP_ENDPOINT', }, }); + const clusterArn = Stack.of(this).formatArn({ service: 'rds', resource: `cluster:${props.serverlessCluster.clusterIdentifier}`, @@ -390,7 +412,6 @@ export class RdsDataSource extends BackedDataSource { props.secretStore.grantRead(this); // Change to grant with RDS grant becomes implemented - props.serverlessCluster.grantDataApiAccess(this); Grant.addToPrincipal({ diff --git a/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts b/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts index a75657f80a1a8..7f91d270c93ec 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts @@ -16,7 +16,7 @@ import { IEventBus } from '../../aws-events'; import { Grant, IGrantable } from '../../aws-iam'; import { IFunction } from '../../aws-lambda'; import { IDomain as IOpenSearchDomain } from '../../aws-opensearchservice'; -import { IServerlessCluster } from '../../aws-rds'; +import { IDatabaseCluster, IServerlessCluster } from '../../aws-rds'; import { ISecret } from '../../aws-secretsmanager'; import { ArnFormat, CfnResource, IResource, Resource, Stack } from '../../core'; @@ -193,6 +193,23 @@ export interface IGraphqlApi extends IResource { options?: DataSourceOptions ): RdsDataSource; + /** + * add a new Rds Serverless V2 data source to this API + * + * @param id The data source's id + * @param serverlessCluster The serverless V2 cluster to interact with this data source + * @param secretStore The secret store that contains the username and password for the serverless cluster + * @param databaseName The optional name of the database to use within the cluster + * @param options The optional configuration for this data source + */ + addRdsDataSourceV2( + id: string, + serverlessCluster: IDatabaseCluster, + secretStore: ISecret, + databaseName?: string, + options?: DataSourceOptions + ): RdsDataSource; + /** * add a new elasticsearch data source to this API * @@ -367,6 +384,31 @@ export abstract class GraphqlApiBase extends Resource implements IGraphqlApi { }); } + /** + * add a new Rds data source to this API + * @param id The data source's id + * @param serverlessCluster The serverless V2 cluster to interact with this data source + * @param secretStore The secret store that contains the username and password for the serverless cluster + * @param databaseName The optional name of the database to use within the cluster + * @param options The optional configuration for this data source + */ + public addRdsDataSourceV2( + id: string, + serverlessCluster: IDatabaseCluster, + secretStore: ISecret, + databaseName?: string, + options?: DataSourceOptions, + ): RdsDataSource { + return new RdsDataSource(this, id, { + api: this, + name: options?.name, + description: options?.description, + serverlessCluster, + secretStore, + databaseName, + }); + } + /** * add a new elasticsearch data source to this API * diff --git a/packages/aws-cdk-lib/aws-appsync/test/appsync-rds.test.ts b/packages/aws-cdk-lib/aws-appsync/test/appsync-rds.test.ts index fcb400a3208d9..0f8e3a1790a04 100644 --- a/packages/aws-cdk-lib/aws-appsync/test/appsync-rds.test.ts +++ b/packages/aws-cdk-lib/aws-appsync/test/appsync-rds.test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { Template } from '../../assertions'; import { Vpc, SecurityGroup, SubnetType } from '../../aws-ec2'; -import { DatabaseSecret, DatabaseClusterEngine, AuroraMysqlEngineVersion, ServerlessCluster } from '../../aws-rds'; +import { DatabaseSecret, DatabaseClusterEngine, AuroraMysqlEngineVersion, ServerlessCluster, DatabaseCluster, ClusterInstance, AuroraPostgresEngineVersion } from '../../aws-rds'; import * as cdk from '../../core'; import * as appsync from '../lib'; @@ -21,7 +21,8 @@ beforeEach(() => { describe('Rds Data Source configuration', () => { // GIVEN let secret: DatabaseSecret; - let cluster: ServerlessCluster; + let serverlessCluster: ServerlessCluster; + beforeEach(() => { const vpc = new Vpc(stack, 'Vpc', { maxAzs: 2 }); const securityGroup = new SecurityGroup(stack, 'AuroraSecurityGroup', { @@ -31,7 +32,7 @@ describe('Rds Data Source configuration', () => { secret = new DatabaseSecret(stack, 'AuroraSecret', { username: 'clusteradmin', }); - cluster = new ServerlessCluster(stack, 'AuroraCluster', { + serverlessCluster = new ServerlessCluster(stack, 'AuroraCluster', { engine: DatabaseClusterEngine.auroraMysql({ version: AuroraMysqlEngineVersion.VER_2_07_1 }), credentials: { username: 'clusteradmin' }, clusterIdentifier: 'db-endpoint-test', @@ -44,7 +45,7 @@ describe('Rds Data Source configuration', () => { test('appsync creates correct policy', () => { // WHEN - api.addRdsDataSource('ds', cluster, secret); + api.addRdsDataSource('ds', serverlessCluster, secret); // THEN Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { @@ -112,9 +113,33 @@ describe('Rds Data Source configuration', () => { }); }); + test('create new RdsDataSource with Aurora Serverless V1 cluster', () => { + // WHEN + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + + new appsync.RdsDataSource(stack, 'RdsDataSourceAuroraV1', { + serverlessCluster: serverlessCluster, + secretStore: secret, + databaseName: 'Animals', + api: importedApi, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { + EnableHttpEndpoint: true, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + Name: 'RdsDataSourceAuroraV1', + }); + }); + test('rds cluster arn saved to RdsHttpEndpointConfig', () => { // WHEN - api.addRdsDataSource('ds', cluster, secret); + api.addRdsDataSource('ds', serverlessCluster, secret); // THEN Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { @@ -141,7 +166,7 @@ describe('Rds Data Source configuration', () => { test('databaseName saved to RdsHttpEndpointConfig', () => { // WHEN const testDatabaseName = 'testDatabaseName'; - api.addRdsDataSource('ds', cluster, secret, testDatabaseName); + api.addRdsDataSource('ds', serverlessCluster, secret, testDatabaseName); // THEN Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { @@ -168,7 +193,7 @@ describe('Rds Data Source configuration', () => { test('default configuration produces name identical to the id', () => { // WHEN - api.addRdsDataSource('ds', cluster, secret); + api.addRdsDataSource('ds', serverlessCluster, secret); // THEN Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { @@ -179,7 +204,7 @@ describe('Rds Data Source configuration', () => { test('appsync configures name correctly', () => { // WHEN - api.addRdsDataSource('ds', cluster, secret, undefined, { + api.addRdsDataSource('ds', serverlessCluster, secret, undefined, { name: 'custom', }); @@ -192,7 +217,7 @@ describe('Rds Data Source configuration', () => { test('appsync configures name and description correctly', () => { // WHEN - api.addRdsDataSource('ds', cluster, secret, undefined, { + api.addRdsDataSource('ds', serverlessCluster, secret, undefined, { name: 'custom', description: 'custom description', }); @@ -208,8 +233,8 @@ describe('Rds Data Source configuration', () => { test('appsync errors when creating multiple rds data sources with no configuration', () => { // WHEN const when = () => { - api.addRdsDataSource('ds', cluster, secret); - api.addRdsDataSource('ds', cluster, secret); + api.addRdsDataSource('ds', serverlessCluster, secret); + api.addRdsDataSource('ds', serverlessCluster, secret); }; // THEN @@ -217,10 +242,248 @@ describe('Rds Data Source configuration', () => { }); }); +describe('Rds Data Source Serverless V2 configuration', () => { + // GIVEN + let secret: DatabaseSecret; + let serverlessClusterV2: DatabaseCluster; + + beforeEach(() => { + const vpc = new Vpc(stack, 'Vpc', { maxAzs: 2 }); + const securityGroup = new SecurityGroup(stack, 'AuroraSecurityGroup', { + vpc, + allowAllOutbound: true, + }); + secret = new DatabaseSecret(stack, 'AuroraSecret', { + username: 'clusteradmin', + }); + serverlessClusterV2 = new DatabaseCluster(stack, 'AuroraClusterV2', { + engine: DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.VER_15_5 }), + credentials: { username: 'clusteradmin' }, + clusterIdentifier: 'db-endpoint-test', + writer: ClusterInstance.serverlessV2('writer'), + serverlessV2MinCapacity: 0.5, + serverlessV2MaxCapacity: 1, + vpc, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, + securityGroups: [securityGroup], + defaultDatabaseName: 'Animals', + enableDataApi: true, + }); + }); + + test('appsync creates correct policy ServerlessV2', () => { + // WHEN + api.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { Ref: 'AuroraSecret41E6E877' }, + }, + { + Action: [ + 'rds-data:BatchExecuteStatement', + 'rds-data:BeginTransaction', + 'rds-data:CommitTransaction', + 'rds-data:ExecuteStatement', + 'rds-data:RollbackTransaction', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, + ':rds:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':cluster:', + { Ref: 'AuroraClusterV2A232B19B' }]], + }, + }, + { + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { Ref: 'AuroraClusterV2SecretAttachmentA83795D8' }, + }, + { + Action: [ + 'rds-data:DeleteItems', + 'rds-data:ExecuteSql', + 'rds-data:GetItems', + 'rds-data:InsertItems', + 'rds-data:UpdateItems', + ], + Effect: 'Allow', + Resource: [{ + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, + ':rds:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':cluster:', + { Ref: 'AuroraClusterV2A232B19B' }]], + }, + { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, + ':rds:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':cluster:', + { Ref: 'AuroraClusterV2A232B19B' }, + ':*']], + }], + }], + }, + }); + }); + + test('create new RdsDataSource with Aurora Serverless V2 cluster', () => { + // WHEN + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + + new appsync.RdsDataSource(stack, 'RdsDataSourceAuroraV2', { + serverlessCluster: serverlessClusterV2, + secretStore: secret, + databaseName: 'Animals', + api: importedApi, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { + EnableHttpEndpoint: true, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + Name: 'RdsDataSourceAuroraV2', + }); + }); + + test('rds cluster arn saved to RdsHttpEndpointConfig serverlessV2', () => { + // WHEN + api.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + RelationalDatabaseConfig: { + RdsHttpEndpointConfig: { + AwsRegion: { Ref: 'AWS::Region' }, + AwsSecretStoreArn: { Ref: 'AuroraSecret41E6E877' }, + DbClusterIdentifier: { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, + ':rds:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':cluster:', + { Ref: 'AuroraClusterV2A232B19B' }]], + }, + }, + }, + }); + }); + + test('databaseName saved to RdsHttpEndpointConfig serverlessV2', () => { + // WHEN + const testDatabaseName = 'testDatabaseName'; + api.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret, testDatabaseName); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + RelationalDatabaseConfig: { + RdsHttpEndpointConfig: { + AwsRegion: { Ref: 'AWS::Region' }, + AwsSecretStoreArn: { Ref: 'AuroraSecret41E6E877' }, + DbClusterIdentifier: { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, + ':rds:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':cluster:', + { Ref: 'AuroraClusterV2A232B19B' }]], + }, + DatabaseName: testDatabaseName, + }, + }, + }); + }); + + test('default configuration produces name identical to the id serverlessV2', () => { + // WHEN + api.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + Name: 'dsV2', + }); + }); + + test('appsync configures name correctly serverlessV2', () => { + // WHEN + api.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret, undefined, { + name: 'custom', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly ServerlessV2', () => { + // WHEN + api.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret, undefined, { + name: 'custom', + description: 'custom description', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple rds data sources with no configuration ServerlessV2', () => { + // WHEN + const when = () => { + api.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret); + api.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret); + }; + + // THEN + expect(when).toThrow('There is already a Construct with name \'dsV2\' in GraphqlApi [baseApi]'); + }); +}); + describe('adding rds data source from imported api', () => { // GIVEN let secret: DatabaseSecret; - let cluster: ServerlessCluster; + let serverlessCluster: ServerlessCluster; + beforeEach(() => { const vpc = new Vpc(stack, 'Vpc', { maxAzs: 2 }); const securityGroup = new SecurityGroup(stack, 'AuroraSecurityGroup', { @@ -230,7 +493,7 @@ describe('adding rds data source from imported api', () => { secret = new DatabaseSecret(stack, 'AuroraSecret', { username: 'clusteradmin', }); - cluster = new ServerlessCluster(stack, 'AuroraCluster', { + serverlessCluster = new ServerlessCluster(stack, 'AuroraCluster', { engine: DatabaseClusterEngine.auroraMysql({ version: AuroraMysqlEngineVersion.VER_2_07_1 }), credentials: { username: 'clusteradmin' }, clusterIdentifier: 'db-endpoint-test', @@ -246,7 +509,7 @@ describe('adding rds data source from imported api', () => { const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, }); - importedApi.addRdsDataSource('ds', cluster, secret); + importedApi.addRdsDataSource('ds', serverlessCluster, secret); // THEN Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { @@ -261,7 +524,67 @@ describe('adding rds data source from imported api', () => { graphqlApiId: api.apiId, graphqlApiArn: api.arn, }); - importedApi.addRdsDataSource('ds', cluster, secret); + importedApi.addRdsDataSource('ds', serverlessCluster, secret); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + ApiId: { 'Fn::GetAtt': ['baseApiCDA4D43A', 'ApiId'] }, + }); + }); +}); + +describe('adding rds data source Serverless V2 from imported api', () => { + // GIVEN + let secret: DatabaseSecret; + let serverlessClusterV2: DatabaseCluster; + + beforeEach(() => { + const vpc = new Vpc(stack, 'Vpc', { maxAzs: 2 }); + const securityGroup = new SecurityGroup(stack, 'AuroraSecurityGroup', { + vpc, + allowAllOutbound: true, + }); + secret = new DatabaseSecret(stack, 'AuroraSecret', { + username: 'clusteradmin', + }); + + serverlessClusterV2 = new DatabaseCluster(stack, 'AuroraClusterV2', { + engine: DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.VER_15_5 }), + credentials: { username: 'clusteradmin' }, + clusterIdentifier: 'db-endpoint-test', + writer: ClusterInstance.serverlessV2('writer'), + serverlessV2MinCapacity: 0.5, + serverlessV2MaxCapacity: 1, + vpc, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, + securityGroups: [securityGroup], + defaultDatabaseName: 'Animals', + enableDataApi: true, + }); + }); + + test('imported api can add RdsDbDataSource V2 from id', () => { + // WHEN + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + ApiId: { 'Fn::GetAtt': ['baseApiCDA4D43A', 'ApiId'] }, + }); + }); + + test('imported api can add RdsDataSource V2 from attributes', () => { + // WHEN + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addRdsDataSourceV2('dsV2', serverlessClusterV2, secret); // THEN Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DataSource', { diff --git a/packages/aws-cdk-lib/aws-rds/lib/cluster-ref.ts b/packages/aws-cdk-lib/aws-rds/lib/cluster-ref.ts index 1785548c82af4..5102a320c2632 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/cluster-ref.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/cluster-ref.ts @@ -68,6 +68,13 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable, secretsma * */ grantConnect(grantee: iam.IGrantable, dbUser: string): iam.Grant; + + /** + * Grant the given identity to access to the Data API. + * + * @param grantee The principal to grant access to + */ + grantDataApiAccess(grantee: iam.IGrantable): iam.Grant; } /** diff --git a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts index 5119f57a3dec5..5dbaa0a8b0604 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts @@ -465,12 +465,12 @@ export abstract class DatabaseClusterBase extends Resource implements IDatabaseC */ public abstract readonly connections: ec2.Connections; - protected abstract enableDataApi?: boolean; - /** - * Secret in SecretsManager to store the database cluster user credentials. + * The secret attached to this cluster */ - public abstract readonly secret?: secretsmanager.ISecret; + public abstract readonly secret?: secretsmanager.ISecret + + protected abstract enableDataApi?: boolean; /** * The ARN of the cluster @@ -526,12 +526,14 @@ export abstract class DatabaseClusterBase extends Resource implements IDatabaseC } this.enableDataApi = true; - this.secret?.grantRead(grantee); - return iam.Grant.addToPrincipal({ - actions: DATA_API_ACTIONS, + const ret = iam.Grant.addToPrincipal({ grantee, + actions: DATA_API_ACTIONS, resourceArns: [this.clusterArn], + scope: this, }); + this.secret?.grantRead(grantee); + return ret; } } @@ -552,6 +554,11 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { private readonly domainId?: string; private readonly domainRole?: iam.IRole; + /** + * Secret in SecretsManager to store the database cluster user credentials. + */ + public abstract readonly secret?: secretsmanager.ISecret; + /** * The VPC network to place the cluster in. */ diff --git a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts index 45bf7fedf6f49..20ca5417b6303 100644 --- a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts +++ b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts @@ -4300,16 +4300,6 @@ describe('cluster', () => { Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ - { - Action: [ - 'secretsmanager:GetSecretValue', - 'secretsmanager:DescribeSecret', - ], - Effect: 'Allow', - Resource: { - Ref: 'DatabaseSecretAttachmentE5D1B020', - }, - }, { Action: [ 'rds-data:BatchExecuteStatement', @@ -4331,6 +4321,16 @@ describe('cluster', () => { ], }, }, + { + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { + Ref: 'DatabaseSecretAttachmentE5D1B020', + }, + }, ], }, });