1# This code is part of Ansible, but is an independent component.
2# This particular file snippet, and this file snippet only, is BSD licensed.
3# Modules you write using this snippet, which is embedded dynamically by Ansible
4# still belong to the author of the module, and may assign their own license
5# to the complete work.
6#
7# (c) 2016 Red Hat Inc.
8#
9# Redistribution and use in source and binary forms, with or without modification,
10# are permitted provided that the following conditions are met:
11#
12#    * Redistributions of source code must retain the above copyright
13#      notice, this list of conditions and the following disclaimer.
14#    * Redistributions in binary form must reproduce the above copyright notice,
15#      this list of conditions and the following disclaimer in the documentation
16#      and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
26# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27#
28import json
29from ansible.module_utils._text import to_text
30from ansible.module_utils.basic import env_fallback
31from ansible.module_utils.network.common.utils import to_list, ComplexList
32from ansible.module_utils.common._collections_compat import Mapping
33from ansible.module_utils.connection import Connection, ConnectionError
34
35_DEVICE_CONNECTION = None
36
37
38class Cli:
39    def __init__(self, module):
40        self._module = module
41        self._device_configs = {}
42        self._connection = None
43
44    def get_capabilities(self):
45        """Returns platform info of the remove device
46        """
47        connection = self._get_connection()
48        return json.loads(connection.get_capabilities())
49
50    def _get_connection(self):
51        if not self._connection:
52            self._connection = Connection(self._module._socket_path)
53        return self._connection
54
55    def get_config(self, flags=None):
56        """Retrieves the current config from the device or cache
57        """
58        flags = [] if flags is None else flags
59        if self._device_configs == {}:
60            connection = self._get_connection()
61            try:
62                out = connection.get_config(flags=flags)
63            except ConnectionError as exc:
64                self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
65            self._device_configs = to_text(out, errors='surrogate_then_replace').strip()
66        return self._device_configs
67
68    def run_commands(self, commands, check_rc=True):
69        """Runs list of commands on remote device and returns results
70        """
71        connection = self._get_connection()
72        try:
73            response = connection.run_commands(commands=commands, check_rc=check_rc)
74        except ConnectionError as exc:
75            self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
76        return response
77
78    def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
79        conn = self._get_connection()
80        try:
81            diff = conn.get_diff(candidate=candidate, running=running, diff_match=diff_match,
82                                 diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace)
83        except ConnectionError as exc:
84            self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
85        return diff
86
87
88class HttpApi:
89    def __init__(self, module):
90        self._module = module
91        self._device_configs = {}
92        self._connection_obj = None
93
94    def get_capabilities(self):
95        """Returns platform info of the remove device
96        """
97        try:
98            capabilities = self._connection.get_capabilities()
99        except ConnectionError as exc:
100            self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
101
102        return json.loads(capabilities)
103
104    @property
105    def _connection(self):
106        if not self._connection_obj:
107            self._connection_obj = Connection(self._module._socket_path)
108        return self._connection_obj
109
110    def get_config(self, flags=None):
111        """Retrieves the current config from the device or cache
112        """
113        flags = [] if flags is None else flags
114        if self._device_configs == {}:
115            try:
116                out = self._connection.get_config(flags=flags)
117            except ConnectionError as exc:
118                self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
119            self._device_configs = to_text(out, errors='surrogate_then_replace').strip()
120        return self._device_configs
121
122    def run_commands(self, commands, check_rc=True):
123        """Runs list of commands on remote device and returns results
124        """
125        try:
126            response = self._connection.run_commands(commands=commands, check_rc=check_rc)
127        except ConnectionError as exc:
128            self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
129        return response
130
131    def send_requests(self, requests):
132        """Send a list of http requests to remote device and return results
133        """
134        if requests is None:
135            raise ValueError("'requests' value is required")
136
137        responses = list()
138        for req in to_list(requests):
139            try:
140                response = self._connection.send_request(**req)
141            except ConnectionError as exc:
142                self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
143            responses.append(response)
144        return responses
145
146    def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
147        try:
148            diff = self._connection.get_diff(candidate=candidate, running=running, diff_match=diff_match,
149                                             diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace)
150        except ConnectionError as exc:
151            self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
152        return diff
153
154
155def get_capabilities(module):
156    conn = get_connection(module)
157    return conn.get_capabilities()
158
159
160def get_connection(module):
161    global _DEVICE_CONNECTION
162    if not _DEVICE_CONNECTION:
163        connection_proxy = Connection(module._socket_path)
164        cap = json.loads(connection_proxy.get_capabilities())
165        if cap['network_api'] == 'cliconf':
166            conn = Cli(module)
167        elif cap['network_api'] == 'exosapi':
168            conn = HttpApi(module)
169        else:
170            module.fail_json(msg='Invalid connection type %s' % cap['network_api'])
171        _DEVICE_CONNECTION = conn
172    return _DEVICE_CONNECTION
173
174
175def get_config(module, flags=None):
176    flags = None if flags is None else flags
177    conn = get_connection(module)
178    return conn.get_config(flags)
179
180
181def load_config(module, commands):
182    conn = get_connection(module)
183    return conn.run_commands(to_command(module, commands))
184
185
186def run_commands(module, commands, check_rc=True):
187    conn = get_connection(module)
188    return conn.run_commands(to_command(module, commands), check_rc=check_rc)
189
190
191def to_command(module, commands):
192    transform = ComplexList(dict(
193        command=dict(key=True),
194        output=dict(default='text'),
195        prompt=dict(type='list'),
196        answer=dict(type='list'),
197        sendonly=dict(type='bool', default=False),
198        check_all=dict(type='bool', default=False),
199    ), module)
200    return transform(to_list(commands))
201
202
203def send_requests(module, requests):
204    conn = get_connection(module)
205    return conn.send_requests(to_request(module, requests))
206
207
208def to_request(module, requests):
209    transform = ComplexList(dict(
210        path=dict(key=True),
211        method=dict(),
212        data=dict(type='dict'),
213    ), module)
214    return transform(to_list(requests))
215
216
217def get_diff(module, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
218    conn = get_connection(module)
219    return conn.get_diff(candidate=candidate, running=running, diff_match=diff_match, diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace)
220