From d8b4bd3c34c1e8989394decad04912973a4dcc96 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Thu, 13 Nov 2014 19:38:52 -0500 Subject: [PATCH 01/11] Split out route table and subnet functionality from VPC module. --- cloud/amazon/ec2_vpc_subnet.py | 307 +++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 cloud/amazon/ec2_vpc_subnet.py diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py new file mode 100644 index 00000000000..396719d4e0a --- /dev/null +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -0,0 +1,307 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: ec2_vpc_subnet +short_description: Configure subnets in AWS virtual private clouds. +description: + - Create or removes AWS subnets in a VPC. This module has a''' +''' dependency on python-boto. +version_added: "1.8" +options: + vpc_id: + description: + - A VPC id in which the subnet resides + required: false + default: null + aliases: [] + resource_tags: + description: + - 'A dictionary array of resource tags of the form: { tag1: value1,''' +''' tag2: value2 }. This module identifies a subnet by CIDR and will update''' +''' the subnet's tags to match. Tags not in this list will be ignored. + required: false + default: null + aliases: [] + cidr: + description: + - "The cidr block for the subnet, e.g. 10.0.0.0/16" + required: false, unless state=present + az: + description: + - "The availability zone for the subnet" + required: false, unless state=present + region: + description: + - region in which the resource exists. + required: false + default: null + aliases: ['aws_region', 'ec2_region'] + state: + description: + - Create or remove the subnet + required: true + default: present + aliases: [] + aws_secret_key: + description: + - AWS secret key. If not set then the value of the AWS_SECRET_KEY''' +''' environment variable is used. + required: false + default: None + aliases: ['ec2_secret_key', 'secret_key' ] + aws_access_key: + description: + - AWS access key. If not set then the value of the AWS_ACCESS_KEY''' +''' environment variable is used. + required: false + default: None + aliases: ['ec2_access_key', 'access_key' ] + validate_certs: + description: + - When set to "no", SSL certificates will not be validated for''' +''' boto versions >= 2.6.0. + required: false + default: "yes" + choices: ["yes", "no"] + aliases: [] + +requirements: ["boto"] +author: Robert Estelle +''' + +EXAMPLES = ''' +# Note: None of these examples set aws_access_key, aws_secret_key, or region. +# It is assumed that their matching environment variables are set. + +# Basic creation example: +- name: Set up the subnet for database servers + local_action: + module: ec2_vpc_subnet + state: present + vpc_id: vpc-123456 + region: us-west-1 + cidr: 10.0.1.16/28 + resource_tags: + Name: Database Subnet + register: database_subnet + +# Removal of a VPC by id +- name: Set up the subnet for database servers + local_action: + module: ec2_vpc + state: absent + vpc_id: vpc-123456 + region: us-west-1 + cidr: 10.0.1.16/28 +''' + + +import sys +import time + +try: + import boto.ec2 + import boto.vpc + from boto.exception import EC2ResponseError +except ImportError: + print "failed=True msg='boto required for this module'" + sys.exit(1) + + +class VPCSubnetException(Exception): + pass + + +class VPCSubnetCreationException(VPCSubnetException): + pass + + +class VPCSubnetDeletionException(VPCSubnetException): + pass + + +class TagCreationException(VPCSubnetException): + pass + + +def subnet_exists(vpc_conn, subnet_id): + filters = {'subnet-id': subnet_id} + return len(vpc_conn.get_all_subnets(filters=filters)) > 0 + + +def create_subnet(vpc_conn, vpc_id, cidr, az): + try: + new_subnet = vpc_conn.create_subnet(vpc_id, cidr, 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 + # created subnet, i.e.: it's always 'pending'. + while not subnet_exists(vpc_conn, new_subnet.id): + time.sleep(0.1) + except EC2ResponseError as e: + raise VPCSubnetCreationException( + 'Unable to create subnet {0}, error: {1}'.format(cidr, e)) + return new_subnet + + +def get_resource_tags(vpc_conn, resource_id): + return {t.name: t.value for t in + vpc_conn.get_all_tags(filters={'resource-id': resource_id})} + + +def dict_diff(old, new): + x = {} + old_keys = set(old.keys()) + new_keys = set(new.keys()) + + for k in old_keys.difference(new_keys): + x[k] = {'old': old[k]} + + for k in new_keys.difference(old_keys): + x[k] = {'new': new[k]} + + for k in new_keys.intersection(old_keys): + if new[k] != old[k]: + x[k] = {'new': new[k], 'old': old[k]} + + return x + + +def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): + try: + cur_tags = get_resource_tags(vpc_conn, resource_id) + diff = dict_diff(cur_tags, tags) + if not diff: + return {'changed': False, 'tags': cur_tags} + + to_delete = {k: diff[k]['old'] for k in diff if 'new' not in diff[k]} + if to_delete and not add_only: + vpc_conn.delete_tags(resource_id, to_delete, dry_run=dry_run) + + to_add = {k: diff[k]['new'] for k in diff if 'old' not in diff[k]} + if to_add: + vpc_conn.create_tags(resource_id, to_add, dry_run=dry_run) + + latest_tags = get_resource_tags(vpc_conn, resource_id) + return {'changed': True, 'tags': latest_tags} + except EC2ResponseError as e: + raise TagCreationException('Unable to update tags for {0}, error: {1}' + .format(resource_id, e)) + + +def get_matching_subnet(vpc_conn, vpc_id, cidr): + subnets = vpc_conn.get_all_subnets(filters={'vpc_id': vpc_id}) + return next((s for s in subnets if s.cidr_block == cidr), None) + + +def ensure_subnet_present(vpc_conn, vpc_id, cidr, az, tags, check_mode): + subnet = get_matching_subnet(vpc_conn, vpc_id, cidr) + changed = False + if subnet is None: + if check_mode: + return {'changed': True, 'subnet_id': None, 'subnet': {}} + + subnet = create_subnet(vpc_conn, vpc_id, cidr, az) + changed = True + + if tags is not None: + tag_result = ensure_tags(vpc_conn, subnet.id, tags, add_only=True, + dry_run=check_mode) + tags = tag_result['tags'] + changed = changed or tag_result['changed'] + else: + tags = get_resource_tags(vpc_conn, subnet.id) + + return { + 'changed': changed, + 'subnet_id': subnet.id, + 'subnet': { + 'tags': tags, + 'cidr': subnet.cidr_block, + 'az': subnet.availability_zone, + 'id': subnet.id, + } + } + + +def ensure_subnet_absent(vpc_conn, vpc_id, cidr, check_mode): + subnet = get_matching_subnet(vpc_conn, vpc_id, cidr) + if subnet is None: + return {'changed': False} + elif check_mode: + return {'changed': True} + + try: + vpc_conn.delete_subnet(subnet.id) + return {'changed': True} + except EC2ResponseError as e: + raise VPCSubnetDeletionException( + 'Unable to delete subnet {0}, error: {1}' + .format(subnet.cidr_block, e)) + + +def main(): + argument_spec = ec2_argument_spec() + argument_spec.update({ + 'vpc_id': {'required': True}, + 'resource_tags': {'type': 'dict', 'required': True}, + 'cidr': {'required': True}, + 'az': {}, + 'state': {'choices': ['present', 'absent'], 'default': 'present'}, + }) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) + if not region: + module.fail_json(msg='Region must be specified') + + try: + vpc_conn = boto.vpc.connect_to_region( + region, + aws_access_key_id=aws_access_key, + aws_secret_access_key=aws_secret_key + ) + except boto.exception.NoAuthHandlerFound as e: + module.fail_json(msg=str(e)) + + vpc_id = module.params.get('vpc_id') + tags = module.params.get('resource_tags') + cidr = module.params.get('cidr') + az = module.params.get('az') + state = module.params.get('state', 'present') + + try: + if state == 'present': + result = ensure_subnet_present(vpc_conn, vpc_id, cidr, az, tags, + check_mode=module.check_mode) + elif state == 'absent': + result = ensure_subnet_absent(vpc_conn, vpc_id, cidr, + check_mode=module.check_mode) + except VPCSubnetException as e: + module.fail_json(msg=str(e)) + + module.exit_json(**result) + +from ansible.module_utils.basic import * # noqa +from ansible.module_utils.ec2 import * # noqa + +if __name__ == '__main__': + main() From fa7848a6c859b4c8842380f4f25852168ecca79f Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Thu, 13 Nov 2014 19:57:15 -0500 Subject: [PATCH 02/11] EC2 subnet/route-table: Simplify tag updating. --- cloud/amazon/ec2_vpc_subnet.py | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index 396719d4e0a..3d2d52c0a58 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -164,36 +164,17 @@ def get_resource_tags(vpc_conn, resource_id): vpc_conn.get_all_tags(filters={'resource-id': resource_id})} -def dict_diff(old, new): - x = {} - old_keys = set(old.keys()) - new_keys = set(new.keys()) - - for k in old_keys.difference(new_keys): - x[k] = {'old': old[k]} - - for k in new_keys.difference(old_keys): - x[k] = {'new': new[k]} - - for k in new_keys.intersection(old_keys): - if new[k] != old[k]: - x[k] = {'new': new[k], 'old': old[k]} - - return x - - def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): try: cur_tags = get_resource_tags(vpc_conn, resource_id) - diff = dict_diff(cur_tags, tags) - if not diff: + if cur_tags == tags: return {'changed': False, 'tags': cur_tags} - to_delete = {k: diff[k]['old'] for k in diff if 'new' not in diff[k]} + to_delete = {k: cur_tags[k] for k in cur_tags if k not in tags} if to_delete and not add_only: vpc_conn.delete_tags(resource_id, to_delete, dry_run=dry_run) - to_add = {k: diff[k]['new'] for k in diff if 'old' not in diff[k]} + to_add = {k: tags[k] for k in tags if k not in cur_tags} if to_add: vpc_conn.create_tags(resource_id, to_add, dry_run=dry_run) From 54809003e31d296d544d8b51ee73cf8183dbe463 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 13:41:03 -0500 Subject: [PATCH 03/11] ec2_vpc - VPCException -> AnsibleVPCException --- cloud/amazon/ec2_vpc_subnet.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index 3d2d52c0a58..f0b8d3d5011 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -123,19 +123,19 @@ except ImportError: sys.exit(1) -class VPCSubnetException(Exception): +class AnsibleVPCSubnetException(Exception): pass -class VPCSubnetCreationException(VPCSubnetException): +class AnsibleVPCSubnetCreationException(AnsibleVPCSubnetException): pass -class VPCSubnetDeletionException(VPCSubnetException): +class AnsibleVPCSubnetDeletionException(AnsibleVPCSubnetException): pass -class TagCreationException(VPCSubnetException): +class AnsibleTagCreationException(AnsibleVPCSubnetException): pass @@ -154,7 +154,7 @@ def create_subnet(vpc_conn, vpc_id, cidr, az): while not subnet_exists(vpc_conn, new_subnet.id): time.sleep(0.1) except EC2ResponseError as e: - raise VPCSubnetCreationException( + raise AnsibleVPCSubnetCreationException( 'Unable to create subnet {0}, error: {1}'.format(cidr, e)) return new_subnet @@ -181,8 +181,8 @@ def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): latest_tags = get_resource_tags(vpc_conn, resource_id) return {'changed': True, 'tags': latest_tags} except EC2ResponseError as e: - raise TagCreationException('Unable to update tags for {0}, error: {1}' - .format(resource_id, e)) + raise AnsibleTagCreationException( + 'Unable to update tags for {0}, error: {1}'.format(resource_id, e)) def get_matching_subnet(vpc_conn, vpc_id, cidr): @@ -231,7 +231,7 @@ def ensure_subnet_absent(vpc_conn, vpc_id, cidr, check_mode): vpc_conn.delete_subnet(subnet.id) return {'changed': True} except EC2ResponseError as e: - raise VPCSubnetDeletionException( + raise AnsibleVPCSubnetDeletionException( 'Unable to delete subnet {0}, error: {1}' .format(subnet.cidr_block, e)) @@ -276,7 +276,7 @@ def main(): elif state == 'absent': result = ensure_subnet_absent(vpc_conn, vpc_id, cidr, check_mode=module.check_mode) - except VPCSubnetException as e: + except AnsibleVPCSubnetException as e: module.fail_json(msg=str(e)) module.exit_json(**result) From 6f978b9c0018b04d1ec10d6643a85123a9fa5a50 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 13:45:50 -0500 Subject: [PATCH 04/11] ec2_vpc - Fail module using fail_json on boto import failure. --- cloud/amazon/ec2_vpc_subnet.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index f0b8d3d5011..a22d4b45de1 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -111,16 +111,18 @@ EXAMPLES = ''' ''' -import sys +import sys # noqa import time try: import boto.ec2 import boto.vpc from boto.exception import EC2ResponseError + HAS_BOTO = True except ImportError: - print "failed=True msg='boto required for this module'" - sys.exit(1) + HAS_BOTO = False + if __name__ != '__main__': + raise class AnsibleVPCSubnetException(Exception): @@ -249,6 +251,8 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, ) + if not HAS_BOTO: + module.fail_json(msg='boto is required for this module') ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) if not region: From 0de9722e0cc1cb910af2845d298c73a1da1d63d1 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 14:43:58 -0500 Subject: [PATCH 05/11] ec2_vpc_subnet - resource_tags is not required. --- cloud/amazon/ec2_vpc_subnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index a22d4b45de1..5c7fc8596a2 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -242,7 +242,7 @@ def main(): argument_spec = ec2_argument_spec() argument_spec.update({ 'vpc_id': {'required': True}, - 'resource_tags': {'type': 'dict', 'required': True}, + 'resource_tags': {'type': 'dict', 'required': False}, 'cidr': {'required': True}, 'az': {}, 'state': {'choices': ['present', 'absent'], 'default': 'present'}, From e33de8bd88cdf0c0f8cb4e8489889147bc3767e8 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 15:18:56 -0500 Subject: [PATCH 06/11] ec2_vpc - More dry running in check mode. --- cloud/amazon/ec2_vpc_subnet.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index 5c7fc8596a2..b79a48f25fe 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -166,7 +166,7 @@ def get_resource_tags(vpc_conn, resource_id): vpc_conn.get_all_tags(filters={'resource-id': resource_id})} -def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): +def ensure_tags(vpc_conn, resource_id, tags, add_only, check_mode): try: cur_tags = get_resource_tags(vpc_conn, resource_id) if cur_tags == tags: @@ -174,11 +174,11 @@ def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): to_delete = {k: cur_tags[k] for k in cur_tags if k not in tags} if to_delete and not add_only: - vpc_conn.delete_tags(resource_id, to_delete, dry_run=dry_run) + vpc_conn.delete_tags(resource_id, to_delete, dry_run=check_mode) to_add = {k: tags[k] for k in tags if k not in cur_tags} if to_add: - vpc_conn.create_tags(resource_id, to_add, dry_run=dry_run) + vpc_conn.create_tags(resource_id, to_add, dry_run=check_mode) latest_tags = get_resource_tags(vpc_conn, resource_id) return {'changed': True, 'tags': latest_tags} @@ -204,7 +204,7 @@ def ensure_subnet_present(vpc_conn, vpc_id, cidr, az, tags, check_mode): if tags is not None: tag_result = ensure_tags(vpc_conn, subnet.id, tags, add_only=True, - dry_run=check_mode) + check_mode=check_mode) tags = tag_result['tags'] changed = changed or tag_result['changed'] else: @@ -226,11 +226,9 @@ def ensure_subnet_absent(vpc_conn, vpc_id, cidr, check_mode): subnet = get_matching_subnet(vpc_conn, vpc_id, cidr) if subnet is None: return {'changed': False} - elif check_mode: - return {'changed': True} try: - vpc_conn.delete_subnet(subnet.id) + vpc_conn.delete_subnet(subnet.id, dry_run=check_mode) return {'changed': True} except EC2ResponseError as e: raise AnsibleVPCSubnetDeletionException( From 3b38209afd8e9f255e9fdde976c7d338675e1896 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Wed, 3 Dec 2014 13:24:54 -0500 Subject: [PATCH 07/11] ec2_vpc_subnet - Use dict constructor instead of comprehension. --- cloud/amazon/ec2_vpc_subnet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index b79a48f25fe..775e8b9b12d 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -162,8 +162,8 @@ def create_subnet(vpc_conn, vpc_id, cidr, az): def get_resource_tags(vpc_conn, resource_id): - return {t.name: t.value for t in - vpc_conn.get_all_tags(filters={'resource-id': resource_id})} + return dict((t.name, t.value) for t in + vpc_conn.get_all_tags(filters={'resource-id': resource_id})) def ensure_tags(vpc_conn, resource_id, tags, add_only, check_mode): @@ -172,11 +172,11 @@ def ensure_tags(vpc_conn, resource_id, tags, add_only, check_mode): if cur_tags == tags: return {'changed': False, 'tags': cur_tags} - to_delete = {k: cur_tags[k] for k in cur_tags if k not in 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: vpc_conn.delete_tags(resource_id, to_delete, dry_run=check_mode) - to_add = {k: tags[k] for k in tags if k not in cur_tags} + to_add = dict((k, tags[k]) for k in tags if k not in cur_tags) if to_add: vpc_conn.create_tags(resource_id, to_add, dry_run=check_mode) From 65f98d53af8400259f35f3f6eedd7d262a269053 Mon Sep 17 00:00:00 2001 From: whiter Date: Thu, 11 Jun 2015 15:00:55 +1000 Subject: [PATCH 08/11] Updated documentation --- cloud/amazon/ec2_vpc_subnet.py | 83 +++++++++------------------------- 1 file changed, 21 insertions(+), 62 deletions(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index 775e8b9b12d..1bfe8bd4741 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -1,116 +1,74 @@ #!/usr/bin/python -# This file is part of Ansible # -# Ansible is free software: you can redistribute it and/or modify +# This is a free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# Ansible is distributed in the hope that it will be useful, +# This Ansible library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . +# along with this library. If not, see . DOCUMENTATION = ''' --- module: ec2_vpc_subnet -short_description: Configure subnets in AWS virtual private clouds. +short_description: Manage subnets in AWS virtual private clouds description: - - Create or removes AWS subnets in a VPC. This module has a''' -''' dependency on python-boto. -version_added: "1.8" + - Manage subnets in AWS virtual private clouds +version_added: "2.0" +author: Robert Estelle, @erydo options: vpc_id: description: - - A VPC id in which the subnet resides + - VPC ID of the VPC in which to create the subnet. required: false default: null - aliases: [] resource_tags: description: - - 'A dictionary array of resource tags of the form: { tag1: value1,''' -''' tag2: value2 }. This module identifies a subnet by CIDR and will update''' -''' the subnet's tags to match. Tags not in this list will be ignored. + - A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. This module identifies a subnet by CIDR and will update the subnet's tags to match. Tags not in this list will be ignored. required: false default: null - aliases: [] cidr: description: - - "The cidr block for the subnet, e.g. 10.0.0.0/16" - required: false, unless state=present + - The CIDR block for the subnet. E.g. 10.0.0.0/16. Only required when state=present." + required: false az: description: - - "The availability zone for the subnet" - required: false, unless state=present - region: - description: - - region in which the resource exists. + - "The availability zone for the subnet. Only required when state=present." required: false - default: null - aliases: ['aws_region', 'ec2_region'] state: description: - Create or remove the subnet required: true default: present - aliases: [] - aws_secret_key: - description: - - AWS secret key. If not set then the value of the AWS_SECRET_KEY''' -''' environment variable is used. - required: false - default: None - aliases: ['ec2_secret_key', 'secret_key' ] - aws_access_key: - description: - - AWS access key. If not set then the value of the AWS_ACCESS_KEY''' -''' environment variable is used. - required: false - default: None - aliases: ['ec2_access_key', 'access_key' ] - validate_certs: - description: - - When set to "no", SSL certificates will not be validated for''' -''' boto versions >= 2.6.0. - required: false - default: "yes" - choices: ["yes", "no"] - aliases: [] - -requirements: ["boto"] -author: Robert Estelle + choices: [ 'present', 'absent' ] +extends_documentation_fragment: aws ''' EXAMPLES = ''' -# Note: None of these examples set aws_access_key, aws_secret_key, or region. -# It is assumed that their matching environment variables are set. +# Note: These examples do not set authentication details, see the AWS Guide for details. -# Basic creation example: -- name: Set up the subnet for database servers - local_action: - module: ec2_vpc_subnet +- name: Create subnet for database servers + ec2_vpc_subnet: state: present vpc_id: vpc-123456 - region: us-west-1 cidr: 10.0.1.16/28 resource_tags: Name: Database Subnet register: database_subnet -# Removal of a VPC by id -- name: Set up the subnet for database servers - local_action: - module: ec2_vpc +- name: Remove subnet for database servers + ec2_vpc_subnet: state: absent vpc_id: vpc-123456 - region: us-west-1 cidr: 10.0.1.16/28 + ''' - import sys # noqa import time @@ -288,3 +246,4 @@ from ansible.module_utils.ec2 import * # noqa if __name__ == '__main__': main() + \ No newline at end of file From c93cf8c0547eb0631145c60bbd0a19a55893f6dd Mon Sep 17 00:00:00 2001 From: whiter Date: Fri, 19 Jun 2015 12:10:03 +1000 Subject: [PATCH 09/11] Changed to use "connect_to_aws" method --- cloud/amazon/ec2_vpc_subnet.py | 54 +++++++++++++++++----------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index 1bfe8bd4741..56efd85841a 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -20,7 +20,7 @@ short_description: Manage subnets in AWS virtual private clouds description: - Manage subnets in AWS virtual private clouds version_added: "2.0" -author: Robert Estelle, @erydo +author: Robert Estelle (@erydo) options: vpc_id: description: @@ -36,14 +36,16 @@ options: description: - The CIDR block for the subnet. E.g. 10.0.0.0/16. Only required when state=present." required: false + default: null az: description: - "The availability zone for the subnet. Only required when state=present." required: false + default: null state: description: - Create or remove the subnet - required: true + required: false default: present choices: [ 'present', 'absent' ] extends_documentation_fragment: aws @@ -196,45 +198,43 @@ def ensure_subnet_absent(vpc_conn, vpc_id, cidr, check_mode): def main(): argument_spec = ec2_argument_spec() - argument_spec.update({ - 'vpc_id': {'required': True}, - 'resource_tags': {'type': 'dict', 'required': False}, - 'cidr': {'required': True}, - 'az': {}, - 'state': {'choices': ['present', 'absent'], 'default': 'present'}, - }) - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, + argument_spec.update( + dict( + vpc_id = dict(default=None, required=True), + resource_tags = dict(default=None, required=False, type='dict'), + cidr = dict(default=None, required=True), + az = dict(default=None, required=False), + state = dict(default='present', choices=['present', 'absent']) + ) ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + if not HAS_BOTO: module.fail_json(msg='boto is required for this module') - ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) - if not region: - module.fail_json(msg='Region must be specified') - - try: - vpc_conn = boto.vpc.connect_to_region( - region, - aws_access_key_id=aws_access_key, - aws_secret_access_key=aws_secret_key - ) - except boto.exception.NoAuthHandlerFound as e: - module.fail_json(msg=str(e)) + region, ec2_url, aws_connect_params = get_aws_connection_info(module) + + if region: + try: + connection = connect_to_aws(boto.vpc, region, **aws_connect_params) + except (boto.exception.NoAuthHandlerFound, StandardError), e: + module.fail_json(msg=str(e)) + else: + module.fail_json(msg="region must be specified") vpc_id = module.params.get('vpc_id') tags = module.params.get('resource_tags') cidr = module.params.get('cidr') az = module.params.get('az') - state = module.params.get('state', 'present') + state = module.params.get('state') try: if state == 'present': - result = ensure_subnet_present(vpc_conn, vpc_id, cidr, az, tags, + result = ensure_subnet_present(connection, vpc_id, cidr, az, tags, check_mode=module.check_mode) elif state == 'absent': - result = ensure_subnet_absent(vpc_conn, vpc_id, cidr, + result = ensure_subnet_absent(connection, vpc_id, cidr, check_mode=module.check_mode) except AnsibleVPCSubnetException as e: module.fail_json(msg=str(e)) From d40bdd464588a1d971393af7af7a033a52797088 Mon Sep 17 00:00:00 2001 From: whiter Date: Thu, 30 Jul 2015 11:37:01 +1000 Subject: [PATCH 10/11] Updated doco for vpc_subnet --- cloud/amazon/ec2_vpc_subnet.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index 56efd85841a..f7d96862685 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -22,32 +22,34 @@ description: version_added: "2.0" author: Robert Estelle (@erydo) options: - vpc_id: - description: - - VPC ID of the VPC in which to create the subnet. - required: false - default: null - resource_tags: + az: description: - - A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. This module identifies a subnet by CIDR and will update the subnet's tags to match. Tags not in this list will be ignored. + - "The availability zone for the subnet. Only required when state=present." required: false default: null cidr: description: - - The CIDR block for the subnet. E.g. 10.0.0.0/16. Only required when state=present." + - "The CIDR block for the subnet. E.g. 10.0.0.0/16. Only required when state=present." required: false default: null - az: + tags: description: - - "The availability zone for the subnet. Only required when state=present." + - "A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. This module identifies a subnet by CIDR and will update the subnet's tags to match. Tags not in this list will be ignored." required: false default: null + aliases: [ 'resource_tags' ] state: description: - - Create or remove the subnet + - "Create or remove the subnet" required: false default: present choices: [ 'present', 'absent' ] + vpc_id: + description: + - "VPC ID of the VPC in which to create the subnet." + required: false + default: null + extends_documentation_fragment: aws ''' From e299952bca2e81d83461ad78c98606aeee3f29b3 Mon Sep 17 00:00:00 2001 From: whiter Date: Sun, 2 Aug 2015 21:46:40 +1000 Subject: [PATCH 11/11] Changed resource_tags to tags to match other modules (resource_tags still an alias) Added get_subnet_info method to return more subnet info - matches same method in ec2_vpc_subnet_facts module Rework of tags - will now only apply the tags listed in the module. Existing tags not listed will be removed (desired state!) --- cloud/amazon/ec2_vpc_subnet.py | 85 +++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/cloud/amazon/ec2_vpc_subnet.py b/cloud/amazon/ec2_vpc_subnet.py index f7d96862685..45e84f66939 100644 --- a/cloud/amazon/ec2_vpc_subnet.py +++ b/cloud/amazon/ec2_vpc_subnet.py @@ -34,7 +34,7 @@ options: default: null tags: description: - - "A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. This module identifies a subnet by CIDR and will update the subnet's tags to match. Tags not in this list will be ignored." + - "A dict of tags to apply to the subnet. Any tags currently applied to the subnet and not present here will be removed." required: false default: null aliases: [ 'resource_tags' ] @@ -103,24 +103,49 @@ class AnsibleTagCreationException(AnsibleVPCSubnetException): pass +def get_subnet_info(subnet): + + subnet_info = { 'id': subnet.id, + 'availability_zone': subnet.availability_zone, + 'available_ip_address_count': subnet.available_ip_address_count, + 'cidr_block': subnet.cidr_block, + 'default_for_az': subnet.defaultForAz, + 'map_public_ip_on_launch': subnet.mapPublicIpOnLaunch, + 'state': subnet.state, + 'tags': subnet.tags, + 'vpc_id': subnet.vpc_id + } + + return subnet_info + def subnet_exists(vpc_conn, subnet_id): filters = {'subnet-id': subnet_id} - return len(vpc_conn.get_all_subnets(filters=filters)) > 0 + subnet = vpc_conn.get_all_subnets(filters=filters) + if subnet[0].state == "available": + return subnet[0] + else: + return False -def create_subnet(vpc_conn, vpc_id, cidr, az): +def create_subnet(vpc_conn, vpc_id, cidr, az, check_mode): try: - new_subnet = vpc_conn.create_subnet(vpc_id, cidr, az) + new_subnet = vpc_conn.create_subnet(vpc_id, cidr, az, dry_run=check_mode) # 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 # created subnet, i.e.: it's always 'pending'. - while not subnet_exists(vpc_conn, new_subnet.id): + subnet = False + while subnet is False: + subnet = subnet_exists(vpc_conn, new_subnet.id) time.sleep(0.1) except EC2ResponseError as e: - raise AnsibleVPCSubnetCreationException( - 'Unable to create subnet {0}, error: {1}'.format(cidr, e)) - return new_subnet + if e.error_code == "DryRunOperation": + subnet = None + else: + raise AnsibleVPCSubnetCreationException( + 'Unable to create subnet {0}, error: {1}'.format(cidr, e)) + + return subnet def get_resource_tags(vpc_conn, resource_id): @@ -158,29 +183,25 @@ def ensure_subnet_present(vpc_conn, vpc_id, cidr, az, tags, check_mode): subnet = get_matching_subnet(vpc_conn, vpc_id, cidr) changed = False if subnet is None: - if check_mode: - return {'changed': True, 'subnet_id': None, 'subnet': {}} - - subnet = create_subnet(vpc_conn, vpc_id, cidr, az) + subnet = create_subnet(vpc_conn, vpc_id, cidr, az, check_mode) + changed = True + # Subnet will be None when check_mode is true + if subnet is None: + return { + 'changed': changed, + 'subnet': {} + } + + if tags != subnet.tags: + ensure_tags(vpc_conn, subnet.id, tags, False, check_mode) + subnet.tags = tags changed = True - if tags is not None: - tag_result = ensure_tags(vpc_conn, subnet.id, tags, add_only=True, - check_mode=check_mode) - tags = tag_result['tags'] - changed = changed or tag_result['changed'] - else: - tags = get_resource_tags(vpc_conn, subnet.id) + subnet_info = get_subnet_info(subnet) return { 'changed': changed, - 'subnet_id': subnet.id, - 'subnet': { - 'tags': tags, - 'cidr': subnet.cidr_block, - 'az': subnet.availability_zone, - 'id': subnet.id, - } + 'subnet': subnet_info } @@ -202,11 +223,11 @@ def main(): argument_spec = ec2_argument_spec() argument_spec.update( dict( - vpc_id = dict(default=None, required=True), - resource_tags = dict(default=None, required=False, type='dict'), - cidr = dict(default=None, required=True), az = dict(default=None, required=False), - state = dict(default='present', choices=['present', 'absent']) + cidr = dict(default=None, required=True), + state = dict(default='present', choices=['present', 'absent']), + tags = dict(default=None, required=False, type='dict', aliases=['resource_tags']), + vpc_id = dict(default=None, required=True) ) ) @@ -226,7 +247,7 @@ def main(): module.fail_json(msg="region must be specified") vpc_id = module.params.get('vpc_id') - tags = module.params.get('resource_tags') + tags = module.params.get('tags') cidr = module.params.get('cidr') az = module.params.get('az') state = module.params.get('state') @@ -248,4 +269,4 @@ from ansible.module_utils.ec2 import * # noqa if __name__ == '__main__': main() - \ No newline at end of file +