Repository: ansible/ansible

Issue:
Create a module for Keystone endpoints
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
os_keystone_endpoint


##### SUMMARY
<!--- Explain the problem briefly -->
The OpenStack Keystone modules have support for creating domains, services and roles.
Would be good to also have a module for creating Keystone endpoints.


Discussion:

Resolving patch:
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
--- /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()


Test patch: