Refactored identity_user{,_info} modules

Change-Id: Iae52d1a86f8f78790290be3966681f2277b9701d
This commit is contained in:
Jakob Meng 2023-01-13 12:21:04 +01:00
parent 4a27306440
commit c9afdbfd73
7 changed files with 444 additions and 553 deletions

View File

@ -1,4 +1,4 @@
os_identity_user_fields:
expected_fields:
- default_project_id
- description
- domain_id

View File

@ -1,29 +1,4 @@
---
- name: setup
block:
- name: Delete user before running tests
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: "{{ item }}"
loop:
- ansible_user
- ansible_user2
register: user
- block:
- name: Delete unexistent user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user
register: user
- name: Ensure user was not changed
assert:
that: user is not changed
- block:
- name: Create a user without a password
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
@ -31,17 +6,17 @@
name: ansible_user
email: ansible.user@nowhere.net
domain: default
description: "ansible user"
default_project: demo
register: user
- name: Ensure user was changed
- name: Assert return values of identity_user module
assert:
that: user is changed
- name: Ensure user has fields
assert:
that: item in user['user']
loop: "{{ os_identity_user_fields }}"
that:
- user.user.name == 'ansible_user'
- user.user.description == 'ansible user'
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(user.user.keys())|length == 0
- name: Fail when update_password is always but no password specified
openstack.cloud.identity_user:
@ -55,8 +30,11 @@
register: user
ignore_errors: yes
- assert:
that: user.msg == "update_password is always but a password value is missing"
- name: Assert that update failed
assert:
that:
- user is failed
- user.msg == "update_password is 'always' but password is missing"
- name: Delete user
openstack.cloud.identity_user:
@ -64,7 +42,7 @@
state: absent
name: ansible_user
- block:
- name: Create user with a password
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
@ -75,15 +53,8 @@
update_password: on_create
domain: default
default_project: demo
register: user
- name: Assert user has fields
assert:
that: item in user['user']
loop: "{{ os_identity_user_fields }}"
- block:
- name: Create identical user
- name: Create user with a password again
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
@ -97,14 +68,9 @@
- name: Assert user was not changed
assert:
that: user is not changed
that:
- user is not changed
- name: Assert user has fields
assert:
that: item in user['user']
loop: "{{ os_identity_user_fields }}"
- block:
- name: Update user with password
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
@ -116,16 +82,11 @@
- name: Ensure user was changed
assert:
that: user is changed
- name: Ensure user has fields
assert:
that: item in user['user']
loop: "{{ os_identity_user_fields }}"
that:
- user is changed
- name: Update user without password and update_password set to always
block:
- openstack.cloud.identity_user:
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
@ -134,10 +95,12 @@
register: user
ignore_errors: yes
- assert:
that: user.msg == "update_password is always but a password value is missing"
- name: Assert user update failed
assert:
that:
- user is failed
- user.msg == "update_password is 'always' but password is missing"
- block:
- name: Ensure user with update_password set to on_create
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
@ -150,9 +113,9 @@
- name: Ensure user was not changed
assert:
that: user is not changed
that:
- user is not changed
- block:
- name: Ensure user with update_password set to always
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
@ -165,9 +128,9 @@
- name: Ensure user was changed
assert:
that: user is changed
that:
- user is changed
- block:
- name: Create user without a password
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
@ -180,12 +143,35 @@
default_project: demo
register: user
- name: Assert user has fields
assert:
that: item in user['user']
loop: "{{ os_identity_user_fields }}"
- name: Fetch users
openstack.cloud.identity_user_info:
cloud: "{{ cloud }}"
register: users
- name: Assert return values of identity_user_info module
assert:
that:
- users.users | length > 0
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(users.users.0.keys())|length == 0
- name: Fetch user by name
openstack.cloud.identity_user_info:
cloud: "{{ cloud }}"
name: ansible_user
register: users
- name: Assert named user
assert:
that:
- users.users | length == 1
- name: Delete user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user2
- block:
- name: Delete user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
@ -194,4 +180,39 @@
- name: Ensure user was changed
assert:
that: user is changed
that:
- user is changed
- name: Delete user again
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user
register: user
- name: Ensure user was not changed
assert:
that:
- user is not changed
- name: Fetch ansible_user
openstack.cloud.identity_user_info:
cloud: "{{ cloud }}"
name: ansible_user
register: users
- name: Assert ansible_user does not exist
assert:
that:
- users.users | length == 0
- name: Fetch ansible_user2
openstack.cloud.identity_user_info:
cloud: "{{ cloud }}"
name: ansible_user2
register: users
- name: Assert ansible_user2 does not exist
assert:
that:
- users.users | length == 0

View File

@ -1,11 +0,0 @@
os_expected_user_info_fields:
- default_project_id
- description
- domain_id
- email
- id
- is_enabled
- links
- name
- password
- password_expires_at

View File

@ -1,69 +0,0 @@
- name: Ensure user does not exist before tests
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user
- block:
- name: Get unexistent user
openstack.cloud.identity_user_info:
cloud: "{{ cloud }}"
name: ansible_user
register: userinfo
- name: Ensure nothing was returned
assert:
that: not userinfo.users
- block:
- name: Create user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
password: secret
email: ansible.user@nowhere.net
domain: default
default_project: demo
register: user
- name: Create second user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user2
password: secret
email: ansible.user2@nowhere.net
domain: default
default_project: demo
register: user
- name: Get first user info
openstack.cloud.identity_user_info:
cloud: "{{ cloud }}"
name: ansible_user
register: userinfo
- name: Assert only one result exists
assert:
that: "{{ userinfo.users | length }} == 1"
- name: Assert userinfo has fields
assert:
that: item in userinfo.users[0]
loop: "{{ os_expected_user_info_fields }}"
- block:
- name: Get all users
openstack.cloud.identity_user_info:
cloud: "{{ cloud }}"
register: userinfo
- name: Assert results were returned
assert:
that: "{{ userinfo.users | length }} > 0"
- name: Post-test cleanup
block:
- name: Ensure users do not exist
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: "{{ item }}"
loop:
- ansible_user
- ansible_user2

View File

@ -21,7 +21,6 @@
- { role: identity_domain, tags: identity_domain }
- { role: identity_group, tags: identity_group }
- { role: identity_user, tags: identity_user }
- { role: identity_user_info, tags: identity_user_info }
- { role: identity_role, tags: identity_role }
- { role: image, tags: image }
- { role: keypair, tags: keypair }

View File

@ -4,69 +4,68 @@
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: identity_user
short_description: Manage OpenStack Identity Users
short_description: Manage a OpenStack identity (Keystone) user
author: OpenStack Ansible SIG
description:
- Manage OpenStack Identity users. Users can be created,
updated or deleted using this module. A user will be updated
if I(name) matches an existing user and I(state) is present.
The value for I(name) cannot be updated without deleting and
re-creating the user.
- Create, update or delete a OpenStack identity (Keystone) user.
options:
default_project:
description:
- Name or ID of the project, the user should be created in.
type: str
description:
description:
- Description about the user.
type: str
domain:
description:
- Domain to create the user in if the cloud supports domains.
type: str
email:
description:
- Email address for the user.
type: str
is_enabled:
description:
- Whether the user is enabled or not.
type: bool
default: 'yes'
aliases: ['enabled']
name:
description:
- Username for the user
- Name of the user.
- I(name) cannot be updated without deleting and re-creating the user.
required: true
type: str
password:
description:
- Password for the user
- Password for the user.
type: str
update_password:
required: false
choices: ['always', 'on_create']
default: on_create
description:
- C(always) will attempt to update password. C(on_create) will only
set the password for newly created users.
type: str
email:
description:
- Email address for the user
type: str
description:
description:
- Description about the user
type: str
default_project:
description:
- Project name or ID that the user should be associated with by default
type: str
domain:
description:
- Domain to create the user in if the cloud supports domains
type: str
enabled:
description:
- Is the user enabled
type: bool
default: 'yes'
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
update_password:
choices: ['always', 'on_create']
default: on_create
description:
- When I(update_password) is C(always), then the password will always be
updated.
- When I(update_password) is C(on_create), the the password is only set
when creating a user.
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a user
- openstack.cloud.identity_user:
EXAMPLES = r'''
- name: Create a user
openstack.cloud.identity_user:
cloud: mycloud
state: present
name: demouser
@ -75,14 +74,14 @@ EXAMPLES = '''
domain: default
default_project: demo
# Delete a user
- openstack.cloud.identity_user:
- name: Delete a user
openstack.cloud.identity_user:
cloud: mycloud
state: absent
name: demouser
# Create a user but don't update password if user exists
- openstack.cloud.identity_user:
- name: Create a user but don't update password if user exists
openstack.cloud.identity_user:
cloud: mycloud
state: present
name: demouser
@ -92,8 +91,8 @@ EXAMPLES = '''
domain: default
default_project: demo
# Create a user without password
- openstack.cloud.identity_user:
- name: Create a user without password
openstack.cloud.identity_user:
cloud: mycloud
state: present
name: demouser
@ -102,36 +101,30 @@ EXAMPLES = '''
default_project: demo
'''
RETURN = '''
RETURN = r'''
user:
description: Dictionary describing the user.
returned: On success when I(state) is 'present'
description: Dictionary describing the identity user.
returned: On success when I(state) is C(present).
type: dict
contains:
default_project_id:
description: User default project ID. Only present with Keystone >= v3.
returned: success
type: str
sample: "4427115787be45f08f0ec22a03bfc735"
description:
description: The description of this user
returned: success
type: str
sample: "a user"
domain_id:
description: User domain ID. Only present with Keystone >= v3.
returned: success
type: str
sample: "default"
email:
description: User email address
returned: success
type: str
sample: "demo@example.com"
id:
description: User ID
returned: success
type: str
sample: "f59382db809c43139982ca4189404650"
is_enabled:
@ -139,121 +132,106 @@ user:
type: bool
links:
description: The links for the user resource
returned: success
type: dict
elements: str
name:
description: Unique user name, within the owning domain
returned: success
type: str
sample: "demouser"
password:
description: Credential used during authentication
returned: success
type: str
password_expires_at:
description: The date and time when the password expires. The time zone is UTC. A none value means the password never expires
returned: success
description: The date and time when the password expires. The time zone
is UTC. A none value means the password never expires
type: str
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
from ansible_collections.openstack.cloud.plugins.module_utils.resource import StateMachine
class IdentityUserModule(OpenStackModule):
argument_spec = dict(
name=dict(required=True),
password=dict(no_log=True),
email=dict(),
default_project=dict(),
description=dict(),
domain=dict(),
enabled=dict(default=True, type='bool'),
email=dict(),
is_enabled=dict(default=True, type='bool', aliases=['enabled']),
name=dict(required=True),
password=dict(no_log=True),
state=dict(default='present', choices=['absent', 'present']),
update_password=dict(default='on_create', choices=['always', 'on_create']),
update_password=dict(default='on_create',
choices=['always', 'on_create']),
)
module_kwargs = dict()
def _needs_update(self, params_dict, user):
for k in params_dict:
# We don't get password back in the user object, so assume any supplied
# password is a change.
if k == 'password':
return True
if user[k] != params_dict[k]:
return True
return False
class _StateMachine(StateMachine):
def _build_update(self, resource, attributes, updateable_attributes,
non_updateable_attributes,
update_password='on_create', **kwargs):
if update_password == 'always' and 'password' not in attributes:
self.ansible.fail_json(msg="update_password is 'always'"
" but password is missing")
elif update_password == 'on_create' and 'password' in attributes:
attributes.pop('password')
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
return super()._build_update(resource, attributes,
updateable_attributes,
non_updateable_attributes, **kwargs)
def _get_default_project_id(self, default_project, domain_id):
project = self.conn.identity.find_project(default_project, domain_id=domain_id)
if not project:
self.fail_json(msg='Default project %s is not valid' % default_project)
return project['id']
def _find(self, attributes, **kwargs):
query_args = dict((k, attributes[k])
for k in ['domain_id']
if k in attributes and attributes[k] is not None)
return self.find_function(attributes['name'], **query_args)
def run(self):
name = self.params['name']
password = self.params.get('password')
email = self.params['email']
default_project = self.params['default_project']
domain = self.params['domain']
enabled = self.params['enabled']
state = self.params['state']
update_password = self.params['update_password']
description = self.params['description']
sm = self._StateMachine(connection=self.conn,
service_name='identity',
type_name='user',
sdk=self.sdk,
ansible=self.ansible)
domain_id = None
if domain:
domain_id = self._get_domain_id(domain)
user = self.conn.identity.find_user(name, domain_id=domain_id)
kwargs = dict((k, self.params[k])
for k in ['state', 'timeout', 'update_password']
if self.params[k] is not None)
changed = False
if state == 'present':
user_args = {
'name': name,
'email': email,
'domain_id': domain_id,
'description': description,
'is_enabled': enabled,
}
if default_project:
default_project_id = self._get_default_project_id(
default_project, domain_id)
user_args['default_project_id'] = default_project_id
user_args = {k: v for k, v in user_args.items() if v is not None}
kwargs['attributes'] = \
dict((k, self.params[k])
for k in ['description', 'email', 'is_enabled', 'name',
'password']
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['attributes']['domain_id'] = domain.id
default_project_name_or_id = self.params['default_project']
if default_project_name_or_id is not None:
query_args = dict((k, kwargs['attributes'][k])
for k in ['domain_id']
if k in kwargs['attributes']
and kwargs['attributes'][k] is not None)
project = self.conn.identity.find_project(
default_project_name_or_id, ignore_missing=False, **query_args)
kwargs['attributes']['default_project_id'] = project.id
user, is_changed = sm(check_mode=self.ansible.check_mode,
updateable_attributes=None,
non_updateable_attributes=['domain_id'],
wait=False,
**kwargs)
changed = False
if user is None:
if password:
user_args['password'] = password
user = self.conn.identity.create_user(**user_args)
changed = True
self.exit_json(changed=is_changed)
else:
if update_password == 'always':
if not password:
self.fail_json(msg="update_password is always but a password value is missing")
user_args['password'] = password
# else we do not want to update the password
if self._needs_update(user_args, user):
user = self.conn.identity.update_user(user['id'], **user_args)
changed = True
user = user.to_dict(computed=False)
self.exit_json(changed=changed, user=user)
elif state == 'absent' and user is not None:
self.conn.identity.delete_user(user)
changed = True
self.exit_json(changed=changed)
self.exit_json(changed=is_changed,
user=user.to_dict(computed=False))
def main():

View File

@ -4,125 +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: identity_user_info
short_description: Retrieve information about one or more OpenStack users
short_description: Fetch OpenStack identity (Keystone) users
author: OpenStack Ansible SIG
description:
- Retrieve information about a one or more OpenStack users
- Fetch OpenStack identity (Keystone) users.
options:
name:
description:
- Name or ID of the user
type: str
domain:
description:
- Name or ID of the domain containing the user if the cloud supports domains
- Name or ID of the domain containing the user.
type: str
filters:
description:
- A dictionary of meta data to use for further filtering. Elements of
this dictionary may be additional dictionaries.
type: dict
default: {}
name:
description:
- Name or ID of the user.
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Gather information about previously created users
- openstack.cloud.identity_user_info:
EXAMPLES = r'''
- name: Gather previously created users
openstack.cloud.identity_user_info:
cloud: awesomecloud
register: result
- debug:
msg: "{{ result.users }}"
# Gather information about a previously created user by name
- openstack.cloud.identity_user_info:
- name: Gather previously created user by name
openstack.cloud.identity_user_info:
cloud: awesomecloud
name: demouser
register: result
- debug:
msg: "{{ result.users }}"
# Gather information about a previously created user in a specific domain
- openstack.cloud.identity_user_info:
- name: Gather previously created user in a specific domain
openstack.cloud.identity_user_info:
cloud: awesomecloud
name: demouser
domain: admindomain
register: result
- debug:
msg: "{{ result.users }}"
# Gather information about a previously created user in a specific domain with filter
- openstack.cloud.identity_user_info:
- name: Gather previously created user with filters
openstack.cloud.identity_user_info:
cloud: awesomecloud
name: demouser
domain: admindomain
filters:
enabled: False
register: result
- debug:
msg: "{{ result.users }}"
is_enabled: False
'''
RETURN = '''
RETURN = r'''
users:
description: has all the OpenStack information about users
description: Dictionary describing all matching identity users.
returned: always
type: list
elements: dict
contains:
id:
description: Unique UUID.
returned: success
type: str
name:
description: Username of the user.
returned: success
type: str
default_project_id:
description: Default project ID of the user
returned: success
type: str
description:
description: The description of this user
returned: success
type: str
domain_id:
description: Domain ID containing the user
returned: success
type: str
email:
description: Email of the user
returned: success
type: str
is_enabled:
description: Flag to indicate if the user is enabled
returned: success
type: bool
links:
description: The links for the user resource
returned: success
type: complex
contains:
self:
description: Link to this user resource
returned: success
type: str
type: dict
password:
description: The default form of credential used during authentication.
returned: success
type: str
password_expires_at:
description: The date and time when the password expires. The time zone is UTC. A Null value means the password never expires.
returned: success
description: The date and time when the password expires. The time zone
is UTC. A Null value means the password never expires.
type: str
username:
description: Username with Identity API v2 (OpenStack Pike or earlier) else Null
returned: success
description: Username with Identity API v2 (OpenStack Pike or earlier)
else Null.
type: str
'''
@ -131,9 +103,9 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
class IdentityUserInfoModule(OpenStackModule):
argument_spec = dict(
name=dict(),
domain=dict(),
filters=dict(type='dict', default={}),
filters=dict(type='dict'),
name=dict(),
)
module_kwargs = dict(
supports_check_mode=True
@ -141,19 +113,20 @@ class IdentityUserInfoModule(OpenStackModule):
def run(self):
name = self.params['name']
domain = self.params['domain']
filters = self.params['filters']
filters = self.params['filters'] or {}
args = {}
if domain:
dom_obj = self.conn.identity.find_domain(domain)
if dom_obj is None:
self.fail_json(
msg="Domain name or ID '{0}' does not exist".format(domain))
args['domain_id'] = dom_obj.id
kwargs = {}
domain_name_or_id = self.params['domain']
if domain_name_or_id:
domain = self.conn.identity.find_domain(domain_name_or_id)
if domain is None:
self.exit_json(changed=False, groups=[])
kwargs['domain_id'] = domain['id']
users = [user.to_dict(computed=False) for user in self.conn.search_users(name, filters, **args)]
self.exit_json(changed=False, users=users)
self.exit_json(changed=False,
users=[u.to_dict(computed=False)
for u in self.conn.search_users(name, filters,
**kwargs)])
def main():