#!/usr/bin/python # coding: utf-8 -*- # (c) 2017, Wayne Witzel III # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} DOCUMENTATION = ''' --- module: tower_role version_added: "2.3" author: "Wayne Witzel III (@wwitzel3)" short_description: grant or revoke an Ansible Tower role. description: - Roles are used for access control, this module is for managing user access to server resources. - Grant or revoke Ansible Tower roles to users. See U(https://www.ansible.com/tower) for an overview. options: user: description: - User that receives the permissions specified by the role. type: str team: description: - Team that receives the permissions specified by the role. type: str role: description: - The role type to grant/revoke. required: True choices: ["admin", "read", "member", "execute", "adhoc", "update", "use", "auditor", "project_admin", "inventory_admin", "credential_admin", "workflow_admin", "notification_admin", "job_template_admin"] type: str target_team: 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: str inventory: description: - Inventory the role acts on. type: str job_template: description: - The job template the role acts on. type: str workflow: description: - The workflow job template the role acts on. type: str credential: description: - Credential the role acts on. type: str organization: description: - Organization the role acts on. type: str project: description: - Project the role acts on. type: str state: description: - 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" choices: ["present", "absent"] type: str tower_oauthtoken: description: - The Tower OAuth token to use. required: False type: str version_added: "3.7" requirements: - ansible-tower-cli >= 3.0.2 extends_documentation_fragment: awx.awx.auth ''' EXAMPLES = ''' - name: Add jdoe to the member role of My Team tower_role: user: jdoe target_team: "My Team" role: member state: present ''' from ..module_utils.tower_api import TowerModule def main(): argument_spec = dict( user=dict(), team=dict(), role=dict(choices=["admin", "read", "member", "execute", "adhoc", "update", "use", "auditor", "project_admin", "inventory_admin", "credential_admin", "workflow_admin", "notification_admin", "job_template_admin"], required=True), target_team=dict(), inventory=dict(), job_template=dict(), workflow=dict(), credential=dict(), organization=dict(), project=dict(), state=dict(choices=['present', 'absent'], default='present'), ) module = TowerModule(argument_spec=argument_spec, supports_check_mode=True) role_type = module.params.pop('role') role_field = role_type + '_role' state = module.params.pop('state') module.json_output['role'] = role_type # Lookup data for all the objects specified in params params = module.params.copy() resource_param_keys = ( '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' resource_name = params.get(param) 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 # separate actors from resources actor_data = {} for key in ('user', 'team'): if key in resource_data: actor_data[key] = resource_data.pop(key) # build association agenda 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__': main()