diff --git a/CHANGELOG.md b/CHANGELOG.md index 62adb86..286b41a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,3 +47,15 @@ - code 0.1.18 - lots of bug fixes - update to use launch_time as the timestamp in the TXT ecords to be able to reconstruct it to delete it + +* 0.2.0 -- 2022-03-08 + - code 0.2.0 + - SNS code prep + - SNS resource prep + - refactor the route53 API calls + - add better API timeouts + - new variables: + - sns_topic_name + - sqs_queue_name + - enable_sns + - enable_sqs diff --git a/README.md b/README.md index 1e3c14d..ab76264 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,14 @@ No modules. | [aws_lambda_alias.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_alias) | resource | | [aws_lambda_function.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | | [aws_lambda_permission.allow_cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_sns_topic.topic](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | +| [aws_sns_topic_policy.topic](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_policy) | resource | | [aws_arn.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy.lambda_policies](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source | | [aws_iam_policy_document.lambda_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.lambda_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.topic](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | ## Inputs @@ -43,11 +46,15 @@ No modules. | [account\_id](#input\_account\_id) | AWS Account ID (default will pull from current user) | `string` | `""` | no | | [component\_tags](#input\_component\_tags) | Additional tags for Components (s3, kms, ddb) | `map(map(string))` |
{
"ddb": {},
"kms": {},
"s3": {}
}
| no | | [create](#input\_create) | Flag to indicate whether to create the resources or not (default: true) | `bool` | `true` | no | -| [dynamodb\_table\_name](#input\_dynamodb\_table\_name) | Different DynamoDB table name to override default of var.name) | `string` | `null` | no | -| [lambda\_environment\_variables](#input\_lambda\_environment\_variables) | Map of lambda environment variables and values | `map(string)` |
{
"DNS_RR_TimeToLive": 60,
"DynamoDBName": null,
"HeritageIdentifier": "dynr53",
"HeritageTXTRecordPrefix": "_txt",
"SleepTime": 60,
"TagKeyCname": "boc:dns:cname",
"TagKeyHostName": "boc:dns:name",
"TagKeyZone": "boc:dns:zone"
}
| no | -| [lambda\_name](#input\_lambda\_name) | Different Lambda name to override default of var.name) | `string` | `null` | no | +| [dynamodb\_table\_name](#input\_dynamodb\_table\_name) | Different DynamoDB table name to override default of var.name | `string` | `null` | no | +| [enable\_sns](#input\_enable\_sns) | Enable use of SNS for reporting errors | `bool` | `false` | no | +| [enable\_sqs](#input\_enable\_sqs) | Enable use of SQS for SNS to send errors | `bool` | `false` | no | +| [lambda\_environment\_variables](#input\_lambda\_environment\_variables) | Map of lambda environment variables and values | `map(string)` |
{
"DNS_RR_TimeToLive": 60,
"DynamoDBName": null,
"HeritageIdentifier": "dynr53",
"HeritageTXTRecordPrefix": "_txt",
"MaxApiRetry": 10,
"SleepTime": 60,
"SnsEnable": false,
"SnsTopicArn": "",
"TagKeyCname": "boc:dns:cname",
"TagKeyHostName": "boc:dns:name",
"TagKeyZone": "boc:dns:zone"
}
| no | +| [lambda\_name](#input\_lambda\_name) | Different Lambda name to override default of var.name | `string` | `null` | no | | [name](#input\_name) | Name to use within all the created resources (default: inf-dynamic-route53) | `string` | `"inf-dynamic-route53"` | no | | [override\_prefixes](#input\_override\_prefixes) | Override built-in prefixes by component. This should be used primarily for common infrastructure things | `map(string)` | `{}` | no | +| [sns\_topic\_name](#input\_sns\_topic\_name) | Different SNS Topic name to override default of var.name | `string` | `null` | no | +| [sqs\_queue\_name](#input\_sqs\_queue\_name) | Different SQS queue name to override default of var.name | `string` | `null` | no | | [tags](#input\_tags) | AWS Tags to apply to appropriate resources | `map(string)` | `{}` | no | ## Outputs diff --git a/code/ddns-lambda.py b/code/ddns-lambda.py index 87c0846..5e29838 100755 --- a/code/ddns-lambda.py +++ b/code/ddns-lambda.py @@ -72,7 +72,7 @@ LOGGER = logging.getLogger() ACCOUNT = None REGION = None -VERSION = '0.1.20' +VERSION = '0.2.0' # Read Env variables DEBUG_LOG_LEVEL = os.environ.get('DebugLogLevel', 'INFO') @@ -86,7 +86,8 @@ TF_MODULE_VERSION = os.environ.get('tf_module_version', '(unknown)') MAX_API_RETRY = int(os.environ.get('MaxApiRetry', '10')) SNS_TOPIC_ARN = os.environ.get('SnsTopicArn', '') -SNS_ENABLE = os.environ.get('SnsEnable', 'False') +SNS_ENABLE = (os.environ.get('SnsEnable', 'False').lower() in [ + 'yes', 'y', 'true', '1']) and SNS_TOPIC_ARN != '' # for CNAMEs TXT_RR_PREFIX = os.environ.get('HeritageTXTRecordPrefix', '_txt') @@ -104,11 +105,12 @@ elif DEBUG_LOG_LEVEL == 'CRITICAL': LOGGER.setLevel(logging.CRITICAL) else: - LOGGER.setLevel(logging.INFO) + LOGGER.setLevel(logging.INFO) print('Loading function v{} tf_module_version {}: {}'.format( VERSION, TF_MODULE_VERSION, datetime.datetime.now().time().isoformat())) + def lineno(): # pragma: no cover """ Returns the current line number in our script @@ -178,7 +180,7 @@ def lambda_handler( dynamodb_client=get_dynamodb_client(), compute=get_ec2_client(), route53=get_route53_client(), - sns_client=get_sns_client() + sns_client=get_sns_client() ): @@ -249,7 +251,8 @@ def lambda_handler( "%s", t_private_dns_name + "," + t_private_ip + "," + t_subnet_id + "," + t_vpc_id + lineno()) break except: - LOGGER.info("instance: %s, no instance data, repeat check: %s", instance_id, lineno()) + LOGGER.info("instance: %s, no instance data, repeat check: %s", + instance_id, lineno()) # Remove response metadata from the response if 'ResponseMetadata' in instance: @@ -360,7 +363,8 @@ def lambda_handler( # Check to see whether a reverse lookup zone for the instance # already exists. If it does, check to see whether # the reverse lookup zone is associated with the instance's VPC. - LOGGER.info("instance: %s, reversed_lookup_zone: %s", instance_id, str(reversed_lookup_zone) + lineno()) + LOGGER.info("instance: %s, reversed_lookup_zone: %s", + instance_id, str(reversed_lookup_zone) + lineno()) reverse_zone = None for record in hosted_zones['HostedZones']: LOGGER.debug("record name: %s", str(record['Name']) + lineno()) @@ -375,15 +379,16 @@ def lambda_handler( reverse_lookup_zone_id) + lineno()) reverse_hosted_zone_properties = new_get_hosted_zone_properties( - route53, instance_id, reverse_lookup_zone_id) + route53, instance_id, reverse_lookup_zone_id) # need to check if the property is empty {} if reverse_hosted_zone_properties == {}: - LOGGER.error("get_private_hosted_zone_properties returned no zone property", reverse_lookup_zone_id + lineno()) + LOGGER.error("get_private_hosted_zone_properties returned no zone property", + reverse_lookup_zone_id + lineno()) reverse_zone_associated = False - else: + else: LOGGER.debug("reverse_hosted_zone_properties:" - " %s", str(reverse_hosted_zone_properties) + lineno()) + " %s", str(reverse_hosted_zone_properties) + lineno()) if vpc_id in map(lambda x: x['VPCId'], reverse_hosted_zone_properties['VPCs']): LOGGER.info("instance: %s, Reverse lookup zone %s is associated with VPC %s %s", @@ -424,8 +429,9 @@ def lambda_handler( LOGGER.debug("dhcp_configurations: %s", str(get_dhcp_configurations) + lineno()) except BaseException as err: - LOGGER.error("instance: %s, No DHCP option set assigned to this VPC %s\n", instance_id, str(err) + lineno()) - if SNS_ENABLE.lower() == 'true': + LOGGER.error("instance: %s, No DHCP option set assigned to this VPC %s\n", + instance_id, str(err) + lineno()) + if SNS_ENABLE: sns_msg = {} sns_msg['instance_id'] = instance_id sns_msg['client'] = 'ec2' @@ -616,7 +622,8 @@ def lambda_handler( # determine correct A/PTR record to be created based upon the boolean values from the tags above if has_valid_hostname_tag and has_valid_zone_tag: - LOGGER.info("instance: %s, custom hostname tag and custom zone tag valid.", instance_id) + LOGGER.info( + "instance: %s, custom hostname tag and custom zone tag valid.", instance_id) final_private_hostname = custom_host_name final_hosted_zone_name = zone_tag_hosted_zone_name elif has_valid_hostname_tag and not (has_valid_zone_tag) and has_dhcp_dns_zone_associated_vpc: # 3 @@ -624,19 +631,23 @@ def lambda_handler( final_private_hostname = custom_host_name final_hosted_zone_name = private_hosted_zone_name elif has_valid_Name_tag_hostname and has_valid_Name_tag_zonename: - LOGGER.info("instance: %s, Name tag hostname valid and Name tag zonename valid.", instance_id) + LOGGER.info( + "instance: %s, Name tag hostname valid and Name tag zonename valid.", instance_id) final_private_hostname = name_host final_hosted_zone_name = name_domain_suffix elif has_valid_Name_tag_hostname and has_valid_zone_tag: - LOGGER.info("instance: %s, Name tag hostname valid and custom zone tag valid.", instance_id) + LOGGER.info( + "instance: %s, Name tag hostname valid and custom zone tag valid.", instance_id) final_private_hostname = name_host final_hosted_zone_name = zone_tag_hosted_zone_name elif has_valid_Name_tag_hostname and has_dhcp_dns_zone_associated_vpc: - LOGGER.info("instance: %s, Name tag hostname valid and DHCP zone is valid.", instance_id) + LOGGER.info( + "instance: %s, Name tag hostname valid and DHCP zone is valid.", instance_id) final_private_hostname = name_host final_hosted_zone_name = private_hosted_zone_name elif has_valid_zone_tag and not (has_valid_hostname_tag) and not(has_valid_Name_tag_hostname): - LOGGER.info("instance: %s, custom zone tag valid but no custom hostname, using IP address.", instance_id) + LOGGER.info( + "instance: %s, custom zone tag valid but no custom hostname, using IP address.", instance_id) final_private_hostname = private_host_name final_hosted_zone_name = zone_tag_hosted_zone_name elif has_dhcp_dns_zone_associated_vpc: @@ -644,7 +655,8 @@ def lambda_handler( final_private_hostname = private_host_name final_hosted_zone_name = private_hosted_zone_name else: # none of the use-case and no suitable zone to create the A record - LOGGER.info("instance: %s, No DHCP Associated for VPC and no custom tags. Exiting Script", instance_id) + LOGGER.info( + "instance: %s, No DHCP Associated for VPC and no custom tags. Exiting Script", instance_id) # nothing to do, exit out script caller_response.append( 'No DHCP Associated for VPC and no custom tags. Exiting Script') @@ -688,119 +700,129 @@ def lambda_handler( try: LOGGER.debug("Creating resource records %s", lineno()) create_response = create_resource_record( - route53, - instance_id, - final_hosted_zone_id, - final_private_hostname, - final_hosted_zone_name, - 'A', - private_ip - ) + route53, + instance_id, + final_hosted_zone_id, + final_private_hostname, + final_hosted_zone_name, + 'A', + private_ip + ) append_msg = 'A record in zone id: ' + \ - str(final_hosted_zone_id) + \ - ' for hosted zone ' + \ - str(final_private_hostname) + '.' + \ - str(final_hosted_zone_name) + \ - ' with value: ' + \ - str(private_ip) + str(final_hosted_zone_id) + \ + ' for hosted zone ' + \ + str(final_private_hostname) + '.' + \ + str(final_hosted_zone_name) + \ + ' with value: ' + \ + str(private_ip) if create_response == 'success': - LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) + LOGGER.info("instance: %s, Created %s", + instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) else: caller_response.append(create_response) - caller_response.append('Failed to create ' + append_msg) + caller_response.append('Failed to create ' + append_msg) LOGGER.error('Failed to create A record: %s', create_response) except BaseException as err: - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) try: if len(heritage) > 0: - LOGGER.debug("Creating heritage TXT resource records %s", final_private_hostname + lineno()) + LOGGER.debug("Creating heritage TXT resource records %s", + final_private_hostname + lineno()) create_response = create_resource_record( - route53, - instance_id, - final_hosted_zone_id, - final_private_hostname, - final_hosted_zone_name, - 'TXT', - heritage_value - ) + route53, + instance_id, + final_hosted_zone_id, + final_private_hostname, + final_hosted_zone_name, + 'TXT', + heritage_value + ) append_msg = 'TXT record in zone id: ' + \ - str(final_hosted_zone_id) + \ - ' for hosted zone ' + \ - str(final_private_hostname) + '.' + \ - str(final_hosted_zone_name) + \ - ' with value: ' + \ - str(heritage_value) + str(final_hosted_zone_id) + \ + ' for hosted zone ' + \ + str(final_private_hostname) + '.' + \ + str(final_hosted_zone_name) + \ + ' with value: ' + \ + str(heritage_value) if create_response == 'success': - LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) + LOGGER.info("instance: %s, Created %s", + instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) else: caller_response.append(create_response) - caller_response.append('Failed to create ' + append_msg) + caller_response.append('Failed to create ' + append_msg) LOGGER.error('Failed to create TXT record: %s', create_response) except BaseException as err: - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) try: if reverse_zone_associated: create_response = create_resource_record( - route53, - instance_id, - reverse_lookup_zone_id, - reversed_ip_address, - 'in-addr.arpa', - 'PTR', - final_private_dns_name - ) + route53, + instance_id, + reverse_lookup_zone_id, + reversed_ip_address, + 'in-addr.arpa', + 'PTR', + final_private_dns_name + ) append_msg = 'PTR record in zone id: ' + \ - str(reverse_lookup_zone_id) + \ - ' for hosted zone ' + \ - str(reversed_ip_address) + \ - 'in-addr.arpa with value: ' + \ - str(final_private_dns_name) + str(reverse_lookup_zone_id) + \ + ' for hosted zone ' + \ + str(reversed_ip_address) + \ + 'in-addr.arpa with value: ' + \ + str(final_private_dns_name) if create_response == 'success': - LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) + LOGGER.info("instance: %s, Created %s", + instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) else: caller_response.append(create_response) - caller_response.append('Failed to create ' + append_msg) + caller_response.append('Failed to create ' + append_msg) LOGGER.error('Failed to create PTR record: %s', create_response) except BaseException as err: - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) try: if reverse_zone_associated and len(heritage) > 0: - LOGGER.debug("Creating heritage TXT resource records %s", reversed_ip_address + lineno()) + LOGGER.debug("Creating heritage TXT resource records %s", + reversed_ip_address + lineno()) create_response = create_resource_record( - route53, - instance_id, - reverse_lookup_zone_id, - reversed_ip_address, - 'in-addr.arpa', - 'TXT', - heritage_value - ) + route53, + instance_id, + reverse_lookup_zone_id, + reversed_ip_address, + 'in-addr.arpa', + 'TXT', + heritage_value + ) append_msg = 'TXT reverse record in zone id: ' + \ - str(reverse_lookup_zone_id) + \ - ' for hosted zone ' + \ - str(reversed_ip_address) + \ - 'in-addr.arpa with value: ' + \ - str(heritage_value) + str(reverse_lookup_zone_id) + \ + ' for hosted zone ' + \ + str(reversed_ip_address) + \ + 'in-addr.arpa with value: ' + \ + str(heritage_value) if create_response == 'success': - LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) + LOGGER.info("instance: %s, Created %s", + instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) else: caller_response.append(create_response) - caller_response.append('Failed to create ' + append_msg) + caller_response.append('Failed to create ' + append_msg) LOGGER.error('Failed to create TXT record: %s', create_response) except BaseException as err: - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) # elif state == 'terminated': else: # not running so delete the records @@ -808,19 +830,19 @@ def lambda_handler( try: LOGGER.debug("Deleting A record %s", final_private_hostname + lineno()) response_text = 'Delete A record in zone id: ' + str(final_hosted_zone_id) + \ - ' for hosted zone ' + str(final_private_hostname) + \ - '.' + str(final_hosted_zone_name) + ' with value: ' + \ - str(private_ip) + ' for hosted zone ' + str(final_private_hostname) + \ + '.' + str(final_hosted_zone_name) + ' with value: ' + \ + str(private_ip) delete_response = new_delete_resource_record( - route53, - instance_id, - final_hosted_zone_id, - final_private_hostname, - final_hosted_zone_name, - 'A', - private_ip - ) + route53, + instance_id, + final_hosted_zone_id, + final_private_hostname, + final_hosted_zone_name, + 'A', + private_ip + ) if delete_response == 'NoSuchHostedZone': delete_records = False @@ -829,16 +851,20 @@ def lambda_handler( caller_response.append("Failed, Record Not Found: " + response_text) elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': delete_records = False - caller_response.append("Failed, requested delete do not match existing record: " + response_text) + caller_response.append( + "Failed, requested delete do not match existing record: " + response_text) elif delete_response == {}: delete_records = False - caller_response.append("Failed, could NOT delete Record: " + response_text) - else: + caller_response.append( + "Failed, could NOT delete Record: " + response_text) + else: caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", instance_id, response_text + lineno()) + LOGGER.info("instance: %s, Success: %s", + instance_id, response_text + lineno()) except BaseException as err: delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) # delete TXT record associated with A record try: @@ -855,21 +881,22 @@ def lambda_handler( heritage_value ) if len(heritage) > 0: - LOGGER.debug("Deleting heritage TXT resource records %s", final_private_hostname + lineno()) + LOGGER.debug("Deleting heritage TXT resource records %s", + final_private_hostname + lineno()) response_text = 'Delete TXT record in zone id: ' + str(final_hosted_zone_id) + \ - ' for hosted zone ' + str(final_private_hostname) + \ - '.' + str(final_hosted_zone_name) + ' with value: ' + \ - str(heritage_value) + ' for hosted zone ' + str(final_private_hostname) + \ + '.' + str(final_hosted_zone_name) + ' with value: ' + \ + str(heritage_value) delete_response = new_delete_resource_record( - route53, - instance_id, - final_hosted_zone_id, - final_private_hostname, - final_hosted_zone_name, - 'TXT', - heritage_value - ) + route53, + instance_id, + final_hosted_zone_id, + final_private_hostname, + final_hosted_zone_name, + 'TXT', + heritage_value + ) if delete_response == 'NoSuchHostedZone': delete_records = False caller_response.append("Failed, no such zone: " + response_text) @@ -877,16 +904,20 @@ def lambda_handler( caller_response.append("Failed, Record Not Found: " + response_text) elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': delete_records = False - caller_response.append("Failed, requested delete do not match existing record: " + response_text) + caller_response.append( + "Failed, requested delete do not match existing record: " + response_text) elif delete_response == {}: delete_records = False - caller_response.append("Failed, could NOT delete Record: " + response_text) - else: + caller_response.append( + "Failed, could NOT delete Record: " + response_text) + else: caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", instance_id, response_text + lineno()) + LOGGER.info("instance: %s, Success: %s", + instance_id, response_text + lineno()) except BaseException as err: delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) # delete PTR record try: @@ -895,18 +926,19 @@ def lambda_handler( LOGGER.debug("Deleting PTR record %s", reversed_ip_address + lineno()) response_text = 'Delete PTR record in zone id: ' + str(reverse_lookup_zone_id) + \ - ' for hosted zone ' + str(reversed_ip_address) + \ - str(private_dns_name) + ' with value: ' + str(final_private_dns_name) + ' for hosted zone ' + str(reversed_ip_address) + \ + str(private_dns_name) + ' with value: ' + \ + str(final_private_dns_name) delete_response = new_delete_resource_record( - route53, - instance_id, - reverse_lookup_zone_id, - reversed_ip_address, - 'in-addr.arpa', - 'PTR', - final_private_dns_name - ) + route53, + instance_id, + reverse_lookup_zone_id, + reversed_ip_address, + 'in-addr.arpa', + 'PTR', + final_private_dns_name + ) if delete_response == 'NoSuchHostedZone': delete_records = False @@ -915,16 +947,20 @@ def lambda_handler( caller_response.append("Failed, Record Not Found: " + response_text) elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': delete_records = False - caller_response.append("Failed, requested delete do not match existing record: " + response_text) + caller_response.append( + "Failed, requested delete do not match existing record: " + response_text) elif delete_response == {}: delete_records = False - caller_response.append("Failed, could NOT delete Record: " + response_text) - else: + caller_response.append( + "Failed, could NOT delete Record: " + response_text) + else: caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", instance_id, response_text + lineno()) + LOGGER.info("instance: %s, Success: %s", + instance_id, response_text + lineno()) except BaseException as err: delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) try: if get_rr: @@ -940,20 +976,21 @@ def lambda_handler( heritage_value ) if len(heritage) > 0: - LOGGER.debug("Deleting heritage TXT resource records %s", reversed_ip_address + lineno()) + LOGGER.debug("Deleting heritage TXT resource records %s", + reversed_ip_address + lineno()) response_text = 'Delete TXT record in zone id: ' + str(reverse_lookup_zone_id) + \ - ' for hosted zone ' + str(reversed_ip_address) + str(private_dns_name) + \ - ' with value: ' + str(heritage_value) + ' for hosted zone ' + str(reversed_ip_address) + str(private_dns_name) + \ + ' with value: ' + str(heritage_value) delete_response = new_delete_resource_record( - route53, - instance_id, - reverse_lookup_zone_id, - reversed_ip_address, - 'in-addr.arpa', - 'TXT', - heritage_value - ) + route53, + instance_id, + reverse_lookup_zone_id, + reversed_ip_address, + 'in-addr.arpa', + 'TXT', + heritage_value + ) if delete_response == 'NoSuchHostedZone': delete_records = False caller_response.append("Failed, no such zone: " + response_text) @@ -961,16 +998,20 @@ def lambda_handler( caller_response.append("Failed, Record Not Found: " + response_text) elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': delete_records = False - caller_response.append("Failed, requested delete do not match existing record: " + response_text) + caller_response.append( + "Failed, requested delete do not match existing record: " + response_text) elif delete_response == {}: delete_records = False - caller_response.append("Failed, could NOT delete Record: " + response_text) - else: + caller_response.append( + "Failed, could NOT delete Record: " + response_text) + else: caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", instance_id, response_text + lineno()) + LOGGER.info("instance: %s, Success: %s", + instance_id, response_text + lineno()) except BaseException as err: delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) # Create the CNAME record only if it has passed the check if has_valid_cname_tag: @@ -992,84 +1033,90 @@ def lambda_handler( " %s", str(cname_domain_suffix_id) + lineno()) create_response = create_resource_record( - route53, - instance_id, - cname_domain_suffix_id, - cname_host_name, - cname_domain_suffix, - 'CNAME', - final_private_dns_name - ) + route53, + instance_id, + cname_domain_suffix_id, + cname_host_name, + cname_domain_suffix, + 'CNAME', + final_private_dns_name + ) append_msg = 'CNAME record in zone id: ' + \ - str(cname_domain_suffix_id) + \ - ' for hosted zone ' + \ - str(cname_host_name) + '.' + \ - str(cname_domain_suffix) + \ - ' with value: ' + \ - str(final_private_dns_name) + str(cname_domain_suffix_id) + \ + ' for hosted zone ' + \ + str(cname_host_name) + '.' + \ + str(cname_domain_suffix) + \ + ' with value: ' + \ + str(final_private_dns_name) if create_response == 'success': - LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) + LOGGER.info("instance: %s, Created %s", + instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) else: caller_response.append(create_response) - caller_response.append('Failed to create ' + append_msg) + caller_response.append('Failed to create ' + append_msg) LOGGER.error('Failed to create CNAME record: %s', create_response) except BaseException as err: - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) try: if len(heritage) > 0: - LOGGER.debug("Creating heritage TXT resource records %s", TXT_RR_PREFIX + '.' + cname_host_name + lineno()) + LOGGER.debug("Creating heritage TXT resource records %s", + TXT_RR_PREFIX + '.' + cname_host_name + lineno()) create_response = create_resource_record( - route53, - instance_id, - cname_domain_suffix_id, - TXT_RR_PREFIX + '.' + cname_host_name, - cname_domain_suffix, - 'TXT', - heritage_value - ) + route53, + instance_id, + cname_domain_suffix_id, + TXT_RR_PREFIX + '.' + cname_host_name, + cname_domain_suffix, + 'TXT', + heritage_value + ) append_msg = 'TXT for CNAME record in zone id: ' + \ - str(cname_domain_suffix_id) + \ - ' for hosted zone ' + \ - str(TXT_RR_PREFIX) + '.' + \ - str(cname_host_name) + '.' + \ - str(cname_domain_suffix) + \ - ' with value: ' + \ - str(heritage_value) + str(cname_domain_suffix_id) + \ + ' for hosted zone ' + \ + str(TXT_RR_PREFIX) + '.' + \ + str(cname_host_name) + '.' + \ + str(cname_domain_suffix) + \ + ' with value: ' + \ + str(heritage_value) if create_response == 'success': - LOGGER.info("instance: %s, Created %s", instance_id, append_msg + lineno()) + LOGGER.info("instance: %s, Created %s", + instance_id, append_msg + lineno()) caller_response.append('Created ' + append_msg) else: caller_response.append(create_response) caller_response.append('Failed to create ' + append_msg) - LOGGER.error('Failed to create TXT fpr CNAME record: %s', create_response) + LOGGER.error( + 'Failed to create TXT fpr CNAME record: %s', create_response) except BaseException as err: - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) else: # delete the CNAME record try: LOGGER.debug("deleting CNAME record %s", lineno()) response_text = 'Delete CNAME record in zone id: ' + str(cname_domain_suffix_id) + \ - ' for hosted zone ' + str(cname_host_name) + '.' + \ - str(cname_domain_suffix) + ' with value: ' + \ - str(final_private_dns_name) + ' for hosted zone ' + str(cname_host_name) + '.' + \ + str(cname_domain_suffix) + ' with value: ' + \ + str(final_private_dns_name) # pause 1 before deleting to avoid API limit # time.sleep(1) delete_response = new_delete_resource_record( - route53, - instance_id, - cname_domain_suffix_id, - cname_host_name, - cname_domain_suffix, - 'CNAME', - final_private_dns_name - ) + route53, + instance_id, + cname_domain_suffix_id, + cname_host_name, + cname_domain_suffix, + 'CNAME', + final_private_dns_name + ) if delete_response == 'NoSuchHostedZone': delete_records = False @@ -1078,16 +1125,20 @@ def lambda_handler( caller_response.append("Failed, Record Not Found: " + response_text) elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': delete_records = False - caller_response.append("Failed, requested delete do not match existing record: " + response_text) + caller_response.append( + "Failed, requested delete do not match existing record: " + response_text) elif delete_response == {}: delete_records = False - caller_response.append("Failed, could NOT delete Record: " + response_text) - else: + caller_response.append( + "Failed, could NOT delete Record: " + response_text) + else: caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", instance_id, response_text + lineno()) + LOGGER.info("instance: %s, Success: %s", + instance_id, response_text + lineno()) except BaseException as err: delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) # delete the CNAME txt record try: @@ -1102,52 +1153,61 @@ def lambda_handler( heritage_value ) if len(heritage) > 0: - LOGGER.debug("Deleting heritage TXT resource records %s", TXT_RR_PREFIX + '.' + cname_host_name + lineno()) + LOGGER.debug("Deleting heritage TXT resource records %s", + TXT_RR_PREFIX + '.' + cname_host_name + lineno()) response_text = 'Delete TXT for CNAME record in zone id: ' + str(cname_domain_suffix_id) \ - + ' for hosted zone ' + str(TXT_RR_PREFIX) + '.' + str(cname_host_name) \ - + '.' + str(cname_domain_suffix) + ' with value: ' \ - + str(heritage_value) + + ' for hosted zone ' + str(TXT_RR_PREFIX) + '.' + str(cname_host_name) \ + + '.' + str(cname_domain_suffix) + ' with value: ' \ + + str(heritage_value) delete_response = new_delete_resource_record( - route53, - instance_id, - cname_domain_suffix_id, - TXT_RR_PREFIX + '.' + cname_host_name, - cname_domain_suffix, - 'TXT', - heritage_value - ) + route53, + instance_id, + cname_domain_suffix_id, + TXT_RR_PREFIX + '.' + cname_host_name, + cname_domain_suffix, + 'TXT', + heritage_value + ) if delete_response == 'NoSuchHostedZone': delete_records = False caller_response.append("Failed, no such zone: " + response_text) elif delete_response == 'InvalidChangeBatch-RecordNotFound': - caller_response.append("Failed, Record Not Found: " + response_text) + caller_response.append( + "Failed, Record Not Found: " + response_text) elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': delete_records = False - caller_response.append("Failed, requested delete do not match existing record: " + response_text) + caller_response.append( + "Failed, requested delete do not match existing record: " + response_text) elif delete_response == {}: delete_records = False - caller_response.append("Failed, could NOT delete Record: " + response_text) - else: + caller_response.append( + "Failed, could NOT delete Record: " + response_text) + else: caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", instance_id, response_text + lineno()) + LOGGER.info("instance: %s, Success: %s", + instance_id, response_text + lineno()) except BaseException as err: delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) # Clean up DynamoDB after deleting records if state != 'running': # only if all records were succesfully deleted - if delete_records: + if delete_records: delete_item_from_dynamodb_table(dynamodb_client, DDBNAME, instance_id) - LOGGER.info("instance: %s, deleted the item from DynamoDB: %s", instance_id, DDBNAME + lineno()) + LOGGER.info("instance: %s, deleted the item from DynamoDB: %s", + instance_id, DDBNAME + lineno()) caller_response.insert(0, 'Successfully removed recordsets') return caller_response else: - LOGGER.info("instance: %s, not all records deleted, leaving item in DynamoDB: %s", instance_id, DDBNAME + lineno()) - caller_response.insert(0, 'Failed to remove recordsets, leaving DynamoDB item for instance: ' + instance_id) + LOGGER.info("instance: %s, not all records deleted, leaving item in DynamoDB: %s", + instance_id, DDBNAME + lineno()) + caller_response.insert( + 0, 'Failed to remove recordsets, leaving DynamoDB item for instance: ' + instance_id) return caller_response else: @@ -1208,7 +1268,7 @@ def new_list_hosted_zones(client, instance_id): i = 0 hosted_zones = {} # retry to handle errors in the possible API call - while i < MAX_API_RETRY: + while i < MAX_API_RETRY: try: hosted_zones = client.list_hosted_zones() LOGGER.debug("list_hosted_zones returned without error. %s", lineno()) @@ -1216,18 +1276,23 @@ def new_list_hosted_zones(client, instance_id): except ClientError as 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()) + LOGGER.debug( + "list_hosted_zones throttled due to API limit, retrying: %s", str(err) + lineno()) else: - LOGGER.info("instance: %s, unexpected error. %s\n", instance_id, error_message + lineno()) - i +=1 - LOGGER.info("instance: %s, list_hosted_zones returned error, waiting before retry. %s", instance_id, str(i) + lineno()) + LOGGER.info("instance: %s, unexpected error. %s\n", + instance_id, error_message + lineno()) + i += 1 + LOGGER.info("instance: %s, list_hosted_zones returned error, waiting before retry. %s", + instance_id, str(i) + lineno()) time.sleep(i) if hosted_zones == {}: - LOGGER.error("instance: %s, list_hosted_zones returned error. Timed out. %s", instance_id, str(i) + lineno()) + LOGGER.error("instance: %s, list_hosted_zones returned error. Timed out. %s", + instance_id, str(i) + lineno()) return hosted_zones + def list_tables(client): """ List the dynamodb tables @@ -1255,7 +1320,8 @@ def delete_item_from_dynamodb_table(client, table, instance_id): 'InstanceId': {'S': instance_id} }) except ClientError as err: - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) def put_item_in_dynamodb_table(client, table, instance_id, instance_attributes): @@ -1279,7 +1345,8 @@ def put_item_in_dynamodb_table(client, table, instance_id, instance_attributes): } ) except ClientError as err: - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) def get_item_from_dynamodb_table(client, table, instance_id): @@ -1314,7 +1381,8 @@ def get_item_from_dynamodb_table(client, table, instance_id): return json.loads(item) return None except ClientError as err: - LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) def get_private_hosted_zone_collection(private_hosted_zones): @@ -1461,36 +1529,38 @@ def new_change_resource_recordset(client, instance_id, zone_id, host_name, hoste i = 0 update_response = {} # retry to handle errors in the possible API call - while i < MAX_API_RETRY: + while i < MAX_API_RETRY: try: LOGGER.debug("Creating %s record %s in zone %s" - " %s", record_type, host_name, hosted_zone_name, lineno()) + " %s", record_type, host_name, hosted_zone_name, lineno()) change_batch = { - "Comment": "Updated by Lambda DDNS", - "Changes": [ - { - "Action": "UPSERT", - "ResourceRecordSet": { - "Name": host_name + hosted_zone_name, - "Type": record_type, - "TTL": DNS_RR_TTL, - "ResourceRecords": [ - { - "Value": value - }, - ] - } - }, - ] - } - - LOGGER.debug("change_resource_record_sets change_batch: %s", json.dumps(change_batch) + lineno()) + "Comment": "Updated by Lambda DDNS", + "Changes": [ + { + "Action": "UPSERT", + "ResourceRecordSet": { + "Name": host_name + hosted_zone_name, + "Type": record_type, + "TTL": DNS_RR_TTL, + "ResourceRecords": [ + { + "Value": value + }, + ] + } + }, + ] + } + + LOGGER.debug("change_resource_record_sets change_batch: %s", + json.dumps(change_batch) + lineno()) update_response = client.change_resource_record_sets( HostedZoneId=zone_id, ChangeBatch=change_batch ) - LOGGER.debug("change_resource_record_sets UPSERT returned without error - response: %s", str(update_response) + lineno()) + LOGGER.debug("change_resource_record_sets UPSERT returned without error - response: %s", + str(update_response) + lineno()) break # return response except ClientError as err: @@ -1499,33 +1569,40 @@ def new_change_resource_recordset(client, instance_id, zone_id, host_name, hoste 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()) + LOGGER.error( + "Cannot create record - most likely wrong zone name specified: %s", str(err) + lineno()) 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(err) + lineno()) + LOGGER.debug("change_resource_record_sets UPSERT throttled due to API limit, retrying: %s", str( + err) + lineno()) else: - LOGGER.info("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) - - i +=1 - LOGGER.info("instance: %s, change_resource_record_sets UPSERT returned error, waiting before retry. %s", instance_id, str(i) + lineno()) + LOGGER.info("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) + + i += 1 + LOGGER.info("instance: %s, change_resource_record_sets UPSERT returned error, waiting before retry. %s", + instance_id, str(i) + lineno()) time.sleep(i) if update_response == {}: - if SNS_ENABLE.lower() == 'true': + if SNS_ENABLE: try: sns_msg = {} sns_msg['instance_id'] = instance_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} + sns_msg['change_resource_record_sets'] = { + 'HostedZoneId': zone_id, 'ChangeBatch': change_batch} publish_to_sns(get_sns_client(), json.dumps(sns_msg)) except: - LOGGER.info("instance: %s, error: %s", instance_id, str(sys.exc_info()[0]) + lineno()) + LOGGER.info("instance: %s, error: %s", instance_id, + str(sys.exc_info()[0]) + lineno()) return update_response + def create_resource_record(client, instance_id, zone_id, host_name, hosted_zone_name, record_type, value): """ This function creates resource records in the hosted zone passed by the calling function. @@ -1553,25 +1630,29 @@ def create_resource_record(client, instance_id, zone_id, host_name, hosted_zone_ # time.sleep(1) create_response = new_change_resource_recordset( - client, - instance_id, - zone_id, - host_name, - hosted_zone_name, - record_type, - value - ) + client, + instance_id, + zone_id, + host_name, + hosted_zone_name, + record_type, + value + ) if create_response == 'NoSuchHostedZone': - LOGGER.debug("DNS Record create failed: %s", str(create_response) + lineno()) + LOGGER.debug("DNS Record create failed: %s", + str(create_response) + lineno()) msg = 'NoSuchHostedZone: ' + str(create_response) elif create_response == 'InvalidChangeBatch-WrongZoneName': - LOGGER.debug("DNS Record create failed: %s", str(create_response) + lineno()) + LOGGER.debug("DNS Record create failed: %s", + str(create_response) + lineno()) msg = 'InvalidChangeBatch-WrongZoneName: ' + str(create_response) - elif create_response =={}: - LOGGER.debug("DNS Record create failed: %s", str(create_response) + lineno()) + elif create_response == {}: + LOGGER.debug("DNS Record create failed: %s", + str(create_response) + lineno()) msg = 'DNS Recored Create Failed: ' + str(create_response) - else: - LOGGER.debug("DNS Record create success: %s", str(create_response) + lineno()) + else: + LOGGER.debug("DNS Record create success: %s", + str(create_response) + lineno()) msg = 'success' LOGGER.debug("response: %s", str(create_response) + lineno()) @@ -1640,37 +1721,42 @@ def new_get_resource_record(client, instance_id, zone_id, host_name, hosted_zone i = 0 value = None - while i < MAX_API_RETRY: + while i < MAX_API_RETRY: try: LOGGER.debug("Getting %s record %s in zone %s" - " %s", record_type, host_name, hosted_zone_name, lineno()) + " %s", record_type, host_name, hosted_zone_name, lineno()) if host_name[-1] != '.': host_name = host_name + '.' response = client.list_resource_record_sets( - HostedZoneId=zone_id, - StartRecordName=host_name, - StartRecordType=record_type, - MaxItems=1) + HostedZoneId=zone_id, + StartRecordName=host_name, + StartRecordType=record_type, + MaxItems=1) if len(response) > 0: rr_set = response['ResourceRecordSets'][0] if rr_set['Name'] == host_name and rr_set['Type'] == record_type: value = rr_set['ResourceRecords'][0]['Value'] - - LOGGER.debug("list_resource_record_sets returned without error. %s", lineno()) + + LOGGER.debug( + "list_resource_record_sets returned without error. %s", lineno()) break except ClientError as err: if 'Not Found' in str(err): - LOGGER.debug("list_resource_record_sets not found error: %s", str(err) + lineno()) + LOGGER.debug("list_resource_record_sets not found error: %s", + str(err) + lineno()) if 'InvalidChangeBatch' in str(err) and 'it was not found' in str(err): - LOGGER.debug("list_resource_record_sets not found error: %s", str(err) + lineno()) + LOGGER.debug("list_resource_record_sets not found error: %s", + str(err) + lineno()) - LOGGER.info("instance: %s, list_resource_record_sets unexpected error. %s\n", instance_id, str(err) + lineno()) - - i +=1 - LOGGER.info("instance: %s, list_resource_record_sets returned error, waiting before retry. %s", instance_id, str(i) + lineno()) + LOGGER.info("instance: %s, list_resource_record_sets unexpected error. %s\n", + instance_id, str(err) + lineno()) + + i += 1 + LOGGER.info("instance: %s, list_resource_record_sets returned error, waiting before retry. %s", + instance_id, str(i) + lineno()) time.sleep(i) return value @@ -1745,44 +1831,46 @@ def new_delete_resource_record(client, instance_id, zone_id, host_name, hosted_z i = 0 delete_response = {} # retry to handle errors in the possible API call - while i < MAX_API_RETRY: + while i < MAX_API_RETRY: try: LOGGER.debug("Deleting %s record %s in zone %s" - " %s", record_type, host_name, hosted_zone_name, lineno()) + " %s", record_type, host_name, hosted_zone_name, lineno()) if host_name[-1] != '.': host_name = host_name + '.' change_batch = { - "Comment": "Updated by Lambda DDNS", - "Changes": [ - { - "Action": "DELETE", - "ResourceRecordSet": { - "Name": host_name + hosted_zone_name, - "Type": record_type, - "TTL": DNS_RR_TTL, - "ResourceRecords": [ - { - "Value": value - }, - ] - } - } - ] - } - - LOGGER.debug("change_resource_record_sets change_batch: %s", json.dumps(change_batch) + lineno()) + "Comment": "Updated by Lambda DDNS", + "Changes": [ + { + "Action": "DELETE", + "ResourceRecordSet": { + "Name": host_name + hosted_zone_name, + "Type": record_type, + "TTL": DNS_RR_TTL, + "ResourceRecords": [ + { + "Value": value + }, + ] + } + } + ] + } + + LOGGER.debug("change_resource_record_sets change_batch: %s", + json.dumps(change_batch) + lineno()) delete_response = client.change_resource_record_sets( HostedZoneId=zone_id, ChangeBatch=change_batch ) - LOGGER.debug("change_resource_record_sets DELETE returned without error - response: %s", str(delete_response) + lineno()) + LOGGER.debug("change_resource_record_sets DELETE returned without error - response: %s", + str(delete_response) + lineno()) break except ClientError as err: - + 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" @@ -1792,30 +1880,36 @@ def new_delete_resource_record(client, instance_id, zone_id, host_name, hosted_z 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()) + LOGGER.debug("Record do not match current value error: %s", + str(err) + lineno()) 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(err) + lineno()) + LOGGER.debug("change_resource_record_sets DELETE throttled due to API limit, retrying: %s", str( + err) + lineno()) else: - LOGGER.info("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) - - i +=1 - LOGGER.info("instance: %s, change_resource_record_sets DELETE returned error, waiting before retry. %s", instance_id, str(i) + lineno()) + LOGGER.info("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) + + i += 1 + LOGGER.info("instance: %s, change_resource_record_sets DELETE returned error, waiting before retry. %s", + instance_id, str(i) + lineno()) time.sleep(i) if (delete_response == {} or delete_response == "InvalidChangeBatch-RecordDoNotMatch"): - if SNS_ENABLE.lower() == 'true': + if SNS_ENABLE: try: sns_msg = {} sns_msg['instance_id'] = instance_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} + sns_msg['change_resource_record_sets'] = { + 'HostedZoneId': zone_id, 'ChangeBatch': change_batch} publish_to_sns(get_sns_client(), json.dumps(sns_msg)) except: - LOGGER.info("instance: %s, error: %s", instance_id, str(sys.exc_info()[0]) + lineno()) + LOGGER.info("instance: %s, error: %s", instance_id, + str(sys.exc_info()[0]) + lineno()) return delete_response @@ -1900,26 +1994,27 @@ def is_valid_zone(route53, instance_id, zonename, hosted_zones, vpc_id, private_ hosted_zone_id + lineno()) private_hosted_zone_properties = new_get_hosted_zone_properties( - route53, - instance_id, - hosted_zone_id - ) - + route53, + instance_id, + hosted_zone_id + ) + # need to check if the property is empty {} if private_hosted_zone_properties == {}: - LOGGER.error("get_private_hosted_zone_properties returned no zone property", hosted_zone_id + lineno()) + LOGGER.error( + "get_private_hosted_zone_properties returned no zone property", hosted_zone_id + lineno()) else: LOGGER.debug("private_hosted_zone_properties:" - " %s", str(private_hosted_zone_properties) + lineno()) + " %s", str(private_hosted_zone_properties) + lineno()) # check if the VPC is associated with the PHZ if vpc_id in map(lambda x: x['VPCId'], private_hosted_zone_properties['VPCs']): LOGGER.debug("Privated Hosted Zone associated with VPC: %s", - zonename + lineno()) + zonename + lineno()) return True else: LOGGER.debug("Private Hosted Zone is NOT associated with vpc: %s", - zonename + lineno()) + zonename + lineno()) else: LOGGER.debug("Domain Name does not match Private Hosted Zones: %s", zonename + lineno()) @@ -2149,10 +2244,10 @@ def new_get_hosted_zone_properties(client, instance_id, zone_id): i = 0 hosted_zone_properties = {} # retry to handle errors in the possible API call - while i < MAX_API_RETRY: + while i < MAX_API_RETRY: try: LOGGER.debug('getting hosted zone properties: zone_id: %s', - str(zone_id) + lineno()) + str(zone_id) + lineno()) hosted_zone_properties = client.get_hosted_zone(Id=zone_id) LOGGER.debug('hosted_zone_properties: %s', str( hosted_zone_properties) + lineno()) @@ -2164,12 +2259,15 @@ def new_get_hosted_zone_properties(client, instance_id, zone_id): except ClientError as err: error_message = str(err) if "(Throttling)" in str(err): - LOGGER.debug("get_hosted_zone throttled due to API limit, retrying: %s", str(err) + lineno()) + LOGGER.debug( + "get_hosted_zone throttled due to API limit, retrying: %s", str(err) + lineno()) else: - LOGGER.info("instance: %s, unexpected error. %s\n", instance_id, error_message + lineno()) + LOGGER.info("instance: %s, unexpected error. %s\n", + instance_id, error_message + lineno()) - i +=1 - LOGGER.info("instance: %s, get_hosted_zone returned error, waiting before retry. %s", instance_id, str(i) + lineno()) + i += 1 + LOGGER.info("instance: %s, get_hosted_zone returned error, waiting before retry. %s", + instance_id, str(i) + lineno()) time.sleep(i) return hosted_zone_properties @@ -2291,6 +2389,7 @@ def parse_heritage(info): except: return {} + def publish_to_sns(client, message): """ Publish a simple message to the specified SNS topic @@ -2301,16 +2400,17 @@ def publish_to_sns(client, message): :return: """ - LOGGER.debug("Sending SNS message: %s to SNSTopic %s", str(message), SNS_TOPIC_ARN + lineno()) + LOGGER.debug("Sending SNS message: %s to SNSTopic %s", + str(message), SNS_TOPIC_ARN + lineno()) if SNS_TOPIC_ARN != '': try: response = client.publish( - TopicArn = SNS_TOPIC_ARN, - Message = str(message) - ) - LOGGER.debug("sns response: %s", str(response)+lineno()) + TopicArn=SNS_TOPIC_ARN, + Message=str(message) + ) + LOGGER.debug("sns response: %s", str(response) + lineno()) except ClientError as err: - LOGGER.debug("Unexpected error: %s", str(err)+lineno()) + LOGGER.debug("Unexpected error: %s", str(err) + lineno()) else: LOGGER.debug("No SNS Topic specified, ignoring") diff --git a/role.tf b/role.tf index d4bd1f0..bca3db4 100644 --- a/role.tf +++ b/role.tf @@ -80,6 +80,16 @@ data "aws_iam_policy_document" "lambda_policy" { ] resources = [var.create ? aws_dynamodb_table.table[0].arn : null] } + dynamic "statement" { + for_each = var.create && var.enable_sns > 0 ? toset(["1"]) : toset([]) + iterator = s + content { + sid = "SNSLambdaAccess" + effect = "Allow" + actions = ["sns:Get*", "sns:Publish*"] + resources = [var.create && var.enable_sns ? aws_sns_topic.topic[0].arn : ""] + } + } } data "aws_iam_policy_document" "lambda_assume" { diff --git a/sns.tf b/sns.tf new file mode 100644 index 0000000..2992e4f --- /dev/null +++ b/sns.tf @@ -0,0 +1,55 @@ +locals { + sns_name = var.sns_topic_name != null ? var.sns_topic_name : local.name +} + +resource "aws_sns_topic" "topic" { + count = var.create && var.enable_sns ? 1 : 0 + name = local.sns_name + display_name = "dynr53" +} + +resource "aws_sns_topic_policy" "topic" { + count = var.create && var.enable_sns ? 1 : 0 + arn = var.create && var.enable_sns ? aws_sns_topic.topic[0].arn : "" + policy = data.aws_iam_policy_document.topic.json +} + +# is this too much? +data "aws_iam_policy_document" "topic" { + policy_id = local.sns_name + statement { + sid = "SNSPermissions" + effect = "Allow" + principals { + type = "AWS" + identifiers = ["*"] + } + actions = [ + "sns:Subscribe", + "sns:SetTopicAttributes", + "sns:RemovePermission", + "sns:Receive", + "sns:Publish", + "sns:ListSubscriptionsByTopic", + "sns:GetTopicAttributes", + "sns:DeleteTopic", + "sns:AddPermission", + ] + condition { + test = "StringEquals" + variable = "AWS:SourceOwner" + values = [local.account_id] + } + resources = [aws_sns_topic.topic.arn] + } +} +## +## { +## "Sid": "sns", +## "Effect": "Allow", +## "Action": { +## "sns:Get*", +## "sns:Publish" +## "Resource": +## } +## diff --git a/sqs.tf b/sqs.tf new file mode 100644 index 0000000..e69de29 diff --git a/variables.tf b/variables.tf index 060eafb..b011b4f 100644 --- a/variables.tf +++ b/variables.tf @@ -5,13 +5,25 @@ variable "name" { } variable "dynamodb_table_name" { - description = "Different DynamoDB table name to override default of var.name)" + description = "Different DynamoDB table name to override default of var.name" type = string default = null } variable "lambda_name" { - description = "Different Lambda name to override default of var.name)" + description = "Different Lambda name to override default of var.name" + type = string + default = null +} + +variable "sns_topic_name" { + description = "Different SNS Topic name to override default of var.name" + type = string + default = null +} + +variable "sqs_queue_name" { + description = "Different SQS queue name to override default of var.name" type = string default = null } @@ -26,7 +38,22 @@ variable "lambda_environment_variables" { TagKeyZone = "boc:dns:zone" TagKeyHostName = "boc:dns:name" DNS_RR_TimeToLive = 60 + MaxApiRetry = 10 + SnsTopicArn = "" + SnsEnable = false HeritageTXTRecordPrefix = "_txt" HeritageIdentifier = "dynr53" } } + +variable "enable_sns" { + description = "Enable use of SNS for reporting errors" + type = bool + default = false +} + +variable "enable_sqs" { + description = "Enable use of SQS for SNS to send errors" + type = bool + default = false +}