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