Repository URL to install this package:
|
Version:
6.0.0 ▾
|
#!/usr/bin/python
'''
# (c) 2020, 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': 'certified'}
DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Run cli commands on ONTAP over SSH using paramiko.
- Output is returned in C(stdout) and C(stderr), and also as C(stdout_lines), C(stdout_lines_filtered), C(stderr_lines).
- Note that the module can succeed even though the command failed. You need to analyze stdout and check the results.
- If the SSH host key is unknown and accepted, C(warnings) is updated.
- Options related to ZAPI or REST APIs are ignored.
extends_documentation_fragment:
- netapp.ontap.netapp.na_ontap
module: na_ontap_ssh_command
short_description: NetApp ONTAP Run any cli command over plain SSH using paramiko.
version_added: 20.8.0
options:
command:
description:
- a string containing the command and arguments.
required: true
type: str
privilege:
description:
- privilege level at which to run the command, eg admin, advanced.
- if set, the command is prefixed with C(set -privilege <privilege>;).
type: str
accept_unknown_host_keys:
description:
- When false, reject the connection if the host key is not in known_hosts file.
- When true, if the host key is unknown, accept it, but report a warning.
- Note that the key is not added to the file. You could add the key by manually using SSH.
type: bool
default: false
include_lines:
description:
- return only lines containing string pattern in C(stdout_lines_filtered)
default: ''
type: str
exclude_lines:
description:
- return only lines containing string pattern in C(stdout_lines_filtered)
default: ''
type: str
service_processor:
description:
- whether the target system is ONTAP or the service processor (SP)
- only menaningful when privilege is set
aliases: [sp]
default: false
type: bool
'''
EXAMPLES = """
- name: run ontap cli command using SSH
na_ontap_ssh_command:
hostname: "{{ hostname }}"
username: "{{ admin_username }}"
password: "{{ admin_password }}"
command: version
# Same as above, with parameters
- name: run ontap cli command
na_ontap_ssh_command:
hostname: "{{ hostname }}"
username: "{{ admin_username }}"
password: "{{ admin_password }}"
command: node show -fields node,health,uptime,model
privilege: admin
# Same as above, but with lines filtering
- name: run ontap cli command
na_ontap_ssh_command:
hostname: "{{ hostname }}"
username: "{{ admin_username }}"
password: "{{ admin_password }}"
command: node show -fields node,health,uptime,model
exclude_lines: 'ode ' # Exclude lines with 'Node ' or 'node'
# use with caution!
accept_unknown_host_keys: true
privilege: admin
- name: run ontap SSH command on SP
na_ontap_ssh_command:
# <<: *sp_login
command: sp switch-version
privilege: diag
sp: true
register: result
- debug: var=result
"""
RETURN = """
stdout_lines_filtered:
description:
- In addition to stdout and stdout_lines, a list of non-white lines, excluding last and failed login information.
- The list can be further refined using the include_lines and exclude_lines filters.
returned: always
type: list
"""
import traceback
import warnings
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
try:
import paramiko
HAS_PARAMIKO = True
except ImportError:
HAS_PARAMIKO = False
class NetAppONTAPSSHCommand(object):
''' calls a CLI command using SSH'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
command=dict(required=True, type='str'),
privilege=dict(required=False, type='str'),
accept_unknown_host_keys=dict(required=False, type='bool', default=False),
include_lines=dict(required=False, type='str', default=''),
exclude_lines=dict(required=False, type='str', default=''),
service_processor=dict(required=False, type='bool', default=False, aliases=['sp']),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
parameters = self.module.params
# set up state variables
self.command = parameters['command']
self.privilege = parameters['privilege']
self.include_lines = parameters['include_lines']
self.exclude_lines = parameters['exclude_lines']
self.accept_unknown_host_keys = parameters['accept_unknown_host_keys']
self.service_processor = parameters['service_processor']
self.warnings = list()
self.failed = False
if not HAS_PARAMIKO:
self.module.fail_json(msg="the python paramiko module is required")
client = paramiko.SSHClient()
client.load_system_host_keys() # load ~/.ssh/known_hosts if it exists
if self.accept_unknown_host_keys:
# accept unknown key, but raise a python warning
client.set_missing_host_key_policy(paramiko.WarningPolicy())
with warnings.catch_warnings(record=True) as wngs:
try:
client.connect(hostname=parameters['hostname'], username=parameters['username'], password=parameters['password'])
if len(wngs) > 0:
self.warnings.extend([str(warning.message) for warning in wngs])
except paramiko.SSHException as exc:
self.module.fail_json(msg="SSH connection failed: %s" % repr(exc))
self.client = client
def parse_output(self, out):
out_string = out.read()
# ONTAP makes copious use of \r
out_string = out_string.replace(b'\r\r\n', b'\n')
out_string = out_string.replace(b'\r\n', b'\n')
return(out_string)
def run_ssh_command(self, command):
''' calls SSH '''
try:
stdin, stdout, stderr = self.client.exec_command(command)
except paramiko.SSHException as exc:
self.module.fail_json(msg='Error running command %s: %s' %
(command, to_native(exc)),
exception=traceback.format_exc())
stdin.close() # if we don't close, we may see a TypeError
return stdout, stderr
def filter_output(self, output):
''' Generate stdout_lines_filtered list
Remove login information if found in the first non white lines
'''
result = list()
find_banner = True
for line in output.splitlines():
try:
stripped_line = line.strip().decode()
except Exception as exc:
self.warnings.append("Unable to decode ONTAP output. Skipping filtering. Error: %s" % repr(exc))
result.append('ERROR: truncated, cannot decode: %s' % line)
self.failed = False
return result
if not stripped_line:
continue
if find_banner and stripped_line.startswith(('Last login time:', 'Unsuccessful login attempts since last login:')):
continue
find_banner = False
if self.exclude_lines:
if self.include_lines in stripped_line and self.exclude_lines not in stripped_line:
result.append(stripped_line)
elif self.include_lines:
if self.include_lines in stripped_line:
result.append(stripped_line)
else:
result.append(stripped_line)
return result
def run_command(self):
''' calls SSH '''
# self.ems()
command = self.command
if self.privilege is not None:
if self.service_processor:
command = "priv set %s;%s" % (self.privilege, command)
else:
command = "set -privilege %s;%s" % (self.privilege, command)
stdout, stderr = self.run_ssh_command(command)
stdout_string = self.parse_output(stdout)
stdout_filtered = self.filter_output(stdout_string)
return stdout_string, stdout_filtered, self.parse_output(stderr)
def apply(self):
''' calls the command and returns raw output '''
changed = True
stdout, filtered, stderr = '', '', ''
if not self.module.check_mode:
stdout, filtered, stderr = self.run_command()
if stderr:
self.failed = True
self.module.exit_json(changed=changed, failed=self.failed, stdout=stdout, stdout_lines_filtered=filtered, stderr=stderr, warnings=self.warnings)
def main():
"""
Execute action from playbook
"""
command = NetAppONTAPSSHCommand()
command.apply()
if __name__ == '__main__':
main()