diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py new file mode 100644 index 00000000000..70f53bad26a --- /dev/null +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -0,0 +1,597 @@ +#!/usr/bin/python +# +# 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. +# +# 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 this library. If not, see . + +DOCUMENTATION = ''' +--- +module: ec2_vpc_route_table +short_description: Manage route tables for AWS virtual private clouds +description: + - Manage route tables for AWS virtual private clouds +version_added: "2.0" +author: Robert Estelle (@erydo), Rob White (@wimnat) +options: + lookup: + description: + - "Look up route table by either tags or by route table ID. Non-unique tag lookup will fail. If no tags are specifed then no lookup for an existing route table is performed and a new route table will be created. To change tags of a route table, you must look up by id." + required: false + default: tag + choices: [ 'tag', 'id' ] + propagating_vgw_ids: + description: + - "Enable route propagation from virtual gateways specified by ID." + required: false + route_table_id: + description: + - "The ID of the route table to update or delete." + required: false + default: null + routes: + description: + - "List of routes in the route table. Routes are specified as dicts containing the keys 'dest' and one of 'gateway_id', 'instance_id', 'interface_id', or 'vpc_peering_connection'. If 'gateway_id' is specified, you can refer to the VPC's IGW by using the value 'igw'." + required: true + state: + description: + - "Create or destroy the VPC route table" + required: false + default: present + choices: [ 'present', 'absent' ] + subnets: + description: + - "An array of subnets to add to this route table. Subnets may be specified by either subnet ID, Name tag, or by a CIDR such as '10.0.0.0/24'." + required: true + tags: + description: + - "A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. Tags in this list are used to uniquely identify route tables within a VPC when the route_table_id is not supplied." + required: false + default: null + aliases: [ "resource_tags" ] + vpc_id: + description: + - "VPC ID of the VPC in which to create the route table." + required: true + +extends_documentation_fragment: aws +''' + +EXAMPLES = ''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +# Basic creation example: +- name: Set up public subnet route table + ec2_vpc_route_table: + vpc_id: vpc-1245678 + region: us-west-1 + tags: + Name: Public + subnets: + - "{{ jumpbox_subnet.subnet_id }}" + - "{{ frontend_subnet.subnet_id }}" + - "{{ vpn_subnet.subnet_id }}" + routes: + - dest: 0.0.0.0/0 + gateway_id: "{{ igw.gateway_id }}" + register: public_route_table + +- name: Set up NAT-protected route table + ec2_vpc_route_table: + vpc_id: vpc-1245678 + region: us-west-1 + tags: + - Name: Internal + subnets: + - "{{ application_subnet.subnet_id }}" + - 'Database Subnet' + - '10.0.0.0/8' + routes: + - dest: 0.0.0.0/0 + instance_id: "{{ nat.instance_id }}" + register: nat_route_table + +''' + + +import sys # noqa +import re + +try: + import boto.ec2 + import boto.vpc + from boto.exception import EC2ResponseError + HAS_BOTO = True +except ImportError: + HAS_BOTO = False + if __name__ != '__main__': + raise + + +class AnsibleRouteTableException(Exception): + pass + + +class AnsibleIgwSearchException(AnsibleRouteTableException): + pass + + +class AnsibleTagCreationException(AnsibleRouteTableException): + pass + + +class AnsibleSubnetSearchException(AnsibleRouteTableException): + pass + +CIDR_RE = re.compile('^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$') +SUBNET_RE = re.compile('^subnet-[A-z0-9]+$') +ROUTE_TABLE_RE = re.compile('^rtb-[A-z0-9]+$') + + +def find_subnets(vpc_conn, vpc_id, identified_subnets): + """ + Finds a list of subnets, each identified either by a raw ID, a unique + 'Name' tag, or a CIDR such as 10.0.0.0/8. + + Note that this function is duplicated in other ec2 modules, and should + potentially be moved into potentially be moved into a shared module_utils + """ + subnet_ids = [] + subnet_names = [] + subnet_cidrs = [] + for subnet in (identified_subnets or []): + if re.match(SUBNET_RE, subnet): + subnet_ids.append(subnet) + elif re.match(CIDR_RE, subnet): + subnet_cidrs.append(subnet) + else: + subnet_names.append(subnet) + + subnets_by_id = [] + if subnet_ids: + subnets_by_id = vpc_conn.get_all_subnets( + subnet_ids, filters={'vpc_id': vpc_id}) + + for subnet_id in subnet_ids: + if not any(s.id == subnet_id for s in subnets_by_id): + raise AnsibleSubnetSearchException( + 'Subnet ID "{0}" does not exist'.format(subnet_id)) + + subnets_by_cidr = [] + if subnet_cidrs: + subnets_by_cidr = vpc_conn.get_all_subnets( + filters={'vpc_id': vpc_id, 'cidr': subnet_cidrs}) + + for cidr in subnet_cidrs: + if not any(s.cidr_block == cidr for s in subnets_by_cidr): + raise AnsibleSubnetSearchException( + 'Subnet CIDR "{0}" does not exist'.format(subnet_cidr)) + + subnets_by_name = [] + if subnet_names: + subnets_by_name = vpc_conn.get_all_subnets( + filters={'vpc_id': vpc_id, 'tag:Name': subnet_names}) + + for name in subnet_names: + matching = [s.tags.get('Name') == name for s in subnets_by_name] + if len(matching) == 0: + raise AnsibleSubnetSearchException( + 'Subnet named "{0}" does not exist'.format(name)) + elif len(matching) > 1: + raise AnsibleSubnetSearchException( + 'Multiple subnets named "{0}"'.format(name)) + + return subnets_by_id + subnets_by_cidr + subnets_by_name + + +def find_igw(vpc_conn, vpc_id): + """ + Finds the Internet gateway for the given VPC ID. + + Raises an AnsibleIgwSearchException if either no IGW can be found, or more + than one found for the given VPC. + + Note that this function is duplicated in other ec2 modules, and should + potentially be moved into potentially be moved into a shared module_utils + """ + igw = vpc_conn.get_all_internet_gateways( + filters={'attachment.vpc-id': vpc_id}) + + if not igw: + raise AnsibleIgwSearchException('No IGW found for VPC {0}'. + format(vpc_id)) + elif len(igw) == 1: + return igw[0].id + else: + raise AnsibleIgwSearchException('Multiple IGWs found for VPC {0}'. + format(vpc_id)) + + +def get_resource_tags(vpc_conn, resource_id): + return dict((t.name, t.value) for t in + vpc_conn.get_all_tags(filters={'resource-id': resource_id})) + + +def tags_match(match_tags, candidate_tags): + return all((k in candidate_tags and candidate_tags[k] == v + for k, v in match_tags.iteritems())) + + +def ensure_tags(vpc_conn, resource_id, tags, add_only, check_mode): + try: + cur_tags = get_resource_tags(vpc_conn, resource_id) + if tags == cur_tags: + return {'changed': False, 'tags': cur_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 = 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) + + latest_tags = get_resource_tags(vpc_conn, resource_id) + return {'changed': True, 'tags': latest_tags} + except EC2ResponseError as e: + raise AnsibleTagCreationException( + 'Unable to update tags for {0}, error: {1}'.format(resource_id, e)) + + +def get_route_table_by_id(vpc_conn, vpc_id, route_table_id): + + route_table = None + route_tables = vpc_conn.get_all_route_tables(route_table_ids=[route_table_id], filters={'vpc_id': vpc_id}) + if route_tables: + route_table = route_tables[0] + + return route_table + +def get_route_table_by_tags(vpc_conn, vpc_id, tags): + + count = 0 + route_table = None + route_tables = vpc_conn.get_all_route_tables(filters={'vpc_id': vpc_id}) + for table in route_tables: + this_tags = get_resource_tags(vpc_conn, table.id) + if tags_match(tags, this_tags): + route_table = table + count +=1 + + if count > 1: + raise RuntimeError("Tags provided do not identify a unique route table") + else: + return route_table + + +def route_spec_matches_route(route_spec, route): + key_attr_map = { + 'destination_cidr_block': 'destination_cidr_block', + 'gateway_id': 'gateway_id', + 'instance_id': 'instance_id', + 'interface_id': 'interface_id', + 'vpc_peering_connection_id': 'vpc_peering_connection_id', + } + for k in key_attr_map.iterkeys(): + if k in route_spec: + if route_spec[k] != getattr(route, k): + return False + return True + + +def rename_key(d, old_key, new_key): + d[new_key] = d[old_key] + del d[old_key] + + +def index_of_matching_route(route_spec, routes_to_match): + for i, route in enumerate(routes_to_match): + if route_spec_matches_route(route_spec, route): + return i + + +def ensure_routes(vpc_conn, route_table, route_specs, propagating_vgw_ids, + check_mode): + routes_to_match = list(route_table.routes) + route_specs_to_create = [] + for route_spec in route_specs: + i = index_of_matching_route(route_spec, routes_to_match) + if i is None: + route_specs_to_create.append(route_spec) + else: + del routes_to_match[i] + + # NOTE: As of boto==2.38.0, the origin of a route is not available + # (for example, whether it came from a gateway with route propagation + # enabled). Testing for origin == 'EnableVgwRoutePropagation' is more + # correct than checking whether the route uses a propagating VGW. + # The current logic will leave non-propagated routes using propagating + # VGWs in place. + routes_to_delete = [r for r in routes_to_match + if r.gateway_id != 'local' + and r.gateway_id not in propagating_vgw_ids] + + changed = routes_to_delete or route_specs_to_create + if changed: + for route_spec in route_specs_to_create: + vpc_conn.create_route(route_table.id, + dry_run=check_mode, + **route_spec) + + for route in routes_to_delete: + vpc_conn.delete_route(route_table.id, + route.destination_cidr_block, + dry_run=check_mode) + return {'changed': changed} + + +def ensure_subnet_association(vpc_conn, vpc_id, route_table_id, subnet_id, + check_mode): + route_tables = vpc_conn.get_all_route_tables( + filters={'association.subnet_id': subnet_id, 'vpc_id': vpc_id} + ) + for route_table in route_tables: + if route_table.id is None: + continue + for a in route_table.associations: + if a.subnet_id == subnet_id: + if route_table.id == route_table_id: + return {'changed': False, 'association_id': a.id} + else: + if check_mode: + return {'changed': True} + vpc_conn.disassociate_route_table(a.id) + + association_id = vpc_conn.associate_route_table(route_table_id, subnet_id) + return {'changed': True, 'association_id': association_id} + + +def ensure_subnet_associations(vpc_conn, vpc_id, route_table, subnets, + check_mode): + current_association_ids = [a.id for a in route_table.associations] + new_association_ids = [] + changed = False + for subnet in subnets: + result = ensure_subnet_association( + vpc_conn, vpc_id, route_table.id, subnet.id, check_mode) + changed = changed or result['changed'] + if changed and check_mode: + return {'changed': True} + new_association_ids.append(result['association_id']) + + to_delete = [a_id for a_id in current_association_ids + if a_id not in new_association_ids] + + for a_id in to_delete: + changed = True + vpc_conn.disassociate_route_table(a_id, dry_run=check_mode) + + return {'changed': changed} + + +def ensure_propagation(vpc_conn, route_table, propagating_vgw_ids, + check_mode): + + # NOTE: As of boto==2.38.0, it is not yet possible to query the existing + # propagating gateways. However, EC2 does support this as shown in its API + # documentation. For now, a reasonable proxy for this is the presence of + # propagated routes using the gateway in the route table. If such a route + # is found, propagation is almost certainly enabled. + changed = False + for vgw_id in propagating_vgw_ids: + for r in list(route_table.routes): + if r.gateway_id == vgw_id: + return {'changed': False} + + changed = True + vpc_conn.enable_vgw_route_propagation(route_table.id, + vgw_id, + dry_run=check_mode) + + return {'changed': changed} + + +def ensure_route_table_absent(connection, module): + + lookup = module.params.get('lookup') + route_table_id = module.params.get('route_table_id') + tags = module.params.get('tags') + vpc_id = module.params.get('vpc_id') + check_mode = module.params.get('check_mode') + + if lookup == 'tag': + if tags is not None: + try: + route_table = get_route_table_by_tags(connection, vpc_id, tags) + except EC2ResponseError as e: + module.fail_json(msg=e.message) + except RuntimeError as e: + module.fail_json(msg=e.args[0]) + else: + route_table = None + elif lookup == 'id': + try: + route_table = get_route_table_by_id(connection, vpc_id, route_table_id) + except EC2ResponseError as e: + module.fail_json(msg=e.message) + + if route_table is None: + return {'changed': False} + + try: + connection.delete_route_table(route_table.id, dry_run=check_mode) + except EC2ResponseError as e: + module.fail_json(msg=e.message) + + return {'changed': True} + + +def get_route_table_info(route_table): + + # Add any routes to array + routes = [] + for route in route_table.routes: + routes.append(route.__dict__) + + route_table_info = { 'id': route_table.id, + 'routes': routes, + 'tags': route_table.tags, + 'vpc_id': route_table.vpc_id + } + + return route_table_info + +def create_route_spec(connection, routes, vpc_id): + + for route_spec in routes: + rename_key(route_spec, 'dest', 'destination_cidr_block') + + if 'gateway_id' in route_spec and route_spec['gateway_id'] and \ + route_spec['gateway_id'].lower() == 'igw': + igw = find_igw(connection, vpc_id) + route_spec['gateway_id'] = igw + + return routes + +def ensure_route_table_present(connection, module): + + lookup = module.params.get('lookup') + propagating_vgw_ids = module.params.get('propagating_vgw_ids', []) + route_table_id = module.params.get('route_table_id') + subnets = module.params.get('subnets') + tags = module.params.get('tags') + vpc_id = module.params.get('vpc_id') + check_mode = module.params.get('check_mode') + try: + routes = create_route_spec(connection, module.params.get('routes'), vpc_id) + except AnsibleIgwSearchException as e: + module.fail_json(msg=e[0]) + + changed = False + tags_valid = False + + if lookup == 'tag': + if tags is not None: + try: + route_table = get_route_table_by_tags(connection, vpc_id, tags) + except EC2ResponseError as e: + module.fail_json(msg=e.message) + except RuntimeError as e: + module.fail_json(msg=e.args[0]) + else: + route_table = None + elif lookup == 'id': + try: + route_table = get_route_table_by_id(connection, vpc_id, route_table_id) + except EC2ResponseError as e: + module.fail_json(msg=e.message) + + # If no route table returned then create new route table + if route_table is None: + try: + route_table = connection.create_route_table(vpc_id, check_mode) + changed = True + except EC2ResponseError, e: + module.fail_json(msg=e.message) + + if routes is not None: + try: + result = ensure_routes(connection, route_table, routes, propagating_vgw_ids, check_mode) + changed = changed or result['changed'] + except EC2ResponseError as e: + module.fail_json(msg=e.message) + + if propagating_vgw_ids is not None: + result = ensure_propagation(connection, route_table, + propagating_vgw_ids, + check_mode=check_mode) + changed = changed or result['changed'] + + if not tags_valid and tags is not None: + result = ensure_tags(connection, route_table.id, tags, + add_only=True, check_mode=check_mode) + changed = changed or result['changed'] + + if subnets: + associated_subnets = [] + try: + associated_subnets = find_subnets(connection, vpc_id, subnets) + except EC2ResponseError as e: + raise AnsibleRouteTableException( + 'Unable to find subnets for route table {0}, error: {1}' + .format(route_table, e) + ) + + try: + result = ensure_subnet_associations(connection, vpc_id, route_table, associated_subnets, check_mode) + changed = changed or result['changed'] + except EC2ResponseError as e: + raise AnsibleRouteTableException( + 'Unable to associate subnets for route table {0}, error: {1}' + .format(route_table, e) + ) + + module.exit_json(changed=changed, route_table=get_route_table_info(route_table)) + + +def main(): + argument_spec = ec2_argument_spec() + argument_spec.update( + dict( + lookup = dict(default='tag', required=False, choices=['tag', 'id']), + propagating_vgw_ids = dict(default=None, required=False, type='list'), + route_table_id = dict(default=None, required=False), + routes = dict(default=None, required=False, type='list'), + state = dict(default='present', choices=['present', 'absent']), + subnets = dict(default=None, required=False, type='list'), + tags = dict(default=None, required=False, type='dict', aliases=['resource_tags']), + vpc_id = dict(default=None, required=True) + ) + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_BOTO: + module.fail_json(msg='boto is required for this module') + + 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") + + lookup = module.params.get('lookup') + route_table_id = module.params.get('route_table_id') + state = module.params.get('state', 'present') + + if lookup == 'id' and route_table_id is None: + module.fail_json("You must specify route_table_id if lookup is set to id") + + try: + if state == 'present': + result = ensure_route_table_present(connection, module) + elif state == 'absent': + result = ensure_route_table_absent(connection, module) + except AnsibleRouteTableException 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() + diff --git a/cloud/cloudstack/cs_domain.py b/cloud/cloudstack/cs_domain.py index c9f345a00c2..27410040aec 100644 --- a/cloud/cloudstack/cs_domain.py +++ b/cloud/cloudstack/cs_domain.py @@ -37,7 +37,7 @@ options: - Network domain for networks in the domain. required: false default: null - cleanup: + clean_up: description: - Clean up all domain resources like child domains and accounts. - Considered on C(state=absent). @@ -225,7 +225,7 @@ class AnsibleCloudStackDomain(AnsibleCloudStack): if not self.module.check_mode: args = {} args['id'] = domain['id'] - args['cleanup'] = self.module.params.get('cleanup') + args['cleanup'] = self.module.params.get('clean_up') res = self.cs.deleteDomain(**args) if 'errortext' in res: @@ -244,7 +244,7 @@ def main(): path = dict(required=True), state = dict(choices=['present', 'absent'], default='present'), network_domain = dict(default=None), - cleanup = dict(choices=BOOLEANS, default=False), + clean_up = dict(choices=BOOLEANS, default=False), poll_async = dict(choices=BOOLEANS, default=True), api_key = dict(default=None), api_secret = dict(default=None, no_log=True), diff --git a/cloud/cloudstack/cs_instance.py b/cloud/cloudstack/cs_instance.py index 201449b870d..6f1339123d8 100644 --- a/cloud/cloudstack/cs_instance.py +++ b/cloud/cloudstack/cs_instance.py @@ -548,7 +548,7 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): return user_data - def deploy_instance(self): + def deploy_instance(self, start_vm=True): self.result['changed'] = True networkids = self.get_network_ids() if networkids is not None: @@ -573,6 +573,7 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): args['group'] = self.module.params.get('group') args['keypair'] = self.module.params.get('ssh_key') args['size'] = self.module.params.get('disk_size') + args['startvm'] = start_vm args['rootdisksize'] = self.module.params.get('root_disk_size') args['securitygroupnames'] = ','.join(self.module.params.get('security_groups')) args['affinitygroupnames'] = ','.join(self.module.params.get('affinity_groups')) @@ -700,10 +701,12 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): def stop_instance(self): instance = self.get_instance() + if not instance: - self.module.fail_json(msg="Instance named '%s' not found" % self.module.params.get('name')) + instance = self.deploy_instance(start_vm=False) + return instance - if instance['state'].lower() in ['stopping', 'stopped']: + elif instance['state'].lower() in ['stopping', 'stopped']: return instance if instance['state'].lower() in ['starting', 'running']: @@ -722,10 +725,12 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): def start_instance(self): instance = self.get_instance() + if not instance: - self.module.fail_json(msg="Instance named '%s' not found" % module.params.get('name')) + instance = self.deploy_instance() + return instance - if instance['state'].lower() in ['starting', 'running']: + elif instance['state'].lower() in ['starting', 'running']: return instance if instance['state'].lower() in ['stopped', 'stopping']: @@ -744,10 +749,12 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): def restart_instance(self): instance = self.get_instance() + if not instance: - module.fail_json(msg="Instance named '%s' not found" % self.module.params.get('name')) + instance = self.deploy_instance() + return instance - if instance['state'].lower() in [ 'running', 'starting' ]: + elif instance['state'].lower() in [ 'running', 'starting' ]: self.result['changed'] = True if not self.module.check_mode: instance = self.cs.rebootVirtualMachine(id=instance['id']) @@ -779,7 +786,7 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): self.result['affinity_groups'] = affinity_groups if 'nic' in instance: for nic in instance['nic']: - if nic['isdefault']: + if nic['isdefault'] and 'ipaddress' in nic: self.result['default_ip'] = nic['ipaddress'] return self.result diff --git a/cloud/cloudstack/cs_portforward.py b/cloud/cloudstack/cs_portforward.py index 2fc14aa5ed3..f2f87b660ef 100644 --- a/cloud/cloudstack/cs_portforward.py +++ b/cloud/cloudstack/cs_portforward.py @@ -361,9 +361,9 @@ class AnsibleCloudStackPortforwarding(AnsibleCloudStack): super(AnsibleCloudStackPortforwarding, self).get_result(portforwarding_rule) if portforwarding_rule: # Bad bad API does not always return int when it should. - for search_key, return_key in returns_to_int.iteritems(): - if search_key in resource: - self.result[return_key] = int(resource[search_key]) + for search_key, return_key in self.returns_to_int.iteritems(): + if search_key in portforwarding_rule: + self.result[return_key] = int(portforwarding_rule[search_key]) return self.result diff --git a/cloud/cloudstack/cs_staticnat.py b/cloud/cloudstack/cs_staticnat.py index 500c533915b..4b73d86e32b 100644 --- a/cloud/cloudstack/cs_staticnat.py +++ b/cloud/cloudstack/cs_staticnat.py @@ -154,7 +154,7 @@ from ansible.module_utils.cloudstack import * class AnsibleCloudStackStaticNat(AnsibleCloudStack): def __init__(self, module): - super(AnsibleCloudStackPortforwarding, self).__init__(module) + super(AnsibleCloudStackStaticNat, self).__init__(module) self.returns = { 'virtualmachinedisplayname': 'vm_display_name', 'virtualmachinename': 'vm_name', diff --git a/cloud/cloudstack/cs_template.py b/cloud/cloudstack/cs_template.py index d451ece7138..c6c482f9c0f 100644 --- a/cloud/cloudstack/cs_template.py +++ b/cloud/cloudstack/cs_template.py @@ -86,6 +86,12 @@ options: - Only used if C(state) is present. required: false default: false + cross_zones: + description: + - Whether the template should be syned across zones. + - Only used if C(state) is present. + required: false + default: false project: description: - Name of the project the template to be registered in. @@ -185,9 +191,8 @@ EXAMPLES = ''' url: "http://packages.shapeblue.com/systemvmtemplate/4.5/systemvm64template-4.5-vmware.ova" hypervisor: VMware format: OVA - zone: tokio-ix + cross_zones: yes os_type: Debian GNU/Linux 7(64-bit) - is_routing: yes # Create a template from a stopped virtual machine's volume - local_action: @@ -456,11 +461,15 @@ class AnsibleCloudStackTemplate(AnsibleCloudStack): args['isrouting'] = self.module.params.get('is_routing') args['sshkeyenabled'] = self.module.params.get('sshkey_enabled') args['hypervisor'] = self.get_hypervisor() - args['zoneid'] = self.get_zone(key='id') args['domainid'] = self.get_domain(key='id') args['account'] = self.get_account(key='name') args['projectid'] = self.get_project(key='id') + if not self.module.params.get('cross_zones'): + args['zoneid'] = self.get_zone(key='id') + else: + args['zoneid'] = -1 + if not self.module.check_mode: res = self.cs.registerTemplate(**args) if 'errortext' in res: @@ -473,11 +482,13 @@ class AnsibleCloudStackTemplate(AnsibleCloudStack): args = {} args['isready'] = self.module.params.get('is_ready') args['templatefilter'] = self.module.params.get('template_filter') - args['zoneid'] = self.get_zone(key='id') args['domainid'] = self.get_domain(key='id') args['account'] = self.get_account(key='name') args['projectid'] = self.get_project(key='id') + if not self.module.params.get('cross_zones'): + args['zoneid'] = self.get_zone(key='id') + # if checksum is set, we only look on that. checksum = self.module.params.get('checksum') if not checksum: @@ -543,6 +554,7 @@ def main(): details = dict(default=None), bits = dict(type='int', choices=[ 32, 64 ], default=64), state = dict(choices=['present', 'absent'], default='present'), + cross_zones = dict(type='bool', choices=BOOLEANS, default=False), zone = dict(default=None), domain = dict(default=None), account = dict(default=None), diff --git a/cloud/lxc/lxc_container.py b/cloud/lxc/lxc_container.py index 1f82bcb829e..adb9637acf9 100644 --- a/cloud/lxc/lxc_container.py +++ b/cloud/lxc/lxc_container.py @@ -448,7 +448,7 @@ LXC_BACKING_STORE = { 'zfs_root' ], 'btrfs': [ - 'lv_name', 'vg_name', 'thinpool', 'zfs_root' + 'lv_name', 'vg_name', 'thinpool', 'zfs_root', 'fs_type', 'fs_size' ], 'loop': [ 'lv_name', 'vg_name', 'thinpool', 'zfs_root' diff --git a/cloud/vmware/vmware_dvs_portgroup.py b/cloud/vmware/vmware_dvs_portgroup.py new file mode 100644 index 00000000000..265f9fd71ef --- /dev/null +++ b/cloud/vmware/vmware_dvs_portgroup.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Joseph Callen +# +# 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: vmware_dvs_portgroup +short_description: Create or remove a Distributed vSwitch portgroup +description: + - Create or remove a Distributed vSwitch portgroup +version_added: 2.0 +author: "Joseph Callen (@jcpowermac)" +notes: + - Tested on vSphere 5.5 +requirements: + - "python >= 2.6" + - PyVmomi +options: + hostname: + description: + - The hostname or IP address of the vSphere vCenter API server + required: True + username: + description: + - The username of the vSphere vCenter + required: True + aliases: ['user', 'admin'] + password: + description: + - The password of the vSphere vCenter + required: True + aliases: ['pass', 'pwd'] + portgroup_name: + description: + - The name of the portgroup that is to be created or deleted + required: True + switch_name: + description: + - The name of the distributed vSwitch the port group should be created on. + required: True + vlan_id: + description: + - The VLAN ID that should be configured with the portgroup + required: True + num_ports: + description: + - The number of ports the portgroup should contain + required: True + portgroup_type: + description: + - See VMware KB 1022312 regarding portgroup types + required: True + choices: + - 'earlyBinding' + - 'lateBinding' + - 'ephemeral' +''' + +EXAMPLES = ''' + - name: Create Management portgroup + local_action: + module: vmware_dvs_portgroup + hostname: vcenter_ip_or_hostname + username: vcenter_username + password: vcenter_password + portgroup_name: Management + switch_name: dvSwitch + vlan_id: 123 + num_ports: 120 + portgroup_type: earlyBinding + state: present +''' + +try: + from pyVmomi import vim, vmodl + HAS_PYVMOMI = True +except ImportError: + HAS_PYVMOMI = False + + +def create_port_group(dv_switch, portgroup_name, vlan_id, num_ports, portgroup_type): + config = vim.dvs.DistributedVirtualPortgroup.ConfigSpec() + + config.name = portgroup_name + config.numPorts = num_ports + + # vim.VMwareDVSPortSetting() does not exist in the pyvmomi documentation + # but this is the correct managed object type. + + config.defaultPortConfig = vim.VMwareDVSPortSetting() + + # vim.VmwareDistributedVirtualSwitchVlanIdSpec() does not exist in the + # pyvmomi documentation but this is the correct managed object type + config.defaultPortConfig.vlan = vim.VmwareDistributedVirtualSwitchVlanIdSpec() + config.defaultPortConfig.vlan.inherited = False + config.defaultPortConfig.vlan.vlanId = vlan_id + config.type = portgroup_type + + spec = [config] + task = dv_switch.AddDVPortgroup_Task(spec) + changed, result = wait_for_task(task) + return changed, result + + +def state_destroy_dvspg(module): + dvs_portgroup = module.params['dvs_portgroup'] + changed = True + result = None + + if not module.check_mode: + task = dvs_portgroup.Destroy_Task() + changed, result = wait_for_task(task) + module.exit_json(changed=changed, result=str(result)) + + +def state_exit_unchanged(module): + module.exit_json(changed=False) + + +def state_update_dvspg(module): + module.exit_json(changed=False, msg="Currently not implemented.") + return + + +def state_create_dvspg(module): + + switch_name = module.params['switch_name'] + portgroup_name = module.params['portgroup_name'] + dv_switch = module.params['dv_switch'] + vlan_id = module.params['vlan_id'] + num_ports = module.params['num_ports'] + portgroup_type = module.params['portgroup_type'] + changed = True + result = None + + if not module.check_mode: + changed, result = create_port_group(dv_switch, portgroup_name, vlan_id, num_ports, portgroup_type) + module.exit_json(changed=changed, result=str(result)) + + +def check_dvspg_state(module): + + switch_name = module.params['switch_name'] + portgroup_name = module.params['portgroup_name'] + + content = connect_to_api(module) + module.params['content'] = content + + dv_switch = find_dvs_by_name(content, switch_name) + + if dv_switch is None: + raise Exception("A distributed virtual switch with name %s does not exist" % switch_name) + + module.params['dv_switch'] = dv_switch + dvs_portgroup = find_dvspg_by_name(dv_switch, portgroup_name) + + if dvs_portgroup is None: + return 'absent' + else: + module.params['dvs_portgroup'] = dvs_portgroup + return 'present' + + +def main(): + + argument_spec = vmware_argument_spec() + argument_spec.update(dict(portgroup_name=dict(required=True, type='str'), + switch_name=dict(required=True, type='str'), + vlan_id=dict(required=True, type='int'), + num_ports=dict(required=True, type='int'), + portgroup_type=dict(required=True, choices=['earlyBinding', 'lateBinding', 'ephemeral'], type='str'), + state=dict(default='present', choices=['present', 'absent'], type='str'))) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi is required for this module') + + try: + dvspg_states = { + 'absent': { + 'present': state_destroy_dvspg, + 'absent': state_exit_unchanged, + }, + 'present': { + 'update': state_update_dvspg, + 'present': state_exit_unchanged, + 'absent': state_create_dvspg, + } + } + dvspg_states[module.params['state']][check_dvspg_state(module)](module) + except vmodl.RuntimeFault as runtime_fault: + module.fail_json(msg=runtime_fault.msg) + except vmodl.MethodFault as method_fault: + module.fail_json(msg=method_fault.msg) + except Exception as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.vmware import * +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() diff --git a/cloud/vmware/vmware_dvswitch.py b/cloud/vmware/vmware_dvswitch.py new file mode 100644 index 00000000000..26212a06c5f --- /dev/null +++ b/cloud/vmware/vmware_dvswitch.py @@ -0,0 +1,225 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Joseph Callen +# +# 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: vmware_dvswitch +short_description: Create or remove a distributed vSwitch +description: + - Create or remove a distributed vSwitch +version_added: 2.0 +author: "Joseph Callen (@jcpowermac)" +notes: + - Tested on vSphere 5.5 +requirements: + - "python >= 2.6" + - PyVmomi +options: + hostname: + description: + - The hostname or IP address of the vSphere vCenter API server + required: True + username: + description: + - The username of the vSphere vCenter + required: True + aliases: ['user', 'admin'] + password: + description: + - The password of the vSphere vCenter + required: True + aliases: ['pass', 'pwd'] + datacenter_name: + description: + - The name of the datacenter that will contain the dvSwitch + required: True + switch_name: + description: + - The name of the switch to create or remove + required: True + mtu: + description: + - The switch maximum transmission unit + required: True + uplink_quantity: + description: + - Quantity of uplink per ESXi host added to the switch + required: True + discovery_proto: + description: + - Link discovery protocol between Cisco and Link Layer discovery + choices: + - 'cdp' + - 'lldp' + required: True + discovery_operation: + description: + - Select the discovery operation + choices: + - 'both' + - 'none' + - 'advertise' + - 'listen' + state: + description: + - Create or remove dvSwitch + default: 'present' + choices: + - 'present' + - 'absent' + required: False +''' +EXAMPLES = ''' +- name: Create dvswitch + local_action: + module: vmware_dvswitch + hostname: vcenter_ip_or_hostname + username: vcenter_username + password: vcenter_password + datacenter_name: datacenter + switch_name: dvSwitch + mtu: 9000 + uplink_quantity: 2 + discovery_proto: lldp + discovery_operation: both + state: present +''' + +try: + from pyVmomi import vim, vmodl + HAS_PYVMOMI = True +except ImportError: + HAS_PYVMOMI = False + + +def create_dvswitch(network_folder, switch_name, mtu, uplink_quantity, discovery_proto, discovery_operation): + + result = None + changed = False + + spec = vim.DistributedVirtualSwitch.CreateSpec() + spec.configSpec = vim.dvs.VmwareDistributedVirtualSwitch.ConfigSpec() + spec.configSpec.uplinkPortPolicy = vim.DistributedVirtualSwitch.NameArrayUplinkPortPolicy() + spec.configSpec.linkDiscoveryProtocolConfig = vim.host.LinkDiscoveryProtocolConfig() + + spec.configSpec.name = switch_name + spec.configSpec.maxMtu = mtu + spec.configSpec.linkDiscoveryProtocolConfig.protocol = discovery_proto + spec.configSpec.linkDiscoveryProtocolConfig.operation = discovery_operation + spec.productInfo = vim.dvs.ProductSpec() + spec.productInfo.name = "DVS" + spec.productInfo.vendor = "VMware" + + for count in range(1, uplink_quantity+1): + spec.configSpec.uplinkPortPolicy.uplinkPortName.append("uplink%d" % count) + + task = network_folder.CreateDVS_Task(spec) + changed, result = wait_for_task(task) + return changed, result + + +def state_exit_unchanged(module): + module.exit_json(changed=False) + + +def state_destroy_dvs(module): + dvs = module.params['dvs'] + task = dvs.Destroy_Task() + changed, result = wait_for_task(task) + module.exit_json(changed=changed, result=str(result)) + + +def state_update_dvs(module): + module.exit_json(changed=False, msg="Currently not implemented.") + + +def state_create_dvs(module): + switch_name = module.params['switch_name'] + datacenter_name = module.params['datacenter_name'] + content = module.params['content'] + mtu = module.params['mtu'] + uplink_quantity = module.params['uplink_quantity'] + discovery_proto = module.params['discovery_proto'] + discovery_operation = module.params['discovery_operation'] + + changed = True + result = None + + if not module.check_mode: + dc = find_datacenter_by_name(content, datacenter_name) + changed, result = create_dvswitch(dc.networkFolder, switch_name, + mtu, uplink_quantity, discovery_proto, + discovery_operation) + module.exit_json(changed=changed, result=str(result)) + + +def check_dvs_configuration(module): + switch_name = module.params['switch_name'] + content = connect_to_api(module) + module.params['content'] = content + dvs = find_dvs_by_name(content, switch_name) + if dvs is None: + return 'absent' + else: + module.params['dvs'] = dvs + return 'present' + + +def main(): + argument_spec = vmware_argument_spec() + argument_spec.update(dict(datacenter_name=dict(required=True, type='str'), + switch_name=dict(required=True, type='str'), + mtu=dict(required=True, type='int'), + uplink_quantity=dict(required=True, type='int'), + discovery_proto=dict(required=True, choices=['cdp', 'lldp'], type='str'), + discovery_operation=dict(required=True, choices=['both', 'none', 'advertise', 'listen'], type='str'), + state=dict(default='present', choices=['present', 'absent'], type='str'))) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi is required for this module') + + try: + # Currently state_update_dvs is not implemented. + dvs_states = { + 'absent': { + 'present': state_destroy_dvs, + 'absent': state_exit_unchanged, + }, + 'present': { + 'update': state_update_dvs, + 'present': state_exit_unchanged, + 'absent': state_create_dvs, + } + } + dvs_states[module.params['state']][check_dvs_configuration(module)](module) + except vmodl.RuntimeFault as runtime_fault: + module.fail_json(msg=runtime_fault.msg) + except vmodl.MethodFault as method_fault: + module.fail_json(msg=method_fault.msg) + except Exception as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.vmware import * +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() diff --git a/cloud/vmware/vmware_host.py b/cloud/vmware/vmware_host.py new file mode 100644 index 00000000000..162397a2190 --- /dev/null +++ b/cloud/vmware/vmware_host.py @@ -0,0 +1,241 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Joseph Callen +# +# 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: vmware_host +short_description: Add/remove ESXi host to/from vCenter +description: + - This module can be used to add/remove an ESXi host to/from vCenter +version_added: 2.0 +author: "Joseph Callen (@jcpowermac), Russell Teague (@mtnbikenc)" +notes: + - Tested on vSphere 5.5 +requirements: + - "python >= 2.6" + - PyVmomi +options: + hostname: + description: + - The hostname or IP address of the vSphere vCenter API server + required: True + username: + description: + - The username of the vSphere vCenter + required: True + aliases: ['user', 'admin'] + password: + description: + - The password of the vSphere vCenter + required: True + aliases: ['pass', 'pwd'] + datacenter_name: + description: + - Name of the datacenter to add the host + required: True + cluster_name: + description: + - Name of the cluster to add the host + required: True + esxi_hostname: + description: + - ESXi hostname to manage + required: True + esxi_username: + description: + - ESXi username + required: True + esxi_password: + description: + - ESXi password + required: True + state: + description: + - Add or remove the host + default: 'present' + choices: + - 'present' + - 'absent' + required: False +''' + +EXAMPLES = ''' +Example from Ansible playbook + + - name: Add ESXi Host to VCSA + local_action: + module: vmware_host + hostname: vcsa_host + username: vcsa_user + password: vcsa_pass + datacenter_name: datacenter_name + cluster_name: cluster_name + esxi_hostname: esxi_hostname + esxi_username: esxi_username + esxi_password: esxi_password + state: present +''' + +try: + from pyVmomi import vim, vmodl + HAS_PYVMOMI = True +except ImportError: + HAS_PYVMOMI = False + + +def find_host_by_cluster_datacenter(module): + datacenter_name = module.params['datacenter_name'] + cluster_name = module.params['cluster_name'] + content = module.params['content'] + esxi_hostname = module.params['esxi_hostname'] + + dc = find_datacenter_by_name(content, datacenter_name) + cluster = find_cluster_by_name_datacenter(dc, cluster_name) + + for host in cluster.host: + if host.name == esxi_hostname: + return host, cluster + + return None, cluster + + +def add_host_to_vcenter(module): + cluster = module.params['cluster'] + + host_connect_spec = vim.host.ConnectSpec() + host_connect_spec.hostName = module.params['esxi_hostname'] + host_connect_spec.userName = module.params['esxi_username'] + host_connect_spec.password = module.params['esxi_password'] + host_connect_spec.force = True + host_connect_spec.sslThumbprint = "" + as_connected = True + esxi_license = None + resource_pool = None + + try: + task = cluster.AddHost_Task(host_connect_spec, as_connected, resource_pool, esxi_license) + success, result = wait_for_task(task) + return success, result + except TaskError as add_task_error: + # This is almost certain to fail the first time. + # In order to get the sslThumbprint we first connect + # get the vim.fault.SSLVerifyFault then grab the sslThumbprint + # from that object. + # + # args is a tuple, selecting the first tuple + ssl_verify_fault = add_task_error.args[0] + host_connect_spec.sslThumbprint = ssl_verify_fault.thumbprint + + task = cluster.AddHost_Task(host_connect_spec, as_connected, resource_pool, esxi_license) + success, result = wait_for_task(task) + return success, result + + +def state_exit_unchanged(module): + module.exit_json(changed=False) + + +def state_remove_host(module): + host = module.params['host'] + changed = True + result = None + if not module.check_mode: + if not host.runtime.inMaintenanceMode: + maintenance_mode_task = host.EnterMaintenanceMode_Task(300, True, None) + changed, result = wait_for_task(maintenance_mode_task) + + if changed: + task = host.Destroy_Task() + changed, result = wait_for_task(task) + else: + raise Exception(result) + module.exit_json(changed=changed, result=str(result)) + + +def state_update_host(module): + module.exit_json(changed=False, msg="Currently not implemented.") + + +def state_add_host(module): + + changed = True + result = None + + if not module.check_mode: + changed, result = add_host_to_vcenter(module) + module.exit_json(changed=changed, result=str(result)) + + +def check_host_state(module): + + content = connect_to_api(module) + module.params['content'] = content + + host, cluster = find_host_by_cluster_datacenter(module) + + module.params['cluster'] = cluster + if host is None: + return 'absent' + else: + module.params['host'] = host + return 'present' + + +def main(): + argument_spec = vmware_argument_spec() + argument_spec.update(dict(datacenter_name=dict(required=True, type='str'), + cluster_name=dict(required=True, type='str'), + esxi_hostname=dict(required=True, type='str'), + esxi_username=dict(required=True, type='str'), + esxi_password=dict(required=True, type='str', no_log=True), + state=dict(default='present', choices=['present', 'absent'], type='str'))) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi is required for this module') + + try: + # Currently state_update_dvs is not implemented. + host_states = { + 'absent': { + 'present': state_remove_host, + 'absent': state_exit_unchanged, + }, + 'present': { + 'present': state_exit_unchanged, + 'absent': state_add_host, + } + } + + host_states[module.params['state']][check_host_state(module)](module) + + except vmodl.RuntimeFault as runtime_fault: + module.fail_json(msg=runtime_fault.msg) + except vmodl.MethodFault as method_fault: + module.fail_json(msg=method_fault.msg) + except Exception as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.vmware import * +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() diff --git a/cloud/vmware/vmware_migrate_vmk.py b/cloud/vmware/vmware_migrate_vmk.py new file mode 100644 index 00000000000..c658c71b682 --- /dev/null +++ b/cloud/vmware/vmware_migrate_vmk.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Joseph Callen +# +# 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: vmware_migrate_vmk +short_description: Migrate a VMK interface from VSS to VDS +description: + - Migrate a VMK interface from VSS to VDS +version_added: 2.0 +author: "Joseph Callen (@jcpowermac), Russell Teague (@mtnbikenc)" +notes: + - Tested on vSphere 5.5 +requirements: + - "python >= 2.6" + - PyVmomi +options: + hostname: + description: + - The hostname or IP address of the vSphere vCenter API server + required: True + username: + description: + - The username of the vSphere vCenter + required: True + aliases: ['user', 'admin'] + password: + description: + - The password of the vSphere vCenter + required: True + aliases: ['pass', 'pwd'] + esxi_hostname: + description: + - ESXi hostname to be managed + required: True + device: + description: + - VMK interface name + required: True + current_switch_name: + description: + - Switch VMK interface is currently on + required: True + current_portgroup_name: + description: + - Portgroup name VMK interface is currently on + required: True + migrate_switch_name: + description: + - Switch name to migrate VMK interface to + required: True + migrate_portgroup_name: + description: + - Portgroup name to migrate VMK interface to + required: True +''' + +EXAMPLES = ''' +Example from Ansible playbook + + - name: Migrate Management vmk + local_action: + module: vmware_migrate_vmk + hostname: vcsa_host + username: vcsa_user + password: vcsa_pass + esxi_hostname: esxi_hostname + device: vmk1 + current_switch_name: temp_vswitch + current_portgroup_name: esx-mgmt + migrate_switch_name: dvSwitch + migrate_portgroup_name: Management +''' + + +try: + from pyVmomi import vim, vmodl + HAS_PYVMOMI = True +except ImportError: + HAS_PYVMOMI = False + + +def state_exit_unchanged(module): + module.exit_json(changed=False) + + +def state_migrate_vds_vss(module): + module.exit_json(changed=False, msg="Currently Not Implemented") + + +def create_host_vnic_config(dv_switch_uuid, portgroup_key, device): + + host_vnic_config = vim.host.VirtualNic.Config() + host_vnic_config.spec = vim.host.VirtualNic.Specification() + host_vnic_config.changeOperation = "edit" + host_vnic_config.device = device + host_vnic_config.portgroup = "" + host_vnic_config.spec.distributedVirtualPort = vim.dvs.PortConnection() + host_vnic_config.spec.distributedVirtualPort.switchUuid = dv_switch_uuid + host_vnic_config.spec.distributedVirtualPort.portgroupKey = portgroup_key + + return host_vnic_config + + +def create_port_group_config(switch_name, portgroup_name): + port_group_config = vim.host.PortGroup.Config() + port_group_config.spec = vim.host.PortGroup.Specification() + + port_group_config.changeOperation = "remove" + port_group_config.spec.name = portgroup_name + port_group_config.spec.vlanId = -1 + port_group_config.spec.vswitchName = switch_name + port_group_config.spec.policy = vim.host.NetworkPolicy() + + return port_group_config + + +def state_migrate_vss_vds(module): + content = module.params['content'] + host_system = module.params['host_system'] + migrate_switch_name = module.params['migrate_switch_name'] + migrate_portgroup_name = module.params['migrate_portgroup_name'] + current_portgroup_name = module.params['current_portgroup_name'] + current_switch_name = module.params['current_switch_name'] + device = module.params['device'] + + host_network_system = host_system.configManager.networkSystem + + dv_switch = find_dvs_by_name(content, migrate_switch_name) + pg = find_dvspg_by_name(dv_switch, migrate_portgroup_name) + + config = vim.host.NetworkConfig() + config.portgroup = [create_port_group_config(current_switch_name, current_portgroup_name)] + config.vnic = [create_host_vnic_config(dv_switch.uuid, pg.key, device)] + host_network_system.UpdateNetworkConfig(config, "modify") + module.exit_json(changed=True) + + +def check_vmk_current_state(module): + + device = module.params['device'] + esxi_hostname = module.params['esxi_hostname'] + current_portgroup_name = module.params['current_portgroup_name'] + current_switch_name = module.params['current_switch_name'] + + content = connect_to_api(module) + + host_system = find_hostsystem_by_name(content, esxi_hostname) + + module.params['content'] = content + module.params['host_system'] = host_system + + for vnic in host_system.configManager.networkSystem.networkInfo.vnic: + if vnic.device == device: + module.params['vnic'] = vnic + if vnic.spec.distributedVirtualPort is None: + if vnic.portgroup == current_portgroup_name: + return "migrate_vss_vds" + else: + dvs = find_dvs_by_name(content, current_switch_name) + if dvs is None: + return "migrated" + if vnic.spec.distributedVirtualPort.switchUuid == dvs.uuid: + return "migrate_vds_vss" + + +def main(): + + argument_spec = vmware_argument_spec() + argument_spec.update(dict(esxi_hostname=dict(required=True, type='str'), + device=dict(required=True, type='str'), + current_switch_name=dict(required=True, type='str'), + current_portgroup_name=dict(required=True, type='str'), + migrate_switch_name=dict(required=True, type='str'), + migrate_portgroup_name=dict(required=True, type='str'))) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi required for this module') + + try: + vmk_migration_states = { + 'migrate_vss_vds': state_migrate_vss_vds, + 'migrate_vds_vss': state_migrate_vds_vss, + 'migrated': state_exit_unchanged + } + + vmk_migration_states[check_vmk_current_state(module)](module) + + except vmodl.RuntimeFault as runtime_fault: + module.fail_json(msg=runtime_fault.msg) + except vmodl.MethodFault as method_fault: + module.fail_json(msg=method_fault.msg) + except Exception as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.vmware import * +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() diff --git a/cloud/vmware/vmware_target_canonical_facts.py b/cloud/vmware/vmware_target_canonical_facts.py new file mode 100644 index 00000000000..987b4a98753 --- /dev/null +++ b/cloud/vmware/vmware_target_canonical_facts.py @@ -0,0 +1,108 @@ +#!/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Joseph Callen +# +# 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: vmware_target_canonical_facts +short_description: Return canonical (NAA) from an ESXi host +description: + - Return canonical (NAA) from an ESXi host based on SCSI target ID +version_added: 2.0 +author: Joseph Callen +notes: +requirements: + - Tested on vSphere 5.5 + - PyVmomi installed +options: + hostname: + description: + - The hostname or IP address of the vSphere vCenter + required: True + username: + description: + - The username of the vSphere vCenter + required: True + aliases: ['user', 'admin'] + password: + description: + - The password of the vSphere vCenter + required: True + aliases: ['pass', 'pwd'] + target_id: + description: + - The target id based on order of scsi device + required: True +''' + +EXAMPLES = ''' +# Example vmware_target_canonical_facts command from Ansible Playbooks +- name: Get Canonical name + local_action: > + vmware_target_canonical_facts + hostname="{{ ansible_ssh_host }}" username=root password=vmware + target_id=7 +''' + +try: + from pyVmomi import vim, vmodl + HAS_PYVMOMI = True +except ImportError: + HAS_PYVMOMI = False + + +def find_hostsystem(content): + host_system = get_all_objs(content, [vim.HostSystem]) + for host in host_system: + return host + return None + + +def main(): + + argument_spec = vmware_argument_spec() + argument_spec.update(dict(target_id=dict(required=True, type='int'))) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi is required for this module') + + content = connect_to_api(module) + host = find_hostsystem(content) + + target_lun_uuid = {} + scsilun_canonical = {} + + # Associate the scsiLun key with the canonicalName (NAA) + for scsilun in host.config.storageDevice.scsiLun: + scsilun_canonical[scsilun.key] = scsilun.canonicalName + + # Associate target number with LUN uuid + for target in host.config.storageDevice.scsiTopology.adapter[0].target: + for lun in target.lun: + target_lun_uuid[target.target] = lun.scsiLun + + module.exit_json(changed=False, canonical=scsilun_canonical[target_lun_uuid[module.params['target_id']]]) + +from ansible.module_utils.basic import * +from ansible.module_utils.vmware import * + +if __name__ == '__main__': + main() + diff --git a/cloud/vmware/vmware_vm_vss_dvs_migrate.py b/cloud/vmware/vmware_vm_vss_dvs_migrate.py new file mode 100644 index 00000000000..ff51f86ed09 --- /dev/null +++ b/cloud/vmware/vmware_vm_vss_dvs_migrate.py @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Joseph Callen +# +# 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: vmware_vm_vss_dvs_migrate +short_description: Migrates a virtual machine from a standard vswitch to distributed +description: + - Migrates a virtual machine from a standard vswitch to distributed +version_added: 2.0 +author: "Joseph Callen (@jcpowermac)" +notes: + - Tested on vSphere 5.5 +requirements: + - "python >= 2.6" + - PyVmomi +options: + hostname: + description: + - The hostname or IP address of the vSphere vCenter API server + required: True + username: + description: + - The username of the vSphere vCenter + required: True + aliases: ['user', 'admin'] + password: + description: + - The password of the vSphere vCenter + required: True + aliases: ['pass', 'pwd'] + vm_name: + description: + - Name of the virtual machine to migrate to a dvSwitch + required: True + dvportgroup_name: + description: + - Name of the portgroup to migrate to the virtual machine to + required: True +''' + +EXAMPLES = ''' +- name: Migrate VCSA to vDS + local_action: + module: vmware_vm_vss_dvs_migrate + hostname: vcenter_ip_or_hostname + username: vcenter_username + password: vcenter_password + vm_name: virtual_machine_name + dvportgroup_name: distributed_portgroup_name +''' + +try: + from pyVmomi import vim, vmodl + HAS_PYVMOMI = True +except ImportError: + HAS_PYVMOMI = False + + +def _find_dvspg_by_name(content, pg_name): + + vmware_distributed_port_group = get_all_objs(content, [vim.dvs.DistributedVirtualPortgroup]) + for dvspg in vmware_distributed_port_group: + if dvspg.name == pg_name: + return dvspg + return None + + +def find_vm_by_name(content, vm_name): + + virtual_machines = get_all_objs(content, [vim.VirtualMachine]) + for vm in virtual_machines: + if vm.name == vm_name: + return vm + return None + + +def migrate_network_adapter_vds(module): + vm_name = module.params['vm_name'] + dvportgroup_name = module.params['dvportgroup_name'] + content = module.params['content'] + + vm_configspec = vim.vm.ConfigSpec() + nic = vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo() + port = vim.dvs.PortConnection() + devicespec = vim.vm.device.VirtualDeviceSpec() + + pg = _find_dvspg_by_name(content, dvportgroup_name) + + if pg is None: + module.fail_json(msg="The standard portgroup was not found") + + vm = find_vm_by_name(content, vm_name) + if vm is None: + module.fail_json(msg="The virtual machine was not found") + + dvswitch = pg.config.distributedVirtualSwitch + port.switchUuid = dvswitch.uuid + port.portgroupKey = pg.key + nic.port = port + + for device in vm.config.hardware.device: + if isinstance(device, vim.vm.device.VirtualEthernetCard): + devicespec.device = device + devicespec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit + devicespec.device.backing = nic + vm_configspec.deviceChange.append(devicespec) + + task = vm.ReconfigVM_Task(vm_configspec) + changed, result = wait_for_task(task) + module.exit_json(changed=changed, result=result) + + +def state_exit_unchanged(module): + module.exit_json(changed=False) + + +def check_vm_network_state(module): + vm_name = module.params['vm_name'] + try: + content = connect_to_api(module) + module.params['content'] = content + vm = find_vm_by_name(content, vm_name) + module.params['vm'] = vm + if vm is None: + module.fail_json(msg="A virtual machine with name %s does not exist" % vm_name) + for device in vm.config.hardware.device: + if isinstance(device, vim.vm.device.VirtualEthernetCard): + if isinstance(device.backing, vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo): + return 'present' + return 'absent' + except vmodl.RuntimeFault as runtime_fault: + module.fail_json(msg=runtime_fault.msg) + except vmodl.MethodFault as method_fault: + module.fail_json(msg=method_fault.msg) + + +def main(): + + argument_spec = vmware_argument_spec() + argument_spec.update(dict(vm_name=dict(required=True, type='str'), + dvportgroup_name=dict(required=True, type='str'))) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi is required for this module') + + vm_nic_states = { + 'absent': migrate_network_adapter_vds, + 'present': state_exit_unchanged, + } + + vm_nic_states[check_vm_network_state(module)](module) + +from ansible.module_utils.vmware import * +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/cloud/vmware/vmware_vmkernel.py b/cloud/vmware/vmware_vmkernel.py new file mode 100644 index 00000000000..0221f68ad2e --- /dev/null +++ b/cloud/vmware/vmware_vmkernel.py @@ -0,0 +1,221 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Joseph Callen +# +# 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: vmware_vmkernel +short_description: Create a VMware VMkernel Interface +description: + - Create a VMware VMkernel Interface +version_added: 2.0 +author: "Joseph Callen (@jcpowermac), Russell Teague (@mtnbikenc)" +notes: + - Tested on vSphere 5.5 +requirements: + - "python >= 2.6" + - PyVmomi +options: + hostname: + description: + - The hostname or IP address of the ESXi Server + required: True + username: + description: + - The username of the ESXi Server + required: True + aliases: ['user', 'admin'] + password: + description: + - The password of ESXi Server + required: True + aliases: ['pass', 'pwd'] + vswitch_name: + description: + - The name of the vswitch where to add the VMK interface + required: True + portgroup_name: + description: + - The name of the portgroup for the VMK interface + required: True + ip_address: + description: + - The IP Address for the VMK interface + required: True + subnet_mask: + description: + - The Subnet Mask for the VMK interface + required: True + vland_id: + description: + - The VLAN ID for the VMK interface + required: True + mtu: + description: + - The MTU for the VMK interface + required: False + enable_vsan: + description: + - Enable the VMK interface for VSAN traffic + required: False + enable_vmotion: + description: + - Enable the VMK interface for vMotion traffic + required: False + enable_mgmt: + description: + - Enable the VMK interface for Management traffic + required: False + enable_ft: + description: + - Enable the VMK interface for Fault Tolerance traffic + required: False +''' + +EXAMPLES = ''' +# Example command from Ansible Playbook + +- name: Add Management vmkernel port (vmk1) + local_action: + module: vmware_vmkernel + hostname: esxi_hostname + username: esxi_username + password: esxi_password + vswitch_name: vswitch_name + portgroup_name: portgroup_name + vlan_id: vlan_id + ip_address: ip_address + subnet_mask: subnet_mask + enable_mgmt: True +''' + +try: + from pyVmomi import vim, vmodl + HAS_PYVMOMI = True +except ImportError: + HAS_PYVMOMI = False + + +def create_vmkernel_adapter(host_system, port_group_name, + vlan_id, vswitch_name, + ip_address, subnet_mask, + mtu, enable_vsan, enable_vmotion, enable_mgmt, enable_ft): + + host_config_manager = host_system.configManager + host_network_system = host_config_manager.networkSystem + host_virtual_vic_manager = host_config_manager.virtualNicManager + config = vim.host.NetworkConfig() + + config.portgroup = [vim.host.PortGroup.Config()] + config.portgroup[0].changeOperation = "add" + config.portgroup[0].spec = vim.host.PortGroup.Specification() + config.portgroup[0].spec.name = port_group_name + config.portgroup[0].spec.vlanId = vlan_id + config.portgroup[0].spec.vswitchName = vswitch_name + config.portgroup[0].spec.policy = vim.host.NetworkPolicy() + + config.vnic = [vim.host.VirtualNic.Config()] + config.vnic[0].changeOperation = "add" + config.vnic[0].portgroup = port_group_name + config.vnic[0].spec = vim.host.VirtualNic.Specification() + config.vnic[0].spec.ip = vim.host.IpConfig() + config.vnic[0].spec.ip.dhcp = False + config.vnic[0].spec.ip.ipAddress = ip_address + config.vnic[0].spec.ip.subnetMask = subnet_mask + if mtu: + config.vnic[0].spec.mtu = mtu + + host_network_config_result = host_network_system.UpdateNetworkConfig(config, "modify") + + for vnic_device in host_network_config_result.vnicDevice: + if enable_vsan: + vsan_system = host_config_manager.vsanSystem + vsan_config = vim.vsan.host.ConfigInfo() + vsan_config.networkInfo = vim.vsan.host.ConfigInfo.NetworkInfo() + + vsan_config.networkInfo.port = [vim.vsan.host.ConfigInfo.NetworkInfo.PortConfig()] + + vsan_config.networkInfo.port[0].device = vnic_device + host_vsan_config_result = vsan_system.UpdateVsan_Task(vsan_config) + + if enable_vmotion: + host_virtual_vic_manager.SelectVnicForNicType("vmotion", vnic_device) + + if enable_mgmt: + host_virtual_vic_manager.SelectVnicForNicType("management", vnic_device) + + if enable_ft: + host_virtual_vic_manager.SelectVnicForNicType("faultToleranceLogging", vnic_device) + return True + + +def main(): + + argument_spec = vmware_argument_spec() + argument_spec.update(dict(portgroup_name=dict(required=True, type='str'), + ip_address=dict(required=True, type='str'), + subnet_mask=dict(required=True, type='str'), + mtu=dict(required=False, type='int'), + enable_vsan=dict(required=False, type='bool'), + enable_vmotion=dict(required=False, type='bool'), + enable_mgmt=dict(required=False, type='bool'), + enable_ft=dict(required=False, type='bool'), + vswitch_name=dict(required=True, type='str'), + vlan_id=dict(required=True, type='int'))) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi is required for this module') + + port_group_name = module.params['portgroup_name'] + ip_address = module.params['ip_address'] + subnet_mask = module.params['subnet_mask'] + mtu = module.params['mtu'] + enable_vsan = module.params['enable_vsan'] + enable_vmotion = module.params['enable_vmotion'] + enable_mgmt = module.params['enable_mgmt'] + enable_ft = module.params['enable_ft'] + vswitch_name = module.params['vswitch_name'] + vlan_id = module.params['vlan_id'] + + try: + content = connect_to_api(module) + host = get_all_objs(content, [vim.HostSystem]) + if not host: + module.fail_json(msg="Unable to locate Physical Host.") + host_system = host.keys()[0] + changed = create_vmkernel_adapter(host_system, port_group_name, + vlan_id, vswitch_name, + ip_address, subnet_mask, + mtu, enable_vsan, enable_vmotion, enable_mgmt, enable_ft) + module.exit_json(changed=changed) + except vmodl.RuntimeFault as runtime_fault: + module.fail_json(msg=runtime_fault.msg) + except vmodl.MethodFault as method_fault: + module.fail_json(msg=method_fault.msg) + except Exception as e: + module.fail_json(msg=str(e)) + + +from ansible.module_utils.vmware import * +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() diff --git a/cloud/vmware/vmware_vmkernel_ip_config.py b/cloud/vmware/vmware_vmkernel_ip_config.py new file mode 100644 index 00000000000..c07526f0aeb --- /dev/null +++ b/cloud/vmware/vmware_vmkernel_ip_config.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Joseph Callen +# +# 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: vmware_vmkernel_ip_config +short_description: Configure the VMkernel IP Address +description: + - Configure the VMkernel IP Address +version_added: 2.0 +author: "Joseph Callen (@jcpowermac), Russell Teague (@mtnbikenc)" +notes: + - Tested on vSphere 5.5 +requirements: + - "python >= 2.6" + - PyVmomi +options: + hostname: + description: + - The hostname or IP address of the ESXi server + required: True + username: + description: + - The username of the ESXi server + required: True + aliases: ['user', 'admin'] + password: + description: + - The password of the ESXi server + required: True + aliases: ['pass', 'pwd'] + vmk_name: + description: + - VMkernel interface name + required: True + ip_address: + description: + - IP address to assign to VMkernel interface + required: True + subnet_mask: + description: + - Subnet Mask to assign to VMkernel interface + required: True +''' + +EXAMPLES = ''' +# Example command from Ansible Playbook + +- name: Configure IP address on ESX host + local_action: + module: vmware_vmkernel_ip_config + hostname: esxi_hostname + username: esxi_username + password: esxi_password + vmk_name: vmk0 + ip_address: 10.0.0.10 + subnet_mask: 255.255.255.0 +''' + +try: + from pyVmomi import vim, vmodl + HAS_PYVMOMI = True +except ImportError: + HAS_PYVMOMI = False + + +def configure_vmkernel_ip_address(host_system, vmk_name, ip_address, subnet_mask): + + host_config_manager = host_system.configManager + host_network_system = host_config_manager.networkSystem + + for vnic in host_network_system.networkConfig.vnic: + if vnic.device == vmk_name: + spec = vnic.spec + if spec.ip.ipAddress != ip_address: + spec.ip.dhcp = False + spec.ip.ipAddress = ip_address + spec.ip.subnetMask = subnet_mask + host_network_system.UpdateVirtualNic(vmk_name, spec) + return True + return False + + +def main(): + + argument_spec = vmware_argument_spec() + argument_spec.update(dict(vmk_name=dict(required=True, type='str'), + ip_address=dict(required=True, type='str'), + subnet_mask=dict(required=True, type='str'))) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi is required for this module') + + vmk_name = module.params['vmk_name'] + ip_address = module.params['ip_address'] + subnet_mask = module.params['subnet_mask'] + + try: + content = connect_to_api(module, False) + host = get_all_objs(content, [vim.HostSystem]) + if not host: + module.fail_json(msg="Unable to locate Physical Host.") + host_system = host.keys()[0] + changed = configure_vmkernel_ip_address(host_system, vmk_name, ip_address, subnet_mask) + module.exit_json(changed=changed) + except vmodl.RuntimeFault as runtime_fault: + module.fail_json(msg=runtime_fault.msg) + except vmodl.MethodFault as method_fault: + module.fail_json(msg=method_fault.msg) + except Exception as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.vmware import * +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() diff --git a/monitoring/nagios.py b/monitoring/nagios.py index 16edca2aa6a..ed1da7a1e2e 100644 --- a/monitoring/nagios.py +++ b/monitoring/nagios.py @@ -76,7 +76,7 @@ options: servicegroup: version_added: "2.0" description: - - the Servicegroup we want to set downtimes/alerts for. + - The Servicegroup we want to set downtimes/alerts for. B(Required) option when using the C(servicegroup_service_downtime) amd C(servicegroup_host_downtime). command: description: @@ -86,7 +86,7 @@ options: required: true default: null -author: "Tim Bielawa (@tbielawa)" +author: "Tim Bielawa (@tbielawa)" requirements: [ "Nagios" ] ''' diff --git a/monitoring/pagerduty_alert.py b/monitoring/pagerduty_alert.py new file mode 100644 index 00000000000..e2d127f0155 --- /dev/null +++ b/monitoring/pagerduty_alert.py @@ -0,0 +1,213 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# 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: pagerduty_alert +short_description: Trigger, acknowledge or resolve PagerDuty incidents +description: + - This module will let you trigger, acknowledge or resolve a PagerDuty incident by sending events +version_added: "1.9" +author: + - "Amanpreet Singh (@aps-sids)" +requirements: + - PagerDuty API access +options: + name: + description: + - PagerDuty unique subdomain. + required: true + service_key: + description: + - The GUID of one of your "Generic API" services. + - This is the "service key" listed on a Generic API's service detail page. + required: true + state: + description: + - Type of event to be sent. + required: true + choices: + - 'triggered' + - 'acknowledged' + - 'resolved' + api_key: + description: + - The pagerduty API key (readonly access), generated on the pagerduty site. + required: true + desc: + description: + - For C(triggered) I(state) - Required. Short description of the problem that led to this trigger. This field (or a truncated version) will be used when generating phone calls, SMS messages and alert emails. It will also appear on the incidents tables in the PagerDuty UI. The maximum length is 1024 characters. + - For C(acknowledged) or C(resolved) I(state) - Text that will appear in the incident's log associated with this event. + required: false + default: Created via Ansible + incident_key: + description: + - Identifies the incident to which this I(state) should be applied. + - For C(triggered) I(state) - If there's no open (i.e. unresolved) incident with this key, a new one will be created. If there's already an open incident with a matching key, this event will be appended to that incident's log. The event key provides an easy way to "de-dup" problem reports. + - For C(acknowledged) or C(resolved) I(state) - This should be the incident_key you received back when the incident was first opened by a trigger event. Acknowledge events referencing resolved or nonexistent incidents will be discarded. + required: false + client: + description: + - The name of the monitoring client that is triggering this event. + required: false + client_url: + description: + - The URL of the monitoring client that is triggering this event. + required: false +''' + +EXAMPLES = ''' +# Trigger an incident with just the basic options +- pagerduty_alert: + name: companyabc + service_key=xxx + api_key:yourapikey + state=triggered + desc="problem that led to this trigger" + +# Trigger an incident with more options +- pagerduty_alert: + service_key=xxx + api_key=yourapikey + state=triggered + desc="problem that led to this trigger" + incident_key=somekey + client="Sample Monitoring Service" + client_url=http://service.example.com + +# Acknowledge an incident based on incident_key +- pagerduty_alert: + service_key=xxx + api_key=yourapikey + state=acknowledged + incident_key=somekey + desc="some text for incident's log" + +# Resolve an incident based on incident_key +- pagerduty_alert: + service_key=xxx + api_key=yourapikey + state=resolved + incident_key=somekey + desc="some text for incident's log" +''' + + +def check(module, name, state, service_key, api_key, incident_key=None): + url = "https://%s.pagerduty.com/api/v1/incidents" % name + headers = { + "Content-type": "application/json", + "Authorization": "Token token=%s" % api_key + } + + data = { + "service_key": service_key, + "incident_key": incident_key, + "sort_by": "incident_number:desc" + } + + response, info = fetch_url(module, url, method='get', + headers=headers, data=json.dumps(data)) + + if info['status'] != 200: + module.fail_json(msg="failed to check current incident status." + "Reason: %s" % info['msg']) + json_out = json.loads(response.read())["incidents"][0] + + if state != json_out["status"]: + return json_out, True + return json_out, False + + +def send_event(module, service_key, event_type, desc, + incident_key=None, client=None, client_url=None): + url = "https://events.pagerduty.com/generic/2010-04-15/create_event.json" + headers = { + "Content-type": "application/json" + } + + data = { + "service_key": service_key, + "event_type": event_type, + "incident_key": incident_key, + "description": desc, + "client": client, + "client_url": client_url + } + + response, info = fetch_url(module, url, method='post', + headers=headers, data=json.dumps(data)) + if info['status'] != 200: + module.fail_json(msg="failed to %s. Reason: %s" % + (event_type, info['msg'])) + json_out = json.loads(response.read()) + return json_out + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + service_key=dict(required=True), + api_key=dict(required=True), + state=dict(required=True, + choices=['triggered', 'acknowledged', 'resolved']), + client=dict(required=False, default=None), + client_url=dict(required=False, default=None), + desc=dict(required=False, default='Created via Ansible'), + incident_key=dict(required=False, default=None) + ), + supports_check_mode=True + ) + + name = module.params['name'] + service_key = module.params['service_key'] + api_key = module.params['api_key'] + state = module.params['state'] + client = module.params['client'] + client_url = module.params['client_url'] + desc = module.params['desc'] + incident_key = module.params['incident_key'] + + state_event_dict = { + 'triggered': 'trigger', + 'acknowledged': 'acknowledge', + 'resolved': 'resolve' + } + + event_type = state_event_dict[state] + + if event_type != 'trigger' and incident_key is None: + module.fail_json(msg="incident_key is required for " + "acknowledge or resolve events") + + out, changed = check(module, name, state, + service_key, api_key, incident_key) + + if not module.check_mode and changed is True: + out = send_event(module, service_key, event_type, desc, + incident_key, client, client_url) + + module.exit_json(result=out, changed=changed) + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.urls import * + +if __name__ == '__main__': + main() diff --git a/system/firewalld.py b/system/firewalld.py index 04dd4981584..9a63da3a544 100644 --- a/system/firewalld.py +++ b/system/firewalld.py @@ -52,7 +52,7 @@ options: - 'The firewalld zone to add/remove to/from (NOTE: default zone can be configured per system but "public" is default from upstream. Available choices can be extended based on per-system configs, listed here are "out of the box" defaults).' required: false default: system-default(public) - choices: [ "work", "drop", "internal", "external", "trusted", "home", "dmz", "public", "block"] + choices: [ "work", "drop", "internal", "external", "trusted", "home", "dmz", "public", "block" ] permanent: description: - "Should this configuration be in the running firewalld configuration or persist across reboots." @@ -67,6 +67,7 @@ options: description: - "Should this port accept(enabled) or reject(disabled) connections." required: true + choices: [ "enabled", "disabled" ] timeout: description: - "The amount of time the rule should be in effect for when non-permanent."