in functions/source/ACMCert/lambda_function.py [0:0]
def handler(event, context):
print('Received event: %s' % json.dumps(event))
status = cfnresponse.SUCCESS
physical_resource_id = None
data = {}
reason = None
try:
if event['RequestType'] == 'Create':
token = ''.join(ch for ch in str(event['StackId'] + event['LogicalResourceId']) if ch.isalnum())
token = token[len(token)-32:]
if len(event['ResourceProperties']['HostNames']) > 1:
arn = acm_client.request_certificate(
ValidationMethod='DNS',
DomainName=event['ResourceProperties']['HostNames'][0],
SubjectAlternativeNames=event['ResourceProperties']['HostNames'][1:],
IdempotencyToken=token
)['CertificateArn']
else:
arn = acm_client.request_certificate(
ValidationMethod='DNS',
DomainName=event['ResourceProperties']['HostNames'][0],
IdempotencyToken=token
)['CertificateArn']
physical_resource_id = arn
logging.info("certificate arn: %s" % arn)
rs = {}
time.sleep(15) # Give ACM time to generate CNAME records
while True:
try:
for d in acm_client.describe_certificate(CertificateArn=arn)['Certificate']['DomainValidationOptions']:
rs[d['ResourceRecord']['Name']] = d['ResourceRecord']['Value']
break
except KeyError:
if (context.get_remaining_time_in_millis() / 1000.00) > 20.0:
print('waiting for ResourceRecord to be available')
time.sleep(15)
else:
logging.error('timed out waiting for ResourceRecord')
status = cfnresponse.FAILED
time.sleep(15)
for r in rs.keys():
change = [{'Action': 'CREATE', 'ResourceRecordSet': {'Name': r, 'Type': 'CNAME', 'TTL': 600, 'ResourceRecords': [{'Value': rs[r]}]}}]
try:
r53_client.change_resource_record_sets(HostedZoneId=event['ResourceProperties']['HostedZoneId'], ChangeBatch={'Changes': change})
except Exception as e:
if 'but it already exists' not in str(e):
raise
while 'PENDING_VALIDATION' in [v['ValidationStatus'] for v in acm_client.describe_certificate(CertificateArn=arn)['Certificate']['DomainValidationOptions']]:
print('waiting for validation to complete')
if (context.get_remaining_time_in_millis() / 1000.00) > 20.0:
time.sleep(15)
else:
logging.error('validation timed out')
status = cfnresponse.FAILED
for r in [v for v in acm_client.describe_certificate(CertificateArn=arn)['Certificate']['DomainValidationOptions']]:
if r['ValidationStatus'] != 'SUCCESS':
logging.debug(r)
status = cfnresponse.FAILED
reason = 'One or more domains failed to validate'
logging.error(reason)
data['Arn'] = arn
# delay as long as possible to give the cert a chance to propogate
# TODO: Remove this commented out code
# while context.get_remaining_time_in_millis() / 1000.00 > 10.0:
# time.sleep(5)
elif event['RequestType'] == 'Update':
reason = 'Exception: Stack updates are not supported'
logging.error(reason)
status = cfnresponse.FAILED
physical_resource_id = event['PhysicalResourceId']
elif event['RequestType'] == 'Delete':
physical_resource_id=event['PhysicalResourceId']
if not re.match(r'arn:[\w+=/,.@-]+:[\w+=/,.@-]+:[\w+=/,.@-]*:[0-9]+:[\w+=,.@-]+(/[\w+=,.@-]+)*', physical_resource_id):
logging.info("PhysicalId is not an acm arn, assuming creation never happened and skipping delete")
else:
rs={}
for d in acm_client.describe_certificate(CertificateArn=physical_resource_id)['Certificate']['DomainValidationOptions']:
rs[d['ResourceRecord']['Name']] = d['ResourceRecord']['Value']
rs = [{'Action': 'DELETE', 'ResourceRecordSet': {'Name': r, 'Type': 'CNAME', 'TTL': 600,'ResourceRecords': [{'Value': rs[r]}]}} for r in rs.keys()]
try:
r53_client.change_resource_record_sets(HostedZoneId=event['ResourceProperties']['HostedZoneId'], ChangeBatch={'Changes': rs})
except r53_client.exceptions.InvalidChangeBatch as e:
pass
time.sleep(30)
try:
acm_client.delete_certificate(CertificateArn=physical_resource_id)
except acm_client.exceptions.ResourceInUseException as e:
time.sleep(60)
acm_client.delete_certificate(CertificateArn=physical_resource_id)
except Exception as e:
logging.error('Exception: %s' % e, exc_info=True)
reason = str(e)
status = cfnresponse.FAILED
finally:
if event['RequestType'] == 'Delete':
try:
wait_message = 'waiting for events for request_id %s to propagate to cloudwatch...' % context.aws_request_id
while not logs_client.filter_log_events(
logGroupName=context.log_group_name,
logStreamNames=[context.log_stream_name],
filterPattern='"%s"' % wait_message
)['events']:
print(wait_message)
time.sleep(5)
except Exception as e:
logging.error('Exception: %s' % e, exc_info=True)
time.sleep(120)
cfnresponse.send(event, context, status, data, physical_resource_id, reason)