1# 2# (c) 2015 Peter Sprygada, <psprygada@ansible.com> 3# (c) 2017 Red Hat, Inc 4# 5# Copyright (c) 2016 Dell Inc. 6# 7# This code is part of Ansible, but is an independent component. 8# This particular file snippet, and this file snippet only, is BSD licensed. 9# Modules you write using this snippet, which is embedded dynamically by Ansible 10# still belong to the author of the module, and may assign their own license 11# to the complete work. 12# 13# Redistribution and use in source and binary forms, with or without modification, 14# are permitted provided that the following conditions are met: 15# 16# * Redistributions of source code must retain the above copyright 17# notice, this list of conditions and the following disclaimer. 18# * Redistributions in binary form must reproduce the above copyright notice, 19# this list of conditions and the following disclaimer in the documentation 20# and/or other materials provided with the distribution. 21# 22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 27# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 30# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31# 32import re 33 34from ansible.module_utils._text import to_text 35from ansible.module_utils.basic import env_fallback 36from ansible.module_utils.network.common.utils import to_list, ComplexList 37from ansible.module_utils.connection import exec_command 38from ansible.module_utils.network.common.config import NetworkConfig, ConfigLine, ignore_line 39 40_DEVICE_CONFIGS = {} 41 42WARNING_PROMPTS_RE = [ 43 r"[\r\n]?\[confirm yes/no\]:\s?$", 44 r"[\r\n]?\[y/n\]:\s?$", 45 r"[\r\n]?\[yes/no\]:\s?$" 46] 47 48dellos6_provider_spec = { 49 'host': dict(), 50 'port': dict(type='int'), 51 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), 52 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), 53 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), 54 'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'), 55 'auth_pass': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS']), no_log=True), 56 'timeout': dict(type='int'), 57} 58dellos6_argument_spec = { 59 'provider': dict(type='dict', options=dellos6_provider_spec), 60} 61dellos6_top_spec = { 62 'host': dict(removed_in_version=2.9), 63 'port': dict(removed_in_version=2.9, type='int'), 64 'username': dict(removed_in_version=2.9), 65 'password': dict(removed_in_version=2.9, no_log=True), 66 'ssh_keyfile': dict(removed_in_version=2.9, type='path'), 67 'authorize': dict(removed_in_version=2.9, type='bool'), 68 'auth_pass': dict(removed_in_version=2.9, no_log=True), 69 'timeout': dict(removed_in_version=2.9, type='int'), 70} 71dellos6_argument_spec.update(dellos6_top_spec) 72 73 74def check_args(module, warnings): 75 pass 76 77 78def get_config(module, flags=None): 79 flags = [] if flags is None else flags 80 81 cmd = 'show running-config ' 82 cmd += ' '.join(flags) 83 cmd = cmd.strip() 84 85 try: 86 return _DEVICE_CONFIGS[cmd] 87 except KeyError: 88 rc, out, err = exec_command(module, cmd) 89 if rc != 0: 90 module.fail_json(msg='unable to retrieve current config', stderr=to_text(err, errors='surrogate_or_strict')) 91 cfg = to_text(out, errors='surrogate_or_strict').strip() 92 _DEVICE_CONFIGS[cmd] = cfg 93 return cfg 94 95 96def to_commands(module, commands): 97 spec = { 98 'command': dict(key=True), 99 'prompt': dict(), 100 'answer': dict() 101 } 102 transform = ComplexList(spec, module) 103 return transform(commands) 104 105 106def run_commands(module, commands, check_rc=True): 107 responses = list() 108 commands = to_commands(module, to_list(commands)) 109 for cmd in commands: 110 cmd = module.jsonify(cmd) 111 rc, out, err = exec_command(module, cmd) 112 if check_rc and rc != 0: 113 module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), rc=rc) 114 responses.append(to_text(out, errors='surrogate_or_strict')) 115 return responses 116 117 118def load_config(module, commands): 119 rc, out, err = exec_command(module, 'configure terminal') 120 if rc != 0: 121 module.fail_json(msg='unable to enter configuration mode', err=to_text(err, errors='surrogate_or_strict')) 122 123 for command in to_list(commands): 124 if command == 'end': 125 continue 126# cmd = {'command': command, 'prompt': WARNING_PROMPTS_RE, 'answer': 'yes'} 127 rc, out, err = exec_command(module, command) 128 if rc != 0: 129 module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), command=command, rc=rc) 130 exec_command(module, 'end') 131 132 133def get_sublevel_config(running_config, module): 134 contents = list() 135 current_config_contents = list() 136 sublevel_config = Dellos6NetworkConfig(indent=0) 137 obj = running_config.get_object(module.params['parents']) 138 if obj: 139 contents = obj._children 140 for c in contents: 141 if isinstance(c, ConfigLine): 142 current_config_contents.append(c.raw) 143 sublevel_config.add(current_config_contents, module.params['parents']) 144 return sublevel_config 145 146 147def os6_parse(lines, indent=None, comment_tokens=None): 148 sublevel_cmds = [ 149 re.compile(r'^vlan !(priority).*$'), 150 re.compile(r'^stack.*$'), 151 re.compile(r'^interface.*$'), 152 re.compile(r'datacenter-bridging.*$'), 153 re.compile(r'line (console|telnet|ssh).*$'), 154 re.compile(r'ip ssh !(server).*$'), 155 re.compile(r'ip dhcp pool.*$'), 156 re.compile(r'ip vrf !(forwarding).*$'), 157 re.compile(r'(ip|mac|management|arp) access-list.*$'), 158 re.compile(r'ipv6 (dhcp pool|router).*$'), 159 re.compile(r'mail-server.*$'), 160 re.compile(r'vpc domain.*$'), 161 re.compile(r'router.*$'), 162 re.compile(r'route-map.*$'), 163 re.compile(r'policy-map.*$'), 164 re.compile(r'class-map match-all.*$'), 165 re.compile(r'captive-portal.*$'), 166 re.compile(r'admin-profile.*$'), 167 re.compile(r'link-dependency group.*$'), 168 re.compile(r'banner motd.*$'), 169 re.compile(r'openflow.*$'), 170 re.compile(r'support-assist.*$'), 171 re.compile(r'template.*$'), 172 re.compile(r'address-family.*$'), 173 re.compile(r'spanning-tree mst configuration.*$'), 174 re.compile(r'logging (?!.*(cli-command|buffered|console|email|facility|file|monitor|protocol|snmp|source-interface|traps|web-session)).*$'), 175 re.compile(r'(radius-server|tacacs-server) host.*$')] 176 177 childline = re.compile(r'^exit$') 178 config = list() 179 parent = list() 180 children = [] 181 parent_match = False 182 for line in str(lines).split('\n'): 183 text = str(re.sub(r'([{};])', '', line)).strip() 184 cfg = ConfigLine(text) 185 cfg.raw = line 186 if not text or ignore_line(text, comment_tokens): 187 parent = list() 188 children = [] 189 continue 190 191 else: 192 parent_match = False 193 # handle sublevel parent 194 for pr in sublevel_cmds: 195 if pr.match(line): 196 if len(parent) != 0: 197 cfg._parents.extend(parent) 198 parent.append(cfg) 199 config.append(cfg) 200 if children: 201 children.insert(len(parent) - 1, []) 202 children[len(parent) - 2].append(cfg) 203 parent_match = True 204 continue 205 # handle exit 206 if childline.match(line): 207 if children: 208 parent[len(children) - 1]._children.extend(children[len(children) - 1]) 209 if len(children) > 1: 210 parent[len(children) - 2]._children.extend(parent[len(children) - 1]._children) 211 cfg._parents.extend(parent) 212 children.pop() 213 parent.pop() 214 if not children: 215 children = list() 216 if parent: 217 cfg._parents.extend(parent) 218 parent = list() 219 config.append(cfg) 220 # handle sublevel children 221 elif parent_match is False and len(parent) > 0: 222 if not children: 223 cfglist = [cfg] 224 children.append(cfglist) 225 else: 226 children[len(parent) - 1].append(cfg) 227 cfg._parents.extend(parent) 228 config.append(cfg) 229 # handle global commands 230 elif not parent: 231 config.append(cfg) 232 return config 233 234 235class Dellos6NetworkConfig(NetworkConfig): 236 237 def load(self, contents): 238 self._items = os6_parse(contents, self._indent) 239 240 def _diff_line(self, other, path=None): 241 diff = list() 242 for item in self.items: 243 if str(item) == "exit": 244 for diff_item in diff: 245 if diff_item._parents: 246 if item._parents == diff_item._parents: 247 diff.append(item) 248 break 249 else: 250 diff.append(item) 251 break 252 elif item not in other: 253 diff.append(item) 254 return diff 255