Skip to content

Commit

Permalink
🐛 Fixed resource failing to delete when the certificate was deleted
Browse files Browse the repository at this point in the history
  • Loading branch information
dflook committed Jan 31, 2019
1 parent ca0b1fd commit ca6f375
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 26 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.5.1] - 2019-01-31

### Fixed
- Cloudformation resource failing to delete when the certificate was deleted

## [1.5.0] - 2019-01-26
### Added
- `Route53RoleArn` is now a property of DomainValidationOption, allowing a different role per hosted zone
Expand Down Expand Up @@ -40,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- First release

[1.5.1]: https://github.com/dflook/cloudformation-dns-certificate/compare/1.5.0...1.5.1
[1.5.0]: https://github.com/dflook/cloudformation-dns-certificate/compare/a64051e43ae8696c898b6634fbe663abc4a87785...1.5.0
[1.4.0]: https://github.com/dflook/cloudformation-dns-certificate/compare/d0884b638cb2e7873aa7b7f9fda2a1bf377d8892...a64051e43ae8696c898b6634fbe663abc4a87785
[1.3.0]: https://github.com/dflook/cloudformation-dns-certificate/compare/91ef66d068be9fbc97882ae8c6bf51e0d875f9fd...d0884b638cb2e7873aa7b7f9fda2a1bf377d8892
Expand Down
2 changes: 1 addition & 1 deletion cloudformation.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"Properties": {
"Code": {
"ZipFile": "l='FAILED'\nk='ResourceProperties'\nj='Certificate'\ni=' missing'\nh='DNS'\ng='Region'\nZ='OldResourceProperties'\nY=True\nX='DomainName'\nW='Route53RoleArn'\nQ='Reinvoked'\nP='ValidationMethod'\nM='Status'\nF='DomainValidationOptions'\nI=RuntimeError\nH='Tags'\nG=False\nD=None\nB='PhysicalResourceId'\nimport copy as J,hashlib as a,json,logging as A,time as N\nfrom boto3 import client as K\nfrom botocore.exceptions import ClientError as b,ParamValidationError as c\nfrom botocore.vendored import requests as d\nE=0\nC=A.getLogger()\nC.setLevel(A.INFO)\ndef L(event):A=event;C.info(A);B=d.put(A['ResponseURL'],json=A,headers={'content-type':''});C.info(B.content);B.raise_for_status()\ndef R(props,i_token):\n\tA=props;B=J.copy(A);del B['ServiceToken'];B.pop(g,D);B.pop(H,D);B.pop(W,D)\n\tif P in A:\n\t\tif A[P]==h:\n\t\t\ttry:\n\t\t\t\tfor C in set([A[X]]+A.get('SubjectAlternativeNames',[])):S(C,A)\n\t\t\texcept KeyError:raise I(F+i)\n\t\t\tdel B[F]\n\treturn E.request_certificate(IdempotencyToken=i_token,**B)['CertificateArn']\ndef O(arn,props):\n\tA=props\n\tif H in A:E.add_tags_to_certificate(CertificateArn=arn,Tags=A[H])\ndef S(name,props):\n\tC='.';B=name;B=B.rstrip(C);D={A[X].rstrip(C):A for A in(props[F])};A=B.split(C)\n\twhile len(A):\n\t\tif C.join(A)in D:return D[C.join(A)]\n\t\tA=A[1:]\n\traise I(F+i+' for '+B)\ndef T(event,props):\n\tc='Value';b='Type';a='Name';Z='ValidationStatus';V='PENDING_VALIDATION';Q='ResourceRecord';I=event;H=props\n\tif P in H and H[P]==h:\n\t\tJ=G\n\t\twhile not J:\n\t\t\tJ=Y;L=E.describe_certificate(CertificateArn=I[B])[j];C.info(L)\n\t\t\tif L[M]!=V:return\n\t\t\tfor A in L[F]:\n\t\t\t\tif Z not in A or Q not in A:J=G;continue\n\t\t\t\tif A[Z]==V:R=S(A[X],H);T=R.get(W,H.get(W,D));O=K('sts').assume_role(RoleArn=T,RoleSessionName=('DNSCertificate'+I['LogicalResourceId'])[:64],DurationSeconds=900)['Credentials']if T is not D else{};U=K('route53',aws_access_key_id=O.get('AccessKeyId',D),aws_secret_access_key=O.get('SecretAccessKey',D),aws_session_token=O.get('SessionToken',D)).change_resource_record_sets(HostedZoneId=R['HostedZoneId'],ChangeBatch={'Comment':'Domain validation for '+I[B],'Changes':[{'Action':'UPSERT','ResourceRecordSet':{a:A[Q][a],b:A[Q][b],'TTL':60,'ResourceRecords':[{c:A[Q][c]}]}}]});C.info(U)\n\t\t\tN.sleep(1)\ndef e(event):A=event;B=J.copy(A[Z]);B.pop(H,D);C=J.copy(A[k]);C.pop(H,D);return B!=C\ndef U(arn,context):\n\twhile context.get_remaining_time_in_millis()/1000>30:\n\t\tA=E.describe_certificate(CertificateArn=arn)[j];C.info(A)\n\t\tif A[M]=='ISSUED':return Y\n\t\telif A[M]==l:raise I(A.get('FailureReason','Failed to issue certificate'))\n\t\tN.sleep(5)\n\treturn G\ndef V(event,context):\n\tA=event\n\tif A.get(Q,G):raise I('Certificate not issued in time')\n\tA[Q]=Y;C.info('Reinvoking');C.info(A);K('lambda').invoke(FunctionName=context.invoked_function_arn,InvocationType='Event',Payload=json.dumps(A).encode())\ndef f(arn,context):\n\tF='Error';A='Timeout waiting for delete_certificate'\n\twhile context.get_remaining_time_in_millis()/1000>30:\n\t\ttry:E.delete_certificate(CertificateArn=arn)\n\t\texcept b as B:\n\t\t\tD=B.response[F]['Code'];A=B.response[F]['Message'];C.info(A)\n\t\t\tif D=='ResourceInUseException':N.sleep(5);continue\n\t\t\telif D in['ResourceNotFoundException','ValidationException']:return\n\t\t\tbreak\n\t\texcept c:return\n\traise I(A)\ndef handler(event,context):\n\tW='None';P='RequestType';J=context;A=event;C.info(A)\n\ttry:\n\t\tN=a.new('md5',(A['RequestId']+A['StackId']).encode()).hexdigest();F=A[k];global E;E=K('acm',region_name=F.get(g,D));A[M]='SUCCESS'\n\t\tif A[P]=='Create':\n\t\t\tif A.get(Q,G)is G:A[B]=W;A[B]=R(F,N);O(A[B],F)\n\t\t\tT(A,F)\n\t\t\tif U(A[B],J):return L(A)\n\t\t\telse:return V(A,J)\n\t\telif A[P]=='Delete':\n\t\t\tif A[B]!=W:f(A[B],J)\n\t\t\treturn L(A)\n\t\telif A[P]=='Update':\n\t\t\tif e(A):\n\t\t\t\tif A.get(Q,G)is G:A[B]=R(F,N);O(A[B],F)\n\t\t\t\tT(A,F)\n\t\t\t\tif not U(A[B],J):return V(A,J)\n\t\t\telse:\n\t\t\t\tif H in A[Z]:E.remove_tags_from_certificate(CertificateArn=A[B],Tags=A[Z][H])\n\t\t\t\tO(A[B],F)\n\t\t\treturn L(A)\n\t\telse:raise I('Unknown RequestType')\n\texcept Exception as S:C.exception('');A[M]=l;A['Reason']=str(S);return L(A)"
"ZipFile": "l='FAILED'\nk='ResourceProperties'\nj='Certificate'\ni=' missing'\nh='DNS'\ng='Region'\nZ='OldResourceProperties'\nY=True\nX='DomainName'\nW='Route53RoleArn'\nQ='Reinvoked'\nP='ValidationMethod'\nM='Status'\nF='DomainValidationOptions'\nI=RuntimeError\nH='Tags'\nG=False\nD=None\nC='PhysicalResourceId'\nimport copy as J,hashlib as a,json,logging as A,time as N\nfrom boto3 import client as K\nfrom botocore.exceptions import ClientError as b,ParamValidationError as c\nfrom botocore.vendored import requests as d\nE=0\nB=A.getLogger()\nB.setLevel(A.INFO)\ndef L(event):A=event;B.info(A);C=d.put(A['ResponseURL'],json=A,headers={'content-type':''});B.info(C.content);C.raise_for_status()\ndef R(props,i_token):\n\tA=props;B=J.copy(A);del B['ServiceToken'];B.pop(g,D);B.pop(H,D);B.pop(W,D)\n\tif P in A:\n\t\tif A[P]==h:\n\t\t\ttry:\n\t\t\t\tfor C in set([A[X]]+A.get('SubjectAlternativeNames',[])):S(C,A)\n\t\t\texcept KeyError:raise I(F+i)\n\t\t\tdel B[F]\n\treturn E.request_certificate(IdempotencyToken=i_token,**B)['CertificateArn']\ndef O(arn,props):\n\tA=props\n\tif H in A:E.add_tags_to_certificate(CertificateArn=arn,Tags=A[H])\ndef S(name,props):\n\tC='.';B=name;B=B.rstrip(C);D={A[X].rstrip(C):A for A in(props[F])};A=B.split(C)\n\twhile len(A):\n\t\tif C.join(A)in D:return D[C.join(A)]\n\t\tA=A[1:]\n\traise I(F+i+' for '+B)\ndef T(event,props):\n\tc='Value';b='Type';a='Name';Z='ValidationStatus';V='PENDING_VALIDATION';Q='ResourceRecord';I=event;H=props\n\tif P in H and H[P]==h:\n\t\tJ=G\n\t\twhile not J:\n\t\t\tJ=Y;L=E.describe_certificate(CertificateArn=I[C])[j];B.info(L)\n\t\t\tif L[M]!=V:return\n\t\t\tfor A in L[F]:\n\t\t\t\tif Z not in A or Q not in A:J=G;continue\n\t\t\t\tif A[Z]==V:R=S(A[X],H);T=R.get(W,H.get(W,D));O=K('sts').assume_role(RoleArn=T,RoleSessionName=('DNSCertificate'+I['LogicalResourceId'])[:64],DurationSeconds=900)['Credentials']if T is not D else{};U=K('route53',aws_access_key_id=O.get('AccessKeyId',D),aws_secret_access_key=O.get('SecretAccessKey',D),aws_session_token=O.get('SessionToken',D)).change_resource_record_sets(HostedZoneId=R['HostedZoneId'],ChangeBatch={'Comment':'Domain validation for '+I[C],'Changes':[{'Action':'UPSERT','ResourceRecordSet':{a:A[Q][a],b:A[Q][b],'TTL':60,'ResourceRecords':[{c:A[Q][c]}]}}]});B.info(U)\n\t\t\tN.sleep(1)\ndef e(event):A=event;B=J.copy(A[Z]);B.pop(H,D);C=J.copy(A[k]);C.pop(H,D);return B!=C\ndef U(arn,context):\n\twhile context.get_remaining_time_in_millis()/1000>30:\n\t\tA=E.describe_certificate(CertificateArn=arn)[j];B.info(A)\n\t\tif A[M]=='ISSUED':return Y\n\t\telif A[M]==l:raise I(A.get('FailureReason','Failed to issue certificate'))\n\t\tN.sleep(5)\n\treturn G\ndef V(event,context):\n\tA=event\n\tif A.get(Q,G):raise I('Certificate not issued in time')\n\tA[Q]=Y;B.info('Reinvoking');B.info(A);K('lambda').invoke(FunctionName=context.invoked_function_arn,InvocationType='Event',Payload=json.dumps(A).encode())\ndef f(arn,context):\n\tG='Error';F='Failed to delete certificate';A=F\n\twhile context.get_remaining_time_in_millis()/1000>30:\n\t\ttry:E.delete_certificate(CertificateArn=arn);return\n\t\texcept b as C:\n\t\t\tB.exception(F);D=C.response[G]['Code'];A=C.response[G]['Message']\n\t\t\tif D=='ResourceInUseException':N.sleep(5);continue\n\t\t\telif D in['ResourceNotFoundException','ValidationException']:return\n\t\t\traise\n\t\texcept c:B.exception(F);return\n\traise I(A)\ndef handler(event,context):\n\tW='None';P='RequestType';J=context;A=event;B.info(A)\n\ttry:\n\t\tN=a.new('md5',(A['RequestId']+A['StackId']).encode()).hexdigest();F=A[k];global E;E=K('acm',region_name=F.get(g,D));A[M]='SUCCESS'\n\t\tif A[P]=='Create':\n\t\t\tif A.get(Q,G)is G:A[C]=W;A[C]=R(F,N);O(A[C],F)\n\t\t\tT(A,F)\n\t\t\tif U(A[C],J):return L(A)\n\t\t\telse:return V(A,J)\n\t\telif A[P]=='Delete':\n\t\t\tif A[C]!=W:f(A[C],J)\n\t\t\treturn L(A)\n\t\telif A[P]=='Update':\n\t\t\tif e(A):\n\t\t\t\tif A.get(Q,G)is G:A[C]=R(F,N);O(A[C],F)\n\t\t\t\tT(A,F)\n\t\t\t\tif not U(A[C],J):return V(A,J)\n\t\t\telse:\n\t\t\t\tif H in A[Z]:E.remove_tags_from_certificate(CertificateArn=A[C],Tags=A[Z][H])\n\t\t\t\tO(A[C],F)\n\t\t\treturn L(A)\n\t\telse:raise I('Unknown RequestType')\n\texcept Exception as S:B.exception('');A[M]=l;A['Reason']=str(S);return L(A)"
},
"Description": "Cloudformation custom resource for DNS validated certificates",
"Handler": "index.handler",
Expand Down
40 changes: 20 additions & 20 deletions cloudformation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ Resources:
ZipFile: "l='FAILED'\nk='ResourceProperties'\nj='Certificate'\ni=' missing'\n\
h='DNS'\ng='Region'\nZ='OldResourceProperties'\nY=True\nX='DomainName'\n\
W='Route53RoleArn'\nQ='Reinvoked'\nP='ValidationMethod'\nM='Status'\nF='DomainValidationOptions'\n\
I=RuntimeError\nH='Tags'\nG=False\nD=None\nB='PhysicalResourceId'\nimport\
I=RuntimeError\nH='Tags'\nG=False\nD=None\nC='PhysicalResourceId'\nimport\
\ copy as J,hashlib as a,json,logging as A,time as N\nfrom boto3 import\
\ client as K\nfrom botocore.exceptions import ClientError as b,ParamValidationError\
\ as c\nfrom botocore.vendored import requests as d\nE=0\nC=A.getLogger()\n\
C.setLevel(A.INFO)\ndef L(event):A=event;C.info(A);B=d.put(A['ResponseURL'],json=A,headers={'content-type':''});C.info(B.content);B.raise_for_status()\n\
\ as c\nfrom botocore.vendored import requests as d\nE=0\nB=A.getLogger()\n\
B.setLevel(A.INFO)\ndef L(event):A=event;B.info(A);C=d.put(A['ResponseURL'],json=A,headers={'content-type':''});B.info(C.content);C.raise_for_status()\n\
def R(props,i_token):\n\tA=props;B=J.copy(A);del B['ServiceToken'];B.pop(g,D);B.pop(H,D);B.pop(W,D)\n\
\tif P in A:\n\t\tif A[P]==h:\n\t\t\ttry:\n\t\t\t\tfor C in set([A[X]]+A.get('SubjectAlternativeNames',[])):S(C,A)\n\
\t\t\texcept KeyError:raise I(F+i)\n\t\t\tdel B[F]\n\treturn E.request_certificate(IdempotencyToken=i_token,**B)['CertificateArn']\n\
Expand All @@ -27,32 +27,32 @@ Resources:
\ A in(props[F])};A=B.split(C)\n\twhile len(A):\n\t\tif C.join(A)in D:return\
\ D[C.join(A)]\n\t\tA=A[1:]\n\traise I(F+i+' for '+B)\ndef T(event,props):\n\
\tc='Value';b='Type';a='Name';Z='ValidationStatus';V='PENDING_VALIDATION';Q='ResourceRecord';I=event;H=props\n\
\tif P in H and H[P]==h:\n\t\tJ=G\n\t\twhile not J:\n\t\t\tJ=Y;L=E.describe_certificate(CertificateArn=I[B])[j];C.info(L)\n\
\tif P in H and H[P]==h:\n\t\tJ=G\n\t\twhile not J:\n\t\t\tJ=Y;L=E.describe_certificate(CertificateArn=I[C])[j];B.info(L)\n\
\t\t\tif L[M]!=V:return\n\t\t\tfor A in L[F]:\n\t\t\t\tif Z not in A or\
\ Q not in A:J=G;continue\n\t\t\t\tif A[Z]==V:R=S(A[X],H);T=R.get(W,H.get(W,D));O=K('sts').assume_role(RoleArn=T,RoleSessionName=('DNSCertificate'+I['LogicalResourceId'])[:64],DurationSeconds=900)['Credentials']if\
\ T is not D else{};U=K('route53',aws_access_key_id=O.get('AccessKeyId',D),aws_secret_access_key=O.get('SecretAccessKey',D),aws_session_token=O.get('SessionToken',D)).change_resource_record_sets(HostedZoneId=R['HostedZoneId'],ChangeBatch={'Comment':'Domain\
\ validation for '+I[B],'Changes':[{'Action':'UPSERT','ResourceRecordSet':{a:A[Q][a],b:A[Q][b],'TTL':60,'ResourceRecords':[{c:A[Q][c]}]}}]});C.info(U)\n\
\ validation for '+I[C],'Changes':[{'Action':'UPSERT','ResourceRecordSet':{a:A[Q][a],b:A[Q][b],'TTL':60,'ResourceRecords':[{c:A[Q][c]}]}}]});B.info(U)\n\
\t\t\tN.sleep(1)\ndef e(event):A=event;B=J.copy(A[Z]);B.pop(H,D);C=J.copy(A[k]);C.pop(H,D);return\
\ B!=C\ndef U(arn,context):\n\twhile context.get_remaining_time_in_millis()/1000>30:\n\
\t\tA=E.describe_certificate(CertificateArn=arn)[j];C.info(A)\n\t\tif A[M]=='ISSUED':return\
\t\tA=E.describe_certificate(CertificateArn=arn)[j];B.info(A)\n\t\tif A[M]=='ISSUED':return\
\ Y\n\t\telif A[M]==l:raise I(A.get('FailureReason','Failed to issue certificate'))\n\
\t\tN.sleep(5)\n\treturn G\ndef V(event,context):\n\tA=event\n\tif A.get(Q,G):raise\
\ I('Certificate not issued in time')\n\tA[Q]=Y;C.info('Reinvoking');C.info(A);K('lambda').invoke(FunctionName=context.invoked_function_arn,InvocationType='Event',Payload=json.dumps(A).encode())\n\
def f(arn,context):\n\tF='Error';A='Timeout waiting for delete_certificate'\n\
\twhile context.get_remaining_time_in_millis()/1000>30:\n\t\ttry:E.delete_certificate(CertificateArn=arn)\n\
\t\texcept b as B:\n\t\t\tD=B.response[F]['Code'];A=B.response[F]['Message'];C.info(A)\n\
\ I('Certificate not issued in time')\n\tA[Q]=Y;B.info('Reinvoking');B.info(A);K('lambda').invoke(FunctionName=context.invoked_function_arn,InvocationType='Event',Payload=json.dumps(A).encode())\n\
def f(arn,context):\n\tG='Error';F='Failed to delete certificate';A=F\n\t\
while context.get_remaining_time_in_millis()/1000>30:\n\t\ttry:E.delete_certificate(CertificateArn=arn);return\n\
\t\texcept b as C:\n\t\t\tB.exception(F);D=C.response[G]['Code'];A=C.response[G]['Message']\n\
\t\t\tif D=='ResourceInUseException':N.sleep(5);continue\n\t\t\telif D in['ResourceNotFoundException','ValidationException']:return\n\
\t\t\tbreak\n\t\texcept c:return\n\traise I(A)\ndef handler(event,context):\n\
\tW='None';P='RequestType';J=context;A=event;C.info(A)\n\ttry:\n\t\tN=a.new('md5',(A['RequestId']+A['StackId']).encode()).hexdigest();F=A[k];global\
\t\t\traise\n\t\texcept c:B.exception(F);return\n\traise I(A)\ndef handler(event,context):\n\
\tW='None';P='RequestType';J=context;A=event;B.info(A)\n\ttry:\n\t\tN=a.new('md5',(A['RequestId']+A['StackId']).encode()).hexdigest();F=A[k];global\
\ E;E=K('acm',region_name=F.get(g,D));A[M]='SUCCESS'\n\t\tif A[P]=='Create':\n\
\t\t\tif A.get(Q,G)is G:A[B]=W;A[B]=R(F,N);O(A[B],F)\n\t\t\tT(A,F)\n\t\t\
\tif U(A[B],J):return L(A)\n\t\t\telse:return V(A,J)\n\t\telif A[P]=='Delete':\n\
\t\t\tif A[B]!=W:f(A[B],J)\n\t\t\treturn L(A)\n\t\telif A[P]=='Update':\n\
\t\t\tif e(A):\n\t\t\t\tif A.get(Q,G)is G:A[B]=R(F,N);O(A[B],F)\n\t\t\t\t\
T(A,F)\n\t\t\t\tif not U(A[B],J):return V(A,J)\n\t\t\telse:\n\t\t\t\tif\
\ H in A[Z]:E.remove_tags_from_certificate(CertificateArn=A[B],Tags=A[Z][H])\n\
\t\t\t\tO(A[B],F)\n\t\t\treturn L(A)\n\t\telse:raise I('Unknown RequestType')\n\
\texcept Exception as S:C.exception('');A[M]=l;A['Reason']=str(S);return\
\t\t\tif A.get(Q,G)is G:A[C]=W;A[C]=R(F,N);O(A[C],F)\n\t\t\tT(A,F)\n\t\t\
\tif U(A[C],J):return L(A)\n\t\t\telse:return V(A,J)\n\t\telif A[P]=='Delete':\n\
\t\t\tif A[C]!=W:f(A[C],J)\n\t\t\treturn L(A)\n\t\telif A[P]=='Update':\n\
\t\t\tif e(A):\n\t\t\t\tif A.get(Q,G)is G:A[C]=R(F,N);O(A[C],F)\n\t\t\t\t\
T(A,F)\n\t\t\t\tif not U(A[C],J):return V(A,J)\n\t\t\telse:\n\t\t\t\tif\
\ H in A[Z]:E.remove_tags_from_certificate(CertificateArn=A[C],Tags=A[Z][H])\n\
\t\t\t\tO(A[C],F)\n\t\t\treturn L(A)\n\t\telse:raise I('Unknown RequestType')\n\
\texcept Exception as S:B.exception('');A[M]=l;A['Reason']=str(S);return\
\ L(A)"
Description: Cloudformation custom resource for DNS validated certificates
Handler: index.handler
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
setup(
name='troposphere-dns-certificate',
description='Cloudformation DNS validated certificate resource for troposphere',
version='1.5.0',
version='1.5.1',
author='Daniel Flook',
author_email='daniel@flook.org',
url='https://github.com/dflook/cloudformation-dns-certificate',
Expand Down
11 changes: 7 additions & 4 deletions src/troposphere_dns_certificate/certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,16 +259,18 @@ def delete_certificate(arn, context):
"""

err_msg = 'Timeout waiting for delete_certificate'
err_msg = 'Failed to delete certificate'

while (context.get_remaining_time_in_millis() / 1000) > 30:

try:
acm.delete_certificate(CertificateArn=arn)
return
except ClientError as e:
logger.exception('Failed to delete certificate')

err_code = e.response['Error']['Code']
err_msg = e.response['Error']['Message']
logger.info(err_msg)

if err_code == 'ResourceInUseException':
time.sleep(5)
Expand All @@ -278,14 +280,15 @@ def delete_certificate(arn, context):
# If the arn is invalid, it didn't exist anyway.
return

break
raise

except ParamValidationError:
# invalid arn
logger.exception('Failed to delete certificate')
return

raise RuntimeError(err_msg)


def handler(event, context):
"""
Cloudformation custom resource handler
Expand Down

0 comments on commit ca6f375

Please sign in to comment.