mirror of
https://github.com/ansible/awx.git
synced 2026-05-09 18:37:36 -02:30
Merge pull request #6370 from AlanCoding/convert_tower_role
Initial conversion of tower_role Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -190,6 +190,15 @@ class TowerModule(AnsibleModule):
|
|||||||
else:
|
else:
|
||||||
setattr(self, honorred_setting, config_data[honorred_setting])
|
setattr(self, honorred_setting, config_data[honorred_setting])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def param_to_endpoint(name):
|
||||||
|
exceptions = {
|
||||||
|
'inventory': 'inventories',
|
||||||
|
'target_team': 'teams',
|
||||||
|
'workflow': 'workflow_job_templates'
|
||||||
|
}
|
||||||
|
return exceptions.get(name, '{0}s'.format(name))
|
||||||
|
|
||||||
def head_endpoint(self, endpoint, *args, **kwargs):
|
def head_endpoint(self, endpoint, *args, **kwargs):
|
||||||
return self.make_request('HEAD', endpoint, **kwargs)
|
return self.make_request('HEAD', endpoint, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ DOCUMENTATION = '''
|
|||||||
module: tower_role
|
module: tower_role
|
||||||
version_added: "2.3"
|
version_added: "2.3"
|
||||||
author: "Wayne Witzel III (@wwitzel3)"
|
author: "Wayne Witzel III (@wwitzel3)"
|
||||||
short_description: create, update, or destroy Ansible Tower role.
|
short_description: grant or revoke an Ansible Tower role.
|
||||||
description:
|
description:
|
||||||
- Create, update, or destroy Ansible Tower roles. See
|
- Roles are used for access control, this module is for managing user access to server resources.
|
||||||
U(https://www.ansible.com/tower) for an overview.
|
- Grant or revoke Ansible Tower roles to users. See U(https://www.ansible.com/tower) for an overview.
|
||||||
options:
|
options:
|
||||||
user:
|
user:
|
||||||
description:
|
description:
|
||||||
@@ -41,6 +41,8 @@ options:
|
|||||||
target_team:
|
target_team:
|
||||||
description:
|
description:
|
||||||
- Team that the role acts on.
|
- 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: str
|
type: str
|
||||||
inventory:
|
inventory:
|
||||||
description:
|
description:
|
||||||
@@ -52,7 +54,7 @@ options:
|
|||||||
type: str
|
type: str
|
||||||
workflow:
|
workflow:
|
||||||
description:
|
description:
|
||||||
- The job template the role acts on.
|
- The workflow job template the role acts on.
|
||||||
type: str
|
type: str
|
||||||
credential:
|
credential:
|
||||||
description:
|
description:
|
||||||
@@ -68,10 +70,18 @@ options:
|
|||||||
type: str
|
type: str
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Desired state of the resource.
|
- Desired state.
|
||||||
|
- State of present indicates the user should have the role.
|
||||||
|
- State of absent indicates the user should have the role taken away, if they have it.
|
||||||
default: "present"
|
default: "present"
|
||||||
choices: ["present", "absent"]
|
choices: ["present", "absent"]
|
||||||
type: str
|
type: str
|
||||||
|
tower_oauthtoken:
|
||||||
|
description:
|
||||||
|
- The Tower OAuth token to use.
|
||||||
|
required: False
|
||||||
|
type: str
|
||||||
|
version_added: "3.7"
|
||||||
|
|
||||||
requirements:
|
requirements:
|
||||||
- ansible-tower-cli >= 3.0.2
|
- ansible-tower-cli >= 3.0.2
|
||||||
@@ -87,45 +97,9 @@ EXAMPLES = '''
|
|||||||
target_team: "My Team"
|
target_team: "My Team"
|
||||||
role: member
|
role: member
|
||||||
state: present
|
state: present
|
||||||
tower_config_file: "~/tower_cli.cfg"
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ..module_utils.ansible_tower import TowerModule, tower_auth_config, tower_check_mode
|
from ..module_utils.tower_api import TowerModule
|
||||||
|
|
||||||
try:
|
|
||||||
import tower_cli
|
|
||||||
import tower_cli.exceptions as exc
|
|
||||||
|
|
||||||
from tower_cli.conf import settings
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def update_resources(module, p):
|
|
||||||
'''update_resources attempts to fetch any of the resources given
|
|
||||||
by name using their unique field (identity)
|
|
||||||
'''
|
|
||||||
params = p.copy()
|
|
||||||
identity_map = {
|
|
||||||
'user': 'username',
|
|
||||||
'team': 'name',
|
|
||||||
'target_team': 'name',
|
|
||||||
'inventory': 'name',
|
|
||||||
'job_template': 'name',
|
|
||||||
'workflow': 'name',
|
|
||||||
'credential': 'name',
|
|
||||||
'organization': 'name',
|
|
||||||
'project': 'name',
|
|
||||||
}
|
|
||||||
for k, v in identity_map.items():
|
|
||||||
try:
|
|
||||||
if params[k]:
|
|
||||||
key = 'team' if k == 'target_team' else k
|
|
||||||
result = tower_cli.get_resource(key).get(**{v: params[k]})
|
|
||||||
params[k] = result['id']
|
|
||||||
except (exc.NotFound) as excinfo:
|
|
||||||
module.fail_json(msg='Failed to update role, {0} not found: {1}'.format(k, excinfo), changed=False)
|
|
||||||
return params
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -147,32 +121,75 @@ def main():
|
|||||||
|
|
||||||
module = TowerModule(argument_spec=argument_spec, supports_check_mode=True)
|
module = TowerModule(argument_spec=argument_spec, supports_check_mode=True)
|
||||||
|
|
||||||
module.deprecate(msg="This module is being moved to a different collection. Instead of awx.awx it will be migrated into awx.tower_cli", version="3.7")
|
|
||||||
|
|
||||||
role_type = module.params.pop('role')
|
role_type = module.params.pop('role')
|
||||||
|
role_field = role_type + '_role'
|
||||||
state = module.params.pop('state')
|
state = module.params.pop('state')
|
||||||
|
|
||||||
json_output = {'role': role_type, 'state': state}
|
module.json_output['role'] = role_type
|
||||||
|
|
||||||
tower_auth = tower_auth_config(module)
|
# Lookup data for all the objects specified in params
|
||||||
with settings.runtime_values(**tower_auth):
|
params = module.params.copy()
|
||||||
tower_check_mode(module)
|
resource_param_keys = (
|
||||||
role = tower_cli.get_resource('role')
|
'user', 'team',
|
||||||
|
'target_team', 'inventory', 'job_template', 'workflow', 'credential', 'organization', 'project'
|
||||||
|
)
|
||||||
|
resource_data = {}
|
||||||
|
for param in resource_param_keys:
|
||||||
|
endpoint = module.param_to_endpoint(param)
|
||||||
|
name_field = 'username' if param == 'user' else 'name'
|
||||||
|
|
||||||
params = update_resources(module, module.params)
|
resource_name = params.get(param)
|
||||||
params['type'] = role_type
|
if resource_name:
|
||||||
|
resource = module.get_one(endpoint, **{'data': {name_field: resource_name}})
|
||||||
|
if not resource:
|
||||||
|
module.fail_json(
|
||||||
|
msg='Failed to update role, {0} not found in {1}'.format(param, endpoint),
|
||||||
|
changed=False
|
||||||
|
)
|
||||||
|
resource_data[param] = resource
|
||||||
|
|
||||||
try:
|
# separate actors from resources
|
||||||
if state == 'present':
|
actor_data = {}
|
||||||
result = role.grant(**params)
|
for key in ('user', 'team'):
|
||||||
json_output['id'] = result['id']
|
if key in resource_data:
|
||||||
elif state == 'absent':
|
actor_data[key] = resource_data.pop(key)
|
||||||
result = role.revoke(**params)
|
|
||||||
except (exc.ConnectionError, exc.BadRequest, exc.NotFound, exc.AuthError) as excinfo:
|
|
||||||
module.fail_json(msg='Failed to update role: {0}'.format(excinfo), changed=False)
|
|
||||||
|
|
||||||
json_output['changed'] = result['changed']
|
# build association agenda
|
||||||
module.exit_json(**json_output)
|
associations = {}
|
||||||
|
for actor_type, actor in actor_data.items():
|
||||||
|
for resource_type, resource in resource_data.items():
|
||||||
|
resource_roles = resource['summary_fields']['object_roles']
|
||||||
|
if role_field not in resource_roles:
|
||||||
|
available_roles = ', '.join(list(resource_roles.keys()))
|
||||||
|
module.fail_json(msg='Resource {0} has no role {1}, available roles: {2}'.format(
|
||||||
|
resource['url'], role_field, available_roles
|
||||||
|
), changed=False)
|
||||||
|
role_data = resource_roles[role_field]
|
||||||
|
endpoint = '/roles/{0}/{1}/'.format(role_data['id'], module.param_to_endpoint(actor_type))
|
||||||
|
associations.setdefault(endpoint, [])
|
||||||
|
associations[endpoint].append(actor['id'])
|
||||||
|
|
||||||
|
# perform associations
|
||||||
|
for association_endpoint, new_association_list in associations.items():
|
||||||
|
response = module.get_all_endpoint(association_endpoint)
|
||||||
|
existing_associated_ids = [association['id'] for association in response['json']['results']]
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
for an_id in list(set(new_association_list) - set(existing_associated_ids)):
|
||||||
|
response = module.post_endpoint(association_endpoint, **{'data': {'id': int(an_id)}})
|
||||||
|
if response['status_code'] == 204:
|
||||||
|
module.json_output['changed'] = True
|
||||||
|
else:
|
||||||
|
module.fail_json(msg="Failed to grant role {0}".format(response['json']['detail']))
|
||||||
|
else:
|
||||||
|
for an_id in list(set(existing_associated_ids) & set(new_association_list)):
|
||||||
|
response = module.post_endpoint(association_endpoint, **{'data': {'id': int(an_id), 'disassociate': True}})
|
||||||
|
if response['status_code'] == 204:
|
||||||
|
module.json_output['changed'] = True
|
||||||
|
else:
|
||||||
|
module.fail_json(msg="Failed to revoke role {0}".format(response['json']['detail']))
|
||||||
|
|
||||||
|
module.exit_json(**module.json_output)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -7,31 +7,58 @@ from awx.main.models import WorkflowJobTemplate, User
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_organization_permission(run_module, admin_user, organization, silence_deprecation):
|
@pytest.mark.parametrize('state', ('present', 'absent'))
|
||||||
|
def test_grant_organization_permission(run_module, admin_user, organization, state):
|
||||||
rando = User.objects.create(username='rando')
|
rando = User.objects.create(username='rando')
|
||||||
|
if state == 'absent':
|
||||||
|
organization.admin_role.members.add(rando)
|
||||||
|
|
||||||
result = run_module('tower_role', {
|
result = run_module('tower_role', {
|
||||||
'user': rando.username,
|
'user': rando.username,
|
||||||
'organization': organization.name,
|
'organization': organization.name,
|
||||||
'role': 'admin',
|
'role': 'admin',
|
||||||
'state': 'present'
|
'state': state
|
||||||
}, admin_user)
|
}, admin_user)
|
||||||
assert not result.get('failed', False), result.get('msg', result)
|
assert not result.get('failed', False), result.get('msg', result)
|
||||||
|
|
||||||
assert rando in organization.execute_role
|
if state == 'present':
|
||||||
|
assert rando in organization.execute_role
|
||||||
|
else:
|
||||||
|
assert rando not in organization.execute_role
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_workflow_permission(run_module, admin_user, organization, silence_deprecation):
|
@pytest.mark.parametrize('state', ('present', 'absent'))
|
||||||
|
def test_grant_workflow_permission(run_module, admin_user, organization, state):
|
||||||
wfjt = WorkflowJobTemplate.objects.create(organization=organization, name='foo-workflow')
|
wfjt = WorkflowJobTemplate.objects.create(organization=organization, name='foo-workflow')
|
||||||
rando = User.objects.create(username='rando')
|
rando = User.objects.create(username='rando')
|
||||||
|
if state == 'absent':
|
||||||
|
wfjt.execute_role.members.add(rando)
|
||||||
|
|
||||||
result = run_module('tower_role', {
|
result = run_module('tower_role', {
|
||||||
'user': rando.username,
|
'user': rando.username,
|
||||||
'workflow': wfjt.name,
|
'workflow': wfjt.name,
|
||||||
'role': 'execute',
|
'role': 'execute',
|
||||||
'state': 'present'
|
'state': state
|
||||||
}, admin_user)
|
}, admin_user)
|
||||||
assert not result.get('failed', False), result.get('msg', result)
|
assert not result.get('failed', False), result.get('msg', result)
|
||||||
|
|
||||||
assert rando in wfjt.execute_role
|
if state == 'present':
|
||||||
|
assert rando in wfjt.execute_role
|
||||||
|
else:
|
||||||
|
assert rando not in wfjt.execute_role
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_invalid_role(run_module, admin_user, project):
|
||||||
|
rando = User.objects.create(username='rando')
|
||||||
|
result = run_module('tower_role', {
|
||||||
|
'user': rando.username,
|
||||||
|
'project': project.name,
|
||||||
|
'role': 'adhoc',
|
||||||
|
'state': 'present'
|
||||||
|
}, admin_user)
|
||||||
|
assert result.get('failed', False)
|
||||||
|
msg = result.get('msg')
|
||||||
|
assert 'has no role adhoc_role' in msg
|
||||||
|
assert 'available roles: admin_role, use_role, update_role, read_role' in msg
|
||||||
|
|||||||
@@ -50,6 +50,18 @@
|
|||||||
that:
|
that:
|
||||||
- "result is changed"
|
- "result is changed"
|
||||||
|
|
||||||
|
- name: Add Joe to workflow execute role, no-op
|
||||||
|
tower_role:
|
||||||
|
user: "{{ username }}"
|
||||||
|
role: execute
|
||||||
|
workflow: test-role-workflow
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result is not changed"
|
||||||
|
|
||||||
- name: Delete a User
|
- name: Delete a User
|
||||||
tower_user:
|
tower_user:
|
||||||
username: "{{ username }}"
|
username: "{{ username }}"
|
||||||
|
|||||||
Reference in New Issue
Block a user