Refactored {group,role}_assignment modules

Change-Id: I6ec79eb203d0f68661b54bc89a194c366b3574c6
This commit is contained in:
Jakob Meng
2023-01-16 20:46:42 +01:00
parent 754ae5e50d
commit d5ab2bf33f
8 changed files with 382 additions and 327 deletions

View File

@@ -0,0 +1,68 @@
---
- 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
- name: Assign user to nonadmins group
openstack.cloud.group_assignment:
cloud: "{{ cloud }}"
state: present
user: ansible_user
group: nonadmins
register: group_assignment
- name: Assert group assignment
assert:
that:
- group_assignment is changed
- name: Assign user to nonadmins group again
openstack.cloud.group_assignment:
cloud: "{{ cloud }}"
state: present
user: ansible_user
group: nonadmins
register: group_assignment
- name: Assert group assignment
assert:
that:
- group_assignment is not changed
- name: Remove user from nonadmins group
openstack.cloud.group_assignment:
cloud: "{{ cloud }}"
state: absent
user: ansible_user
group: nonadmins
register: group_assignment
- name: Assert group assignment
assert:
that:
- group_assignment is changed
- name: Remove user from nonadmins group again
openstack.cloud.group_assignment:
cloud: "{{ cloud }}"
state: absent
user: ansible_user
group: nonadmins
register: group_assignment
- name: Assert group assignment
assert:
that:
- group_assignment is not changed
- name: Delete user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user

View File

@@ -15,6 +15,12 @@
project: ansible_project project: ansible_project
role: admin role: admin
user: admin user: admin
register: role_assignment
- name: Assert role assignment
assert:
that:
- role_assignment is changed
- name: Grant an admin role on the user admin in the project ansible_project again - name: Grant an admin role on the user admin in the project ansible_project again
openstack.cloud.role_assignment: openstack.cloud.role_assignment:
@@ -23,12 +29,12 @@
project: ansible_project project: ansible_project
role: admin role: admin
user: admin user: admin
register: grant_again register: role_assignment
- name: Ensure grant again doesn't change anything - name: Ensure grant again did not change anything
assert: assert:
that: that:
- not grant_again.changed - role_assignment is not changed
- name: Revoke the admin role on the user admin in the project ansible_project - name: Revoke the admin role on the user admin in the project ansible_project
openstack.cloud.role_assignment: openstack.cloud.role_assignment:
@@ -44,3 +50,124 @@
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: absent state: absent
name: ansible_project name: ansible_project
- name: Create domain
openstack.cloud.identity_domain:
cloud: "{{ cloud }}"
state: present
name: ansible_domain
register: domain
- name: Create group in default domain
openstack.cloud.identity_group:
cloud: "{{ cloud }}"
state: present
name: ansible_group
domain_id: default
- name: Create group in specific domain
openstack.cloud.identity_group:
cloud: "{{ cloud }}"
state: present
name: ansible_group
domain_id: "{{ domain.domain.id }}"
- name: Create user in default domain
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
domain: default
- name: Create user in specific domain
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: ansible_user
domain: "{{ domain.domain.id }}"
- name: Assign role to group in default domain
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: anotherrole
group: ansible_group
domain: default
register: role_assignment
- name: Assert role assignment
assert:
that:
- role_assignment is changed
- name: Assign role to group in specific domain
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: anotherrole
group: ansible_group
domain: "{{ domain.domain.id }}"
register: role_assignment
- name: Assert role assignment
assert:
that:
- role_assignment is changed
- name: Assign role to user in default domain
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: anotherrole
user: ansible_user
domain: default
register: role_assignment
- name: Assert role assignment
assert:
that:
- role_assignment is changed
- name: Assign role to user in specific domain
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: anotherrole
user: ansible_user
domain: "{{ domain.domain.id }}"
register: role_assignment
- name: Assert role assignment
assert:
that:
- role_assignment is changed
- name: Delete group in default domain
openstack.cloud.identity_group:
cloud: "{{ cloud }}"
state: absent
name: ansible_group
domain_id: default
- name: Delete group in specific domain
openstack.cloud.identity_group:
cloud: "{{ cloud }}"
state: absent
name: ansible_group
domain_id: "{{ domain.domain.id }}"
- name: Delete user in default domain
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user
domain: default
- name: Delete user in specific domain
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user
domain: "{{ domain.domain.id }}"
- name: Delete domain
openstack.cloud.identity_domain:
cloud: "{{ cloud }}"
state: absent
name: ansible_domain

View File

@@ -1,31 +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
- name: Assign user to nonadmins group
openstack.cloud.group_assignment:
cloud: "{{ cloud }}"
state: present
user: ansible_user
group: nonadmins
- name: Remove user from nonadmins group
openstack.cloud.group_assignment:
cloud: "{{ cloud }}"
state: absent
user: ansible_user
group: nonadmins
- name: Delete user
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: ansible_user

View File

@@ -1,4 +0,0 @@
domain_name: ansible_domain
user_name: ansible_user
group_name: ansible_group
keystone_role_name: anotherrole

View File

@@ -1,96 +0,0 @@
- name: Create domain
openstack.cloud.identity_domain:
cloud: "{{ cloud }}"
state: present
name: "{{ domain_name }}"
register: domain
- name: Create group in default domain
openstack.cloud.identity_group:
cloud: "{{ cloud }}"
state: present
name: "{{ group_name }}"
domain_id: default
- name: Create group in specific domain
openstack.cloud.identity_group:
cloud: "{{ cloud }}"
state: present
name: "{{ group_name }}"
domain_id: "{{ domain.domain.id }}"
- name: Create user in default domain
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: "{{ user_name }}"
domain: default
- name: Create user in specific domain
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: present
name: "{{ user_name }}"
domain: "{{ domain.domain.id }}"
- name: Assign role to group in default domain
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: "{{ keystone_role_name }}"
group: "{{ group_name }}"
domain: default
- name: Assign role to group in specific domain
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: "{{ keystone_role_name }}"
group: "{{ group_name }}"
domain: "{{ domain.domain.id }}"
- name: Assign role to user in default domain
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: "{{ keystone_role_name }}"
user: "{{ user_name }}"
domain: default
- name: Assign role to user in specific domain
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: "{{ keystone_role_name }}"
user: "{{ user_name }}"
domain: "{{ domain.domain.id }}"
- name: Delete group in default domain
openstack.cloud.identity_group:
cloud: "{{ cloud }}"
state: absent
name: "{{ group_name }}"
domain_id: default
- name: Delete group in specific domain
openstack.cloud.identity_group:
cloud: "{{ cloud }}"
state: absent
name: "{{ group_name }}"
domain_id: "{{ domain.domain.id }}"
- name: Delete user in default domain
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: "{{ user_name }}"
domain: default
- name: Delete user in specific domain
openstack.cloud.identity_user:
cloud: "{{ cloud }}"
state: absent
name: "{{ user_name }}"
domain: "{{ domain.domain.id }}"
- name: Delete domain
openstack.cloud.identity_domain:
cloud: "{{ cloud }}"
state: absent
name: "{{ domain_name }}"

View File

@@ -17,6 +17,7 @@
- { role: endpoint, tags: endpoint } - { role: endpoint, tags: endpoint }
- { role: federation_mapping, tags: federation_mapping } - { role: federation_mapping, tags: federation_mapping }
- { role: floating_ip, tags: floating_ip } - { role: floating_ip, tags: floating_ip }
- { role: group_assignment, tags: group_assignment }
- { role: host_aggregate, tags: host_aggregate } - { role: host_aggregate, tags: host_aggregate }
- { role: identity_domain, tags: identity_domain } - { role: identity_domain, tags: identity_domain }
- { role: identity_group, tags: identity_group } - { role: identity_group, tags: identity_group }
@@ -50,8 +51,6 @@
- { role: stack, tags: stack } - { role: stack, tags: stack }
- { role: subnet, tags: subnet } - { role: subnet, tags: subnet }
- { role: subnet_pool, tags: subnet_pool } - { role: subnet_pool, tags: subnet_pool }
- { role: user_group, tags: user_group }
- { role: user_role, tags: user_role }
- { role: volume, tags: volume } - { role: volume, tags: volume }
- { role: volume_backup, tags: volume_backup } - { role: volume_backup, tags: volume_backup }
- { role: volume_snapshot, tags: volume_snapshot } - { role: volume_snapshot, tags: volume_snapshot }

View File

@@ -4,40 +4,40 @@
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. # 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) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = ''' DOCUMENTATION = r'''
--- ---
module: group_assignment module: group_assignment
short_description: Associate OpenStack Identity users and groups short_description: Assign OpenStack identity users to groups
author: OpenStack Ansible SIG author: OpenStack Ansible SIG
description: description:
- Add and remove users from groups - Add and remove OpenStack identity (Keystone) users to/from groups.
options: options:
user: group:
description: description:
- Name or id for the user - Name or ID for the group.
required: true required: true
type: str type: str
group: state:
description: description:
- Name or id for the group. - Should the user be present or absent in the group.
required: true choices: [present, absent]
type: str default: present
state: type: str
description: user:
- Should the user be present or absent in the group description:
choices: [present, absent] - Name or ID for the user.
default: present required: true
type: str type: str
extends_documentation_fragment: extends_documentation_fragment:
- openstack.cloud.openstack - openstack.cloud.openstack
''' '''
EXAMPLES = ''' EXAMPLES = r'''
# Add the demo user to the demo group - name: Add demo_user user to demo_group group
- openstack.cloud.group_assignment: openstack.cloud.group_assignment:
cloud: mycloud cloud: mycloud
user: demo user: demo_user
group: demo group: demo_group
''' '''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
@@ -45,44 +45,42 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
class IdentityGroupAssignment(OpenStackModule): class IdentityGroupAssignment(OpenStackModule):
argument_spec = dict( argument_spec = dict(
user=dict(required=True),
group=dict(required=True), group=dict(required=True),
state=dict(default='present', choices=['absent', 'present']), state=dict(default='present', choices=['absent', 'present']),
user=dict(required=True),
) )
module_kwargs = dict( module_kwargs = dict(
supports_check_mode=True supports_check_mode=True
) )
def _system_state_change(self, state, in_group):
if state == 'present' and not in_group:
return True
if state == 'absent' and in_group:
return True
return False
def run(self): def run(self):
user = self.params['user'] user_name_or_id = self.params['user']
group = self.params['group'] user = self.conn.identity.find_user(user_name_or_id,
ignore_missing=False)
group_name_or_id = self.params['group']
group = self.conn.identity.find_group(group_name_or_id,
ignore_missing=False)
is_user_in_group = \
self.conn.identity.check_user_in_group(user, group)
state = self.params['state'] state = self.params['state']
in_group = self.conn.is_user_in_group(user, group)
if self.ansible.check_mode: if self.ansible.check_mode:
self.exit_json(changed=self._system_state_change(state, in_group)) self.exit_json(
changed=(
(state == 'present' and not is_user_in_group)
or (state == 'absent' and is_user_in_group)))
changed = False if state == 'present' and not is_user_in_group:
if state == 'present': self.conn.identity.add_user_to_group(user, group)
if not in_group: self.exit_json(changed=True)
self.conn.add_user_to_group(user, group) elif state == 'absent' and is_user_in_group:
changed = True self.conn.identity.remove_user_from_group(user, group)
self.exit_json(changed=True)
elif state == 'absent': else:
if in_group: self.exit_json(changed=False)
self.conn.remove_user_from_group(user, group)
changed = True
self.exit_json(changed=changed)
def main(): def main():

View File

@@ -4,67 +4,89 @@
# Copyright (c) 2016 IBM # Copyright (c) 2016 IBM
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = ''' DOCUMENTATION = r'''
--- ---
module: role_assignment module: role_assignment
short_description: Associate OpenStack Identity users and roles short_description: Assign OpenStack identity groups and users to roles
author: OpenStack Ansible SIG author: OpenStack Ansible SIG
description: description:
- Grant and revoke roles in either project or domain context for - Grant and revoke roles in either project or domain context for
OpenStack Identity Users. OpenStack identity (Keystone) users and groups.
options: options:
role: domain:
description: description:
- Name or ID for the role. - Name or ID of the domain to scope the role association to.
required: true - Valid only with keystone version 3.
type: str - Required if I(project) is not specified.
user: - When I(project) is specified, then I(domain) will not be used for
description: scoping the role association, only for finding resources.
- Name or ID for the user. If I(user) is not specified, then - "When scoping the role association, I(project) has precedence over
I(group) is required. Both may not be specified. I(domain) and I(domain) has precedence over I(system): When I(project)
type: str is specified, then I(domain) and I(system) are not used for role
group: association. When I(domain) is specified, then I(system) will not be
description: used for role association."
- Name or ID for the group. Valid only with keystone version 3. type: str
If I(group) is not specified, then I(user) is required. Both group:
may not be specified. description:
type: str - Name or ID for the group.
project: - Valid only with keystone version 3.
description: - If I(group) is not specified, then I(user) is required. Both may not be
- Name or ID of the project to scope the role association to. specified at the same time.
If you are using keystone version 2, then this value is required. type: str
type: str project:
domain: description:
description: - Name or ID of the project to scope the role association to.
- Name or ID of the domain to scope the role association to. Valid only - If you are using keystone version 2, then this value is required.
with keystone version 3, and required if I(project) is not specified. - When I(project) is specified, then I(domain) will not be used for
type: str scoping the role association, only for finding resources.
system: - "When scoping the role association, I(project) has precedence over
description: I(domain) and I(domain) has precedence over I(system): When I(project)
- Name of system to scope the role association to. Valid only with is specified, then I(domain) and I(system) are not used for role
keystone version 3, and required if I(project) and I(domain) association. When I(domain) is specified, then I(system) will not be
are not specified. used for role association."
type: str type: str
state: role:
description: description:
- Should the roles be present or absent on the user. - Name or ID for the role.
choices: [present, absent] required: true
default: present type: str
type: str state:
description:
- Should the roles be present or absent on the user.
choices: [present, absent]
default: present
type: str
system:
description:
- Name of system to scope the role association to.
- Valid only with keystone version 3.
- Required if I(project) and I(domain) are not specified.
- "When scoping the role association, I(project) has precedence over
I(domain) and I(domain) has precedence over I(system): When I(project)
is specified, then I(domain) and I(system) are not used for role
association. When I(domain) is specified, then I(system) will not be
used for role association."
type: str
user:
description:
- Name or ID for the user.
- If I(user) is not specified, then I(group) is required. Both may not be
specified at the same time.
type: str
extends_documentation_fragment: extends_documentation_fragment:
- openstack.cloud.openstack - openstack.cloud.openstack
''' '''
EXAMPLES = ''' EXAMPLES = r'''
# Grant an admin role on the user admin in the project project1 - name: Grant an admin role on the user admin in the project project1
- openstack.cloud.role_assignment: openstack.cloud.role_assignment:
cloud: mycloud cloud: mycloud
user: admin user: admin
role: admin role: admin
project: project1 project: project1
# Revoke the admin role from the user barney in the newyork domain - name: Revoke the admin role from the user barney in the newyork domain
- openstack.cloud.role_assignment: openstack.cloud.role_assignment:
cloud: mycloud cloud: mycloud
state: absent state: absent
user: barney user: barney
@@ -72,123 +94,95 @@ EXAMPLES = '''
domain: newyork domain: newyork
''' '''
RETURN = '''
#
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class IdentityRoleAssignmentModule(OpenStackModule): class IdentityRoleAssignmentModule(OpenStackModule):
argument_spec = dict( argument_spec = dict(
role=dict(required=True), domain=dict(),
user=dict(),
group=dict(), group=dict(),
project=dict(), project=dict(),
domain=dict(), role=dict(required=True),
system=dict(),
state=dict(default='present', choices=['absent', 'present']), state=dict(default='present', choices=['absent', 'present']),
system=dict(),
user=dict(),
) )
module_kwargs = dict( module_kwargs = dict(
required_one_of=[ required_one_of=[
['user', 'group'] ('user', 'group'),
('domain', 'project', 'system'),
], ],
supports_check_mode=True supports_check_mode=True
) )
def _system_state_change(self, state, assignment):
if state == 'present' and not assignment:
return True
elif state == 'absent' and assignment:
return True
return False
def _build_kwargs(self, user, group, project, domain, system):
kwargs = {}
if user:
kwargs['user'] = user
if group:
kwargs['group'] = group
if project:
kwargs['project'] = project
if domain:
kwargs['domain'] = domain
if system:
kwargs['system'] = system
return kwargs
def run(self): def run(self):
role = self.params.get('role')
user = self.params.get('user')
group = self.params.get('group')
project = self.params.get('project')
domain = self.params.get('domain')
system = self.params.get('system')
state = self.params.get('state')
filters = {} filters = {}
find_filters = {} find_filters = {}
domain_id = None kwargs = {}
r = self.conn.identity.find_role(role) role_name_or_id = self.params['role']
if r is None: role = self.conn.identity.find_role(role_name_or_id,
self.fail_json(msg="Role %s is not valid" % role) ignore_missing=False)
filters['role'] = r['id'] filters['role_id'] = role['id']
if domain: domain_name_or_id = self.params['domain']
d = self.conn.identity.find_domain(domain) if domain_name_or_id is not None:
if d is None: domain = self.conn.identity.find_domain(
self.fail_json(msg="Domain %s is not valid" % domain) domain_name_or_id, ignore_missing=False)
domain_id = d['id'] filters['scope_domain_id'] = domain['id']
find_filters['domain_id'] = domain_id find_filters['domain_id'] = domain['id']
if user: kwargs['domain'] = domain['id']
u = self.conn.identity.find_user(user, **find_filters)
if u is None:
self.fail_json(msg="User %s is not valid" % user)
filters['user'] = u['id']
if group: user_name_or_id = self.params['user']
g = self.conn.identity.find_group(group, **find_filters) if user_name_or_id is not None:
if g is None: user = self.conn.identity.find_user(
self.fail_json(msg="Group %s is not valid" % group) user_name_or_id, ignore_missing=False, **find_filters)
filters['group'] = g['id'] filters['user_id'] = user['id']
if project: kwargs['user'] = user['id']
p = self.conn.identity.find_project(project, **find_filters)
if p is None:
self.fail_json(msg="Project %s is not valid" % project)
filters['project'] = p['id']
if system:
# the system role name is the argument. list_role_assignments will
# fail if the system role name is not valid
filters['system'] = system
# Keeping the self.conn.list_role_assignments because it calls directly group_name_or_id = self.params['group']
# the identity.role_assignments and there are some logics for the if group_name_or_id is not None:
# filters that won't worth rewrite here. group = self.conn.identity.find_group(
assignment = self.conn.list_role_assignments(filters=filters) group_name_or_id, ignore_missing=False, **find_filters)
filters['group_id'] = group['id']
kwargs['group'] = group['id']
system_name = self.params['system']
if system_name is not None:
# domain has precedence over system
if 'scope_domain_id' not in filters:
filters['scope.system'] = system_name
kwargs['system'] = system_name
project_name_or_id = self.params['project']
if project_name_or_id is not None:
project = self.conn.identity.find_project(
project_name_or_id, ignore_missing=False, **find_filters)
filters['scope_project_id'] = project['id']
kwargs['project'] = project['id']
# project has precedence over domain and system
filters.pop('scope_domain_id', None)
filters.pop('scope.system', None)
role_assignments = list(self.conn.identity.role_assignments(**filters))
state = self.params['state']
if self.ansible.check_mode: if self.ansible.check_mode:
self.exit_json(changed=self._system_state_change(state, assignment)) self.exit_json(
changed=((state == 'present' and not role_assignments)
or (state == 'absent' and role_assignments)))
changed = False if state == 'present' and not role_assignments:
self.conn.grant_role(role['id'], **kwargs)
# Both grant_role and revoke_role calls directly the proxy layer, and self.exit_json(changed=True)
# has some logic that won't worth to rewrite here so keeping it is a elif state == 'absent' and role_assignments:
# good idea self.conn.revoke_role(role['id'], **kwargs)
if state == 'present': self.exit_json(changed=True)
if not assignment: else:
kwargs = self._build_kwargs(user, group, project, domain_id, system) self.exit_json(changed=False)
self.conn.grant_role(role, **kwargs)
changed = True
elif state == 'absent':
if assignment:
kwargs = self._build_kwargs(user, group, project, domain_id, system)
self.conn.revoke_role(role, **kwargs)
changed = True
self.exit_json(changed=changed)
def main(): def main():