From f98d41c1219ddac35559764b5db899462ef2a814 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Wed, 15 Feb 2017 11:59:03 -0500 Subject: [PATCH] Ansible Tower user and credential module (#21020) * rename tower config module parameters to avoid conflicts * add Ansible Tower user module * add Ansible Tower credential module * remove errant hash from interpreter line * friendlier error messages * Update tower_verify_ssl defaults and module examples * Update tower_verify_ssl default documentation * Tower expects satellite6 not foreman --- lib/ansible/module_utils/ansible_tower.py | 8 +- .../ansible_tower/tower_credential.py | 298 ++++++++++++++++++ .../ansible_tower/tower_organization.py | 33 +- .../ansible_tower/tower_user.py | 199 ++++++++++++ 4 files changed, 516 insertions(+), 22 deletions(-) create mode 100644 lib/ansible/modules/web_infrastructure/ansible_tower/tower_credential.py create mode 100644 lib/ansible/modules/web_infrastructure/ansible_tower/tower_user.py diff --git a/lib/ansible/module_utils/ansible_tower.py b/lib/ansible/module_utils/ansible_tower.py index 3055ddfd830..85980c34692 100644 --- a/lib/ansible/module_utils/ansible_tower.py +++ b/lib/ansible/module_utils/ansible_tower.py @@ -52,16 +52,16 @@ def tower_auth_config(module): return parser.string_to_dict(f.read()) else: auth_config = {} - host = module.params.get('host') + host = module.params.get('tower_host') if host: auth_config['host'] = host - username = module.params.get('username') + username = module.params.get('tower_username') if username: auth_config['username'] = username - password = module.params.get('password') + password = module.params.get('tower_password') if password: auth_config['password'] = password - verify_ssl = module.params.get('verify_ssl') + verify_ssl = module.params.get('tower_verify_ssl') if verify_ssl: auth_config['verify_ssl'] = verify_ssl return auth_config diff --git a/lib/ansible/modules/web_infrastructure/ansible_tower/tower_credential.py b/lib/ansible/modules/web_infrastructure/ansible_tower/tower_credential.py new file mode 100644 index 00000000000..9b17e0bd8d4 --- /dev/null +++ b/lib/ansible/modules/web_infrastructure/ansible_tower/tower_credential.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (c) 2017, Wayne Witzel III +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'version': '1.0'} + +DOCUMENTATION = ''' +--- +module: tower_credential +version_added: "2.3" +short_description: create, update, or destroy Ansible Tower credential. +description: + - Create, update, or destroy Ansible Tower credentials. See + U(https://www.ansible.com/tower) for an overview. +options: + name: + description: + - The name to use for the credential. + required: True + description: + description: + - The description to use for the credential. + user: + description: + - User that should own this credential. + required: False + default: null + team: + description: + - Team that should own this credential. + required: False + default: null + project: + description: + - Project that should for this credential. + required: False + default: null + organization: + description: + - Organization that should own the credential. + required: False + default: null + kind: + description: + - Type of credential being added. + required: True + choices: ["ssh", "net", "scm", "aws", "rax", "vmware", "satellite6", "cloudforms", "gce", "azure", "azure_rm", "openstack"] + host: + description: + - Host for this credential. + required: False + default: null + username: + description: + - Username for this credential. access_key for AWS. + required: False + default: null + password: + description: + - Password for this credential. Use ASK for prompting. secret_key for AWS. api_key for RAX. + required: False + default: null + ssh_key_data: + description: + - Path to SSH private key. + required: False + default: null + ssh_key_unlock: + description: + - Unlock password for ssh_key. Use ASK for prompting. + authorize: + description: + - Should use authroize for net type. + required: False + default: False + authorize_password: + description: + - Password for net credentials that require authroize. + required: False + default: null + client: + description: + - Client or application ID for azure_rm type. + required: False + default: null + secret: + description: + - Secret token for azure_rm type. + required: False + default: null + subscription: + description: + - Subscription ID for azure_rm type. + required: False + default: null + tenant: + description: + - Tenant ID for azure_rm type. + required: False + default: null + domain: + description: + - Domain for openstack type. + required: False + default: null + become_method: + description: + - Become method to Use for privledge escalation. + required: False + choices: ["None", "sudo", "su", "pbrun", "pfexec"] + default: "None" + become_username: + description: + - Become username. Use ASK for prompting. + required: False + default: null + become_password: + description: + - Become password. Use ASK for prompting. + required: False + default: null + vault_password: + description: + - Valut password. Use ASK for prompting. + state: + description: + - Desired state of the resource. + required: False + default: "present" + choices: ["present", "absent"] + tower_host: + description: + - URL to your Tower instance. + required: False + default: null + tower_username: + description: + - Username for your Tower instance. + required: False + default: null + tower_password: + description: + - Password for your Tower instance. + required: False + default: null + tower_verify_ssl: + description: + - Dis/allow insecure connections to Tower. If C(no), SSL certificates will not be validated. + This should only be used on personally controlled sites using self-signed certificates. + required: False + default: True + tower_config_file: + description: + - Path to the Tower config file. See notes. + required: False + default: null + + +requirements: + - "python >= 2.6" + - "ansible-tower-cli >= 3.0.2" + +notes: + - If no I(config_file) is provided we will attempt to use the tower-cli library + defaults to find your Tower host information. + - I(config_file) should contain Tower configuration in the following format + host=hostname + username=username + password=password +''' + + +EXAMPLES = ''' +- name: Add tower credential + tower_credential: + name: Team Name + description: Team Description + organization: test-org + state: present + tower_config_file: "~/tower_cli.cfg" +''' + +try: + import os + import tower_cli + import tower_cli.utils.exceptions as exc + + from tower_cli.conf import settings + from ansible.module_utils.ansible_tower import tower_auth_config, tower_check_mode + + HAS_TOWER_CLI = True +except ImportError: + HAS_TOWER_CLI = False + + +def main(): + module = AnsibleModule( + argument_spec = dict( + name = dict(required=True), + user = dict(), + team = dict(), + kind = dict(required=True, + choices=["ssh", "net", "scm", "aws", "rax", "vmware", "satellite6", + "cloudforms", "gce", "azure", "azure_rm", "openstack"]), + host = dict(), + username = dict(), + password = dict(no_log=True), + ssh_key_data = dict(no_log=True), + ssh_key_unlock = dict(no_log=True), + authorize = dict(type='bool', default=False), + authorize_password = dict(no_log=True), + client = dict(), + secret = dict(), + tenant = dict(), + subscription = dict(), + domain = dict(), + become_method = dict(), + become_username = dict(), + become_password = dict(no_log=True), + vault_password = dict(no_log=True), + description = dict(), + organization = dict(required=True), + project = dict(), + tower_host = dict(), + tower_username = dict(), + tower_password = dict(no_log=True), + tower_verify_ssl = dict(type='bool', default=True), + tower_config_file = dict(type='path'), + state = dict(choices=['present', 'absent'], default='present'), + ), + supports_check_mode=True + ) + + if not HAS_TOWER_CLI: + module.fail_json(msg='ansible-tower-cli required for this module') + + name = module.params.get('name') + organization = module.params.get('organization') + state = module.params.get('state') + + json_output = {'credential': name, 'state': state} + + tower_auth = tower_auth_config(module) + with settings.runtime_values(**tower_auth): + tower_check_mode(module) + credential = tower_cli.get_resource('credential') + try: + params = module.params.copy() + params['create_on_missing'] = True + + if organization: + org_res = tower_cli.get_resource('organization') + org = org_res.get(name=organization) + params['organization'] = org['id'] + + if params['ssh_key_data']: + filename = params['ssh_key_data'] + filename = os.path.expanduser(filename) + if not os.path.exists(filename): + module.fail_json(msg='file not found: %s' % filename) + if os.path.isdir(filename): + module.fail_json(msg='attempted to read contents of directory: %s' % filename) + with open(filename, 'rb') as f: + params['ssh_key_data'] = f.read() + + if state == 'present': + result = credential.modify(**params) + json_output['id'] = result['id'] + elif state == 'absent': + result = credential.delete(**params) + except (exc.NotFound) as excinfo: + module.fail_json(msg='Failed to update credential, organization not found: {0}'.format(excinfo), changed=False) + except (exc.ConnectionError, exc.BadRequest, exc.NotFound) as excinfo: + module.fail_json(msg='Failed to update credential: {0}'.format(excinfo), changed=False) + + json_output['changed'] = result['changed'] + module.exit_json(**json_output) + + +from ansible.module_utils.basic import AnsibleModule +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/web_infrastructure/ansible_tower/tower_organization.py b/lib/ansible/modules/web_infrastructure/ansible_tower/tower_organization.py index 7c1d3397ac4..406c3804347 100644 --- a/lib/ansible/modules/web_infrastructure/ansible_tower/tower_organization.py +++ b/lib/ansible/modules/web_infrastructure/ansible_tower/tower_organization.py @@ -44,27 +44,27 @@ options: required: False default: "present" choices: ["present", "absent"] - host: + tower_host: description: - URL to your Tower instance. required: False default: null - username: + tower_username: description: - Username for your Tower instance. required: False default: null - password: + tower_password: description: - Password for your Tower instance. required: False default: null - verify_ssl: + tower_verify_ssl: description: - Dis/allow insecure connections to Tower. If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. required: False - default: 'yes' + default: True tower_config_file: description: - Path to the Tower config file. See notes. @@ -87,17 +87,14 @@ notes: EXAMPLES = ''' - - tasks - - name: Create organization - tower_organization: - name: "Foo" - description: "Foo bar organization" - state: present - tower_config_file: "~/tower_cli.cfg" +- name: Create tower organization + tower_organization: + name: "Foo" + description: "Foo bar organization" + state: present + tower_config_file: "~/tower_cli.cfg" ''' -import os - try: import tower_cli import tower_cli.utils.exceptions as exc @@ -115,10 +112,10 @@ def main(): argument_spec = dict( name = dict(required=True), description = dict(), - host = dict(), - username = dict(), - password = dict(no_log=True), - verify_ssl = dict(type='bool', default='yes'), + tower_host = dict(), + tower_username = dict(), + tower_password = dict(no_log=True), + tower_verify_ssl = dict(type='bool', default=True), tower_config_file = dict(type='path'), state = dict(choices=['present', 'absent'], default='present'), ), diff --git a/lib/ansible/modules/web_infrastructure/ansible_tower/tower_user.py b/lib/ansible/modules/web_infrastructure/ansible_tower/tower_user.py new file mode 100644 index 00000000000..ad879a73291 --- /dev/null +++ b/lib/ansible/modules/web_infrastructure/ansible_tower/tower_user.py @@ -0,0 +1,199 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (c) 2017, Wayne Witzel III +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'version': '1.0'} + +DOCUMENTATION = ''' +--- +module: tower_user +version_added: "2.3" +short_description: create, update, or destroy Ansible Tower user. +description: + - Create, update, or destroy Ansible Tower users. See + U(https://www.ansible.com/tower) for an overview. +options: + username: + description: + - The username of the user. + required: True + first_name: + description: + - First name of the user. + required: False + default: null + last_name: + description: + - Last name of the user. + required: False + default: null + email: + description: + - Email address of the user. + required: True + password: + description: + - Password of the user. + required: False + default: null + organization: + description: + - Organization the user should be made a member of. + required: False + default: null + superuser: + description: + - User is a system wide administator. + required: False + default: False + auditor: + description: + - User is a system wide auditor. + required: False + default: False + state: + description: + - Desired state of the resource. + required: False + default: "present" + choices: ["present", "absent"] + tower_host: + description: + - URL to your Tower instance. + required: False + default: null + tower_username: + description: + - Username for your Tower instance. + required: False + default: null + tower_password: + description: + - Password for your Tower instance. + required: False + default: null + tower_verify_ssl: + description: + - Dis/allow insecure connections to Tower. If C(no), SSL certificates will not be validated. + This should only be used on personally controlled sites using self-signed certificates. + required: False + default: True + tower_config_file: + description: + - Path to the Tower config file. See notes. + required: False + default: null + + +requirements: + - "python >= 2.6" + - "ansible-tower-cli >= 3.0.3" + +notes: + - If no I(config_file) is provided we will attempt to use the tower-cli library + defaults to find your Tower host information. + - I(config_file) should contain Tower configuration in the following format + host=hostname + username=username + password=password +''' + + +EXAMPLES = ''' +- name: Add tower user + tower_user: + username: jdoe + password: foobarbaz + email: jdoe@example.org + first_name: John + last_name: Doe + state: present + tower_config_file: "~/tower_cli.cfg" +''' + +try: + import tower_cli + import tower_cli.utils.exceptions as exc + + from tower_cli.conf import settings + from ansible.module_utils.ansible_tower import tower_auth_config, tower_check_mode + + HAS_TOWER_CLI = True +except ImportError: + HAS_TOWER_CLI = False + + +def main(): + module = AnsibleModule( + argument_spec = dict( + username = dict(required=True), + first_name = dict(), + last_name = dict(), + password = dict(no_log=True), + email = dict(required=True), + organization = dict(), + superuser = dict(type='bool', default=False), + auditor = dict(type='bool', default=False), + tower_host = dict(), + tower_username = dict(), + tower_password = dict(no_log=True), + tower_verify_ssl = dict(type='bool', default=True), + tower_config_file = dict(type='path'), + state = dict(choices=['present', 'absent'], default='present'), + ), + supports_check_mode=True + ) + + if not HAS_TOWER_CLI: + module.fail_json(msg='ansible-tower-cli required for this module') + + username = module.params.get('username') + first_name = module.params.get('first_name') + last_name = module.params.get('last_name') + password = module.params.get('password') + email = module.params.get('email') + organization = module.params.get('organization') + superuser = module.params.get('superuser') + auditor = module.params.get('auditor') + state = module.params.get('state') + + json_output = {'username': username, 'state': state} + + tower_auth = tower_auth_config(module) + with settings.runtime_values(**tower_auth): + tower_check_mode(module) + user = tower_cli.get_resource('user') + try: + if state == 'present': + result = user.modify(username=username, first_name=first_name, last_name=last_name, + email=email, password=password, organization=organization, + is_superuser=superuser, is_auditor=auditor, create_on_missing=True) + json_output['id'] = result['id'] + elif state == 'absent': + result = user.delete(username=username) + except (exc.ConnectionError, exc.BadRequest) as excinfo: + module.fail_json(msg='Failed to update the user: {0}'.format(excinfo), changed=False) + + json_output['changed'] = result['changed'] + module.exit_json(**json_output) + + +from ansible.module_utils.basic import AnsibleModule +if __name__ == '__main__': + main()