1+ const { getCloudFrontDomainName } = require ( "./cloudFront" ) ;
2+ const { getCertificateArn, requestCertificateWithDNS} = require ( "./acm" ) ;
3+
4+ const entity = 'Fullstack'
5+
6+ const getHostedZoneForDomain = async ( awsClient , domainName ) => {
7+ const r53response = await awsClient . request ( 'Route53' , 'listHostedZones' , { } ) ,
8+ hostedZone = r53response . HostedZones
9+ . find ( hostedZone => `${ domainName } .` . includes ( hostedZone . Name ) ) ;
10+
11+ if ( ! hostedZone ) throw `Domain is not managed by AWS, you will have to add a record for ${ domainName } manually.` ;
12+
13+ return hostedZone ;
14+ } ;
15+
16+ const checkChangeStatus = async ( awsClient , changeInfo ) => {
17+ const getChangeParams = {
18+ Id : changeInfo . Id
19+ } ,
20+ getChangeResponse = await awsClient . request ( 'Route53' , 'getChange' , getChangeParams ) ;
21+
22+ return getChangeResponse . ChangeInfo . Status === 'INSYNC' ;
23+ } ;
24+
25+ const waitForChange = async ( checkChange ) => {
26+ const isChangeComplete = await checkChange ;
27+
28+ if ( isChangeComplete ) {
29+ return isChangeComplete
30+ } else {
31+ await setTimeout ( waitForChange , 1000 ) ;
32+ } ;
33+ } ;
34+
35+ const entryExists = async ( awsClient , hostedZone , domainName , target ) => {
36+ const requestParams = {
37+ HostedZoneId : hostedZone . Id
38+ } ,
39+ r53response = await awsClient . request ( 'Route53' , 'listResourceRecordSets' , requestParams )
40+ sets = r53response . ResourceRecordSets ;
41+
42+ return sets . find ( set => set . Name === `${ domainName } .` && set . AliasTarget ?. DNSName === `${ target } .` ) ;
43+ }
44+
45+ const addAliasRecord = async ( serverless , domainName ) => {
46+ const awsClient = serverless . getProvider ( 'aws' )
47+ target = await getCloudFrontDomainName ( serverless ) ,
48+ hostedZone = await getHostedZoneForDomain ( awsClient , domainName ) ;
49+
50+ if ( await entryExists ( awsClient , hostedZone , domainName , target ) ) return ;
51+
52+ serverless . cli . log ( `Adding ALIAS record for ${ domainName } to point to ${ target } ...` , entity ) ;
53+
54+ const changeRecordParams = {
55+ HostedZoneId : hostedZone . Id ,
56+ ChangeBatch : {
57+ Changes : [
58+ {
59+ Action : 'UPSERT' ,
60+ ResourceRecordSet : {
61+ Name : domainName ,
62+ Type : 'A' ,
63+ AliasTarget : {
64+ HostedZoneId : 'Z2FDTNDATAQYW2' , // global CloudFront HostedZoneId
65+ DNSName : target ,
66+ EvaluateTargetHealth : false
67+ }
68+ }
69+ }
70+ ]
71+ }
72+ } ,
73+ changeRecordResult = await awsClient . request ( 'Route53' , 'changeResourceRecordSets' , changeRecordParams ) ;
74+
75+ // wait for DNS entry
76+ await waitForChange ( ( ) => checkChangeStatus ( awsClient , changeRecordResult . ChangeInfo ) ) ;
77+
78+ serverless . cli . log ( `ALIAS ${ domainName } -> ${ target } successfully added.` , entity ) ;
79+
80+ // waitFor can't be called using Provider.request yet
81+ /*
82+ waitForRecordParams = {
83+ Id: changeRecordResult.ChangeInfo.Id
84+ },
85+
86+ {err, waitForRecordResult} = await awsClient.request('Route53', 'waitFor', 'resourceRecordSetsChanged', waitForRecordParams)
87+
88+ serverless.cli.log(err)
89+ serverless.cli.log(waitForRecordResult)
90+ */
91+ } ;
92+
93+ const setupCertificate = async ( serverless , domainName ) => {
94+ const existingCertificateArn = await getCertificateArn ( serverless , domainName ) ;
95+ if ( existingCertificateArn ) return existingCertificateArn ;
96+
97+ serverless . cli . log ( `Requesting certificate for ${ domainName } ...` , entity ) ;
98+
99+ const awsClient = serverless . getProvider ( 'aws' )
100+ hostedZone = await getHostedZoneForDomain ( awsClient , domainName ) ,
101+ getCertificateRecord = async ( serverless , domainName ) => {
102+ const certificaterequest = await requestCertificateWithDNS ( serverless , domainName ) ;
103+ return certificaterequest . DomainValidationOptions [ 0 ] . ResourceRecord
104+ } ,
105+ // sometimes the ResourceRecord entry isn't immediately available, so we wait until it is
106+ // the anonymous function given to waitForChange has to call itself after waitForChange returns it so we get the value
107+ certificateResourceRecord = await ( await waitForChange ( ( ) => getCertificateRecord ( serverless , domainName ) ) ) ( ) ,
108+ changeRecordParams = {
109+ HostedZoneId : hostedZone . Id ,
110+ ChangeBatch : {
111+ Changes : [
112+ {
113+ Action : 'UPSERT' ,
114+ ResourceRecordSet : {
115+ Name : certificateResourceRecord . Name ,
116+ Type : certificateResourceRecord . Type ,
117+ TTL : 60 ,
118+ ResourceRecords : [
119+ {
120+ Value : certificateResourceRecord . Value
121+ }
122+ ]
123+ }
124+ }
125+ ]
126+ }
127+ } ,
128+ changeRecordResult = await awsClient . request ( 'Route53' , 'changeResourceRecordSets' , changeRecordParams ) ;
129+
130+ // wait for DNS entry
131+ await waitForChange ( ( ) => checkChangeStatus ( awsClient , changeRecordResult . ChangeInfo ) ) ;
132+
133+ // wait for issued certificate
134+ await waitForChange ( ( ) => getCertificateArn ( serverless , domainName ) ) ;
135+
136+ serverless . cli . log ( `Certificate for ${ domainName } successfully issued.` , entity ) ;
137+ } ;
138+
139+ module . exports = {
140+ addAliasRecord,
141+ setupCertificate
142+ } ;
0 commit comments