1#!/usr/bin/python
2#
3# Copyright: Ansible Project
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
7__metaclass__ = type
8
9
10ANSIBLE_METADATA = {'metadata_version': '1.1',
11                    'status': ['preview'],
12                    'supported_by': 'certified'}
13
14DOCUMENTATION = """
15---
16module: sros_command
17version_added: "2.2"
18author: "Peter Sprygada (@privateip)"
19short_description: Run commands on remote devices running Nokia SR OS
20description:
21  - Sends arbitrary commands to an SR OS node and returns the results
22    read from the device. This module includes an argument that will
23    cause the module to wait for a specific condition before returning
24    or timing out if the condition is not met.
25  - This module does not support running commands in configuration mode.
26    Please use M(sros_config) to configure SR OS devices.
27extends_documentation_fragment: sros
28options:
29  commands:
30    description:
31      - List of commands to send to the remote SR OS device over the
32        configured provider. The resulting output from the command
33        is returned. If the I(wait_for) argument is provided, the
34        module is not returned until the condition is satisfied or
35        the number of retries has expired.
36    required: true
37  wait_for:
38    description:
39      - List of conditions to evaluate against the output of the
40        command. The task will wait for each condition to be true
41        before moving forward. If the conditional is not true
42        within the configured number of retries, the task fails.
43        See examples.
44    aliases: ['waitfor']
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 = """
72# Note: examples below use the following provider dict to handle
73#       transport and authentication to the node.
74---
75vars:
76  cli:
77    host: "{{ inventory_hostname }}"
78    username: admin
79    password: admin
80    transport: cli
81
82---
83tasks:
84  - name: run show version on remote devices
85    sros_command:
86      commands: show version
87      provider: "{{ cli }}"
88
89  - name: run show version and check to see if output contains sros
90    sros_command:
91      commands: show version
92      wait_for: result[0] contains sros
93      provider: "{{ cli }}"
94
95  - name: run multiple commands on remote nodes
96    sros_command:
97      commands:
98        - show version
99        - show port detail
100      provider: "{{ cli }}"
101
102  - name: run multiple commands and evaluate the output
103    sros_command:
104      commands:
105        - show version
106        - show port detail
107      wait_for:
108        - result[0] contains TiMOS-B-14.0.R4
109      provider: "{{ cli }}"
110"""
111
112RETURN = """
113stdout:
114  description: The set of responses from the commands
115  returned: always apart from low level errors (such as action plugin)
116  type: list
117  sample: ['...', '...']
118
119stdout_lines:
120  description: The value of stdout split into a list
121  returned: always apart from low level errors (such as action plugin)
122  type: list
123  sample: [['...', '...'], ['...'], ['...']]
124
125failed_conditions:
126  description: The list of conditionals that have failed
127  returned: failed
128  type: list
129  sample: ['...', '...']
130"""
131import time
132
133from ansible.module_utils.basic import AnsibleModule
134from ansible.module_utils.network.common.parsing import Conditional
135from ansible.module_utils.network.common.utils import ComplexList
136from ansible.module_utils.six import string_types
137from ansible.module_utils.network.sros.sros import run_commands, sros_argument_spec, check_args
138
139
140def to_lines(stdout):
141    for item in stdout:
142        if isinstance(item, string_types):
143            item = str(item).split('\n')
144        yield item
145
146
147def parse_commands(module, warnings):
148    command = ComplexList(dict(
149        command=dict(key=True),
150        prompt=dict(),
151        answer=dict()
152    ), module)
153    commands = command(module.params['commands'])
154    for index, item in enumerate(commands):
155        if module.check_mode and not item['command'].startswith('show'):
156            warnings.append(
157                'only show commands are supported when using check mode, not '
158                'executing `%s`' % item['command']
159            )
160        elif item['command'].startswith('conf'):
161            module.fail_json(
162                msg='sros_command does not support running config mode '
163                    'commands.  Please use sros_config instead'
164            )
165    return commands
166
167
168def main():
169    """main entry point for module execution
170    """
171    argument_spec = dict(
172        commands=dict(type='list', required=True),
173
174        wait_for=dict(type='list', aliases=['waitfor']),
175        match=dict(default='all', choices=['all', 'any']),
176
177        retries=dict(default=10, type='int'),
178        interval=dict(default=1, type='int')
179    )
180
181    argument_spec.update(sros_argument_spec)
182
183    module = AnsibleModule(argument_spec=argument_spec,
184                           supports_check_mode=True)
185
186    result = {'changed': False}
187
188    warnings = list()
189    check_args(module, warnings)
190    commands = parse_commands(module, warnings)
191    result['warnings'] = warnings
192
193    wait_for = module.params['wait_for'] or list()
194    conditionals = [Conditional(c) for c in wait_for]
195
196    retries = module.params['retries']
197    interval = module.params['interval']
198    match = module.params['match']
199
200    while retries > 0:
201        responses = run_commands(module, commands)
202
203        for item in list(conditionals):
204            if item(responses):
205                if match == 'any':
206                    conditionals = list()
207                    break
208                conditionals.remove(item)
209
210        if not conditionals:
211            break
212
213        time.sleep(interval)
214        retries -= 1
215
216    if conditionals:
217        failed_conditions = [item.raw for item in conditionals]
218        msg = 'One or more conditional statements have not been satisfied'
219        module.fail_json(msg=msg, failed_conditions=failed_conditions)
220
221    result = {
222        'changed': False,
223        'stdout': responses,
224        'stdout_lines': list(to_lines(responses))
225    }
226
227    module.exit_json(**result)
228
229
230if __name__ == '__main__':
231    main()
232