├── README.md ├── examples ├── firmwaredownload.yml ├── removeZones.yml ├── createZones.yml └── initconfig.yml └── library └── brocade_fos_command.py /README.md: -------------------------------------------------------------------------------- 1 | # FOS Command Module for Ansible 2 | 3 | This modules provides a mechanism for executing FOS commands via an Ansible task. 4 | 5 | For details, see the DOCUMENTATION section of the module. 6 | 7 | -------------------------------------------------------------------------------- /examples/firmwaredownload.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: switches 3 | gather_facts: False 4 | 5 | tasks: 6 | 7 | - name: Update firmware 8 | brocade_fos_command: 9 | switch_login: admin 10 | switch_password: password 11 | switch_address: 10.1.2.3 12 | command_set: 13 | # 14 | - command: firmwaredownload -p ftp 10.2.3.4,anonymous,v8.2.1c,anyone@anywhere 15 | prompts: 16 | - question: Do you want to continue 17 | response: "Y" 18 | exit_tests: 19 | - Failed 20 | - failed 21 | - Rebooting 22 | - rebooting 23 | timeout: 60 24 | 25 | register: configout 26 | 27 | - name: print ansible_facts gathered 28 | debug: 29 | var: configout 30 | -------------------------------------------------------------------------------- /examples/removeZones.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: switches 3 | gather_facts: False 4 | 5 | tasks: 6 | 7 | - name: run fos commands 8 | brocade_fos_command: 9 | switch_login: admin 10 | switch_password: password 11 | switch_address: 10.1.2.3 12 | command_set: 13 | 14 | 15 | - command: alidelete "SampleAlias1" 16 | 17 | - command: alidelete "SampleAlias2" 18 | 19 | 20 | - command: zonedelete "SampleZone" 21 | 22 | - command: cfgsave 23 | prompts: 24 | - question: Do you want to save 25 | response: "yes" 26 | 27 | - command: zoneshow 28 | 29 | - command: alishow 30 | 31 | register: configout 32 | 33 | - name: print ansible_facts gathered 34 | debug: 35 | var: configout 36 | -------------------------------------------------------------------------------- /examples/createZones.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: switches 3 | gather_facts: False 4 | 5 | tasks: 6 | 7 | - name: run fos commands 8 | brocade_fos_command: 9 | switch_login: admin 10 | switch_password: password 11 | switch_address: 10.1.2.3 12 | command_set: 13 | # 14 | - command: zoneshow 15 | 16 | - command: alishow 17 | 18 | - command: portname 19 | 20 | - command: alicreate "SampleAlias1", "10:23:45:67:76:54:32:10" 21 | 22 | - command: alicreate "SampleAlias2", "10:23:45:67:76:54:32:11" 23 | 24 | - command: zonecreate "SampleZone", "SampleAlias1;SampleAlias2" 25 | 26 | - command: cfgsave 27 | prompts: 28 | - question: Do you want to save 29 | response: "yes" 30 | 31 | 32 | 33 | register: configout 34 | 35 | - name: print ansible_facts gathered 36 | debug: 37 | var: configout 38 | -------------------------------------------------------------------------------- /examples/initconfig.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: switches 3 | gather_facts: False 4 | 5 | tasks: 6 | 7 | - name: run fos commands 8 | brocade_fos_command: 9 | switch_login: admin 10 | switch_password: password 11 | switch_address: 10.1.2.3 12 | command_set: 13 | # 14 | - command: timeout 30 15 | start_state: 16 | - flag: changed 17 | value: True 18 | 19 | - command: defzone --allaccess 20 | prompts: 21 | - question: Do you want to 22 | response: "yes" 23 | 24 | - command: cfgsave 25 | prompts: 26 | - question: Do you want to 27 | response: "yes" 28 | 29 | - command: dnsconfig --add -domain example.com -serverip1 8.8.8.8 -serverip2 8.8.4.4 30 | 31 | - command: tstimezone America/Chicago 32 | 33 | - command: switchdisable 34 | 35 | - command: configure 36 | prompts: 37 | - question: Fabric parameters 38 | response: "yes" 39 | - question: "Domain: (1..239)" 40 | response: "1" 41 | - question: Enable 8 bit Dynamic Area Mode 42 | response: "" 43 | - question: F-Port Device Update Mode 44 | response: "" 45 | - question: Location ID 46 | response: "" 47 | - question: R_A_TOV 48 | response: "" 49 | - question: E_D_TOV 50 | response: "" 51 | - question: WAN_TOV 52 | response: "" 53 | - question: MAX_HOPS 54 | response: "" 55 | - question: Data field size 56 | response: "" 57 | - question: Sequence Level Switching 58 | response: "" 59 | - question: Disable Device Probing 60 | response: "" 61 | - question: Suppress Class F Traffic 62 | response: "" 63 | - question: Per-frame Route Priority 64 | response: "" 65 | - question: Long Distance Fabric 66 | response: "" 67 | - question: BB credit 68 | response: "" 69 | - question: Disable FID Check 70 | response: "" 71 | - question: Insistent Domain ID Mode 72 | response: "yes" 73 | - question: Disable Default PortName 74 | response: "" 75 | - question: Display FDMI Host Name 76 | response: "" 77 | - question: Dynamic Portname 78 | response: "on" 79 | - question: Edge Hold Time 80 | response: "" 81 | - question: Remote Fosexec feature 82 | response: "" 83 | - question: High Integrity Fabric Mode 84 | response: "" 85 | - question: Virtual Channel parameters 86 | response: "" 87 | - question: F-Port login parameters 88 | response: "" 89 | - question: D-Port Parameters 90 | response: "" 91 | - question: RDP Polling Cycle 92 | response: "" 93 | - question: Zoning Operation parameters 94 | response: "" 95 | - question: RSCN Transmission Mode 96 | response: "" 97 | - question: System services 98 | response: "" 99 | - question: Portlog events enable 100 | response: "" 101 | 102 | - command: switchenable 103 | 104 | - command: 'portname -d "C.T.A.R"' 105 | 106 | - command: fabricprincipal --enable -p 0x03 -f 107 | 108 | - command: creditrecovmode --cfg onLrOnly 109 | 110 | - command: dlsset --enable -lossless 111 | 112 | - command: bannerset 113 | prompts: 114 | - question: Please input content of security banner 115 | response: "This is to demo the banner set command.\n." 116 | 117 | - command: ipfilter --clone ipv4_telnet_http -from default_ipv4 118 | - command: ipfilter --delrule ipv4_telnet_http -rule 2 119 | - command: ipfilter --addrule ipv4_telnet_http -rule 2 -sip any -dp 23 -proto tcp -act deny 120 | - command: ipfilter --addrule ipv4_telnet_http -rule 9 -sip any -dp 389 -proto tcp -act permit 121 | - command: ipfilter --addrule ipv4_telnet_http -rule 10 -sip any -dp 389 -proto udp -act permit 122 | - command: ipfilter --activate ipv4_telnet_http  123 | - command: ipfilter --show 124 | 125 | - command: snmpconfig --set systemgroup 126 | prompts: 127 | - question: sysDescr 128 | response: DemoSwitch 129 | - question: sysLocation 130 | response: San Jose 131 | - question: sysContact 132 | response: "" 133 | - question: authTrapEnabled 134 | response: "true" 135 | 136 | - command: auditcfg --class 1,2,3,4,5,8,9 137 | 138 | - command: syslogadmin --set -ip 10.1.2.4 139 | 140 | register: configout 141 | 142 | - name: print ansible_facts gathered 143 | debug: 144 | var: configout 145 | -------------------------------------------------------------------------------- /library/brocade_fos_command.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2020 Chip Copper 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | """ Ansible module to allow CLI commands to be run from inside of playbooks """ 8 | 9 | import sys 10 | import time 11 | import socket 12 | import re 13 | import paramiko 14 | from ansible.module_utils.basic import AnsibleModule 15 | 16 | 17 | ANSIBLE_METADATA = {'metadata_version': '1.1', 18 | 'status': ['preview'], 19 | 'supported_by': 'community'} 20 | 21 | DOCUMENTATION = ''' 22 | --- 23 | module: brocade_fos_command 24 | short_description: This module enables SAN automation through the FOS CLI. 25 | description: 26 | - This modules provides a mechanism for executing FOS commands via an Ansible task. 27 | - Each task will be a separate virtual terminal session. 28 | - One or more commands may be executed in each task. 29 | - Each command begins by sending the CLI command as it would be entered at a system prompt. 30 | - The module then waits for responses. Each response is examined to see if it contains 31 | - the prompt, an exit string, or a dialog question. An exit string is something other than the prompt that 32 | - indicates that the session should be ended. An example of this is when the firmwaredownload 33 | - command is executed. The system does not return to the prompt but instead returns a 34 | - response saying Rebooting. A dialog question is prompting for further user input. A typical 35 | - example is when a command has additional required parameters that cannot be provided as 36 | - CLI flags or when the system is asking for confirmation a la "Are you sure you want to reboot?" 37 | - Returning to the prompt indicated that the command has completed. 38 | - The module includes a configurable timeout value so that if an unexpected response comes from 39 | - the switch, the module will not hang indefinately. 40 | - The module also provides the ability to indicate if the command has changed the state of the 41 | - switch. Since some commands affirm on change and others affirm on no change, it is up to 42 | - the user to indicate when change has and has not occurred. Brocade will be providing 43 | - examples for many commands to indicate which options should be used with which commands. 44 | 45 | version_added: "10.1.20.1" 46 | author: "Chip Copper (chip.copper@broadcom.com)"" 47 | options: 48 | switch_login: 49 | description: 50 | - Account name under which commands should be run. 51 | required: True 52 | switch_password: 53 | description: 54 | - Password for the account. 55 | required: True 56 | switch_address: 57 | description: 58 | - IP address or logical name of the switch to be managed. 59 | required: True 60 | global_timeout: 61 | description: 62 | - Overall expected timeout value for the CLI session in seconds. 63 | required: False 64 | default: 15 65 | login_delay: 66 | description: 67 | - Delay between session establishment and first expected response from the target 68 | required: False 69 | default: 5 70 | command_set: 71 | description: 72 | - List of commands to be executed in this session. 73 | required: True 74 | type: list 75 | suboptions: 76 | command: 77 | description: 78 | - CLI command exactly as it would appear at a system prompt. 79 | - To reduce dialogs, as many flags and parameters should be included as possible. 80 | required: True 81 | prompts: 82 | description: 83 | - List of prompts and responses for the interactive parts of the command. 84 | required: False 85 | type: list 86 | suboptions: 87 | question: 88 | description: 89 | - Prompt string as displayed by the CLI typically captured in a screen scrape. 90 | - This string should be unambigouous and differentiated from other prompts. 91 | required: True 92 | response: 93 | description: 94 | - Answer to the prompt. A default response is indicated by "". 95 | required: True 96 | start_state: 97 | description: 98 | - Assumed values for returned failure and changed state variables. 99 | - These values are returned if no result tests change them. 100 | required: False 101 | type: list 102 | suboptions: 103 | flag: 104 | description: 105 | - State variable to be set 106 | choices: ['failed', 'changed'] 107 | required: True 108 | value: 109 | description: 110 | - State variable default value 111 | type: boolean 112 | required: True 113 | default: 114 | - flag: changed 115 | value: False 116 | - flag: failed 117 | value: False 118 | result_tests: 119 | description: 120 | - List of tests to be run to determine changes in the failed or changed state 121 | required: False 122 | type: list 123 | suboptions: 124 | test: 125 | description: 126 | - Prompt string as displayed by the CLI typically captured in a screen scrape. 127 | - This string should be unambigouous and differentiated from other prompts. 128 | required: True 129 | flag: 130 | description: 131 | - State variable to be set 132 | choices: ['failed', 'changed'] 133 | required: True 134 | value: 135 | description: 136 | - State variable default value 137 | type: boolean 138 | required: True 139 | exit_tests: 140 | description: 141 | - List of strings other than the standard prompt that would indicated command termination. 142 | required: False 143 | timeout: 144 | description: 145 | - Timeout value for this command if it should be different than the global value. 146 | - Depending on the situation, a particular command may require more or less time. 147 | required: False 148 | default: -1 indicating the global value should be used. 149 | ''' 150 | 151 | EXAMPLES = ''' 152 | - name: run fos commands 153 | brocade_fos_command: 154 | switch_login: {{ username}} 155 | switch_password: {{ password }} 156 | switch_address: {{ switch_ip_address }} 157 | command_set: 158 | # 159 | - command: timeout 30 160 | start_state: 161 | - flag: changed 162 | value: True 163 | 164 | - command: defzone --allaccess 165 | prompts: 166 | - question: Do you want to 167 | response: "yes" 168 | 169 | - command: cfgsave 170 | prompts: 171 | - question: Do you want to 172 | response: "yes" 173 | 174 | - command: dnsconfig --add -domain mydomain.com -serverip1 8.8.8.8 -serverip2 8.8.4.4 175 | 176 | - command: tstimezone America/Chicago 177 | 178 | - command: switchdisable 179 | 180 | - command: switchenable 181 | 182 | - command: 'portname -d "C.T.A.R"' 183 | 184 | - command: fabricprincipal --enable -p 0x03 -f 185 | 186 | - command: creditrecovmode --cfg onLrOnly 187 | 188 | - command: dlsset --enable -lossless 189 | 190 | - command: bannerset 191 | prompts: 192 | - question: Please input content of security banner 193 | response: "This is to demo the banner set command.\n." 194 | 195 | - command: ipfilter --clone ipv4_telnet_http -from default_ipv4 196 | - command: ipfilter --delrule ipv4_telnet_http -rule 2 197 | - command: ipfilter --addrule ipv4_telnet_http -rule 2 -sip any -dp 23 -proto tcp -act deny 198 | - command: ipfilter --activate ipv4_telnet_http  199 | - command: ipfilter --show 200 | 201 | - command: snmpconfig --set systemgroup 202 | prompts: 203 | - question: sysDescr 204 | response: DemoSwitch 205 | - question: sysLocation 206 | response: San Jose 207 | - question: sysContact 208 | response: "" 209 | - question: authTrapEnabled 210 | response: "true" 211 | 212 | - command: auditcfg --class 1,2,3,4,5,8,9 213 | 214 | - command: syslogadmin --set -ip 10.155.2.151 215 | 216 | ''' 217 | 218 | RETURN = ''' 219 | messages: 220 | description: Log of the terminal session. 221 | returned: always 222 | type: list 223 | sample: 224 | - "SW170_X6-4:FID128:admin> timeout 30", 225 | - "IDLE Timeout Changed to 30 minutes", 226 | - "The modified IDLE Timeout will be in effect after NEXT login", 227 | - "SW170_X6-4:FID128:admin> defzone --allaccess", 228 | - "You are about to set the Default Zone access mode to All Access", 229 | - "Do you want to set the Default Zone access mode to All Access ? (yes, y, no, n): [no] yes", 230 | - "defzone setting is same and nothing to update.", 231 | - "", 232 | - "SW170_X6-4:FID128:admin> 233 | 234 | ''' 235 | 236 | 237 | 238 | def open_shell(module, ip_address, username, password, hostkeymust, messages, globaltimeout): 239 | changed = False 240 | failed = False 241 | messages.append("") 242 | messages.append("SSH into " + ip_address) 243 | ssh = paramiko.SSHClient() 244 | ssh.load_system_host_keys() 245 | if not hostkeymust: 246 | ssh.set_missing_host_key_policy(paramiko.client.WarningPolicy()) 247 | try: 248 | ssh.connect(ip_address, username=username, password=password, timeout=globaltimeout) 249 | except paramiko.ssh_exception.AuthenticationException as exception: 250 | messages.append("invalid name/password") 251 | messages.append("Invalid session credentials: " + str(exception)) 252 | failed = True 253 | module.fail_json(msg="Invalid login credentials.", messages=messages) 254 | #return ssh, shell, changed, failed 255 | except BaseException as exception: 256 | messages.append("Login error: " + str(exception)) 257 | failed = True 258 | module.fail_json(msg="Login error.", messages=messages) 259 | #return ssh, shell, changed, failed 260 | 261 | shell = ssh.invoke_shell() 262 | shell.settimeout(globaltimeout) 263 | 264 | return ssh, shell, changed, failed 265 | 266 | 267 | def close_session(ssh_session): 268 | ssh_session.close() 269 | return 270 | 271 | def send_characters(module, messages, shell, the_characters): 272 | 273 | try: 274 | shell.send(the_characters) 275 | except BaseException as exception: 276 | messages.append("Sending error. Characters: " + the_characters + "Exception: " + str(exception)) 277 | failed = True 278 | module.fail_json(msg="Sending characters error: ", messages=messages, failed=failed) 279 | return 280 | 281 | 282 | def get_prompt(module, messages, shell, login_delay): 283 | 284 | # Send a newline, wait for prompt, and flush everything up to this point (assuming motd, etc.) 285 | send_characters(module, messages, shell, "\n") 286 | time.sleep(login_delay) 287 | try: 288 | response = shell.recv(9999) 289 | except socket.timeout as exception: 290 | messages.append("Prompt timeout - Step 1: " + str(exception)) 291 | failed = True 292 | module.fail_json(msg="Prompt timeout - Step 1.", failed=failed) 293 | 294 | # Send another newline to get a fresh prompt 295 | send_characters(module, messages, shell, "\n") 296 | 297 | # This will be the \n from the send. 298 | try: 299 | response = shell.recv(1) 300 | except socket.timeout as exception: 301 | messages.append("Prompt timeout - Step 2: " + str(exception)) 302 | failed = True 303 | module.fail_json(msg="Prompt timeout - Step 2. ", failed=failed) 304 | 305 | # This will be the \n from the prompt to begin on a new line. 306 | try: 307 | response = shell.recv(1) 308 | except socket.timeout as exception: 309 | messages.append("Prompt timeout - Step 3: " + str(exception)) 310 | failed = True 311 | module.fail_json(msg="Prompt timeout - Step 3. ") 312 | 313 | # This should be the prompt 314 | try: 315 | response = shell.recv(9999).decode() 316 | except socket.timeout as exception: 317 | messages.append("Prompt timeout - Step 4: " + str(exception)) 318 | failed = True 319 | module.fail_json(msg="Prompt timeout - Step 4. ") 320 | return str(response) 321 | 322 | def receive_until_match(module, messages, shell, match_array, exit_array, prompt_change): 323 | response_buffer = "" 324 | index = -1 325 | 326 | found = False 327 | closed = False 328 | exited = False 329 | 330 | while not found and not closed and not exited: 331 | try: 332 | temp_buffer = shell.recv(9999).decode() 333 | except socket.timeout as exception: 334 | messages.append("Receive error. Buffer: " + response_buffer + "Exception: " + str(exception)) 335 | 336 | failed = True 337 | messages.append(response_buffer.split("\r\n")) 338 | module.fail_json(msg="Receive timeout.", messages=messages, failed=failed) 339 | response_buffer += temp_buffer 340 | for i in range(len(match_array)): 341 | if match_array[i] in response_buffer: 342 | index = i 343 | found = True 344 | break 345 | if len(temp_buffer) == 0: 346 | closed = True 347 | for i in range(len(exit_array)): 348 | if exit_array[i] in response_buffer: 349 | exited = True 350 | if prompt_change: 351 | prompt_match = re.search("\n[a-zA-Z0-9_.-]*:?[a-zA-Z_0-9]*:[a-zA-Z_0-9_.-]*>", \ 352 | response_buffer) 353 | if prompt_match is not None: 354 | new_prompt = prompt_match.group()[1:] 355 | exited = True 356 | else: 357 | new_prompt = None 358 | 359 | return index, response_buffer, exited, new_prompt 360 | 361 | def cleanup_response(response_buffer): 362 | response_lines = response_buffer.split("\r\n") 363 | return response_lines 364 | 365 | 366 | def main(argv): 367 | 368 | prompt_options = dict( 369 | question=dict(type='str', required=True), 370 | response=dict(type='str', required=True), 371 | ) 372 | 373 | result_test_options = dict( 374 | test=dict(type='str', required=True), 375 | flag=dict(type='str', required=True, choices=['failed', 'changed']), 376 | value=dict(type='bool', required=True), 377 | 378 | ) 379 | 380 | start_state_options = dict( 381 | flag=dict(type='str', required=True, choices=['failed', 'changed']), 382 | value=dict(type='bool', required=True), 383 | ) 384 | 385 | command_set_options = dict( 386 | command=dict(type='str', required=True), 387 | prompts=dict(type='list', elements='dict', options=prompt_options, default=[]), 388 | start_state=dict(type='list', elements='dict', options=start_state_options, 389 | default=[{"flag": "changed", "value": False}, 390 | {"flag": "failed", "value": False}]), 391 | result_tests=dict(type='list', elements='dict', options=result_test_options, default=[]), 392 | exit_tests=dict(type='list', elements='str', default=[]), 393 | timeout=dict(type='int', default=-1), 394 | ) 395 | 396 | 397 | argument_spec = dict( 398 | switch_login=dict(type='str'), 399 | switch_password=dict(type='str'), 400 | switch_address=dict(type='str'), 401 | global_timeout=dict(type='int', default=15), 402 | command_set=dict(type='list', elements='dict', options=command_set_options), 403 | hostkeymust=dict(type='bool', default=False), 404 | login_delay=dict(type='int', default=5), 405 | ) 406 | #global messages 407 | 408 | module = AnsibleModule(argument_spec=argument_spec, 409 | supports_check_mode=True) 410 | warnings = list() 411 | messages = list() 412 | 413 | changed = False 414 | failed = False 415 | 416 | prompt_change_commands = [] 417 | prompt_change_commands.append("setcontext") 418 | 419 | # Wrangle out the variables 420 | switch_login = module.params['switch_login'] 421 | switch_password = module.params['switch_password'] 422 | switch_address = module.params['switch_address'] 423 | command_set = module.params['command_set'] 424 | hostkeymust = module.params['hostkeymust'] 425 | global_timeout = module.params['global_timeout'] 426 | login_delay = module.params['login_delay'] 427 | 428 | result = {} 429 | 430 | # Establish session with switch 431 | ssh, shell, changed, failed = open_shell(module, switch_address, switch_login, switch_password, 432 | hostkeymust, messages, global_timeout) 433 | 434 | # Discover prompt string 435 | switch_prompt = get_prompt(module, messages, shell, login_delay) 436 | collected_responses = switch_prompt 437 | 438 | 439 | command_state = {'changed': False, 'failed': False} 440 | 441 | # For each command 442 | for command_index in range(len(command_set)): 443 | # Build the expected responses for each question or prompt 444 | questions = [] 445 | 446 | # Set the individual command starting state 447 | 448 | for i in range(len(command_set[command_index]['start_state'])): 449 | command_state[command_set[command_index]['start_state'][i]['flag']] = \ 450 | command_set[command_index]['start_state'][i]['value'] 451 | 452 | if len(command_set[command_index]['prompts']) > 0: 453 | for prompt_index in range(len(command_set[command_index]['prompts'])): 454 | questions.append(command_set[command_index]['prompts'][prompt_index]['question']) 455 | 456 | # Build the list of possible exit strings in addition to the prompt 457 | exit_array = list(command_set[command_index]['exit_tests']) 458 | exit_array.append(switch_prompt) 459 | 460 | # Start the accumulated dialog with the command 461 | command_results = "" 462 | 463 | # Set the command specific timeout if one is indicated 464 | if command_set[command_index]['timeout'] == -1: 465 | shell.settimeout(global_timeout) 466 | else: 467 | shell.settimeout(command_set[command_index]['timeout']) 468 | 469 | # If the command is in the prompt change list, set the flag. Otherwise clear the flag 470 | prompt_change = False 471 | for i in range(len(prompt_change_commands)): 472 | if prompt_change_commands[i] in command_set[command_index]['command']: 473 | prompt_change = True 474 | 475 | # Send the inital command line 476 | send_characters(module, messages, shell, command_set[command_index]['command'] + "\n") 477 | 478 | # This loop will repeat until either the prompt or another exit string is found 479 | back_to_prompt = False 480 | while not back_to_prompt: 481 | prompt_index, response_buffer, exited, new_prompt = \ 482 | receive_until_match(module, messages, shell, questions, exit_array, prompt_change) 483 | command_results += response_buffer 484 | if exited: 485 | back_to_prompt = True 486 | if prompt_change: 487 | switch_prompt = new_prompt 488 | else: 489 | send_characters(module, messages, shell, 490 | command_set[command_index]['prompts'][prompt_index]['response'] + "\n") 491 | 492 | 493 | for check_index in range(len(command_set[command_index]['result_tests'])): 494 | if command_set[command_index]['result_tests'][check_index]['test'] in command_results: 495 | command_state[command_set[command_index]['result_tests'][check_index]['flag']] = \ 496 | command_set[command_index]['result_tests'][check_index]['value'] 497 | 498 | if command_state['changed'] is True: 499 | changed = True 500 | if command_state['failed'] is True: 501 | failed = True 502 | collected_responses += command_results 503 | 504 | # Look at final fail and changed state and update accordingly 505 | 506 | # End session and return 507 | #messages.append(cleanup_response(collected_responses)) 508 | messages = cleanup_response(collected_responses) 509 | 510 | 511 | close_session(ssh) 512 | 513 | 514 | #result['stdout'] = show_stdout 515 | #result['stderr'] = show_stderr 516 | result['changed'] = changed 517 | result['failed'] = failed 518 | result['messages'] = messages 519 | result['warnings'] = warnings 520 | #result['switch_prompt'] = switch_prompt 521 | 522 | # Debugging returns 523 | #result['command_set'] = command_set 524 | module.exit_json(**result) 525 | 526 | if __name__ == "__main__": 527 | main(sys.argv[1:]) 528 | --------------------------------------------------------------------------------