diff --git a/code/ddns-lambda.py b/code/ddns-lambda.py index 74878ab..03b4afe 100755 --- a/code/ddns-lambda.py +++ b/code/ddns-lambda.py @@ -73,7 +73,7 @@ LOGGER = logging.getLogger() account_id = None region = None -VERSION = '1.2.0b50' +VERSION = '1.2.0b51' # Read Env variables DEBUG_LOG_LEVEL = os.environ.get('DebugLogLevel', 'INFO') @@ -719,6 +719,10 @@ def lambda_handler( " %s", str(heritage_value) + lineno()) delete_records = True + dns_data_fields = ['zone_id', 'rr_name', 'zone_name', 'rr_type', 'rr_value'] + dns_data_tuple = namedtuple('DnsData', dns_data_fields) + dns_data = [] + # Create OR Delete the A / PTR Record if state == 'running': if not flags['noforward']: @@ -738,6 +742,7 @@ def lambda_handler( f"zone {final_hosted_zone_name} to value {private_ip}" count[create_response] += 1 if create_response == 'success': + dns_data.append(dns_data_tuple(zone_data_forward.zone_id, final_private_hostname, zone_data_forward.name, 'A', private_ip) LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) @@ -754,7 +759,7 @@ def lambda_handler( if len(heritage) > 0: LOGGER.debug( f"Creating heritage TXT resource records {final_private_hostname} with value {heritage_value}: {lineno()}") - create_response = create_resource_record( + create_response=create_resource_record( route53, instance_id, zone_data_forward.zone_id, @@ -763,11 +768,12 @@ def lambda_handler( 'TXT', heritage_value ) - append_msg = f"TXT record in zone id: {zone_data_forward.zone_id} owner {zone_data_forward.owner_account} for hostname {final_private_hostname} " + \ + append_msg=f"TXT record in zone id: {zone_data_forward.zone_id} owner {zone_data_forward.owner_account} for hostname {final_private_hostname} " + f"zone {zone_data_forward.name} to value {heritage_value}" count[create_response] += 1 if create_response == 'success': + dns_data.append(dns_data_tuple(zone_data_forward.zone_id, final_private_hostname, zone_data_forward.name, 'TXT', heritage_value) LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) @@ -791,7 +797,7 @@ def lambda_handler( # fqdn = create_fqdn(final_private_hostname, final_hosted_zone_name) try: if reverse_zone_associated: - create_response = create_resource_record( + create_response=create_resource_record( route53, instance_id, zone_data_reverse.zone_id, @@ -800,10 +806,11 @@ def lambda_handler( 'PTR', final_private_dns_name, ) - append_msg = f"PTR record in zone id: {zone_data_reverse.zone_id} owner {zone_data_reverse.owner_account} for hostname {tag_data['ptr_entry'].hostname} " + \ + append_msg=f"PTR record in zone id: {zone_data_reverse.zone_id} owner {zone_data_reverse.owner_account} for hostname {tag_data['ptr_entry'].hostname} " + \ f"zone {tag_data['ptr_entry'].zonename} to value {final_private_dns_name}" count[create_response] += 1 if create_response == 'success': + dns_data.append(dns_data_tuple(zone_data_reverse.zone_id, tag_data['ptr_entry'].hostname, tag_data['ptr_entry'].zonename, 'PTR', final_private_dns_name) LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) @@ -821,7 +828,7 @@ def lambda_handler( if reverse_zone_associated and len(heritage) > 0: LOGGER.debug( f"Creating heritage TXT resource records {tag_data['ptr_entry'].hostname} with value {heritage_value}: {lineno()}") - create_response = create_resource_record( + create_response=create_resource_record( route53, instance_id, zone_data_reverse.zone_id, @@ -830,11 +837,12 @@ def lambda_handler( 'TXT', heritage_value ) - append_msg = f"TXT record in zone id: {zone_data_reverse.zone_id} owner {zone_data_reverse.owner_account} for hostname {tag_data['ptr_entry'].hostname} " + \ + append_msg=f"TXT record in zone id: {zone_data_reverse.zone_id} owner {zone_data_reverse.owner_account} for hostname {tag_data['ptr_entry'].hostname} " + \ f"zone {tag_data['ptr_entry'].zonename} to value {heritage_value}" count[create_response] += 1 if create_response == 'success': + dns_data.append(dns_data_tuple(zone_data_reverse.zone_id, tag_data['ptr_entry'].hostname, tag_data['ptr_entry'].zonename, 'TXT', heritage_value) LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) @@ -857,7 +865,7 @@ def lambda_handler( else: # not running so delete the records. Note this may leave orphans around if the flags are set and then the host is shut down. We may want to remove no matter what. if not flags['noforward']: # Process and delete A record and associated TXT record - process_response = process_delete_records( + process_response=process_delete_records( route53, instance_id, zone_data_forward.zone_id, @@ -869,14 +877,14 @@ def lambda_handler( ) # only true if existing delete_records and the delete_success from the subroutine is true - delete_records = delete_records and process_response['delete_success'] + delete_records=delete_records and process_response['delete_success'] # append to the lsit - caller_response = caller_response + process_response['msg'] + caller_response=caller_response + process_response['msg'] count[f"delete_success.{process_response.get('delete_success')}"] += 1 if not flags['noreverse']: # Process and delete PTR record and associated TXT record - process_response = process_delete_records( + process_response=process_delete_records( route53, instance_id, zone_data_reverse.zone_id, @@ -887,20 +895,20 @@ def lambda_handler( heritage_value ) # only true if existing delete_records and the delete_success from the subroutine is true - delete_records = delete_records and process_response['delete_success'] + delete_records=delete_records and process_response['delete_success'] # append to the lsit - caller_response = caller_response + process_response['msg'] + caller_response=caller_response + process_response['msg'] count[f"delete_success.{process_response.get('delete_success')}"] += 1 # Process the CNAME record only if it has passed the check if tag_data['option_cname'].valid: - cname_host_name = tag_data['option_cname'].hostname - cname_domain_suffix = tag_data['option_cname'].zonename + cname_host_name=tag_data['option_cname'].hostname + cname_domain_suffix=tag_data['option_cname'].zonename LOGGER.debug("cname record is valid - creating CNAME record:" " %s", str(cname_host_name) + "." + str(cname_domain_suffix) + lineno()) - cname_domain_suffix_item = phz_collection_by_vpc[cname_domain_suffix] - cname_domain_suffix_id = cname_domain_suffix_item['zone_id'] + cname_domain_suffix_item=phz_collection_by_vpc[cname_domain_suffix] + cname_domain_suffix_id=cname_domain_suffix_item['zone_id'] LOGGER.debug("cname_domain_suffix_id: %s", str(cname_domain_suffix_id)) # create CNAME record in private zone @@ -913,7 +921,7 @@ def lambda_handler( LOGGER.debug("cname_domain_suffix_id:" " %s", str(cname_domain_suffix_id) + lineno()) - create_response = create_resource_record( + create_response=create_resource_record( route53, instance_id, cname_domain_suffix_id, @@ -922,10 +930,11 @@ def lambda_handler( 'CNAME', final_private_dns_name ) - append_msg = f"CNAME record in zone id: {cname_domain_suffix_id} owner {phz_collection_by_vpc[cname_domain_suffix]['owner_account']} " + \ + append_msg=f"CNAME record in zone id: {cname_domain_suffix_id} owner {phz_collection_by_vpc[cname_domain_suffix]['owner_account']} " + \ f"hostname {cname_host_name} in zone {cname_domain_suffix} with value {final_private_dns_name}" if create_response == 'success': + dns_data.append(dns_data_tuple(cname_domain_suffix_id, cname_host_name, cname_domain_suffix, 'CNAME', final_private_dns_name) LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) @@ -942,19 +951,21 @@ def lambda_handler( if len(heritage) > 0: LOGGER.debug("Creating heritage TXT resource records %s, with value of %s", TXT_RR_PREFIX + '.' + cname_host_name, str(heritage_value) + lineno()) - create_response = create_resource_record( + cname_host_name_txt=TXT_RR_PREFIX + '.' + cname_host_name + create_response=create_resource_record( route53, instance_id, cname_domain_suffix_id, - TXT_RR_PREFIX + '.' + cname_host_name, + cname_host_name_txt, cname_domain_suffix, 'TXT', heritage_value ) - append_msg = f"TXT for CNAME record in zone id: {cname_domain_suffix_id} owner {phz_collection_by_vpc[cname_domain_suffix]['owner_account']} " + \ + append_msg=f"TXT for CNAME record in zone id: {cname_domain_suffix_id} owner {phz_collection_by_vpc[cname_domain_suffix]['owner_account']} " + \ f"hostname {cname_host_name} in zone {cname_domain_suffix} with value {heritage_value}" if create_response == 'success': + dns_data.append(dns_data_tuple(cname_domain_suffix_id, cname_host_name_txt, cname_domain_suffix, 'TXT', heritage_value) LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) @@ -971,7 +982,7 @@ def lambda_handler( # not running, so process delete CNAME and associated TXT record else: # Process and delete CNAME record and associated TXT record - process_response = process_delete_records( + process_response=process_delete_records( route53, instance_id, cname_domain_suffix_id, @@ -983,9 +994,9 @@ def lambda_handler( ) # only true if existing delete_records and the delete_success from the subroutine is true - delete_records = delete_records and process_response['delete_success'] + delete_records=delete_records and process_response['delete_success'] # append to the lsit - caller_response = caller_response + process_response['msg'] + caller_response=caller_response + process_response['msg'] # Clean up DynamoDB after deleting records if state != 'running': @@ -1007,8 +1018,10 @@ def lambda_handler( instance_id, lineno()) caller_response.insert(0, 'Successfully created recordsets') - count['end'] = datetime.datetime.now() - count['elapsed_ms'] = (count['end'] - count['start']).total_seconds() * 1000.0 + LOGGER.info(f"dns_data records written:\n{pformat(dns_data)}") + + count['end']=datetime.datetime.now() + count['elapsed_ms']=(count['end'] - count['start']).total_seconds() * 1000.0 LOGGER.info(f"{APPNAME} stats: source={event_source} state={state} " + ' '.join([f"{c}={count[c]}" for c in sorted(count.keys())])) return caller_response @@ -1026,7 +1039,7 @@ def get_cname_from_tags(tags): LOGGER.debug("tag: %s", str(tag)) if TAGKEY_CNAME.upper() in tag.get('Key', {}).lstrip().upper(): - cname = tag.get('Value').lstrip().lower() + cname=tag.get('Value').lstrip().lower() return cname return None @@ -1040,12 +1053,12 @@ def get_instances(client, instance_id): :return: """ - i = 0 - instance_data = {} + i=0 + instance_data={} while i < MAX_API_RETRY: try: - instance_data = client.describe_instances(InstanceIds=[instance_id]) + instance_data=client.describe_instances(InstanceIds=[instance_id]) LOGGER.debug("%s", str(instance_data) + lineno()) break except ClientError as err: @@ -1086,16 +1099,16 @@ def new_list_hosted_zones(client, instance_id): :return: """ - i = 0 - hosted_zones = {} + i=0 + hosted_zones={} # retry to handle errors in the possible API call while i < MAX_API_RETRY: try: - hosted_zones = client.list_hosted_zones() + hosted_zones=client.list_hosted_zones() LOGGER.debug("list_hosted_zones returned without error. %s", lineno()) break except ClientError as err: - error_message = str(err) + error_message=str(err) if "(Throttling)" in str(err): LOGGER.debug( "list_hosted_zones throttled due to API limit, retrying: %s", str(err) + lineno()) @@ -1115,12 +1128,12 @@ def new_list_hosted_zones(client, instance_id): instance_id, str(i) + lineno()) if SNS_ENABLE: try: - sns_msg = {} - sns_msg['instance_id'] = instance_id - sns_msg['account_id'] = get_caller_account_id() - sns_msg['client'] = 'route53' - sns_msg['boto3_method'] = 'list_hosted_zones' - sns_msg['message'] = 'list_hosted_zones timed out' + sns_msg={} + sns_msg['instance_id']=instance_id + sns_msg['account_id']=get_caller_account_id() + sns_msg['client']='route53' + sns_msg['boto3_method']='list_hosted_zones' + sns_msg['message']='list_hosted_zones timed out' publish_to_sns(get_sns_client(), json.dumps(sns_msg)) LOGGER.info("instance: %s, sending sns message %s", instance_id, json.dumps(sns_msg) + lineno()) @@ -1141,18 +1154,18 @@ def new_list_hosted_zones_by_vpc(client, instance_id, vpc_id, region): :return: """ - i = 0 - hosted_zones = {} + i=0 + hosted_zones={} # retry to handle errors in the possible API call while i < MAX_API_RETRY: try: - hosted_zones = client.list_hosted_zones_by_vpc( + hosted_zones=client.list_hosted_zones_by_vpc( VPCId=vpc_id, VPCRegion=region) LOGGER.debug( "list_hosted_zones_by_vpc returned without error. %s", lineno()) break except ClientError as err: - error_message = str(err) + error_message=str(err) if "(Throttling)" in str(err): LOGGER.debug( "list_hosted_zones_by_vpc throttled due to API limit, retrying: %s", str(err) + lineno()) @@ -1172,14 +1185,14 @@ def new_list_hosted_zones_by_vpc(client, instance_id, vpc_id, region): vpc_id, instance_id, str(i) + lineno()) if SNS_ENABLE: try: - sns_msg = {} - sns_msg['instance_id'] = instance_id - sns_msg['vpc_id'] = vpc_id - sns_msg['region'] = region - sns_msg['account_id'] = get_caller_account_id() - sns_msg['client'] = 'route53' - sns_msg['boto3_method'] = 'list_hosted_zones_by_vpc' - sns_msg['message'] = 'list_hosted_zones_by_vpc timed out' + sns_msg={} + sns_msg['instance_id']=instance_id + sns_msg['vpc_id']=vpc_id + sns_msg['region']=region + sns_msg['account_id']=get_caller_account_id() + sns_msg['client']='route53' + sns_msg['boto3_method']='list_hosted_zones_by_vpc' + sns_msg['message']='list_hosted_zones_by_vpc timed out' publish_to_sns(get_sns_client(), json.dumps(sns_msg)) LOGGER.info("vpc_id: %s, instance: %s, sending sns message %s", vpc_id, instance_id, json.dumps(sns_msg) + lineno()) @@ -1256,7 +1269,7 @@ def get_item_from_dynamodb_table(client, table, instance_id): """ try: # Fetch item from DynamoDB - item = client.get_item( + item=client.get_item( TableName=table, Key={ 'InstanceId': { @@ -1272,16 +1285,16 @@ def get_item_from_dynamodb_table(client, table, instance_id): LOGGER.debug("returned item:" " %s", str(item['Item']['InstanceAttributes']['S']) + lineno()) - instance_attribute = item['Item']['InstanceAttributes']['S'] + instance_attribute=item['Item']['InstanceAttributes']['S'] # these 7 lines are handling for legacy DDB items created prior how items were written - instance_attribute = instance_attribute.replace("'", '"') - instance_attribute = instance_attribute.replace(" True,", ' "True",') - instance_attribute = instance_attribute.replace(" True}", ' "True"}') - instance_attribute = instance_attribute.replace(" True,", ' "True",') - instance_attribute = instance_attribute.replace(" False,", ' "False",') - instance_attribute = instance_attribute.replace(" False,", ' "False",') - instance_attribute = instance_attribute.replace(" False,", ' "False",') + instance_attribute=instance_attribute.replace("'", '"') + instance_attribute=instance_attribute.replace(" True,", ' "True",') + instance_attribute=instance_attribute.replace(" True}", ' "True"}') + instance_attribute=instance_attribute.replace(" True,", ' "True",') + instance_attribute=instance_attribute.replace(" False,", ' "False",') + instance_attribute=instance_attribute.replace(" False,", ' "False",') + instance_attribute=instance_attribute.replace(" False,", ' "False",') LOGGER.debug("item: %s", str(instance_attribute) + lineno()) return json.loads(instance_attribute) @@ -1298,7 +1311,7 @@ def get_private_hosted_zone_collection(private_hosted_zones): :return: """ try: - private_hosted_zone_collection = [] + private_hosted_zone_collection=[] for item in private_hosted_zones: LOGGER.debug("item: %s", str(item) + lineno()) @@ -1316,11 +1329,11 @@ def get_private_hosted_zone_collection_by_vpc(private_hosted_zones): :return: """ try: - private_hosted_zone_collection = [] + private_hosted_zone_collection=[] for item in private_hosted_zones: LOGGER.debug("item: %s", str(item) + lineno()) - my_item = { + my_item={ 'name': item['Name'], 'zone_id': item['HostedZoneId'], 'owner_account': item['Owner'].get('OwningAccount', ''), @@ -1341,7 +1354,7 @@ def get_private_hosted_zones(hosted_zones): :return: """ try: - private_hosted_zones = [] + private_hosted_zones=[] for item in hosted_zones['HostedZones']: LOGGER.debug("item: %s", str(item) + lineno()) @@ -1363,7 +1376,7 @@ def get_private_hosted_zones_by_vpc(hosted_zones): :return: """ try: - private_hosted_zones = [] + private_hosted_zones=[] for item in hosted_zones['HostedZoneSummaries']: LOGGER.debug("item: %s", str(item) + lineno()) @@ -1383,19 +1396,19 @@ def get_dhcp_option_set_id_for_vpc(client, instance_id, vpc_id): :return: """ - i = 0 + i=0 while i < MAX_API_RETRY: try: - option_sets = {} - results = client.describe_vpcs() + option_sets={} + results=client.describe_vpcs() for item in results['Vpcs']: if 'DhcpOptionsId' in item: - option_sets[str(item['VpcId'])] = item['DhcpOptionsId'] + option_sets[str(item['VpcId'])]=item['DhcpOptionsId'] else: - option_sets[str(item['VpcId'])] = None - option_set_for_vpc = option_sets[vpc_id] + option_sets[str(item['VpcId'])]=None + option_set_for_vpc=option_sets[vpc_id] LOGGER.debug("option set for vpc: %s", str(option_set_for_vpc) + lineno()) break @@ -1499,24 +1512,24 @@ def new_change_resource_recordset(oclient, instance_id, zone_id, host_name, host # this ignores the client, and uses the session from sessions[account] with a new route53 client global phz_collection_by_vpc - zone_item = phz_collection_by_vpc[hosted_zone_name] + zone_item=phz_collection_by_vpc[hosted_zone_name] LOGGER.debug("Using zone %s, zone item %s: %s", str( hosted_zone_name), str(zone_item), lineno()) - zone_account = zone_item['owner_account'] + zone_account=zone_item['owner_account'] try: LOGGER.debug("Calling get_session_assume_role() on account %s: %s", zone_account, lineno()) - this_session = get_session_assume_role(zone_account) + this_session=get_session_assume_role(zone_account) except Exception as err: LOGGER.error("Unable to esablish assume_role session in account %s: %s", str(zone_account), str(err) + lineno()) - update_response = "AssumeRoleFailed" + update_response="AssumeRoleFailed" return update_response - client = this_session.client('route53') + client=this_session.client('route53') - i = 0 - update_response = {} + i=0 + update_response={} LOGGER.debug("Creating %s record %s in zone %s: %s", record_type, host_name, hosted_zone_name, lineno()) @@ -1526,7 +1539,7 @@ def new_change_resource_recordset(oclient, instance_id, zone_id, host_name, host try: LOGGER.debug("Try %s Creating %s record %s in zone %s: %s", str( i), record_type, host_name, hosted_zone_name, lineno()) - change_batch = { + change_batch={ "Comment": f"Updated by {APPNAME} v{VERSION} from {account_id} in {region}", "Changes": [ { @@ -1547,7 +1560,7 @@ def new_change_resource_recordset(oclient, instance_id, zone_id, host_name, host LOGGER.debug("change_resource_record_sets change_batch: %s", json.dumps(change_batch) + lineno()) - update_response = client.change_resource_record_sets( + update_response=client.change_resource_record_sets( HostedZoneId=zone_id, ChangeBatch=change_batch ) @@ -1558,12 +1571,12 @@ def new_change_resource_recordset(oclient, instance_id, zone_id, host_name, host except ClientError as err: if 'NoSuchHostedZone' in str(err) and 'No hosted zone found with ID' in str(err): LOGGER.error("Hosted zone not found error: %s", str(err) + lineno()) - update_response = "NoSuchHostedZone" + update_response="NoSuchHostedZone" break elif 'InvalidChangeBatch' in str(err) and 'is not permitted in zone' in str(err): LOGGER.error( "Cannot create record - most likely wrong zone name specified: %s", str(err) + lineno()) - update_response = "InvalidChangeBatch-WrongZoneName" + update_response="InvalidChangeBatch-WrongZoneName" break elif "(Throttling)" in str(err): LOGGER.debug("change_resource_record_sets UPSERT throttled due to API limit, retrying: %s", str( @@ -1587,13 +1600,13 @@ def new_change_resource_recordset(oclient, instance_id, zone_id, host_name, host if update_response == {}: if SNS_ENABLE: try: - sns_msg = {} - sns_msg['instance_id'] = instance_id - sns_msg['account_id'] = get_caller_account_id() - sns_msg['client'] = 'route53' - sns_msg['boto3_method'] = 'change_resource_record_sets' - sns_msg['message'] = 'change_resource_record_sets could not UPSERT record' - sns_msg['change_resource_record_sets'] = { + sns_msg={} + sns_msg['instance_id']=instance_id + sns_msg['account_id']=get_caller_account_id() + sns_msg['client']='route53' + sns_msg['boto3_method']='change_resource_record_sets' + sns_msg['message']='change_resource_record_sets could not UPSERT record' + sns_msg['change_resource_record_sets']={ 'HostedZoneId': zone_id, 'ChangeBatch': change_batch} publish_to_sns(get_sns_client(), json.dumps(sns_msg)) LOGGER.info("instance: %s, sending sns message %s", instance_id, @@ -1628,7 +1641,7 @@ def create_resource_record(client, instance_id, zone_id, host_name, hosted_zone_ # To prevent rate throttling # time.sleep(1) - create_response = new_change_resource_recordset( + create_response=new_change_resource_recordset( client, instance_id, zone_id, @@ -1640,19 +1653,19 @@ def create_resource_record(client, instance_id, zone_id, host_name, hosted_zone_ if create_response == 'NoSuchHostedZone': LOGGER.debug("DNS Record create failed: %s", str(create_response) + lineno()) - msg = 'NoSuchHostedZone: ' + str(create_response) + msg='NoSuchHostedZone: ' + str(create_response) elif create_response == 'InvalidChangeBatch-WrongZoneName': LOGGER.debug("DNS Record create failed: %s", str(create_response) + lineno()) - msg = 'InvalidChangeBatch-WrongZoneName: ' + str(create_response) + msg='InvalidChangeBatch-WrongZoneName: ' + str(create_response) elif create_response == {}: LOGGER.debug("DNS Record create failed: %s", str(create_response) + lineno()) - msg = 'DNS Recored Create Failed: ' + str(create_response) + msg='DNS Recored Create Failed: ' + str(create_response) else: LOGGER.debug("DNS Record create success: %s", str(create_response) + lineno()) - msg = 'success' + msg='success' LOGGER.debug("response: %s", str(create_response) + lineno()) return msg @@ -1720,24 +1733,24 @@ def new_get_resource_record(oclient, instance_id, zone_id, host_name, hosted_zon # this ignores the client, and uses the session from sessions[account] with a new route53 client global phz_collection_by_vpc - zone_item = phz_collection_by_vpc[hosted_zone_name] + zone_item=phz_collection_by_vpc[hosted_zone_name] LOGGER.debug("Using zone %s, zone item %s: %s", str( hosted_zone_name), str(zone_item), lineno()) - zone_account = zone_item['owner_account'] + zone_account=zone_item['owner_account'] try: LOGGER.debug("Calling get_session_assume_role() on account %s: %s", zone_account, lineno()) - this_session = get_session_assume_role(zone_account) + this_session=get_session_assume_role(zone_account) except Exception as err: LOGGER.error("Unable to esablish assume_role session in account %s: %s", str(zone_account), str(err) + lineno()) - update_response = "AssumeRoleFailed" + update_response="AssumeRoleFailed" return update_response - client = this_session.client('route53') + client=this_session.client('route53') - i = 0 - value = '' + i=0 + value='' while i < MAX_API_RETRY: try: @@ -1750,8 +1763,8 @@ def new_get_resource_record(oclient, instance_id, zone_id, host_name, hosted_zon LOGGER.debug("list_resource_record_sets looking for record %s in zone %s", str(host_name), str(hosted_zone_name) + lineno()) - fqdn = create_fqdn(host_name, hosted_zone_name) - response = client.list_resource_record_sets( + fqdn=create_fqdn(host_name, hosted_zone_name) + response=client.list_resource_record_sets( HostedZoneId=zone_id, # StartRecordName=host_name + hosted_zone_name, StartRecordName=fqdn, @@ -1762,12 +1775,12 @@ def new_get_resource_record(oclient, instance_id, zone_id, host_name, hosted_zon json.dumps(response) + lineno()) for rr_set in response['ResourceRecordSets']: - rr_name = rr_set['Name'] + rr_name=rr_set['Name'] # check if the return value matches the record, if not ignore # if the record isn't there, it returns the list_resource_record_sets returns the next record # if rr_name == (host_name + hosted_zone_name): if rr_name == fqdn: - value = rr_set['ResourceRecords'][0]['Value'] + value=rr_set['ResourceRecords'][0]['Value'] LOGGER.debug( f"list_resource_record_sets returned value {value}: {lineno()}") else: @@ -1803,12 +1816,12 @@ def new_get_resource_record(oclient, instance_id, zone_id, host_name, hosted_zon instance_id, str(MAX_API_RETRY) + lineno()) if SNS_ENABLE: try: - sns_msg = {} - sns_msg['instance_id'] = instance_id - sns_msg['account_id'] = get_caller_account_id() - sns_msg['client'] = 'route53' - sns_msg['boto3_method'] = 'list_resource_record_sets' - sns_msg['message'] = 'list_resource_record_sets timed out' + sns_msg={} + sns_msg['instance_id']=instance_id + sns_msg['account_id']=get_caller_account_id() + sns_msg['client']='route53' + sns_msg['boto3_method']='list_resource_record_sets' + sns_msg['message']='list_resource_record_sets timed out' publish_to_sns(get_sns_client(), json.dumps(sns_msg)) LOGGER.info("instance: %s, sending sns message %s", instance_id, json.dumps(sns_msg) + lineno()) @@ -1835,24 +1848,24 @@ def new_delete_resource_record(oclient, instance_id, zone_id, host_name, hosted_ # this ignores the client, and uses the session from sessions[account] with a new route53 client global phz_collection_by_vpc - zone_item = phz_collection_by_vpc[hosted_zone_name] + zone_item=phz_collection_by_vpc[hosted_zone_name] LOGGER.debug("Using zone %s, zone item %s: %s", str( hosted_zone_name), str(zone_item), lineno()) - zone_account = zone_item['owner_account'] + zone_account=zone_item['owner_account'] try: LOGGER.debug("Calling get_session_assume_role() on account %s: %s", zone_account, lineno()) - this_session = get_session_assume_role(zone_account) + this_session=get_session_assume_role(zone_account) except Exception as err: LOGGER.error("Unable to esablish assume_role session in account %s: %s", str(zone_account), str(err) + lineno()) - update_response = "AssumeRoleFailed" + update_response="AssumeRoleFailed" return update_response - client = this_session.client('route53') + client=this_session.client('route53') - i = 0 - delete_response = {} + i=0 + delete_response={} LOGGER.debug("Deleting %s record %s in zone %s: %s", record_type, host_name, hosted_zone_name, lineno()) @@ -1867,7 +1880,7 @@ def new_delete_resource_record(oclient, instance_id, zone_id, host_name, hosted_ try: LOGGER.debug("Try %s Deleting %s record %s in zone %s: %s", str( i), record_type, host_name, hosted_zone_name, lineno()) - change_batch = { + change_batch={ "Comment": f"Deleted by {APPNAME} v{VERSION} from {account_id} in {region}", "Changes": [ { @@ -1888,7 +1901,7 @@ def new_delete_resource_record(oclient, instance_id, zone_id, host_name, hosted_ LOGGER.debug("change_resource_record_sets change_batch: %s", json.dumps(change_batch) + lineno()) - delete_response = client.change_resource_record_sets( + delete_response=client.change_resource_record_sets( HostedZoneId=zone_id, ChangeBatch=change_batch ) @@ -1901,16 +1914,16 @@ def new_delete_resource_record(oclient, instance_id, zone_id, host_name, hosted_ if 'NoSuchHostedZone' in str(err) and 'No hosted zone found with ID' in str(err): LOGGER.debug("Hosted zone not found error: %s", str(err) + lineno()) - delete_response = "NoSuchHostedZone" + delete_response="NoSuchHostedZone" break elif 'InvalidChangeBatch' in str(err) and 'it was not found' in str(err): LOGGER.debug("Record not found error: %s", str(err) + lineno()) - delete_response = "InvalidChangeBatch-RecordNotFound" + delete_response="InvalidChangeBatch-RecordNotFound" break elif 'InvalidChangeBatch' in str(err) and 'values provided do not match the current values' in str(err): LOGGER.debug("Record do not match current value error: %s", str(err) + lineno()) - delete_response = "InvalidChangeBatch-RecordDoNotMatch" + delete_response="InvalidChangeBatch-RecordDoNotMatch" break elif '(Throttling)' in str(err): LOGGER.debug("change_resource_record_sets DELETE throttled due to API limit, retrying: %s", str( @@ -1933,13 +1946,13 @@ def new_delete_resource_record(oclient, instance_id, zone_id, host_name, hosted_ if (delete_response == {} or delete_response == "InvalidChangeBatch-RecordDoNotMatch"): if SNS_ENABLE: try: - sns_msg = {} - sns_msg['instance_id'] = instance_id - sns_msg['account_id'] = get_caller_account_id() - sns_msg['client'] = 'route53' - sns_msg['boto3_method'] = 'change_resource_record_sets' - sns_msg['message'] = 'change_resource_record_sets could not DELETE record' - sns_msg['change_resource_record_sets'] = { + sns_msg={} + sns_msg['instance_id']=instance_id + sns_msg['account_id']=get_caller_account_id() + sns_msg['client']='route53' + sns_msg['boto3_method']='change_resource_record_sets' + sns_msg['message']='change_resource_record_sets could not DELETE record' + sns_msg['change_resource_record_sets']={ 'HostedZoneId': zone_id, 'ChangeBatch': change_batch} publish_to_sns(get_sns_client(), json.dumps(sns_msg)) LOGGER.info("instance: %s, sending sns message %s", instance_id, @@ -1960,11 +1973,11 @@ def get_zone_id(zone_name, hosted_zones, private_zone=True): """ try: if zone_name[-1] != '.': - zone_name = zone_name + '.' + zone_name=zone_name + '.' LOGGER.debug("zone name: %s", str(zone_name) + lineno()) LOGGER.debug("hosted_zones: %s", str(hosted_zones) + lineno()) - zones = [] + zones=[] for record in hosted_zones['HostedZones']: LOGGER.debug("record: %s", str(record) + lineno()) if record['Config']['PrivateZone'] == private_zone: @@ -1973,9 +1986,9 @@ def get_zone_id(zone_name, hosted_zones, private_zone=True): LOGGER.debug("zones: %s", str(zones) + lineno()) try: - zone_id_long = zones[0]['Id'] + zone_id_long=zones[0]['Id'] LOGGER.debug("zone id: %s", str(zone_id_long) + lineno()) - zone_id = str.split(str(zone_id_long), '/')[2] + zone_id=str.split(str(zone_id_long), '/')[2] return zone_id except: return None @@ -1995,8 +2008,8 @@ def is_valid_hostname(hostname): if hostname is None or len(hostname) > 255: return False if hostname[-1] == ".": - hostname = hostname[:-1] - allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(? 1: # remove beginning quote if info[0] == '"': - info = info[1:] + info=info[1:] # remove ending quote if info[-1] == '"': - info = info[:-1] + info=info[:-1] - kv_results = {} - kv = info.split(',') + kv_results={} + kv=info.split(',') LOGGER.debug("heritage split result: %s", str(kv) + lineno()) - header = kv.pop(0).split('=') + header=kv.pop(0).split('=') if header[0] != 'heritage': LOGGER.debug("heritage analysis: does not contain heritage header, returning: %s", str(kv_results) + lineno()) return kv_results else: - appname = header[1] - kv_results['application_name'] = appname + appname=header[1] + kv_results['application_name']=appname LOGGER.debug("heritage analysis: assigning application_name: %s", str(appname) + lineno()) try: for item in kv: - k, v = item.split('=', 2) + k, v=item.split('=', 2) LOGGER.debug("heritage item: key: %s, value: %s", str(k), str(v) + lineno()) # print('appname',appname,'k',k,'v',v) if appname + '/' in k: - nk = k.replace(appname + '/', '') - kv_results[nk] = v + nk=k.replace(appname + '/', '') + kv_results[nk]=v # print('nk',nk) if kv_results.get('version') is None: # version=kv_result.pop('version') # else: - version = 'null' + version='null' # return initialize_heritage(appname,version,kv_results) LOGGER.debug("heritage parsed dictionary: %s", @@ -2610,7 +2623,7 @@ def get_heritage_item(data, key): str(data), str(type(data)) + lineno()) return None else: - result = data.get(key, None) + result=data.get(key, None) LOGGER.debug("get_heritage_item: getting key %s value %s", str(key), str(result) + lineno()) return result @@ -2656,7 +2669,7 @@ def publish_to_sns(client, message): if SNS_TOPIC_ARN != '': try: - response = client.publish( + response=client.publish( TopicArn=SNS_TOPIC_ARN, Message=str(message) ) @@ -2681,20 +2694,20 @@ def process_delete_records(route53, instance_id, zone_id, :return response: # dictionary of 'delete_success' and 'msg' """ - response = {} - response_delete_success = True - response_msg = [] + response={} + response_delete_success=True + response_msg=[] LOGGER.info("instance: %s, Delete %s Record. Checking TXT record association for %s in zone %s", instance_id, record_type, record_name, zone_name + lineno()) # if record type is CNAME, we need to add the TXT RR prefix if record_type == 'CNAME': - txt_record_name = TXT_RR_PREFIX + '.' + record_name + txt_record_name=TXT_RR_PREFIX + '.' + record_name else: - txt_record_name = record_name + txt_record_name=record_name - heritage_value = new_get_resource_record( + heritage_value=new_get_resource_record( route53, instance_id, zone_id, @@ -2705,25 +2718,25 @@ def process_delete_records(route53, instance_id, zone_id, ) # Return the dictionary of the value with comma separated - heritage = parse_heritage(heritage_value) + heritage=parse_heritage(heritage_value) LOGGER.debug("heritage parsed data in string format: %s", str(heritage) + lineno()) # check if the TXT record was created by the Lambda as match instance-id if verify_heritage_owner(heritage, HERITAGE_TAG): LOGGER.debug("TXT record was created by Lambda DDNS %s", HERITAGE_TAG + lineno()) - heritage_own = True + heritage_own=True else: LOGGER.info("TXT record was not created by Lambda DDNS %s", HERITAGE_TAG + lineno()) - heritage_own = False + heritage_own=False if compare_heritage(heritage, 'instance_id', instance_id): LOGGER.debug("TXT record matches instance_id: %s", instance_id + lineno()) - heritage_instance_match = True + heritage_instance_match=True else: LOGGER.info("TXT record does not match instance_id: %s", instance_id + lineno()) - heritage_instance_match = False + heritage_instance_match=False # delete A/PTR/AAAA/CNAME record if heritage_own and heritage_instance_match: @@ -2732,13 +2745,13 @@ def process_delete_records(route53, instance_id, zone_id, LOGGER.info("Deleting %s resource record %s, in zone %s, with a value of %s", record_type, record_name, zone_name, record_value + lineno()) - response_text = 'Delete ' + record_type + \ + response_text='Delete ' + record_type + \ ' record in zone id: ' + zone_id + \ ' for record ' + record_name + \ ' in zone named ' + zone_name + \ ' with value: ' + record_value - delete_response = new_delete_resource_record( + delete_response=new_delete_resource_record( route53, instance_id, zone_id, @@ -2749,7 +2762,7 @@ def process_delete_records(route53, instance_id, zone_id, ) if delete_response == 'NoSuchHostedZone': - response_delete_success = False + response_delete_success=False response_msg.append("Failed, no such zone: " + response_text) LOGGER.info("instance: %s, NoSuchHostedZone: %s", instance_id, response_text + lineno()) @@ -2758,13 +2771,13 @@ def process_delete_records(route53, instance_id, zone_id, LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", instance_id, response_text + lineno()) elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': - response_delete_success = False + response_delete_success=False response_msg.append( "Failed, requested delete do not match existing record: " + response_text) LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", instance_id, response_text + lineno()) elif delete_response == {}: - response_delete_success = False + response_delete_success=False response_msg.append( "Failed, could NOT delete Record: " + response_text) LOGGER.info("instance: %s, Failed, could NOT delete Record: %s", @@ -2775,31 +2788,31 @@ def process_delete_records(route53, instance_id, zone_id, response_msg.append("Success: " + response_text) except BaseException as err: - response_delete_success = False + response_delete_success=False LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) else: - response_delete_success = False + response_delete_success=False response_msg.append("Failed, the TXT record for the " + record_type + " record does not match expected value. Will not delete the " + record_type + " record.") LOGGER.error("instance: %s, the TXT record for the %s record does not match expected value. Will not delete the %s record. %s\n", instance_id, record_type, record_type, lineno()) if SNS_ENABLE: try: - sns_msg = {} - sns_msg['instance_id'] = instance_id - sns_msg['account_id'] = get_caller_account_id() - sns_msg['message'] = 'TXT record does not match. Will not delete the A/PTR/CNAME/AAAA record.' - - sns_heritage = {} - sns_heritage['record_type'] = record_type - sns_heritage['record_name'] = record_name - sns_heritage['zone_name'] = zone_name - sns_heritage['zone_id'] = zone_id - sns_heritage['heritage_value'] = heritage_value - - sns_msg['heritage'] = sns_heritage + sns_msg={} + sns_msg['instance_id']=instance_id + sns_msg['account_id']=get_caller_account_id() + sns_msg['message']='TXT record does not match. Will not delete the A/PTR/CNAME/AAAA record.' + + sns_heritage={} + sns_heritage['record_type']=record_type + sns_heritage['record_name']=record_name + sns_heritage['zone_name']=zone_name + sns_heritage['zone_id']=zone_id + sns_heritage['heritage_value']=heritage_value + + sns_msg['heritage']=sns_heritage publish_to_sns(get_sns_client(), json.dumps(sns_msg)) LOGGER.info("instance: %s, sending sns message %s", instance_id, json.dumps(sns_msg) + lineno()) @@ -2813,13 +2826,13 @@ def process_delete_records(route53, instance_id, zone_id, LOGGER.info("Deleting heritage TXT resource record %s, in the zone %s, with value of %s", txt_record_name, zone_name, str(heritage_value) + lineno()) - response_text = 'Delete ' + 'TXT' + \ + response_text='Delete ' + 'TXT' + \ ' record in zone id: ' + zone_id + \ ' for record ' + txt_record_name + \ ' in zone named ' + zone_name + \ ' with value: ' + str(heritage_value) - delete_response = new_delete_resource_record( + delete_response=new_delete_resource_record( route53, instance_id, zone_id, @@ -2829,7 +2842,7 @@ def process_delete_records(route53, instance_id, zone_id, str(heritage_value)) if delete_response == 'NoSuchHostedZone': - response_delete_success = False + response_delete_success=False response_msg.append("Failed, no such zone: " + response_text) LOGGER.info("instance: %s, NoSuchHostedZone: %s", instance_id, response_text + lineno()) @@ -2838,13 +2851,13 @@ def process_delete_records(route53, instance_id, zone_id, LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", instance_id, response_text + lineno()) elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': - response_delete_success = False + response_delete_success=False response_msg.append( "Failed, requested delete do not match existing record: " + response_text) LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", instance_id, response_text + lineno()) elif delete_response == {}: - response_delete_success = False + response_delete_success=False response_msg.append( "Failed, could NOT delete Record: " + response_text) LOGGER.info("instance: %s, Failed Could NOT delete Record: %s", @@ -2854,11 +2867,11 @@ def process_delete_records(route53, instance_id, zone_id, LOGGER.info("instance: %s, Success: %s", instance_id, response_text + lineno()) except BaseException as err: - response_delete_success = False + response_delete_success=False LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) else: - response_delete_success = False + response_delete_success=False response_msg.append("Failed, the TXT record for " + record_type + " does not match expected value. Will not delete the TXT record.") LOGGER.error("instance: %s, the TXT record for the %s does not match expected value. Will not delete TXT record. %s", @@ -2866,19 +2879,19 @@ def process_delete_records(route53, instance_id, zone_id, if SNS_ENABLE: try: - sns_msg = {} - sns_msg['instance_id'] = instance_id - sns_msg['account_id'] = get_caller_account_id() - sns_msg['message'] = 'TXT record does not match. Will not delete the TXT record.' - - sns_heritage = {} - sns_heritage['record_type'] = 'TXT' - sns_heritage['record_name'] = txt_record_name - sns_heritage['zone_name'] = zone_name - sns_heritage['zone_id'] = zone_id - sns_heritage['heritage_value'] = heritage_value - - sns_msg['heritage'] = sns_heritage + sns_msg={} + sns_msg['instance_id']=instance_id + sns_msg['account_id']=get_caller_account_id() + sns_msg['message']='TXT record does not match. Will not delete the TXT record.' + + sns_heritage={} + sns_heritage['record_type']='TXT' + sns_heritage['record_name']=txt_record_name + sns_heritage['zone_name']=zone_name + sns_heritage['zone_id']=zone_id + sns_heritage['heritage_value']=heritage_value + + sns_msg['heritage']=sns_heritage publish_to_sns(get_sns_client(), json.dumps(sns_msg)) LOGGER.info("instance: %s, sending sns message %s", instance_id, json.dumps(sns_msg) + lineno()) @@ -2888,8 +2901,8 @@ def process_delete_records(route53, instance_id, zone_id, str(sys.exc_info()[0]) + lineno()) # create a dictionary to return - response['delete_success'] = response_delete_success - response['msg'] = response_msg + response['delete_success']=response_delete_success + response['msg']=response_msg return response @@ -2902,13 +2915,13 @@ def process_tags_flags(tags): :return dict(string): flag settings in defaultdict for controlling which names are registered and when """ - tag_dict = {tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} - flags_dict = defaultdict(lambda: False) - flags = tag_dict.get(TAGKEY_FLAGS.lower(), '').split(',') + tag_dict={tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} + flags_dict=defaultdict(lambda: False) + flags=tag_dict.get(TAGKEY_FLAGS.lower(), '').split(',') for flag in flags: if flag != '': LOGGER.debug("Setting 'flags' to True: %s", str(flag) + lineno()) - flags_dict[flag] = True + flags_dict[flag]=True return flags_dict @@ -2924,7 +2937,7 @@ def process_tags_value(name): """ if name != '': - components = parse_hostname_to_components(name) + components=parse_hostname_to_components(name) if components: return (True, components[0], components[1]) return (False, None, None) @@ -2938,10 +2951,10 @@ def process_tags_option_cname(tags): : return tuple(bool, str, str): true | false if vaid, hostname, domainname """ - tag_dict = {tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} + tag_dict={tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} # value = tag_dict.get(TAGKEY_CNAME.lower(), '').split(',') # need additional work to handle a comma-separated list - value = tag_dict.get(TAGKEY_CNAME.lower(), '') + value=tag_dict.get(TAGKEY_CNAME.lower(), '') return process_tags_value(value) @@ -2953,8 +2966,8 @@ def process_tags_option_zone(tags): : return tuple(bool, str, str): true | false if vaid, hostname, domainname """ - tag_dict = {tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} - value = tag_dict.get(TAGKEY_ZONE.lower(), '') + tag_dict={tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} + value=tag_dict.get(TAGKEY_ZONE.lower(), '') return process_tags_value(value) @@ -2966,8 +2979,8 @@ def process_tags_option_name(tags): : return: """ - tag_dict = {tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} - value = tag_dict.get(TAGKEY_HOSTNAME.lower(), '') + tag_dict={tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} + value=tag_dict.get(TAGKEY_HOSTNAME.lower(), '') return process_tags_value(value) @@ -2979,8 +2992,8 @@ def process_tags_name(tags): : return: """ - tag_dict = {tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} - value = tag_dict.get('name', '') + tag_dict={tag['Key'].lstrip().lower(): tag['Value'] for tag in tags} + value=tag_dict.get('name', '') return process_tags_value(value) @@ -2992,26 +3005,26 @@ def get_session_assume_role(account): : return: boto3.session corresonding to the assumed role """ - this_session = sessions.get(account, None) + this_session=sessions.get(account, None) try: if this_session is None: LOGGER.debug("Existing session not found for account %s: %s", account, lineno()) - role_arn = format(REMOTE_ROLE_ARN_FORMAT % (partition, account)) - response = sts_client.assume_role(RoleArn=role_arn, RoleSessionName=APPNAME) + role_arn=format(REMOTE_ROLE_ARN_FORMAT % (partition, account)) + response=sts_client.assume_role(RoleArn=role_arn, RoleSessionName=APPNAME) LOGGER.debug("Called sts:assumerole for arn %s: %s", str(role_arn), lineno()) - credentials = response['Credentials'] + credentials=response['Credentials'] LOGGER.info( f"Called assume_role for {account} ARN {role_arn}, got credentials with expiration {credentials['Expiration']}: {lineno()}") count['assumed_role.new'] += 1 - this_session = boto3.Session( + this_session=boto3.Session( aws_access_key_id=credentials["AccessKeyId"], aws_secret_access_key=credentials["SecretAccessKey"], aws_session_token=credentials["SessionToken"], region_name=region) - sessions[account] = this_session + sessions[account]=this_session LOGGER.debug("Crated new session for account %s: %s", str(account), lineno()) else: @@ -3037,10 +3050,10 @@ def parse_hostname_to_components(name): """ global phz_collection_by_vpc - names = name.rstrip('.').split('.') + names=name.rstrip('.').split('.') for i in range(len(names)): - host = '.'.join(names[0:i]) - domain = '.'.join(names[i:]) + '.' + host='.'.join(names[0:i]) + domain='.'.join(names[i:]) + '.' if phz_collection_by_vpc.get(domain): return (host, domain) LOGGER.error( @@ -3061,6 +3074,6 @@ def create_fqdn(host, zone): :return (str,str): Tuple containing hostname components (may include dot) and domain name for which a PHZ exists. None is returned if not found. """ - fqdn = host.replace(zone, '').rstrip('.') + '.' + zone + fqdn=host.replace(zone, '').rstrip('.') + '.' + zone fqdn += '.' if fqdn[-1] != '.' else '' return fqdn diff --git a/code/ddns-lambda.zip b/code/ddns-lambda.zip index 9d59990..664fd9b 100644 Binary files a/code/ddns-lambda.zip and b/code/ddns-lambda.zip differ