1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2015, Peter Sprygada <psprygada@ansible.com>
5# Copyright: (c) 2017, Dell Inc.
6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
7
8from __future__ import absolute_import, division, print_function
9__metaclass__ = type
10
11
12ANSIBLE_METADATA = {'metadata_version': '1.1',
13                    'status': ['preview'],
14                    'supported_by': 'community'}
15
16
17DOCUMENTATION = """
18---
19module: dellos10_command
20version_added: "2.2"
21author: "Senthil Kumar Ganesan (@skg-net)"
22short_description: Run commands on remote devices running Dell OS10
23description:
24  - Sends arbitrary commands to a Dell EMC OS10 node and returns the results
25    read from the device. This module includes an
26    argument that will cause the module to wait for a specific condition
27    before returning or timing out if the condition is not met.
28  - This module does not support running commands in configuration mode.
29    Please use M(dellos10_config) to configure Dell EMC OS10 devices.
30extends_documentation_fragment: dellos10
31options:
32  commands:
33    description:
34      - List of commands to send to the remote dellos10 device over the
35        configured provider. The resulting output from the command
36        is returned. If the I(wait_for) argument is provided, the
37        module is not returned until the condition is satisfied or
38        the number of retries has expired.
39    type: list
40    required: true
41  wait_for:
42    description:
43      - List of conditions to evaluate against the output of the
44        command. The task will wait for each condition to be true
45        before moving forward. If the conditional is not true
46        within the configured number of I(retries), the task fails.
47        See examples.
48    type: list
49    version_added: "2.2"
50  match:
51    description:
52      - The I(match) argument is used in conjunction with the
53        I(wait_for) argument to specify the match policy.  Valid
54        values are C(all) or C(any).  If the value is set to C(all)
55        then all conditionals in the wait_for must be satisfied.  If
56        the value is set to C(any) then only one of the values must be
57        satisfied.
58    type: str
59    default: all
60    choices: [ all, any ]
61    version_added: "2.5"
62  retries:
63    description:
64      - Specifies the number of retries a command should be tried
65        before it is considered failed. The command is run on the
66        target device every retry and evaluated against the
67        I(wait_for) conditions.
68    type: int
69    default: 10
70  interval:
71    description:
72      - Configures the interval in seconds to wait between retries
73        of the command. If the command does not pass the specified
74        conditions, the interval indicates how long to wait before
75        trying the command again.
76    type: int
77    default: 1
78"""
79
80EXAMPLES = """
81tasks:
82  - name: run show version on remote devices
83    dellos10_command:
84      commands: show version
85
86  - name: run show version and check to see if output contains OS10
87    dellos10_command:
88      commands: show version
89      wait_for: result[0] contains OS10
90
91  - name: run multiple commands on remote nodes
92    dellos10_command:
93      commands:
94        - show version
95        - show interface
96
97  - name: run multiple commands and evaluate the output
98    dellos10_command:
99      commands:
100        - show version
101        - show interface
102      wait_for:
103        - result[0] contains OS10
104        - result[1] contains Ethernet
105"""
106
107RETURN = """
108stdout:
109  description: The set of responses from the commands
110  returned: always apart from low level errors (such as action plugin)
111  type: list
112  sample: ['...', '...']
113stdout_lines:
114  description: The value of stdout split into a list
115  returned: always apart from low level errors (such as action plugin)
116  type: list
117  sample: [['...', '...'], ['...'], ['...']]
118failed_conditions:
119  description: The list of conditionals that have failed
120  returned: failed
121  type: list
122  sample: ['...', '...']
123warnings:
124  description: The list of warnings (if any) generated by module based on arguments
125  returned: always
126  type: list
127  sample: ['...', '...']
128"""
129import time
130
131from ansible.module_utils.basic import AnsibleModule
132from ansible.module_utils.network.dellos10.dellos10 import run_commands
133from ansible.module_utils.network.dellos10.dellos10 import dellos10_argument_spec, check_args
134from ansible.module_utils.network.common.utils import ComplexList
135from ansible.module_utils.network.common.parsing import Conditional
136from ansible.module_utils.six import string_types
137
138
139def to_lines(stdout):
140    for item in stdout:
141        if isinstance(item, string_types):
142            item = str(item).split('\n')
143        yield item
144
145
146def parse_commands(module, warnings):
147    command = ComplexList(dict(
148        command=dict(key=True),
149        prompt=dict(),
150        answer=dict()
151    ), module)
152    commands = command(module.params['commands'])
153    for index, item in enumerate(commands):
154        if module.check_mode and not item['command'].startswith('show'):
155            warnings.append(
156                'only show commands are supported when using check mode, not '
157                'executing `%s`' % item['command']
158            )
159        elif item['command'].startswith('conf'):
160            module.fail_json(
161                msg='dellos10_command does not support running config mode '
162                    'commands.  Please use dellos10_config instead'
163            )
164    return commands
165
166
167def main():
168    """main entry point for module execution
169    """
170    argument_spec = dict(
171        # { command: <str>, prompt: <str>, response: <str> }
172        commands=dict(type='list', required=True),
173
174        wait_for=dict(type='list'),
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(dellos10_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.update({
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