From 2c28b77a5029db8f210836d6924a877debd01257 Mon Sep 17 00:00:00 2001 From: badra001 Date: Tue, 25 Apr 2023 20:25:12 -0400 Subject: [PATCH] fix --- code/ddns-lambda.py | 541 ++++++++++++++++++++++--------------------- code/ddns-lambda.zip | Bin 21361 -> 21547 bytes 2 files changed, 277 insertions(+), 264 deletions(-) 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 9d59990007d7cbd79ab09937a128ba6992f86490..664fd9b565abd0cb6c55c07f773f44442f4ba6d5 100644 GIT binary patch literal 21547 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