Skip to content

Commit

Permalink
feat: improve custom domain integration (#494)
Browse files Browse the repository at this point in the history
  • Loading branch information
bboure authored May 8, 2022
1 parent 26e96ee commit 55d8c1e
Show file tree
Hide file tree
Showing 16 changed files with 784 additions and 205 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ on:
pull_request:
branches:
- master
- v2
- alpha
push:
branches:
- master
- v2
- alpha

jobs:
tests:
Expand Down
50 changes: 21 additions & 29 deletions doc/custom-domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,44 @@ The configuration for custom domain can be found under the `appSync.domain` attr
appSync:
name: my-api
domain:
name: api.example.rehab
certificateArn: arn:aws:acm:us-east-1:123456789:certificate/7e14a3b2-f7a5-4da5-8150-4a03ede7158c
name: api.example.com
hostedZoneId: Z111111QQQQQQQ
```
## Configuration
- `name`: Required. The fully qualified domain name to assiciate this API to.
- `certificateArn`: Required. A valid certificate ARN for the domain name.
- `certificateArn`: Optional. A valid certificate ARN for the domain name. See [Certificate](#certificate).
- `useCloudFormation`: Boolean. Optional. Wheter to use CloudFormation or CLI commands to manage the domain. See [Using CloudFormation or CLI commands](#using-cloudformation-vs-the-cli-commands). Defaults to `true`.
- `retain`: Boolean. Optional. Whether to retain the domain and domain association when they are removed from CloudFormation. Defaults to `false`. See [Ejecting from CloudFormation](#ejecting-from-cloudformation)
- `route53`: See [Route53 configuration](#route53-configuration). Defaults to `true`
- `retain`: Boolean, optional. Whether to retain the domain and domain association when they are removed from CloudFormation. Defaults to `false`. See [Ejecting from CloudFormation](#ejecting-from-cloudformation)
- `hostedZoneId`: Boolean, conditional. The Route53 hosted zone id where to create the certificate validation and/or AppSync Alias records. Required if `useCloudFormation` is `true` and `certificateArn` is not provided.
- `hostedZoneName`: The hosted zone name where to create the route53 Alias record. If `certificateArn` is provided, it takes precedence over `hostedZoneName`.
- `route53`: Boolean. Wether to create the Route53 Alias record for this domain. Set to `false` if you don't use Route53. Defaults to `true`.

## Certificate

This plugin does not provide any way to generate or manage your domain certificate. This is usually a set-and-forget kind of operation. You still need to provide its ARN and it must be a valid certificate for the provided domain name.
If `useCloudFormation` is `true` and a valid `certificateArn` is not provided, a certificate will be created for the provided domain `name` using CloudFormation. You must provide the `hostedZoneId`
where the DNS validation records for the certificate will be created.

## Route53 configuration
⚠️ Any change that requires a change of certificate attached to the domain requires a replacement of the AppSync domain resource. CloudFormation will usually fail with the following error when that happens:

When `true`, this plugin will try to create a Route53 CNAME entry in the Hosted Zone corresponding to the domain. This plugin will do its best to find the best Hosted Zone that matches the domain name.

When `false`, no CNAME record will be created.

You can also specify which hosted zone you want to create the record into:

- `hostedZoneName`: The specific hosted zone name where to create the CNAME record.
- `hostedZoneId`: The specific hosted zone id where to create the CNAME record.

example:

```yaml
appSync:
domain: api.example.com
route53:
hostedZoneId: ABCDEFGHIJ
```bash
CloudFormation cannot update a stack when a custom-named resource requires replacing. Rename api.example.com and update the stack again.
```

If `useCloudFormation` is `false`, when creating the domain with the `domain create` command, this plugin will try to find an existing certificate that
matches the given domain. If no valid certificate is found, an error will be thrown. No certificate will be auto-generated.

## Using CloudFormation vs the CLI commands

There are two ways to manage your custom domain:

- using CloudFormation
- using CloudFormation (default)
- using the CLI [commands](commands.md#domain)

If `useCloudFormation` is set to `true`, the domain and domain association will be automatically created and managed by CloudFormation. However, in some cases you might not want that.
If `useCloudFormation` is set to `true`, the domain, domain association, and optionally, the domain certificate will be automatically created and managed by CloudFormation. However, in some cases you might not want that.

For example, if you wanted to use blue/green deployments, you might need to associate APIs from different stacks to the same domain. In that case, the only way to do it is to use the CLI.
For example, if you want to use blue/green deployments, you might need to associate APIs from different stacks to the same domain. In that case, the only way to do it is to use the CLI.

For more information about managing domains with the CLI, see the [Commands](commands.md#domain) section.

Expand Down Expand Up @@ -83,15 +75,15 @@ You can use different domains by stage easily thanks to [Serverless Framework St
params:
prod:
domain: api.example.com
domainCert: arn:aws:acm:us-east-1:123456789:certificate/7e14a3b2-f7a5-4da5-8150-4a03ede7158c
domainCert: arn:aws:acm:us-east-1:123456789012:certificate/7e14a3b2-f7a5-4da5-8150-4a03ede7158c
staging:
domain: qa.example.com
domainCert: arn:aws:acm:us-east-1:123456789:certificate/61d7d798-d656-4630-9ff9-d77a7d616dbe
domainCert: arn:aws:acm:us-east-1:123456789012:certificate/61d7d798-d656-4630-9ff9-d77a7d616dbe
default:
domain: ${sls:stage}.example.com
domainCert: arn:aws:acm:us-east-1:379730309663:certificate/44211071-e102-4bf4-b7b0-06d0b78cd667
domainCert: arn:aws:acm:us-east-1:123456789012:certificate/44211071-e102-4bf4-b7b0-06d0b78cd667
appSync:
name: my-api
Expand Down
129 changes: 108 additions & 21 deletions src/__tests__/__snapshots__/api.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -97,29 +97,104 @@ Object {
},
"Type": "AWS::AppSync::DomainNameApiAssociation",
},
"GraphQlDomainCertificate": Object {
"DeletionPolicy": "Delete",
"Properties": Object {
"DomainName": "api.example.com",
"DomainValidationOptions": Array [
Object {
"DomainName": "api.example.com",
"HostedZoneId": "Z111111QQQQQQQ",
},
],
"ValidationMethod": "DNS",
},
"Type": "AWS::CertificateManager::Certificate",
},
"GraphQlDomainName": Object {
"DeletionPolicy": "Delete",
"Properties": Object {
"CertificateArn": "arn:aws:acm:us-east-1:1234567890:certificate/e4b6e9be-1aa7-458d-880e-069622e5be52",
"CertificateArn": Object {
"Ref": "GraphQlDomainCertificate",
},
"DomainName": "api.example.com",
},
"Type": "AWS::AppSync::DomainName",
},
"GraphQlDomainRoute53Record": Object {
"DeletionPolicy": "Delete",
"Properties": Object {
"HostedZoneName": "example.com.",
"AliasTarget": Object {
"DNSName": Object {
"Fn::GetAtt": Array [
"GraphQlDomainName",
"AppSyncDomainName",
],
},
"EvaluateTargetHealth": false,
"HostedZoneId": Object {
"Fn::GetAtt": Array [
"GraphQlDomainName",
"HostedZoneId",
],
},
},
"HostedZoneId": "Z111111QQQQQQQ",
"Name": "api.example.com",
"ResourceRecords": Array [
Object {
"Type": "A",
},
"Type": "AWS::Route53::RecordSet",
},
}
`;

exports[`Domains should generate domain resources with custom certificate ARN 1`] = `
Object {
"GraphQlDomainAssociation": Object {
"DeletionPolicy": "Delete",
"DependsOn": Array [
"GraphQlDomainName",
],
"Properties": Object {
"ApiId": Object {
"Fn::GetAtt": Array [
"GraphQlApi",
"ApiId",
],
},
"DomainName": "api.example.com",
},
"Type": "AWS::AppSync::DomainNameApiAssociation",
},
"GraphQlDomainName": Object {
"DeletionPolicy": "Delete",
"Properties": Object {
"CertificateArn": "arn:aws:acm:us-east-1:1234567890:certificate/e4b6e9be-1aa7-458d-880e-069622e5be52",
"DomainName": "api.example.com",
},
"Type": "AWS::AppSync::DomainName",
},
"GraphQlDomainRoute53Record": Object {
"DeletionPolicy": "Delete",
"Properties": Object {
"AliasTarget": Object {
"DNSName": Object {
"Fn::GetAtt": Array [
"GraphQlDomainName",
"AppSyncDomainName",
],
},
],
"TTL": 300,
"Type": "CNAME",
"EvaluateTargetHealth": false,
"HostedZoneId": Object {
"Fn::GetAtt": Array [
"GraphQlDomainName",
"HostedZoneId",
],
},
},
"HostedZoneName": "example.com.",
"Name": "api.example.com",
"Type": "A",
},
"Type": "AWS::Route53::RecordSet",
},
Expand Down Expand Up @@ -155,18 +230,24 @@ Object {
"GraphQlDomainRoute53Record": Object {
"DeletionPolicy": "Delete",
"Properties": Object {
"HostedZoneId": "ABCDEFGHI",
"Name": "api.example.com",
"ResourceRecords": Array [
Object {
"AliasTarget": Object {
"DNSName": Object {
"Fn::GetAtt": Array [
"GraphQlDomainName",
"AppSyncDomainName",
],
},
],
"TTL": 300,
"Type": "CNAME",
"EvaluateTargetHealth": false,
"HostedZoneId": Object {
"Fn::GetAtt": Array [
"GraphQlDomainName",
"HostedZoneId",
],
},
},
"HostedZoneId": "Z111111QQQQQQQ",
"Name": "api.example.com",
"Type": "A",
},
"Type": "AWS::Route53::RecordSet",
},
Expand Down Expand Up @@ -202,18 +283,24 @@ Object {
"GraphQlDomainRoute53Record": Object {
"DeletionPolicy": "Delete",
"Properties": Object {
"HostedZoneName": "example.com.",
"Name": "foo.api.example.com",
"ResourceRecords": Array [
Object {
"AliasTarget": Object {
"DNSName": Object {
"Fn::GetAtt": Array [
"GraphQlDomainName",
"AppSyncDomainName",
],
},
],
"TTL": 300,
"Type": "CNAME",
"EvaluateTargetHealth": false,
"HostedZoneId": Object {
"Fn::GetAtt": Array [
"GraphQlDomainName",
"HostedZoneId",
],
},
},
"HostedZoneName": "example.com.",
"Name": "foo.api.example.com",
"Type": "A",
},
"Type": "AWS::Route53::RecordSet",
},
Expand Down
23 changes: 17 additions & 6 deletions src/__tests__/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,19 @@ describe('Domains', () => {
});

it('should generate domain resources', () => {
const api = new Api(
given.appSyncConfig({
domain: {
name: 'api.example.com',
hostedZoneId: `Z111111QQQQQQQ`,
},
}),
plugin,
);
expect(api.compileCustomDomain()).toMatchSnapshot();
});

it('should generate domain resources with custom certificate ARN', () => {
const api = new Api(
given.appSyncConfig({
domain: {
Expand Down Expand Up @@ -814,9 +827,8 @@ describe('Domains', () => {
name: 'api.example.com',
certificateArn:
'arn:aws:acm:us-east-1:1234567890:certificate/e4b6e9be-1aa7-458d-880e-069622e5be52',
route53: {
hostedZoneId: 'ABCDEFGHI',
},
hostedZoneId: 'Z111111QQQQQQQ',
route53: true,
},
}),
plugin,
Expand All @@ -831,9 +843,8 @@ describe('Domains', () => {
name: 'foo.api.example.com',
certificateArn:
'arn:aws:acm:us-east-1:1234567890:certificate/e4b6e9be-1aa7-458d-880e-069622e5be52',
route53: {
hostedZoneName: 'example.com.',
},
hostedZoneName: 'example.com.',
route53: true,
},
}),
plugin,
Expand Down
Loading

0 comments on commit 55d8c1e

Please sign in to comment.