diff --git a/ci/roles/compute_flavor_access/tasks/main.yml b/ci/roles/compute_flavor_access/tasks/main.yml index 996f65b1..6bb31786 100644 --- a/ci/roles/compute_flavor_access/tasks/main.yml +++ b/ci/roles/compute_flavor_access/tasks/main.yml @@ -21,8 +21,8 @@ - name: Verify demo project assert: that: - - projects.openstack_projects|length == 1 - - projects.openstack_projects.0.name == "demo" + - projects.projects|length == 1 + - projects.projects.0.name == "demo" - name: Grant access to flavor openstack.cloud.compute_flavor_access: @@ -47,7 +47,7 @@ assert: that: - (flavor_show.stdout | from_json).name == 'ansible_flavor' - - projects.openstack_projects.0.id in (flavor_show.stdout | from_json).access_project_ids + - projects.projects.0.id in (flavor_show.stdout | from_json).access_project_ids - name: Grant access to flavor again openstack.cloud.compute_flavor_access: diff --git a/ci/roles/image/tasks/main.yml b/ci/roles/image/tasks/main.yml index e89710be..baed225d 100644 --- a/ci/roles/image/tasks/main.yml +++ b/ci/roles/image/tasks/main.yml @@ -239,8 +239,8 @@ state: present name: image_owner_project description: Project owning test image - domain_id: default - enabled: True + domain: default + is_enabled: True register: owner_project - name: Create raw image (owner by project name) @@ -309,7 +309,7 @@ cloud: "{{ cloud }}" state: absent name: image_owner_project - domain_id: default + domain: default - name: Delete test image file file: diff --git a/ci/roles/neutron_rbac_policy/tasks/main.yml b/ci/roles/neutron_rbac_policy/tasks/main.yml index 840095c3..1a39811d 100644 --- a/ci/roles/neutron_rbac_policy/tasks/main.yml +++ b/ci/roles/neutron_rbac_policy/tasks/main.yml @@ -5,8 +5,8 @@ state: present name: ansible_source_project description: Source project for network RBAC test - domain_id: default - enabled: True + domain: default + is_enabled: True register: source_project - name: Create network diff --git a/ci/roles/project/defaults/main.yml b/ci/roles/project/defaults/main.yml index 3a57b6b4..a3f81b6a 100644 --- a/ci/roles/project/defaults/main.yml +++ b/ci/roles/project/defaults/main.yml @@ -1,4 +1,4 @@ -project_fields: +expected_fields: - description - domain_id - id diff --git a/ci/roles/project/tasks/main.yml b/ci/roles/project/tasks/main.yml index ed64b26d..f82a8b4a 100644 --- a/ci/roles/project/tasks/main.yml +++ b/ci/roles/project/tasks/main.yml @@ -1,155 +1,181 @@ --- -- name: Ensure project doesn't exist before tests +- name: Create project openstack.cloud.project: cloud: "{{ cloud }}" - state: absent + state: present name: ansible_project + description: dummy description + domain: default + is_enabled: True + register: project -- block: - - name: Create project - openstack.cloud.project: - cloud: "{{ cloud }}" - state: present - name: ansible_project - description: dummy description - domain: default - enabled: True - register: project +- name: Assert return values of project module + assert: + that: + - project is changed + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(project.project.keys())|length == 0 - - name: Assert project changed - assert: - that: project is changed +- name: Fetch project + openstack.cloud.project_info: + cloud: "{{ cloud }}" + name: ansible_project + register: project - - name: Assert project fields - assert: - that: item in project['project'] - loop: "{{ project_fields }}" +- name: Assert project + assert: + that: + - project.projects | length == 1 + - project.projects.0.name == 'ansible_project' + - project.projects.0.description == 'dummy description' - - name: Get project - openstack.cloud.project_info: - cloud: "{{ cloud }}" - name: ansible_project - register: project_info +- name: Create project again + openstack.cloud.project: + cloud: "{{ cloud }}" + state: present + name: ansible_project + description: dummy description + domain: default + is_enabled: True + register: project - - name: Assert project - assert: - that: - - project_info.openstack_projects | length == 1 - - project_info.openstack_projects[0]['name'] == 'ansible_project' - - project_info.openstack_projects[0]['description'] == 'dummy description' +- name: Assert return values of project module + assert: + that: + - project is not changed + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(project.project.keys())|length == 0 -- block: - - name: Create identical project - openstack.cloud.project: - cloud: "{{ cloud }}" - state: present - name: ansible_project - description: dummy description - domain: default - enabled: True - register: project +- name: Update project + openstack.cloud.project: + cloud: "{{ cloud }}" + state: present + name: ansible_project + description: new description + extra_specs: + tags: + - example_tag + register: project - - name: Assert project not changed - assert: - that: project is not changed +- name: Assert project changed + assert: + that: + - project is changed + - project.project.description == 'new description' - - name: Assert project fields - assert: - that: item in project['project'] - loop: "{{ project_fields }}" +- name: Fetch all projects + openstack.cloud.project_info: + cloud: "{{ cloud }}" + register: projects +- name: Assert return values of project_info module + assert: + that: + - projects.projects | length > 0 + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(projects.projects.0.keys())|length == 0 -- block: - - name: Update project - openstack.cloud.project: - cloud: "{{ cloud }}" - state: present - name: ansible_project - description: new description - properties: - tags: - - example_tag - register: project +- name: Fetch project by name + openstack.cloud.project_info: + cloud: "{{ cloud }}" + name: 'ansible_project' + register: projects - - name: Assert project changed - assert: - that: project is changed +- name: Assert return values of project_info module + assert: + that: + - projects.projects | length == 1 + - projects.projects.0.name == 'ansible_project' - - name: Assert project fields - assert: - that: item in project['project'] - loop: "{{ project_fields }}" +- name: Fetch projects with filter + openstack.cloud.project_info: + cloud: "{{ cloud }}" + filters: + name: 'ansible_project' + register: projects - - name: Get project - openstack.cloud.project_info: - cloud: "{{ cloud }}" - name: ansible_project - register: project_info +- name: Assert return values of project_info module + assert: + that: + - projects.projects | length == 1 + - projects.projects.0.name == 'ansible_project' - - name: Assert project - assert: - that: - - project_info.openstack_projects | length == 1 - - project_info.openstack_projects[0]['description'] == 'new description' +- name: Fetch project by name and domain + openstack.cloud.project_info: + cloud: "{{ cloud }}" + name: 'ansible_project' + domain: 'default' + register: projects -- block: - - name: Delete project - openstack.cloud.project: - cloud: "{{ cloud }}" - state: absent - name: ansible_project - register: project +- name: Assert return values of project_info module + assert: + that: + - projects.projects | length == 1 + - projects.projects.0.name == 'ansible_project' - - name: Assert project changed - assert: - that: project is changed +- name: Delete project + openstack.cloud.project: + cloud: "{{ cloud }}" + state: absent + name: ansible_project + register: project - - name: Get project - openstack.cloud.project_info: - cloud: "{{ cloud }}" - name: ansible_project - register: project_info +- name: Assert project changed + assert: + that: project is changed - - name: Assert project deleted - assert: - that: - - project_info.openstack_projects | length == 0 +- name: Get project + openstack.cloud.project_info: + cloud: "{{ cloud }}" + name: ansible_project + register: project_info +- name: Assert project deleted + assert: + that: + - project_info.projects | length == 0 -- block: - - name: Delete non existant project - openstack.cloud.project: - cloud: "{{ cloud }}" - state: absent - name: ansible_project - register: project +- name: Delete project again + openstack.cloud.project: + cloud: "{{ cloud }}" + state: absent + name: ansible_project + register: project - - name: Assert project not changed - assert: - that: project is not changed +- name: Assert project not changed + assert: + that: project is not changed -- block: - - name: Create project with properties - openstack.cloud.project: - cloud: "{{ cloud }}" - state: present - name: ansible_project - description: dummy description - domain: default - enabled: True - properties: - dummy_key: dummy_value - register: project +- name: Create project with extra_specs + openstack.cloud.project: + cloud: "{{ cloud }}" + state: present + name: ansible_project + extra_specs: + is_enabled: False + register: project -- block: - - name: Update project with properties - openstack.cloud.project: - cloud: "{{ cloud }}" - state: present - name: ansible_project - description: dummy description - domain: default - enabled: True - properties: - dummy_key: other_dummy_value - register: project +- name: Assert return values of project module + assert: + that: + - project.project.is_enabled == False + +- name: Update project with extra_specs + openstack.cloud.project: + cloud: "{{ cloud }}" + state: present + name: ansible_project + extra_specs: + is_enabled: True + register: project + +- name: Assert return values of project module + assert: + that: + - project.project.is_enabled == True + +- name: Delete project + openstack.cloud.project: + cloud: "{{ cloud }}" + state: absent + name: ansible_project diff --git a/ci/roles/project_info/tasks/main.yml b/ci/roles/project_info/tasks/main.yml deleted file mode 100644 index 3c5c1d78..00000000 --- a/ci/roles/project_info/tasks/main.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -- name: List admin project - openstack.cloud.project_info: - cloud: "{{ cloud }}" - name: 'admin' - register: project_admin - -- name: List admin project with filter - openstack.cloud.project_info: - cloud: "{{ cloud }}" - filters: - name: 'admin' - -- name: Check output of list admin project - assert: - that: - - project_admin.openstack_projects | length == 1 - -- name: List all projects - openstack.cloud.project_info: - cloud: "{{ cloud }}" - register: all_projects - -- name: Check output of list all projects - assert: - that: - - all_projects.openstack_projects | length > 0 - -- name: List admin project with domain - openstack.cloud.project_info: - cloud: "{{ cloud }}" - name: 'admin' - domain: 'default' - register: project_domain - -- name: Check output of list admin project with admin domain - assert: - that: - - project_domain.openstack_projects | length == 1 - -- name: Assert fields on SDK 1.* - assert: - that: - - '["description", "domain_id", "is_domain", "is_enabled", "options", - "parent_id", "id", "name", "tags"] | - difference(project_admin.openstack_projects.0.keys()) | length == 0' diff --git a/ci/roles/role_assignment/tasks/main.yml b/ci/roles/role_assignment/tasks/main.yml index 7f1e4201..0ea9e6cf 100644 --- a/ci/roles/role_assignment/tasks/main.yml +++ b/ci/roles/role_assignment/tasks/main.yml @@ -5,9 +5,8 @@ state: present name: ansible_project description: dummy description - domain_id: default - enabled: True - register: project + domain: default + is_enabled: True - name: Grant an admin role on the user admin in the project ansible_project openstack.cloud.role_assignment: diff --git a/ci/roles/router/tasks/shared_network.yml b/ci/roles/router/tasks/shared_network.yml index cc5bd128..a9060c2e 100644 --- a/ci/roles/router/tasks/shared_network.yml +++ b/ci/roles/router/tasks/shared_network.yml @@ -5,8 +5,8 @@ state: present name: "shared_net_test_1" description: "Project that contains the subnet to be shared" - domain_id: default - enabled: True + domain: default + is_enabled: True register: project_1 - name: Create the network to be shared @@ -37,8 +37,8 @@ state: present name: "shared_net_test_2" description: "Project that contains the subnet to be shared" - domain_id: default - enabled: True + domain: default + is_enabled: True register: project_2 - name: Share the network with the second project diff --git a/ci/roles/volume_type_access/tasks/main.yml b/ci/roles/volume_type_access/tasks/main.yml index 1c774d9b..5e16ce7e 100644 --- a/ci/roles/volume_type_access/tasks/main.yml +++ b/ci/roles/volume_type_access/tasks/main.yml @@ -23,8 +23,8 @@ - name: Verify demo project assert: that: - - projects.openstack_projects|length == 1 - - projects.openstack_projects.0.name == "demo" + - projects.projects|length == 1 + - projects.projects.0.name == "demo" - name: Grant access to volume type openstack.cloud.volume_type_access: @@ -49,7 +49,7 @@ assert: that: - (volume_type.stdout | from_json).name == 'ansible_volume_type' - - projects.openstack_projects.0.id in (volume_type.stdout | from_json).access_project_ids + - projects.projects.0.id in (volume_type.stdout | from_json).access_project_ids - name: Grant access to volume type again openstack.cloud.volume_type_access: diff --git a/ci/run-collection.yml b/ci/run-collection.yml index e4f0de2b..6798aa62 100644 --- a/ci/run-collection.yml +++ b/ci/run-collection.yml @@ -42,7 +42,6 @@ - { role: object_container, tags: object_container } - { role: port, tags: port } - { role: project, tags: project } - - { role: project_info, tags: project_info } - { role: recordset, tags: recordset } - { role: role_assignment, tags: role_assignment } - { role: router, tags: router } diff --git a/plugins/modules/project.py b/plugins/modules/project.py index 53a0b2dd..77d92dc1 100644 --- a/plugins/modules/project.py +++ b/plugins/modules/project.py @@ -4,44 +4,40 @@ # 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 + - Name for the project. + - This attribute cannot be updated. required: true type: str description: description: - - Description for the project + - Description for the project. type: str domain: description: - - Domain name or id to create the project in if the cloud supports - domains. + - Domain name or id to create the project in if the cloud supports + domains. aliases: ['domain_id'] type: str - is_enabled: - description: - - Is the project enabled - aliases: ['enabled'] - type: bool - default: 'yes' - properties: + extra_specs: description: - Additional properties to be associated with this project. type: dict - required: false + 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. @@ -49,73 +45,70 @@ options: default: present type: str requirements: - - "python >= 3.6" - - "openstacksdk" - + - "python >= 3.6" + - "openstacksdk" 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: demoid is_enabled: True - properties: + 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: dict - contains: - description: - description: Project description - type: str - sample: "demodescription" - domain_id: - description: domain 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 + 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 @@ -123,98 +116,137 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O class IdentityProjectModule(OpenStackModule): argument_spec = dict( - name=dict(required=True), description=dict(), domain=dict(aliases=['domain_id']), - is_enabled=dict(default=True, type='bool', aliases=['enabled']), - properties=dict(type='dict', min_ver='0.45.1'), + extra_specs=dict(type='dict', aliases=['properties']), + is_enabled=dict(type='bool', aliases=['enabled']), + name=dict(required=True), state=dict(default='present', choices=['absent', 'present']) ) module_kwargs = dict( supports_check_mode=True ) - def _needs_update(self, project, update, extra): - # We cannot update a project name because name find projects by name so - # only a project with an already matching name will be considered for - # updates - keys = ('description', 'is_enabled') - if any((k in update and update[k] != project[k]) for k in keys): - return True - - # Additional keys passed by user will be checked completely - if extra and any(k not in project or extra[k] != project[k] - for k in extra.keys()): - return True - - return False - - def _get_domain_id(self, domain): - dom_obj = self.conn.identity.find_domain(domain) - if dom_obj is None: - # Ok, let's hope the user is non-admin and passing a sane id - return domain - return dom_obj.id - - def _system_state_change(self, state, project, attrs, extra_attrs): - if state == 'present': - if project is None: - return True - return self._needs_update(project, attrs, extra_attrs) - # Else state is absent - return project is not None - def run(self): - name = self.params['name'] - domain = self.params['domain'] state = self.params['state'] - properties = self.params['properties'] - enabled = self.params['is_enabled'] - description = self.params['description'] - find_project_kwargs = {} - domain_id = None - if domain: - domain_id = self._get_domain_id(domain) - find_project_kwargs['domain_id'] = domain_id + project = self._find() - project = None - if name is not None: - project = self.conn.identity.find_project( - name, **find_project_kwargs) + if self.ansible.check_mode: + self.exit_json(changed=self._will_change(state, project)) - project_attrs = { - 'name': name, - 'description': description, - 'is_enabled': enabled, - 'domain_id': domain_id, - } - project_attrs = {k: v for k, v in project_attrs.items() - if v is not None} - # Add in arbitrary properties - if properties: - project_attrs.update(properties) - - if self.check_mode: - self.exit_json(changed=self._system_state_change(state, project, - project_attrs, - properties)) - - changed = False - if state == 'present': - if project is None: - project = self.conn.identity.create_project(**project_attrs) - changed = True - elif self._needs_update(project, project_attrs, properties): - project = self.conn.identity.update_project( - project, **project_attrs) - changed = True - self.exit_json(changed=changed, + if state == 'present' and not project: + # Create project + project = self._create() + self.exit_json(changed=True, project=project.to_dict(computed=False)) - elif state == 'absent' and project is not None: - self.conn.identity.delete_project(project['id']) - changed = True - self.exit_json(changed=changed) + + 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(): diff --git a/plugins/modules/project_info.py b/plugins/modules/project_info.py index a6f46b92..e92aee42 100644 --- a/plugins/modules/project_info.py +++ b/plugins/modules/project_info.py @@ -4,117 +4,97 @@ # Copyright (c) 2016 Hewlett-Packard Enterprise Corporation # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -DOCUMENTATION = ''' +DOCUMENTATION = r''' --- module: project_info short_description: Retrieve information about one or more OpenStack projects author: OpenStack Ansible SIG description: - - Retrieve information about a one or more OpenStack projects + - Retrieve information about a one or more OpenStack projects options: - name: - description: - - Name or ID of the project - type: str - domain: - description: - - Name or ID of the domain containing the project if the cloud supports domains - type: str - filters: - description: - - A dictionary of meta data to use for filtering projects. Elements of - this dictionary are parsed as queries for openstack identity api in - the new openstacksdk. - type: dict + name: + description: + - Name or ID of the project. + type: str + domain: + description: + - Name or ID of the domain containing the project. + type: str + filters: + description: + - A dictionary of meta data to use for filtering projects. + - Elements of I(filters) are passed as query parameters to + OpenStack Identity API. + type: dict requirements: - - "python >= 3.6" - - "openstacksdk" - + - "python >= 3.6" + - "openstacksdk" extends_documentation_fragment: -- openstack.cloud.openstack + - openstack.cloud.openstack ''' -EXAMPLES = ''' -# Gather information about previously created projects -- openstack.cloud.project_info: +EXAMPLES = r''' +- name: Fetch all Identity (Keystone) projects + openstack.cloud.project_info: cloud: awesomecloud - register: result -- debug: - msg: "{{ result.openstack_projects }}" -# Gather information about a previously created project by name -- openstack.cloud.project_info: +- name: Fetch all projects with a name + openstack.cloud.project_info: cloud: awesomecloud name: demoproject - register: result -- debug: - msg: "{{ result.openstack_projects }}" -# Gather information about a previously created project in a specific domain -- openstack.cloud.project_info: +- name: Fetch all projects with a name in a domain + openstack.cloud.project_info: cloud: awesomecloud name: demoproject domain: admindomain - register: result -- debug: - msg: "{{ result.openstack_projects }}" -# Gather information about a previously created project in a specific domain with filter -- openstack.cloud.project_info: +- name: Fetch all disabled projects + openstack.cloud.project_info: cloud: awesomecloud - name: demoproject - domain: admindomain filters: - is_enabled: False - register: result -- debug: - msg: "{{ result.openstack_projects }}" + is_enabled: false ''' - -RETURN = ''' -openstack_projects: - description: has all the OpenStack information about projects - elements: dict - returned: always, but can be empty - type: list - contains: - id: - description: Unique UUID. - returned: success - type: str - name: - description: Name given to the project. - returned: success - type: str - description: - description: Description of the project - returned: success - type: str - is_enabled: - description: Flag to indicate if the project is enabled - returned: success - type: bool - domain_id: - description: Domain ID containing the project (keystone v3 clouds only) - returned: success - type: bool - tags: - description: A list of simple strings assigned to a project - returned: success - type: list - parent_id: - description: The ID of the parent for the project - returned: success - type: str - is_domain: - description: Indicates whether the project also acts as a domain. - returned: success - type: bool - options: - description: Set of options for the project - returned: success - type: dict +RETURN = r''' +projects: + description: List of dictionaries describing Identity (Keystone) projects. + elements: dict + returned: always, but can be empty + type: list + 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 @@ -122,8 +102,8 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O class IdentityProjectInfoModule(OpenStackModule): argument_spec = dict( - name=dict(), domain=dict(), + name=dict(), filters=dict(type='dict'), ) module_kwargs = dict( @@ -131,18 +111,22 @@ class IdentityProjectInfoModule(OpenStackModule): ) def run(self): - name = self.params['name'] - domain = self.params['domain'] filters = self.params['filters'] or {} - if domain: - filters['domain_id'] = self.conn.identity.find_domain( - domain, ignore_missing=False).id + 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) - projects = self.conn.search_projects(name, filters=filters) - projects = [p.to_dict(computed=False) for p in projects] + if not domain: + self.exit_json(changed=False, projects=[]) - self.exit_json(changed=False, openstack_projects=projects) + filters['domain_id'] = domain.id + + projects = self.conn.search_projects(name_or_id=self.params['name'], + filters=filters) + + self.exit_json(changed=False, + projects=[p.to_dict(computed=False) for p in projects]) def main():