Skip to content

Commit

Permalink
Bug fixes to handle facts, idempotency etc
Browse files Browse the repository at this point in the history
    - module utils fix for exit handling in multilevel parent commands 
    - config module fix to handle multiline banner
    - terminal plugin fix to handle error reported by management access lists
  • Loading branch information
komalupatil authored Dec 18, 2020
1 parent e79f77e commit 4788f93
Show file tree
Hide file tree
Showing 26 changed files with 303 additions and 106 deletions.
10 changes: 10 additions & 0 deletions changelogs/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ Ansible Network Collection for Dell EMC OS6 Release Notes

.. contents:: Topics

v1.0.6
======

Bugfixes
---------------

- module utils fix for exit handling in multilevel parent commands
- config module fix to handle multiline banner
- terminal plugin fix to handle error reported by management access lists

v1.0.5
======

Expand Down
9 changes: 9 additions & 0 deletions changelogs/changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,12 @@ releases:
fragments:
- 1.0.5.yaml
release_date: "2020-12-09"
1.0.6:
changes:
bugfixes:
- module utils fix for exit handling in multilevel parent commands
- config module fix to handle multiline banner
- terminal plugin fix to handle error reported by management access lists
fragments:
- 1.0.6.yaml
release_date: "2020-12-18"
2 changes: 1 addition & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace: dellemc
name: os6
version: 1.0.5
version: 1.0.6
readme: README.md
authors:
- Komal Patil <[email protected]>
Expand Down
4 changes: 2 additions & 2 deletions playbooks/ibgp/host_vars/switch1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ os6_vlan:
default_vlan: False
name: "os6vlan"
untagged_members:
- port: TenGigabitEthernet 7/0/1
- port: Te7/0/1
state: present
state: present

os6_interface:
TenGigabitEthernet 7/0/1:
Te7/0/1:
desc: "bgp"
admin: up
portmode: access
Expand Down
4 changes: 2 additions & 2 deletions playbooks/ibgp/host_vars/switch2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ os6_vlan:
default_vlan: False
name: "os6vlan"
untagged_members:
- port: TenGigabitEthernet 1/0/48
- port: Te1/0/48
state: present
state: present

os6_interface:
TenGigabitEthernet 1/0/48:
Te1/0/48:
desc: "bgp"
admin: up
portmode: access
Expand Down
43 changes: 38 additions & 5 deletions plugins/module_utils/network/os6.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
__metaclass__ = type

import re

import json
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import env_fallback
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.connection import exec_command
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, ConfigLine, ignore_line
from ansible.module_utils._text import to_bytes
from ansible.module_utils.connection import Connection, ConnectionError

_DEVICE_CONFIGS = {}

Expand Down Expand Up @@ -67,6 +69,31 @@ def check_args(module, warnings):
pass


def get_connection(module):
if hasattr(module, "_os6_connection"):
return module._os6_connection

capabilities = get_capabilities(module)
network_api = capabilities.get("network_api")
if network_api in ["cliconf"]:
module._os6_connection = Connection(module._socket_path)
else:
module.fail_json(msg="Invalid connection type %s" % network_api)

return module._os6_connection


def get_capabilities(module):
if hasattr(module, "_os6_capabilities"):
return module._os6_capabilities
try:
capabilities = Connection(module._socket_path).get_capabilities()
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
module._os6_capabilities = json.loads(capabilities)
return module._os6_capabilities


def get_config(module, flags=None):
flags = [] if flags is None else flags

Expand All @@ -89,7 +116,9 @@ def to_commands(module, commands):
spec = {
'command': dict(key=True),
'prompt': dict(),
'answer': dict()
'answer': dict(),
'sendonly': dict(),
'newline': dict()
}
transform = ComplexList(spec, module)
return transform(commands)
Expand All @@ -115,7 +144,6 @@ def load_config(module, commands):
for command in to_list(commands):
if command == 'end':
continue
# cmd = {'command': command, 'prompt': WARNING_PROMPTS_RE, 'answer': 'yes'}
rc, out, err = exec_command(module, command)
if rc != 0:
module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), command=command, rc=rc)
Expand Down Expand Up @@ -145,7 +173,7 @@ def os6_parse(lines, indent=None, comment_tokens=None):
re.compile(r'line (console|telnet|ssh).*$'),
re.compile(r'ip ssh !(server).*$'),
re.compile(r'ip dhcp pool.*$'),
re.compile(r'ip vrf !(forwarding).*$'),
re.compile(r'ip vrf (?!forwarding).*$'),
re.compile(r'(ip|mac|management|arp) access-list.*$'),
re.compile(r'ipv6 (dhcp pool|router).*$'),
re.compile(r'mail-server.*$'),
Expand All @@ -172,6 +200,7 @@ def os6_parse(lines, indent=None, comment_tokens=None):
children = []
parent_match = False
for line in str(lines).split('\n'):
line = str(line).strip()
text = str(re.sub(r'([{};])', '', line)).strip()
cfg = ConfigLine(text)
cfg.raw = line
Expand All @@ -191,6 +220,10 @@ def os6_parse(lines, indent=None, comment_tokens=None):
if children:
children.insert(len(parent) - 1, [])
children[len(parent) - 2].append(cfg)
if not children and len(parent) > 1:
configlist = [cfg]
children.append(configlist)
children.insert(len(parent) - 1, [])
parent_match = True
continue
# handle exit
Expand Down Expand Up @@ -237,7 +270,7 @@ def _diff_line(self, other, path=None):
if item._parents == diff_item._parents:
diff.append(item)
break
else:
elif [e for e in item._parents if e == diff_item]:
diff.append(item)
break
elif item not in other:
Expand Down
101 changes: 87 additions & 14 deletions plugins/modules/os6_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,18 @@
from ansible_collections.dellemc.os6.plugins.module_utils.network.os6 import load_config, run_commands
from ansible_collections.dellemc.os6.plugins.module_utils.network.os6 import WARNING_PROMPTS_RE
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import dumps
import re
from ansible.module_utils.six import iteritems
from ansible.module_utils.connection import exec_command
from ansible.module_utils._text import to_bytes


def get_candidate(module):
candidate = NetworkConfig(indent=0)
banners = {}
if module.params['src']:
candidate.load(module.params['src'])
src, banners = extract_banners(module.params['src'])
candidate.load(src)
elif module.params['lines']:
parents = module.params['parents'] or list()
commands = module.params['lines'][0]
Expand All @@ -222,19 +228,69 @@ def get_candidate(module):
elif (isinstance(commands, dict)) and (isinstance(commands['command'], str)):
candidate.add([commands['command']], parents=parents)
else:
candidate.add(module.params['lines'], parents=parents)
lines, banners = extract_banners(module.params['lines'])
candidate.add(lines, parents=parents)
return candidate, banners


def extract_banners(config):
flag = False
if isinstance(config, list):
str1 = "\n"
config = str1.join(config)
flag = True
banners = {}
banner_cmds = re.findall(r'^banner (\w+)', config, re.M)
for cmd in banner_cmds:
regex = r'banner %s \"(.+?)\".*' % cmd
match = re.search(regex, config, re.S)
if match:
key = 'banner %s' % cmd
banners[key] = match.group(1).strip()

for cmd in banner_cmds:
regex = r'banner %s \"(.+?)\".*' % cmd
match = re.search(regex, config, re.S)
if match:
config = config.replace(str(match.group(1)), '')
config = re.sub(r'banner \w+ \"\"', '', config)
if flag:
config = config.split("\n")
return (config, banners)


def diff_banners(want, have):
candidate = {}
for key, value in iteritems(want):
if value != have.get(key):
candidate[key] = value
return candidate


def get_running_config(module):
contents = module.params['config']
if not contents:
contents = get_config(module)
return contents
contents, banners = extract_banners(contents)
return contents, banners


def main():
def load_banners(module, banners):
result_banners = []
exec_command(module, 'configure terminal')
for each in banners:
delimiter = '"'
cmdline = ""
for key, value in each.items():
cmdline = key + " " + delimiter + value + delimiter
for cmd in cmdline.split("\n"):
rc, out, err = exec_command(module, module.jsonify({'command': cmd, 'sendonly': True}))
result_banners.append(cmdline)
exec_command(module, 'end')
return result_banners


def main():
backup_spec = dict(
filename=dict(),
dir_path=dict(type='path')
Expand Down Expand Up @@ -276,24 +332,27 @@ def main():
check_args(module, warnings)
result = dict(changed=False, saved=False, warnings=warnings)

candidate = get_candidate(module)
candidate, want_banners = get_candidate(module)
if module.params['backup']:
if not module.check_mode:
result['__backup__'] = get_config(module)

commands = list()

if any((module.params['lines'], module.params['src'])):
if match != 'none':
config = get_running_config(module)
config, have_banners = get_running_config(module)
config = NetworkConfig(contents=config, indent=0)
if parents:
config = get_sublevel_config(config, module)
configobjs = candidate.difference(config, match=match, replace=replace)
else:
configobjs = candidate.items

if configobjs:
have_banners = {}
diffbanners = diff_banners(want_banners, have_banners)
banners = list()
if diffbanners:
banners.append(diffbanners)
if configobjs or banners:
commands = dumps(configobjs, 'commands')
if ((isinstance(module.params['lines'], list)) and
(isinstance(module.params['lines'][0], dict)) and
Expand All @@ -303,20 +362,34 @@ def main():
'answer': module.params['lines'][0]['answer']}
commands = [module.jsonify(cmd)]
else:
commands = commands.split('\n')
if commands:
commands = commands.split('\n')

if module.params['before']:
commands[:0] = module.params['before']
commands[:0], before_banners = extract_banners(module.params['before'])
if before_banners:
banners.insert(0, before_banners)

if module.params['after']:
commands.extend(module.params['after'])
commands_after, after_banners = extract_banners(module.params['after'])
commands.extend(commands_after)
if after_banners:
banners.insert(len(banners), after_banners)

if not module.check_mode and module.params['update'] == 'merge':
load_config(module, commands)
if commands:
load_config(module, commands)
if banners:
result_banners = load_banners(module, banners)
else:
result_banners = []

result['changed'] = True
result['commands'] = commands
result['updates'] = commands
result['updates'] = commands if commands else []
result['banners'] = result_banners
if result['banners']:
result['updates'].extend(result_banners)

if module.params['save']:
result['changed'] = True
Expand Down
25 changes: 11 additions & 14 deletions plugins/modules/os6_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,20 +309,17 @@ def parse_interfaces(self, data):
return parsed

def parse_description(self, key, desc):
desc = re.split(r'[-+\s](?:-+\s)[-+\s].*', desc, maxsplit=1)
desc_next = desc[1]
if desc_next.find('Oob') > 0:
desc_val, desc_info = desc_next.split('Oob')
elif desc_next.find('Port') > 0:
desc_val, desc_info = desc_next.split('Port')
if desc_val:
for en in desc_val.splitlines():
if key in en:
match = re.search(r'^(\S+)\s+(\S+)', en)
if match.group(2) in ['Full', 'N/A']:
return "Null"
else:
return match.group(2)
desc_val, desc_info = "", ""
desc = re.split(r'[-+\s](?:-+\s)[-+\s].*', desc)
for desc_val in desc:
if desc_val:
for en in desc_val.splitlines():
if key in en:
match = re.search(r'^(\S+)\s+(\S+)', en)
if match.group(2) in ['Full', 'N/A']:
return "Null"
else:
return match.group(2)

def parse_macaddress(self, data):
match = re.search(r'Burned In MAC Address(.+)\s([A-Z0-9.]*)\n', data)
Expand Down
3 changes: 2 additions & 1 deletion plugins/terminal/os6.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ class TerminalModule(TerminalBase):
re.compile(br"(\bThe maximum number of users have already been created\b)|(\bUse '-' for range\b)"),
re.compile(br"(?:incomplete|ambiguous) command", re.I),
re.compile(br"connection timed out", re.I),
re.compile(br"\bParameter length should be exactly 32 characters\b"),
re.compile(br"'[^']' +returned error code: ?\d+"),
re.compile(br"Invalid|invalid.*$", re.I),
re.compile(br"((\bout of range\b)|(\bnot found\b)|(\bCould not\b)|(\bUnable to\b)|(\bCannot\b)|(\bError\b)).*", re.I),
re.compile(br"((\balready exists\b)|(\bnot exist\b)|(\bnot active\b)|(\bFailed\b)|(\bIncorrect\b)|(\bnot enabled\b)).*", re.I),
re.compile(br"((\balready exists\b)|(\bnot exist\b)|(\bnot active\b)|(\bFailed\b)|(\bIncorrect\b)|(\bnot enabled\b)|(\bDeactivate\b)).*", re.I),

]

Expand Down
Loading

0 comments on commit 4788f93

Please sign in to comment.