From efb989a50cec69d0fbea79a571630f02eaae8fb1 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sat, 2 Aug 2014 15:31:10 -0700 Subject: [PATCH 1/2] Consume standard OpenStack environment settings The OpenStack client utilities consume a set of input environment variables for things like username and auth_url, so it's very common for OpenStack users to have such settings set in their environment. Indeed, things like devstack also output a shell file to be sourced to set them. Although in a playbook it's entirely expected that variables should be used to pass in system settings like api passwords, for ad-hoc command line usage, needing to pass in five parameters which are almost certainly in the environment already reduces the utility. Grab the environment variables and inject them as default. Special care is taken to ensure that in the case where the values are not found, the behavior of which parameters are required is not altered. --- library/cloud/nova_compute | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/library/cloud/nova_compute b/library/cloud/nova_compute index 37df3665ea4..8a3725ed2fc 100644 --- a/library/cloud/nova_compute +++ b/library/cloud/nova_compute @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License # along with this software. If not, see . +import os + try: from novaclient.v1_1 import client as nova_client from novaclient import exceptions @@ -224,13 +226,32 @@ def _get_server_state(module, nova): def main(): + # Consume standard OpenStack environment variables. + # This is mainly only useful for ad-hoc command line operation as + # in playbooks one would assume variables would be used appropriately + OS_USERNAME=os.environ.get('OS_USERNAME', 'admin') + OS_PASSWORD=os.environ.get('OS_PASSWORD', None) + login_password_arg = dict() + if OS_PASSWORD: + login_password_arg['default'] = OS_PASSWORD + else: + login_password_arg['required'] = True + OS_TENANT_NAME=os.environ.get('OS_TENANT_NAME', None) + tenant_name_arg = dict() + if OS_TENANT_NAME: + tenant_name_arg['default'] = OS_TENANT_NAME + else: + tenant_name_arg['required'] = True + OS_REGION_NAME=os.environ.get('OS_REGION_NAME', None) + OS_AUTH_URL=os.environ.get('OS_AUTH_URL', 'http://127.0.0.1:35357/v2.0/') + module = AnsibleModule( argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required='True'), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), + login_username = dict(default=OS_USERNAME), + login_password = login_password_arg, + login_tenant_name = tenant_name_arg, + auth_url = dict(default=OS_AUTH_URL), + region_name = dict(default=OS_REGION_NAME), name = dict(required=True), image_id = dict(default=None), flavor_id = dict(default=1), From 4012272fa2ae0a6caafad0e0783c8d8fcc3e852a Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sat, 2 Aug 2014 17:12:24 -0700 Subject: [PATCH 2/2] Add common auth handling across openstack modules Taking a page out of the ec2 config, make sure that all of the OpenStack modules handle the inbound auth config in the same way. The one outlier is keystone wrt auth_url. --- lib/ansible/module_utils/openstack.py | 56 +++++++++++++++++++++ library/cloud/glance_image | 36 +++++++------ library/cloud/keystone_user | 14 +++--- library/cloud/nova_compute | 35 +++---------- library/cloud/nova_keypair | 14 ++---- library/cloud/quantum_floating_ip | 14 ++---- library/cloud/quantum_floating_ip_associate | 14 ++---- library/cloud/quantum_network | 14 ++---- library/cloud/quantum_router | 14 ++---- library/cloud/quantum_router_gateway | 14 ++---- library/cloud/quantum_router_interface | 14 ++---- library/cloud/quantum_subnet | 14 ++---- 12 files changed, 127 insertions(+), 126 deletions(-) create mode 100644 lib/ansible/module_utils/openstack.py diff --git a/lib/ansible/module_utils/openstack.py b/lib/ansible/module_utils/openstack.py new file mode 100644 index 00000000000..c70eb9fbfa8 --- /dev/null +++ b/lib/ansible/module_utils/openstack.py @@ -0,0 +1,56 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os + + +def openstack_argument_spec(): + # Consume standard OpenStack environment variables. + # This is mainly only useful for ad-hoc command line operation as + # in playbooks one would assume variables would be used appropriately + OS_AUTH_URL=os.environ.get('OS_AUTH_URL', 'http://127.0.0.1:35357/v2.0/') + OS_PASSWORD=os.environ.get('OS_PASSWORD', None) + OS_REGION_NAME=os.environ.get('OS_REGION_NAME', None) + OS_USERNAME=os.environ.get('OS_USERNAME', 'admin') + OS_TENANT_NAME=os.environ.get('OS_TENANT_NAME', OS_USERNAME) + + spec = dict( + login_username = dict(default=OS_USERNAME), + auth_url = dict(default=OS_AUTH_URL), + region_name = dict(default=OS_REGION_NAME), + availability_zone = dict(default=None), + ) + if OS_PASSWORD: + spec['login_password'] = dict(default=OS_PASSWORD) + else: + spec['login_password'] = dict(required=True) + if OS_TENANT_NAME: + spec['login_tenant_name'] = dict(default=OS_TENANT_NAME) + else: + spec['login_tenant_name'] = dict(required=True) + return spec diff --git a/library/cloud/glance_image b/library/cloud/glance_image index b73b3bfea7a..d8b02602feb 100644 --- a/library/cloud/glance_image +++ b/library/cloud/glance_image @@ -217,26 +217,23 @@ def _glance_delete_image(module, params, client): def main(): + argument_spec = openstack_argument_spec() + argument_spec.update(dict( + name = dict(required=True), + disk_format = dict(default='qcow2', choices=['aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso']), + container_format = dict(default='bare', choices=['aki', 'ari', 'bare', 'ovf']), + owner = dict(default=None), + min_disk = dict(default=None), + min_ram = dict(default=None), + is_public = dict(default=True), + copy_from = dict(default= None), + timeout = dict(default=180), + file = dict(default=None), + endpoint_type = dict(default='publicURL', choices=['publicURL', 'internalURL']), + state = dict(default='present', choices=['absent', 'present']) + )) module = AnsibleModule( - argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required=True), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), - name = dict(required=True), - disk_format = dict(default='qcow2', choices=['aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso']), - container_format = dict(default='bare', choices=['aki', 'ari', 'bare', 'ovf']), - owner = dict(default=None), - min_disk = dict(default=None), - min_ram = dict(default=None), - is_public = dict(default=True), - copy_from = dict(default= None), - timeout = dict(default=180), - file = dict(default=None), - endpoint_type = dict(default='publicURL', choices=['publicURL', 'internalURL']), - state = dict(default='present', choices=['absent', 'present']) - ), + argument_spec=argument_spec, mutually_exclusive = [['file','copy_from']], ) if module.params['state'] == 'present': @@ -258,4 +255,5 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main() diff --git a/library/cloud/keystone_user b/library/cloud/keystone_user index f3295b7f19b..a5d3aa940d8 100644 --- a/library/cloud/keystone_user +++ b/library/cloud/keystone_user @@ -287,11 +287,8 @@ def ensure_role_absent(keystone, uesr, tenant, role, check_mode): def main(): - module = AnsibleModule( - argument_spec=dict( - user=dict(required=False), - password=dict(required=False), - tenant=dict(required=False), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( tenant_description=dict(required=False), email=dict(required=False), role=dict(required=False), @@ -302,7 +299,11 @@ def main(): login_user=dict(required=False), login_password=dict(required=False), login_tenant_name=dict(required=False) - ), + )) + # keystone operations themselves take an endpoint, not a keystone auth_url + del(argument_spec['auth_url']) + module = AnsibleModule( + argument_spec=argument_spec, supports_check_mode=True, mutually_exclusive=[['token', 'login_user'], ['token', 'login_password'], @@ -388,5 +389,6 @@ def dispatch(keystone, user=None, password=None, tenant=None, # import module snippets from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * if __name__ == '__main__': main() diff --git a/library/cloud/nova_compute b/library/cloud/nova_compute index 8a3725ed2fc..86ea57d8996 100644 --- a/library/cloud/nova_compute +++ b/library/cloud/nova_compute @@ -226,32 +226,8 @@ def _get_server_state(module, nova): def main(): - # Consume standard OpenStack environment variables. - # This is mainly only useful for ad-hoc command line operation as - # in playbooks one would assume variables would be used appropriately - OS_USERNAME=os.environ.get('OS_USERNAME', 'admin') - OS_PASSWORD=os.environ.get('OS_PASSWORD', None) - login_password_arg = dict() - if OS_PASSWORD: - login_password_arg['default'] = OS_PASSWORD - else: - login_password_arg['required'] = True - OS_TENANT_NAME=os.environ.get('OS_TENANT_NAME', None) - tenant_name_arg = dict() - if OS_TENANT_NAME: - tenant_name_arg['default'] = OS_TENANT_NAME - else: - tenant_name_arg['required'] = True - OS_REGION_NAME=os.environ.get('OS_REGION_NAME', None) - OS_AUTH_URL=os.environ.get('OS_AUTH_URL', 'http://127.0.0.1:35357/v2.0/') - - module = AnsibleModule( - argument_spec = dict( - login_username = dict(default=OS_USERNAME), - login_password = login_password_arg, - login_tenant_name = tenant_name_arg, - auth_url = dict(default=OS_AUTH_URL), - region_name = dict(default=OS_REGION_NAME), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( name = dict(required=True), image_id = dict(default=None), flavor_id = dict(default=1), @@ -262,9 +238,9 @@ def main(): wait = dict(default='yes', choices=['yes', 'no']), wait_for = dict(default=180), state = dict(default='present', choices=['absent', 'present']), - user_data = dict(default=None) - ), - ) + user_data = dict(default=None), + )) + module = AnsibleModule(argument_spec=argument_spec) nova = nova_client.Client(module.params['login_username'], module.params['login_password'], @@ -291,5 +267,6 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main() diff --git a/library/cloud/nova_keypair b/library/cloud/nova_keypair index 553683d3a89..be2bbb1d93d 100644 --- a/library/cloud/nova_keypair +++ b/library/cloud/nova_keypair @@ -87,18 +87,13 @@ EXAMPLES = ''' ''' def main(): - module = AnsibleModule( - argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required='True'), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( name = dict(required=True), public_key = dict(default=None), state = dict(default='present', choices=['absent', 'present']) - ), - ) + )) + module = AnsibleModule(argument_spec=argument_spec) nova = nova_client.Client(module.params['login_username'], module.params['login_password'], @@ -138,5 +133,6 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main() diff --git a/library/cloud/quantum_floating_ip b/library/cloud/quantum_floating_ip index 9bde712576d..17f78effffd 100644 --- a/library/cloud/quantum_floating_ip +++ b/library/cloud/quantum_floating_ip @@ -220,19 +220,14 @@ def _update_floating_ip(neutron, module, port_id, floating_ip_id): def main(): - module = AnsibleModule( - argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required='True'), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( network_name = dict(required=True), instance_name = dict(required=True), state = dict(default='present', choices=['absent', 'present']), internal_network_name = dict(default=None), - ), - ) + )) + module = AnsibleModule(argument_spec=argument_spec) try: nova = nova_client.Client(module.params['login_username'], module.params['login_password'], @@ -266,5 +261,6 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main() diff --git a/library/cloud/quantum_floating_ip_associate b/library/cloud/quantum_floating_ip_associate index 29df6856571..91df2690b62 100644 --- a/library/cloud/quantum_floating_ip_associate +++ b/library/cloud/quantum_floating_ip_associate @@ -178,18 +178,13 @@ def _update_floating_ip(neutron, module, port_id, floating_ip_id): def main(): - module = AnsibleModule( - argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required='True'), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( ip_address = dict(required=True), instance_name = dict(required=True), state = dict(default='present', choices=['absent', 'present']) - ), - ) + )) + module = AnsibleModule(argument_spec=argument_spec) try: nova = nova_client.Client(module.params['login_username'], module.params['login_password'], @@ -218,5 +213,6 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main() diff --git a/library/cloud/quantum_network b/library/cloud/quantum_network index 744fe44d8dc..606c493f398 100644 --- a/library/cloud/quantum_network +++ b/library/cloud/quantum_network @@ -230,13 +230,8 @@ def _delete_network(module, net_id, neutron): def main(): - module = AnsibleModule( - argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required='True'), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( name = dict(required=True), tenant_name = dict(default=None), provider_network_type = dict(default=None, choices=['local', 'vlan', 'flat', 'gre']), @@ -246,8 +241,8 @@ def main(): shared = dict(default=False, type='bool'), admin_state_up = dict(default=True, type='bool'), state = dict(default='present', choices=['absent', 'present']) - ), - ) + )) + module = AnsibleModule(argument_spec=argument_spec) if module.params['provider_network_type'] in ['vlan' , 'flat']: if not module.params['provider_physical_network']: @@ -279,5 +274,6 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main() diff --git a/library/cloud/quantum_router b/library/cloud/quantum_router index 36e027eccd1..d5f5d56a362 100644 --- a/library/cloud/quantum_router +++ b/library/cloud/quantum_router @@ -175,19 +175,14 @@ def _delete_router(module, neutron, router_id): return True def main(): - module = AnsibleModule( - argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required='True'), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( name = dict(required=True), tenant_name = dict(default=None), state = dict(default='present', choices=['absent', 'present']), admin_state_up = dict(type='bool', default=True), - ), - ) + )) + module = AnsibleModule(argument_spec=argument_spec) neutron = _get_neutron_client(module, module.params) _set_tenant_id(module) @@ -210,5 +205,6 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main() diff --git a/library/cloud/quantum_router_gateway b/library/cloud/quantum_router_gateway index 55295f76e40..5de19fd4785 100644 --- a/library/cloud/quantum_router_gateway +++ b/library/cloud/quantum_router_gateway @@ -174,18 +174,13 @@ def _remove_gateway_router(neutron, module, router_id): def main(): - module = AnsibleModule( - argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required='True'), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( router_name = dict(required=True), network_name = dict(required=True), state = dict(default='present', choices=['absent', 'present']), - ), - ) + )) + module = AnsibleModule(argument_spec=argument_spec) neutron = _get_neutron_client(module, module.params) router_id = _get_router_id(module, neutron) @@ -213,5 +208,6 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main() diff --git a/library/cloud/quantum_router_interface b/library/cloud/quantum_router_interface index 47e5f6b211a..8fdad4f8954 100644 --- a/library/cloud/quantum_router_interface +++ b/library/cloud/quantum_router_interface @@ -208,19 +208,14 @@ def _remove_interface_router(neutron, module, router_id, subnet_id): return True def main(): - module = AnsibleModule( - argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required='True'), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( router_name = dict(required=True), subnet_name = dict(required=True), tenant_name = dict(default=None), state = dict(default='present', choices=['absent', 'present']), - ), - ) + )) + module = AnsibleModule(argument_spec=argument_spec) neutron = _get_neutron_client(module, module.params) _set_tenant_id(module) @@ -249,5 +244,6 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main() diff --git a/library/cloud/quantum_subnet b/library/cloud/quantum_subnet index 5afe566c8f8..5034a736a10 100644 --- a/library/cloud/quantum_subnet +++ b/library/cloud/quantum_subnet @@ -252,13 +252,8 @@ def _delete_subnet(module, neutron, subnet_id): def main(): - module = AnsibleModule( - argument_spec = dict( - login_username = dict(default='admin'), - login_password = dict(required=True), - login_tenant_name = dict(required='True'), - auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), - region_name = dict(default=None), + argument_spec = openstack_argument_spec() + argument_spec.update(dict( name = dict(required=True), network_name = dict(required=True), cidr = dict(required=True), @@ -270,8 +265,8 @@ def main(): dns_nameservers = dict(default=None), allocation_pool_start = dict(default=None), allocation_pool_end = dict(default=None), - ), - ) + )) + module = AnsibleModule(argument_spec=argument_spec) neutron = _get_neutron_client(module, module.params) _set_tenant_id(module) if module.params['state'] == 'present': @@ -291,5 +286,6 @@ def main(): # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * main()