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