@@ -22,12 +22,19 @@ class CreateCertificatePlugin {
2222 'create'
2323 ]
2424 } ,
25+ 'remove-cert' : {
26+ usage : 'removes the certificate previously created by create-cert command' ,
27+ lifecycleEvents : [
28+ 'remove'
29+ ]
30+ }
2531 } ;
2632
2733 this . hooks = {
2834 'create-cert:create' : this . createCertificate . bind ( this ) ,
2935 'after:deploy:deploy' : this . certificateSummary . bind ( this ) ,
3036 'after:info:info' : this . certificateSummary . bind ( this ) ,
37+ 'remove-cert:remove' : this . deleteCertificate . bind ( this ) ,
3138 } ;
3239
3340 this . variableResolvers = {
@@ -224,6 +231,46 @@ class CreateCertificatePlugin {
224231 } )
225232 }
226233
234+ /**
235+ * Deletes the certificate for the given options set in serverless.yml under custom->customCertificate
236+ * (if it exists)
237+ */
238+ deleteCertificate ( ) {
239+ this . initializeVariables ( ) ;
240+ if ( ! this . enabled ) {
241+ return this . reportDisabled ( ) ;
242+ }
243+ this . serverless . cli . log ( `Trying to delete certificate for ${ this . domain } in ${ this . region } ...` ) ;
244+ return this . getExistingCertificate ( ) . then ( existingCert => {
245+
246+
247+ if ( ! existingCert ) {
248+ this . serverless . cli . log ( `Certificate for ${ this . domain } in ${ this . region } does not exist. Skipping ...` ) ;
249+ return ;
250+ }
251+
252+ let params = {
253+ CertificateArn : existingCert . CertificateArn
254+ } ;
255+
256+ return this . acm . describeCertificate ( params ) . promise ( )
257+ . then ( certificate => this . deleteRecordSetForDnsValidation ( certificate ) )
258+ . then ( ( ) => this . acm . deleteCertificate ( params ) . promise ( ) )
259+ . then ( ( ) => this . serverless . cli . log ( `deleted cert: ${ existingCert . CertificateArn } ` ) )
260+ . catch ( error => {
261+ this . serverless . cli . log ( 'could not delete cert' , error ) ;
262+ console . log ( 'problem' , error ) ;
263+ throw error ;
264+ } ) ;
265+
266+
267+ } ) . catch ( error => {
268+ this . serverless . cli . log ( 'could not get certs' , error ) ;
269+ console . log ( 'problem' , error ) ;
270+ throw error ;
271+ } )
272+ }
273+
227274 waitUntilCertificateIsValidated ( certificateArn ) {
228275 this . serverless . cli . log ( 'waiting until certificate is validated...' ) ;
229276 var params = {
@@ -301,6 +348,93 @@ class CreateCertificatePlugin {
301348 } ) ;
302349 }
303350
351+ /**
352+ * deletes the record set required for validation type dns.
353+ */
354+ deleteRecordSetForDnsValidation ( certificate ) {
355+ return this . getHostedZoneIds ( ) . then ( ( hostedZoneIds ) => {
356+
357+ return Promise . all ( hostedZoneIds . map ( ( { hostedZoneId, Name } ) => {
358+
359+ // Make sure the recordset exist before batching up a delete (in case they got manually deleted),
360+ // otherwise the whole batch will fail
361+ return this . listResourceRecordSets ( hostedZoneId ) . then ( existingRecords => {
362+
363+ let changes = certificate . Certificate . DomainValidationOptions
364+ . filter ( ( { DomainName} ) => DomainName . endsWith ( Name ) )
365+ . map ( opt => opt . ResourceRecord )
366+ . filter ( record => existingRecords . find ( x => x . Name === record . Name && x . Type === record . Type ) )
367+ . map ( record => {
368+ return {
369+ Action : "DELETE" ,
370+ ResourceRecordSet : {
371+ Name : record . Name ,
372+ ResourceRecords : [
373+ {
374+ Value : record . Value
375+ }
376+ ] ,
377+ TTL : 60 ,
378+ Type : record . Type
379+ }
380+ }
381+ } ) ;
382+
383+ if ( changes . length === 0 ) {
384+ this . serverless . cli . log ( 'no matching dns validation record(s) found in route53' ) ;
385+ return ;
386+ }
387+
388+ var params = {
389+ ChangeBatch : {
390+ Changes : changes
391+ } ,
392+ HostedZoneId : hostedZoneId
393+ } ;
394+ return this . route53 . changeResourceRecordSets ( params ) . promise ( ) . then ( recordSetResult => {
395+ this . serverless . cli . log ( `${ changes . length } dns validation record(s) deleted` ) ;
396+ } ) . catch ( error => {
397+ this . serverless . cli . log ( 'could not delete record set(s) for dns validation' , error ) ;
398+ console . log ( 'problem' , error ) ;
399+ throw error ;
400+ } ) ;
401+ } ) ;
402+ } ) ) ;
403+ } ) ;
404+ }
405+
406+ /**
407+ * Lists up all resource recordsets in the given route53 hosted zone.
408+ */
409+ listResourceRecordSets ( hostedZoneId ) {
410+ var initialParams = {
411+ HostedZoneId : hostedZoneId
412+ }
413+
414+ this . serverless . cli . log ( 'listing existing record sets in hosted zone' , hostedZoneId ) ;
415+
416+ let listRecords = ( params ) => this . route53 . listResourceRecordSets ( params ) . promise ( )
417+ . then ( ( { ResourceRecordSets, IsTruncated, NextRecordName, NextRecordType, NextRecordIdentifier } ) => {
418+
419+ if ( IsTruncated ) {
420+ let listMoreParams = Object . assign ( params , {
421+ StartRecordName : NextRecordName ,
422+ StartRecordType : NextRecordType
423+ } ) ;
424+ // Resource record sets that have a routing policy other than simple, should not be the case for our DNS validation records
425+ if ( NextRecordIdentifier ) {
426+ listMoreParams = Object . assign ( listMoreParams , { StartRecordIdentifier : NextRecordIdentifier } ) ;
427+ }
428+
429+ return listRecords ( listMoreParams ) . then ( moreRecords => ResourceRecordSets . concat ( moreRecords ) ) ;
430+ } else {
431+ return ResourceRecordSets ;
432+ }
433+ } ) ;
434+
435+ return listRecords ( initialParams ) ;
436+ }
437+
304438 /**
305439 * Prints out a summary of all certificate related info
306440 */
0 commit comments