From 7ae4027c588432cdcb0dc4aaa569d44351e151b8 Mon Sep 17 00:00:00 2001 From: Will Thames Date: Fri, 30 Jun 2017 22:04:48 +1000 Subject: [PATCH] Improve ec2_vpc_subnet check mode (#23108) check_mode should behave pretty similarly to non-check mode - just don't actually create or delete subnets or change tags. Using DryRun for check_mode behaves very differently and results in the following module failure: ``` "msg": "Unable to update tags for subnet-abcd1234, error: EC2ResponseError: 412 Precondition Failed DryRunOperation Request would have succeeded, but DryRun flag is set. 12345678-abcd-1234-abcd-abcd1234abcd" ``` --- .../modules/cloud/amazon/ec2_vpc_subnet.py | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/lib/ansible/modules/cloud/amazon/ec2_vpc_subnet.py b/lib/ansible/modules/cloud/amazon/ec2_vpc_subnet.py index 32c537c7455..bb0c25b9e92 100644 --- a/lib/ansible/modules/cloud/amazon/ec2_vpc_subnet.py +++ b/lib/ansible/modules/cloud/amazon/ec2_vpc_subnet.py @@ -87,22 +87,17 @@ EXAMPLES = ''' ''' import time +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ec2 import ansible_dict_to_boto3_filter_list, ansible_dict_to_boto3_tag_list, ec2_argument_spec +from ansible.module_utils.ec2 import camel_dict_to_snake_dict, get_aws_connection_info +from ansible.module_utils.ec2 import boto3_conn, boto3_tag_list_to_ansible_dict, HAS_BOTO3 try: - import boto3 - from botocore.exceptions import ClientError, NoCredentialsError - HAS_BOTO3 = True + import botocore except ImportError: - HAS_BOTO3 = False - - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.ec2 import (ansible_dict_to_boto3_filter_list, - ansible_dict_to_boto3_tag_list, - ec2_argument_spec, boto3_conn, - boto3_tag_list_to_ansible_dict, - camel_dict_to_snake_dict, - get_aws_connection_info) + pass # caught by imported boto3 def get_subnet_info(subnet): @@ -136,7 +131,7 @@ def subnet_exists(conn, subnet_id): def create_subnet(conn, module, vpc_id, cidr, az, check_mode): try: - new_subnet = get_subnet_info(conn.create_subnet(VpcId=vpc_id, CidrBlock=cidr, AvailabilityZone=az, DryRun=check_mode)) + new_subnet = get_subnet_info(conn.create_subnet(VpcId=vpc_id, CidrBlock=cidr, AvailabilityZone=az)) # Sometimes AWS takes its time to create a subnet and so using # new subnets's id to do things like create tags results in # exception. boto doesn't seem to refresh 'state' of the newly @@ -145,7 +140,7 @@ def create_subnet(conn, module, vpc_id, cidr, az, check_mode): while subnet is False: subnet = subnet_exists(conn, new_subnet['id']) time.sleep(0.1) - except ClientError as e: + except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "DryRunOperation": subnet = None else: @@ -160,14 +155,14 @@ def ensure_tags(conn, module, subnet, tags, add_only, check_mode): cur_tags = subnet['tags'] to_delete = dict((k, cur_tags[k]) for k in cur_tags if k not in tags) - if to_delete and not add_only: - conn.delete_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_delete), DryRun=check_mode) + if to_delete and not add_only and not check_mode: + conn.delete_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_delete)) to_add = dict((k, tags[k]) for k in tags if k not in cur_tags or cur_tags[k] != tags[k]) - if to_add: - conn.create_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_add), DryRun=check_mode) + if to_add and not check_mode: + conn.create_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_add)) - except ClientError as e: + except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] != "DryRunOperation": module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) @@ -179,7 +174,7 @@ def ensure_map_public(conn, module, subnet, map_public, check_mode): try: conn.modify_subnet_attribute(SubnetId=subnet['id'], MapPublicIpOnLaunch={'Value': map_public}) - except ClientError as e: + except botocore.exceptions.ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) @@ -197,7 +192,8 @@ def ensure_subnet_present(conn, module, vpc_id, cidr, az, tags, map_public, chec subnet = get_matching_subnet(conn, vpc_id, cidr) changed = False if subnet is None: - subnet = create_subnet(conn, module, vpc_id, cidr, az, check_mode) + if not check_mode: + subnet = create_subnet(conn, module, vpc_id, cidr, az, check_mode) changed = True # Subnet will be None when check_mode is true if subnet is None: @@ -227,9 +223,10 @@ def ensure_subnet_absent(conn, module, vpc_id, cidr, check_mode): return {'changed': False} try: - conn.delete_subnet(SubnetId=subnet['id'], DryRun=check_mode) + if not check_mode: + conn.delete_subnet(SubnetId=subnet['id'], DryRun=check_mode) return {'changed': True} - except ClientError as e: + except botocore.exceptions.ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) @@ -250,7 +247,7 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) if not HAS_BOTO3: - module.fail_json(msg='boto3 is required for this module') + module.fail_json(msg='boto3 and botocore are required for this module') region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True) @@ -273,7 +270,7 @@ def main(): elif state == 'absent': result = ensure_subnet_absent(connection, module, vpc_id, cidr, check_mode=module.check_mode) - except ClientError as e: + except botocore.exceptions.ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))