mirror of
https://github.com/ansible/awx.git
synced 2026-02-18 11:40:05 -03:30
Merge pull request #8413 from sean-m-sullivan/role_list
Tower_role Improvements, Approvals and use of Lists Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -34,7 +34,7 @@ options:
|
|||||||
description:
|
description:
|
||||||
- The role type to grant/revoke.
|
- The role type to grant/revoke.
|
||||||
required: True
|
required: True
|
||||||
choices: ["admin", "read", "member", "execute", "adhoc", "update", "use", "auditor", "project_admin", "inventory_admin", "credential_admin",
|
choices: ["admin", "read", "member", "execute", "adhoc", "update", "use", "approval", "auditor", "project_admin", "inventory_admin", "credential_admin",
|
||||||
"workflow_admin", "notification_admin", "job_template_admin"]
|
"workflow_admin", "notification_admin", "job_template_admin"]
|
||||||
type: str
|
type: str
|
||||||
target_team:
|
target_team:
|
||||||
@@ -42,31 +42,81 @@ options:
|
|||||||
- Team that the role acts on.
|
- Team that the role acts on.
|
||||||
- For example, make someone a member or an admin of a team.
|
- For example, make someone a member or an admin of a team.
|
||||||
- Members of a team implicitly receive the permissions that the team has.
|
- Members of a team implicitly receive the permissions that the team has.
|
||||||
|
- Deprecated, use 'target_teams'.
|
||||||
type: str
|
type: str
|
||||||
|
target_teams:
|
||||||
|
description:
|
||||||
|
- Team that the role acts on.
|
||||||
|
- For example, make someone a member or an admin of a team.
|
||||||
|
- Members of a team implicitly receive the permissions that the team has.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
inventory:
|
inventory:
|
||||||
description:
|
description:
|
||||||
- Inventory the role acts on.
|
- Inventory the role acts on.
|
||||||
|
- Deprecated, use 'inventories'.
|
||||||
type: str
|
type: str
|
||||||
|
inventories:
|
||||||
|
description:
|
||||||
|
- Inventory the role acts on.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
job_template:
|
job_template:
|
||||||
description:
|
description:
|
||||||
- The job template the role acts on.
|
- The job template the role acts on.
|
||||||
|
- Deprecated, use 'job_templates'.
|
||||||
type: str
|
type: str
|
||||||
|
job_templates:
|
||||||
|
description:
|
||||||
|
- The job template the role acts on.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
workflow:
|
workflow:
|
||||||
description:
|
description:
|
||||||
- The workflow job template the role acts on.
|
- The workflow job template the role acts on.
|
||||||
|
- Deprecated, use 'workflows'.
|
||||||
type: str
|
type: str
|
||||||
|
workflows:
|
||||||
|
description:
|
||||||
|
- The workflow job template the role acts on.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
credential:
|
credential:
|
||||||
description:
|
description:
|
||||||
- Credential the role acts on.
|
- Credential the role acts on.
|
||||||
|
- Deprecated, use 'credentials'.
|
||||||
type: str
|
type: str
|
||||||
|
credentials:
|
||||||
|
description:
|
||||||
|
- Credential the role acts on.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
organization:
|
organization:
|
||||||
description:
|
description:
|
||||||
- Organization the role acts on.
|
- Organization the role acts on.
|
||||||
|
- Deprecated, use 'organizations'.
|
||||||
|
type: str
|
||||||
|
organizations:
|
||||||
|
description:
|
||||||
|
- Organization the role acts on.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
lookup_organization:
|
||||||
|
description:
|
||||||
|
- Organization the inventories, job templates, projects, or workflows the items exists in.
|
||||||
|
- Used to help lookup the object, for organization roles see organization.
|
||||||
|
- If not provided, will lookup by name only, which does not work with duplicates.
|
||||||
type: str
|
type: str
|
||||||
project:
|
project:
|
||||||
description:
|
description:
|
||||||
- Project the role acts on.
|
- Project the role acts on.
|
||||||
|
- Deprecated, use 'projects'.
|
||||||
type: str
|
type: str
|
||||||
|
projects:
|
||||||
|
description:
|
||||||
|
- Project the role acts on.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Desired state.
|
- Desired state.
|
||||||
@@ -87,6 +137,16 @@ EXAMPLES = '''
|
|||||||
target_team: "My Team"
|
target_team: "My Team"
|
||||||
role: member
|
role: member
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
- name: Add Joe to multiple job templates and a workflow
|
||||||
|
tower_role:
|
||||||
|
user: joe
|
||||||
|
role: execute
|
||||||
|
workflow: test-role-workflow
|
||||||
|
job_templates:
|
||||||
|
- jt1
|
||||||
|
- jt2
|
||||||
|
state: present
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ..module_utils.tower_api import TowerAPIModule
|
from ..module_utils.tower_api import TowerAPIModule
|
||||||
@@ -97,15 +157,24 @@ def main():
|
|||||||
argument_spec = dict(
|
argument_spec = dict(
|
||||||
user=dict(),
|
user=dict(),
|
||||||
team=dict(),
|
team=dict(),
|
||||||
role=dict(choices=["admin", "read", "member", "execute", "adhoc", "update", "use", "auditor", "project_admin", "inventory_admin", "credential_admin",
|
role=dict(choices=["admin", "read", "member", "execute", "adhoc", "update", "use", "approval",
|
||||||
|
"auditor", "project_admin", "inventory_admin", "credential_admin",
|
||||||
"workflow_admin", "notification_admin", "job_template_admin"], required=True),
|
"workflow_admin", "notification_admin", "job_template_admin"], required=True),
|
||||||
target_team=dict(),
|
target_team=dict(),
|
||||||
|
target_teams=dict(type='list', elements='str'),
|
||||||
inventory=dict(),
|
inventory=dict(),
|
||||||
|
inventories=dict(type='list', elements='str'),
|
||||||
job_template=dict(),
|
job_template=dict(),
|
||||||
|
job_templates=dict(type='list', elements='str'),
|
||||||
workflow=dict(),
|
workflow=dict(),
|
||||||
|
workflows=dict(type='list', elements='str'),
|
||||||
credential=dict(),
|
credential=dict(),
|
||||||
|
credentials=dict(type='list', elements='str'),
|
||||||
organization=dict(),
|
organization=dict(),
|
||||||
|
organizations=dict(type='list', elements='str'),
|
||||||
|
lookup_organization=dict(),
|
||||||
project=dict(),
|
project=dict(),
|
||||||
|
projects=dict(type='list', elements='str'),
|
||||||
state=dict(choices=['present', 'absent'], default='present'),
|
state=dict(choices=['present', 'absent'], default='present'),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -117,41 +186,83 @@ def main():
|
|||||||
|
|
||||||
module.json_output['role'] = role_type
|
module.json_output['role'] = role_type
|
||||||
|
|
||||||
# Lookup data for all the objects specified in params
|
# Deal with legacy parameters
|
||||||
params = module.params.copy()
|
resource_list_param_keys = {
|
||||||
|
'credentials': 'credential',
|
||||||
|
'inventories': 'inventory',
|
||||||
|
'job_templates': 'job_template',
|
||||||
|
'organizations': 'organization',
|
||||||
|
'projects': 'project',
|
||||||
|
'target_teams': 'target_team',
|
||||||
|
'workflows': 'workflow'
|
||||||
|
}
|
||||||
|
# Singular parameters
|
||||||
resource_param_keys = (
|
resource_param_keys = (
|
||||||
'user', 'team',
|
'user', 'team', 'lookup_organization'
|
||||||
'target_team', 'inventory', 'job_template', 'workflow', 'credential', 'organization', 'project'
|
|
||||||
)
|
)
|
||||||
resource_data = {}
|
|
||||||
for param in resource_param_keys:
|
|
||||||
endpoint = module.param_to_endpoint(param)
|
|
||||||
|
|
||||||
resource_name = params.get(param)
|
resources = {}
|
||||||
if resource_name:
|
for resource_group in resource_list_param_keys:
|
||||||
resource = module.get_exactly_one(module.param_to_endpoint(param), resource_name)
|
if module.params.get(resource_group) is not None:
|
||||||
resource_data[param] = resource
|
resources.setdefault(resource_group, []).extend(module.params.get(resource_group))
|
||||||
|
if module.params.get(resource_list_param_keys[resource_group]) is not None:
|
||||||
|
resources.setdefault(resource_group, []).append(module.params.get(resource_list_param_keys[resource_group]))
|
||||||
|
for resource_group in resource_param_keys:
|
||||||
|
if module.params.get(resource_group) is not None:
|
||||||
|
resources[resource_group] = module.params.get(resource_group)
|
||||||
|
# Change workflows and target_teams key to its endpoint name.
|
||||||
|
if 'workflows' in resources:
|
||||||
|
resources['workflow_job_templates'] = resources.pop('workflows')
|
||||||
|
if 'target_teams' in resources:
|
||||||
|
resources['teams'] = resources.pop('target_teams')
|
||||||
|
|
||||||
|
# Set lookup data to use
|
||||||
|
lookup_data = {}
|
||||||
|
if 'lookup_organization' in resources:
|
||||||
|
lookup_data['organization'] = module.resolve_name_to_id('organizations', resources['lookup_organization'])
|
||||||
|
resources.pop('lookup_organization')
|
||||||
|
|
||||||
|
# Lookup actor data
|
||||||
# separate actors from resources
|
# separate actors from resources
|
||||||
actor_data = {}
|
actor_data = {}
|
||||||
for key in ('user', 'team'):
|
for key in ('user', 'team'):
|
||||||
if key in resource_data:
|
if key in resources:
|
||||||
actor_data[key] = resource_data.pop(key)
|
if key == 'user':
|
||||||
|
lookup_data_populated = {}
|
||||||
|
else:
|
||||||
|
lookup_data_populated = lookup_data
|
||||||
|
# Attempt to look up project based on the provided name or ID and lookup data
|
||||||
|
actor_data[key] = module.get_one('{0}s'.format(key), name_or_id=resources[key], data=lookup_data_populated)
|
||||||
|
resources.pop(key)
|
||||||
|
|
||||||
|
# Lookup Resources
|
||||||
|
resource_data = {}
|
||||||
|
for key in resources:
|
||||||
|
for resource in resources[key]:
|
||||||
|
# Attempt to look up project based on the provided name or ID and lookup data
|
||||||
|
if key in resources:
|
||||||
|
if key == 'organizations':
|
||||||
|
lookup_data_populated = {}
|
||||||
|
else:
|
||||||
|
lookup_data_populated = lookup_data
|
||||||
|
|
||||||
|
resource_data.setdefault(key, []).append(module.get_one(key, name_or_id=resource, data=lookup_data_populated))
|
||||||
|
|
||||||
# build association agenda
|
# build association agenda
|
||||||
associations = {}
|
associations = {}
|
||||||
for actor_type, actor in actor_data.items():
|
for actor_type, actor in actor_data.items():
|
||||||
for resource_type, resource in resource_data.items():
|
for key in resource_data:
|
||||||
resource_roles = resource['summary_fields']['object_roles']
|
for resource in resource_data[key]:
|
||||||
if role_field not in resource_roles:
|
resource_roles = resource['summary_fields']['object_roles']
|
||||||
available_roles = ', '.join(list(resource_roles.keys()))
|
if role_field not in resource_roles:
|
||||||
module.fail_json(msg='Resource {0} has no role {1}, available roles: {2}'.format(
|
available_roles = ', '.join(list(resource_roles.keys()))
|
||||||
resource['url'], role_field, available_roles
|
module.fail_json(msg='Resource {0} has no role {1}, available roles: {2}'.format(
|
||||||
), changed=False)
|
resource['url'], role_field, available_roles
|
||||||
role_data = resource_roles[role_field]
|
), changed=False)
|
||||||
endpoint = '/roles/{0}/{1}/'.format(role_data['id'], module.param_to_endpoint(actor_type))
|
role_data = resource_roles[role_field]
|
||||||
associations.setdefault(endpoint, [])
|
endpoint = '/roles/{0}/{1}/'.format(role_data['id'], module.param_to_endpoint(actor_type))
|
||||||
associations[endpoint].append(actor['id'])
|
associations.setdefault(endpoint, [])
|
||||||
|
associations[endpoint].append(actor['id'])
|
||||||
|
|
||||||
# perform associations
|
# perform associations
|
||||||
for association_endpoint, new_association_list in associations.items():
|
for association_endpoint, new_association_list in associations.items():
|
||||||
|
|||||||
@@ -49,6 +49,51 @@ def test_grant_workflow_permission(run_module, admin_user, organization, state):
|
|||||||
assert rando not in wfjt.execute_role
|
assert rando not in wfjt.execute_role
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize('state', ('present', 'absent'))
|
||||||
|
def test_grant_workflow_list_permission(run_module, admin_user, organization, state):
|
||||||
|
wfjt = WorkflowJobTemplate.objects.create(organization=organization, name='foo-workflow')
|
||||||
|
rando = User.objects.create(username='rando')
|
||||||
|
if state == 'absent':
|
||||||
|
wfjt.execute_role.members.add(rando)
|
||||||
|
|
||||||
|
result = run_module('tower_role', {
|
||||||
|
'user': rando.username,
|
||||||
|
'lookup_organization': wfjt.organization.name,
|
||||||
|
'workflows': [wfjt.name],
|
||||||
|
'role': 'execute',
|
||||||
|
'state': state
|
||||||
|
}, admin_user)
|
||||||
|
assert not result.get('failed', False), result.get('msg', result)
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
assert rando in wfjt.execute_role
|
||||||
|
else:
|
||||||
|
assert rando not in wfjt.execute_role
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize('state', ('present', 'absent'))
|
||||||
|
def test_grant_workflow_approval_permission(run_module, admin_user, organization, state):
|
||||||
|
wfjt = WorkflowJobTemplate.objects.create(organization=organization, name='foo-workflow')
|
||||||
|
rando = User.objects.create(username='rando')
|
||||||
|
if state == 'absent':
|
||||||
|
wfjt.execute_role.members.add(rando)
|
||||||
|
|
||||||
|
result = run_module('tower_role', {
|
||||||
|
'user': rando.username,
|
||||||
|
'workflow': wfjt.name,
|
||||||
|
'role': 'approval',
|
||||||
|
'state': state
|
||||||
|
}, admin_user)
|
||||||
|
assert not result.get('failed', False), result.get('msg', result)
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
assert rando in wfjt.approval_role
|
||||||
|
else:
|
||||||
|
assert rando not in wfjt.approval_role
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_invalid_role(run_module, admin_user, project):
|
def test_invalid_role(run_module, admin_user, project):
|
||||||
rando = User.objects.create(username='rando')
|
rando = User.objects.create(username='rando')
|
||||||
|
|||||||
@@ -6,7 +6,10 @@
|
|||||||
- name: Generate names
|
- name: Generate names
|
||||||
set_fact:
|
set_fact:
|
||||||
username: "AWX-Collection-tests-tower_role-user-{{ test_id }}"
|
username: "AWX-Collection-tests-tower_role-user-{{ test_id }}"
|
||||||
project_name: "AWX-Collection-tests-tower_role-project-{{ test_id }}"
|
project_name: "AWX-Collection-tests-tower_role-project-1-{{ test_id }}"
|
||||||
|
jt1: "AWX-Collection-tests-tower_role-jt1-{{ test_id }}"
|
||||||
|
jt2: "AWX-Collection-tests-tower_role-jt2-{{ test_id }}"
|
||||||
|
wfjt_name: "AWX-Collection-tests-tower_role-project-wfjt-{{ test_id }}"
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
- name: Create a User
|
- name: Create a User
|
||||||
@@ -29,17 +32,33 @@
|
|||||||
organization: Default
|
organization: Default
|
||||||
scm_type: git
|
scm_type: git
|
||||||
scm_url: https://github.com/ansible/test-playbooks
|
scm_url: https://github.com/ansible/test-playbooks
|
||||||
wait: false
|
wait: true
|
||||||
register: project_info
|
register: project_info
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- project_info is changed
|
- project_info is changed
|
||||||
|
|
||||||
- name: Add Joe to the update role of the default Project
|
- name: Create job templates
|
||||||
|
tower_job_template:
|
||||||
|
name: "{{ item }}"
|
||||||
|
project: "{{ project_name }}"
|
||||||
|
inventory: "Demo Inventory"
|
||||||
|
playbook: become.yml
|
||||||
|
with_items:
|
||||||
|
- jt1
|
||||||
|
- jt2
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result is changed"
|
||||||
|
|
||||||
|
- name: Add Joe to the update role of the default Project with lookup Organization
|
||||||
tower_role:
|
tower_role:
|
||||||
user: "{{ username }}"
|
user: "{{ username }}"
|
||||||
role: update
|
role: update
|
||||||
|
lookup_organization: Default
|
||||||
project: "Demo Project"
|
project: "Demo Project"
|
||||||
state: "{{ item }}"
|
state: "{{ item }}"
|
||||||
register: result
|
register: result
|
||||||
@@ -77,6 +96,9 @@
|
|||||||
user: "{{ username }}"
|
user: "{{ username }}"
|
||||||
role: execute
|
role: execute
|
||||||
workflow: test-role-workflow
|
workflow: test-role-workflow
|
||||||
|
job_templates:
|
||||||
|
- jt1
|
||||||
|
- jt2
|
||||||
state: present
|
state: present
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
@@ -96,6 +118,18 @@
|
|||||||
that:
|
that:
|
||||||
- "result is not changed"
|
- "result is not changed"
|
||||||
|
|
||||||
|
- name: Add Joe to workflow approve role
|
||||||
|
tower_role:
|
||||||
|
user: "{{ username }}"
|
||||||
|
role: approval
|
||||||
|
workflow: test-role-workflow
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result is changed"
|
||||||
|
|
||||||
always:
|
always:
|
||||||
- name: Delete a User
|
- name: Delete a User
|
||||||
tower_user:
|
tower_user:
|
||||||
@@ -104,6 +138,18 @@
|
|||||||
state: absent
|
state: absent
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
- name: Delete job templates
|
||||||
|
tower_job_template:
|
||||||
|
name: "{{ item }}"
|
||||||
|
project: "{{ project_name }}"
|
||||||
|
inventory: "Demo Inventory"
|
||||||
|
playbook: debug.yml
|
||||||
|
state: absent
|
||||||
|
with_items:
|
||||||
|
- jt1
|
||||||
|
- jt2
|
||||||
|
register: result
|
||||||
|
|
||||||
- name: Delete the project
|
- name: Delete the project
|
||||||
tower_project:
|
tower_project:
|
||||||
name: "{{ project_name }}"
|
name: "{{ project_name }}"
|
||||||
|
|||||||
Reference in New Issue
Block a user