1#!/usr/bin/python 2 3# Copyright: (c) 2018, Extreme Networks Inc. 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6from __future__ import (absolute_import, division, print_function) 7ANSIBLE_METADATA = {'metadata_version': '1.1', 8 'status': ['preview'], 9 'supported_by': 'community'} 10 11 12DOCUMENTATION = """ 13--- 14module: slxos_command 15version_added: "2.6" 16author: "Lindsay Hill (@LindsayHill)" 17short_description: Run commands on remote devices running Extreme Networks SLX-OS 18description: 19 - Sends arbitrary commands to an SLX node and returns the results 20 read from the device. This module includes an 21 argument that will cause the module to wait for a specific condition 22 before returning or timing out if the condition is not met. 23 - This module does not support running commands in configuration mode. 24 Please use M(slxos_config) to configure SLX-OS devices. 25notes: 26 - Tested against SLX-OS 17s.1.02 27 - If a command sent to the device requires answering a prompt, it is possible 28 to pass a dict containing I(command), I(answer) and I(prompt). See examples. 29options: 30 commands: 31 description: 32 - List of commands to send to the remote SLX-OS device over the 33 configured provider. The resulting output from the command 34 is returned. If the I(wait_for) argument is provided, the 35 module is not returned until the condition is satisfied or 36 the number of retries has expired. 37 required: true 38 wait_for: 39 description: 40 - List of conditions to evaluate against the output of the 41 command. The task will wait for each condition to be true 42 before moving forward. If the conditional is not true 43 within the configured number of retries, the task fails. 44 See examples. 45 match: 46 description: 47 - The I(match) argument is used in conjunction with the 48 I(wait_for) argument to specify the match policy. Valid 49 values are C(all) or C(any). If the value is set to C(all) 50 then all conditionals in the wait_for must be satisfied. If 51 the value is set to C(any) then only one of the values must be 52 satisfied. 53 default: all 54 choices: ['any', 'all'] 55 retries: 56 description: 57 - Specifies the number of retries a command should by tried 58 before it is considered failed. The command is run on the 59 target device every retry and evaluated against the 60 I(wait_for) conditions. 61 default: 10 62 interval: 63 description: 64 - Configures the interval in seconds to wait between retries 65 of the command. If the command does not pass the specified 66 conditions, the interval indicates how long to wait before 67 trying the command again. 68 default: 1 69""" 70 71EXAMPLES = """ 72tasks: 73 - name: run show version on remote devices 74 slxos_command: 75 commands: show version 76 77 - name: run show version and check to see if output contains SLX 78 slxos_command: 79 commands: show version 80 wait_for: result[0] contains SLX 81 82 - name: run multiple commands on remote nodes 83 slxos_command: 84 commands: 85 - show version 86 - show interfaces 87 88 - name: run multiple commands and evaluate the output 89 slxos_command: 90 commands: 91 - show version 92 - show interface status 93 wait_for: 94 - result[0] contains SLX 95 - result[1] contains Eth 96 - name: run command that requires answering a prompt 97 slxos_command: 98 commands: 99 - command: 'clear sessions' 100 prompt: 'This operation will logout all the user sessions. Do you want to continue (yes/no)?:' 101 answer: y 102""" 103 104RETURN = """ 105stdout: 106 description: The set of responses from the commands 107 returned: always apart from low level errors (such as action plugin) 108 type: list 109 sample: ['...', '...'] 110stdout_lines: 111 description: The value of stdout split into a list 112 returned: always apart from low level errors (such as action plugin) 113 type: list 114 sample: [['...', '...'], ['...'], ['...']] 115failed_conditions: 116 description: The list of conditionals that have failed 117 returned: failed 118 type: list 119 sample: ['...', '...'] 120""" 121import re 122import time 123 124from ansible.module_utils.network.slxos.slxos import run_commands 125from ansible.module_utils.basic import AnsibleModule 126from ansible.module_utils.network.common.utils import ComplexList 127from ansible.module_utils.network.common.parsing import Conditional 128from ansible.module_utils.six import string_types 129 130 131__metaclass__ = type 132 133 134def to_lines(stdout): 135 for item in stdout: 136 if isinstance(item, string_types): 137 item = str(item).split('\n') 138 yield item 139 140 141def parse_commands(module, warnings): 142 command = ComplexList(dict( 143 command=dict(key=True), 144 prompt=dict(), 145 answer=dict() 146 ), module) 147 commands = command(module.params['commands']) 148 for item in list(commands): 149 configure_type = re.match(r'conf(?:\w*)(?:\s+(\w+))?', item['command']) 150 if module.check_mode: 151 if configure_type and configure_type.group(1) not in ('confirm', 'replace', 'revert', 'network'): 152 module.fail_json( 153 msg='slxos_command does not support running config mode ' 154 'commands. Please use slxos_config instead' 155 ) 156 if not item['command'].startswith('show'): 157 warnings.append( 158 'only show commands are supported when using check mode, not ' 159 'executing `%s`' % item['command'] 160 ) 161 commands.remove(item) 162 return commands 163 164 165def main(): 166 """main entry point for module execution 167 """ 168 argument_spec = dict( 169 commands=dict(type='list', required=True), 170 171 wait_for=dict(type='list'), 172 match=dict(default='all', choices=['all', 'any']), 173 174 retries=dict(default=10, type='int'), 175 interval=dict(default=1, type='int') 176 ) 177 178 module = AnsibleModule(argument_spec=argument_spec, 179 supports_check_mode=True) 180 181 result = {'changed': False} 182 183 warnings = list() 184 commands = parse_commands(module, warnings) 185 result['warnings'] = warnings 186 187 wait_for = module.params['wait_for'] or list() 188 conditionals = [Conditional(c) for c in wait_for] 189 190 retries = module.params['retries'] 191 interval = module.params['interval'] 192 match = module.params['match'] 193 194 while retries > 0: 195 responses = run_commands(module, commands) 196 197 for item in list(conditionals): 198 if item(responses): 199 if match == 'any': 200 conditionals = list() 201 break 202 conditionals.remove(item) 203 204 if not conditionals: 205 break 206 207 time.sleep(interval) 208 retries -= 1 209 210 if conditionals: 211 failed_conditions = [item.raw for item in conditionals] 212 msg = 'One or more conditional statements have not been satisfied' 213 module.fail_json(msg=msg, failed_conditions=failed_conditions) 214 215 result.update({ 216 'changed': False, 217 'stdout': responses, 218 'stdout_lines': list(to_lines(responses)) 219 }) 220 221 module.exit_json(**result) 222 223 224if __name__ == '__main__': 225 main() 226