diff --git a/lib/ansible/modules/network/aci/msc_user.py b/lib/ansible/modules/network/aci/msc_user.py new file mode 100644 index 00000000000..b58c544075e --- /dev/null +++ b/lib/ansible/modules/network/aci/msc_user.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r''' +--- +module: msc_user +short_description: Manage users +description: +- Manage users on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +version_added: '2.8' +options: + user_id: + description: + - The ID of the user. + required: yes + user: + description: + - The name of the user. + required: yes + aliases: [ name, user_name ] + user_password: + description: + - The password of the user. + first_name: + description: + - The first name of the user. + last_name: + description: + - The last name of the user. + email: + description: + - The email address of the user. + phone: + description: + - The phone number of the user. + account_status: + description: + - The status of the user account. + choices: [ active ] + domain: + description: + - The domain this user belongs to. + roles: + description: + - The roles this user has. + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: msc +''' + +EXAMPLES = r''' +- name: Add a new user + msc_user: + host: msc_host + username: admin + password: SomeSecretPassword + name: north_europe + user_id: 101 + description: North European Datacenter + state: present + delegate_to: localhost + +- name: Remove a user + msc_user: + host: msc_host + username: admin + password: SomeSecretPassword + name: north_europe + state: absent + delegate_to: localhost + +- name: Query a user + msc_user: + host: msc_host + username: admin + password: SomeSecretPassword + name: north_europe + state: query + delegate_to: localhost + register: query_result + +- name: Query all users + msc_user: + host: msc_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +''' + +RETURN = r''' # ''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.aci.msc import MSCModule, msc_argument_spec, issubset + + +def main(): + argument_spec = msc_argument_spec() + argument_spec.update( + user_id=dict(type='str', required=False), + user=dict(type='str', required=False, aliases=['name', 'user_name']), + user_password=dict(type='str', no_log=True), + first_name=dict(type='str'), + last_name=dict(type='str'), + email=dict(type='str'), + phone=dict(type='str'), + # FIXME: What possible options do we have ? + account_status=dict(type='str', choices=['active']), + domain=dict(type='str'), + roles=dict(type='list'), + state=dict(type='str', default='present', choices=['absent', 'present', 'query']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ['state', 'absent', ['user_name']], + ['state', 'present', ['user_name', 'password', 'first_name', 'last_name', 'email', 'phone', 'account_status']], + ], + ) + + user_id = module.params['user_id'] + user_name = module.params['user'] + user_password = module.params['user_password'] + first_name = module.params['first_name'] + last_name = module.params['last_name'] + email = module.params['email'] + phone = module.params['phone'] + account_status = module.params['account_status'] + # FIXME: Look up domain + domain = module.params['domain'] + # FIXME: Look up roles + roles = module.params['roles'] + roles_dict = list() + if roles: + for role in roles: + roles_dict.append(dict(roleId=role)) + state = module.params['state'] + + msc = MSCModule(module) + + path = 'users' + + # Query for existing object(s) + if user_id is None and user_name is None: + msc.existing = msc.query_objs(path) + elif user_id is None: + msc.existing = msc.get_obj(path, username=user_name) + if msc.existing: + user_id = msc.existing['id'] + elif user_name is None: + msc.existing = msc.get_obj(path, id=user_id) + else: + msc.existing = msc.get_obj(path, id=user_id) + existing_by_name = msc.get_obj(path, username=user_name) + if existing_by_name and user_id != existing_by_name['id']: + msc.fail_json(msg="Provided user '{1}' with id '{2}' does not match existing id '{3}'.".format(user_name, user_id, existing_by_name['id'])) + + # If we found an existing object, continue with it + if user_id: + path = 'users/{id}'.format(id=user_id) + + if state == 'query': + pass + + elif state == 'absent': + msc.previous = msc.existing + if msc.existing: + if module.check_mode: + msc.existing = {} + else: + msc.existing = msc.request(path, method='DELETE') + + elif state == 'present': + msc.previous = msc.existing + + msc.sanitize(dict( + id=user_id, + username=user_name, + password=user_password, + firstName=first_name, + lastName=last_name, + emailAddress=email, + phoneNumber=phone, + # accountStatus={}, + accountStatus=account_status, + needsPasswordUpdate=False, + domainId=domain, + roles=roles_dict, + # active=True, + # remote=True, + ), collate=True) + + if msc.existing: + if not issubset(msc.sent, msc.existing): + # NOTE: Since MSC always returns '******' as password, we need to assume a change + if 'password' in msc.sent: + msc.result['changed'] = True + + if module.check_mode: + msc.existing = msc.proposed + else: + msc.existing = msc.request(path, method='PUT', data=msc.sent) + else: + if module.check_mode: + msc.existing = msc.proposed + else: + msc.existing = msc.request(path, method='POST', data=msc.sent) + + msc.exit_json() + + +if __name__ == "__main__": + main() diff --git a/test/integration/targets/msc_user/aliases b/test/integration/targets/msc_user/aliases new file mode 100644 index 00000000000..cd28d3c3621 --- /dev/null +++ b/test/integration/targets/msc_user/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +unsupported diff --git a/test/integration/targets/msc_user/tasks/main.yml b/test/integration/targets/msc_user/tasks/main.yml new file mode 100644 index 00000000000..ca2feeb8c1d --- /dev/null +++ b/test/integration/targets/msc_user/tasks/main.yml @@ -0,0 +1,291 @@ +# Test code for the MSC modules +# Copyright: (c) 2018, Dag Wieers (@dagwieers) + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: msc_hostname, msc_username and msc_password.' + when: msc_hostname is not defined or msc_username is not defined or msc_password is not defined + + +# CLEAN ENVIRONMENT +- name: Remove user ansible_test2 + msc_user: &user_absent + host: '{{ msc_hostname }}' + username: '{{ msc_username }}' + password: '{{ msc_password }}' + validate_certs: '{{ msc_validate_certs | default(false) }}' + use_ssl: '{{ msc_use_ssl | default(true) }}' + use_proxy: '{{ msc_use_proxy | default(true) }}' + output_level: '{{ msc_output_level | default("info") }}' + user_name: ansible_test2 + state: absent + +- name: Remove user ansible_test + msc_user: + <<: *user_absent + user_name: ansible_test + register: cm_remove_user + + +# ADD USER +- name: Add user (check_mode) + msc_user: &user_present + host: '{{ msc_hostname }}' + username: '{{ msc_username }}' + password: '{{ msc_password }}' + validate_certs: '{{ msc_validate_certs | default(false) }}' + use_ssl: '{{ msc_use_ssl | default(true) }}' + use_proxy: '{{ msc_use_proxy | default(true) }}' + output_level: '{{ msc_output_level | default("info") }}' + user_name: ansible_test + user_password: 'S0m3!1n1t14l!p455w0rd' + # NOTE: First name, last name, phone and email are mandatory on creation + first_name: Ansible + last_name: Test + email: msc@cisco.com + phone: +32 478 436 299 + account_status: active + roles: + - 0000ffff0000000000000031 + + state: present + check_mode: yes + register: cm_add_user + +- name: Verify cm_add_user + assert: + that: + - cm_add_user is changed + - cm_add_user.previous == {} + - cm_add_user.current.id is not defined + - cm_add_user.current.username == 'ansible_test' + +- name: Add user (normal mode) + msc_user: *user_present + register: nm_add_user + +- name: nm_Verify add_user + assert: + that: + - nm_add_user is changed + - nm_add_user.previous == {} + - nm_add_user.current.id is defined + - nm_add_user.current.username == 'ansible_test' + +- name: Add user again (check_mode) + msc_user: + <<: *user_present + # NOTE: We need to modify the password for a new user + user_password: 'S0m3!n3w!p455w0rd' + check_mode: yes + register: cm_add_user_again + +- name: Verify cm_add_user_again + assert: + that: + - cm_add_user_again is changed + - cm_add_user_again.previous.username == 'ansible_test' + - cm_add_user_again.current.id == nm_add_user.current.id + - cm_add_user_again.current.username == 'ansible_test' + +- name: Add user again (normal mode) + msc_user: + <<: *user_present + # NOTE: We need to modify the password for a new user + user_password: 'S0m3!n3w!p455w0rd' + register: nm_add_user_again + +- name: Verify nm_add_user_again + assert: + that: + - nm_add_user_again is changed + - nm_add_user_again.previous.username == 'ansible_test' + - nm_add_user_again.current.id == nm_add_user.current.id + - nm_add_user_again.current.username == 'ansible_test' + + +# CHANGE USER +- name: Change user (check_mode) + msc_user: + <<: *user_present + user_id: '{{ nm_add_user.current.id }}' + user_name: ansible_test2 + user_password: null + check_mode: yes + register: cm_change_user + +- name: Verify cm_change_user + assert: + that: + - cm_change_user is changed + - cm_change_user.current.id == nm_add_user.current.id + - cm_change_user.current.username == 'ansible_test2' + +- name: Change user (normal mode) + msc_user: + <<: *user_present + user_id: '{{ nm_add_user.current.id }}' + user_name: ansible_test2 + user_password: null + output_level: debug + register: nm_change_user + +- name: Verify nm_change_user + assert: + that: + - nm_change_user is changed + - nm_change_user.current.id == nm_add_user.current.id + - nm_change_user.current.username == 'ansible_test2' + +- name: Change user again (check_mode) + msc_user: + <<: *user_present + user_id: '{{ nm_add_user.current.id }}' + user_name: ansible_test2 + user_password: null + check_mode: yes + register: cm_change_user_again + +- name: Verify cm_change_user_again + assert: + that: + - cm_change_user_again is not changed + - cm_change_user_again.current.id == nm_add_user.current.id + - cm_change_user_again.current.username == 'ansible_test2' + +- name: Change user again (normal mode) + msc_user: + <<: *user_present + user_id: '{{ nm_add_user.current.id }}' + user_name: ansible_test2 + user_password: null + register: nm_change_user_again + +- name: Verify nm_change_user_again + assert: + that: + - nm_change_user_again is not changed + - nm_change_user_again.current.id == nm_add_user.current.id + - nm_change_user_again.current.username == 'ansible_test2' + + +# QUERY ALL USERS +- name: Query all users (check_mode) + msc_user: &user_query + host: '{{ msc_hostname }}' + username: '{{ msc_username }}' + password: '{{ msc_password }}' + validate_certs: '{{ msc_validate_certs | default(false) }}' + use_ssl: '{{ msc_use_ssl | default(true) }}' + use_proxy: '{{ msc_use_proxy | default(true) }}' + output_level: '{{ msc_output_level | default("info") }}' + state: query + check_mode: yes + register: cm_query_all_users + +- name: Query all users (normal mode) + msc_user: *user_query + register: nm_query_all_users + +- name: Verify query_all_users + assert: + that: + - cm_query_all_users is not changed + - nm_query_all_users is not changed + # NOTE: Order of users is not stable between calls + #- cm_query_all_users == nm_query_all_users + + +# QUERY A USER +- name: Query our user + msc_user: + <<: *user_query + user_name: ansible_test2 + check_mode: yes + register: cm_query_user + +- name: Query our user + msc_user: + <<: *user_query + user_name: ansible_test2 + register: nm_query_user + +- name: Verify query_user + assert: + that: + - cm_query_user is not changed + - cm_query_user.current.id == nm_add_user.current.id + - cm_query_user.current.username == 'ansible_test2' + - nm_query_user is not changed + - nm_query_user.current.id == nm_add_user.current.id + - nm_query_user.current.username == 'ansible_test2' + - cm_query_user == nm_query_user + + +# REMOVE USER +- name: Remove user (check_mode) + msc_user: *user_absent + check_mode: yes + register: cm_remove_user + +- name: Verify cm_remove_user + assert: + that: + - cm_remove_user is changed + - cm_remove_user.current == {} + +- name: Remove user (normal mode) + msc_user: *user_absent + register: nm_remove_user + +- name: Verify nm_remove_user + assert: + that: + - nm_remove_user is changed + - nm_remove_user.current == {} + +- name: Remove user again (check_mode) + msc_user: *user_absent + check_mode: yes + register: cm_remove_user_again + +- name: Verify cm_remove_user_again + assert: + that: + - cm_remove_user_again is not changed + - cm_remove_user_again.current == {} + +- name: Remove user again (normal mode) + msc_user: *user_absent + register: nm_remove_user_again + +- name: Verify nm_remove_user_again + assert: + that: + - nm_remove_user_again is not changed + - nm_remove_user_again.current == {} + + +# QUERY NON-EXISTING USER +- name: Query non-existing user (check_mode) + msc_user: + <<: *user_query + user_name: ansible_test + check_mode: yes + register: cm_query_non_user + +- name: Query non-existing user (normal mode) + msc_user: + <<: *user_query + user_name: ansible_test + register: nm_query_non_user + +# TODO: Implement more tests +- name: Verify query_non_user + assert: + that: + - cm_query_non_user is not changed + - nm_query_non_user is not changed + - cm_query_non_user == nm_query_non_user