Skip to content

Commit

Permalink
feat(appsync): support custom domain mappings (#19368)
Browse files Browse the repository at this point in the history
fixes #18040

This adds support for custom domains with AppSync.
  • Loading branch information
moofish32 committed Mar 18, 2022
1 parent 49ea263 commit 8c7a4ac
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 3 deletions.
37 changes: 37 additions & 0 deletions packages/@aws-cdk/aws-appsync/README.md
Expand Up @@ -285,6 +285,43 @@ 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 * as acm from '@aws-cdk/aws-certificatemanager';
import * as route53 from '@aws-cdk/aws-route53';

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: myDomainName,
},
});

// hosted zone and route53 features
declare const hostedZoneId: string;
declare const zoneName = 'example.com';

// 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 route53.CnameRecord(this, `CnameApiRecord`, {
recordName: 'api',
zone,
domainName: myDomainName,
});
```

## Schema

Every GraphQL Api needs a schema to define the Api. CDK offers `appsync.Schema`
Expand Down
45 changes: 42 additions & 3 deletions 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';
Expand Down Expand Up @@ -254,6 +255,21 @@ export interface LogConfig {
readonly role?: IRole;
}

/**
* Domain name configuration for AppSync
*/
export interface DomainOptions {
/**
* The certificate to use with the domain name.
*/
readonly certificate: ICertificate;

/**
* The actual domain name. For example, `api.example.com`.
*/
readonly domainName: string;
}

/**
* Properties for an AppSync GraphQL API
*/
Expand Down Expand Up @@ -292,6 +308,16 @@ export interface GraphqlApiProps {
* @default - false
*/
readonly xrayEnabled?: boolean;

/**
* The domain name configuration for the GraphQL API
*
* The Route 53 hosted zone and CName DNS record must be configured in addition to this setting to
* enable custom domain URL
*
* @default - no domain name
*/
readonly domainName?: DomainOptions;
}

/**
Expand Down Expand Up @@ -391,7 +417,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);
}
}
Expand Down Expand Up @@ -450,7 +476,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);

Expand All @@ -472,6 +498,19 @@ 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;
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-appsync/package.json
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
36 changes: 36 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync.test.ts
@@ -1,5 +1,6 @@
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';
Expand Down Expand Up @@ -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,
});
});

0 comments on commit 8c7a4ac

Please sign in to comment.