Use proxy layer in identity_user module

This patch changes the module to use the sdk proxy layer and does some
general refactoring to simplify the code. It will no longer fail if
no password is supplied since it is perfectly fine to create a user
with an password.

Renamed the test role from user to identity_user to match the module
name

Change-Id: I97ee9b626f269abde3be7b2b9211d2bb5b7b3c26
This commit is contained in:
Rafael Castillo 2022-02-10 09:44:04 -07:00
parent a8589e9f4d
commit fd1b9fc0d2
6 changed files with 281 additions and 122 deletions

View File

@ -70,6 +70,7 @@
dns_zone_info dns_zone_info
floating_ip_info floating_ip_info
group group
identity_user
identity_user_info identity_user_info
identity_role identity_role
image image

View File

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

View File

@ -0,0 +1,197 @@
---
- 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 }}"
state: present
name: ansible_user
email: ansible.user@nowhere.net
domain: default
default_project: demo
register: user
- 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 }}"
- name: Fail when update_password is always but no password specified
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
update_password: always
email: ansible.user@nowhere.net
domain: default
default_project: demo
register: user
ignore_errors: yes
- assert:
that: user.msg == "update_password is always but a password value is missing"
- name: Delete user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user
- block:
- name: Create user with a password
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
password: secret
email: ansible.user@nowhere.net
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
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
password: secret
email: ansible.user@nowhere.net
update_password: on_create
domain: default
default_project: demo
register: user
- name: Assert user was not changed
assert:
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 }}"
state: present
name: ansible_user
password: secret2
email: updated.ansible.user@nowhere.net
register: user
- 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 }}"
- name: Update user without password and update_password set to always
block:
- openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
update_password: always
email: updated.ansible.user@nowhere.net
register: user
ignore_errors: yes
- assert:
that: user.msg == "update_password is always but a password value is missing"
- block:
- name: Ensure user with update_password set to on_create
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
update_password: on_create
password: secret3
email: updated.ansible.user@nowhere.net
register: user
- name: Ensure user was not changed
assert:
that: user is not changed
- block:
- name: Ensure user with update_password set to always
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
update_password: always
password: secret3
email: updated.ansible.user@nowhere.net
register: user
- name: Ensure user was changed
assert:
that: user is changed
- block:
- name: Create user without a password
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user2
password: secret
email: ansible.user2@nowhere.net
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: Delete user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user
- name: Ensure user was changed
assert:
that: user is changed

View File

@ -1,30 +0,0 @@
---
- 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
- debug: var=user
- name: Update user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
password: secret
email: updated.ansible.user@nowhere.net
register: updateduser
- debug: var=updateduser
- name: Delete user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user

View File

@ -16,6 +16,7 @@
tags: dns tags: dns
when: sdk_version is version(0.28, '>=') when: sdk_version is version(0.28, '>=')
- { role: floating_ip_info, tags: floating_ip_info } - { role: floating_ip_info, tags: floating_ip_info }
- { role: identity_user, tags: identity_user }
- { role: identity_user_info, tags: identity_user_info } - { role: identity_user_info, tags: identity_user_info }
- { role: identity_role, tags: identity_role } - { role: identity_role, tags: identity_role }
- { role: image, tags: image } - { role: image, tags: image }
@ -49,7 +50,6 @@
- { role: server, tags: server } - { role: server, tags: server }
- { role: subnet, tags: subnet } - { role: subnet, tags: subnet }
- { role: subnet_pool, tags: subnet_pool } - { role: subnet_pool, tags: subnet_pool }
- { role: user, tags: user }
- { role: user_group, tags: user_group } - { role: user_group, tags: user_group }
- { role: user_role, tags: user_role } - { role: user_role, tags: user_role }
- { role: volume, tags: volume } - { role: volume, tags: volume }

View File

@ -26,6 +26,7 @@ options:
update_password: update_password:
required: false required: false
choices: ['always', 'on_create'] choices: ['always', 'on_create']
default: on_create
description: description:
- C(always) will attempt to update password. C(on_create) will only - C(always) will attempt to update password. C(on_create) will only
set the password for newly created users. set the password for newly created users.
@ -108,28 +109,55 @@ RETURN = '''
user: user:
description: Dictionary describing the user. description: Dictionary describing the user.
returned: On success when I(state) is 'present' returned: On success when I(state) is 'present'
type: complex type: dict
contains: contains:
default_project_id: default_project_id:
description: User default project ID. Only present with Keystone >= v3. description: User default project ID. Only present with Keystone >= v3.
returned: success
type: str type: str
sample: "4427115787be45f08f0ec22a03bfc735" sample: "4427115787be45f08f0ec22a03bfc735"
description:
description: The description of this user
returned: success
type: str
sample: "a user"
domain_id: domain_id:
description: User domain ID. Only present with Keystone >= v3. description: User domain ID. Only present with Keystone >= v3.
returned: success
type: str type: str
sample: "default" sample: "default"
email: email:
description: User email address description: User email address
returned: success
type: str type: str
sample: "demo@example.com" sample: "demo@example.com"
id: id:
description: User ID description: User ID
returned: success
type: str type: str
sample: "f59382db809c43139982ca4189404650" sample: "f59382db809c43139982ca4189404650"
is_enabled:
description: Indicates whether the user is enabled
type: bool
links:
description: The links for the user resource
returned: success
type: dict
elements: str
name: name:
description: User name description: Unique user name, within the owning domain
returned: success
type: str type: str
sample: "demouser" 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
type: str
''' '''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
@ -145,46 +173,32 @@ class IdentityUserModule(OpenStackModule):
domain=dict(required=False, default=None), domain=dict(required=False, default=None),
enabled=dict(default=True, type='bool'), enabled=dict(default=True, type='bool'),
state=dict(default='present', choices=['absent', 'present']), state=dict(default='present', choices=['absent', 'present']),
update_password=dict(default=None, choices=['always', 'on_create']), update_password=dict(default='on_create', choices=['always', 'on_create']),
) )
module_kwargs = dict() module_kwargs = dict()
def _needs_update(self, params_dict, user): def _needs_update(self, params_dict, user):
for k in params_dict: for k in params_dict:
if k not in ('password', 'update_password') and user[k] != params_dict[k]: # 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 True
# We don't get password back in the user object, so assume any supplied
# password is a change.
if (
params_dict['password'] is not None
and params_dict['update_password'] == 'always'
):
return True
return False return False
def _get_domain_id(self, domain): def _get_domain_id(self, domain):
try: dom_obj = self.conn.identity.find_domain(domain)
# We assume admin is passing domain id if dom_obj is None:
domain_id = self.conn.get_domain(domain)['id'] # Ok, let's hope the user is non-admin and passing a sane id
except Exception: return domain
# If we fail, maybe admin is passing a domain name. return dom_obj.id
# Note that domains have unique names, just like id.
try:
domain_id = self.conn.search_domains(filters={'name': domain})[0]['id']
except Exception:
# Ok, let's hope the user is non-admin and passing a sane id
domain_id = domain
return domain_id
def _get_default_project_id(self, default_project, domain_id): def _get_default_project_id(self, default_project, domain_id):
project = self.conn.get_project(default_project, domain_id=domain_id) project = self.conn.identity.find_project(default_project, domain_id=domain_id)
if not project: if not project:
self.fail_json(msg='Default project %s is not valid' % default_project) self.fail_json(msg='Default project %s is not valid' % default_project)
return project['id'] return project['id']
def run(self): def run(self):
@ -201,81 +215,47 @@ class IdentityUserModule(OpenStackModule):
domain_id = None domain_id = None
if domain: if domain:
domain_id = self._get_domain_id(domain) domain_id = self._get_domain_id(domain)
user = self.conn.get_user(name, domain_id=domain_id) user = self.conn.identity.find_user(name, domain_id=domain_id)
else:
user = self.conn.get_user(name)
changed = False
if state == 'present': if state == 'present':
if update_password in ('always', 'on_create'): user_args = {
if not password: 'name': name,
msg = "update_password is %s but a password value is missing" % update_password 'email': email,
self.fail_json(msg=msg) 'domain_id': domain_id,
default_project_id = None 'description': description,
'is_enabled': enabled,
}
if default_project: if default_project:
default_project_id = self._get_default_project_id( default_project_id = self._get_default_project_id(
default_project, domain_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}
changed = False
if user is None: if user is None:
if description is not None: if password:
user = self.conn.create_user( user_args['password'] = password
name=name, password=password, email=email,
default_project=default_project_id, domain_id=domain_id, user = self.conn.identity.create_user(**user_args)
enabled=enabled, description=description)
else:
user = self.conn.create_user(
name=name, password=password, email=email,
default_project=default_project_id, domain_id=domain_id,
enabled=enabled)
changed = True changed = True
else: else:
params_dict = {'email': email, 'enabled': enabled, if update_password == 'always':
'password': password, if not password:
'update_password': update_password} self.fail_json(msg="update_password is always but a password value is missing")
if description is not None: user_args['password'] = password
params_dict['description'] = description # else we do not want to update the password
if domain_id is not None:
params_dict['domain_id'] = domain_id
if default_project_id is not None:
params_dict['default_project_id'] = default_project_id
if self._needs_update(params_dict, user): if self._needs_update(user_args, user):
if update_password == 'always': user = self.conn.identity.update_user(user['id'], **user_args)
if description is not None:
user = self.conn.update_user(
user['id'], password=password, email=email,
default_project=default_project_id,
domain_id=domain_id, enabled=enabled, description=description)
else:
user = self.conn.update_user(
user['id'], password=password, email=email,
default_project=default_project_id,
domain_id=domain_id, enabled=enabled)
else:
if description is not None:
user = self.conn.update_user(
user['id'], email=email,
default_project=default_project_id,
domain_id=domain_id, enabled=enabled, description=description)
else:
user = self.conn.update_user(
user['id'], email=email,
default_project=default_project_id,
domain_id=domain_id, enabled=enabled)
changed = True changed = True
else:
changed = False
self.exit_json(changed=changed, user=user)
elif state == 'absent': user = user.to_dict(computed=False)
if user is None: self.exit_json(changed=changed, user=user)
changed = False elif state == 'absent' and user is not None:
else: self.conn.identity.delete_user(user)
if domain: changed = True
self.conn.delete_user(user['id'], domain_id=domain_id) self.exit_json(changed=changed)
else:
self.conn.delete_user(user['id'])
changed = True
self.exit_json(changed=changed)
def main(): def main():