From bace230f25692146bc3343e5c9ad4f2b4a829568 Mon Sep 17 00:00:00 2001 From: Joel Thompson Date: Fri, 4 Dec 2015 21:40:49 -0500 Subject: [PATCH] Add new ec2_vpc_dhcp_options module This module manages EC2 DHCP options for a given VPC. It allows you to specify all the options which AWS allows you to set in a DHCP option set. --- .../cloud/amazon/ec2_vpc_dhcp_options.py | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 lib/ansible/modules/extras/cloud/amazon/ec2_vpc_dhcp_options.py diff --git a/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_dhcp_options.py b/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_dhcp_options.py new file mode 100644 index 00000000000..f68a689a887 --- /dev/null +++ b/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_dhcp_options.py @@ -0,0 +1,236 @@ +#!/usr/bin/python + +# 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_dhcp_options +short_description: Ensures the DHCP options for the given VPC match what's + requested +description: + - Converges the DHCP option set for the given VPC to the variables requested. + If any of the optional values are missing, they will either be treated + as a no-op (i.e., inherit what already exists for the VPC) or a purge of + existing options. Most of the options should be self-explanatory. +author: "Joel Thompson (@joelthompson)" +version_added: 2.1 +options: + - domain_name: + description: + - The domain name to set in the DHCP option sets + required: + - false + default: "" + - dns_servers: + description: + - A list of hosts to set the DNS servers for the VPC to. (Should be a + list of IP addresses rather than host names.) + required: false + default: [] + - ntp_servers: + description: + - List of hosts to advertise as NTP servers for the VPC. + required: false + default: [] + - netbios_name_servers: + description: + - List of hosts to advertise as NetBIOS servers. + required: false + default: [] + - netbios_node_type: + description: + - NetBIOS node type to advertise in the DHCP options. The + default is 2, per AWS recommendation + http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_DHCP_Options.html + required: false + default: 2 + - vpc_id: + description: + - VPC ID to associate with the requested DHCP option set + required: true + - delete_old: + description: + - Whether to delete the old VPC DHCP option set when creating a new one. + This is primarily useful for debugging/development purposes when you + want to quickly roll back to the old option set. Note that this setting + will be ignored, and the old DHCP option set will be preserved, if it + is in use by any other VPC. (Otherwise, AWS will return an error.) + required: false + default: true + - inherit_existing: + description: + - For any DHCP options not specified in these parameters, whether to + inherit them from the options set already applied to vpc_id, or to + reset them to be empty. + required: false + default: false +extends_documentation_fragment: aws +requirements: + - boto +""" + +RETURN = """ +new_options: + description: The new DHCP options associated with your VPC + returned: changed + type: dict + sample: + domain-name-servers: + - 10.0.0.1 + - 10.0.1.1 + netbois-name-servers: + - 10.0.0.1 + - 10.0.1.1 + ntp-servers: None + netbios-node-type: 2 + domain-name: "my.example.com" +""" + +EXAMPLES = """ +# Completely overrides the VPC DHCP options associated with VPC vpc-123456 and deletes any existing +# DHCP option set that may have been attached to that VPC. +- ec2_vpc_dhcp_options: + domain_name: "foo.example.com" + region: us-east-1 + dns_servers: + - 10.0.0.1 + - 10.0.1.1 + ntp_servers: + - 10.0.0.2 + - 10.0.1.2 + netbios_name_servers: + - 10.0.0.1 + - 10.0.1.1 + netbios_node_type: 2 + vpc_id: vpc-123456 + delete_old: True + inherit_existing: False + + +# Ensure the DHCP option set for the VPC has 10.0.0.4 and 10.0.1.4 as the specified DNS servers, but +# keep any other existing settings. Also, keep the old DHCP option set around. +- ec2_vpc_dhcp_options: + region: us-east-1 + dns_servers: + - "{{groups['dns-primary']}}" + - "{{groups['dns-secondary']}}" + vpc_id: vpc-123456 + inherit_existing: True + delete_old: False +""" + +import boto.vpc +import socket +import collections + +def _get_associated_dhcp_options(vpc_id, vpc_connection): + """ + Returns the DHCP options object currently associated with the requested VPC ID using the VPC + connection variable. + """ + vpcs = vpc_connection.get_all_vpcs(vpc_ids=[vpc_id]) + if len(vpcs) != 1: + return None + dhcp_options = vpc_connection.get_all_dhcp_options(dhcp_options_ids=[vpcs[0].dhcp_options_id]) + if len(dhcp_options) != 1: + return None + return dhcp_options[0] + + +def _get_vpcs_by_dhcp_options(dhcp_options_id, vpc_connection): + return vpc_connection.get_all_vpcs(filters={'dhcpOptionsId': dhcp_options_id}) + + +def _get_updated_option(requested, existing, inherit): + if inherit and (not requested or requested == ['']): + return existing + else: + return requested + + +def main(): + argument_spec = ec2_argument_spec() + argument_spec.update(dict( + domain_name=dict(type='str', default=''), + dns_servers=dict(type='list', default=[]), + ntp_servers=dict(type='list', default=[]), + netbios_name_servers=dict(type='list', default=[]), + netbios_node_type=dict(type='int', default=2), + vpc_id=dict(type='str', required=True), + delete_old=dict(type='bool', default=True), + inherit_existing=dict(type='bool', default=False) + ) + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + params = module.params + + + region, ec2_url, boto_params = get_aws_connection_info(module) + connection = connect_to_aws(boto.vpc, region, **boto_params) + + inherit_existing = params['inherit_existing'] + + existing_options = _get_associated_dhcp_options(params['vpc_id'], connection) + new_options = collections.defaultdict(lambda: None) + + new_options['domain-name-servers'] = _get_updated_option( params['dns_servers'], + existing_options.options.get('domain-name-servers'), inherit_existing) + + new_options['netbios-name-servers'] = _get_updated_option(params['netbios_name_servers'], + existing_options.options.get('netbios-name-servers'), inherit_existing) + + + new_options['ntp-servers'] = _get_updated_option(params['ntp_servers'], + existing_options.options.get('ntp-servers'), inherit_existing) + + # HACK: Why do I make the next two lists? The boto api returns a list if present, so + # I need this to properly compare so == works. + + # HACK: netbios-node-type is an int, but boto returns a string. So, asking for an int from Ansible + # for data validation, but still need to cast it to a string + new_options['netbios-node-type'] = _get_updated_option( + [str(params['netbios_node_type'])], existing_options.options.get('netbios-node-type'), + inherit_existing) + + new_options['domain-name'] = _get_updated_option( + [params['domain_name']], existing_options.options.get('domain-name'), inherit_existing) + + if existing_options and new_options == existing_options.options: + module.exit_json(changed=False) + + if new_options['netbios-node-type']: + new_options['netbios-node-type'] = new_options['netbios-node-type'][0] + + if new_options['domain-name']: + new_options['domain-name'] = new_options['domain-name'][0] + + if not module.check_mode: + dhcp_option = connection.create_dhcp_options(new_options['domain-name'], + new_options['domain-name-servers'], new_options['ntp-servers'], + new_options['netbios-name-servers'], new_options['netbios-node-type']) + connection.associate_dhcp_options(dhcp_option.id, params['vpc_id']) + if params['delete_old'] and existing_options: + other_vpcs = _get_vpcs_by_dhcp_options(existing_options.id, connection) + if len(other_vpcs) == 0 or (len(other_vpcs) == 1 and other_vpcs[0].id == params['vpc_id']): + connection.delete_dhcp_options(existing_options.id) + + module.exit_json(changed=True, new_options=new_options) + + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import * + +if __name__ == "__main__": + main()