1#!/usr/bin/python
2#
3# Copyright (c) 2019 Ericsson AB.
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6
7from __future__ import (absolute_import, division, print_function)
8__metaclass__ = type
9
10ANSIBLE_METADATA = {'metadata_version': '1.1',
11                    'status': ['preview'],
12                    'supported_by': 'community'}
13
14
15DOCUMENTATION = """
16---
17module: eric_eccli_command
18version_added: "2.9"
19author: Ericsson IPOS OAM team (@itercheng)
20short_description: Run commands on remote devices running ERICSSON ECCLI
21description:
22  - Sends arbitrary commands to an ERICSSON eccli node and returns the results
23    read from the device. This module includes an
24    argument that will cause the module to wait for a specific condition
25    before returning or timing out if the condition is not met.
26  - This module also support running commands in configuration mode
27    in raw command style.
28options:
29  commands:
30    description:
31      - List of commands to send to the remote ECCLI 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. If a command sent to the
36        device requires answering a prompt, it is possible to pass
37        a dict containing I(command), I(answer) and I(prompt).
38        Common answers are 'y' or "\\r" (carriage return, must be
39        double quotes). See examples.
40    type: list
41    required: true
42  wait_for:
43    description:
44      - List of conditions to evaluate against the output of the
45        command. The task will wait for each condition to be true
46        before moving forward. If the conditional is not true
47        within the configured number of retries, the task fails.
48        See examples.
49    type: list
50    aliases: ['waitfor']
51  match:
52    description:
53      - The I(match) argument is used in conjunction with the
54        I(wait_for) argument to specify the match policy.  Valid
55        values are C(all) or C(any).  If the value is set to C(all)
56        then all conditionals in the wait_for must be satisfied.  If
57        the value is set to C(any) then only one of the values must be
58        satisfied.
59    type: str
60    default: all
61    choices: ['any', 'all']
62  retries:
63    description:
64      - Specifies the number of retries a command should by 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
78notes:
79  - Tested against IPOS 19.3
80  - For more information on using Ansible to manage network devices see the :ref:`Ansible Network Guide <network_guide>`
81  - For more information on using Ansible to manage Ericsson devices see the Ericsson documents.
82  - "Starting with Ansible 2.5 we recommend using C(connection: network_cli)."
83  - For more information please see the L(ERIC_ECCLI Platform Options guide,../network/user_guide/platform_eric_eccli.html).
84"""
85
86EXAMPLES = r"""
87tasks:
88  - name: run show version on remote devices
89    eric_eccli_command:
90      commands: show version
91
92  - name: run show version and check to see if output contains IPOS
93    eric_eccli_command:
94      commands: show version
95      wait_for: result[0] contains IPOS
96
97  - name: run multiple commands on remote nodes
98    eric_eccli_command:
99      commands:
100        - show version
101        - show running-config interfaces
102
103  - name: run multiple commands and evaluate the output
104    eric_eccli_command:
105      commands:
106        - show version
107        - show running-config interfaces
108      wait_for:
109        - result[0] contains IPOS
110        - result[1] contains management
111"""
112
113RETURN = """
114stdout:
115  description: The set of responses from the commands
116  returned: always apart from low level errors (such as action plugin)
117  type: list
118  sample: ['...', '...']
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: [['...', '...'], ['...'], ['...']]
124failed_conditions:
125  description: The list of conditionals that have failed
126  returned: failed
127  type: list
128  sample: ['...', '...']
129"""
130import re
131import time
132
133from ansible.module_utils.network.eric_eccli.eric_eccli import run_commands
134from ansible.module_utils.basic import AnsibleModule
135from ansible.module_utils.network.common.utils import transform_commands
136from ansible.module_utils.network.common.parsing import Conditional
137from ansible.module_utils.six import string_types
138
139
140def parse_commands(module, warnings):
141    commands = transform_commands(module)
142
143    for item in list(commands):
144        if module.check_mode:
145            if item['command'].startswith('conf'):
146                warnings.append(
147                    'only non-config commands are supported when using check mode, not '
148                    'executing %s' % item['command']
149                )
150                commands.remove(item)
151    return commands
152
153
154def main():
155    """main entry point for module execution
156    """
157    argument_spec = dict(
158        commands=dict(type='list', required=True),
159
160        wait_for=dict(type='list', aliases=['waitfor']),
161        match=dict(default='all', choices=['all', 'any']),
162
163        retries=dict(default=10, type='int'),
164        interval=dict(default=1, type='int')
165    )
166
167    module = AnsibleModule(argument_spec=argument_spec,
168                           supports_check_mode=True)
169
170    result = {'changed': False}
171
172    warnings = list()
173    commands = parse_commands(module, warnings)
174    result['warnings'] = warnings
175
176    wait_for = module.params['wait_for'] or list()
177    conditionals = [Conditional(c) for c in wait_for]
178
179    retries = module.params['retries']
180    interval = module.params['interval']
181    match = module.params['match']
182
183    while retries > 0:
184        responses = run_commands(module, commands)
185
186        for item in list(conditionals):
187            if item(responses):
188                if match == 'any':
189                    conditionals = list()
190                    break
191                conditionals.remove(item)
192
193        if not conditionals:
194            break
195
196        time.sleep(interval)
197        retries -= 1
198
199    if conditionals:
200        failed_conditions = [item.raw for item in conditionals]
201        msg = 'One or more conditional statements have not been satisfied'
202        module.fail_json(msg=msg, failed_conditions=failed_conditions)
203
204    result.update({
205        'changed': False,
206        'stdout': responses,
207        'stdout_lines': list()
208    })
209
210    module.exit_json(**result)
211
212
213if __name__ == '__main__':
214    main()
215