From 3e7b240696f855b5e2bff06eb74b399c7fc665d8 Mon Sep 17 00:00:00 2001 From: Alberto Murillo Date: Thu, 28 Sep 2017 16:05:44 -0500 Subject: [PATCH] os_keystone_endpoint.py (#29031) * Add os_keystone_service_endpoint This patch adds a new Ansible module which allows a user to create an endpoint to a service with Keystone. Fixes #23909 * os_keystone_endpoint: Fix style and messages Fix comments, pep8, version, metadata, license header and imports according to the Contributing Modules Checklist Signed-off-by: Alberto Murillo * os_keystone_endpoint: Fix return values - Change type of 'endpoint' return value from dictionary to complex in order to get validate_module checks passed. - Remove 'id' from the return data since it is included inside the 'endpoint' value wich is already being returned. - Rename 'service' field to 'service_id' which is the correct name for the service id field returned in json. Signed-off-by: Alberto Murillo * os_keystone_endpoint: Update shade version Update minimum shade version to 1.11.0 Signed-off-by: Alberto Murillo * os_keystone_endpoint: Make region optional Signed-off-by: Alberto Murillo * os_keystone_endpoint: Validate service exists before using service.id Signed-off-by: Alberto Murillo * os_keystone_endpoint: Fix documentation for service to accept name or id Signed-off-by: Alberto Murillo * os_keystone_endpoint: Pass the full service object to create_endpoint() We already have the service object retrieved in code, by passing service.id to create_endpoint, the shade librarie queries the api again to get the full service object. By Passing the already rerieved service object to create_endpoint() we save one request to the API. Signed-off-by: Alberto Murillo * os_keystone_endpoint: Make type explicit in module arguments. Althoug type is default to str when not specified in module arguments this commit explicitly defines type='str' for better readability. Signed-off-by: Alberto Murillo --- .../cloud/openstack/os_keystone_endpoint.py | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 lib/ansible/modules/cloud/openstack/os_keystone_endpoint.py diff --git a/lib/ansible/modules/cloud/openstack/os_keystone_endpoint.py b/lib/ansible/modules/cloud/openstack/os_keystone_endpoint.py new file mode 100644 index 0000000000..2bd9a81138 --- /dev/null +++ b/lib/ansible/modules/cloud/openstack/os_keystone_endpoint.py @@ -0,0 +1,218 @@ +#!/usr/bin/python + +# Copyright: (c) 2017, VEXXHOST, Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: os_keystone_endpoint +short_description: Manage OpenStack Identity service endpoints +extends_documentation_fragment: openstack +author: + - Mohammed Naser (@mnaser) + - Alberto Murillo (@albertomurillo) +version_added: "2.5" +description: + - Create, update, or delete OpenStack Identity service endpoints. If a + service with the same combination of I(service), I(interface) and I(region) + exist, the I(url) and I(state) (C(present) or C(absent)) will be updated. +options: + service: + description: + - Name or id of the service. + required: true + interface: + description: + - Interface of the service. + choices: [admin, public, internal] + required: true + url: + description: + - URL of the service. + required: true + region: + description: + - Region that the service belongs to. Note that I(region_name) is used for authentication. + enabled: + description: + - Is the service enabled. + default: True + state: + description: + - Should the resource be C(present) or C(absent). + choices: [present, absent] + default: present +requirements: + - shade >= 1.11.0 +''' + +EXAMPLES = ''' +- name: Create a service for glance + os_keystone_endpoint: + cloud: mycloud + service: glance + interface: public + url: http://controller:9292 + region: RegionOne + state: present + +- name: Delete a service for nova + os_keystone_endpoint: + cloud: mycloud + service: nova + interface: public + region: RegionOne + state: absent +''' + +RETURN = ''' +endpoint: + description: Dictionary describing the endpoint. + returned: On success when I(state) is C(present) + type: complex + contains: + id: + description: Endpoint ID. + type: string + sample: 3292f020780b4d5baf27ff7e1d224c44 + region: + description: Region Name. + type: string + sample: RegionOne + service_id: + description: Service ID. + type: string + sample: b91f1318f735494a825a55388ee118f3 + interface: + description: Endpoint Interface. + type: string + sample: public + url: + description: Service URL. + type: string + sample: http://controller:9292 + enabled: + description: Service status. + type: boolean + sample: True +''' + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +from distutils.version import StrictVersion +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.openstack import openstack_full_argument_spec +from ansible.module_utils.openstack import openstack_module_kwargs + + +def _needs_update(module, endpoint): + if endpoint.enabled != module.params['enabled']: + return True + if endpoint.url != module.params['url']: + return True + return False + + +def _system_state_change(module, endpoint): + state = module.params['state'] + if state == 'absent' and endpoint: + return True + + if state == 'present': + if endpoint is None: + return True + return _needs_update(module, endpoint) + + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + service=dict(type='str', required=True), + interface=dict(type='str', required=True, choices=['admin', 'public', 'internal']), + url=dict(type='str', required=True), + region=dict(type='str'), + enabled=dict(type='bool', default=True), + state=dict(type='str', default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_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') + if StrictVersion(shade.__version__) < StrictVersion('1.11.0'): + module.fail_json(msg="To utilize this module, the installed version of" + "the shade library MUST be >=1.11.0") + + service_name_or_id = module.params['service'] + interface = module.params['interface'] + url = module.params['url'] + region = module.params['region'] + enabled = module.params['enabled'] + state = module.params['state'] + + try: + cloud = shade.operator_cloud(**module.params) + + service = cloud.get_service(service_name_or_id) + if service is None: + module.fail_json(msg='Service %s does not exist' % service_name_or_id) + + filters = dict(service_id=service.id, interface=interface) + if region is not None: + filters['region'] = region + endpoints = cloud.search_endpoints(filters=filters) + + if len(endpoints) > 1: + module.fail_json(msg='Service %s, interface %s and region %s are ' + 'not unique' % + (service_name_or_id, interface, region)) + elif len(endpoints) == 1: + endpoint = endpoints[0] + else: + endpoint = None + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, endpoint)) + + if state == 'present': + if endpoint is None: + result = cloud.create_endpoint(service_name_or_id=service, + url=url, interface=interface, + region=region, enabled=enabled) + endpoint = result[0] + changed = True + else: + if _needs_update(module, endpoint): + endpoint = cloud.update_endpoint( + endpoint.id, url=url, enabled=enabled) + changed = True + else: + changed = False + module.exit_json(changed=changed, endpoint=endpoint) + + elif state == 'absent': + if endpoint is None: + changed = False + else: + cloud.delete_endpoint(endpoint.id) + changed = True + module.exit_json(changed=changed) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main()