1# -*- coding: utf-8 -*- 2# 3# (c) 2017, Ansible by Red Hat, inc 4# 5# This file is part of Ansible by Red Hat 6# 7# Ansible is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# Ansible is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 19# 20from __future__ import (absolute_import, division, print_function) 21__metaclass__ = type 22 23import json 24 25from ansible.module_utils._text import to_text 26from ansible.module_utils.connection import Connection, ConnectionError 27from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, EntityCollection 28 29_DEVICE_CONFIGS = {} 30_CONNECTION = None 31 32_COMMAND_SPEC = { 33 'command': dict(key=True), 34 'prompt': dict(), 35 'answer': dict() 36} 37 38 39def get_connection(module): 40 global _CONNECTION 41 if _CONNECTION: 42 return _CONNECTION 43 _CONNECTION = Connection(module._socket_path) 44 return _CONNECTION 45 46 47def to_commands(module, commands): 48 if not isinstance(commands, list): 49 raise AssertionError('argument must be of type <list>') 50 51 transform = EntityCollection(module, _COMMAND_SPEC) 52 commands = transform(commands) 53 return commands 54 55 56def run_commands(module, commands, check_rc=True): 57 connection = get_connection(module) 58 59 commands = to_commands(module, to_list(commands)) 60 61 responses = list() 62 63 for cmd in commands: 64 out = connection.get(**cmd) 65 responses.append(to_text(out, errors='surrogate_then_replace')) 66 67 return responses 68 69 70def get_config(module, source='running'): 71 conn = get_connection(module) 72 out = conn.get_config(source) 73 cfg = to_text(out, errors='surrogate_then_replace').strip() 74 return cfg 75 76 77def load_config(module, config): 78 try: 79 conn = get_connection(module) 80 conn.edit_config(config) 81 except ConnectionError as exc: 82 module.fail_json(msg=to_text(exc)) 83 84 85def _parse_json_output(out): 86 out_list = out.split('\n') 87 first_index = 0 88 opening_char = None 89 lines_count = len(out_list) 90 while first_index < lines_count: 91 first_line = out_list[first_index].strip() 92 if not first_line or first_line[0] not in ("[", "{"): 93 first_index += 1 94 continue 95 opening_char = first_line[0] 96 break 97 if not opening_char: 98 return "null" 99 closing_char = ']' if opening_char == '[' else '}' 100 last_index = lines_count - 1 101 found = False 102 while last_index > first_index: 103 last_line = out_list[last_index].strip() 104 if not last_line or last_line[0] != closing_char: 105 last_index -= 1 106 continue 107 found = True 108 break 109 if not found: 110 return opening_char + closing_char 111 return "".join(out_list[first_index:last_index + 1]) 112 113 114def show_cmd(module, cmd, json_fmt=True, fail_on_error=True): 115 if json_fmt: 116 cmd += " | json-print" 117 conn = get_connection(module) 118 command_obj = to_commands(module, to_list(cmd))[0] 119 try: 120 out = conn.get(**command_obj) 121 except ConnectionError: 122 if fail_on_error: 123 raise 124 return None 125 if json_fmt: 126 out = _parse_json_output(out) 127 try: 128 cfg = json.loads(out) 129 except ValueError: 130 module.fail_json( 131 msg="got invalid json", 132 stderr=to_text(out, errors='surrogate_then_replace')) 133 else: 134 cfg = to_text(out, errors='surrogate_then_replace').strip() 135 return cfg 136 137 138def get_interfaces_config(module, interface_type, flags=None, json_fmt=True): 139 cmd = "show interfaces %s" % interface_type 140 if flags: 141 cmd += " %s" % flags 142 return show_cmd(module, cmd, json_fmt) 143 144 145def get_bgp_summary(module): 146 cmd = "show running-config protocol bgp" 147 return show_cmd(module, cmd, json_fmt=False, fail_on_error=False) 148 149 150def get_capabilities(module): 151 """Returns platform info of the remove device 152 """ 153 if hasattr(module, '_capabilities'): 154 return module._capabilities 155 156 connection = get_connection(module) 157 try: 158 capabilities = connection.get_capabilities() 159 except ConnectionError as exc: 160 module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) 161 162 module._capabilities = json.loads(capabilities) 163 return module._capabilities 164 165 166class BaseOnyxModule(object): 167 ONYX_API_VERSION = "3.6.6000" 168 169 def __init__(self): 170 self._module = None 171 self._commands = list() 172 self._current_config = None 173 self._required_config = None 174 self._os_version = None 175 176 def init_module(self): 177 pass 178 179 def load_current_config(self): 180 pass 181 182 def get_required_config(self): 183 pass 184 185 def _get_os_version(self): 186 capabilities = get_capabilities(self._module) 187 device_info = capabilities['device_info'] 188 return device_info['network_os_version'] 189 190 # pylint: disable=unused-argument 191 def check_declarative_intent_params(self, result): 192 return None 193 194 def _validate_key(self, param, key): 195 validator = getattr(self, 'validate_%s' % key) 196 if callable(validator): 197 validator(param.get(key)) 198 199 def validate_param_values(self, obj, param=None): 200 if param is None: 201 param = self._module.params 202 for key in obj: 203 # validate the param value (if validator func exists) 204 try: 205 self._validate_key(param, key) 206 except AttributeError: 207 pass 208 209 @classmethod 210 def get_config_attr(cls, item, arg): 211 return item.get(arg) 212 213 @classmethod 214 def get_mtu(cls, item): 215 mtu = cls.get_config_attr(item, "MTU") 216 mtu_parts = mtu.split() 217 try: 218 return int(mtu_parts[0]) 219 except ValueError: 220 return None 221 222 def _validate_range(self, attr_name, min_val, max_val, value): 223 if value is None: 224 return True 225 if not min_val <= int(value) <= max_val: 226 msg = '%s must be between %s and %s' % ( 227 attr_name, min_val, max_val) 228 self._module.fail_json(msg=msg) 229 230 def validate_mtu(self, value): 231 self._validate_range('mtu', 1500, 9612, value) 232 233 def generate_commands(self): 234 pass 235 236 def run(self): 237 self.init_module() 238 239 result = {'changed': False} 240 241 self.get_required_config() 242 self.load_current_config() 243 244 self.generate_commands() 245 result['commands'] = self._commands 246 247 if self._commands: 248 if not self._module.check_mode: 249 load_config(self._module, self._commands) 250 result['changed'] = True 251 252 failed_conditions = self.check_declarative_intent_params(result) 253 254 if failed_conditions: 255 msg = 'One or more conditional statements have not been satisfied' 256 self._module.fail_json(msg=msg, 257 failed_conditions=failed_conditions) 258 259 self._module.exit_json(**result) 260 261 @classmethod 262 def main(cls): 263 app = cls() 264 app.run() 265