diff options
Diffstat (limited to 'ansible_collections/openstack/cloud/plugins/modules/project.py')
-rw-r--r-- | ansible_collections/openstack/cloud/plugins/modules/project.py | 375 |
1 files changed, 205 insertions, 170 deletions
diff --git a/ansible_collections/openstack/cloud/plugins/modules/project.py b/ansible_collections/openstack/cloud/plugins/modules/project.py index 9719452dc..7db66012a 100644 --- a/ansible_collections/openstack/cloud/plugins/modules/project.py +++ b/ansible_collections/openstack/cloud/plugins/modules/project.py @@ -1,102 +1,111 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + # Copyright (c) 2015 IBM Corporation # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -DOCUMENTATION = ''' +DOCUMENTATION = r''' --- module: project -short_description: Manage OpenStack Projects +short_description: Manage OpenStack Identity (Keystone) projects author: OpenStack Ansible SIG description: - - Manage OpenStack Projects. Projects can be created, - updated or deleted using this module. A project will be updated - if I(name) matches an existing project and I(state) is present. - The value for I(name) cannot be updated without deleting and - re-creating the project. + - Create, update or delete a OpenStack Identity (Keystone) project. options: - name: - description: - - Name for the project - required: true - type: str - description: - description: - - Description for the project - type: str - domain_id: - description: - - Domain id to create the project in if the cloud supports domains. - aliases: ['domain'] - type: str - enabled: - description: - - Is the project enabled - type: bool - default: 'yes' - properties: - description: - - Additional properties to be associated with this project. Requires - openstacksdk>0.45. - type: dict - required: false - state: - description: - - Should the resource be present or absent. - choices: [present, absent] - default: present - type: str -requirements: - - "python >= 3.6" - - "openstacksdk" - + name: + description: + - Name for the project. + - This attribute cannot be updated. + required: true + type: str + description: + description: + - Description for the project. + type: str + domain: + description: + - Domain name or id to create the project in if the cloud supports + domains. + aliases: ['domain_id'] + type: str + extra_specs: + description: + - Additional properties to be associated with this project. + type: dict + aliases: ['properties'] + is_enabled: + description: + - Whether this project is enabled or not. + aliases: ['enabled'] + type: bool + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str extends_documentation_fragment: -- openstack.cloud.openstack + - openstack.cloud.openstack ''' -EXAMPLES = ''' -# Create a project -- openstack.cloud.project: +EXAMPLES = r''' +- name: Create a project + openstack.cloud.project: cloud: mycloud - endpoint_type: admin - state: present - name: demoproject description: demodescription - domain_id: demoid - enabled: True - properties: + domain: demoid + is_enabled: True + name: demoproject + extra_specs: internal_alias: demo_project + state: present -# Delete a project -- openstack.cloud.project: +- name: Delete a project + openstack.cloud.project: cloud: mycloud endpoint_type: admin - state: absent name: demoproject + state: absent ''' - -RETURN = ''' +RETURN = r''' project: - description: Dictionary describing the project. - returned: On success when I(state) is 'present' - type: complex - contains: - id: - description: Project ID - type: str - sample: "f59382db809c43139982ca4189404650" - name: - description: Project name - type: str - sample: "demoproject" - description: - description: Project description - type: str - sample: "demodescription" - enabled: - description: Boolean to indicate if project is enabled - type: bool - sample: True + description: Dictionary describing the project. + returned: On success when I(state) is C(present). + type: dict + contains: + description: + description: Project description + type: str + sample: "demodescription" + domain_id: + description: Domain ID to which the project belongs + type: str + sample: "default" + id: + description: Project ID + type: str + sample: "f59382db809c43139982ca4189404650" + is_domain: + description: Indicates whether the project also acts as a domain. + type: bool + is_enabled: + description: Indicates whether the project is enabled + type: bool + name: + description: Project name + type: str + sample: "demoproject" + options: + description: The resource options for the project + type: dict + parent_id: + description: The ID of the parent of the project + type: str + tags: + description: A list of associated tags + type: list + elements: str ''' from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule @@ -104,111 +113,137 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O class IdentityProjectModule(OpenStackModule): argument_spec = dict( + description=dict(), + domain=dict(aliases=['domain_id']), + extra_specs=dict(type='dict', aliases=['properties']), + is_enabled=dict(type='bool', aliases=['enabled']), name=dict(required=True), - description=dict(required=False), - domain_id=dict(required=False, aliases=['domain']), - properties=dict(required=False, type='dict', min_ver='0.45.1'), - enabled=dict(default=True, type='bool'), state=dict(default='present', choices=['absent', 'present']) ) module_kwargs = dict( supports_check_mode=True ) - def _needs_update(self, project): - keys = ('description', 'enabled') - for key in keys: - if self.params[key] is not None and self.params[key] != project.get(key): - return True - - properties = self.params['properties'] - if properties: - project_properties = project.get('properties') - for k, v in properties.items(): - if v is not None and (k not in project_properties or v != project_properties[k]): - return True - - return False - - def _system_state_change(self, project): - state = self.params['state'] - if state == 'present': - if project is None: - changed = True - else: - if self._needs_update(project): - changed = True - else: - changed = False - - elif state == 'absent': - changed = project is not None - - return changed - def run(self): - name = self.params['name'] - description = self.params['description'] - domain = self.params['domain_id'] - enabled = self.params['enabled'] - properties = self.params['properties'] or {} state = self.params['state'] - if domain: - try: - # We assume admin is passing domain id - dom = self.conn.get_domain(domain)['id'] - domain = dom - except Exception: - # If we fail, maybe admin is passing a domain name. - # Note that domains have unique names, just like id. - try: - dom = self.conn.search_domains(filters={'name': domain})[0]['id'] - domain = dom - except Exception: - # Ok, let's hope the user is non-admin and passing a sane id - pass - - if domain: - project = self.conn.get_project(name, domain_id=domain) - else: - project = self.conn.get_project(name) + project = self._find() if self.ansible.check_mode: - self.exit_json(changed=self._system_state_change(project)) - - if state == 'present': - if project is None: - project = self.conn.create_project( - name=name, description=description, - domain_id=domain, - enabled=enabled) - changed = True - - project = self.conn.update_project( - project['id'], - description=description, - enabled=enabled, - **properties) - else: - if self._needs_update(project): - project = self.conn.update_project( - project['id'], - description=description, - enabled=enabled, - **properties) - changed = True - else: - changed = False - self.exit_json(changed=changed, project=project) - - elif state == 'absent': - if project is None: - changed = False - else: - self.conn.delete_project(project['id']) - changed = True - self.exit_json(changed=changed) + self.exit_json(changed=self._will_change(state, project)) + + if state == 'present' and not project: + # Create project + project = self._create() + self.exit_json(changed=True, + project=project.to_dict(computed=False)) + + elif state == 'present' and project: + # Update project + update = self._build_update(project) + if update: + project = self._update(project, update) + + self.exit_json(changed=bool(update), + project=project.to_dict(computed=False)) + + elif state == 'absent' and project: + # Delete project + self._delete(project) + self.exit_json(changed=True) + + elif state == 'absent' and not project: + # Do nothing + self.exit_json(changed=False) + + def _build_update(self, project): + update = {} + + # Params name and domain are being used to find this project. + + non_updateable_keys = [k for k in [] + if self.params[k] is not None + and self.params[k] != project[k]] + + if non_updateable_keys: + self.fail_json(msg='Cannot update parameters {0}' + .format(non_updateable_keys)) + + attributes = dict((k, self.params[k]) + for k in ['description', 'is_enabled'] + if self.params[k] is not None + and self.params[k] != project[k]) + + extra_specs = self.params['extra_specs'] + if extra_specs: + duplicate_keys = set(attributes.keys()) & set(extra_specs.keys()) + if duplicate_keys: + raise ValueError('Duplicate key(s) in extra_specs: {0}' + .format(', '.join(list(duplicate_keys)))) + for k, v in extra_specs.items(): + if v != project[k]: + attributes[k] = v + + if attributes: + update['attributes'] = attributes + + return update + + def _create(self): + kwargs = dict((k, self.params[k]) + for k in ['description', 'is_enabled', 'name'] + if self.params[k] is not None) + + domain_name_or_id = self.params['domain'] + if domain_name_or_id is not None: + domain = self.conn.identity.find_domain(domain_name_or_id, + ignore_missing=False) + kwargs['domain_id'] = domain.id + + extra_specs = self.params['extra_specs'] + if extra_specs: + duplicate_keys = set(kwargs.keys()) & set(extra_specs.keys()) + if duplicate_keys: + raise ValueError('Duplicate key(s) in extra_specs: {0}' + .format(', '.join(list(duplicate_keys)))) + kwargs = dict(kwargs, **extra_specs) + + return self.conn.identity.create_project(**kwargs) + + def _delete(self, project): + self.conn.identity.delete_project(project.id) + + def _find(self): + name = self.params['name'] + kwargs = {} + + domain_name_or_id = self.params['domain'] + if domain_name_or_id is not None: + domain = self.conn.identity.find_domain(domain_name_or_id, + ignore_missing=False) + kwargs['domain_id'] = domain.id + + return self.conn.identity.find_project(name_or_id=name, + **kwargs) + + def _update(self, project, update): + attributes = update.get('attributes') + if attributes: + project = self.conn.identity.update_project(project.id, + **attributes) + + return project + + def _will_change(self, state, project): + if state == 'present' and not project: + return True + elif state == 'present' and project: + return bool(self._build_update(project)) + elif state == 'absent' and project: + return True + else: + # state == 'absent' and not project: + return False def main(): |