@@ -12,6 +12,7 @@ const bucketUtils = require('./lib/bucketUtils');
1212const uploadDirectory = require ( './lib/upload' ) ;
1313const validateClient = require ( './lib/validate' ) ;
1414const invalidateCloudfrontDistribution = require ( './lib/cloudFront' ) ;
15+ const { groupDomainsByHostedZone} = require ( './lib/route53' ) ;
1516
1617class ServerlessFullstackPlugin {
1718 constructor ( serverless , cliOptions ) {
@@ -256,8 +257,9 @@ class ServerlessFullstackPlugin {
256257 filename : filename
257258 } ) ;
258259
259- this . prepareResources ( resources ) ;
260- return _ . merge ( baseResources , resources ) ;
260+ return this . prepareResources ( resources ) . then ( ( ) => {
261+ return _ . merge ( baseResources , resources ) ;
262+ } ) ;
261263 }
262264
263265 checkForApiGataway ( ) {
@@ -335,9 +337,11 @@ class ServerlessFullstackPlugin {
335337 this . serverless . cli . consoleLog ( ` ${ apiDistributionDomain . OutputValue } (CNAME: ${ cnameDomain } )` ) ;
336338 }
337339
338- prepareResources ( resources ) {
340+ async prepareResources ( resources ) {
339341 const distributionConfig = resources . Resources . ApiDistribution . Properties . DistributionConfig ;
340342
343+ await this . prepareRoute53 ( resources . Resources ) ;
344+
341345 this . prepareLogging ( distributionConfig ) ;
342346 this . prepareDomain ( distributionConfig ) ;
343347 this . preparePriceClass ( distributionConfig ) ;
@@ -353,6 +357,56 @@ class ServerlessFullstackPlugin {
353357
354358 }
355359
360+ async prepareRoute53 ( resources ) {
361+ if ( this . options . domain ) {
362+ const certificate = this . getConfig ( "certificate" , null ) ;
363+ const distributionCertificate = resources . ApiDistribution . Properties . DistributionConfig . ViewerCertificate ;
364+
365+ if ( this . getConfig ( "route53" , false ) === true ) {
366+ const filename = path . resolve ( __dirname , 'lib/resources/templates.yml' ) ;
367+ const content = fs . readFileSync ( filename , 'utf-8' ) ;
368+ const templates = yaml . safeLoad ( content , { filename} ) ;
369+
370+ const domains = Array . isArray ( this . options . domain ) ? this . options . domain : [ this . options . domain ] ;
371+ const domainsByHostedZones = await groupDomainsByHostedZone ( this . serverless , domains ) ;
372+ const domainsWithoutHostedZone = domainsByHostedZones
373+ . filter ( ( hostedZone ) => ! hostedZone . Id )
374+ . reduce ( ( acc , hostedZone ) => [ ...acc , ...hostedZone . domains ] , [ ] ) ;
375+ const filteredDomainsByHostedZones = domainsByHostedZones
376+ . filter ( hostedZone => ! ! hostedZone . Id && hostedZone . domains . length ) ;
377+
378+ if ( domainsWithoutHostedZone ?. length > 0 )
379+ this . serverless . cli . log ( `No hosted zones found for ${ domainsWithoutHostedZone } , records pointing to`
380+ + ` the cloudfront domain will have to be added manually.` , "Route53" , { color : "orange" , underline : true } ) ;
381+
382+ const aliasTemplate = templates . Route53AliasTemplate ;
383+ const recordSetTemplate = aliasTemplate . Properties . RecordSets . pop ( ) ;
384+ recordSetTemplate . AliasTarget . DNSName = { "Fn::GetAtt" : [ "ApiDistribution" , "DomainName" ] } ;
385+
386+ for ( const hostedZone of filteredDomainsByHostedZones ) {
387+ const recordSets = hostedZone . domains . map ( domain => ( { ...recordSetTemplate , Name : domain } ) ) ;
388+ const alias = { ...aliasTemplate , Properties : { ...aliasTemplate . Properties , RecordSets : recordSets , HostedZoneId : hostedZone . Id } } ;
389+ resources [ "Route53AliasHZ" + hostedZone . Id ] = alias ;
390+ }
391+
392+ // only create and override if not specified
393+ if ( certificate === null ) {
394+ const certTemplate = templates . CertTemplate ;
395+ certTemplate . Properties . DomainName = domains [ 0 ] ;
396+ if ( domains . length > 1 ) certTemplate . Properties . SubjectAlternativeNames = domains . slice ( 1 ) ;
397+
398+ const route53domainValidations = filteredDomainsByHostedZones . flatMap ( hz => hz . domains . map ( DomainName => ( { DomainName, HostedZoneId : hz . Id } ) ) ) ;
399+ const manualValidations = domainsWithoutHostedZone . map ( DomainName => ( { DomainName, ValidationDomain : DomainName } ) ) ;
400+ certTemplate . Properties . DomainValidationOptions = [ ...route53domainValidations , ...manualValidations ] ;
401+
402+ const certResourceName = "ApiDistributionCertificate" ;
403+ resources [ certResourceName ] = certTemplate ;
404+ distributionCertificate . AcmCertificateArn = { Ref : certResourceName } ;
405+ }
406+ }
407+ }
408+ }
409+
356410 prepareLogging ( distributionConfig ) {
357411 const loggingBucket = this . getConfig ( 'logging.bucket' , null ) ;
358412
@@ -428,7 +482,7 @@ class ServerlessFullstackPlugin {
428482 if ( certificate !== null ) {
429483 this . serverless . cli . log ( `Configuring SSL certificate...` ) ;
430484 distributionConfig . ViewerCertificate . AcmCertificateArn = certificate ;
431- } else {
485+ } else if ( ! distributionConfig . ViewerCertificate . AcmCertificateArn ) {
432486 delete distributionConfig . ViewerCertificate ;
433487 }
434488 }
0 commit comments