diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.merged-api-1.graphql b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.merged-api-1.graphql new file mode 100644 index 0000000000000..78abf8034fa4e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.merged-api-1.graphql @@ -0,0 +1,9 @@ +type firstTest { + version: String! +} +type Query { + getFirstTests: [firstTest]! +} +type Mutation { + addFirstTest(version: String!): firstTest +} diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.merged-api-2.graphql b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.merged-api-2.graphql new file mode 100644 index 0000000000000..56903d9002f32 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/appsync.merged-api-2.graphql @@ -0,0 +1,9 @@ +type secondTest { + version: String! +} +type Query { + getSecondTests: [secondTest]! +} +type Mutation { + addSecondTest(version: String!): secondTest +} diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/apiDefaultTestDeployAssert018781F2.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/apiDefaultTestDeployAssert018781F2.assets.json new file mode 100644 index 0000000000000..640fdd8085ead --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/apiDefaultTestDeployAssert018781F2.assets.json @@ -0,0 +1,19 @@ +{ + "version": "33.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "apiDefaultTestDeployAssert018781F2.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.appsync-merged-api.js.snapshot/apiDefaultTestDeployAssert018781F2.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/apiDefaultTestDeployAssert018781F2.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/apiDefaultTestDeployAssert018781F2.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.appsync-merged-api.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/cdk.out new file mode 100644 index 0000000000000..560dae10d018f --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"33.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/integ.json new file mode 100644 index 0000000000000..49f9cf6d897f9 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "33.0.0", + "testCases": { + "api/DefaultTest": { + "stacks": [ + "stack" + ], + "assertionStack": "api/DefaultTest/DeployAssert", + "assertionStackName": "apiDefaultTestDeployAssert018781F2" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/manifest.json new file mode 100644 index 0000000000000..4f442a2e9917e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/manifest.json @@ -0,0 +1,189 @@ +{ + "version": "33.0.0", + "artifacts": { + "stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "stack.template.json", + "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}/227b4b7375fe3e7375a62383a2f862327ab90f469d554dcca064258ce7b2821b.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "stack.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": [ + "stack.assets" + ], + "metadata": { + "/stack/FirstSourceAPI/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FirstSourceAPIB0DE8D5A" + } + ], + "/stack/FirstSourceAPI/Schema": [ + { + "type": "aws:cdk:logicalId", + "data": "FirstSourceAPISchemaF2FDB692" + } + ], + "/stack/FirstSourceAPI/DefaultApiKey": [ + { + "type": "aws:cdk:logicalId", + "data": "FirstSourceAPIDefaultApiKey9D9AE06D" + } + ], + "/stack/FirstSourceAPI/FirstSourceDS/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FirstSourceAPIFirstSourceDS6AD299B7" + } + ], + "/stack/SecondSourceAPI/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SecondSourceAPIE903371D" + } + ], + "/stack/SecondSourceAPI/Schema": [ + { + "type": "aws:cdk:logicalId", + "data": "SecondSourceAPISchema65B7401E" + } + ], + "/stack/SecondSourceAPI/DefaultApiKey": [ + { + "type": "aws:cdk:logicalId", + "data": "SecondSourceAPIDefaultApiKeyCE5A0A64" + } + ], + "/stack/SecondSourceAPI/SecondSourceDS/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SecondSourceAPISecondSourceDSEA46EBA7" + } + ], + "/stack/MergedAPI/MergedApiExecutionRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MergedAPIMergedApiExecutionRole5F2BCCAD" + } + ], + "/stack/MergedAPI/MergedApiExecutionRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MergedAPIMergedApiExecutionRoleDefaultPolicy31A96DBC" + } + ], + "/stack/MergedAPI/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MergedAPI08D3EAD1" + } + ], + "/stack/MergedAPI/FirstSourceAPIAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "MergedAPIFirstSourceAPIAssociationEC781BA9" + } + ], + "/stack/MergedAPI/SecondSourceAPIAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "MergedAPISecondSourceAPIAssociationBD1A08F4" + } + ], + "/stack/MergedAPI/DefaultApiKey": [ + { + "type": "aws:cdk:logicalId", + "data": "MergedAPIDefaultApiKeyAF5EA13C" + } + ], + "/stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "stack" + }, + "apiDefaultTestDeployAssert018781F2.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "apiDefaultTestDeployAssert018781F2.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "apiDefaultTestDeployAssert018781F2": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "apiDefaultTestDeployAssert018781F2.template.json", + "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": [ + "apiDefaultTestDeployAssert018781F2.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": [ + "apiDefaultTestDeployAssert018781F2.assets" + ], + "metadata": { + "/api/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/api/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "api/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.appsync-merged-api.js.snapshot/stack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/stack.assets.json new file mode 100644 index 0000000000000..33b89654181f0 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/stack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "33.0.0", + "files": { + "227b4b7375fe3e7375a62383a2f862327ab90f469d554dcca064258ce7b2821b": { + "source": { + "path": "stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "227b4b7375fe3e7375a62383a2f862327ab90f469d554dcca064258ce7b2821b.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.appsync-merged-api.js.snapshot/stack.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/stack.template.json new file mode 100644 index 0000000000000..f7461097d9297 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/stack.template.json @@ -0,0 +1,246 @@ +{ + "Resources": { + "FirstSourceAPIB0DE8D5A": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "AuthenticationType": "API_KEY", + "Name": "FirstSourceAPI" + } + }, + "FirstSourceAPISchemaF2FDB692": { + "Type": "AWS::AppSync::GraphQLSchema", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "ApiId" + ] + }, + "Definition": "type firstTest {\n version: String!\n}\ntype Query {\n getFirstTests: [firstTest]!\n}\ntype Mutation {\n addFirstTest(version: String!): firstTest\n}\n" + } + }, + "FirstSourceAPIDefaultApiKey9D9AE06D": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "ApiId" + ] + } + }, + "DependsOn": [ + "FirstSourceAPISchemaF2FDB692" + ] + }, + "FirstSourceAPIFirstSourceDS6AD299B7": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "ApiId" + ] + }, + "Name": "FirstSourceDS", + "Type": "NONE" + } + }, + "SecondSourceAPIE903371D": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "AuthenticationType": "API_KEY", + "Name": "SecondSourceAPI" + } + }, + "SecondSourceAPISchema65B7401E": { + "Type": "AWS::AppSync::GraphQLSchema", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "ApiId" + ] + }, + "Definition": "type secondTest {\n version: String!\n}\ntype Query {\n getSecondTests: [secondTest]!\n}\ntype Mutation {\n addSecondTest(version: String!): secondTest\n}\n" + } + }, + "SecondSourceAPIDefaultApiKeyCE5A0A64": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "ApiId" + ] + } + }, + "DependsOn": [ + "SecondSourceAPISchema65B7401E" + ] + }, + "SecondSourceAPISecondSourceDSEA46EBA7": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "ApiId" + ] + }, + "Name": "SecondSourceDS", + "Type": "NONE" + } + }, + "MergedAPIMergedApiExecutionRole5F2BCCAD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MergedAPIMergedApiExecutionRoleDefaultPolicy31A96DBC": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "appsync:SourceGraphQL", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MergedAPIMergedApiExecutionRoleDefaultPolicy31A96DBC", + "Roles": [ + { + "Ref": "MergedAPIMergedApiExecutionRole5F2BCCAD" + } + ] + } + }, + "MergedAPI08D3EAD1": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "ApiType": "MERGED", + "AuthenticationType": "API_KEY", + "MergedApiExecutionRoleArn": { + "Fn::GetAtt": [ + "MergedAPIMergedApiExecutionRole5F2BCCAD", + "Arn" + ] + }, + "Name": "MergedAPI" + } + }, + "MergedAPIFirstSourceAPIAssociationEC781BA9": { + "Type": "AWS::AppSync::SourceApiAssociation", + "Properties": { + "MergedApiIdentifier": { + "Fn::GetAtt": [ + "MergedAPI08D3EAD1", + "ApiId" + ] + }, + "SourceApiAssociationConfig": { + "MergeType": "MANUAL_MERGE" + }, + "SourceApiIdentifier": { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "ApiId" + ] + } + } + }, + "MergedAPISecondSourceAPIAssociationBD1A08F4": { + "Type": "AWS::AppSync::SourceApiAssociation", + "Properties": { + "MergedApiIdentifier": { + "Fn::GetAtt": [ + "MergedAPI08D3EAD1", + "ApiId" + ] + }, + "SourceApiAssociationConfig": { + "MergeType": "AUTO_MERGE" + }, + "SourceApiIdentifier": { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "ApiId" + ] + } + } + }, + "MergedAPIDefaultApiKeyAF5EA13C": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "MergedAPI08D3EAD1", + "ApiId" + ] + } + } + } + }, + "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.appsync-merged-api.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/tree.json new file mode 100644 index 0000000000000..bda43203cb492 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.js.snapshot/tree.json @@ -0,0 +1,517 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "stack": { + "id": "stack", + "path": "stack", + "children": { + "FirstSourceAPI": { + "id": "FirstSourceAPI", + "path": "stack/FirstSourceAPI", + "children": { + "Resource": { + "id": "Resource", + "path": "stack/FirstSourceAPI/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::GraphQLApi", + "aws:cdk:cloudformation:props": { + "authenticationType": "API_KEY", + "name": "FirstSourceAPI" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnGraphQLApi", + "version": "0.0.0" + } + }, + "Schema": { + "id": "Schema", + "path": "stack/FirstSourceAPI/Schema", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::GraphQLSchema", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "ApiId" + ] + }, + "definition": "type firstTest {\n version: String!\n}\ntype Query {\n getFirstTests: [firstTest]!\n}\ntype Mutation {\n addFirstTest(version: String!): firstTest\n}\n" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnGraphQLSchema", + "version": "0.0.0" + } + }, + "DefaultApiKey": { + "id": "DefaultApiKey", + "path": "stack/FirstSourceAPI/DefaultApiKey", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::ApiKey", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "ApiId" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnApiKey", + "version": "0.0.0" + } + }, + "LogGroup": { + "id": "LogGroup", + "path": "stack/FirstSourceAPI/LogGroup", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "FirstSourceDS": { + "id": "FirstSourceDS", + "path": "stack/FirstSourceAPI/FirstSourceDS", + "children": { + "Resource": { + "id": "Resource", + "path": "stack/FirstSourceAPI/FirstSourceDS/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::DataSource", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "ApiId" + ] + }, + "name": "FirstSourceDS", + "type": "NONE" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnDataSource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.NoneDataSource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.GraphqlApi", + "version": "0.0.0" + } + }, + "SecondSourceAPI": { + "id": "SecondSourceAPI", + "path": "stack/SecondSourceAPI", + "children": { + "Resource": { + "id": "Resource", + "path": "stack/SecondSourceAPI/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::GraphQLApi", + "aws:cdk:cloudformation:props": { + "authenticationType": "API_KEY", + "name": "SecondSourceAPI" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnGraphQLApi", + "version": "0.0.0" + } + }, + "Schema": { + "id": "Schema", + "path": "stack/SecondSourceAPI/Schema", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::GraphQLSchema", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "ApiId" + ] + }, + "definition": "type secondTest {\n version: String!\n}\ntype Query {\n getSecondTests: [secondTest]!\n}\ntype Mutation {\n addSecondTest(version: String!): secondTest\n}\n" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnGraphQLSchema", + "version": "0.0.0" + } + }, + "DefaultApiKey": { + "id": "DefaultApiKey", + "path": "stack/SecondSourceAPI/DefaultApiKey", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::ApiKey", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "ApiId" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnApiKey", + "version": "0.0.0" + } + }, + "LogGroup": { + "id": "LogGroup", + "path": "stack/SecondSourceAPI/LogGroup", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "SecondSourceDS": { + "id": "SecondSourceDS", + "path": "stack/SecondSourceAPI/SecondSourceDS", + "children": { + "Resource": { + "id": "Resource", + "path": "stack/SecondSourceAPI/SecondSourceDS/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::DataSource", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "ApiId" + ] + }, + "name": "SecondSourceDS", + "type": "NONE" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnDataSource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.NoneDataSource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.GraphqlApi", + "version": "0.0.0" + } + }, + "MergedAPI": { + "id": "MergedAPI", + "path": "stack/MergedAPI", + "children": { + "MergedApiExecutionRole": { + "id": "MergedApiExecutionRole", + "path": "stack/MergedAPI/MergedApiExecutionRole", + "children": { + "ImportMergedApiExecutionRole": { + "id": "ImportMergedApiExecutionRole", + "path": "stack/MergedAPI/MergedApiExecutionRole/ImportMergedApiExecutionRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "stack/MergedAPI/MergedApiExecutionRole/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": "stack/MergedAPI/MergedApiExecutionRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "stack/MergedAPI/MergedApiExecutionRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": "appsync:SourceGraphQL", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "MergedAPIMergedApiExecutionRoleDefaultPolicy31A96DBC", + "roles": [ + { + "Ref": "MergedAPIMergedApiExecutionRole5F2BCCAD" + } + ] + } + }, + "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": "stack/MergedAPI/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::GraphQLApi", + "aws:cdk:cloudformation:props": { + "apiType": "MERGED", + "authenticationType": "API_KEY", + "mergedApiExecutionRoleArn": { + "Fn::GetAtt": [ + "MergedAPIMergedApiExecutionRole5F2BCCAD", + "Arn" + ] + }, + "name": "MergedAPI" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnGraphQLApi", + "version": "0.0.0" + } + }, + "FirstSourceAPIAssociation": { + "id": "FirstSourceAPIAssociation", + "path": "stack/MergedAPI/FirstSourceAPIAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::SourceApiAssociation", + "aws:cdk:cloudformation:props": { + "mergedApiIdentifier": { + "Fn::GetAtt": [ + "MergedAPI08D3EAD1", + "ApiId" + ] + }, + "sourceApiAssociationConfig": { + "mergeType": "MANUAL_MERGE" + }, + "sourceApiIdentifier": { + "Fn::GetAtt": [ + "FirstSourceAPIB0DE8D5A", + "ApiId" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnSourceApiAssociation", + "version": "0.0.0" + } + }, + "SecondSourceAPIAssociation": { + "id": "SecondSourceAPIAssociation", + "path": "stack/MergedAPI/SecondSourceAPIAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::SourceApiAssociation", + "aws:cdk:cloudformation:props": { + "mergedApiIdentifier": { + "Fn::GetAtt": [ + "MergedAPI08D3EAD1", + "ApiId" + ] + }, + "sourceApiAssociationConfig": { + "mergeType": "AUTO_MERGE" + }, + "sourceApiIdentifier": { + "Fn::GetAtt": [ + "SecondSourceAPIE903371D", + "ApiId" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnSourceApiAssociation", + "version": "0.0.0" + } + }, + "DefaultApiKey": { + "id": "DefaultApiKey", + "path": "stack/MergedAPI/DefaultApiKey", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::ApiKey", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "MergedAPI08D3EAD1", + "ApiId" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.CfnApiKey", + "version": "0.0.0" + } + }, + "LogGroup": { + "id": "LogGroup", + "path": "stack/MergedAPI/LogGroup", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_appsync.GraphqlApi", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "stack/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "stack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "api": { + "id": "api", + "path": "api", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "api/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "api/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.69" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "api/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "api/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "api/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.2.69" + } + } + }, + "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.appsync-merged-api.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.ts new file mode 100644 index 0000000000000..06e3f5fbb9aa3 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-appsync/test/integ.appsync-merged-api.ts @@ -0,0 +1,47 @@ +import * as path from 'path'; +import * as cdk from 'aws-cdk-lib'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import * as appsync from 'aws-cdk-lib/aws-appsync'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'stack'); + +const firstApi = new appsync.GraphqlApi(stack, 'FirstSourceAPI', { + name: 'FirstSourceAPI', + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.merged-api-1.graphql')), +}); + +firstApi.addNoneDataSource('FirstSourceDS', { + name: cdk.Lazy.string({ produce(): string { return 'FirstSourceDS'; } }), +}); + +const secondApi = new appsync.GraphqlApi(stack, 'SecondSourceAPI', { + name: 'SecondSourceAPI', + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.merged-api-2.graphql')), +}); + +secondApi.addNoneDataSource('SecondSourceDS', { + name: cdk.Lazy.string({ produce(): string { return 'SecondSourceDS'; } }), +}); + +new appsync.GraphqlApi(stack, 'MergedAPI', { + name: 'MergedAPI', + definition: appsync.Definition.fromSourceApis({ + sourceApis: [ + { + sourceApi: firstApi, + mergeType: appsync.MergeType.MANUAL_MERGE, + }, + { + sourceApi: secondApi, + mergeType: appsync.MergeType.AUTO_MERGE, + }, + ], + }), +}); + +new IntegTest(app, 'api', { + testCases: [stack], +}); + +app.synth(); \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-appsync/README.md b/packages/aws-cdk-lib/aws-appsync/README.md index 438dd045f794a..5aa5c01f7b9aa 100644 --- a/packages/aws-cdk-lib/aws-appsync/README.md +++ b/packages/aws-cdk-lib/aws-appsync/README.md @@ -38,7 +38,7 @@ CDK stack file `app-stack.ts`: ```ts const api = new appsync.GraphqlApi(this, 'Api', { name: 'demo', - schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'schema.graphql')), + definition: appsync.Definition.fromFile(path.join(__dirname, 'schema.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM, @@ -208,7 +208,7 @@ CDK stack file `app-stack.ts`: ```ts const api = new appsync.GraphqlApi(this, 'api', { name: 'api', - schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'schema.graphql')), + definition: appsync.Definition.fromFile(path.join(__dirname, 'schema.graphql')), }); const httpDs = api.addHttpDataSource( @@ -233,9 +233,9 @@ httpDs.createResolver('MutationCallStepFunctionResolver', { ``` ### EventBridge -Integrating AppSync with EventBridge enables developers to use EventBridge rules to route commands for GraphQl mutations +Integrating AppSync with EventBridge enables developers to use EventBridge rules to route commands for GraphQL mutations that need to perform any one of a variety of asynchronous tasks. More broadly, it enables teams to expose an event bus -as a part of a GraphQl schema. +as a part of a GraphQL schema. GraphQL schema file `schema.graphql`: @@ -306,7 +306,7 @@ import * as events from 'aws-cdk-lib/aws-events'; const api = new appsync.GraphqlApi(this, 'EventBridgeApi', { name: 'EventBridgeApi', - schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'appsync.eventbridge.graphql')), + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.eventbridge.graphql')), }); const bus = new events.EventBus(this, 'DestinationEventBus', {}); @@ -366,6 +366,48 @@ ds.createResolver('QueryGetTestsResolver', { }); ``` +## Merged APIs +AppSync supports [Merged APIs](https://docs.aws.amazon.com/appsync/latest/devguide/merged-api.html) which can be used to merge multiple source APIs into a single API. + +```ts +import * as cdk from 'aws-cdk-lib'; + +// first source API +const firstApi = new appsync.GraphqlApi(this, 'FirstSourceAPI', { + name: 'FirstSourceAPI', + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.merged-api-1.graphql')), +}); +firstApi.addNoneDataSource('FirstSourceDS', { + name: cdk.Lazy.string({ produce(): string { return 'FirstSourceDS'; } }), +}); + +// second source API +const secondApi = new appsync.GraphqlApi(this, 'SecondSourceAPI', { + name: 'SecondSourceAPI', + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.merged-api-2.graphql')), +}); +secondApi.addNoneDataSource('SecondSourceDS', { + name: cdk.Lazy.string({ produce(): string { return 'SecondSourceDS'; } }), +}); + +// Merged API +new appsync.GraphqlApi(this, 'MergedAPI', { + name: 'MergedAPI', + definition: appsync.Definition.fromSourceApis({ + sourceApis: [ + { + sourceApi: firstApi, + mergeType: appsync.MergeType.MANUAL_MERGE, + }, + { + sourceApi: secondApi, + mergeType: appsync.MergeType.AUTO_MERGE, + }, + ], + }), +}); +``` + ## Custom Domain Names For many use cases you may want to associate a custom domain name with your @@ -380,7 +422,7 @@ const certificate = new acm.Certificate(this, 'cert', { domainName: myDomainName const schema = new appsync.SchemaFile({ filePath: 'mySchemaFile' }) const api = new appsync.GraphqlApi(this, 'api', { name: 'myApi', - schema, + definition: appsync.Definition.fromSchema(schema), domainName: { certificate, domainName: myDomainName, @@ -423,19 +465,19 @@ const logConfig: appsync.LogConfig = { new appsync.GraphqlApi(this, 'api', { authorizationConfig: {}, name: 'myApi', - schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'myApi.graphql')), + definition: appsync.Definition.fromFile(path.join(__dirname, 'myApi.graphql')), logConfig, }); ``` ## Schema -You can define a schema using from a local file using `SchemaFile.fromAsset` +You can define a schema using from a local file using `Definition.fromFile` ```ts const api = new appsync.GraphqlApi(this, 'api', { name: 'myApi', - schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'schema.graphl')), + definition: appsync.Definition.fromFile(path.join(__dirname, 'schema.graphl')), }); ``` @@ -477,7 +519,7 @@ CDK stack file `app-stack.ts`: ```ts const api = new appsync.GraphqlApi(this, 'api', { name: 'MyPrivateAPI', - schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'appsync.schema.graphql')), + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.schema.graphql')), visibility: appsync.Visibility.PRIVATE, }); ``` @@ -507,7 +549,7 @@ declare const authFunction: lambda.Function; new appsync.GraphqlApi(this, 'api', { name: 'api', - schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'appsync.test.graphql')), + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.LAMBDA, diff --git a/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi.ts b/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi.ts index cfc0b406eb4d9..3b09bb03355fc 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi.ts @@ -1,10 +1,10 @@ import { Construct } from 'constructs'; -import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema, CfnDomainName, CfnDomainNameApiAssociation } from './appsync.generated'; +import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema, CfnDomainName, CfnDomainNameApiAssociation, CfnSourceApiAssociation } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; -import { ISchema } from './schema'; +import { ISchema, SchemaFile } from './schema'; import { ICertificate } from '../../aws-certificatemanager'; import { IUserPool } from '../../aws-cognito'; -import { ManagedPolicy, Role, IRole, ServicePrincipal, Grant, IGrantable } from '../../aws-iam'; +import { ManagedPolicy, Role, IRole, ServicePrincipal, Grant, IGrantable, PolicyStatement } from '../../aws-iam'; import { IFunction } from '../../aws-lambda'; import { ILogGroup, LogGroup, LogRetention, RetentionDays } from '../../aws-logs'; import { ArnFormat, CfnResource, Duration, Expiration, IResolvable, Stack } from '../../core'; @@ -288,6 +288,100 @@ export interface DomainOptions { readonly domainName: string; } +/** + * Merge type used to associate the source API + */ +export enum MergeType { + /** + * Manual merge. The merge must be triggered manually when the source API has changed. + */ + MANUAL_MERGE = 'MANUAL_MERGE', + + /** + * Auto merge. The merge is triggered automatically when the source API has changed. + */ + AUTO_MERGE = 'AUTO_MERGE', +} + +/** + * Source API configuration for creating a AppSync Merged API + */ +export interface SourceApiOptions { + /** + * Definition of source APIs associated with this Merged API + */ + readonly sourceApis: SourceApi[]; + + /** + * IAM Role used to validate access to source APIs at runtime and to update the merged API endpoint with the source API changes + * + * @default - An IAM Role with acccess to source schemas will be created + */ + readonly mergedApiExecutionRole?: Role; +} + +/** + * Configuration of source API +*/ +export interface SourceApi { + /** + * Source API that is associated with the merged API + */ + readonly sourceApi: GraphqlApi; + /** + * Merging option used to associate the source API to the Merged API + * + * @default - Auto merge. The merge is triggered automatically when the source API has changed + */ + readonly mergeType?: MergeType; +} + +/** + * AppSync definition. Specify how you want to define your AppSync API. + */ +export abstract class Definition { + /** + * Schema from schema object. + * @param schema SchemaFile.fromAsset(filePath: string) allows schema definition through schema.graphql file + * @returns API Source with schema from file + */ + public static fromSchema(schema: ISchema): Definition { + return { + schema, + }; + } + + /** + * Schema from file, allows schema definition through schema.graphql file + * @param filePath the file path of the schema file + * @returns API Source with schema from file + */ + public static fromFile(filePath: string): Definition { + return this.fromSchema(SchemaFile.fromAsset(filePath)); + } + + /** + * Schema from existing AppSync APIs - used for creating a AppSync Merged API + * @param sourceApiOptions Configuration for AppSync Merged API + * @returns API Source with for AppSync Merged API + */ + public static fromSourceApis(sourceApiOptions: SourceApiOptions): Definition { + return { + sourceApiOptions, + }; + } + + /** + * Schema, when AppSync API is created from schema file + */ + readonly schema?: ISchema; + + /** + * Source APIs for Merged API + */ + readonly sourceApiOptions?: SourceApiOptions; +} + /** * Properties for an AppSync GraphQL API */ @@ -311,15 +405,20 @@ export interface GraphqlApiProps { */ readonly logConfig?: LogConfig; + /** + * Definition (schema file or source APIs) for this GraphQL Api + */ + readonly definition?: Definition; + /** * GraphQL schema definition. Specify how you want to define your schema. * * SchemaFile.fromAsset(filePath: string) allows schema definition through schema.graphql file * * @default - schema will be generated code-first (i.e. addType, addObjectType, etc.) - * + * @deprecated use apiSoure.schema instead */ - readonly schema: ISchema; + readonly schema?: ISchema; /** * A flag indicating whether or not X-Ray tracing is enabled for the GraphQL API. * @@ -473,9 +572,14 @@ export class GraphqlApi extends GraphqlApiBase { public readonly name: string; /** - * the schema attached to this api + * the schema attached to this api (only available for GraphQL APIs, not available for merged APIs) */ - public readonly schema: ISchema; + public get schema(): ISchema { + if (this.definition.schema) { + return this.definition.schema; + } + throw new Error('Schema does not exist for AppSync merged APIs.'); + } /** * The Authorization Types for this GraphQL Api @@ -494,10 +598,12 @@ export class GraphqlApi extends GraphqlApiBase { */ public readonly logGroup: ILogGroup; - private schemaResource: CfnGraphQLSchema; + private definition: Definition; + private schemaResource?: CfnGraphQLSchema; private api: CfnGraphQLApi; private apiKeyResource?: CfnApiKey; private domainNameResource?: CfnDomainName; + private mergedApiExecutionRole?: Role; constructor(scope: Construct, id: string, props: GraphqlApiProps) { super(scope, id); @@ -511,6 +617,18 @@ export class GraphqlApi extends GraphqlApiBase { this.validateAuthorizationProps(modes); + if (!props.schema && !props.definition) { + throw new Error('You must specify a GraphQL schema or source APIs in property definition.'); + } + if ((props.schema !== undefined) === (props.definition !== undefined)) { + throw new Error('You cannot specify both properties schema and definition.'); + } + this.definition = props.schema ? Definition.fromSchema(props.schema) : props.definition!; + + if (this.definition.sourceApiOptions) { + this.setupMergedApiExecutionRole(this.definition.sourceApiOptions); + } + this.api = new CfnGraphQLApi(this, 'Resource', { name: props.name, authenticationType: defaultMode.authorizationType, @@ -521,14 +639,20 @@ export class GraphqlApi extends GraphqlApiBase { additionalAuthenticationProviders: this.setupAdditionalAuthorizationModes(additionalModes), xrayEnabled: props.xrayEnabled, visibility: props.visibility, + mergedApiExecutionRoleArn: this.mergedApiExecutionRole?.roleArn, + apiType: this.definition.sourceApiOptions ? 'MERGED' : undefined, }); this.apiId = this.api.attrApiId; this.arn = this.api.attrArn; this.graphqlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; - this.schema = props.schema; - this.schemaResource = new CfnGraphQLSchema(this, 'Schema', this.schema.bind(this)); + + if (this.definition.schema) { + this.schemaResource = new CfnGraphQLSchema(this, 'Schema', this.definition.schema.bind(this)); + } else { + this.setupSourceApiAssociations(); + } if (props.domainName) { this.domainNameResource = new CfnDomainName(this, 'DomainName', { @@ -549,7 +673,9 @@ export class GraphqlApi extends GraphqlApiBase { return mode.authorizationType === AuthorizationType.API_KEY && mode.apiKeyConfig; })?.apiKeyConfig; this.apiKeyResource = this.createAPIKey(config); - this.apiKeyResource.addDependency(this.schemaResource); + if (this.schemaResource) { + this.apiKeyResource.addDependency(this.schemaResource); + } this.apiKey = this.apiKeyResource.attrApiKey; } @@ -575,6 +701,36 @@ export class GraphqlApi extends GraphqlApiBase { }; } + private setupSourceApiAssociations() { + this.definition.sourceApiOptions?.sourceApis.forEach(sourceApiOption => { + new CfnSourceApiAssociation(this, `${sourceApiOption.sourceApi.node.id}Association`, { + sourceApiIdentifier: sourceApiOption.sourceApi.apiId, + mergedApiIdentifier: this.api.attrApiId, + sourceApiAssociationConfig: { + mergeType: sourceApiOption.mergeType ?? MergeType.AUTO_MERGE, + }, + }); + }); + } + + private setupMergedApiExecutionRole(sourceApiOptions: SourceApiOptions) { + if (sourceApiOptions.mergedApiExecutionRole) { + this.mergedApiExecutionRole = sourceApiOptions.mergedApiExecutionRole; + } else { + const sourceApiArns = sourceApiOptions.sourceApis?.map(sourceApiOption => { + return sourceApiOption.sourceApi.arn; + }); + + this.mergedApiExecutionRole = new Role(this, 'MergedApiExecutionRole', { + assumedBy: new ServicePrincipal('appsync.amazonaws.com'), + }); + this.mergedApiExecutionRole.addToPolicy(new PolicyStatement({ + resources: sourceApiArns, + actions: ['appsync:SourceGraphQL'], + })); + } + } + /** * Adds an IAM policy statement associated with this GraphQLApi to an IAM * principal's policy. @@ -654,7 +810,9 @@ export class GraphqlApi extends GraphqlApiBase { * @param construct the dependee */ public addSchemaDependency(construct: CfnResource): boolean { - construct.addDependency(this.schemaResource); + if (this.schemaResource) { + construct.addDependency(this.schemaResource); + }; return true; } diff --git a/packages/aws-cdk-lib/aws-appsync/test/appsync-merged-api.test.ts b/packages/aws-cdk-lib/aws-appsync/test/appsync-merged-api.test.ts new file mode 100644 index 0000000000000..fcef54d40c0ae --- /dev/null +++ b/packages/aws-cdk-lib/aws-appsync/test/appsync-merged-api.test.ts @@ -0,0 +1,241 @@ +import * as path from 'path'; +import { Template } from '../../assertions'; +import * as iam from '../../aws-iam'; +import * as cdk from '../../core'; +import * as appsync from '../lib'; + +let stack: cdk.Stack; +let api1: appsync.GraphqlApi; +let api2: appsync.GraphqlApi; +beforeEach(() => { + stack = new cdk.Stack(); + api1 = new appsync.GraphqlApi(stack, 'api1', { + authorizationConfig: {}, + name: 'api', + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.test.graphql')), + logConfig: {}, + }); + api2 = new appsync.GraphqlApi(stack, 'api2', { + authorizationConfig: {}, + name: 'api', + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.test.graphql')), + logConfig: {}, + }); +}); + +test('appsync supports merged API', () => { + // WHEN + new appsync.GraphqlApi(stack, 'merged-api', { + name: 'api', + definition: appsync.Definition.fromSourceApis({ + sourceApis: [ + { + sourceApi: api1, + mergeType: appsync.MergeType.MANUAL_MERGE, + }, + { + sourceApi: api2, + mergeType: appsync.MergeType.AUTO_MERGE, + }, + ], + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::GraphQLApi', { + ApiType: 'MERGED', + MergedApiExecutionRoleArn: { + 'Fn::GetAtt': [ + 'mergedapiMergedApiExecutionRole2053D32E', + 'Arn', + ], + }, + }); + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::SourceApiAssociation', { + MergedApiIdentifier: { + 'Fn::GetAtt': [ + 'mergedapiCE4CAF34', + 'ApiId', + ], + }, + SourceApiAssociationConfig: { + MergeType: 'MANUAL_MERGE', + }, + SourceApiIdentifier: { + 'Fn::GetAtt': [ + 'api1A91238E2', + 'ApiId', + ], + }, + }); + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::SourceApiAssociation', { + MergedApiIdentifier: { + 'Fn::GetAtt': [ + 'mergedapiCE4CAF34', + 'ApiId', + ], + }, + SourceApiAssociationConfig: { + MergeType: 'AUTO_MERGE', + }, + SourceApiIdentifier: { + 'Fn::GetAtt': [ + 'api2C4850CEA', + 'ApiId', + ], + }, + }); + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::SourceApiAssociation', { + MergedApiIdentifier: { + 'Fn::GetAtt': [ + 'mergedapiCE4CAF34', + 'ApiId', + ], + }, + SourceApiAssociationConfig: { + MergeType: 'AUTO_MERGE', + }, + SourceApiIdentifier: { + 'Fn::GetAtt': [ + 'api2C4850CEA', + 'ApiId', + ], + }, + }); + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'appsync.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'appsync:SourceGraphQL', + Effect: 'Allow', + Resource: [ + { + 'Fn::GetAtt': [ + 'api1A91238E2', + 'Arn', + ], + }, + { + 'Fn::GetAtt': [ + 'api2C4850CEA', + 'Arn', + ], + }, + ], + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'mergedapiMergedApiExecutionRoleDefaultPolicy6F79FCAF', + Roles: [ + { + Ref: 'mergedapiMergedApiExecutionRole2053D32E', + }, + ], + }); +}); + +test('appsync supports merged API with default merge type', () => { + // WHEN + new appsync.GraphqlApi(stack, 'merged-api', { + name: 'api', + definition: appsync.Definition.fromSourceApis({ + sourceApis: [ + { + sourceApi: api1, + }, + ], + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::SourceApiAssociation', { + MergedApiIdentifier: { + 'Fn::GetAtt': [ + 'mergedapiCE4CAF34', + 'ApiId', + ], + }, + SourceApiAssociationConfig: { + MergeType: 'AUTO_MERGE', + }, + SourceApiIdentifier: { + 'Fn::GetAtt': [ + 'api1A91238E2', + 'ApiId', + ], + }, + }); +}); + +test('appsync merged API with custom merged API execution role', () => { + // WHEN + const role = new iam.Role(stack, 'CustomMergedApiExecutionRole', { + assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'), + }); + new appsync.GraphqlApi(stack, 'merged-api', { + name: 'api', + definition: appsync.Definition.fromSourceApis({ + mergedApiExecutionRole: role, + sourceApis: [ + { + sourceApi: api1, + mergeType: appsync.MergeType.MANUAL_MERGE, + }, + { + sourceApi: api2, + mergeType: appsync.MergeType.AUTO_MERGE, + }, + ], + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::GraphQLApi', { + ApiType: 'MERGED', + MergedApiExecutionRoleArn: { + 'Fn::GetAtt': [ + 'CustomMergedApiExecutionRoleB795A9C4', + 'Arn', + ], + }, + }); +}); + +test('Merged API throws when accessing schema property', () => { + // WHEN + const mergedApi = new appsync.GraphqlApi(stack, 'merged-api', { + name: 'api', + definition: appsync.Definition.fromSourceApis({ + sourceApis: [ + { + sourceApi: api1, + mergeType: appsync.MergeType.MANUAL_MERGE, + }, + { + sourceApi: api2, + mergeType: appsync.MergeType.AUTO_MERGE, + }, + ], + }), + }); + + // THEN + expect(() => { + mergedApi.schema; + }).toThrowError('Schema does not exist for AppSync merged APIs.'); +}); \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-appsync/test/appsync.test.ts b/packages/aws-cdk-lib/aws-appsync/test/appsync.test.ts index 094ae8984263b..39a5fe26011fa 100644 --- a/packages/aws-cdk-lib/aws-appsync/test/appsync.test.ts +++ b/packages/aws-cdk-lib/aws-appsync/test/appsync.test.ts @@ -13,7 +13,7 @@ beforeEach(() => { api = new appsync.GraphqlApi(stack, 'api', { authorizationConfig: {}, name: 'api', - schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'appsync.test.graphql')), + definition: appsync.Definition.fromSchema(appsync.SchemaFile.fromAsset(path.join(__dirname, 'appsync.test.graphql'))), logConfig: {}, }); }); @@ -253,4 +253,49 @@ test('when visibility is set it should be used when creating the API', () => { Template.fromStack(stack).hasResourceProperties('AWS::AppSync::GraphQLApi', { Visibility: 'PRIVATE', }); +}); + +test('appsync should support deprecated schema property', () => { + // WHEN + const schemaFile = appsync.SchemaFile.fromAsset(path.join(__dirname, 'appsync.test.graphql')); + const apiWithSchema = new appsync.GraphqlApi(stack, 'apiWithSchema', { + authorizationConfig: {}, + name: 'api', + schema: schemaFile, + }); + + // THEN + expect(apiWithSchema.schema).toBe(schemaFile); +}); + +test('appsync api from file', () => { + // WHEN + const apiWithSchema = new appsync.GraphqlApi(stack, 'apiWithSchema', { + authorizationConfig: {}, + name: 'api', + definition: appsync.Definition.fromFile(path.join(__dirname, 'appsync.test.graphql')), + }); + + // THEN + expect(apiWithSchema.schema).not.toBeNull(); +}); + +test('appsync fails when properties schema and definition are undefined', () => { + // THEN + expect(() => { + new appsync.GraphqlApi(stack, 'apiWithoutSchemaAndDefinition', { + name: 'api', + }); + }).toThrowError('You must specify a GraphQL schema or source APIs in property definition.'); +}); + +test('appsync fails when specifing schema and definition', () => { + // THEN + expect(() => { + new appsync.GraphqlApi(stack, 'apiWithSchemaAndDefinition', { + name: 'api', + schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'appsync.test.graphql')), + definition: appsync.Definition.fromSchema(appsync.SchemaFile.fromAsset(path.join(__dirname, 'appsync.test.graphql'))), + }); + }).toThrowError('You cannot specify both properties schema and definition.'); }); \ No newline at end of file