1b38b7c500
With "extends_documentation_fragment: ['openstack.cloud.openstack']" it is not necessary to list required Python libraries in section 'requirements' of DOCUMENTATION docstring in modules. Ansible will merge requirements from doc fragments and DOCUMENTATION docstring which previously resulted in duplicates such as in server module [0]: * openstacksdk * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 When removing the 'requirements' section from server module, then Ansible will list openstacksdk once only: * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 To see what documentation Ansible will produce for server module run: ansible-doc --type module openstack.cloud.server [0] https://docs.ansible.com/ansible/latest/collections/openstack/\ cloud/server_module.html Change-Id: I727ed95ee480bb644b5a533f6a9526973677064c
256 lines
7.5 KiB
Python
256 lines
7.5 KiB
Python
#!/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 = r'''
|
|
---
|
|
module: project
|
|
short_description: Manage OpenStack Identity (Keystone) projects
|
|
author: OpenStack Ansible SIG
|
|
description:
|
|
- Create, update or delete a OpenStack Identity (Keystone) project.
|
|
options:
|
|
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
|
|
'''
|
|
|
|
EXAMPLES = r'''
|
|
- name: Create a project
|
|
openstack.cloud.project:
|
|
cloud: mycloud
|
|
description: demodescription
|
|
domain: demoid
|
|
is_enabled: True
|
|
name: demoproject
|
|
extra_specs:
|
|
internal_alias: demo_project
|
|
state: present
|
|
|
|
- name: Delete a project
|
|
openstack.cloud.project:
|
|
cloud: mycloud
|
|
endpoint_type: admin
|
|
name: demoproject
|
|
state: absent
|
|
'''
|
|
|
|
RETURN = r'''
|
|
project:
|
|
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
|
|
|
|
|
|
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),
|
|
state=dict(default='present', choices=['absent', 'present'])
|
|
)
|
|
module_kwargs = dict(
|
|
supports_check_mode=True
|
|
)
|
|
|
|
def run(self):
|
|
state = self.params['state']
|
|
|
|
project = self._find()
|
|
|
|
if self.ansible.check_mode:
|
|
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():
|
|
module = IdentityProjectModule()
|
|
module()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|