From 8c82b2a040df021adfe92627f821c8c6d9b9270c Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Sat, 12 Mar 2022 13:07:32 -0500 Subject: [PATCH 1/9] feat(aws-appsync): enable support for custom domain mappings --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 41 +++++++++++++++++-- packages/@aws-cdk/aws-appsync/package.json | 2 + .../@aws-cdk/aws-appsync/test/appsync.test.ts | 36 ++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index a63b757ba6b0b..b1e81409f3e15 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,9 +1,10 @@ +import { ICertificate } from '@aws-cdk/aws-certificatemanager'; import { IUserPool } from '@aws-cdk/aws-cognito'; import { ManagedPolicy, Role, IRole, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; import { ArnFormat, CfnResource, Duration, Expiration, IResolvable, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; +import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema, CfnDomainName, CfnDomainNameApiAssociation } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; import { Schema } from './schema'; import { IIntermediateType } from './schema-base'; @@ -254,6 +255,18 @@ export interface LogConfig { readonly role?: IRole; } +export interface DomainOptions { + /** + * The certificate to use with the domain name + */ + readonly certificate: ICertificate; + + /** + * The actual domain name e.g. api.example.com + */ + readonly domainName: string; +} + /** * Properties for an AppSync GraphQL API */ @@ -292,6 +305,16 @@ export interface GraphqlApiProps { * @default - false */ readonly xrayEnabled?: boolean; + + /** + * The domain name configuration for the GraphQL API + * + * The hosted zone and CName must be configured in addition to this setting to + * enable custom domain URL + * + * @default - none a unique name is generated + */ + readonly domainName?: DomainOptions; } /** @@ -391,7 +414,7 @@ export class GraphqlApi extends GraphqlApiBase { class Import extends GraphqlApiBase { public readonly apiId = attrs.graphqlApiId; public readonly arn = arn; - constructor (s: Construct, i: string) { + constructor(s: Construct, i: string) { super(s, i); } } @@ -450,7 +473,7 @@ export class GraphqlApi extends GraphqlApiBase { const additionalModes = props.authorizationConfig?.additionalAuthorizationModes ?? []; const modes = [defaultMode, ...additionalModes]; - this.modes = modes.map((mode) => mode.authorizationType ); + this.modes = modes.map((mode) => mode.authorizationType); this.validateAuthorizationProps(modes); @@ -472,6 +495,18 @@ export class GraphqlApi extends GraphqlApiBase { this.schema = props.schema ?? new Schema(); this.schemaResource = this.schema.bind(this); + if (props.domainName) { + new CfnDomainName(this, 'DomainName', { + domainName: props.domainName.domainName, + certificateArn: props.domainName.certificate.certificateArn, + description: `domain for ${this.name} at ${this.graphqlUrl}`, + }); + new CfnDomainNameApiAssociation(this, 'DomainAssociation', { + domainName: props.domainName.domainName, + apiId: this.apiId, + }); + } + if (modes.some((mode) => mode.authorizationType === AuthorizationType.API_KEY)) { const config = modes.find((mode: AuthorizationMode) => { return mode.authorizationType === AuthorizationType.API_KEY && mode.apiKeyConfig; diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index db0d74aa48788..f69c2d957e031 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -89,6 +89,7 @@ "jest": "^27.5.1" }, "dependencies": { + "@aws-cdk/aws-certificatemanager": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", @@ -104,6 +105,7 @@ }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-certificatemanager": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index 9c75c0c7a28c9..a822c5f16dae0 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -3,6 +3,7 @@ import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; +import { Certificate } from '@aws-cdk/aws-certificatemanager'; let stack: cdk.Stack; let api: appsync.GraphqlApi; @@ -155,3 +156,38 @@ test('appsync GraphqlApi should not use custom role for CW Logs when not specifi }, }); }); + +test('appsync GraphqlApi should be configured with custom domain when specified', () => { + const domainName = 'api.example.com'; + // GIVEN + const certificate = new Certificate(stack, 'AcmCertificate', { + domainName, + }); + + // WHEN + new appsync.GraphqlApi(stack, 'api-custom-cw-logs-role', { + authorizationConfig: {}, + name: 'apiWithCustomRole', + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), + domainName: { + domainName, + certificate, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DomainNameApiAssociation', { + ApiId: { + "Fn::GetAtt": [ + "apicustomcwlogsrole508EAC74", + "ApiId" + ] + }, + DomainName: domainName, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DomainName', { + CertificateArn: { "Ref": "AcmCertificate49D3B5AF" }, + DomainName: domainName, + }); +}); From 1419f279735287a24704d67f580bcc39aaa1fe97 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Sat, 12 Mar 2022 13:21:01 -0500 Subject: [PATCH 2/9] chore: update readme --- packages/@aws-cdk/aws-appsync/README.md | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 4d7c6f1014a45..7632cbb367a0b 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -285,6 +285,41 @@ ds.createResolver({ }); ``` +## Custom Domain Names + +For many use cases you may want to associate a custom domain name with your +GraphQL API. This can be done during the API creation. + +```ts +import { Certificate } from '@aws-cdk/aws-certificatemanager'; + +declare const certificate: Certificate; +const api = new appsync.GraphqlApi(this, 'api', { + name: 'myApi', + domainName: { + certificate, + domainName: 'api.mycompany.com', + }, + }); + + // hosted zone and route53 features + declare const hostedZoneId: string; + declare const zoneName = 'mycompany.com' + + // hosten zone for adding appsync domain + const zone = HostedZone.fromHostedZoneAttributes(this, `HostedZone`, { + hostedZoneId, + zoneName, + }); + + // create a cname to the appsync domain. will map to something like xxxx.cloudfront.net + new CnameRecord(this, `CnameApiRecord`, { + recordName: 'api', + zone, + domainName: appsyncDomain.attrAppSyncDomainName + }); +``` + ## Schema Every GraphQL Api needs a schema to define the Api. CDK offers `appsync.Schema` From 87a87c1b0a6b2d590568691b11bb8d7cfb038015 Mon Sep 17 00:00:00 2001 From: Chris Garvis Date: Thu, 17 Mar 2022 13:39:12 -0400 Subject: [PATCH 3/9] Apply suggestions from code review Co-authored-by: Kaizen Conroy <36202692+kaizen3031593@users.noreply.github.com> --- packages/@aws-cdk/aws-appsync/README.md | 2 +- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 2 +- packages/@aws-cdk/aws-appsync/test/appsync.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 7632cbb367a0b..d7753891fe3e9 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -304,7 +304,7 @@ const api = new appsync.GraphqlApi(this, 'api', { // hosted zone and route53 features declare const hostedZoneId: string; - declare const zoneName = 'mycompany.com' + declare const zoneName = 'mycompany.com'; // hosten zone for adding appsync domain const zone = HostedZone.fromHostedZoneAttributes(this, `HostedZone`, { diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index b1e81409f3e15..2f113adc159b3 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -262,7 +262,7 @@ export interface DomainOptions { readonly certificate: ICertificate; /** - * The actual domain name e.g. api.example.com + * The actual domain name. For example, `api.example.com`. */ readonly domainName: string; } diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index a822c5f16dae0..97c5bff90e602 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -180,8 +180,8 @@ test('appsync GraphqlApi should be configured with custom domain when specified' ApiId: { "Fn::GetAtt": [ "apicustomcwlogsrole508EAC74", - "ApiId" - ] + "ApiId", + ], }, DomainName: domainName, }); From 60ca733358cd8ddf823db5d55b4b8bdd1f7201ca Mon Sep 17 00:00:00 2001 From: Chris Garvis Date: Thu, 17 Mar 2022 15:11:15 -0400 Subject: [PATCH 4/9] Fix linting warnings and errors --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 1 + packages/@aws-cdk/aws-appsync/test/appsync.test.ts | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 2f113adc159b3..c64333c534400 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -501,6 +501,7 @@ export class GraphqlApi extends GraphqlApiBase { certificateArn: props.domainName.certificate.certificateArn, description: `domain for ${this.name} at ${this.graphqlUrl}`, }); + new CfnDomainNameApiAssociation(this, 'DomainAssociation', { domainName: props.domainName.domainName, apiId: this.apiId, diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index 97c5bff90e602..350fbff6229db 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -1,9 +1,9 @@ import * as path from 'path'; import { Template } from '@aws-cdk/assertions'; +import { Certificate } from '@aws-cdk/aws-certificatemanager'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; -import { Certificate } from '@aws-cdk/aws-certificatemanager'; let stack: cdk.Stack; let api: appsync.GraphqlApi; @@ -178,16 +178,16 @@ test('appsync GraphqlApi should be configured with custom domain when specified' // THEN Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DomainNameApiAssociation', { ApiId: { - "Fn::GetAtt": [ - "apicustomcwlogsrole508EAC74", - "ApiId", + 'Fn::GetAtt': [ + 'apicustomcwlogsrole508EAC74', + 'ApiId', ], }, DomainName: domainName, }); Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DomainName', { - CertificateArn: { "Ref": "AcmCertificate49D3B5AF" }, + CertificateArn: { Ref: 'AcmCertificate49D3B5AF' }, DomainName: domainName, }); }); From 42c379ca60043755015ebfe383d6f88e606801a8 Mon Sep 17 00:00:00 2001 From: Chris Garvis Date: Thu, 17 Mar 2022 16:04:25 -0400 Subject: [PATCH 5/9] Fix whitespacing --- packages/@aws-cdk/aws-appsync/README.md | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index d7753891fe3e9..e33dc5e2113e5 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -295,29 +295,29 @@ import { Certificate } from '@aws-cdk/aws-certificatemanager'; declare const certificate: Certificate; const api = new appsync.GraphqlApi(this, 'api', { - name: 'myApi', - domainName: { - certificate, - domainName: 'api.mycompany.com', - }, - }); + name: 'myApi', + domainName: { + certificate, + domainName: 'api.mycompany.com', + }, +}); - // hosted zone and route53 features - declare const hostedZoneId: string; - declare const zoneName = 'mycompany.com'; +// hosted zone and route53 features +declare const hostedZoneId: string; +declare const zoneName = 'mycompany.com'; - // hosten zone for adding appsync domain - const zone = HostedZone.fromHostedZoneAttributes(this, `HostedZone`, { - hostedZoneId, - zoneName, - }); +// hosten zone for adding appsync domain +const zone = HostedZone.fromHostedZoneAttributes(this, `HostedZone`, { + hostedZoneId, + zoneName, +}); - // create a cname to the appsync domain. will map to something like xxxx.cloudfront.net - new CnameRecord(this, `CnameApiRecord`, { - recordName: 'api', - zone, - domainName: appsyncDomain.attrAppSyncDomainName - }); +// create a cname to the appsync domain. will map to something like xxxx.cloudfront.net +new CnameRecord(this, `CnameApiRecord`, { + recordName: 'api', + zone, + domainName: appsyncDomain.attrAppSyncDomainName +}); ``` ## Schema From 96ca54cdc798b97c77022c23fc3801553b4a2962 Mon Sep 17 00:00:00 2001 From: Chris Garvis Date: Thu, 17 Mar 2022 17:19:50 -0400 Subject: [PATCH 6/9] Fix documentation --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index c64333c534400..718638679080d 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -255,6 +255,9 @@ export interface LogConfig { readonly role?: IRole; } +/** + * Domain Name configuration for AppSync + */ export interface DomainOptions { /** * The certificate to use with the domain name @@ -309,10 +312,10 @@ export interface GraphqlApiProps { /** * The domain name configuration for the GraphQL API * - * The hosted zone and CName must be configured in addition to this setting to + * The Route 53 hosted zone and CName DNS record must be configured in addition to this setting to * enable custom domain URL * - * @default - none a unique name is generated + * @default - None */ readonly domainName?: DomainOptions; } From ed9cdb5673370733fe53106db23d55ecf4d23765 Mon Sep 17 00:00:00 2001 From: kaizen3031593 Date: Thu, 17 Mar 2022 17:44:58 -0400 Subject: [PATCH 7/9] try to fix readme example --- packages/@aws-cdk/aws-appsync/README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index e33dc5e2113e5..8a7a33d989dee 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -291,32 +291,33 @@ For many use cases you may want to associate a custom domain name with your GraphQL API. This can be done during the API creation. ```ts -import { Certificate } from '@aws-cdk/aws-certificatemanager'; +import * as acm from '@aws-cdk/aws-certificatemanager'; +import * as route53 from '@aws-cdk/aws-route53'; -declare const certificate: Certificate; +const certificate = new acm.Certificate(this, 'cert', { domainName: 'example.com' }); const api = new appsync.GraphqlApi(this, 'api', { name: 'myApi', domainName: { - certificate, - domainName: 'api.mycompany.com', + certificate, + domainName: 'example.com', }, }); // hosted zone and route53 features declare const hostedZoneId: string; -declare const zoneName = 'mycompany.com'; +declare const zoneName = 'example.com'; -// hosten zone for adding appsync domain -const zone = HostedZone.fromHostedZoneAttributes(this, `HostedZone`, { +// hosted zone for adding appsync domain +const zone = route53.HostedZone.fromHostedZoneAttributes(this, `HostedZone`, { hostedZoneId, zoneName, }); // create a cname to the appsync domain. will map to something like xxxx.cloudfront.net -new CnameRecord(this, `CnameApiRecord`, { +new route53.CnameRecord(this, `CnameApiRecord`, { recordName: 'api', zone, - domainName: appsyncDomain.attrAppSyncDomainName + domainName: appsyncDomain.attrAppSyncDomainName, }); ``` From 831fcf7f7b9779151e6e61a2323ecbf809329890 Mon Sep 17 00:00:00 2001 From: kaizen3031593 Date: Thu, 17 Mar 2022 17:47:11 -0400 Subject: [PATCH 8/9] another fix --- packages/@aws-cdk/aws-appsync/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 8a7a33d989dee..e4266b9ae152b 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -317,7 +317,7 @@ const zone = route53.HostedZone.fromHostedZoneAttributes(this, `HostedZone`, { new route53.CnameRecord(this, `CnameApiRecord`, { recordName: 'api', zone, - domainName: appsyncDomain.attrAppSyncDomainName, + domainName: 'appsync.domainName', }); ``` From 0856ecf98fc6ea3ab28532e1ab4aed7db4f31d59 Mon Sep 17 00:00:00 2001 From: kaizen3031593 Date: Thu, 17 Mar 2022 19:40:27 -0400 Subject: [PATCH 9/9] last minor changes --- packages/@aws-cdk/aws-appsync/README.md | 7 ++++--- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index e4266b9ae152b..721fa10390784 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -294,12 +294,13 @@ GraphQL API. This can be done during the API creation. import * as acm from '@aws-cdk/aws-certificatemanager'; import * as route53 from '@aws-cdk/aws-route53'; -const certificate = new acm.Certificate(this, 'cert', { domainName: 'example.com' }); +const myDomainName = 'api.example.com'; +const certificate = new acm.Certificate(this, 'cert', { domainName: myDomainName }); const api = new appsync.GraphqlApi(this, 'api', { name: 'myApi', domainName: { certificate, - domainName: 'example.com', + domainName: myDomainName, }, }); @@ -317,7 +318,7 @@ const zone = route53.HostedZone.fromHostedZoneAttributes(this, `HostedZone`, { new route53.CnameRecord(this, `CnameApiRecord`, { recordName: 'api', zone, - domainName: 'appsync.domainName', + domainName: myDomainName, }); ``` diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 718638679080d..5d7cce7131cbb 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -256,11 +256,11 @@ export interface LogConfig { } /** - * Domain Name configuration for AppSync + * Domain name configuration for AppSync */ export interface DomainOptions { /** - * The certificate to use with the domain name + * The certificate to use with the domain name. */ readonly certificate: ICertificate; @@ -315,7 +315,7 @@ export interface GraphqlApiProps { * The Route 53 hosted zone and CName DNS record must be configured in addition to this setting to * enable custom domain URL * - * @default - None + * @default - no domain name */ readonly domainName?: DomainOptions; }