From edc957a4b50279d0ce9d255fca7ef5e23b01233b Mon Sep 17 00:00:00 2001 From: Robb Wagoner Date: Thu, 29 Jan 2015 16:59:15 -0700 Subject: [PATCH 01/88] Include a CFN stack's resources in the result --- cloud/amazon/cloudformation.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cloud/amazon/cloudformation.py b/cloud/amazon/cloudformation.py index b382e3f05ff..9a77c79c3a8 100644 --- a/cloud/amazon/cloudformation.py +++ b/cloud/amazon/cloudformation.py @@ -292,6 +292,16 @@ def main(): for output in stack.outputs: stack_outputs[output.key] = output.value result['stack_outputs'] = stack_outputs + stack_resources = [] + for res in cfn.list_stack_resources(stack_name): + stack_resources.append({ + "last_updated_time": res.last_updated_time, + "logical_resource_id": res.logical_resource_id, + "physical_resource_id": res.physical_resource_id, + "status": res.resource_status, + "status_reason": res.resource_status_reason, + "resource_type": res.resource_type }) + result['stack_resources'] = stack_resources # absent state is different because of the way delete_stack works. # problem is it it doesn't give an error if stack isn't found From 6d35aa35244f1873fea98c9a1fbdd0741203e702 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Thu, 26 Mar 2015 16:45:27 +0200 Subject: [PATCH 02/88] rackspace: pass full path to the isdir. --- cloud/rackspace/rax_files_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/rackspace/rax_files_objects.py b/cloud/rackspace/rax_files_objects.py index f2510477674..93e65980ddc 100644 --- a/cloud/rackspace/rax_files_objects.py +++ b/cloud/rackspace/rax_files_objects.py @@ -271,7 +271,7 @@ def upload(module, cf, container, src, dest, meta, expires): if path != src: prefix = path.split(src)[-1].lstrip('/') filenames = [os.path.join(prefix, name) for name in filenames - if not os.path.isdir(name)] + if not os.path.isdir(os.path.join(path, name))] objs += filenames _objs = [] From 720aeffca2bd2ae1eca158abc2d1463a8597afb6 Mon Sep 17 00:00:00 2001 From: Scot Spinner Date: Mon, 18 May 2015 10:34:21 -0700 Subject: [PATCH 03/88] adding parameter group option --- cloud/amazon/elasticache.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cloud/amazon/elasticache.py b/cloud/amazon/elasticache.py index bc51fd6d264..5e9c2cc0e12 100644 --- a/cloud/amazon/elasticache.py +++ b/cloud/amazon/elasticache.py @@ -57,6 +57,11 @@ options: - The port number on which each of the cache nodes will accept connections required: false default: 11211 + parameter_group: + description: + - Specify non-default parameter group names to be associated with cache cluster + required: false + default: None cache_subnet_group: description: - The subnet group name to associate with. Only use if inside a vpc. Required if inside a vpc @@ -148,7 +153,7 @@ class ElastiCacheManager(object): EXIST_STATUSES = ['available', 'creating', 'rebooting', 'modifying'] def __init__(self, module, name, engine, cache_engine_version, node_type, - num_nodes, cache_port, cache_subnet_group, + num_nodes, cache_port, parameter_group, cache_subnet_group, cache_security_groups, security_group_ids, zone, wait, hard_modify, region, **aws_connect_kwargs): self.module = module @@ -158,6 +163,7 @@ class ElastiCacheManager(object): self.node_type = node_type self.num_nodes = num_nodes self.cache_port = cache_port + self.parameter_group = parameter_group self.cache_subnet_group = cache_subnet_group self.cache_security_groups = cache_security_groups self.security_group_ids = security_group_ids @@ -216,6 +222,7 @@ class ElastiCacheManager(object): engine_version=self.cache_engine_version, cache_security_group_names=self.cache_security_groups, security_group_ids=self.security_group_ids, + cache_parameter_group_name=self.parameter_group, cache_subnet_group_name=self.cache_subnet_group, preferred_availability_zone=self.zone, port=self.cache_port) @@ -291,6 +298,7 @@ class ElastiCacheManager(object): num_cache_nodes=self.num_nodes, cache_node_ids_to_remove=nodes_to_remove, cache_security_group_names=self.cache_security_groups, + cache_parameter_group_name=self.parameter_group, security_group_ids=self.security_group_ids, apply_immediately=True, engine_version=self.cache_engine_version) @@ -481,6 +489,7 @@ def main(): node_type={'required': False, 'default': 'cache.m1.small'}, num_nodes={'required': False, 'default': None, 'type': 'int'}, cache_port={'required': False, 'default': 11211, 'type': 'int'}, + parameter_group={'required': False, 'default': None}, cache_subnet_group={'required': False, 'default': None}, cache_security_groups={'required': False, 'default': [default], 'type': 'list'}, @@ -514,6 +523,7 @@ def main(): zone = module.params['zone'] wait = module.params['wait'] hard_modify = module.params['hard_modify'] + parameter_group = module.params['parameter_group'] if cache_subnet_group and cache_security_groups == [default]: cache_security_groups = [] @@ -534,7 +544,7 @@ def main(): num_nodes, cache_port, cache_subnet_group, cache_security_groups, - security_group_ids, zone, wait, + security_group_ids, parameter_group, zone, wait, hard_modify, region, **aws_connect_kwargs) if state == 'present': From f9a6c898b45d51a3262932858f0a17de6c07c0f0 Mon Sep 17 00:00:00 2001 From: Kamil Madac Date: Sun, 28 Jun 2015 21:50:11 +0200 Subject: [PATCH 04/88] Fixed bug, when MX records pointed to ORIGIN(@) caused unintentional change of random A record. --- cloud/digital_ocean/digital_ocean_domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/digital_ocean/digital_ocean_domain.py b/cloud/digital_ocean/digital_ocean_domain.py index 905b6dae2d0..3b7a2dce236 100644 --- a/cloud/digital_ocean/digital_ocean_domain.py +++ b/cloud/digital_ocean/digital_ocean_domain.py @@ -195,7 +195,7 @@ def core(module): records = domain.records() at_record = None for record in records: - if record.name == "@": + if record.name == "@" and record.record_type == 'A': at_record = record if not at_record.data == getkeyordie("ip"): From edce6a41cfc3235354fea639baaef8198aed0ffc Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 31 Mar 2015 17:05:43 -0400 Subject: [PATCH 05/88] Add OpenStack Router module Also deprecate old quantum_router module --- .../{quantum_router.py => _quantum_router.py} | 1 + cloud/openstack/os_router.py | 129 ++++++++++++++++++ 2 files changed, 130 insertions(+) rename cloud/openstack/{quantum_router.py => _quantum_router.py} (99%) create mode 100644 cloud/openstack/os_router.py diff --git a/cloud/openstack/quantum_router.py b/cloud/openstack/_quantum_router.py similarity index 99% rename from cloud/openstack/quantum_router.py rename to cloud/openstack/_quantum_router.py index ba94773bbe4..252e1618d90 100644 --- a/cloud/openstack/quantum_router.py +++ b/cloud/openstack/_quantum_router.py @@ -31,6 +31,7 @@ DOCUMENTATION = ''' module: quantum_router version_added: "1.2" author: "Benno Joy (@bennojoy)" +deprecated: Deprecated in 2.0. Use os_router instead short_description: Create or Remove router from openstack description: - Create or Delete routers from OpenStack diff --git a/cloud/openstack/os_router.py b/cloud/openstack/os_router.py new file mode 100644 index 00000000000..f5964b2246f --- /dev/null +++ b/cloud/openstack/os_router.py @@ -0,0 +1,129 @@ +#!/usr/bin/python + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy +# +# This module 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. +# +# This software 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 software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + + +DOCUMENTATION = ''' +--- +module: os_router +short_description: Create or Delete routers from OpenStack +extends_documentation_fragment: openstack +version_added: "1.10" +description: + - Create or Delete routers from OpenStack +options: + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + name: + description: + - Name to be give to the router + required: true + admin_state_up: + description: + - desired admin state of the created router . + required: false + default: true +requirements: ["shade"] +''' + +EXAMPLES = ''' +# Creates a router for tenant admin +- os_router: state=present + username=admin + password=admin + project_name=admin + name=router1" +''' + +def _get_router_id(module, neutron): + kwargs = { + 'name': module.params['name'], + } + try: + routers = neutron.list_routers(**kwargs) + except Exception, e: + module.fail_json(msg = "Error in getting the router list: %s " % e.message) + if not routers['routers']: + return None + return routers['routers'][0]['id'] + +def _create_router(module, neutron): + router = { + 'name': module.params['name'], + 'admin_state_up': module.params['admin_state_up'], + } + try: + new_router = neutron.create_router(dict(router=router)) + except Exception, e: + module.fail_json( msg = "Error in creating router: %s" % e.message) + return new_router['router']['id'] + +def _delete_router(module, neutron, router_id): + try: + neutron.delete_router(router_id) + except: + module.fail_json("Error in deleting the router") + return True + +def main(): + argument_spec = openstack_full_argument_spec( + name = dict(required=True), + state = dict(default='present', choices=['absent', 'present']), + admin_state_up = dict(type='bool', default=True), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + try: + cloud = shade.openstack_cloud(**module.params) + neutron = cloud.neutron_client + + + if module.params['state'] == 'present': + router_id = _get_router_id(module, neutron) + if not router_id: + router_id = _create_router(module, neutron) + module.exit_json(changed=True, result="Created", id=router_id) + else: + module.exit_json(changed=False, result="success" , id=router_id) + + else: + router_id = _get_router_id(module, neutron) + if not router_id: + module.exit_json(changed=False, result="success") + else: + _delete_router(module, neutron, router_id) + module.exit_json(changed=True, result="deleted") + except shade.OpenStackCloudException as e: + module.fail_json(msg=e.message) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +main() From 653060dc47cf1557c487bc007b76ee133f8bc8ef Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Wed, 8 Apr 2015 14:10:34 -0400 Subject: [PATCH 06/88] Update os_router to the latest version This version uses the latest shade for a cleaner interface, support for check mode, and updating an existing router. --- cloud/openstack/os_router.py | 109 +++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 50 deletions(-) diff --git a/cloud/openstack/os_router.py b/cloud/openstack/os_router.py index f5964b2246f..767d1da183f 100644 --- a/cloud/openstack/os_router.py +++ b/cloud/openstack/os_router.py @@ -30,7 +30,9 @@ short_description: Create or Delete routers from OpenStack extends_documentation_fragment: openstack version_added: "1.10" description: - - Create or Delete routers from OpenStack + - Create or Delete routers from OpenStack. Although Neutron allows + routers to share the same name, this module enforces name uniqueness + to be more user friendly. options: state: description: @@ -43,7 +45,7 @@ options: required: true admin_state_up: description: - - desired admin state of the created router . + - Desired admin state of the created router. required: false default: true requirements: ["shade"] @@ -51,75 +53,82 @@ requirements: ["shade"] EXAMPLES = ''' # Creates a router for tenant admin -- os_router: state=present - username=admin - password=admin - project_name=admin - name=router1" +- os_router: + state=present + name=router1 + admin_state_up=True ''' -def _get_router_id(module, neutron): - kwargs = { - 'name': module.params['name'], - } - try: - routers = neutron.list_routers(**kwargs) - except Exception, e: - module.fail_json(msg = "Error in getting the router list: %s " % e.message) - if not routers['routers']: - return None - return routers['routers'][0]['id'] - -def _create_router(module, neutron): - router = { - 'name': module.params['name'], - 'admin_state_up': module.params['admin_state_up'], - } - try: - new_router = neutron.create_router(dict(router=router)) - except Exception, e: - module.fail_json( msg = "Error in creating router: %s" % e.message) - return new_router['router']['id'] -def _delete_router(module, neutron, router_id): - try: - neutron.delete_router(router_id) - except: - module.fail_json("Error in deleting the router") - return True +def _needs_update(router, admin_state_up): + """Decide if the given router needs an update. + + The only attribute of the router that we allow to change is the value + of admin_state_up. Name changes are not supported here. + """ + if router['admin_state_up'] != admin_state_up: + return True + return False + +def _system_state_change(module, router): + """Check if the system state would be changed.""" + state = module.params['state'] + if state == 'absent' and router: + return True + if state == 'present': + if not router: + return True + return _needs_update(router, module.params['admin_state_up']) + return False def main(): argument_spec = openstack_full_argument_spec( - name = dict(required=True), - state = dict(default='present', choices=['absent', 'present']), - admin_state_up = dict(type='bool', default=True), + name=dict(required=True), + admin_state_up=dict(type='bool', default=True), + state=dict(default='present', choices=['absent', 'present']), ) + module_kwargs = openstack_module_kwargs() - module = AnsibleModule(argument_spec, **module_kwargs) + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) if not HAS_SHADE: module.fail_json(msg='shade is required for this module') + name = module.params['name'] + admin_state_up = module.params['admin_state_up'] + state = module.params['state'] + try: cloud = shade.openstack_cloud(**module.params) - neutron = cloud.neutron_client + router = cloud.get_router(name) + if module.check_mode: + module.exit_json(changed=_system_state_change(module, router)) - if module.params['state'] == 'present': - router_id = _get_router_id(module, neutron) - if not router_id: - router_id = _create_router(module, neutron) - module.exit_json(changed=True, result="Created", id=router_id) + if state == 'present': + if not router: + router = cloud.create_router(name, admin_state_up) + module.exit_json(changed=True, result="created", + id=router['id']) else: - module.exit_json(changed=False, result="success" , id=router_id) + if _needs_update(router, admin_state_up): + cloud.update_router(router['id'], + admin_state_up=admin_state_up) + module.exit_json(changed=True, result="updated", + id=router['id']) + else: + module.exit_json(changed=False, result="success", + id=router['id']) - else: - router_id = _get_router_id(module, neutron) - if not router_id: + elif state == 'absent': + if not router: module.exit_json(changed=False, result="success") else: - _delete_router(module, neutron, router_id) + cloud.delete_router(name) module.exit_json(changed=True, result="deleted") + except shade.OpenStackCloudException as e: module.fail_json(msg=e.message) From d1978c33dc6d44adfb9ff0ca529ee35e7d6aa03a Mon Sep 17 00:00:00 2001 From: Mehul Ved Date: Thu, 9 Jul 2015 18:50:14 +0530 Subject: [PATCH 07/88] Added support for instance types. Added type and version parameters to be passed to rax_cdb module so users can create Percona and MariaDB instance types. --- cloud/rackspace/rax_cdb.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/cloud/rackspace/rax_cdb.py b/cloud/rackspace/rax_cdb.py index 6abadd2ebf4..7e67612913b 100644 --- a/cloud/rackspace/rax_cdb.py +++ b/cloud/rackspace/rax_cdb.py @@ -38,6 +38,14 @@ options: description: - Volume size of the database 1-150GB default: 2 + type: + description: + - type of instance (i.e. MySQL, MariaDB, Percona) + default: MySQL + version: + description: + - version of database (MySQL supports 5.1 and 5.6, MariaDB supports 10, Percona supports 5.6) + choices: ['5.1', '5.6', '10'] state: description: - Indicate desired state of the resource @@ -68,6 +76,8 @@ EXAMPLES = ''' name: db-server1 flavor: 1 volume: 2 + type: MySQL + version: 5.6 wait: yes state: present register: rax_db_server @@ -91,10 +101,12 @@ def find_instance(name): return False -def save_instance(module, name, flavor, volume, wait, wait_timeout): +def save_instance(module, name, flavor, volume, type, version, wait, + wait_timeout): for arg, value in dict(name=name, flavor=flavor, - volume=volume).iteritems(): + volume=volume, type=type, version=version + ).iteritems(): if not value: module.fail_json(msg='%s is required for the "rax_cdb"' ' module' % arg) @@ -118,7 +130,8 @@ def save_instance(module, name, flavor, volume, wait, wait_timeout): if not instance: action = 'create' try: - instance = cdb.create(name=name, flavor=flavor, volume=volume) + instance = cdb.create(name=name, flavor=flavor, volume=volume, + type=type, version=version) except Exception, e: module.fail_json(msg='%s' % e.message) else: @@ -189,11 +202,13 @@ def delete_instance(module, name, wait, wait_timeout): cdb=rax_to_dict(instance)) -def rax_cdb(module, state, name, flavor, volume, wait, wait_timeout): +def rax_cdb(module, state, name, flavor, volume, type, version, wait, + wait_timeout): # act on the state if state == 'present': - save_instance(module, name, flavor, volume, wait, wait_timeout) + save_instance(module, name, flavor, volume, type, version, wait, + wait_timeout) elif state == 'absent': delete_instance(module, name, wait, wait_timeout) @@ -205,6 +220,8 @@ def main(): name=dict(type='str', required=True), flavor=dict(type='int', default=1), volume=dict(type='int', default=2), + type=dict(type='str', default='MySQL'), + version=dict(type='str', default='5.6'), state=dict(default='present', choices=['present', 'absent']), wait=dict(type='bool', default=False), wait_timeout=dict(type='int', default=300), @@ -222,12 +239,14 @@ def main(): name = module.params.get('name') flavor = module.params.get('flavor') volume = module.params.get('volume') + type = module.params.get('type') + version = module.params.get('version') state = module.params.get('state') wait = module.params.get('wait') wait_timeout = module.params.get('wait_timeout') setup_rax_module(module, pyrax) - rax_cdb(module, state, name, flavor, volume, wait, wait_timeout) + rax_cdb(module, state, name, flavor, volume, type, version, wait, wait_timeout) # import module snippets From cd587ebce1cc14002bfdeb6cffeb006e1094f051 Mon Sep 17 00:00:00 2001 From: Mehul Ved Date: Fri, 10 Jul 2015 16:50:42 +0530 Subject: [PATCH 08/88] renamed variables type to cdb_type and version to cdb_version. --- cloud/rackspace/rax_cdb.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cloud/rackspace/rax_cdb.py b/cloud/rackspace/rax_cdb.py index 7e67612913b..1a8e91ed1b1 100644 --- a/cloud/rackspace/rax_cdb.py +++ b/cloud/rackspace/rax_cdb.py @@ -38,11 +38,11 @@ options: description: - Volume size of the database 1-150GB default: 2 - type: + cdb_type: description: - type of instance (i.e. MySQL, MariaDB, Percona) default: MySQL - version: + cdb_version: description: - version of database (MySQL supports 5.1 and 5.6, MariaDB supports 10, Percona supports 5.6) choices: ['5.1', '5.6', '10'] @@ -76,8 +76,8 @@ EXAMPLES = ''' name: db-server1 flavor: 1 volume: 2 - type: MySQL - version: 5.6 + cdb_type: MySQL + cdb_version: 5.6 wait: yes state: present register: rax_db_server @@ -101,11 +101,11 @@ def find_instance(name): return False -def save_instance(module, name, flavor, volume, type, version, wait, +def save_instance(module, name, flavor, volume, cdb_type, cdb_version, wait, wait_timeout): for arg, value in dict(name=name, flavor=flavor, - volume=volume, type=type, version=version + volume=volume, type=cdb_type, version=cdb_version ).iteritems(): if not value: module.fail_json(msg='%s is required for the "rax_cdb"' @@ -131,7 +131,7 @@ def save_instance(module, name, flavor, volume, type, version, wait, action = 'create' try: instance = cdb.create(name=name, flavor=flavor, volume=volume, - type=type, version=version) + type=cdb_type, version=cdb_version) except Exception, e: module.fail_json(msg='%s' % e.message) else: @@ -202,12 +202,12 @@ def delete_instance(module, name, wait, wait_timeout): cdb=rax_to_dict(instance)) -def rax_cdb(module, state, name, flavor, volume, type, version, wait, +def rax_cdb(module, state, name, flavor, volume, cdb_type, cdb_version, wait, wait_timeout): # act on the state if state == 'present': - save_instance(module, name, flavor, volume, type, version, wait, + save_instance(module, name, flavor, volume, cdb_type, cdb_version, wait, wait_timeout) elif state == 'absent': delete_instance(module, name, wait, wait_timeout) @@ -220,8 +220,8 @@ def main(): name=dict(type='str', required=True), flavor=dict(type='int', default=1), volume=dict(type='int', default=2), - type=dict(type='str', default='MySQL'), - version=dict(type='str', default='5.6'), + cdb_type=dict(type='str', default='MySQL'), + cdb_version=dict(type='str', default='5.6'), state=dict(default='present', choices=['present', 'absent']), wait=dict(type='bool', default=False), wait_timeout=dict(type='int', default=300), @@ -239,14 +239,14 @@ def main(): name = module.params.get('name') flavor = module.params.get('flavor') volume = module.params.get('volume') - type = module.params.get('type') - version = module.params.get('version') + cdb_type = module.params.get('type') + cdb_version = module.params.get('version') state = module.params.get('state') wait = module.params.get('wait') wait_timeout = module.params.get('wait_timeout') setup_rax_module(module, pyrax) - rax_cdb(module, state, name, flavor, volume, type, version, wait, wait_timeout) + rax_cdb(module, state, name, flavor, volume, cdb_type, cdb_version, wait, wait_timeout) # import module snippets From 8dbdfff36610b3eacc19b7f250cc32d03b024969 Mon Sep 17 00:00:00 2001 From: Jamie Hannaford Date: Fri, 8 May 2015 15:43:42 +0200 Subject: [PATCH 09/88] Add wait and wait_timeout options for provisioning servers --- cloud/rackspace/rax_scaling_group.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cloud/rackspace/rax_scaling_group.py b/cloud/rackspace/rax_scaling_group.py index e6c14fdef0f..bdf12a79cbe 100644 --- a/cloud/rackspace/rax_scaling_group.py +++ b/cloud/rackspace/rax_scaling_group.py @@ -105,7 +105,19 @@ options: - Data to be uploaded to the servers config drive. This option implies I(config_drive). Can be a file path or a string version_added: 1.8 -author: "Matt Martz (@sivel)" + wait + description: + - wait for the scaling group to finish provisioning the minimum amount of + servers + default: "no" + choices: + - "yes" + - "no" + wait_timeout: + description: + - how long before wait gives up, in seconds + default: 300 +author: Matt Martz extends_documentation_fragment: rackspace ''' @@ -144,7 +156,7 @@ def rax_asg(module, cooldown=300, disk_config=None, files={}, flavor=None, image=None, key_name=None, loadbalancers=[], meta={}, min_entities=0, max_entities=0, name=None, networks=[], server_name=None, state='present', user_data=None, - config_drive=False): + config_drive=False, wait=True, wait_timeout=300): changed = False au = pyrax.autoscale @@ -315,6 +327,16 @@ def rax_asg(module, cooldown=300, disk_config=None, files={}, flavor=None, sg.get() + if wait: + end_time = time.time() + wait_timeout + infinite = wait_timeout == 0 + while infinite or time.time() < end_time: + state = sg.get_state() + if state["pending_capacity"] == 0: + break + + time.sleep(5) + module.exit_json(changed=changed, autoscale_group=rax_to_dict(sg)) else: @@ -350,6 +372,8 @@ def main(): server_name=dict(required=True), state=dict(default='present', choices=['present', 'absent']), user_data=dict(no_log=True), + wait=dict(default=False, type='bool'), + wait_timeout=dict(default=300), ) ) From abf20836711ac0835e869adaa201e6732bb49482 Mon Sep 17 00:00:00 2001 From: Scot Spinner Date: Fri, 24 Jul 2015 14:11:53 -0700 Subject: [PATCH 10/88] one thing missed --- cloud/amazon/elasticache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/amazon/elasticache.py b/cloud/amazon/elasticache.py index deea0940995..5e5d3b6ee96 100644 --- a/cloud/amazon/elasticache.py +++ b/cloud/amazon/elasticache.py @@ -56,7 +56,7 @@ options: description: - The port number on which each of the cache nodes will accept connections required: false - default: 11211 + default: none parameter_group: description: - Specify non-default parameter group names to be associated with cache cluster From a7667fcaf28e46eab8bd23807a1e3be6da576b95 Mon Sep 17 00:00:00 2001 From: Frank van Tol Date: Mon, 14 Sep 2015 15:57:48 +0200 Subject: [PATCH 11/88] Update s3.py We are copying from S3, the bucket is the source, not the target. --- cloud/amazon/s3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/amazon/s3.py b/cloud/amazon/s3.py index 64f53cc042a..550998915d4 100644 --- a/cloud/amazon/s3.py +++ b/cloud/amazon/s3.py @@ -486,7 +486,7 @@ def main(): # First, we check to see if the bucket exists, we get "bucket" returned. bucketrtn = bucket_check(module, s3, bucket) if bucketrtn is False: - module.fail_json(msg="Target bucket cannot be found", failed=True) + module.fail_json(msg="Source bucket cannot be found", failed=True) # Next, we check to see if the key in the bucket exists. If it exists, it also returns key_matches md5sum check. keyrtn = key_check(module, s3, bucket, obj, version=version) From ef93fb1c149336032d34c645ae00a99386178516 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Mon, 14 Sep 2015 16:20:18 -0400 Subject: [PATCH 12/88] More os_router module cleanup and fixes. Added a RETURN section, corrected version_added value, removed use of 'result' in exit_json() calls. --- cloud/openstack/os_router.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/cloud/openstack/os_router.py b/cloud/openstack/os_router.py index 767d1da183f..24cbefd0c36 100644 --- a/cloud/openstack/os_router.py +++ b/cloud/openstack/os_router.py @@ -28,7 +28,7 @@ DOCUMENTATION = ''' module: os_router short_description: Create or Delete routers from OpenStack extends_documentation_fragment: openstack -version_added: "1.10" +version_added: "2.0" description: - Create or Delete routers from OpenStack. Although Neutron allows routers to share the same name, this module enforces name uniqueness @@ -45,7 +45,7 @@ options: required: true admin_state_up: description: - - Desired admin state of the created router. + - Desired admin state of the created or existing router. required: false default: true requirements: ["shade"] @@ -59,6 +59,13 @@ EXAMPLES = ''' admin_state_up=True ''' +RETURN = ''' +id: + description: Router ID + returned: On success when I(state) is 'present'. + type: string +''' + def _needs_update(router, admin_state_up): """Decide if the given router needs an update. @@ -110,29 +117,28 @@ def main(): if state == 'present': if not router: router = cloud.create_router(name, admin_state_up) - module.exit_json(changed=True, result="created", - id=router['id']) + module.exit_json(changed=True, id=router['id']) else: if _needs_update(router, admin_state_up): cloud.update_router(router['id'], admin_state_up=admin_state_up) - module.exit_json(changed=True, result="updated", - id=router['id']) + module.exit_json(changed=True, id=router['id']) else: - module.exit_json(changed=False, result="success", - id=router['id']) + module.exit_json(changed=False, id=router['id']) elif state == 'absent': if not router: - module.exit_json(changed=False, result="success") + module.exit_json(changed=False) else: cloud.delete_router(name) - module.exit_json(changed=True, result="deleted") + module.exit_json(changed=True) except shade.OpenStackCloudException as e: module.fail_json(msg=e.message) + # this is magic, see lib/ansible/module_common.py from ansible.module_utils.basic import * from ansible.module_utils.openstack import * -main() +if __name__ == '__main__': + main() From 0e3f7c92d70a74c2ab01a8d3b5471262640b7e33 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 15 Sep 2015 16:26:15 -0400 Subject: [PATCH 13/88] Fix win_lineinfile to pass integration tests when strict mode is enabled. --- windows/win_lineinfile.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/windows/win_lineinfile.ps1 b/windows/win_lineinfile.ps1 index ddf1d4e3000..4ba9086a6e9 100644 --- a/windows/win_lineinfile.ps1 +++ b/windows/win_lineinfile.ps1 @@ -387,8 +387,11 @@ Elseif (Test-Path $dest) { $found = $FALSE; Foreach ($encoding in $sortedlist.GetValueList()) { $preamble = $encoding.GetPreamble(); - If ($preamble) { - Foreach ($i in 0..$preamble.Length) { + If ($preamble -and $bom) { + Foreach ($i in 0..($preamble.Length - 1)) { + If ($i -ge $bom.Length) { + break; + } If ($preamble[$i] -ne $bom[$i]) { break; } @@ -427,7 +430,7 @@ If ($state -eq "present") { } Else { - If ($regex -eq $FALSE -and $line -eq $FALSE) { + If ($regexp -eq $FALSE -and $line -eq $FALSE) { Fail-Json (New-Object psobject) "one of line= or regexp= is required with state=absent"; } From 719f68e057ae71469487c0c4a4d8b5affdffddf0 Mon Sep 17 00:00:00 2001 From: whiter Date: Wed, 16 Sep 2015 17:52:43 +1000 Subject: [PATCH 14/88] Remove 'str' type so that json is properly quoted --- cloud/amazon/iam_policy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/amazon/iam_policy.py b/cloud/amazon/iam_policy.py index 04fc8086939..58429b3e743 100644 --- a/cloud/amazon/iam_policy.py +++ b/cloud/amazon/iam_policy.py @@ -289,7 +289,7 @@ def main(): iam_name=dict(default=None, required=False), policy_name=dict(default=None, required=True), policy_document=dict(default=None, required=False), - policy_json=dict(type='str', default=None, required=False), + policy_json=dict(default=None, required=False), skip_duplicates=dict(type='bool', default=True, required=False) )) From 9bf0d06e4a280a75858bc20f2e4d1620c27be533 Mon Sep 17 00:00:00 2001 From: Rob Date: Mon, 14 Sep 2015 12:25:36 +1000 Subject: [PATCH 15/88] Update iam_policy.py Fixed doc for policy_name - it is a required field Removed empty aliases --- cloud/amazon/iam_policy.py | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/cloud/amazon/iam_policy.py b/cloud/amazon/iam_policy.py index 04fc8086939..475a6c35d65 100644 --- a/cloud/amazon/iam_policy.py +++ b/cloud/amazon/iam_policy.py @@ -27,54 +27,34 @@ options: required: true default: null choices: [ "user", "group", "role"] - aliases: [] iam_name: description: - Name of IAM resource you wish to target for policy actions. In other words, the user name, group name or role name. required: true - aliases: [] policy_name: description: - The name label for the policy to create or remove. - required: false - aliases: [] + required: true policy_document: description: - The path to the properly json formatted policy file (mutually exclusive with C(policy_json)) required: false - aliases: [] policy_json: description: - A properly json formatted policy as string (mutually exclusive with C(policy_document), see https://github.com/ansible/ansible/issues/7005#issuecomment-42894813 on how to use it properly) required: false - aliases: [] state: description: - Whether to create or delete the IAM policy. required: true default: null choices: [ "present", "absent"] - aliases: [] skip_duplicates: description: - By default the module looks for any policies that match the document you pass in, if there is a match it will not make a new policy object with the same rules. You can override this by specifying false which would allow for two policy objects with different names but same rules. required: false default: "/" - 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: null - 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: null - aliases: [ 'ec2_access_key', 'access_key' ] -requirements: [ "boto" ] notes: - 'Currently boto does not support the removal of Managed Policies, the module will not work removing/adding managed policies.' author: "Jonathan I. Davila (@defionscode)" From 854ffcb605aa8305551d66e0de8e54742292c734 Mon Sep 17 00:00:00 2001 From: Matias De Carli Date: Sat, 19 Sep 2015 15:19:28 -0300 Subject: [PATCH 16/88] updated dependencies to work with azure.py 1.0.1 --- cloud/azure/azure.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/cloud/azure/azure.py b/cloud/azure/azure.py index c4fa41a6eb1..32442be1930 100644 --- a/cloud/azure/azure.py +++ b/cloud/azure/azure.py @@ -252,7 +252,7 @@ AZURE_ROLE_SIZES = ['ExtraSmall', try: import azure as windows_azure - from azure import WindowsAzureError, WindowsAzureMissingResourceError + from azure.common import AzureException, AzureMissingResourceHttpError from azure.servicemanagement import (ServiceManagementService, OSVirtualHardDisk, SSH, PublicKeys, PublicKey, LinuxConfigurationSet, ConfigurationSetInputEndpoints, ConfigurationSetInputEndpoint, Listener, WindowsConfigurationSet) @@ -274,7 +274,7 @@ def _wait_for_completion(azure, promise, wait_timeout, msg): if operation_result.status == "Succeeded": return - raise WindowsAzureError('Timed out waiting for async operation ' + msg + ' "' + str(promise.request_id) + '" to complete.') + raise AzureException('Timed out waiting for async operation ' + msg + ' "' + str(promise.request_id) + '" to complete.') def _delete_disks_when_detached(azure, wait_timeout, disk_names): def _handle_timeout(signum, frame): @@ -289,7 +289,7 @@ def _delete_disks_when_detached(azure, wait_timeout, disk_names): if disk.attached_to is None: azure.delete_disk(disk.name, True) disk_names.remove(disk_name) - except WindowsAzureError, e: + except AzureException, e: module.fail_json(msg="failed to get or delete disk, error was: %s" % (disk_name, str(e))) finally: signal.alarm(0) @@ -347,13 +347,13 @@ def create_virtual_machine(module, azure): result = azure.create_hosted_service(service_name=name, label=name, location=location) _wait_for_completion(azure, result, wait_timeout, "create_hosted_service") changed = True - except WindowsAzureError, e: + except AzureException, e: module.fail_json(msg="failed to create the new service, error was: %s" % str(e)) try: # check to see if a vm with this name exists; if so, do nothing azure.get_role(name, name, name) - except WindowsAzureMissingResourceError: + except AzureMissingResourceHttpError: # vm does not exist; create it if os_type == 'linux': @@ -419,13 +419,13 @@ def create_virtual_machine(module, azure): virtual_network_name=virtual_network_name) _wait_for_completion(azure, result, wait_timeout, "create_virtual_machine_deployment") changed = True - except WindowsAzureError, e: + except AzureException, e: module.fail_json(msg="failed to create the new virtual machine, error was: %s" % str(e)) try: deployment = azure.get_deployment_by_name(service_name=name, deployment_name=name) return (changed, urlparse(deployment.url).hostname, deployment) - except WindowsAzureError, e: + except AzureException, e: module.fail_json(msg="failed to lookup the deployment information for %s, error was: %s" % (name, str(e))) @@ -453,9 +453,9 @@ def terminate_virtual_machine(module, azure): disk_names = [] try: deployment = azure.get_deployment_by_name(service_name=name, deployment_name=name) - except WindowsAzureMissingResourceError, e: + except AzureMissingResourceHttpError, e: pass # no such deployment or service - except WindowsAzureError, e: + except AzureException, e: module.fail_json(msg="failed to find the deployment, error was: %s" % str(e)) # Delete deployment @@ -468,13 +468,13 @@ def terminate_virtual_machine(module, azure): role_props = azure.get_role(name, deployment.name, role.role_name) if role_props.os_virtual_hard_disk.disk_name not in disk_names: disk_names.append(role_props.os_virtual_hard_disk.disk_name) - except WindowsAzureError, e: + except AzureException, e: module.fail_json(msg="failed to get the role %s, error was: %s" % (role.role_name, str(e))) try: result = azure.delete_deployment(name, deployment.name) _wait_for_completion(azure, result, wait_timeout, "delete_deployment") - except WindowsAzureError, e: + except AzureException, e: module.fail_json(msg="failed to delete the deployment %s, error was: %s" % (deployment.name, str(e))) # It's unclear when disks associated with terminated deployment get detatched. @@ -482,14 +482,14 @@ def terminate_virtual_machine(module, azure): # become detatched by polling the list of remaining disks and examining the state. try: _delete_disks_when_detached(azure, wait_timeout, disk_names) - except (WindowsAzureError, TimeoutError), e: + except (AzureException, TimeoutError), e: module.fail_json(msg=str(e)) try: # Now that the vm is deleted, remove the cloud service result = azure.delete_hosted_service(service_name=name) _wait_for_completion(azure, result, wait_timeout, "delete_hosted_service") - except WindowsAzureError, e: + except AzureException, e: module.fail_json(msg="failed to delete the service %s, error was: %s" % (name, str(e))) public_dns_name = urlparse(deployment.url).hostname @@ -545,11 +545,7 @@ def main(): subscription_id, management_cert_path = get_azure_creds(module) wait_timeout_redirects = int(module.params.get('wait_timeout_redirects')) - if LooseVersion(windows_azure.__version__) <= "0.8.0": - # wrapper for handling redirects which the sdk <= 0.8.0 is not following - azure = Wrapper(ServiceManagementService(subscription_id, management_cert_path), wait_timeout_redirects) - else: - azure = ServiceManagementService(subscription_id, management_cert_path) + azure = ServiceManagementService(subscription_id, management_cert_path) cloud_service_raw = None if module.params.get('state') == 'absent': @@ -597,7 +593,7 @@ class Wrapper(object): while wait_timeout > time.time(): try: return f() - except WindowsAzureError, e: + except AzureException, e: if not str(e).lower().find("temporary redirect") == -1: time.sleep(5) pass From 539c996edebafbdb2c2d0d2a636116acaa608484 Mon Sep 17 00:00:00 2001 From: Mike Boone Date: Sun, 20 Sep 2015 22:45:51 -0400 Subject: [PATCH 17/88] Updated link to the FAQ. --- system/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/user.py b/system/user.py index 45ce77381ce..5f954694e40 100755 --- a/system/user.py +++ b/system/user.py @@ -83,7 +83,7 @@ options: description: - Optionally set the user's password to this crypted value. See the user example in the github examples directory for what this looks - like in a playbook. See U(http://docs.ansible.com/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module) + like in a playbook. See U(http://docs.ansible.com/ansible/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module) for details on various ways to generate these password values. Note on Darwin system, this value has to be cleartext. Beware of security issues. From 842f09cf24a0ca7b971b618bc864671ef3e3a76f Mon Sep 17 00:00:00 2001 From: dagnello Date: Fri, 18 Sep 2015 12:29:45 -0700 Subject: [PATCH 18/88] Adding new image facts module --- cloud/openstack/os_image_facts.py | 157 ++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 cloud/openstack/os_image_facts.py diff --git a/cloud/openstack/os_image_facts.py b/cloud/openstack/os_image_facts.py new file mode 100644 index 00000000000..fa5678b50b7 --- /dev/null +++ b/cloud/openstack/os_image_facts.py @@ -0,0 +1,157 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# This module 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. +# +# This software 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 software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +--- +module: os_image_facts +short_description: Retrieve facts about an image within OpenStack. +version_added: "2.0" +author: "Davide Agnello (@dagnello)" +description: + - Retrieve facts about a image image from OpenStack. +notes: + - Facts are placed in the C(openstack) variable. +requirements: + - "python >= 2.6" + - "shade" +options: + image: + description: + - Name or ID of the image + required: true +extends_documentation_fragment: openstack +''' + +EXAMPLES = ''' +# Gather facts about a previously created image named image1 +- os_image_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject + image: image1 +- debug: var=openstack +''' + +RETURN = ''' +This module registers image details in facts named: openstack_image. When +image is not found, openstack_image will be null. + +id: + description: Unique UUID. + returned: success + type: string +name: + description: Name given to the image. + returned: success + type: string +status: + description: Image status. + returned: success + type: string +created_at: + description: Image created at timestamp. + returned: success + type: string +deleted: + description: Image deleted flag. + returned: success + type: boolean +container_format: + description: Container format of the image. + returned: success + type: string +min_ram: + description: Min amount of RAM required for this image. + returned: success + type: int +disk_format: + description: Disk format of the image. + returned: success + type: string +updated_at: + description: Image updated at timestamp. + returned: success + type: string +properties: + description: Additional properties associated with the image. + returned: success + type: dict +min_disk: + description: Min amount of disk space required for this image. + returned: success + type: int +protected: + description: Image protected flag. + returned: success + type: boolean +checksum: + description: Checksum for the image. + returned: success + type: string +owner: + description: Owner for the image. + returned: success + type: string +is_public: + description: Is plubic flag of the image. + returned: success + type: boolean +deleted_at: + description: Image deleted at timestamp. + returned: success + type: string +size: + description: Size of the image. + returned: success + type: int +''' + + +def main(): + + argument_spec = openstack_full_argument_spec( + image=dict(required=True), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + try: + cloud = shade.openstack_cloud(**module.params) + image = cloud.get_image(module.params['image']) + module.exit_json(changed=False, ansible_facts=dict( + openstack_image=image)) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=e.message) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +if __name__ == '__main__': + main() + From 0813c5fb40c09fae55b1964e870e2add7d0fffd9 Mon Sep 17 00:00:00 2001 From: Greg DeKoenigsberg Date: Tue, 22 Sep 2015 05:51:02 -0400 Subject: [PATCH 19/88] Change author field for os_redhat_subscription.py --- packaging/os/redhat_subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/os/redhat_subscription.py b/packaging/os/redhat_subscription.py index 8e1482a8c4f..2e6887164cf 100644 --- a/packaging/os/redhat_subscription.py +++ b/packaging/os/redhat_subscription.py @@ -7,7 +7,7 @@ short_description: Manage Red Hat Network registration and subscriptions using t description: - Manage registration and subscription to the Red Hat Network entitlement platform. version_added: "1.2" -author: "James Laska (@jlaska)" +author: "Barnaby Court (@barnabycourt)" notes: - In order to register a system, subscription-manager requires either a username and password, or an activationkey. requirements: From ec24a86f69232c579f86d88fc31f43b2c9eca4ec Mon Sep 17 00:00:00 2001 From: Selivanov Pavel Date: Tue, 22 Sep 2015 16:56:13 +0300 Subject: [PATCH 20/88] ec2_group.py: added ICMP rule example --- cloud/amazon/ec2_group.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cloud/amazon/ec2_group.py b/cloud/amazon/ec2_group.py index d2fe04c968d..c79008f53aa 100644 --- a/cloud/amazon/ec2_group.py +++ b/cloud/amazon/ec2_group.py @@ -116,6 +116,10 @@ EXAMPLES = ''' from_port: 10051 to_port: 10051 group_id: sg-12345678 + - proto: icmp + from_port: 8 # icmp type, -1 = any type + to_port: -1 # icmp subtype, -1 = any subtype + cidr_ip: 10.0.0.0/8 - proto: all # the containing group name may be specified here group_name: example From ba8930c83acf1db306ce3dfc50a9de69a235e99f Mon Sep 17 00:00:00 2001 From: Shawn Silva Date: Tue, 22 Sep 2015 10:46:20 -0400 Subject: [PATCH 21/88] Fix for modifying the size of an RDS instance. When attempting to modify the size of an RDS instance Ansible succeeds and returns a "changed" status. However, no changes are applied to the RDS instance. Boto is looking for a keyword parameter of "allocated_storage" to update the size, and this parameter wasn't being included. --- cloud/amazon/rds.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/amazon/rds.py b/cloud/amazon/rds.py index d56c4ae12de..d509bdf4fa0 100644 --- a/cloud/amazon/rds.py +++ b/cloud/amazon/rds.py @@ -917,6 +917,7 @@ def validate_parameters(required_vars, valid_vars, module): 'subnet': 'db_subnet_group_name', 'license_model': 'license_model', 'option_group': 'option_group_name', + 'size': 'allocated_storage', 'iops': 'iops', 'new_instance_name': 'new_instance_id', 'apply_immediately': 'apply_immediately', From e9139ba4fa66f4f632ca9b200f3abf277a0e6ca4 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Tue, 22 Sep 2015 13:59:37 -0700 Subject: [PATCH 22/88] Change the example of rsync_opts to use a list instead of a string Fixes #9889 --- files/synchronize.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/files/synchronize.py b/files/synchronize.py index 73b0bb13364..0332c7580d2 100644 --- a/files/synchronize.py +++ b/files/synchronize.py @@ -204,10 +204,11 @@ synchronize: mode=pull src=some/relative/path dest=/some/absolute/path # Synchronization of src on delegate host to dest on the current inventory host. # If delegate_to is set to the current inventory host, this can be used to synchronize -# two directories on that host. -synchronize: > - src=some/relative/path dest=/some/absolute/path - delegate_to: delegate.host +# two directories on that host. +synchronize: + src: some/relative/path + dest: /some/absolute/path +delegate_to: delegate.host # Synchronize and delete files in dest on the remote host that are not found in src of localhost. synchronize: src=some/relative/path dest=/some/absolute/path delete=yes @@ -222,7 +223,12 @@ synchronize: src=some/relative/path dest=/some/absolute/path rsync_path="sudo rs + /var/conf # include /var/conf even though it was previously excluded # Synchronize passing in extra rsync options -synchronize: src=/tmp/helloworld dest=/var/www/helloword rsync_opts=--no-motd,--exclude=.git +synchronize: + src: /tmp/helloworld + dest: /var/www/helloword + rsync_opts: + - "--no-motd" + - "--exclude=.git" ''' From 07c5143a9977afc271fb7ac57214c3d59008adb8 Mon Sep 17 00:00:00 2001 From: varnav Date: Wed, 23 Sep 2015 08:24:16 +0300 Subject: [PATCH 23/88] Fixed confusion in the docs As 'path' is actually alias for 'dest', this could be confusing. --- files/file.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/file.py b/files/file.py index c3267f7f18b..8219990d1f6 100644 --- a/files/file.py +++ b/files/file.py @@ -93,10 +93,10 @@ EXAMPLES = ''' # change file ownership, group and mode. When specifying mode using octal numbers, first digit should always be 0. - file: path=/etc/foo.conf owner=foo group=foo mode=0644 - file: src=/file/to/link/to dest=/path/to/symlink owner=foo group=foo state=link -- file: src=/tmp/{{ item.path }} dest={{ item.dest }} state=link +- file: src=/tmp/{{ item.src }} dest={{ item.dest }} state=link with_items: - - { path: 'x', dest: 'y' } - - { path: 'z', dest: 'k' } + - { src: 'x', dest: 'y' } + - { src: 'z', dest: 'k' } # touch a file, using symbolic modes to set the permissions (equivalent to 0644) - file: path=/etc/foo.conf state=touch mode="u=rw,g=r,o=r" From 518ac36878fd5b87fb94cd88f2990dd4e198ba17 Mon Sep 17 00:00:00 2001 From: justnom Date: Wed, 23 Sep 2015 11:54:51 -0400 Subject: [PATCH 24/88] Adding additional Docker log drivers. Adding additional `log_driver` choices: * journald * gelf * fluentd Compatible with Docker version >= 1.8.0 --- cloud/docker/docker.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cloud/docker/docker.py b/cloud/docker/docker.py index 0ab564208ba..cf7d7bac4d5 100644 --- a/cloud/docker/docker.py +++ b/cloud/docker/docker.py @@ -97,9 +97,12 @@ options: - You can specify a different logging driver for the container than for the daemon. "json-file" Default logging driver for Docker. Writes JSON messages to file. docker logs command is available only for this logging driver. - "none" disables any logging for the container. docker logs won't be available with this driver. + "none" disables any logging for the container. "syslog" Syslog logging driver for Docker. Writes log messages to syslog. docker logs command is not available for this logging driver. + "journald" Journald logging driver for Docker. Writes log messages to "journald". + "gelf" Graylog Extended Log Format (GELF) logging driver for Docker. Writes log messages to a GELF endpoint likeGraylog or Logstash. + "fluentd" Fluentd logging driver for Docker. Writes log messages to "fluentd" (forward input). If not defined explicitly, the Docker daemon's default ("json-file") will apply. Requires docker >= 1.6.0. required: false @@ -108,6 +111,9 @@ options: - json-file - none - syslog + - journald + - gelf + - fluentd version_added: "2.0" log_opt: description: @@ -1662,7 +1668,7 @@ def main(): net = dict(default=None), pid = dict(default=None), insecure_registry = dict(default=False, type='bool'), - log_driver = dict(default=None, choices=['json-file', 'none', 'syslog']), + log_driver = dict(default=None, choices=['json-file', 'none', 'syslog', 'journald', 'gelf', 'fluentd']), log_opt = dict(default=None, type='dict'), cpu_set = dict(default=None), cap_add = dict(default=None, type='list'), From 4e1d28e3112e2e992e00e7d8f59e7abd05b99866 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 24 Sep 2015 12:37:03 +0000 Subject: [PATCH 25/88] Clarify available options for device_mapping parameter of ec2_ami module --- cloud/amazon/ec2_ami.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cloud/amazon/ec2_ami.py b/cloud/amazon/ec2_ami.py index 0d504ee3b0c..016df7e6e55 100644 --- a/cloud/amazon/ec2_ami.py +++ b/cloud/amazon/ec2_ami.py @@ -72,7 +72,8 @@ options: device_mapping: version_added: "2.0" description: - - An optional list of devices with custom configurations (same block-device-mapping parameters) + - An optional list of device hashes/dictionaries with custom configurations (same block-device-mapping parameters) + - Valid properties include: device_name, volume_type, size (in GB), delete_on_termination (boolean), no_device (boolean), snapshot_id, iops (for io1 volume_type) required: false default: null delete_snapshot: @@ -133,6 +134,21 @@ EXAMPLES = ''' volume_type: gp2 register: instance +# AMI Creation, excluding a volume attached at /dev/sdb +- ec2_ami + aws_access_key: xxxxxxxxxxxxxxxxxxxxxxx + aws_secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + instance_id: i-xxxxxx + name: newtest + device_mapping: + - device_name: /dev/sda1 + size: XXX + delete_on_termination: true + volume_type: gp2 + - device_name: /dev/sdb + no_device: yes + register: instance + # Deregister/Delete AMI - ec2_ami: aws_access_key: xxxxxxxxxxxxxxxxxxxxxxx From a580acc12a28b48b432607ad506f1d410db742ae Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Thu, 24 Sep 2015 13:28:28 -0700 Subject: [PATCH 26/88] No longer check for tmp_path being sent as we don't use it here anyway --- utilities/helper/accelerate.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/utilities/helper/accelerate.py b/utilities/helper/accelerate.py index 8ae8ab263be..26ce36bcc7e 100644 --- a/utilities/helper/accelerate.py +++ b/utilities/helper/accelerate.py @@ -472,8 +472,6 @@ class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): def command(self, data): if 'cmd' not in data: return dict(failed=True, msg='internal error: cmd is required') - if 'tmp_path' not in data: - return dict(failed=True, msg='internal error: tmp_path is required') vvvv("executing: %s" % data['cmd']) From e64791ef1a3171cd6fed42d2392fc10332d24ad2 Mon Sep 17 00:00:00 2001 From: Abitha Palaniappan Date: Mon, 14 Sep 2015 13:50:21 -0700 Subject: [PATCH 27/88] os_server: Adding support to accept 'n' nic args as a string containing list --- cloud/openstack/os_server.py | 70 +++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/cloud/openstack/os_server.py b/cloud/openstack/os_server.py index cd893536eaa..74a5009f972 100644 --- a/cloud/openstack/os_server.py +++ b/cloud/openstack/os_server.py @@ -82,7 +82,10 @@ options: nics: description: - A list of networks to which the instance's interface should - be attached. Networks may be referenced by net-id or net-name. + be attached. Networks may be referenced by net-id/net-name/port-id + or port-name. + Also this accepts a string containing a list of net-id/port-id. + Eg: nics: "net-id=uuid-1,net-id=uuid-2" required: false default: None public_ip: @@ -241,6 +244,25 @@ EXAMPLES = ''' image: Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM) flavor_ram: 4096 flavor_include: Performance + +# Creates a new instance and attaches to multiple network +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance with a string + os_server: + name: vm1 + auth: + auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0/ + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + nics: "net-id=4cb08b20-62fe-11e5-9d70-feff819cdc9f,net-id=542f0430-62fe-11e5-9d70-feff819cdc9f..." ''' @@ -252,25 +274,33 @@ def _exit_hostvars(module, cloud, server, changed=True): def _network_args(module, cloud): args = [] - for net in module.params['nics']: - if net.get('net-id'): - args.append(net) - elif net.get('net-name'): - by_name = cloud.get_network(net['net-name']) - if not by_name: - module.fail_json( - msg='Could not find network by net-name: %s' % - net['net-name']) - args.append({'net-id': by_name['id']}) - elif net.get('port-id'): - args.append(net) - elif net.get('port-name'): - by_name = cloud.get_port(net['port-name']) - if not by_name: - module.fail_json( - msg='Could not find port by port-name: %s' % - net['port-name']) - args.append({'port-id': by_name['id']}) + nics = module.params['nics'] + if type(nics) == str : + for kv_str in nics.split(","): + nic = {} + k, v = kv_str.split("=") + nic[k] = v + args.append(nic) + else: + for net in module.params['nics']: + if net.get('net-id'): + args.append(net) + elif net.get('net-name'): + by_name = cloud.get_network(net['net-name']) + if not by_name: + module.fail_json( + msg='Could not find network by net-name: %s' % + net['net-name']) + args.append({'net-id': by_name['id']}) + elif net.get('port-id'): + args.append(net) + elif net.get('port-name'): + by_name = cloud.get_port(net['port-name']) + if not by_name: + module.fail_json( + msg='Could not find port by port-name: %s' % + net['port-name']) + args.append({'port-id': by_name['id']}) return args From 7d0943732823c39f754716f61ba615543305a6bc Mon Sep 17 00:00:00 2001 From: dagnello Date: Tue, 15 Sep 2015 10:24:06 -0700 Subject: [PATCH 28/88] Adding os_networks_facts module There can be instances during an Ansible play where the list of networks currently available from OpenStack is required. This update provides network list functionality as a new os_networks_facts module. --- cloud/openstack/os_networks_facts.py | 139 +++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 cloud/openstack/os_networks_facts.py diff --git a/cloud/openstack/os_networks_facts.py b/cloud/openstack/os_networks_facts.py new file mode 100644 index 00000000000..56c30adb203 --- /dev/null +++ b/cloud/openstack/os_networks_facts.py @@ -0,0 +1,139 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# This module 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. +# +# This software 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 software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +--- +module: os_networks_facts +short_description: Retrieve facts about one or more OpenStack networks. +version_added: "2.0" +author: "Davide Agnello (@dagnello)" +description: + - Retrieve facts about one or more networks from OpenStack. +requirements: + - "python >= 2.6" + - "shade" +options: + network: + description: + - Name or ID of the Network + required: false + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false +extends_documentation_fragment: openstack +''' + +EXAMPLES = ''' +# Gather facts about previously created networks +- os_networks_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject +- debug: var=openstack_networks + +# Gather facts about a previously created network by name +- os_networks_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject + name: network1 +- debug: var=openstack_networks + +# Gather facts about a previously created network with filter (note: name and + filters parameters are Not mutually exclusive) +- os_networks_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject + filters: + tenant_id: 55e2ce24b2a245b09f181bf025724cbe + subnets: + - 057d4bdf-6d4d-4728-bb0f-5ac45a6f7400 + - 443d4dc0-91d4-4998-b21c-357d10433483 +- debug: var=openstack_networks +''' + +RETURN = ''' +This module registers network details in facts named: openstack_networks. If a +network name/id and or filter does not result in a network found, an empty +list is set in openstack_networks. +id: + description: Unique UUID. + returned: success + type: string +name: + description: Name given to the network. + returned: success + type: string +status: + description: Network status. + returned: success + type: string +subnets: + description: Subnet(s) included in this network. + returned: success + type: list of strings +tenant_id: + description: Tenant id associated with this network. + returned: success + type: string +shared: + description: Network shared flag. + returned: success + type: boolean +''' + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + filters=dict(required=False, default=None) + ) + module = AnsibleModule(argument_spec) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + try: + cloud = shade.openstack_cloud(**module.params) + networks = cloud.search_networks(module.params['name'], + module.params['filters']) + module.exit_json(changed=False, ansible_facts=dict( + openstack_networks=networks)) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=e.message) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +if __name__ == '__main__': + main() From 40974a9811c9b4f950e79f7eafb1032ec7f94e20 Mon Sep 17 00:00:00 2001 From: dagnello Date: Sun, 20 Sep 2015 21:29:31 -0700 Subject: [PATCH 29/88] Adding new os_subnets_facts module There can be instances during an Ansible play where the list of subnets currently available from OpenStack is required. This update provides subnet list functionality as a new os_subnets_facts module. --- cloud/openstack/os_subnets_facts.py | 152 ++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 cloud/openstack/os_subnets_facts.py diff --git a/cloud/openstack/os_subnets_facts.py b/cloud/openstack/os_subnets_facts.py new file mode 100644 index 00000000000..ca94aa187f9 --- /dev/null +++ b/cloud/openstack/os_subnets_facts.py @@ -0,0 +1,152 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# This module 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. +# +# This software 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 software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +--- +module: os_subnets_facts +short_description: Retrieve facts about one or more OpenStack subnets. +version_added: "2.0" +author: "Davide Agnello (@dagnello)" +description: + - Retrieve facts about one or more subnets from OpenStack. +requirements: + - "python >= 2.6" + - "shade" +options: + subnet: + description: + - Name or ID of the subnet + required: false + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false +extends_documentation_fragment: openstack +''' + +EXAMPLES = ''' +# Gather facts about previously created subnets +- os_subnets_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject +- debug: var=openstack_subnets + +# Gather facts about a previously created subnet by name +- os_subnets_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject + name: subnet1 +- debug: var=openstack_subnets + +# Gather facts about a previously created subnet with filter (note: name and + filters parameters are Not mutually exclusive) +- os_subnets_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject + filters: + tenant_id: 55e2ce24b2a245b09f181bf025724cbe +- debug: var=openstack_subnets +''' + +RETURN = ''' +This module registers subnet details in facts named: openstack_subnets. If a +subnet name/id and or filter does not result in a subnet found, an empty list +is set in openstack_subnets. +id: + description: Unique UUID. + returned: success + type: string +name: + description: Name given to the subnet. + returned: success + type: string +network_id: + description: Network ID this subnet belongs in. + returned: success + type: string +cidr: + description: Subnet's CIDR. + returned: success + type: string +gateway_ip: + description: Subnet's gateway ip. + returned: success + type: string +enable_dhcp: + description: DHCP enable flag for this subnet. + returned: success + type: bool +ip_version: + description: IP version for this subnet. + returned: success + type: int +tenant_id: + description: Tenant id associated with this subnet. + returned: success + type: string +dns_nameservers: + description: DNS name servers for this subnet. + returned: success + type: list of strings +allocation_pools: + description: Allocation pools associated with this subnet. + returned: success + type: list of dicts +''' + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + filters=dict(required=False, default=None) + ) + module = AnsibleModule(argument_spec) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + try: + cloud = shade.openstack_cloud(**module.params) + subnets = cloud.search_subnets(module.params['name'], + module.params['filters']) + module.exit_json(changed=False, ansible_facts=dict( + openstack_subnets=subnets)) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=e.message) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +if __name__ == '__main__': + main() From 94b8ebe1bf1bd3ac3fc1460460d70f53dc7bd9d5 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Fri, 25 Sep 2015 07:47:06 -0700 Subject: [PATCH 30/88] Use is_executable from its new location --- web_infrastructure/supervisorctl.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web_infrastructure/supervisorctl.py b/web_infrastructure/supervisorctl.py index 9bc4c0b8afa..4da81ed6e0e 100644 --- a/web_infrastructure/supervisorctl.py +++ b/web_infrastructure/supervisorctl.py @@ -122,7 +122,7 @@ def main(): if supervisorctl_path: supervisorctl_path = os.path.expanduser(supervisorctl_path) - if os.path.exists(supervisorctl_path) and module.is_executable(supervisorctl_path): + if os.path.exists(supervisorctl_path) and is_executable(supervisorctl_path): supervisorctl_args = [supervisorctl_path] else: module.fail_json( @@ -239,5 +239,6 @@ def main(): # import module snippets from ansible.module_utils.basic import * - -main() +# is_executable from basic +if __name__ == '__main__': + main() From d52bb6797896056861f255a9b463436c324d381c Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Fri, 25 Sep 2015 13:09:02 -0400 Subject: [PATCH 31/88] Allow complete router configuration This change allows one to completely configure a router, including gateway and interfaces, using the latest shade (>0.13.0). --- cloud/openstack/os_router.py | 215 ++++++++++++++++++++++++++++++----- 1 file changed, 184 insertions(+), 31 deletions(-) diff --git a/cloud/openstack/os_router.py b/cloud/openstack/os_router.py index 24cbefd0c36..9be45d94ef7 100644 --- a/cloud/openstack/os_router.py +++ b/cloud/openstack/os_router.py @@ -1,7 +1,4 @@ #!/usr/bin/python - -# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. -# Copyright (c) 2013, Benno Joy # # This module is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,7 +23,7 @@ except ImportError: DOCUMENTATION = ''' --- module: os_router -short_description: Create or Delete routers from OpenStack +short_description: Create or delete routers from OpenStack extends_documentation_fragment: openstack version_added: "2.0" description: @@ -48,36 +45,147 @@ options: - Desired admin state of the created or existing router. required: false default: true + enable_snat: + description: + - Enable Source NAT (SNAT) attribute. + required: false + default: true + network: + description: + - Unique name or ID of the external gateway network. + type: string + required: true when I(interfaces) or I(enable_snat) are provided, + false otherwise. + default: None + interfaces: + description: + - List of subnets to attach to the router. Each is a dictionary with + the subnet name or ID (subnet) and the IP address to assign on that + subnet (ip). If no IP is specified, one is automatically assigned from + that subnet. + required: false + default: None requirements: ["shade"] ''' EXAMPLES = ''' -# Creates a router for tenant admin +# Create a simple router, not attached to a gateway or subnets. +- os_router: + cloud: mycloud + state: present + name: simple_router + +# Creates a router attached to ext_network1 and one subnet interface. +# An IP address from subnet1's IP range will automatically be assigned +# to that interface. - os_router: - state=present - name=router1 - admin_state_up=True + cloud: mycloud + state: present + name: router1 + network: ext_network1 + interfaces: + - subnet: subnet1 + +# Update existing router1 to include subnet2 (10.5.5.0/24), specifying +# the IP address within subnet2's IP range we'd like for that interface. +- os_router: + cloud: mycloud + state: present + name: router1 + network: ext_network1 + interfaces: + - subnet: subnet1 + - subnet: subnet2 + ip: 10.5.5.1 + +# Delete router1 +- os_router: + cloud: mycloud + state: absent + name: router1 ''' RETURN = ''' -id: - description: Router ID - returned: On success when I(state) is 'present'. - type: string +router: + description: Dictionary describing the router. + returned: On success when I(state) is 'present' + type dictionary + contains: + id: + description: Router ID. + type: string + sample: "474acfe5-be34-494c-b339-50f06aa143e4" + name: + description: Router name. + type: string + sample: "router1" + admin_state_up: + description: Administrative state of the router. + type: boolean + sample: true + status: + description: The router status. + type: string + sample: "ACTIVE" + tenant_id: + description: The tenant ID. + type: string + sample: "861174b82b43463c9edc5202aadc60ef" + external_gateway_info: + description: The external gateway parameters. + type: dictionary + sample: { + "enable_snat": true, + "external_fixed_ips": [ + { + "ip_address": "10.6.6.99", + "subnet_id": "4272cb52-a456-4c20-8f3c-c26024ecfa81" + } + } + routes: + description: The extra routes configuration for L3 router. + type: list ''' -def _needs_update(router, admin_state_up): +def _needs_update(cloud, module, router, network): """Decide if the given router needs an update. - - The only attribute of the router that we allow to change is the value - of admin_state_up. Name changes are not supported here. """ - if router['admin_state_up'] != admin_state_up: + if router['admin_state_up'] != module.params['admin_state_up']: return True + if router['external_gateway_info']['enable_snat'] != module.params['enable_snat']: + return True + if network: + if router['external_gateway_info']['network_id'] != network['id']: + return True + + # check subnet interfaces + for new_iface in module.params['interfaces']: + subnet = cloud.get_subnet(new_iface['subnet']) + if not subnet: + module.fail_json(msg='subnet %s not found' % new_iface['subnet']) + exists = False + + # compare the requested interface with existing, looking for an existing match + for existing_iface in router['external_gateway_info']['external_fixed_ips']: + if existing_iface['subnet_id'] == subnet['id']: + if 'ip' in new_iface: + if existing_iface['ip_address'] == new_iface['ip']: + # both subnet id and ip address match + exists = True + break + else: + # only the subnet was given, so ip doesn't matter + exists = True + break + + # this interface isn't present on the existing router + if not exists: + return True + return False -def _system_state_change(module, router): +def _system_state_change(cloud, module, router, network): """Check if the system state would be changed.""" state = module.params['state'] if state == 'absent' and router: @@ -85,14 +193,45 @@ def _system_state_change(module, router): if state == 'present': if not router: return True - return _needs_update(router, module.params['admin_state_up']) + return _needs_update(cloud, module, router, network) return False +def _build_kwargs(cloud, module, router, network): + kwargs = { + 'admin_state_up': module.params['admin_state_up'], + } + + if router: + kwargs['name_or_id'] = router['id'] + else: + kwargs['name'] = module.params['name'] + + if network: + kwargs['ext_gateway_net_id'] = network['id'] + # can't send enable_snat unless we have a network + kwargs['enable_snat'] = module.params['enable_snat'] + + if module.params['interfaces']: + kwargs['ext_fixed_ips'] = [] + for iface in module.params['interfaces']: + subnet = cloud.get_subnet(iface['subnet']) + if not subnet: + module.fail_json(msg='subnet %s not found' % iface['subnet']) + d = {'subnet_id': subnet['id']} + if 'ip' in iface: + d['ip_address'] = iface['ip'] + kwargs['ext_fixed_ips'].append(d) + + return kwargs + def main(): argument_spec = openstack_full_argument_spec( + state=dict(default='present', choices=['absent', 'present']), name=dict(required=True), admin_state_up=dict(type='bool', default=True), - state=dict(default='present', choices=['absent', 'present']), + enable_snat=dict(type='bool', default=True), + network=dict(default=None), + interfaces=dict(type='list', default=None) ) module_kwargs = openstack_module_kwargs() @@ -103,28 +242,42 @@ def main(): if not HAS_SHADE: module.fail_json(msg='shade is required for this module') - name = module.params['name'] - admin_state_up = module.params['admin_state_up'] state = module.params['state'] + name = module.params['name'] + network = module.params['network'] + + if module.params['interfaces'] and not network: + module.fail_json(msg='network is required when supplying interfaces') try: cloud = shade.openstack_cloud(**module.params) router = cloud.get_router(name) + net = None + if network: + net = cloud.get_network(network) + if not net: + module.fail_json(msg='network %s not found' % network) + if module.check_mode: - module.exit_json(changed=_system_state_change(module, router)) + module.exit_json( + changed=_system_state_change(cloud, module, router, net) + ) if state == 'present': + changed = False + if not router: - router = cloud.create_router(name, admin_state_up) - module.exit_json(changed=True, id=router['id']) + kwargs = _build_kwargs(cloud, module, router, net) + router = cloud.create_router(**kwargs) + changed = True else: - if _needs_update(router, admin_state_up): - cloud.update_router(router['id'], - admin_state_up=admin_state_up) - module.exit_json(changed=True, id=router['id']) - else: - module.exit_json(changed=False, id=router['id']) + if _needs_update(cloud, module, router, net): + kwargs = _build_kwargs(cloud, module, router, net) + router = cloud.update_router(**kwargs) + changed = True + + module.exit_json(changed=changed, router=router) elif state == 'absent': if not router: From 750a91520f15c188b32ea5de140e7184f395d240 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Fri, 25 Sep 2015 14:58:47 -0400 Subject: [PATCH 32/88] Allow setting external attribute With shade > 0.13.0, networks can be created that are externally accessible. This adds a parameter for that. Also, add RETURN documentation and 'if __name__' check around call to main(). --- cloud/openstack/os_network.py | 74 ++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/cloud/openstack/os_network.py b/cloud/openstack/os_network.py index f911ce71af1..bc41d3870f4 100644 --- a/cloud/openstack/os_network.py +++ b/cloud/openstack/os_network.py @@ -25,12 +25,12 @@ except ImportError: DOCUMENTATION = ''' --- module: os_network -short_description: Creates/Removes networks from OpenStack +short_description: Creates/removes networks from OpenStack extends_documentation_fragment: openstack version_added: "2.0" author: "Monty Taylor (@emonty)" description: - - Add or Remove network from OpenStack. + - Add or remove network from OpenStack. options: name: description: @@ -46,6 +46,11 @@ options: - Whether the state should be marked as up or down. required: false default: true + external: + description: + - Whether this network is externally accessible. + required: false + default: false state: description: - Indicate desired state of the resource. @@ -56,14 +61,60 @@ requirements: ["shade"] ''' EXAMPLES = ''' +# Create an externally accessible network named 'ext_network'. - os_network: - name: t1network + cloud: mycloud state: present - auth: - auth_url: https://your_api_url.com:9000/v2.0 - username: user - password: password - project_name: someproject + name: ext_network + external: true +''' + +RETURN = ''' +network: + description: Dictionary describing the network. + returned: On success when I(state) is 'present'. + type: dictionary + contains: + id: + description: Network ID. + type: string + sample: "4bb4f9a5-3bd2-4562-bf6a-d17a6341bb56" + name: + description: Network name. + type: string + sample: "ext_network" + shared: + description: Indicates whether this network is shared across all tenants. + type: bool + sample: false + status: + description: Network status. + type: string + sample: "ACTIVE" + mtu: + description: The MTU of a network resource. + type: integer + sample: 0 + admin_state_up: + description: The administrative state of the network. + type: bool + sample: true + port_security_enabled: + description: The port security status + type: bool + sample: true + router:external: + description: Indicates whether this network is externally accessible. + type: bool + sample: true + tenant_id: + description: The tenant ID. + type: string + sample: "06820f94b9f54b119636be2728d216fc" + subnets: + description: The associated subnets. + type: list + sample: [] ''' @@ -72,6 +123,7 @@ def main(): name=dict(required=True), shared=dict(default=False, type='bool'), admin_state_up=dict(default=True, type='bool'), + external=dict(default=False, type='bool'), state=dict(default='present', choices=['absent', 'present']), ) @@ -85,6 +137,7 @@ def main(): name = module.params['name'] shared = module.params['shared'] admin_state_up = module.params['admin_state_up'] + external = module.params['external'] try: cloud = shade.openstack_cloud(**module.params) @@ -92,7 +145,7 @@ def main(): if state == 'present': if not net: - net = cloud.create_network(name, shared, admin_state_up) + net = cloud.create_network(name, shared, admin_state_up, external) module.exit_json(changed=False, network=net, id=net['id']) elif state == 'absent': @@ -109,4 +162,5 @@ def main(): # this is magic, see lib/ansible/module_common.py from ansible.module_utils.basic import * from ansible.module_utils.openstack import * -main() +if __name__ == "__main__": + main() From e25605cd5bca003a5071aebbdaeb2887e8e5c659 Mon Sep 17 00:00:00 2001 From: Jens Carl Date: Fri, 25 Sep 2015 21:59:14 +0000 Subject: [PATCH 33/88] Fix to handle user directory within parameter 'template'. --- cloud/amazon/cloudformation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cloud/amazon/cloudformation.py b/cloud/amazon/cloudformation.py index 236bc89000d..d754ee4bf24 100644 --- a/cloud/amazon/cloudformation.py +++ b/cloud/amazon/cloudformation.py @@ -146,6 +146,7 @@ EXAMPLES = ''' ''' import json +import os import time import yaml @@ -269,7 +270,7 @@ def main(): module.fail_json('Module parameter "template" or "template_url" is required if "state" is "present"') if module.params['template'] is not None: - template_body = open(module.params['template'], 'r').read() + template_body = open(os.path.expanduser(module.params['template']), 'r').read() else: template_body = None From d5f62798f09defbf65f0b682e909c0b946a0c163 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 28 Sep 2015 08:22:36 -0700 Subject: [PATCH 34/88] Docs fixes --- cloud/amazon/ec2_ami.py | 2 +- cloud/rackspace/rax_scaling_group.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cloud/amazon/ec2_ami.py b/cloud/amazon/ec2_ami.py index 016df7e6e55..60de91ebb72 100644 --- a/cloud/amazon/ec2_ami.py +++ b/cloud/amazon/ec2_ami.py @@ -73,7 +73,7 @@ options: version_added: "2.0" description: - An optional list of device hashes/dictionaries with custom configurations (same block-device-mapping parameters) - - Valid properties include: device_name, volume_type, size (in GB), delete_on_termination (boolean), no_device (boolean), snapshot_id, iops (for io1 volume_type) + - "Valid properties include: device_name, volume_type, size (in GB), delete_on_termination (boolean), no_device (boolean), snapshot_id, iops (for io1 volume_type)" required: false default: null delete_snapshot: diff --git a/cloud/rackspace/rax_scaling_group.py b/cloud/rackspace/rax_scaling_group.py index bdf12a79cbe..79b7395f400 100644 --- a/cloud/rackspace/rax_scaling_group.py +++ b/cloud/rackspace/rax_scaling_group.py @@ -105,7 +105,7 @@ options: - Data to be uploaded to the servers config drive. This option implies I(config_drive). Can be a file path or a string version_added: 1.8 - wait + wait: description: - wait for the scaling group to finish provisioning the minimum amount of servers @@ -117,7 +117,7 @@ options: description: - how long before wait gives up, in seconds default: 300 -author: Matt Martz +author: "Matt Martz (@sivel)" extends_documentation_fragment: rackspace ''' From 1857263b63e56feecf820b7990b7bffebfef14aa Mon Sep 17 00:00:00 2001 From: Gerard Lynch Date: Mon, 28 Sep 2015 17:02:46 +0100 Subject: [PATCH 35/88] allow use of volume_type in volumes dict --- cloud/amazon/ec2.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cloud/amazon/ec2.py b/cloud/amazon/ec2.py index 0b0d3c91127..6572a9286f4 100644 --- a/cloud/amazon/ec2.py +++ b/cloud/amazon/ec2.py @@ -216,7 +216,7 @@ options: volumes: version_added: "1.5" description: - - "a list of volume dicts, each containing device name and optionally ephemeral id or snapshot id. Size and type (and number of iops for io device type) must be specified for a new volume or a root volume, and may be passed for a snapshot volume. For any volume, a volume size less than 1 will be interpreted as a request not to create the volume. Encrypt the volume by passing 'encrypted: true' in the volume dict." + - a list of hash/dictionaries of volumes to add to the new instance; '[{"key":"value", "key":"value"}]'; keys allowed are - device_name (str; required), delete_on_termination (bool; False), device_type (deprecated), ephemeral (str), encrypted (bool; False), snapshot (str), volume_type (str), iops (int) - device_type is deprecated use volume_type, iops must be set when volume_type='io1', ephemeral and snapshot are mutually exclusive. required: false default: null aliases: [] @@ -295,7 +295,7 @@ EXAMPLES = ''' volumes: - device_name: /dev/sdb snapshot: snap-abcdef12 - device_type: io1 + volume_type: io1 iops: 1000 volume_size: 100 delete_on_termination: true @@ -710,11 +710,21 @@ def create_block_device(module, ec2, volume): # Not aware of a way to determine this programatically # http://aws.amazon.com/about-aws/whats-new/2013/10/09/ebs-provisioned-iops-maximum-iops-gb-ratio-increased-to-30-1/ MAX_IOPS_TO_SIZE_RATIO = 30 + + # device_type has been used historically to represent volume_type, + # however ec2_vol uses volume_type, as does the BlockDeviceType, so + # we add handling for either/or but not both + if all(key in volume for key in ['device_type','volume_type']): + module.fail_json(msg = 'device_type is a deprecated name for volume_type. Do not use both device_type and volume_type') + + # get whichever one is set, or NoneType if neither are set + volume_type = volume.get('device_type') or volume.get('volume_type') + if 'snapshot' not in volume and 'ephemeral' not in volume: if 'volume_size' not in volume: module.fail_json(msg = 'Size must be specified when creating a new volume or modifying the root volume') if 'snapshot' in volume: - if 'device_type' in volume and volume.get('device_type') == 'io1' and 'iops' not in volume: + if volume_type == 'io1' and 'iops' not in volume: module.fail_json(msg = 'io1 volumes must have an iops value set') if 'iops' in volume: snapshot = ec2.get_all_snapshots(snapshot_ids=[volume['snapshot']])[0] @@ -729,10 +739,11 @@ def create_block_device(module, ec2, volume): return BlockDeviceType(snapshot_id=volume.get('snapshot'), ephemeral_name=volume.get('ephemeral'), size=volume.get('volume_size'), - volume_type=volume.get('device_type'), + volume_type=volume_type, delete_on_termination=volume.get('delete_on_termination', False), iops=volume.get('iops'), encrypted=volume.get('encrypted', None)) + def boto_supports_param_in_spot_request(ec2, param): """ Check if Boto library has a in its request_spot_instances() method. For example, the placement_group parameter wasn't added until 2.3.0. From 841835ebac825a564d5ea109bd0bd7b296c60513 Mon Sep 17 00:00:00 2001 From: Shayne Clausson Date: Mon, 28 Sep 2015 19:50:02 +0200 Subject: [PATCH 36/88] Replaces 'old' get_ec2_creds connection method with get_aws_connection_info to support passing in security_token for temporary creds. --- cloud/amazon/iam_cert.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cloud/amazon/iam_cert.py b/cloud/amazon/iam_cert.py index 1f58be753c8..53b2368bd30 100644 --- a/cloud/amazon/iam_cert.py +++ b/cloud/amazon/iam_cert.py @@ -241,13 +241,10 @@ def main(): 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) + region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module) try: - iam = boto.iam.connection.IAMConnection( - aws_access_key_id=aws_access_key, - aws_secret_access_key=aws_secret_key, - ) + iam = boto.iam.connection.IAMConnection(**aws_connect_kwargs) except boto.exception.NoAuthHandlerFound, e: module.fail_json(msg=str(e)) From dc109387bdf7331d5022c27f2c4e1a99835c7b91 Mon Sep 17 00:00:00 2001 From: Jens Carl Date: Mon, 28 Sep 2015 18:25:23 +0000 Subject: [PATCH 37/88] Change type of parameter 'template' to 'path'. --- cloud/amazon/cloudformation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cloud/amazon/cloudformation.py b/cloud/amazon/cloudformation.py index d754ee4bf24..7c9f48148ce 100644 --- a/cloud/amazon/cloudformation.py +++ b/cloud/amazon/cloudformation.py @@ -146,7 +146,6 @@ EXAMPLES = ''' ''' import json -import os import time import yaml @@ -242,7 +241,7 @@ def main(): stack_name=dict(required=True), template_parameters=dict(required=False, type='dict', default={}), state=dict(default='present', choices=['present', 'absent']), - template=dict(default=None, required=False), + template=dict(default=None, required=False, type='path'), notification_arns=dict(default=None, required=False), stack_policy=dict(default=None, required=False), disable_rollback=dict(default=False, type='bool'), @@ -270,7 +269,7 @@ def main(): module.fail_json('Module parameter "template" or "template_url" is required if "state" is "present"') if module.params['template'] is not None: - template_body = open(os.path.expanduser(module.params['template']), 'r').read() + template_body = open(module.params['template'], 'r').read() else: template_body = None From d8f9c81022014eecc0d9a9366100765de0c8520c Mon Sep 17 00:00:00 2001 From: Matt Calhoun Date: Tue, 29 Sep 2015 16:35:47 -0400 Subject: [PATCH 38/88] The variable $winrm_https_listener_path cannot be retrieved because it has not been set --- windows/setup.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/setup.ps1 b/windows/setup.ps1 index 3e3317d0450..91100912e44 100644 --- a/windows/setup.ps1 +++ b/windows/setup.ps1 @@ -77,6 +77,7 @@ $psversion = $PSVersionTable.PSVersion.Major Set-Attr $result.ansible_facts "ansible_powershell_version" $psversion $winrm_https_listener_parent_path = Get-ChildItem -Path WSMan:\localhost\Listener -Recurse | Where-Object {$_.PSChildName -eq "Transport" -and $_.Value -eq "HTTPS"} | select PSParentPath +$winrm_https_listener_path = $null if ($winrm_https_listener_parent_path ) { $winrm_https_listener_path = $winrm_https_listener_parent_path.PSParentPath.Substring($winrm_https_listener_parent_path.PSParentPath.LastIndexOf("\")) From 1d8334674f14bba79b491e139b05eda7ac64c177 Mon Sep 17 00:00:00 2001 From: Matt Calhoun Date: Tue, 29 Sep 2015 16:37:26 -0400 Subject: [PATCH 39/88] The variable $https_listener cannot be retrieved because it has not been set. --- windows/setup.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/windows/setup.ps1 b/windows/setup.ps1 index 91100912e44..61b36b27fa4 100644 --- a/windows/setup.ps1 +++ b/windows/setup.ps1 @@ -78,6 +78,8 @@ Set-Attr $result.ansible_facts "ansible_powershell_version" $psversion $winrm_https_listener_parent_path = Get-ChildItem -Path WSMan:\localhost\Listener -Recurse | Where-Object {$_.PSChildName -eq "Transport" -and $_.Value -eq "HTTPS"} | select PSParentPath $winrm_https_listener_path = $null +$https_listener = $null + if ($winrm_https_listener_parent_path ) { $winrm_https_listener_path = $winrm_https_listener_parent_path.PSParentPath.Substring($winrm_https_listener_parent_path.PSParentPath.LastIndexOf("\")) From 43501880769bf3d75a21e3e6a7c3e4155c2e9557 Mon Sep 17 00:00:00 2001 From: Matt Calhoun Date: Tue, 29 Sep 2015 16:38:48 -0400 Subject: [PATCH 40/88] The variable $winrm_cert_thumbprint cannot be retrieved because it has not been set. --- windows/setup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/setup.ps1 b/windows/setup.ps1 index 61b36b27fa4..06c160fa288 100644 --- a/windows/setup.ps1 +++ b/windows/setup.ps1 @@ -79,7 +79,7 @@ Set-Attr $result.ansible_facts "ansible_powershell_version" $psversion $winrm_https_listener_parent_path = Get-ChildItem -Path WSMan:\localhost\Listener -Recurse | Where-Object {$_.PSChildName -eq "Transport" -and $_.Value -eq "HTTPS"} | select PSParentPath $winrm_https_listener_path = $null $https_listener = $null - +$winrm_cert_thumbprint = $null if ($winrm_https_listener_parent_path ) { $winrm_https_listener_path = $winrm_https_listener_parent_path.PSParentPath.Substring($winrm_https_listener_parent_path.PSParentPath.LastIndexOf("\")) From 73843693b79e20047da6a1e8f5b4734009ce05d0 Mon Sep 17 00:00:00 2001 From: Matt Calhoun Date: Tue, 29 Sep 2015 16:40:22 -0400 Subject: [PATCH 41/88] The variable $uppercase_cert_thumbprint cannot be retrieved because it has not been set. --- windows/setup.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/setup.ps1 b/windows/setup.ps1 index 06c160fa288..a6f439d7338 100644 --- a/windows/setup.ps1 +++ b/windows/setup.ps1 @@ -80,6 +80,7 @@ $winrm_https_listener_parent_path = Get-ChildItem -Path WSMan:\localhost\Listene $winrm_https_listener_path = $null $https_listener = $null $winrm_cert_thumbprint = $null +$uppercase_cert_thumbprint = $null if ($winrm_https_listener_parent_path ) { $winrm_https_listener_path = $winrm_https_listener_parent_path.PSParentPath.Substring($winrm_https_listener_parent_path.PSParentPath.LastIndexOf("\")) From 4308ae25c489f8f3be2993546ed7474c8b03a0ea Mon Sep 17 00:00:00 2001 From: Alex Kessinger Date: Tue, 29 Sep 2015 16:36:27 -0700 Subject: [PATCH 42/88] Fix a argument mismatch in elasticache I think in this commit 720aeffca2bd2ae1eca158abc2d1463a8597afb6 There was bug introduced where the ElastiCacheManager init method has a number of positional arguments like so. ```py def __init__(self, module, name, engine, cache_engine_version, node_type, num_nodes, cache_port, parameter_group, cache_subnet_group, cache_security_groups, security_group_ids, zone, wait, hard_modify, region, **aws_connect_kwargs): ``` But then later in the code the positional arguments are passed in like this. ```py elasticache_manager = ElastiCacheManager(module, name, engine, cache_engine_version, node_type, num_nodes, cache_port, cache_subnet_group, cache_security_groups, security_group_ids, parameter_group, zone, wait, hard_modify, region, **aws_connect_kwargs) ``` If you count, you can see that cache_subnet_group, is being passed in where the manager expects to see parameter_group. --- cloud/amazon/elasticache.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cloud/amazon/elasticache.py b/cloud/amazon/elasticache.py index 5e5d3b6ee96..11b33f3e241 100644 --- a/cloud/amazon/elasticache.py +++ b/cloud/amazon/elasticache.py @@ -445,6 +445,7 @@ class ElastiCacheManager(object): def _refresh_data(self, cache_cluster_data=None): """Refresh data about this cache cluster""" + if cache_cluster_data is None: try: response = self.conn.describe_cache_clusters(cache_cluster_id=self.name, @@ -542,9 +543,10 @@ def main(): elasticache_manager = ElastiCacheManager(module, name, engine, cache_engine_version, node_type, num_nodes, cache_port, + parameter_group, cache_subnet_group, cache_security_groups, - security_group_ids, parameter_group, zone, wait, + security_group_ids, zone, wait, hard_modify, region, **aws_connect_kwargs) if state == 'present': From da220e0bb0bf7d809c330c1b5fcf6038e652b28a Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Mon, 21 Sep 2015 08:34:56 -0400 Subject: [PATCH 43/88] added version_added --- cloud/amazon/elasticache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/amazon/elasticache.py b/cloud/amazon/elasticache.py index 11b33f3e241..9f9cc93aa9f 100644 --- a/cloud/amazon/elasticache.py +++ b/cloud/amazon/elasticache.py @@ -62,6 +62,7 @@ options: - Specify non-default parameter group names to be associated with cache cluster required: false default: None + version_added: "2.0" cache_subnet_group: description: - The subnet group name to associate with. Only use if inside a vpc. Required if inside a vpc From 3e42527fdec96154436543e155befc0159f8dc12 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 30 Sep 2015 23:59:48 -0400 Subject: [PATCH 44/88] revert must_exist --- system/service.py | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/system/service.py b/system/service.py index 4255ecb83ab..c80154724c6 100644 --- a/system/service.py +++ b/system/service.py @@ -74,14 +74,6 @@ options: description: - Additional arguments provided on the command line aliases: [ 'args' ] - must_exist: - required: false - default: true - version_added: "2.0" - description: - - Avoid a module failure if the named service does not exist. Useful - for opportunistically starting/stopping/restarting a list of - potential services. ''' EXAMPLES = ''' @@ -106,8 +98,6 @@ EXAMPLES = ''' # Example action to restart network service for interface eth0 - service: name=network state=restarted args=eth0 -# Example action to restart nova-compute if it exists -- service: name=nova-compute state=restarted must_exist=no ''' import platform @@ -481,11 +471,8 @@ class LinuxService(Service): self.enable_cmd = location['chkconfig'] if self.enable_cmd is None: - if self.module.params['must_exist']: - self.module.fail_json(msg="no service or tool found for: %s" % self.name) - else: - # exiting without change on non-existent service - self.module.exit_json(changed=False, exists=False) + # exiting without change on non-existent service + self.module.exit_json(changed=False, exists=False) # If no service control tool selected yet, try to see if 'service' is available if self.svc_cmd is None and location.get('service', False): @@ -493,11 +480,7 @@ class LinuxService(Service): # couldn't find anything yet if self.svc_cmd is None and not self.svc_initscript: - if self.module.params['must_exist']: - self.module.fail_json(msg='cannot find \'service\' binary or init script for service, possible typo in service name?, aborting') - else: - # exiting without change on non-existent service - self.module.exit_json(changed=False, exists=False) + self.module.exit_json(changed=False, exists=False) if location.get('initctl', False): self.svc_initctl = location['initctl'] @@ -1442,7 +1425,6 @@ def main(): enabled = dict(type='bool'), runlevel = dict(required=False, default='default'), arguments = dict(aliases=['args'], default=''), - must_exist = dict(type='bool', default=True), ), supports_check_mode=True ) @@ -1527,4 +1509,5 @@ def main(): module.exit_json(**result) from ansible.module_utils.basic import * + main() From f6bbd2ac5be00f76f26ae9e3cd91629841ef40e8 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Thu, 1 Oct 2015 00:12:54 -0400 Subject: [PATCH 45/88] removed syslog in favor of common module logging functions --- packaging/os/rpm_key.py | 5 ----- packaging/os/yum.py | 5 ----- system/cron.py | 14 +++----------- system/group.py | 14 +++----------- system/service.py | 14 +++----------- system/user.py | 16 +++------------- 6 files changed, 12 insertions(+), 56 deletions(-) diff --git a/packaging/os/rpm_key.py b/packaging/os/rpm_key.py index 1d2d208e4be..dde35df7aad 100644 --- a/packaging/os/rpm_key.py +++ b/packaging/os/rpm_key.py @@ -61,7 +61,6 @@ EXAMPLES = ''' - rpm_key: state=absent key=DEADB33F ''' import re -import syslog import os.path import urllib2 import tempfile @@ -74,7 +73,6 @@ def is_pubkey(string): class RpmKey: def __init__(self, module): - self.syslogging = False # If the key is a url, we need to check if it's present to be idempotent, # to do that, we need to check the keyid, which we can get from the armor. keyfile = None @@ -163,9 +161,6 @@ class RpmKey: return re.match('(0x)?[0-9a-f]{8}', keystr, flags=re.IGNORECASE) def execute_command(self, cmd): - if self.syslogging: - syslog.openlog('ansible-%s' % os.path.basename(__file__)) - syslog.syslog(syslog.LOG_NOTICE, 'Command %s' % '|'.join(cmd)) rc, stdout, stderr = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg=stderr) diff --git a/packaging/os/yum.py b/packaging/os/yum.py index c66e73ad98b..b54df227973 100644 --- a/packaging/os/yum.py +++ b/packaging/os/yum.py @@ -26,7 +26,6 @@ import traceback import os import yum import rpm -import syslog import platform import tempfile import shutil @@ -169,10 +168,6 @@ BUFSIZE = 65536 def_qf = "%{name}-%{version}-%{release}.%{arch}" -def log(msg): - syslog.openlog('ansible-yum', 0, syslog.LOG_USER) - syslog.syslog(syslog.LOG_NOTICE, msg) - def yum_base(conf_file=None): my = yum.YumBase() diff --git a/system/cron.py b/system/cron.py index 0415fb252a2..3b842dfefb6 100644 --- a/system/cron.py +++ b/system/cron.py @@ -178,9 +178,6 @@ class CronTab(object): self.lines = None self.ansible = "#Ansible: " - # select whether we dump additional debug info through syslog - self.syslogging = False - if cron_file: self.cron_file = '/etc/cron.d/%s' % cron_file else: @@ -218,10 +215,6 @@ class CronTab(object): self.lines.append(l) count += 1 - def log_message(self, message): - if self.syslogging: - syslog.syslog(syslog.LOG_NOTICE, 'ansible: "%s"' % message) - def is_empty(self): if len(self.lines) == 0: return True @@ -457,10 +450,8 @@ def main(): # Ensure all files generated are only writable by the owning user. Primarily relevant for the cron_file option. os.umask(022) crontab = CronTab(module, user, cron_file) - - if crontab.syslogging: - syslog.openlog('ansible-%s' % os.path.basename(__file__)) - syslog.syslog(syslog.LOG_NOTICE, 'cron instantiated - name: "%s"' % name) +] + module.debug('cron instantiated - name: "%s"' % name) # --- user input validation --- @@ -495,6 +486,7 @@ def main(): (backuph, backup_file) = tempfile.mkstemp(prefix='crontab') crontab.write(backup_file) + if crontab.cron_file and not name and not do_install: changed = crontab.remove_job_file() module.exit_json(changed=changed,cron_file=cron_file,state=state) diff --git a/system/group.py b/system/group.py index ab542d9bc47..b6c1178ff0d 100644 --- a/system/group.py +++ b/system/group.py @@ -57,7 +57,6 @@ EXAMPLES = ''' ''' import grp -import syslog import platform class Group(object): @@ -86,13 +85,8 @@ class Group(object): self.name = module.params['name'] self.gid = module.params['gid'] self.system = module.params['system'] - self.syslogging = False def execute_command(self, cmd): - if self.syslogging: - syslog.openlog('ansible-%s' % os.path.basename(__file__)) - syslog.syslog(syslog.LOG_NOTICE, 'Command %s' % '|'.join(cmd)) - return self.module.run_command(cmd) def group_del(self): @@ -395,11 +389,9 @@ def main(): group = Group(module) - if group.syslogging: - syslog.openlog('ansible-%s' % os.path.basename(__file__)) - syslog.syslog(syslog.LOG_NOTICE, 'Group instantiated - platform %s' % group.platform) - if user.distribution: - syslog.syslog(syslog.LOG_NOTICE, 'Group instantiated - distribution %s' % group.distribution) + module.debug('Group instantiated - platform %s' % group.platform) + if user.distribution: + module.debug('Group instantiated - distribution %s' % group.distribution) rc = None out = '' diff --git a/system/service.py b/system/service.py index c80154724c6..f9a8b1e24c1 100644 --- a/system/service.py +++ b/system/service.py @@ -159,9 +159,6 @@ class Service(object): self.rcconf_value = None self.svc_change = False - # select whether we dump additional debug info through syslog - self.syslogging = False - # =========================================== # Platform specific methods (must be replaced by subclass). @@ -181,9 +178,6 @@ class Service(object): # Generic methods that should be used on all platforms. def execute_command(self, cmd, daemonize=False): - if self.syslogging: - syslog.openlog('ansible-%s' % os.path.basename(__file__)) - syslog.syslog(syslog.LOG_NOTICE, 'Command %s, daemonize %r' % (cmd, daemonize)) # Most things don't need to be daemonized if not daemonize: @@ -1433,11 +1427,9 @@ def main(): service = Service(module) - if service.syslogging: - syslog.openlog('ansible-%s' % os.path.basename(__file__)) - syslog.syslog(syslog.LOG_NOTICE, 'Service instantiated - platform %s' % service.platform) - if service.distribution: - syslog.syslog(syslog.LOG_NOTICE, 'Service instantiated - distribution %s' % service.distribution) + module.debug('Service instantiated - platform %s' % service.platform) + if service.distribution: + module.debug('Service instantiated - distribution %s' % service.distribution) rc = 0 out = '' diff --git a/system/user.py b/system/user.py index 5f954694e40..499228953b2 100755 --- a/system/user.py +++ b/system/user.py @@ -212,7 +212,6 @@ EXAMPLES = ''' import os import pwd import grp -import syslog import platform import socket import time @@ -290,15 +289,8 @@ class User(object): else: self.ssh_file = os.path.join('.ssh', 'id_%s' % self.ssh_type) - # select whether we dump additional debug info through syslog - self.syslogging = False - def execute_command(self, cmd, use_unsafe_shell=False, data=None): - if self.syslogging: - syslog.openlog('ansible-%s' % os.path.basename(__file__)) - syslog.syslog(syslog.LOG_NOTICE, 'Command %s' % '|'.join(cmd)) - return self.module.run_command(cmd, use_unsafe_shell=use_unsafe_shell, data=data) def remove_user_userdel(self): @@ -2079,11 +2071,9 @@ def main(): user = User(module) - if user.syslogging: - syslog.openlog('ansible-%s' % os.path.basename(__file__)) - syslog.syslog(syslog.LOG_NOTICE, 'User instantiated - platform %s' % user.platform) - if user.distribution: - syslog.syslog(syslog.LOG_NOTICE, 'User instantiated - distribution %s' % user.distribution) + module.debug('User instantiated - platform %s' % user.platform) + if user.distribution: + module.debug('User instantiated - distribution %s' % user.distribution) rc = None out = '' From a15aa09251487a4cb3448182acec370d8259cb2c Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Thu, 1 Oct 2015 10:16:34 -0400 Subject: [PATCH 46/88] removed typo --- system/cron.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/cron.py b/system/cron.py index 3b842dfefb6..57c7fd40836 100644 --- a/system/cron.py +++ b/system/cron.py @@ -450,7 +450,7 @@ def main(): # Ensure all files generated are only writable by the owning user. Primarily relevant for the cron_file option. os.umask(022) crontab = CronTab(module, user, cron_file) -] + module.debug('cron instantiated - name: "%s"' % name) # --- user input validation --- From 79d18981c7be1c5c617d3d2278d3e45f86c08577 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Fri, 2 Oct 2015 00:48:04 -0400 Subject: [PATCH 47/88] A few bug fixes and tweaks for the accelerate module --- utilities/helper/accelerate.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/utilities/helper/accelerate.py b/utilities/helper/accelerate.py index 26ce36bcc7e..e9e3b2ef5f6 100644 --- a/utilities/helper/accelerate.py +++ b/utilities/helper/accelerate.py @@ -272,6 +272,7 @@ class LocalSocketThread(Thread): pass def terminate(self): + super(LocalSocketThread, self).terminate() self.terminated = True self.s.shutdown(socket.SHUT_RDWR) self.s.close() @@ -311,7 +312,6 @@ class ThreadedTCPServer(SocketServer.ThreadingTCPServer): SocketServer.ThreadingTCPServer.__init__(self, server_address, RequestHandlerClass) def shutdown(self): - self.local_thread.terminate() self.running = False SocketServer.ThreadingTCPServer.shutdown(self) @@ -599,15 +599,14 @@ def daemonize(module, password, port, timeout, minutes, use_ipv6, pid_file): server.shutdown() else: # reschedule the check - vvvv("daemon idle for %d seconds (timeout=%d)" % (total_seconds,minutes*60)) - signal.alarm(30) + signal.alarm(1) except: pass finally: server.last_event_lock.release() signal.signal(signal.SIGALRM, timer_handler) - signal.alarm(30) + signal.alarm(1) tries = 5 while tries > 0: From 08e91ef68f1d83e9c0d5d6aae1b56b78e5504fc4 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Fri, 2 Oct 2015 09:19:55 -0400 Subject: [PATCH 48/88] Deprecate older router modules. The quantum_router_gateway.py and quantum_router_interface.py modules are deprecated with this change. --- .../{quantum_router_gateway.py => _quantum_router_gateway.py} | 1 + ...{quantum_router_interface.py => _quantum_router_interface.py} | 1 + 2 files changed, 2 insertions(+) rename cloud/openstack/{quantum_router_gateway.py => _quantum_router_gateway.py} (99%) rename cloud/openstack/{quantum_router_interface.py => _quantum_router_interface.py} (99%) diff --git a/cloud/openstack/quantum_router_gateway.py b/cloud/openstack/_quantum_router_gateway.py similarity index 99% rename from cloud/openstack/quantum_router_gateway.py rename to cloud/openstack/_quantum_router_gateway.py index 48248662ed7..891cee55a09 100644 --- a/cloud/openstack/quantum_router_gateway.py +++ b/cloud/openstack/_quantum_router_gateway.py @@ -31,6 +31,7 @@ DOCUMENTATION = ''' module: quantum_router_gateway version_added: "1.2" author: "Benno Joy (@bennojoy)" +deprecated: Deprecated in 2.0. Use os_router instead short_description: set/unset a gateway interface for the router with the specified external network description: - Creates/Removes a gateway interface from the router, used to associate a external network with a router to route external traffic. diff --git a/cloud/openstack/quantum_router_interface.py b/cloud/openstack/_quantum_router_interface.py similarity index 99% rename from cloud/openstack/quantum_router_interface.py rename to cloud/openstack/_quantum_router_interface.py index 7374b542390..4073c7d3b10 100644 --- a/cloud/openstack/quantum_router_interface.py +++ b/cloud/openstack/_quantum_router_interface.py @@ -31,6 +31,7 @@ DOCUMENTATION = ''' module: quantum_router_interface version_added: "1.2" author: "Benno Joy (@bennojoy)" +deprecated: Deprecated in 2.0. Use os_router instead short_description: Attach/Dettach a subnet's interface to a router description: - Attach/Dettach a subnet interface to a router, to provide a gateway for the subnet. From aa1e8b8b055a2e8d804a8895cdf370f550be94db Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Fri, 2 Oct 2015 09:41:18 -0400 Subject: [PATCH 49/88] Add author to os_router --- cloud/openstack/os_router.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/openstack/os_router.py b/cloud/openstack/os_router.py index 9be45d94ef7..007201be193 100644 --- a/cloud/openstack/os_router.py +++ b/cloud/openstack/os_router.py @@ -26,6 +26,7 @@ module: os_router short_description: Create or delete routers from OpenStack extends_documentation_fragment: openstack version_added: "2.0" +author: "David Shrewsbury (@Shrews)" description: - Create or Delete routers from OpenStack. Although Neutron allows routers to share the same name, this module enforces name uniqueness From dbc860daaa466d97550d969536991105f0d3e826 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Fri, 2 Oct 2015 09:21:29 -0700 Subject: [PATCH 50/88] Fix docs build --- cloud/openstack/os_router.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cloud/openstack/os_router.py b/cloud/openstack/os_router.py index 007201be193..3d4218d2d14 100644 --- a/cloud/openstack/os_router.py +++ b/cloud/openstack/os_router.py @@ -110,7 +110,7 @@ RETURN = ''' router: description: Dictionary describing the router. returned: On success when I(state) is 'present' - type dictionary + type: dictionary contains: id: description: Router ID. @@ -142,6 +142,7 @@ router: "ip_address": "10.6.6.99", "subnet_id": "4272cb52-a456-4c20-8f3c-c26024ecfa81" } + ] } routes: description: The extra routes configuration for L3 router. From d616fd12dae71842ba472d363e1fb5a15efb723b Mon Sep 17 00:00:00 2001 From: Simon Li Date: Sat, 3 Oct 2015 01:09:03 +0100 Subject: [PATCH 51/88] Fix incorrect variable in group.py: user -> group --- system/group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/group.py b/system/group.py index b6c1178ff0d..8edb93a1d0a 100644 --- a/system/group.py +++ b/system/group.py @@ -390,7 +390,7 @@ def main(): group = Group(module) module.debug('Group instantiated - platform %s' % group.platform) - if user.distribution: + if group.distribution: module.debug('Group instantiated - distribution %s' % group.distribution) rc = None From c2520e6b6718fe7f807a3e620b2cf35c40e27b23 Mon Sep 17 00:00:00 2001 From: Matias De Carli Date: Sat, 3 Oct 2015 13:48:10 -0300 Subject: [PATCH 52/88] keep backwards compatibility --- cloud/azure/azure.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/cloud/azure/azure.py b/cloud/azure/azure.py index 32442be1930..01a6240cf87 100644 --- a/cloud/azure/azure.py +++ b/cloud/azure/azure.py @@ -249,22 +249,29 @@ AZURE_ROLE_SIZES = ['ExtraSmall', 'Standard_G4', 'Standard_G5'] +from distutils.version import LooseVersion + try: import azure as windows_azure - from azure.common import AzureException, AzureMissingResourceHttpError + if hasattr(windows_azure, '__version__') and LooseVersion(windows_azure.__version__) <= "0.11.1": + from azure import WindowsAzureError as AzureException + from azure import WindowsAzureMissingResourceError as AzureMissingException + else: + from azure.common import AzureException as AzureException + from azure.common import AzureMissingResourceHttpError as AzureMissingException + from azure.servicemanagement import (ServiceManagementService, OSVirtualHardDisk, SSH, PublicKeys, PublicKey, LinuxConfigurationSet, ConfigurationSetInputEndpoints, ConfigurationSetInputEndpoint, Listener, WindowsConfigurationSet) + HAS_AZURE = True except ImportError: HAS_AZURE = False -from distutils.version import LooseVersion from types import MethodType import json - def _wait_for_completion(azure, promise, wait_timeout, msg): if not promise: return wait_timeout = time.time() + wait_timeout @@ -353,7 +360,7 @@ def create_virtual_machine(module, azure): try: # check to see if a vm with this name exists; if so, do nothing azure.get_role(name, name, name) - except AzureMissingResourceHttpError: + except AzureMissingException: # vm does not exist; create it if os_type == 'linux': @@ -453,7 +460,7 @@ def terminate_virtual_machine(module, azure): disk_names = [] try: deployment = azure.get_deployment_by_name(service_name=name, deployment_name=name) - except AzureMissingResourceHttpError, e: + except AzureMissingException, e: pass # no such deployment or service except AzureException, e: module.fail_json(msg="failed to find the deployment, error was: %s" % str(e)) @@ -545,7 +552,12 @@ def main(): subscription_id, management_cert_path = get_azure_creds(module) wait_timeout_redirects = int(module.params.get('wait_timeout_redirects')) - azure = ServiceManagementService(subscription_id, management_cert_path) + + if hasattr(windows_azure, '__version__') and LooseVersion(windows_azure.__version__) <= "0.8.0": + # wrapper for handling redirects which the sdk <= 0.8.0 is not following + azure = Wrapper(ServiceManagementService(subscription_id, management_cert_path), wait_timeout_redirects) + else: + azure = ServiceManagementService(subscription_id, management_cert_path) cloud_service_raw = None if module.params.get('state') == 'absent': From f7aa6c4d2744a76dba2d4d29b3e3e590af803067 Mon Sep 17 00:00:00 2001 From: Colin Hutchinson Date: Sat, 3 Oct 2015 21:50:15 -0400 Subject: [PATCH 53/88] make a text link into a actual hyperlink the text link doesn't fit on some screen resolutions. Making it into sphinx hyperlink will solve that --- cloud/docker/docker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/docker/docker.py b/cloud/docker/docker.py index 0ab564208ba..c9c30705085 100644 --- a/cloud/docker/docker.py +++ b/cloud/docker/docker.py @@ -111,8 +111,8 @@ options: version_added: "2.0" log_opt: description: - - Additional options to pass to the logging driver selected above. See Docker log-driver - documentation for more information (https://docs.docker.com/reference/logging/overview/). + - Additional options to pass to the logging driver selected above. See Docker `log-driver + ` documentation for more information. Requires docker >=1.7.0. required: false default: null From 7943b3e6b0cd7de21ebed0e7cb624aabf96f9081 Mon Sep 17 00:00:00 2001 From: JM Date: Sun, 4 Oct 2015 14:24:07 +0200 Subject: [PATCH 54/88] description for the get_url module describing checksum verification before file download --- network/basics/get_url.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/network/basics/get_url.py b/network/basics/get_url.py index fad0d58f878..2d202c12be5 100644 --- a/network/basics/get_url.py +++ b/network/basics/get_url.py @@ -78,13 +78,17 @@ options: default: null checksum: description: - - 'If a checksum is passed to this parameter, the digest of the + - If a checksum is passed to this parameter, the digest of the destination file will be calculated after it is downloaded to ensure its integrity and verify that the transfer completed successfully. Format: :, e.g.: checksum="sha256:D98291AC[...]B6DC7B97" If you worry about portability, only the sha1 algorithm is available on all platforms and python versions. The third party hashlib - library can be installed for access to additional algorithms.' + library can be installed for access to additional algorithms. + Additionaly, if a checksum is passed to this parameter, and the file exist under + the C(dest) location, the destination_checksum would be calculated, and if + checksum equals destination_checksum, the file download would be skipped + (unless C(force) is true). version_added: "2.0" required: false default: null From 8b1ee3d6cb45543cd2e4a5e386b1e6d17b740059 Mon Sep 17 00:00:00 2001 From: Gerard Lynch Date: Sun, 4 Oct 2015 17:32:12 +0100 Subject: [PATCH 55/88] minor doc fix --- cloud/amazon/ec2_tag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/amazon/ec2_tag.py b/cloud/amazon/ec2_tag.py index 7f5aa9ab4b5..071e65626f3 100644 --- a/cloud/amazon/ec2_tag.py +++ b/cloud/amazon/ec2_tag.py @@ -73,7 +73,7 @@ tasks: Env: production exact_count: 1 group: "{{ security_group }}" - keypair: ""{{ keypair }}" + keypair: "{{ keypair }}" image: "{{ image_id }}" instance_tags: Name: dbserver From 144291120e88b18520cebb534baf0bcd04e7ac05 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Mon, 5 Oct 2015 09:07:16 -0400 Subject: [PATCH 56/88] restored quotes to checksum description to avoid breaking docs --- network/basics/get_url.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/basics/get_url.py b/network/basics/get_url.py index 2d202c12be5..41af97a5520 100644 --- a/network/basics/get_url.py +++ b/network/basics/get_url.py @@ -78,7 +78,7 @@ options: default: null checksum: description: - - If a checksum is passed to this parameter, the digest of the + - 'If a checksum is passed to this parameter, the digest of the destination file will be calculated after it is downloaded to ensure its integrity and verify that the transfer completed successfully. Format: :, e.g.: checksum="sha256:D98291AC[...]B6DC7B97" @@ -88,7 +88,7 @@ options: Additionaly, if a checksum is passed to this parameter, and the file exist under the C(dest) location, the destination_checksum would be calculated, and if checksum equals destination_checksum, the file download would be skipped - (unless C(force) is true). + (unless C(force) is true). ' version_added: "2.0" required: false default: null From 3365dad0d821a196739aa836a33d22c2ca5d34b6 Mon Sep 17 00:00:00 2001 From: dagnello Date: Thu, 17 Sep 2015 18:20:15 -0700 Subject: [PATCH 57/88] Adding os_port Module to openstack ansible modules --- cloud/openstack/os_port.py | 395 +++++++++++++++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 cloud/openstack/os_port.py diff --git a/cloud/openstack/os_port.py b/cloud/openstack/os_port.py new file mode 100644 index 00000000000..8564b07c914 --- /dev/null +++ b/cloud/openstack/os_port.py @@ -0,0 +1,395 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# This module 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. +# +# This software 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 software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + + +DOCUMENTATION = ''' +--- +module: os_port +short_description: Add/Update/Delete ports from an OpenStack cloud. +extends_documentation_fragment: openstack +author: "Davide Agnello (@dagnello)" +version_added: "2.0" +description: + - Add, Update or Remove ports from an OpenStack cloud. A state=present, + will ensure the port is created or updated if required. +options: + network: + description: + - Network ID or name this port belongs to. + required: true + name: + description: + - Name that has to be given to the port. + required: false + default: None + fixed_ips: + description: + - Desired IP and/or subnet for this port. Subnet is referenced by + subnet_id and IP is referenced by ip_address. + required: false + default: None + admin_state_up: + description: + - Sets admin state. + required: false + default: None + mac_address: + description: + - MAC address of this port. + required: false + default: None + security_groups: + description: + - Security group(s) ID(s) or name(s) associated with the port (comma + separated for multiple security groups - no spaces between comma(s) + or YAML list). + required: false + default: None + no_security_groups: + description: + - Do not associate a security group with this port. + required: false + default: False + allowed_address_pairs: + description: + - Allowed address pairs list. Allowed address pairs are supported with + dictionary structure. + e.g. allowed_address_pairs: + - ip_address: 10.1.0.12 + mac_address: ab:cd:ef:12:34:56 + - ip_address: ... + required: false + default: None + extra_dhcp_opt: + description: + - Extra dhcp options to be assigned to this port. Extra options are + supported with dictionary structure. + e.g. extra_dhcp_opt: + - opt_name: opt name1 + opt_value: value1 + - opt_name: ... + required: false + default: None + device_owner: + description: + - The ID of the entity that uses this port. + required: false + default: None + device_id: + description: + - Device ID of device using this port. + required: false + default: None + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present +''' + +EXAMPLES = ''' +# Create a port +- os_port: + state: present + auth: + auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0/ + username: admin + password: admin + project_name: admin + name: port1 + network: foo + +# Create a port with a static IP +- os_port: + state: present + auth: + auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0/ + username: admin + password: admin + project_name: admin + name: port1 + network: foo + fixed_ips: + - ip_address: 10.1.0.21 + +# Create a port with No security groups +- os_port: + state: present + auth: + auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0/ + username: admin + password: admin + project_name: admin + name: port1 + network: foo + no_security_groups: True + +# Update the existing 'port1' port with multiple security groups (version 1) +- os_port: + state: present + auth: + auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0/d + username: admin + password: admin + project_name: admin + name: port1 + security_groups: 1496e8c7-4918-482a-9172-f4f00fc4a3a5,057d4bdf-6d4d-472... + +# Update the existing 'port1' port with multiple security groups (version 2) +- os_port: + state: present + auth: + auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0/d + username: admin + password: admin + project_name: admin + name: port1 + security_groups: + - 1496e8c7-4918-482a-9172-f4f00fc4a3a5 + - 057d4bdf-6d4d-472... +''' + +RETURN = ''' +id: + description: Unique UUID. + returned: success + type: string +name: + description: Name given to the port. + returned: success + type: string +network_id: + description: Network ID this port belongs in. + returned: success + type: string +security_groups: + description: Security group(s) associated with this port. + returned: success + type: list of strings +status: + description: Port's status. + returned: success + type: string +fixed_ips: + description: Fixed ip(s) associated with this port. + returned: success + type: list of dicts +tenant_id: + description: Tenant id associated with this port. + returned: success + type: string +allowed_address_pairs: + description: Allowed address pairs with this port. + returned: success + type: list of dicts +admin_state_up: + description: Admin state up flag for this port. + returned: success + type: bool +''' + + +def _needs_update(module, port, cloud): + """Check for differences in the updatable values. + + NOTE: We don't currently allow name updates. + """ + compare_simple = ['admin_state_up', + 'mac_address', + 'device_owner', + 'device_id'] + compare_dict = ['allowed_address_pairs', + 'extra_dhcp_opt'] + compare_comma_separated_list = ['security_groups'] + + for key in compare_simple: + if module.params[key] is not None and module.params[key] != port[key]: + return True + for key in compare_dict: + if module.params[key] is not None and cmp(module.params[key], + port[key]) != 0: + return True + for key in compare_comma_separated_list: + if module.params[key] is not None and (set(module.params[key]) != + set(port[key])): + return True + + # NOTE: if port was created or updated with 'no_security_groups=True', + # subsequent updates without 'no_security_groups' flag or + # 'no_security_groups=False' and no specified 'security_groups', will not + # result in an update to the port where the default security group is + # applied. + if module.params['no_security_groups'] and port['security_groups'] != []: + return True + + if module.params['fixed_ips'] is not None: + for item in module.params['fixed_ips']: + if 'ip_address' in item: + # if ip_address in request does not match any in existing port, + # update is required. + if not any(match['ip_address'] == item['ip_address'] + for match in port['fixed_ips']): + return True + if 'subnet_id' in item: + return True + for item in port['fixed_ips']: + # if ip_address in existing port does not match any in request, + # update is required. + if not any(match.get('ip_address') == item['ip_address'] + for match in module.params['fixed_ips']): + return True + + return False + + +def _system_state_change(module, port, cloud): + state = module.params['state'] + if state == 'present': + if not port: + return True + return _needs_update(module, port, cloud) + if state == 'absent' and port: + return True + return False + + +def _compose_port_args(module, cloud): + port_kwargs = {} + optional_parameters = ['name', + 'fixed_ips', + 'admin_state_up', + 'mac_address', + 'security_groups', + 'allowed_address_pairs', + 'extra_dhcp_opt', + 'device_owner', + 'device_id'] + for optional_param in optional_parameters: + if module.params[optional_param] is not None: + port_kwargs[optional_param] = module.params[optional_param] + + if module.params['no_security_groups']: + port_kwargs['security_groups'] = [] + + return port_kwargs + + +def get_security_group_id(module, cloud, security_group_name_or_id): + security_group = cloud.get_security_group(security_group_name_or_id) + if not security_group: + module.fail_json(msg="Security group: %s, was not found" + % security_group_name_or_id) + return security_group['id'] + + +def main(): + argument_spec = openstack_full_argument_spec( + network=dict(required=False), + name=dict(required=False), + fixed_ips=dict(default=None), + admin_state_up=dict(default=None), + mac_address=dict(default=None), + security_groups=dict(default=None), + no_security_groups=dict(default=False, type='bool'), + allowed_address_pairs=dict(default=None), + extra_dhcp_opt=dict(default=None), + device_owner=dict(default=None), + device_id=dict(default=None), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[ + ['no_security_groups', 'security_groups'], + ] + ) + + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + name = module.params['name'] + state = module.params['state'] + + try: + cloud = shade.openstack_cloud(**module.params) + if module.params['security_groups']: + if type(module.params['security_groups']) == str: + module.params['security_groups'] = module.params[ + 'security_groups'].split(',') + # translate security_groups to UUID's if names where provided + module.params['security_groups'] = map( + lambda v: get_security_group_id(module, cloud, v), + module.params['security_groups']) + + port = None + network_id = None + if name: + port = cloud.get_port(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, port, cloud)) + + changed = False + if state == 'present': + if not port: + network = module.params['network'] + if not network: + module.fail_json( + msg="Parameter 'network' is required in Port Create" + ) + port_kwargs = _compose_port_args(module, cloud) + network_object = cloud.get_network(network) + + if network_object: + network_id = network_object['id'] + else: + module.fail_json( + msg="Specified network was not found." + ) + + port = cloud.create_port(network_id, **port_kwargs) + changed = True + else: + if _needs_update(module, port, cloud): + port_kwargs = _compose_port_args(module, cloud) + port = cloud.update_port(port['id'], **port_kwargs) + changed = True + module.exit_json(changed=changed, id=port['id'], port=port) + + if state == 'absent': + if port: + cloud.delete_port(port['id']) + changed = True + module.exit_json(changed=changed) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=e.message) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +if __name__ == '__main__': + main() From 6f3cbd7aa2bc70c8fafdc787e0ff51dddaab8877 Mon Sep 17 00:00:00 2001 From: fxfitz Date: Mon, 5 Oct 2015 21:26:52 -0500 Subject: [PATCH 58/88] Fixed spelling mistake: missing --- windows/win_get_url.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/win_get_url.ps1 b/windows/win_get_url.ps1 index 18977bff1ef..a83ad2633b0 100644 --- a/windows/win_get_url.ps1 +++ b/windows/win_get_url.ps1 @@ -30,7 +30,7 @@ If ($params.url) { $url = $params.url } Else { - Fail-Json $result "mising required argument: url" + Fail-Json $result "missing required argument: url" } If ($params.dest) { From 8c7d697c17e2a4440f3b66553332c384175dced0 Mon Sep 17 00:00:00 2001 From: Adrian Lopez Date: Tue, 6 Oct 2015 10:26:44 +0200 Subject: [PATCH 59/88] Fix yaml syntax --- cloud/openstack/os_subnet.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cloud/openstack/os_subnet.py b/cloud/openstack/os_subnet.py index 22876c80869..d54268f415a 100644 --- a/cloud/openstack/os_subnet.py +++ b/cloud/openstack/os_subnet.py @@ -112,23 +112,23 @@ requirements: EXAMPLES = ''' # Create a new (or update an existing) subnet on the specified network - os_subnet: - state=present - network_name=network1 - name=net1subnet - cidr=192.168.0.0/24 + state: present + network_name: network1 + name: net1subnet + cidr: 192.168.0.0/24 dns_nameservers: - 8.8.8.7 - 8.8.8.8 host_routes: - destination: 0.0.0.0/0 - nexthop: 123.456.78.9 + nexthop: 12.34.56.78 - destination: 192.168.0.0/24 nexthop: 192.168.0.1 # Delete a subnet - os_subnet: - state=absent - name=net1subnet + state: absent + name: net1subnet # Create an ipv6 stateless subnet - os_subnet: From d659c79db7335a7a81594f7d977cb6bb67e9c5a3 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Tue, 6 Oct 2015 08:14:42 -0400 Subject: [PATCH 60/88] Adding version file for core modules on devel branch --- VERSION | 1 + 1 file changed, 1 insertion(+) create mode 100644 VERSION diff --git a/VERSION b/VERSION new file mode 100644 index 00000000000..8b31b2b4fdb --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2.0.0-0.3.beta1 From cdf0c8a560af4d7079ec328e2aab88937ebff93f Mon Sep 17 00:00:00 2001 From: gekmihesg Date: Tue, 6 Oct 2015 17:34:50 +0200 Subject: [PATCH 61/88] Support sysctl on OpenBSD Fixes #1233 --- system/sysctl.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/system/sysctl.py b/system/sysctl.py index e48d5df74c5..db1652955fc 100644 --- a/system/sysctl.py +++ b/system/sysctl.py @@ -123,6 +123,8 @@ class SysctlModule(object): def process(self): + self.platform = get_platform().lower() + # Whitespace is bad self.args['name'] = self.args['name'].strip() self.args['value'] = self._parse_value(self.args['value']) @@ -206,7 +208,11 @@ class SysctlModule(object): # Use the sysctl command to find the current value def get_token_curr_value(self, token): - thiscmd = "%s -e -n %s" % (self.sysctl_cmd, token) + if self.platform == 'openbsd': + # openbsd doesn't support -e, just drop it + thiscmd = "%s -n %s" % (self.sysctl_cmd, token) + else: + thiscmd = "%s -e -n %s" % (self.sysctl_cmd, token) rc,out,err = self.module.run_command(thiscmd) if rc != 0: return None @@ -217,7 +223,11 @@ class SysctlModule(object): def set_token_value(self, token, value): if len(value.split()) > 0: value = '"' + value + '"' - thiscmd = "%s -w %s=%s" % (self.sysctl_cmd, token, value) + if self.platform == 'openbsd': + # openbsd doesn't accept -w, but since it's not needed, just drop it + thiscmd = "%s %s=%s" % (self.sysctl_cmd, token, value) + else: + thiscmd = "%s -w %s=%s" % (self.sysctl_cmd, token, value) rc,out,err = self.module.run_command(thiscmd) if rc != 0: self.module.fail_json(msg='setting %s failed: %s' % (token, out + err)) @@ -227,9 +237,20 @@ class SysctlModule(object): # Run sysctl -p def reload_sysctl(self): # do it - if get_platform().lower() == 'freebsd': + if self.platform == 'freebsd': # freebsd doesn't support -p, so reload the sysctl service rc,out,err = self.module.run_command('/etc/rc.d/sysctl reload') + elif self.platform == 'openbsd': + # openbsd doesn't support -p and doesn't have a sysctl service, + # so we have to set every value with its own sysctl call + for k, v in self.file_values.items(): + rc = 0 + if k != self.args['name']: + rc = self.set_token_value(k, v) + if rc != 0: + break + if rc == 0 and self.args['state'] == "present": + rc = self.set_token_value(self.args['name'], self.args['value']) else: # system supports reloading via the -p flag to sysctl, so we'll use that sysctl_args = [self.sysctl_cmd, '-p', self.sysctl_file] From e36e1339e8eba3e6dde2b2284d6785ccc5983c00 Mon Sep 17 00:00:00 2001 From: Andrew Widdersheim Date: Tue, 6 Oct 2015 15:37:45 -0400 Subject: [PATCH 62/88] Fix detached head detection in is_not_a_branch() Detached head detection seems to have broken somewhere a long the way because git decided to change how that situation looks when doing a 'git branch -a' which is performed by get_branches(). This is how git 1.7.1 displays this situation (which works): shell> git branch -a * (no branch) master This is the output from git 1.8.3.1 (which does not work): shell> git branch -a * (detached from e132711) master It looks like this same wording is used in the most recent version of git (2.6.1 as of writing this). --- source_control/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source_control/git.py b/source_control/git.py index 4b1620392a0..d42b284abc5 100644 --- a/source_control/git.py +++ b/source_control/git.py @@ -453,7 +453,7 @@ def is_local_branch(git_path, module, dest, branch): def is_not_a_branch(git_path, module, dest): branches = get_branches(git_path, module, dest) for b in branches: - if b.startswith('* ') and 'no branch' in b: + if b.startswith('* ') and ('no branch' in b or 'detached from' in b): return True return False From ca517abf077092a58e6029d7eac5cbf51c4ebb96 Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 7 Oct 2015 13:54:00 +1100 Subject: [PATCH 63/88] Set default of dimensions parameter to be empty dict --- cloud/amazon/ec2_metric_alarm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/amazon/ec2_metric_alarm.py b/cloud/amazon/ec2_metric_alarm.py index b9ac1524794..dcd71831027 100644 --- a/cloud/amazon/ec2_metric_alarm.py +++ b/cloud/amazon/ec2_metric_alarm.py @@ -184,7 +184,7 @@ def create_metric_alarm(connection, module): comparisons = {'<=' : 'LessThanOrEqualToThreshold', '<' : 'LessThanThreshold', '>=' : 'GreaterThanOrEqualToThreshold', '>' : 'GreaterThanThreshold'} alarm.comparison = comparisons[comparison] - dim1 = module.params.get('dimensions', {}) + dim1 = module.params.get('dimensions') dim2 = alarm.dimensions for keys in dim1: @@ -255,7 +255,7 @@ def main(): unit=dict(type='str', choices=['Seconds', 'Microseconds', 'Milliseconds', 'Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes', 'Bits', 'Kilobits', 'Megabits', 'Gigabits', 'Terabits', 'Percent', 'Count', 'Bytes/Second', 'Kilobytes/Second', 'Megabytes/Second', 'Gigabytes/Second', 'Terabytes/Second', 'Bits/Second', 'Kilobits/Second', 'Megabits/Second', 'Gigabits/Second', 'Terabits/Second', 'Count/Second', 'None']), evaluation_periods=dict(type='int'), description=dict(type='str'), - dimensions=dict(type='dict'), + dimensions=dict(type='dict', default={}), alarm_actions=dict(type='list'), insufficient_data_actions=dict(type='list'), ok_actions=dict(type='list'), From 8c353d051668110417fd5b1b6d8dc50ea41cd333 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 7 Oct 2015 10:38:47 -0400 Subject: [PATCH 64/88] make chdir a path so it resolves shell aliases also removed this_dir logic as it is not needed, chdir is None by default and run_command can handle that. --- packaging/language/pip.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packaging/language/pip.py b/packaging/language/pip.py index 8bbae35038d..30adce43ef1 100644 --- a/packaging/language/pip.py +++ b/packaging/language/pip.py @@ -239,7 +239,7 @@ def main(): virtualenv_python=dict(default=None, required=False, type='str'), use_mirrors=dict(default='yes', type='bool'), extra_args=dict(default=None, required=False), - chdir=dict(default=None, required=False), + chdir=dict(default=None, required=False, type='path'), executable=dict(default=None, required=False), ), required_one_of=[['name', 'requirements']], @@ -285,10 +285,7 @@ def main(): cmd += ' -p%s' % virtualenv_python cmd = "%s %s" % (cmd, env) - this_dir = tempfile.gettempdir() - if chdir: - this_dir = os.path.join(this_dir, chdir) - rc, out_venv, err_venv = module.run_command(cmd, cwd=this_dir) + rc, out_venv, err_venv = module.run_command(cmd, cwd=chdir) out += out_venv err += err_venv if rc != 0: @@ -328,9 +325,6 @@ def main(): elif requirements: cmd += ' -r %s' % requirements - this_dir = tempfile.gettempdir() - if chdir: - this_dir = os.path.join(this_dir, chdir) if module.check_mode: if extra_args or requirements or state == 'latest' or not name: @@ -340,7 +334,8 @@ def main(): module.exit_json(changed=True) freeze_cmd = '%s freeze' % pip - rc, out_pip, err_pip = module.run_command(freeze_cmd, cwd=this_dir) + + rc, out_pip, err_pip = module.run_command(freeze_cmd, cwd=chdir) if rc != 0: module.exit_json(changed=True) @@ -353,7 +348,7 @@ def main(): changed = (state == 'present' and not is_present) or (state == 'absent' and is_present) module.exit_json(changed=changed, cmd=freeze_cmd, stdout=out, stderr=err) - rc, out_pip, err_pip = module.run_command(cmd, path_prefix=path_prefix, cwd=this_dir) + rc, out_pip, err_pip = module.run_command(cmd, path_prefix=path_prefix, cwd=chdir) out += out_pip err += err_pip if rc == 1 and state == 'absent' and \ From a639da7c44472503f88bf3de66647686d01e4839 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 7 Oct 2015 12:18:36 -0400 Subject: [PATCH 65/88] default chdir to tmpdir to avoid virtualenv issues --- packaging/language/pip.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packaging/language/pip.py b/packaging/language/pip.py index 30adce43ef1..e587fad920f 100644 --- a/packaging/language/pip.py +++ b/packaging/language/pip.py @@ -258,6 +258,9 @@ def main(): if state == 'latest' and version is not None: module.fail_json(msg='version is incompatible with state=latest') + if chdir is None: + chdir = tempfile.gettempdir() + err = '' out = '' From 22bfb54d9d8ba784425d64bd4094b0153b159806 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 7 Oct 2015 12:19:50 -0400 Subject: [PATCH 66/88] added comment explaining chdir defaults --- packaging/language/pip.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/language/pip.py b/packaging/language/pip.py index e587fad920f..a4af27ccee5 100644 --- a/packaging/language/pip.py +++ b/packaging/language/pip.py @@ -259,6 +259,7 @@ def main(): module.fail_json(msg='version is incompatible with state=latest') if chdir is None: + # this is done to avoid permissions issues with privilege escalation and virtualenvs chdir = tempfile.gettempdir() err = '' From 7fd3262c3c8a425b21062fc6e71dd65fadda4112 Mon Sep 17 00:00:00 2001 From: fperks Date: Tue, 6 Oct 2015 09:57:47 -0400 Subject: [PATCH 67/88] Fix error on ec2 status change Both `source_dest_check` and `termination_protection` variables are not available within the scope of the startstopec2 instance method. This just pulls them from module.params. --- cloud/amazon/ec2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloud/amazon/ec2.py b/cloud/amazon/ec2.py index 6572a9286f4..6152d4934c8 100644 --- a/cloud/amazon/ec2.py +++ b/cloud/amazon/ec2.py @@ -1228,6 +1228,8 @@ def startstop_instances(module, ec2, instance_ids, state, instance_tags): wait_timeout = int(module.params.get('wait_timeout')) changed = False instance_dict_array = [] + source_dest_check = module.params.get('source_dest_check') + termination_protection = module.params.get('termination_protection') if not isinstance(instance_ids, list) or len(instance_ids) < 1: # Fail unless the user defined instance tags From c4f64d822ce5ef9300c06433ac85a0bbfee0a75c Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 7 Oct 2015 16:16:24 -0400 Subject: [PATCH 68/88] changed modules to use common ec2 docs for region --- cloud/amazon/ec2_ami.py | 10 +++------- cloud/amazon/ec2_asg.py | 9 +++------ cloud/amazon/ec2_eip.py | 10 +++------- cloud/amazon/ec2_elb.py | 9 +++------ cloud/amazon/ec2_elb_lb.py | 9 +++------ cloud/amazon/ec2_group.py | 10 +++------- cloud/amazon/ec2_key.py | 10 +++------- cloud/amazon/ec2_lc.py | 9 +++------ cloud/amazon/ec2_metric_alarm.py | 4 +++- cloud/amazon/ec2_scaling_policy.py | 4 +++- cloud/amazon/ec2_snapshot.py | 9 +++------ cloud/amazon/ec2_tag.py | 10 +++------- cloud/amazon/ec2_vol.py | 10 +++------- cloud/amazon/ec2_vpc.py | 10 +++------- cloud/amazon/ec2_vpc_net.py | 4 +++- cloud/amazon/elasticache.py | 10 +++------- cloud/amazon/elasticache_subnet_group.py | 9 +++------ cloud/amazon/iam.py | 6 ++++-- cloud/amazon/iam_cert.py | 4 +++- cloud/amazon/iam_policy.py | 4 +++- cloud/amazon/rds_param_group.py | 10 +++------- cloud/amazon/rds_subnet_group.py | 10 +++------- 22 files changed, 67 insertions(+), 113 deletions(-) diff --git a/cloud/amazon/ec2_ami.py b/cloud/amazon/ec2_ami.py index 60de91ebb72..53b21a519ba 100644 --- a/cloud/amazon/ec2_ami.py +++ b/cloud/amazon/ec2_ami.py @@ -47,12 +47,6 @@ options: - create or deregister/delete image required: false default: 'present' - region: - description: - - The AWS region to use. Must be specified if ec2_url is not used. If not specified then the value of the EC2_REGION environment variable, if any, is used. - required: false - default: null - aliases: [ 'aws_region', 'ec2_region' ] description: description: - An optional human-readable string describing the contents and purpose of the AMI. @@ -89,7 +83,9 @@ options: version_added: "2.0" author: "Evan Duffield (@scicoin-project) " -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' # Thank you to iAcquire for sponsoring development of this module. diff --git a/cloud/amazon/ec2_asg.py b/cloud/amazon/ec2_asg.py index 7d03a7b35f4..b00ed7c97db 100644 --- a/cloud/amazon/ec2_asg.py +++ b/cloud/amazon/ec2_asg.py @@ -80,11 +80,6 @@ options: required: false version_added: "1.8" default: True - region: - description: - - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used. - required: false - aliases: ['aws_region', 'ec2_region'] vpc_zone_identifier: description: - List of VPC subnets to use @@ -134,7 +129,9 @@ options: default: Default choices: ['OldestInstance', 'NewestInstance', 'OldestLaunchConfiguration', 'ClosestToNextInstanceHour', 'Default'] version_added: "2.0" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 """ EXAMPLES = ''' diff --git a/cloud/amazon/ec2_eip.py b/cloud/amazon/ec2_eip.py index 4975f37a250..020ec67a497 100644 --- a/cloud/amazon/ec2_eip.py +++ b/cloud/amazon/ec2_eip.py @@ -40,12 +40,6 @@ options: required: false choices: ['present', 'absent'] default: present - region: - description: - - the EC2 region to use - required: false - default: null - aliases: [ ec2_region ] in_vpc: description: - allocate an EIP inside a VPC or not @@ -64,7 +58,9 @@ options: required: false default: false version_added: "2.0" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 author: "Lorin Hochstein (@lorin) " author: "Rick Mendes (@rickmendes) " notes: diff --git a/cloud/amazon/ec2_elb.py b/cloud/amazon/ec2_elb.py index 6530a00bcb9..8ce5e004226 100644 --- a/cloud/amazon/ec2_elb.py +++ b/cloud/amazon/ec2_elb.py @@ -41,11 +41,6 @@ options: - List of ELB names, required for registration. The ec2_elbs fact should be used if there was a previous de-register. required: false default: None - region: - description: - - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used. - required: false - aliases: ['aws_region', 'ec2_region'] enable_availability_zone: description: - Whether to enable the availability zone of the instance on the target ELB if the availability zone has not already @@ -73,7 +68,9 @@ options: required: false default: 0 version_added: "1.6" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 """ EXAMPLES = """ diff --git a/cloud/amazon/ec2_elb_lb.py b/cloud/amazon/ec2_elb_lb.py index afd91eaf397..37ba3fc1eb6 100644 --- a/cloud/amazon/ec2_elb_lb.py +++ b/cloud/amazon/ec2_elb_lb.py @@ -69,11 +69,6 @@ options: - An associative array of health check configuration settings (see example) require: false default: None - region: - description: - - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used. - required: false - aliases: ['aws_region', 'ec2_region'] subnets: description: - A list of VPC subnets to use when creating ELB. Zones should be empty if using this. @@ -121,7 +116,9 @@ options: required: false version_added: "2.0" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 """ EXAMPLES = """ diff --git a/cloud/amazon/ec2_group.py b/cloud/amazon/ec2_group.py index c79008f53aa..6ec9086db05 100644 --- a/cloud/amazon/ec2_group.py +++ b/cloud/amazon/ec2_group.py @@ -45,12 +45,6 @@ options: - List of firewall outbound rules to enforce in this group (see example). If none are supplied, a default all-out rule is assumed. If an empty list is supplied, no outbound rules will be enabled. required: false version_added: "1.6" - region: - description: - - the EC2 region to use - required: false - default: null - aliases: [] state: version_added: "1.4" description: @@ -74,7 +68,9 @@ options: default: 'true' aliases: [] -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 notes: - If a rule declares a group_name and that group doesn't exist, it will be diff --git a/cloud/amazon/ec2_key.py b/cloud/amazon/ec2_key.py index fc33257cf34..3fe7b959f71 100644 --- a/cloud/amazon/ec2_key.py +++ b/cloud/amazon/ec2_key.py @@ -31,12 +31,6 @@ options: description: - Public key material. required: false - region: - description: - - the EC2 region to use - required: false - default: null - aliases: [] state: description: - create or delete keypair @@ -58,7 +52,9 @@ options: aliases: [] version_added: "1.6" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 author: "Vincent Viallet (@zbal)" ''' diff --git a/cloud/amazon/ec2_lc.py b/cloud/amazon/ec2_lc.py index fa6c64490ad..41b7effa502 100644 --- a/cloud/amazon/ec2_lc.py +++ b/cloud/amazon/ec2_lc.py @@ -55,11 +55,6 @@ options: description: - A list of security groups into which instances should be found required: false - region: - description: - - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used. - required: false - aliases: ['aws_region', 'ec2_region'] volumes: description: - a list of volume dicts, each containing device name and optionally ephemeral id or snapshot id. Size and type (and number of iops for io device type) must be specified for a new volume or a root volume, and may be passed for a snapshot volume. For any volume, a volume size less than 1 will be interpreted as a request not to create the volume. @@ -128,7 +123,9 @@ options: required: false default: null version_added: "2.0" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 """ EXAMPLES = ''' diff --git a/cloud/amazon/ec2_metric_alarm.py b/cloud/amazon/ec2_metric_alarm.py index b9ac1524794..d907dcf7572 100644 --- a/cloud/amazon/ec2_metric_alarm.py +++ b/cloud/amazon/ec2_metric_alarm.py @@ -89,7 +89,9 @@ options: description: - A list of the names of action(s) to take when the alarm is in the 'ok' status required: false -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 """ EXAMPLES = ''' diff --git a/cloud/amazon/ec2_scaling_policy.py b/cloud/amazon/ec2_scaling_policy.py index 2856644ee9c..220fa325582 100644 --- a/cloud/amazon/ec2_scaling_policy.py +++ b/cloud/amazon/ec2_scaling_policy.py @@ -53,7 +53,9 @@ options: description: - The minimum period of time between which autoscaling actions can take place required: false -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 """ EXAMPLES = ''' diff --git a/cloud/amazon/ec2_snapshot.py b/cloud/amazon/ec2_snapshot.py index 29fd559bea5..09fa0d90389 100644 --- a/cloud/amazon/ec2_snapshot.py +++ b/cloud/amazon/ec2_snapshot.py @@ -22,11 +22,6 @@ description: - creates an EC2 snapshot from an existing EBS volume version_added: "1.5" options: - region: - description: - - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used. - required: false - aliases: ['aws_region', 'ec2_region'] volume_id: description: - volume from which to take the snapshot @@ -82,7 +77,9 @@ options: version_added: "1.9" author: "Will Thames (@willthames)" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/ec2_tag.py b/cloud/amazon/ec2_tag.py index 071e65626f3..0e005f0fb48 100644 --- a/cloud/amazon/ec2_tag.py +++ b/cloud/amazon/ec2_tag.py @@ -22,12 +22,6 @@ description: - Creates, removes and lists tags from any EC2 resource. The resource is referenced by its resource id (e.g. an instance being i-XXXXXXX). It is designed to be used with complex args (tags), see the examples. This module has a dependency on python-boto. version_added: "1.3" options: - region: - description: - - region in which the resource exists. - required: false - default: null - aliases: ['aws_region', 'ec2_region'] resource: description: - The EC2 resource id. @@ -49,7 +43,9 @@ options: aliases: [] author: "Lester Wade (@lwade)" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/ec2_vol.py b/cloud/amazon/ec2_vol.py index 228bb12cfbc..11cfb6eaad1 100644 --- a/cloud/amazon/ec2_vol.py +++ b/cloud/amazon/ec2_vol.py @@ -74,12 +74,6 @@ options: required: false default: null aliases: [] - region: - description: - - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used. - required: false - default: null - aliases: ['aws_region', 'ec2_region'] zone: description: - zone in which to create the volume, if unset uses the zone the instance is in (if set) @@ -108,7 +102,9 @@ options: choices: ['absent', 'present', 'list'] version_added: "1.6" author: "Lester Wade (@lwade)" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/ec2_vpc.py b/cloud/amazon/ec2_vpc.py index 611251e307f..a3003a6dcc6 100644 --- a/cloud/amazon/ec2_vpc.py +++ b/cloud/amazon/ec2_vpc.py @@ -94,14 +94,10 @@ options: required: true default: present aliases: [] - region: - description: - - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used. - required: true - default: null - aliases: ['aws_region', 'ec2_region'] author: "Carson Gee (@carsongee)" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/ec2_vpc_net.py b/cloud/amazon/ec2_vpc_net.py index 2ee730f59cb..51acbcaae37 100644 --- a/cloud/amazon/ec2_vpc_net.py +++ b/cloud/amazon/ec2_vpc_net.py @@ -72,7 +72,9 @@ options: default: false required: false -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/elasticache.py b/cloud/amazon/elasticache.py index 9f9cc93aa9f..5d09521ae0c 100644 --- a/cloud/amazon/elasticache.py +++ b/cloud/amazon/elasticache.py @@ -97,13 +97,9 @@ options: required: false default: no choices: [ "yes", "no" ] - region: - description: - - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used. - required: true - default: null - aliases: ['aws_region', 'ec2_region'] -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 """ EXAMPLES = """ diff --git a/cloud/amazon/elasticache_subnet_group.py b/cloud/amazon/elasticache_subnet_group.py index 4ea7e8aba16..0dcf126b170 100644 --- a/cloud/amazon/elasticache_subnet_group.py +++ b/cloud/amazon/elasticache_subnet_group.py @@ -42,13 +42,10 @@ options: - List of subnet IDs that make up the Elasticache subnet group. required: false default: null - region: - description: - - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used. - required: true - aliases: ['aws_region', 'ec2_region'] author: "Tim Mahoney (@timmahoney)" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/iam.py b/cloud/amazon/iam.py index df09b7ed614..8864cb10a6f 100644 --- a/cloud/amazon/iam.py +++ b/cloud/amazon/iam.py @@ -97,10 +97,12 @@ options: aliases: [ 'ec2_access_key', 'access_key' ] notes: - 'Currently boto does not support the removal of Managed Policies, the module will error out if your user/group/role has managed policies when you try to do state=absent. They will need to be removed manually.' -author: +author: - "Jonathan I. Davila (@defionscode)" - "Paul Seiffert (@seiffert)" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/iam_cert.py b/cloud/amazon/iam_cert.py index 1f58be753c8..bdc5b53ed8c 100644 --- a/cloud/amazon/iam_cert.py +++ b/cloud/amazon/iam_cert.py @@ -85,7 +85,9 @@ options: requirements: [ "boto" ] author: Jonathan I. Davila -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/iam_policy.py b/cloud/amazon/iam_policy.py index 475a6c35d65..e05746f74b2 100644 --- a/cloud/amazon/iam_policy.py +++ b/cloud/amazon/iam_policy.py @@ -58,7 +58,9 @@ options: notes: - 'Currently boto does not support the removal of Managed Policies, the module will not work removing/adding managed policies.' author: "Jonathan I. Davila (@defionscode)" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/rds_param_group.py b/cloud/amazon/rds_param_group.py index 7b875304810..b34e3090b53 100644 --- a/cloud/amazon/rds_param_group.py +++ b/cloud/amazon/rds_param_group.py @@ -61,14 +61,10 @@ options: default: null aliases: [] choices: [ 'mysql5.1', 'mysql5.5', 'mysql5.6', 'oracle-ee-11.2', 'oracle-se-11.2', 'oracle-se1-11.2', 'postgres9.3', 'postgres9.4', 'sqlserver-ee-10.5', 'sqlserver-ee-11.0', 'sqlserver-ex-10.5', 'sqlserver-ex-11.0', 'sqlserver-se-10.5', 'sqlserver-se-11.0', 'sqlserver-web-10.5', 'sqlserver-web-11.0'] - region: - description: - - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used. - required: true - default: null - aliases: ['aws_region', 'ec2_region'] author: "Scott Anderson (@tastychutney)" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' diff --git a/cloud/amazon/rds_subnet_group.py b/cloud/amazon/rds_subnet_group.py index 3b998c34225..90f10027744 100644 --- a/cloud/amazon/rds_subnet_group.py +++ b/cloud/amazon/rds_subnet_group.py @@ -47,14 +47,10 @@ options: required: false default: null aliases: [] - region: - description: - - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used. - required: true - default: null - aliases: ['aws_region', 'ec2_region'] author: "Scott Anderson (@tastychutney)" -extends_documentation_fragment: aws +extends_documentation_fragment: + - aws + - ec2 ''' EXAMPLES = ''' From cb203420ed2328f6e85ea57821909d173688e2d4 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 7 Oct 2015 21:30:17 -0400 Subject: [PATCH 69/88] added version added to new rax_cdb choices --- cloud/rackspace/rax_cdb.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloud/rackspace/rax_cdb.py b/cloud/rackspace/rax_cdb.py index 1a8e91ed1b1..ea5b16528e6 100644 --- a/cloud/rackspace/rax_cdb.py +++ b/cloud/rackspace/rax_cdb.py @@ -42,10 +42,12 @@ options: description: - type of instance (i.e. MySQL, MariaDB, Percona) default: MySQL + version_added: "2.0" cdb_version: description: - version of database (MySQL supports 5.1 and 5.6, MariaDB supports 10, Percona supports 5.6) choices: ['5.1', '5.6', '10'] + version_added: "2.0" state: description: - Indicate desired state of the resource From 5a6599d70d07db17e2e21713288fab76d9e3a080 Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 8 Oct 2015 14:03:40 +1100 Subject: [PATCH 70/88] Move import statements for easier debugging (correct line numbers) --- cloud/amazon/ec2_metric_alarm.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cloud/amazon/ec2_metric_alarm.py b/cloud/amazon/ec2_metric_alarm.py index dcd71831027..44e61b84f14 100644 --- a/cloud/amazon/ec2_metric_alarm.py +++ b/cloud/amazon/ec2_metric_alarm.py @@ -115,9 +115,6 @@ EXAMPLES = ''' import sys -from ansible.module_utils.basic import * -from ansible.module_utils.ec2 import * - try: import boto.ec2.cloudwatch from boto.ec2.cloudwatch import CloudWatchConnection, MetricAlarm @@ -282,4 +279,8 @@ def main(): elif state == 'absent': delete_metric_alarm(connection, module) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import * + main() From 1499288c6472a7aa22c13945d4d9fff9d391e9e4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 8 Oct 2015 19:20:08 +0300 Subject: [PATCH 71/88] Add missing documentation: cron_file requires user to be set --- system/cron.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/cron.py b/system/cron.py index 57c7fd40836..63319096c42 100644 --- a/system/cron.py +++ b/system/cron.py @@ -67,6 +67,7 @@ options: cron_file: description: - If specified, uses this file in cron.d instead of an individual user's crontab. + To use the C(cron_file) parameter you must specify the C(user) as well. required: false default: null backup: From 01dcee98d2fe65dee0ca29f10a05448154019181 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Thu, 8 Oct 2015 09:55:52 -0700 Subject: [PATCH 72/88] Fix for state=latest with wildcard or virtual provide package names --- packaging/os/yum.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/packaging/os/yum.py b/packaging/os/yum.py index b54df227973..bcf9b283a95 100644 --- a/packaging/os/yum.py +++ b/packaging/os/yum.py @@ -755,7 +755,11 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos): if update_all: cmd = yum_basecmd + ['update'] + will_update = set(updates.keys()) + will_update_from_other_package = dict() else: + will_update = set() + will_update_from_other_package = dict() for spec in items: # some guess work involved with groups. update @ will install the group if missing if spec.startswith('@'): @@ -779,8 +783,19 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos): nothing_to_do = False break - if spec in pkgs['update'] and spec in updates.keys(): - nothing_to_do = False + # this contains the full NVR and spec could contain wildcards + # or virtual provides (like "python-*" or "smtp-daemon") while + # updates contains name only. + this_name_only = '-'.join(this.split('-')[:-2]) + if spec in pkgs['update'] and this_name_only in updates.keys(): + nothing_to_do = False + will_update.add(spec) + # Massage the updates list + if spec != this_name_only: + # For reporting what packages would be updated more + # succinctly + will_update_from_other_package[spec] = this_name_only + break if nothing_to_do: res['results'].append("All packages providing %s are up to date" % spec) @@ -793,12 +808,6 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos): res['msg'] += "The following packages have pending transactions: %s" % ", ".join(conflicts) module.fail_json(**res) - # list of package updates - if update_all: - will_update = updates.keys() - else: - will_update = [u for u in pkgs['update'] if u in updates.keys() or u.startswith('@')] - # check_mode output if module.check_mode: to_update = [] @@ -806,6 +815,9 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos): if w.startswith('@'): to_update.append((w, None)) msg = '%s will be updated' % w + elif w not in updates: + other_pkg = will_update_from_other_package[w] + to_update.append((w, 'because of (at least) %s-%s.%s from %s' % (other_pkg, updates[other_pkg]['version'], updates[other_pkg]['dist'], updates[other_pkg]['repo']))) else: to_update.append((w, '%s.%s from %s' % (updates[w]['version'], updates[w]['dist'], updates[w]['repo']))) From 2c24d0482ba29686029a52449bd12a95de06d5f3 Mon Sep 17 00:00:00 2001 From: Frank van Tol Date: Fri, 9 Oct 2015 14:35:26 +0200 Subject: [PATCH 73/88] Update ec2_asg.py --- cloud/amazon/ec2_asg.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cloud/amazon/ec2_asg.py b/cloud/amazon/ec2_asg.py index b00ed7c97db..f6e71b4baf8 100644 --- a/cloud/amazon/ec2_asg.py +++ b/cloud/amazon/ec2_asg.py @@ -255,9 +255,10 @@ def get_properties(autoscaling_group): properties['viable_instances'] = 0 properties['terminating_instances'] = 0 + instance_facts = {} + if autoscaling_group.instances: properties['instances'] = [i.instance_id for i in autoscaling_group.instances] - instance_facts = {} for i in autoscaling_group.instances: instance_facts[i.instance_id] = {'health_status': i.health_status, 'lifecycle_state': i.lifecycle_state, @@ -274,7 +275,7 @@ def get_properties(autoscaling_group): properties['terminating_instances'] += 1 if i.lifecycle_state == 'Pending': properties['pending_instances'] += 1 - properties['instance_facts'] = instance_facts + properties['instance_facts'] = instance_facts properties['load_balancers'] = autoscaling_group.load_balancers if getattr(autoscaling_group, "tags", None): From f0ac28490d026dcc0496f61db1baab995bedada7 Mon Sep 17 00:00:00 2001 From: Tim Barnes Date: Fri, 9 Oct 2015 15:15:53 +0100 Subject: [PATCH 74/88] fixing issue with subversion module whereby the module was reporting local modifications being present when externals were being used --- source_control/subversion.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source_control/subversion.py b/source_control/subversion.py index 24cc065c5a4..64a1b38b044 100644 --- a/source_control/subversion.py +++ b/source_control/subversion.py @@ -171,9 +171,10 @@ class Subversion(object): '''True if revisioned files have been added or modified. Unrevisioned files are ignored.''' lines = self._exec(["status", "--quiet", "--ignore-externals", self.dest]) # The --quiet option will return only modified files. - + # Match only revisioned files, i.e. ignore status '?'. + regex = re.compile(r'^[^?X]') # Has local mods if more than 0 modifed revisioned files. - return len(filter(len, lines)) > 0 + return len(filter(regex.match, lines)) > 0 def needs_update(self): curr, url = self.get_revision() From 83a9f293fd00024c011ceaac6459d15226a78b4c Mon Sep 17 00:00:00 2001 From: dagnello Date: Fri, 9 Oct 2015 15:28:56 -0400 Subject: [PATCH 75/88] Adding string support to metadata argument This patch adds support to setting metadata key/value through a string argument. Variables can now be used for both the metadata key and value. example: meta: "{{ var1 }}:SomeValue,key:{{ var2 }}" --- cloud/openstack/os_server.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/cloud/openstack/os_server.py b/cloud/openstack/os_server.py index 74a5009f972..f948a8556a6 100644 --- a/cloud/openstack/os_server.py +++ b/cloud/openstack/os_server.py @@ -111,7 +111,8 @@ options: meta: description: - A list of key value pairs that should be provided as a metadata to - the new instance. + the new instance or a string containing a list of key-value pairs. + Eg: meta: "key1=value1,key2=value2" required: false default: None wait: @@ -263,6 +264,25 @@ EXAMPLES = ''' timeout: 200 flavor: 4 nics: "net-id=4cb08b20-62fe-11e5-9d70-feff819cdc9f,net-id=542f0430-62fe-11e5-9d70-feff819cdc9f..." + +# Creates a new instance and attaches to a network and passes metadata to +# the instance +- os_server: + state: present + auth: + auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0/ + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + nics: + - net-id: 34605f38-e52a-25d2-b6ec-754a13ffb723 + - net-name: another_network + meta: "hostname=test1,group=uge_master" ''' @@ -335,6 +355,13 @@ def _create_server(module, cloud): nics = _network_args(module, cloud) + if type(module.params['meta']) is str: + metas = {} + for kv_str in module.params['meta'].split(","): + k, v = kv_str.split("=") + metas[k] = v + module.params['meta'] = metas + bootkwargs = dict( name=module.params['name'], image=image_id, From f707a711cd6941201ed8f0043af4a6fd91bf6b07 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Fri, 9 Oct 2015 18:36:58 -0400 Subject: [PATCH 76/88] doc fixes --- cloud/openstack/os_image_facts.py | 145 ++++++++++++++------------- cloud/openstack/os_networks_facts.py | 56 ++++++----- cloud/openstack/os_server.py | 4 +- 3 files changed, 104 insertions(+), 101 deletions(-) diff --git a/cloud/openstack/os_image_facts.py b/cloud/openstack/os_image_facts.py index fa5678b50b7..a54537172eb 100644 --- a/cloud/openstack/os_image_facts.py +++ b/cloud/openstack/os_image_facts.py @@ -22,7 +22,6 @@ except ImportError: HAS_SHADE = False DOCUMENTATION = ''' ---- module: os_image_facts short_description: Retrieve facts about an image within OpenStack. version_added: "2.0" @@ -55,77 +54,79 @@ EXAMPLES = ''' ''' RETURN = ''' -This module registers image details in facts named: openstack_image. When -image is not found, openstack_image will be null. - -id: - description: Unique UUID. - returned: success - type: string -name: - description: Name given to the image. - returned: success - type: string -status: - description: Image status. - returned: success - type: string -created_at: - description: Image created at timestamp. - returned: success - type: string -deleted: - description: Image deleted flag. - returned: success - type: boolean -container_format: - description: Container format of the image. - returned: success - type: string -min_ram: - description: Min amount of RAM required for this image. - returned: success - type: int -disk_format: - description: Disk format of the image. - returned: success - type: string -updated_at: - description: Image updated at timestamp. - returned: success - type: string -properties: - description: Additional properties associated with the image. - returned: success - type: dict -min_disk: - description: Min amount of disk space required for this image. - returned: success - type: int -protected: - description: Image protected flag. - returned: success - type: boolean -checksum: - description: Checksum for the image. - returned: success - type: string -owner: - description: Owner for the image. - returned: success - type: string -is_public: - description: Is plubic flag of the image. - returned: success - type: boolean -deleted_at: - description: Image deleted at timestamp. - returned: success - type: string -size: - description: Size of the image. - returned: success - type: int +openstack_image: + description: has all the openstack facts about the image + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: string + name: + description: Name given to the image. + returned: success + type: string + status: + description: Image status. + returned: success + type: string + created_at: + description: Image created at timestamp. + returned: success + type: string + deleted: + description: Image deleted flag. + returned: success + type: boolean + container_format: + description: Container format of the image. + returned: success + type: string + min_ram: + description: Min amount of RAM required for this image. + returned: success + type: int + disk_format: + description: Disk format of the image. + returned: success + type: string + updated_at: + description: Image updated at timestamp. + returned: success + type: string + properties: + description: Additional properties associated with the image. + returned: success + type: dict + min_disk: + description: Min amount of disk space required for this image. + returned: success + type: int + protected: + description: Image protected flag. + returned: success + type: boolean + checksum: + description: Checksum for the image. + returned: success + type: string + owner: + description: Owner for the image. + returned: success + type: string + is_public: + description: Is plubic flag of the image. + returned: success + type: boolean + deleted_at: + description: Image deleted at timestamp. + returned: success + type: string + size: + description: Size of the image. + returned: success + type: int ''' diff --git a/cloud/openstack/os_networks_facts.py b/cloud/openstack/os_networks_facts.py index 56c30adb203..6ac8786463d 100644 --- a/cloud/openstack/os_networks_facts.py +++ b/cloud/openstack/os_networks_facts.py @@ -82,33 +82,35 @@ EXAMPLES = ''' ''' RETURN = ''' -This module registers network details in facts named: openstack_networks. If a -network name/id and or filter does not result in a network found, an empty -list is set in openstack_networks. -id: - description: Unique UUID. - returned: success - type: string -name: - description: Name given to the network. - returned: success - type: string -status: - description: Network status. - returned: success - type: string -subnets: - description: Subnet(s) included in this network. - returned: success - type: list of strings -tenant_id: - description: Tenant id associated with this network. - returned: success - type: string -shared: - description: Network shared flag. - returned: success - type: boolean +openstack_networks: + description: has all the openstack facts about the networks + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: string + name: + description: Name given to the network. + returned: success + type: string + status: + description: Network status. + returned: success + type: string + subnets: + description: Subnet(s) included in this network. + returned: success + type: list of strings + tenant_id: + description: Tenant id associated with this network. + returned: success + type: string + shared: + description: Network shared flag. + returned: success + type: boolean ''' def main(): diff --git a/cloud/openstack/os_server.py b/cloud/openstack/os_server.py index 74a5009f972..81f809ab7fe 100644 --- a/cloud/openstack/os_server.py +++ b/cloud/openstack/os_server.py @@ -84,8 +84,8 @@ options: - A list of networks to which the instance's interface should be attached. Networks may be referenced by net-id/net-name/port-id or port-name. - Also this accepts a string containing a list of net-id/port-id. - Eg: nics: "net-id=uuid-1,net-id=uuid-2" + - 'Also this accepts a string containing a list of net-id/port-id. + Eg: nics: "net-id=uuid-1,net-id=uuid-2"' required: false default: None public_ip: From ebd8b12e15afa4d76d1d503140a67cdb6ad7fbd9 Mon Sep 17 00:00:00 2001 From: Abhijit Menon-Sen Date: Sun, 11 Oct 2015 07:07:37 +0530 Subject: [PATCH 77/88] Add missing variable initializations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this, «ec2: state=stopped instance_ids=…» would fail with a traceback like this: if inst.get_attribute('sourceDestCheck')['sourceDestCheck'] != source_dest_check: NameError: global name 'source_dest_check' is not defined --- cloud/amazon/ec2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloud/amazon/ec2.py b/cloud/amazon/ec2.py index 6572a9286f4..f4605b4b442 100644 --- a/cloud/amazon/ec2.py +++ b/cloud/amazon/ec2.py @@ -1226,6 +1226,8 @@ def startstop_instances(module, ec2, instance_ids, state, instance_tags): wait = module.params.get('wait') wait_timeout = int(module.params.get('wait_timeout')) + source_dest_check = module.params.get('source_dest_check') + termination_protection = module.params.get('termination_protection') changed = False instance_dict_array = [] From 83291dbefcd6d63aab4472f37dc01cf05284521e Mon Sep 17 00:00:00 2001 From: Constantin Bugneac Date: Mon, 12 Oct 2015 10:49:16 +0100 Subject: [PATCH 78/88] Added deleteOnTermination in the output of list option --- cloud/amazon/ec2_vol.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cloud/amazon/ec2_vol.py b/cloud/amazon/ec2_vol.py index 01a539ae4b0..6eeeec7436d 100644 --- a/cloud/amazon/ec2_vol.py +++ b/cloud/amazon/ec2_vol.py @@ -400,7 +400,8 @@ def main(): 'attachment_set': { 'attach_time': attachment.attach_time, 'device': attachment.device, - 'status': attachment.status + 'status': attachment.status, + 'deleteOnTermination': attachment.deleteOnTermination } }) From 785ebe268453ac07ae2657c6aca6ed8f7a37977c Mon Sep 17 00:00:00 2001 From: Michael Perzel Date: Mon, 12 Oct 2015 16:02:36 -0500 Subject: [PATCH 79/88] Add ansible_date_time to windows facts --- windows/setup.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/windows/setup.ps1 b/windows/setup.ps1 index a6f439d7338..938d98c23e6 100644 --- a/windows/setup.ps1 +++ b/windows/setup.ps1 @@ -64,6 +64,12 @@ Set-Attr $result.ansible_facts "ansible_os_name" ($win32_os.Name.Split('|')[0]). Set-Attr $result.ansible_facts "ansible_distribution" $osversion.VersionString Set-Attr $result.ansible_facts "ansible_distribution_version" $osversion.Version.ToString() +$date = New-Object psobject +Set-Attr $date "day" (Get-Date -format dd) +Set-Attr $date "hour" (Get-Date -format HH) +Set-Attr $date "iso8601" (Get-Date -format s) +Set-Attr $result.ansible_facts "ansible_date_time" $date + Set-Attr $result.ansible_facts "ansible_totalmem" $capacity Set-Attr $result.ansible_facts "ansible_lastboot" $win32_os.lastbootuptime.ToString("u") From 6615e618d94e79f9dfab7e8b8f02c5b52f428433 Mon Sep 17 00:00:00 2001 From: Michael Perzel Date: Mon, 12 Oct 2015 16:16:10 -0500 Subject: [PATCH 80/88] Added date, year and month attributes --- windows/setup.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/windows/setup.ps1 b/windows/setup.ps1 index 938d98c23e6..0b3e2c897e3 100644 --- a/windows/setup.ps1 +++ b/windows/setup.ps1 @@ -65,6 +65,9 @@ Set-Attr $result.ansible_facts "ansible_distribution" $osversion.VersionString Set-Attr $result.ansible_facts "ansible_distribution_version" $osversion.Version.ToString() $date = New-Object psobject +Set-Attr $date "date" (Get-Date -format d) +Set-Attr $date "year" (Get-Date -format yyyy) +Set-Attr $date "month" (Get-Date -format MM) Set-Attr $date "day" (Get-Date -format dd) Set-Attr $date "hour" (Get-Date -format HH) Set-Attr $date "iso8601" (Get-Date -format s) From 833bf77be1101fe77f691b859d4c74e892ed167e Mon Sep 17 00:00:00 2001 From: = Date: Tue, 13 Oct 2015 06:15:25 +0100 Subject: [PATCH 81/88] Fix for ansible modules core 2147 --- windows/win_file.ps1 | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/windows/win_file.ps1 b/windows/win_file.ps1 index f8416120abf..f387780123c 100644 --- a/windows/win_file.ps1 +++ b/windows/win_file.ps1 @@ -71,18 +71,15 @@ If (Test-Path $path) } Else { - # Only files have the .Directory attribute. - If ( $state -eq "directory" -and $fileinfo.Directory ) + If ( $state -eq "directory" -and -not $fileinfo.PsIsContainer ) { Fail-Json (New-Object psobject) "path is not a directory" } - # Only files have the .Directory attribute. - If ( $state -eq "file" -and -not $fileinfo.Directory ) + If ( $state -eq "file" -and $fileinfo.PsIsContainer ) { Fail-Json (New-Object psobject) "path is not a file" } - } } Else From 9db5ac8e450a25a7bda081381516be32f8497a1f Mon Sep 17 00:00:00 2001 From: Sam Yaple Date: Tue, 13 Oct 2015 10:24:36 +0000 Subject: [PATCH 82/88] Don't assume ExposedPorts exists (bug #2257) A recent change [1] in docker between v1.8.2 and v1.8.3 changed what is returned in the json when inspecting an image. Five variables which could have been expected before will now be omited when empty. Only one of those variables is being addressed in the docker, ExposedPorts. Unfortunately there was also no API version change on this so this can't be easily corrected with pinning the API to the older version. This does a get() which will return None if the variable is not in the dict formed from the json that was returned. Everything else works the same way. [1] https://github.com/docker/docker/commit/9098628b2901ae8585ba4c66ee6e14759d2119da --- cloud/docker/docker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/docker/docker.py b/cloud/docker/docker.py index 4df808fec2d..3a2f8fe9e31 100644 --- a/cloud/docker/docker.py +++ b/cloud/docker/docker.py @@ -1062,7 +1062,7 @@ class DockerManager(object): continue # EXPOSED PORTS - expected_exposed_ports = set((image['ContainerConfig']['ExposedPorts'] or {}).keys()) + expected_exposed_ports = set((image['ContainerConfig'].get('ExposedPorts') or {}).keys()) for p in (self.exposed_ports or []): expected_exposed_ports.add("/".join(p)) From f15d285514b7d5ba4e8da2579b7fc2a7ed73b72f Mon Sep 17 00:00:00 2001 From: Sam Yaple Date: Tue, 13 Oct 2015 10:34:08 +0000 Subject: [PATCH 83/88] Catch the second occurance of ExposedPorts as well --- cloud/docker/docker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/docker/docker.py b/cloud/docker/docker.py index 3a2f8fe9e31..faf6e05c470 100644 --- a/cloud/docker/docker.py +++ b/cloud/docker/docker.py @@ -1066,7 +1066,7 @@ class DockerManager(object): for p in (self.exposed_ports or []): expected_exposed_ports.add("/".join(p)) - actually_exposed_ports = set((container["Config"]["ExposedPorts"] or {}).keys()) + actually_exposed_ports = set((container["Config"].get("ExposedPorts") or {}).keys()) if actually_exposed_ports != expected_exposed_ports: self.reload_reasons.append('exposed_ports ({0} => {1})'.format(actually_exposed_ports, expected_exposed_ports)) From 64b8596250e58a55eee8f2d4323d35ca32a8cd53 Mon Sep 17 00:00:00 2001 From: Adam Williamson Date: Tue, 13 Oct 2015 22:33:46 -0700 Subject: [PATCH 84/88] fix #2043: strip empty dict from end of 'pull' stream When pulling an image using Docker 1.8, it seems the output JSON stream has an empty dict at the very end. This causes ansible to fail when pulling an image, as it's expecting a status message in that dict which it uses to determine whether it had to download the image or not. As a bit of an ugly hack for that which remains backward compatible, try the last item in the stream, and if it's an empty dict, take the last-but-one item instead. The strip() is needed as the exact value appears to be '{}/r/n'; we could just match that, but it seems like the kind of thing where maybe it'd happen to just be '{}/n' or '{}' or something in some cases, so let's just use strip() in case. --- cloud/docker/docker.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cloud/docker/docker.py b/cloud/docker/docker.py index 4df808fec2d..cc22632bf7d 100644 --- a/cloud/docker/docker.py +++ b/cloud/docker/docker.py @@ -1392,6 +1392,11 @@ class DockerManager(object): changes = list(self.client.pull(image, tag=tag, stream=True, **extra_params)) try: last = changes[-1] + # seems Docker 1.8 puts an empty dict at the end of the + # stream; catch that and get the previous instead + # https://github.com/ansible/ansible-modules-core/issues/2043 + if last.strip() == '{}': + last = changes[-2] except IndexError: last = '{}' status = json.loads(last).get('status', '') From 5e61f0882b128951947b31ca3ea192465ae539a8 Mon Sep 17 00:00:00 2001 From: whiter Date: Wed, 7 Oct 2015 14:01:47 +1100 Subject: [PATCH 85/88] Add region to doc and handle missing region parameter --- cloud/amazon/ec2_metric_alarm.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cloud/amazon/ec2_metric_alarm.py b/cloud/amazon/ec2_metric_alarm.py index cead47460fa..94f303212ae 100644 --- a/cloud/amazon/ec2_metric_alarm.py +++ b/cloud/amazon/ec2_metric_alarm.py @@ -259,7 +259,6 @@ def main(): insufficient_data_actions=dict(type='list'), ok_actions=dict(type='list'), state=dict(default='present', choices=['present', 'absent']), - region=dict(aliases=['aws_region', 'ec2_region']), ) ) @@ -271,10 +270,14 @@ def main(): state = module.params.get('state') region, ec2_url, aws_connect_params = get_aws_connection_info(module) - try: - connection = connect_to_aws(boto.ec2.cloudwatch, region, **aws_connect_params) - except (boto.exception.NoAuthHandlerFound, StandardError), e: - module.fail_json(msg=str(e)) + + if region: + try: + connection = connect_to_aws(boto.ec2.cloudwatch, 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") if state == 'present': create_metric_alarm(connection, module) From 970185f2d2b9aa04ae88d56139527256007b981d Mon Sep 17 00:00:00 2001 From: Gilad Peleg Date: Fri, 16 Oct 2015 00:37:21 +0300 Subject: [PATCH 86/88] Docs: add state choices to ec2_elb_lb add state options (`present` and `absent`) to `ec2_elb-lb` doc --- cloud/amazon/ec2_elb_lb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/amazon/ec2_elb_lb.py b/cloud/amazon/ec2_elb_lb.py index 37ba3fc1eb6..849a753df5f 100644 --- a/cloud/amazon/ec2_elb_lb.py +++ b/cloud/amazon/ec2_elb_lb.py @@ -29,6 +29,7 @@ options: state: description: - Create or destroy the ELB + choices: ["present", "absent"] required: true name: description: From 68e1ff2f81873a9dc0121491849e0bad0350aa5c Mon Sep 17 00:00:00 2001 From: Gilad Peleg Date: Fri, 16 Oct 2015 00:57:04 +0300 Subject: [PATCH 87/88] Docs: remove redundant quotes in ec2_elb docs --- cloud/amazon/ec2_elb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/amazon/ec2_elb.py b/cloud/amazon/ec2_elb.py index 8ce5e004226..9f333764a5d 100644 --- a/cloud/amazon/ec2_elb.py +++ b/cloud/amazon/ec2_elb.py @@ -82,7 +82,7 @@ pre_tasks: local_action: module: ec2_elb instance_id: "{{ ansible_ec2_instance_id }}" - state: 'absent' + state: absent roles: - myrole post_tasks: @@ -91,7 +91,7 @@ post_tasks: module: ec2_elb instance_id: "{{ ansible_ec2_instance_id }}" ec2_elbs: "{{ item }}" - state: 'present' + state: present with_items: ec2_elbs """ From 5893eb51a5445f255c686c29b7fe49a606f16331 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Fri, 16 Oct 2015 01:09:41 -0400 Subject: [PATCH 88/88] documented new remote_src option --- files/copy.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/files/copy.py b/files/copy.py index 8f6d3d32f28..da976f9a692 100644 --- a/files/copy.py +++ b/files/copy.py @@ -77,6 +77,13 @@ options: already existed. required: false version_added: "1.5" + remote_src: + description: + - If False, it will search for src at originating/master machine, if True it will go to the remote/target machine for the src. Default is False. + choices: [ "True", "False" ] + required: false + default: "False" + version_added: "2.0" extends_documentation_fragment: - files - validate