summaryrefslogtreecommitdiffstats
path: root/ansible_collections/openstack/cloud/plugins/modules/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/openstack/cloud/plugins/modules/project.py')
-rw-r--r--ansible_collections/openstack/cloud/plugins/modules/project.py375
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():