From cb0f0cb67f474274644a83ea880f47979e98efb6 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Thu, 24 May 2018 13:03:25 -0700 Subject: [PATCH] six set of modules (#40492) * six set of modules * fix issues * fix issues * Review fixes * review fixes --- .../modules/storage/netapp/na_ontap_snmp.py | 152 +++++++ .../modules/storage/netapp/na_ontap_svm.py | 346 ++++++++++++++++ .../storage/netapp/na_ontap_ucadapter.py | 196 +++++++++ .../modules/storage/netapp/na_ontap_user.py | 390 ++++++++++++++++++ .../storage/netapp/na_ontap_user_role.py | 210 ++++++++++ 5 files changed, 1294 insertions(+) create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_snmp.py create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_svm.py create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_ucadapter.py create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_user.py create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_user_role.py diff --git a/lib/ansible/modules/storage/netapp/na_ontap_snmp.py b/lib/ansible/modules/storage/netapp/na_ontap_snmp.py new file mode 100644 index 00000000000..83cb39f4d2d --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_snmp.py @@ -0,0 +1,152 @@ +#!/usr/bin/python +""" +create SNMP module to add/delete/modify SNMP user +""" + +# (c) 2018, NetApp, Inc +# 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 = ''' +author: "Archana Ganesan (garchana@netapp.com), Suhas Bangalore Shekar (bsuhas@netapp.com)" +description: + - "Create/Delete SNMP community" +extends_documentation_fragment: + - netapp.na_ontap +module: na_ontap_snmp +options: + access_control: + description: + - "Access control for the community. The only supported value is 'ro' (read-only)" + required: true + community_name: + description: + - "The name of the SNMP community to manage." + required: true + state: + choices: ['present', 'absent'] + description: + - "Whether the specified SNMP community should exist or not." + default: 'present' +short_description: "Manage NetApp SNMP community" +version_added: "2.6" +''' + +EXAMPLES = """ + - name: Create SNMP community + na_ontap_snmp: + state: present + community_name: communityName + access_control: 'ro' + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + - name: Delete SNMP community + na_ontap_snmp: + state: absent + community_name: communityName + access_control: 'ro' + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" +""" + +RETURN = """ +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +import ansible.module_utils.netapp as netapp_utils + +HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() + + +class NetAppONTAPSnmp(object): + '''Class with SNMP methods, doesn't support check mode''' + + def __init__(self): + + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), + community_name=dict(required=True, type='str'), + access_control=dict(required=True, type='str'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=False + ) + + parameters = self.module.params + # set up state variables + self.state = parameters['state'] + self.community_name = parameters['community_name'] + self.access_control = parameters['access_control'] + + if HAS_NETAPP_LIB is False: + self.module.fail_json(msg="the python NetApp-Lib module is required") + else: + self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) + + def invoke_snmp_community(self, zapi): + """ + Invoke zapi - add/delete take the same NaElement structure + @return: SUCCESS / FAILURE with an error_message + """ + snmp_community = netapp_utils.zapi.NaElement.create_node_with_children( + zapi, **{'community': self.community_name, + 'access-control': self.access_control}) + try: + self.server.invoke_successfully(snmp_community, enable_tunneling=True) + except netapp_utils.zapi.NaApiError: # return False for duplicate entry + return False + return True + + def add_snmp_community(self): + """ + Adds a SNMP community + """ + return self.invoke_snmp_community('snmp-community-add') + + def delete_snmp_community(self): + """ + Delete a SNMP community + """ + return self.invoke_snmp_community('snmp-community-delete') + + def apply(self): + """ + Apply action to SNMP community + This module is not idempotent: + Add doesn't fail the playbook if user is trying + to add an already existing snmp community + """ + changed = False + results = netapp_utils.get_cserver(self.server) + cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) + netapp_utils.ems_log_event("na_ontap_snmp", cserver) + if self.state == 'present': # add + if self.add_snmp_community(): + changed = True + elif self.state == 'absent': # delete + if self.delete_snmp_community(): + changed = True + + self.module.exit_json(changed=changed) + + +def main(): + '''Execute action''' + community_obj = NetAppONTAPSnmp() + community_obj.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_svm.py b/lib/ansible/modules/storage/netapp/na_ontap_svm.py new file mode 100644 index 00000000000..497aef1b3a2 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_svm.py @@ -0,0 +1,346 @@ +#!/usr/bin/python + +# (c) 2018, NetApp, Inc +# 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: na_ontap_svm + +short_description: Manage NetApp Ontap svm +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: Sumit Kumar (sumit4@netapp.com), Archana Ganesan (garchana@netapp.com) + +description: +- Create, modify or delete svm on NetApp Ontap + +options: + + state: + description: + - Whether the specified SVM should exist or not. + choices: ['present', 'absent'] + default: 'present' + + name: + description: + - The name of the SVM to manage. + required: true + + new_name: + description: + - New name of the SVM to be renamed + + root_volume: + description: + - Root volume of the SVM. Required when C(state=present). + + root_volume_aggregate: + description: + - The aggregate on which the root volume will be created. + - Required when C(state=present). + + root_volume_security_style: + description: + - Security Style of the root volume. + - When specified as part of the vserver-create, + this field represents the security style for the Vserver root volume. + - When specified as part of vserver-get-iter call, + this will return the list of matching Vservers. + - The 'unified' security style, which applies only to Infinite Volumes, + cannot be applied to a Vserver's root volume. + - Required when C(state=present) + choices: ['unix', 'ntfs', 'mixed', 'unified'] + + allowed_protocols: + description: + - Allowed Protocols. + - When specified as part of a vserver-create, + this field represent the list of protocols allowed on the Vserver. + - When part of vserver-get-iter call, + this will return the list of Vservers + which have any of the protocols specified + as part of the allowed-protocols. + - When part of vserver-modify, + this field should include the existing list + along with new protocol list to be added to prevent data disruptions. + - Possible values + - nfs NFS protocol, + - cifs CIFS protocol, + - fcp FCP protocol, + - iscsi iSCSI protocol, + - ndmp NDMP protocol, + - http HTTP protocol, + - nvme NVMe protocol + + aggr_list: + description: + - List of aggregates assigned for volume operations. + - These aggregates could be shared for use with other Vservers. + - When specified as part of a vserver-create, + this field represents the list of aggregates + that are assigned to the Vserver for volume operations. + - When part of vserver-get-iter call, + this will return the list of Vservers + which have any of the aggregates specified as part of the aggr-list. + +''' + +EXAMPLES = """ + + - name: Create SVM + na_ontap_svm: + state: present + name: ansibleVServer + root_volume: vol1 + root_volume_aggregate: aggr1 + root_volume_security_style: mixed + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +""" + +RETURN = """ +""" +import traceback + +import ansible.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() + + +class NetAppOntapSVM(object): + + def __init__(self): + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=[ + 'present', 'absent'], default='present'), + name=dict(required=True, type='str'), + new_name=dict(required=False, type='str'), + root_volume=dict(type='str'), + root_volume_aggregate=dict(type='str'), + root_volume_security_style=dict(type='str', choices=['unix', + 'ntfs', + 'mixed', + 'unified' + ]), + allowed_protocols=dict(type='list'), + aggr_list=dict(type='list') + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + p = self.module.params + + # set up state variables + self.state = p['state'] + self.name = p['name'] + self.new_name = p['new_name'] + self.root_volume = p['root_volume'] + self.root_volume_aggregate = p['root_volume_aggregate'] + self.root_volume_security_style = p['root_volume_security_style'] + self.allowed_protocols = p['allowed_protocols'] + self.aggr_list = p['aggr_list'] + + if HAS_NETAPP_LIB is False: + self.module.fail_json( + msg="the python NetApp-Lib module is required") + else: + self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) + + def get_vserver(self): + """ + Checks if vserver exists. + + :return: + True if vserver found + False if vserver is not found + :rtype: bool + """ + + vserver_info = netapp_utils.zapi.NaElement('vserver-get-iter') + query_details = netapp_utils.zapi.NaElement.create_node_with_children( + 'vserver-info', **{'vserver-name': self.name}) + + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(query_details) + vserver_info.add_child_elem(query) + + result = self.server.invoke_successfully(vserver_info, + enable_tunneling=False) + vserver_details = None + if (result.get_child_by_name('num-records') and + int(result.get_child_content('num-records')) >= 1): + attributes_list = result.get_child_by_name('attributes-list') + vserver_info = attributes_list.get_child_by_name('vserver-info') + aggr_list = list() + ''' vserver aggr-list can be empty by default''' + get_list = vserver_info.get_child_by_name('aggr-list') + if get_list is not None: + aggregates = get_list.get_children() + for aggr in aggregates: + aggr_list.append(aggr.get_content()) + + protocols = list() + '''allowed-protocols is not empty by default''' + get_protocols = vserver_info.get_child_by_name( + 'allowed-protocols').get_children() + for protocol in get_protocols: + protocols.append(protocol.get_content()) + vserver_details = {'name': vserver_info.get_child_content( + 'vserver-name'), + 'aggr_list': aggr_list, + 'allowed_protocols': protocols} + return vserver_details + + def create_vserver(self): + options = {'vserver-name': self.name, 'root-volume': self.root_volume} + if self.root_volume_aggregate is not None: + options['root-volume-aggregate'] = self.root_volume_aggregate + if self.root_volume_security_style is not None: + options['root-volume-security-style'] = self.root_volume_security_style + + vserver_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'vserver-create', **options) + try: + self.server.invoke_successfully(vserver_create, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg='Error provisioning SVM %s \ + with root volume %s on aggregate %s: %s' + % (self.name, self.root_volume, + self.root_volume_aggregate, to_native(e)), + exception=traceback.format_exc()) + + def delete_vserver(self): + vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children( + 'vserver-destroy', **{'vserver-name': self.name}) + + try: + self.server.invoke_successfully(vserver_delete, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg='Error deleting SVM %s \ + with root volume %s on aggregate %s: %s' + % (self.name, self.root_volume, + self.root_volume_aggregate, to_native(e)), + exception=traceback.format_exc()) + + def rename_vserver(self): + vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children( + 'vserver-rename', **{'vserver-name': self.name, + 'new-name': self.new_name}) + + try: + self.server.invoke_successfully(vserver_rename, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg='Error renaming SVM %s: %s' + % (self.name, to_native(e)), + exception=traceback.format_exc()) + + def modify_vserver(self, allowed_protocols, aggr_list): + vserver_modify = netapp_utils.zapi.NaElement.create_node_with_children( + 'vserver-modify', **{'vserver-name': self.name}) + + if allowed_protocols: + allowed_protocols = netapp_utils.zapi.NaElement( + 'allowed-protocols') + for protocol in self.allowed_protocols: + allowed_protocols.add_new_child('protocol', protocol) + vserver_modify.add_child_elem(allowed_protocols) + + if aggr_list: + aggregates = netapp_utils.zapi.NaElement('aggr-list') + for aggr in self.aggr_list: + aggregates.add_new_child('aggr-name', aggr) + vserver_modify.add_child_elem(aggregates) + + try: + self.server.invoke_successfully(vserver_modify, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg='Error modifying SVM %s: %s' + % (self.name, to_native(e)), + exception=traceback.format_exc()) + + def apply(self): + changed = False + vserver_details = self.get_vserver() + if vserver_details is not None: + results = netapp_utils.get_cserver(self.server) + cserver = netapp_utils.setup_ontap_zapi( + module=self.module, vserver=results) + netapp_utils.ems_log_event("na_ontap_svm", cserver) + + rename_vserver = False + modify_protocols = False + modify_aggr_list = False + obj = open('vserver-log', 'a') + if vserver_details is not None: + if self.state == 'absent': + changed = True + elif self.state == 'present': + if self.new_name is not None and self.new_name != self.name: + rename_vserver = True + changed = True + if self.allowed_protocols is not None: + self.allowed_protocols.sort() + vserver_details['allowed_protocols'].sort() + if self.allowed_protocols != vserver_details['allowed_protocols']: + modify_protocols = True + changed = True + if self.aggr_list is not None: + self.aggr_list.sort() + vserver_details['aggr_list'].sort() + if self.aggr_list != vserver_details['aggr_list']: + modify_aggr_list = True + changed = True + else: + if self.state == 'present': + changed = True + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if vserver_details is None: + self.create_vserver() + else: + if rename_vserver: + self.rename_vserver() + if modify_protocols or modify_aggr_list: + self.modify_vserver( + modify_protocols, modify_aggr_list) + elif self.state == 'absent': + self.delete_vserver() + + self.module.exit_json(changed=changed) + + +def main(): + v = NetAppOntapSVM() + v.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_ucadapter.py b/lib/ansible/modules/storage/netapp/na_ontap_ucadapter.py new file mode 100644 index 00000000000..2af33cfed2d --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_ucadapter.py @@ -0,0 +1,196 @@ +#!/usr/bin/python + +# (c) 2018, NetApp, Inc +# 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: na_ontap_ucadapter +short_description: ONTAP UC adapter configuration +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: chhaya gunawat (chhayag@netapp.com) + +description: + - modify the UC adapter mode and type taking pending type and mode into account. + +options: + state: + description: + - Whether the specified adapter should exist. + required: false + choices: ['present'] + default: 'present' + + adapter_name: + description: + - Specifies the adapter name. + required: true + + node_name: + description: + - Specifies the adapter home node. + required: true + + mode: + description: + - Specifies the mode of the adapter. + + type: + description: + - Specifies the fc4 type of the adapter. + +''' + +EXAMPLES = ''' + - name: Modify adapter + na_ontap_adapter: + state: present + adapter_name: data2 + node_name: laurentn-vsim1 + mode: fc + type: target + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +''' + +RETURN = ''' +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +import ansible.module_utils.netapp as netapp_utils + +HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() + + +class NetAppOntapadapter(object): + ''' object to describe adapter info ''' + + def __init__(self): + + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=['present'], default='present'), + adapter_name=dict(required=True, type='str'), + node_name=dict(required=True, type='str'), + mode=dict(required=False, type='str'), + type=dict(required=False, type='str'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + params = self.module.params + + # set up state variables + self.state = params['state'] + self.adapter_name = params['adapter_name'] + self.node_name = params['node_name'] + self.mode = params['mode'] + self.type = params['type'] + + if HAS_NETAPP_LIB is False: + self.module.fail_json(msg="the python NetApp-Lib module is required") + else: + self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) + + def get_adapter(self): + """ + Return details about the adapter + :param: + name : Name of the name of the adapter + + :return: Details about the adapter. None if not found. + :rtype: dict + """ + adapter_info = netapp_utils.zapi.NaElement('ucm-adapter-get') + adapter_info.add_new_child('adapter-name', self.adapter_name) + adapter_info.add_new_child('node-name', self.node_name) + result = self.server.invoke_successfully(adapter_info, True) + return_value = None + adapter_attributes = result.get_child_by_name('attributes').\ + get_child_by_name('uc-adapter-info') + return_value = { + 'mode': adapter_attributes.get_child_content('mode'), + 'pending-mode': adapter_attributes.get_child_content('pending-mode'), + 'type': adapter_attributes.get_child_content('fc4-type'), + 'pending-type': adapter_attributes.get_child_content('pending-fc4-type'), + 'status': adapter_attributes.get_child_content('status'), + } + return return_value + + def modify_adapter(self): + """ + Modify the adapter. + """ + params = {'adapter-name': self.adapter_name, + 'node-name': self.node_name} + if self.type is not None: + params['fc4-type'] = self.type + if self.mode is not None: + params['mode'] = self.mode + adapter_modify = netapp_utils.zapi.NaElement.create_node_with_children( + 'ucm-adapter-modify', ** params) + try: + self.server.invoke_successfully(adapter_modify, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg='Error modifying adapter %s: %s' % (self.adapter_name, to_native(e)), + exception=traceback.format_exc()) + + def apply(self): + ''' calling all adapter features ''' + changed = False + results = netapp_utils.get_cserver(self.server) + cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) + netapp_utils.ems_log_event("na_ontap_ucadapter", cserver) + adapter_detail = self.get_adapter() + + def need_to_change(expected, pending, current): + if expected is None: + return False + if pending is not None: + return pending != expected + if current is not None: + return current != expected + return False + + if adapter_detail: + changed = need_to_change(self.type, adapter_detail['pending-type'], adapter_detail['type']) or \ + need_to_change(self.mode, adapter_detail['pending-mode'], adapter_detail['mode']) + + if changed: + if self.module.check_mode: + pass + else: + self.modify_adapter() + + self.module.exit_json(changed=changed) + + +def main(): + adapter = NetAppOntapadapter() + adapter.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_user.py b/lib/ansible/modules/storage/netapp/na_ontap_user.py new file mode 100644 index 00000000000..a8e429c461c --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_user.py @@ -0,0 +1,390 @@ +#!/usr/bin/python + +# (c) 2018, NetApp, Inc +# 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: na_ontap_user + +short_description: useradmin configuration and management +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: Sumit Kumar (sumit4@netapp.com) + +description: +- Create or destroy users. + +options: + + state: + description: + - Whether the specified user should exist or not. + choices: ['present', 'absent'] + default: 'present' + + name: + description: + - The name of the user to manage. + required: true + + application: + description: + - Application to grant access to. + required: true + choices: ['console', 'http','ontapi','rsh','snmp','sp','ssh','telnet'] + + authentication_method: + description: + - Authentication method for the application. + - Not all authentication methods are valid for an application. + - Valid authentication methods for each application are as denoted in I(authentication_choices_description). + - Password for console application + - Password, domain, nsswitch, cert for http application. + - Password, domain, nsswitch, cert for ontapi application. + - Community for snmp application (when creating SNMPv1 and SNMPv2 users). + - The usm and community for snmp application (when creating SNMPv3 users). + - Password for sp application. + - Password for rsh application. + - Password for telnet application. + - Password, publickey, domain, nsswitch for ssh application. + required: true + choices: ['community', 'password', 'publickey', 'domain', 'nsswitch', 'usm'] + + set_password: + description: + - Password for the user account. + - It is ignored for creating snmp users, but is required for creating non-snmp users. + - For an existing user, this value will be used as the new password. + + role_name: + description: + - The name of the role. Required when C(state=present) + + lock_user: + description: + - Whether the specified user account is locked. + type: bool + + vserver: + description: + - The name of the vserver to use. + required: true + +''' + +EXAMPLES = """ + + - name: Create User + na_ontap_user: + state: present + name: SampleUser + application: ssh + authentication_method: password + set_password: apn1242183u1298u41 + lock_user: True + role_name: vsadmin + vserver: ansibleVServer + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +""" + +RETURN = """ + +""" +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +import ansible.module_utils.netapp as netapp_utils + + +HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() + + +class NetAppOntapUser(object): + """ + Common operations to manage users and roles. + """ + + def __init__(self): + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=['present', 'absent'], default='present'), + name=dict(required=True, type='str'), + + application=dict(required=True, type='str', choices=[ + 'console', 'http', 'ontapi', 'rsh', + 'snmp', 'sp', 'ssh', 'telnet']), + authentication_method=dict(required=True, type='str', + choices=['community', 'password', + 'publickey', 'domain', + 'nsswitch', 'usm']), + set_password=dict(required=False, type='str'), + role_name=dict(required=False, type='str'), + lock_user=dict(required=False, type='bool'), + vserver=dict(required=True, type='str'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[ + ('state', 'present', ['role_name']) + ], + supports_check_mode=True + ) + + parameters = self.module.params + + # set up state variables + self.state = parameters['state'] + self.name = parameters['name'] + self.application = parameters['application'] + self.authentication_method = parameters['authentication_method'] + self.set_password = parameters['set_password'] + self.role_name = parameters['role_name'] + self.lock_user = parameters['lock_user'] + self.vserver = parameters['vserver'] + + if HAS_NETAPP_LIB is False: + self.module.fail_json(msg="the python NetApp-Lib module is required") + else: + self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver) + + def get_user(self): + """ + Checks if the user exists. + + :return: + True if user found + False if user is not found + :rtype: bool + """ + + security_login_get_iter = netapp_utils.zapi.NaElement('security-login-get-iter') + query_details = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-account-info', **{'vserver': self.vserver, + 'user-name': self.name, + 'application': self.application, + 'authentication-method': + self.authentication_method}) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(query_details) + security_login_get_iter.add_child_elem(query) + return_value = None + try: + result = self.server.invoke_successfully(security_login_get_iter, + enable_tunneling=False) + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) >= 1: + interface_attributes = result.get_child_by_name('attributes-list').\ + get_child_by_name('security-login-account-info') + return_value = { + 'is_locked': interface_attributes.get_child_content('is-locked') + } + return return_value + except netapp_utils.zapi.NaApiError as error: + # Error 16034 denotes a user not being found. + if to_native(error.code) == "16034": + return False + else: + self.module.fail_json(msg='Error getting user %s: %s' % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def get_user_lock_info(self): + """ + gets details of the user. + """ + security_login_get_iter = netapp_utils.zapi.NaElement('security-login-get-iter') + query_details = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-account-info', **{'vserver': self.vserver, + 'user-name': self.name, + 'application': self.application, + # 'role-name': self.role_name, + 'authentication-method': + self.authentication_method}) + + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(query_details) + security_login_get_iter.add_child_elem(query) + + result = self.server.invoke_successfully(security_login_get_iter, True) + return_value = None + + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) == 1: + + interface_attributes = result.get_child_by_name('attributes-list').\ + get_child_by_name('security-login-account-info') + return_value = { + 'is_locked': interface_attributes.get_child_content('is-locked') + } + return return_value + + def create_user(self): + user_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-create', **{'vserver': self.vserver, + 'user-name': self.name, + 'application': self.application, + 'authentication-method': + self.authentication_method, + 'role-name': self.role_name}) + if self.set_password is not None: + user_create.add_new_child('password', self.set_password) + + try: + self.server.invoke_successfully(user_create, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error creating user %s: %s' % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def lock_given_user(self): + """ + locks the user + + :return: + True if user locked + False if lock user is not performed + :rtype: bool + """ + user_lock = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-lock', **{'vserver': self.vserver, + 'user-name': self.name}) + + try: + self.server.invoke_successfully(user_lock, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error locking user %s: %s' % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def unlock_given_user(self): + """ + unlocks the user + + :return: + True if user unlocked + False if unlock user is not performed + :rtype: bool + """ + user_unlock = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-unlock', **{'vserver': self.vserver, + 'user-name': self.name}) + + try: + self.server.invoke_successfully(user_unlock, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + if to_native(error.code) == '13114': + return False + else: + self.module.fail_json(msg='Error unlocking user %s: %s' % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def delete_user(self): + user_delete = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-delete', **{'vserver': self.vserver, + 'user-name': self.name, + 'application': self.application, + 'authentication-method': + self.authentication_method}) + + try: + self.server.invoke_successfully(user_delete, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error removing user %s: %s' % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def change_password(self): + """ + Changes the password + + :return: + True if password updated + False if password is not updated + :rtype: bool + """ + self.server.set_vserver(self.vserver) + modify_password = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-modify-password', **{ + 'new-password': str(self.set_password), + 'user-name': self.name}) + try: + self.server.invoke_successfully(modify_password, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + if to_native(error.code) == '13114': + return False + else: + self.module.fail_json(msg='Error setting password for user %s: %s' % (self.name, to_native(error)), + exception=traceback.format_exc()) + + self.server.set_vserver(None) + return True + + def apply(self): + property_changed = False + password_changed = False + lock_user_changed = False + netapp_utils.ems_log_event("na_ontap_user", self.server) + user_exists = self.get_user() + + if user_exists: + if self.state == 'absent': + property_changed = True + elif self.state == 'present': + if self.set_password is not None: + password_changed = True + if self.lock_user is not None: + if self.lock_user is True and user_exists['is_locked'] != 'true': + lock_user_changed = True + elif self.lock_user is False and user_exists['is_locked'] != 'false': + lock_user_changed = True + else: + if self.state == 'present': + # Check if anything needs to be updated + property_changed = True + + changed = property_changed or password_changed or lock_user_changed + + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if not user_exists: + self.create_user() + else: + if password_changed: + self.change_password() + if lock_user_changed: + if self.lock_user: + self.lock_given_user() + else: + self.unlock_given_user() + elif self.state == 'absent': + self.delete_user() + self.module.exit_json(changed=changed) + + +def main(): + obj = NetAppOntapUser() + obj.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_user_role.py b/lib/ansible/modules/storage/netapp/na_ontap_user_role.py new file mode 100644 index 00000000000..4742e8445ef --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_user_role.py @@ -0,0 +1,210 @@ +#!/usr/bin/python + +# (c) 2018, NetApp, Inc +# 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: na_ontap_user_role + +short_description: useradmin configuration and management +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: Sumit Kumar (sumit4@netapp.com) + +description: +- Create or destroy user roles + +options: + + state: + description: + - Whether the specified user should exist or not. + choices: ['present', 'absent'] + default: present + + name: + description: + - The name of the role to manage. + required: true + + command_directory_name: + description: + - The command or command directory to which the role has an access. + required: true + + access_level: + description: + - The name of the role to manage. + choices: ['none', 'readonly', 'all'] + default: all + + vserver: + description: + - The name of the vserver to use. + required: true + +''' + +EXAMPLES = """ + + - name: Create User Role + na_ontap_user_role: + state: present + name: ansibleRole + command_directory_name: DEFAULT + access_level: none + vserver: ansibleVServer + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +""" + +RETURN = """ + +""" +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +import ansible.module_utils.netapp as netapp_utils + + +HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() + + +class NetAppOntapUserRole(object): + + def __init__(self): + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=['present', 'absent'], default='present'), + name=dict(required=True, type='str'), + command_directory_name=dict(required=True, type='str'), + access_level=dict(required=False, type='str', default='all', + choices=['none', 'readonly', 'all']), + vserver=dict(required=True, type='str'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + parameters = self.module.params + # set up state variables + self.state = parameters['state'] + self.name = parameters['name'] + self.command_directory_name = parameters['command_directory_name'] + self.access_level = parameters['access_level'] + self.vserver = parameters['vserver'] + + if HAS_NETAPP_LIB is False: + self.module.fail_json(msg="the python NetApp-Lib module is required") + else: + self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver) + + def get_role(self): + """ + Checks if the role exists for specific command-directory-name. + + :return: + True if role found + False if role is not found + :rtype: bool + """ + + security_login_role_get_iter = netapp_utils.zapi.NaElement( + 'security-login-role-get-iter') + query_details = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-role-info', **{'vserver': self.vserver, + 'role-name': self.name, + 'command-directory-name': + self.command_directory_name}) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(query_details) + security_login_role_get_iter.add_child_elem(query) + + try: + result = self.server.invoke_successfully( + security_login_role_get_iter, enable_tunneling=False) + except netapp_utils.zapi.NaApiError as e: + # Error 16031 denotes a role not being found. + if to_native(e.code) == "16031": + return False + else: + self.module.fail_json(msg='Error getting role %s: %s' % (self.name, to_native(e)), + exception=traceback.format_exc()) + if (result.get_child_by_name('num-records') and + int(result.get_child_content('num-records')) >= 1): + return True + return False + + def create_role(self): + role_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-role-create', **{'vserver': self.vserver, + 'role-name': self.name, + 'command-directory-name': + self.command_directory_name, + 'access-level': + self.access_level}) + try: + self.server.invoke_successfully(role_create, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error creating role %s: %s' % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def delete_role(self): + role_delete = netapp_utils.zapi.NaElement.create_node_with_children( + 'security-login-role-delete', **{'vserver': self.vserver, + 'role-name': self.name, + 'command-directory-name': + self.command_directory_name}) + + try: + self.server.invoke_successfully(role_delete, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error removing role %s: %s' % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + changed = False + netapp_utils.ems_log_event("na_ontap_user_role", self.server) + role_exists = self.get_role() + if role_exists: + if self.state == 'absent': + changed = True + else: + if self.state == 'present': + changed = True + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if not role_exists: + self.create_role() + elif self.state == 'absent': + self.delete_role() + self.module.exit_json(changed=changed) + + +def main(): + obj = NetAppOntapUserRole() + obj.apply() + + +if __name__ == '__main__': + main()