Add CmdRef logic to handle multiples (#57495)
* Add CmdRef logic to handle multiples * Fix python 2.6 issues * Fix bug when existing is not a dict object * Fix python2 vs python3 incompatibilty * Ignore unnecessary-lambda warning
This commit is contained in:
parent
8f68c8d438
commit
12d656901f
1 changed files with 158 additions and 88 deletions
|
@ -746,6 +746,8 @@ class NxosCmdRef:
|
||||||
# Create a list of supported commands based on ref keys
|
# Create a list of supported commands based on ref keys
|
||||||
ref['commands'] = sorted([k for k in ref if not k.startswith('_')])
|
ref['commands'] = sorted([k for k in ref if not k.startswith('_')])
|
||||||
ref['_proposed'] = []
|
ref['_proposed'] = []
|
||||||
|
ref['_context'] = []
|
||||||
|
ref['_resource_key'] = None
|
||||||
ref['_state'] = module.params.get('state', 'present')
|
ref['_state'] = module.params.get('state', 'present')
|
||||||
self.feature_enable()
|
self.feature_enable()
|
||||||
self.get_platform_defaults()
|
self.get_platform_defaults()
|
||||||
|
@ -882,46 +884,83 @@ class NxosCmdRef:
|
||||||
"""
|
"""
|
||||||
ref = self._ref
|
ref = self._ref
|
||||||
pattern = re.compile(ref[k]['getval'])
|
pattern = re.compile(ref[k]['getval'])
|
||||||
|
multiple = 'multiple' in ref[k].keys()
|
||||||
match_lines = [re.search(pattern, line) for line in output]
|
match_lines = [re.search(pattern, line) for line in output]
|
||||||
if 'dict' == ref[k]['kind']:
|
if 'dict' == ref[k]['kind']:
|
||||||
match = [m for m in match_lines if m]
|
match = [m for m in match_lines if m]
|
||||||
if not match:
|
if not match:
|
||||||
return None
|
return None
|
||||||
match = match[0]
|
if len(match) > 1 and not multiple:
|
||||||
|
raise ValueError("get_existing: multiple matches found for property {0}".format(k))
|
||||||
else:
|
else:
|
||||||
match = [m.groups() for m in match_lines if m]
|
match = [m.groups() for m in match_lines if m]
|
||||||
if not match:
|
if not match:
|
||||||
return None
|
return None
|
||||||
if len(match) > 1:
|
if len(match) > 1 and not multiple:
|
||||||
# TBD: Add support for multiple instances
|
raise ValueError("get_existing: multiple matches found for property {0}".format(k))
|
||||||
raise ValueError("get_existing: multiple match instances are not currently supported")
|
for item in match:
|
||||||
match = list(match[0]) # tuple to list
|
index = match.index(item)
|
||||||
|
match[index] = list(item) # tuple to list
|
||||||
|
|
||||||
# Handle config strings that nvgen with the 'no' prefix.
|
# Handle config strings that nvgen with the 'no' prefix.
|
||||||
# Example match behavior:
|
# Example match behavior:
|
||||||
# When pattern is: '(no )*foo *(\S+)*$' AND
|
# When pattern is: '(no )*foo *(\S+)*$' AND
|
||||||
# When output is: 'no foo' -> match: ['no ', None]
|
# When output is: 'no foo' -> match: ['no ', None]
|
||||||
# When output is: 'foo 50' -> match: [None, '50']
|
# When output is: 'foo 50' -> match: [None, '50']
|
||||||
if None is match[0]:
|
if None is match[index][0]:
|
||||||
match.pop(0)
|
match[index].pop(0)
|
||||||
elif 'no' in match[0]:
|
elif 'no' in match[index][0]:
|
||||||
match.pop(0)
|
match[index].pop(0)
|
||||||
if not match:
|
if not match:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return match
|
return match
|
||||||
|
|
||||||
|
def set_context(self, context=None):
|
||||||
|
"""Update ref with command context.
|
||||||
|
"""
|
||||||
|
if context is None:
|
||||||
|
context = []
|
||||||
|
ref = self._ref
|
||||||
|
# Process any additional context that this propoerty might require.
|
||||||
|
# 1) Global context from NxosCmdRef _template.
|
||||||
|
# 2) Context passed in using context arg.
|
||||||
|
ref['_context'] = ref['_template'].get('context', [])
|
||||||
|
for cmd in context:
|
||||||
|
ref['_context'].append(cmd)
|
||||||
|
# Last key in context is the resource key
|
||||||
|
ref['_resource_key'] = context[-1] if context else ref['_resource_key']
|
||||||
|
|
||||||
def get_existing(self):
|
def get_existing(self):
|
||||||
"""Update ref with existing command states from the device.
|
"""Update ref with existing command states from the device.
|
||||||
Store these states in each command's 'existing' key.
|
Store these states in each command's 'existing' key.
|
||||||
"""
|
"""
|
||||||
ref = self._ref
|
ref = self._ref
|
||||||
if ref.get('_cli_is_feature_disabled'):
|
if ref.get('_cli_is_feature_disabled'):
|
||||||
|
# Add context to proposed if state is present
|
||||||
|
if 'present' in ref['_state']:
|
||||||
|
[ref['_proposed'].append(ctx) for ctx in ref['_context']]
|
||||||
return
|
return
|
||||||
|
|
||||||
show_cmd = ref['_template']['get_command']
|
show_cmd = ref['_template']['get_command']
|
||||||
|
# Add additional command context if needed.
|
||||||
|
for filter in ref['_context']:
|
||||||
|
show_cmd = show_cmd + " | section '{0}'".format(filter)
|
||||||
|
|
||||||
output = self.execute_show_command(show_cmd, 'text') or []
|
output = self.execute_show_command(show_cmd, 'text') or []
|
||||||
if not output:
|
if not output:
|
||||||
|
# Add context to proposed if state is present
|
||||||
|
if 'present' in ref['_state']:
|
||||||
|
[ref['_proposed'].append(ctx) for ctx in ref['_context']]
|
||||||
|
return
|
||||||
|
|
||||||
|
# We need to remove the last item in context for state absent case.
|
||||||
|
if 'absent' in ref['_state'] and ref['_context']:
|
||||||
|
if ref['_resource_key'] and ref['_resource_key'] == ref['_context'][-1]:
|
||||||
|
if ref['_context'][-1] in output:
|
||||||
|
ref['_context'][-1] = 'no ' + ref['_context'][-1]
|
||||||
|
else:
|
||||||
|
del ref['_context'][-1]
|
||||||
return
|
return
|
||||||
|
|
||||||
# Walk each cmd in ref, use cmd pattern to discover existing cmds
|
# Walk each cmd in ref, use cmd pattern to discover existing cmds
|
||||||
|
@ -930,21 +969,24 @@ class NxosCmdRef:
|
||||||
match = self.pattern_match_existing(output, k)
|
match = self.pattern_match_existing(output, k)
|
||||||
if not match:
|
if not match:
|
||||||
continue
|
continue
|
||||||
|
ref[k]['existing'] = {}
|
||||||
|
for item in match:
|
||||||
|
index = match.index(item)
|
||||||
kind = ref[k]['kind']
|
kind = ref[k]['kind']
|
||||||
if 'int' == kind:
|
if 'int' == kind:
|
||||||
ref[k]['existing'] = int(match[0])
|
ref[k]['existing'][index] = int(item[0])
|
||||||
elif 'list' == kind:
|
elif 'list' == kind:
|
||||||
ref[k]['existing'] = [str(i) for i in match]
|
ref[k]['existing'][index] = [str(i) for i in item[0]]
|
||||||
elif 'dict' == kind:
|
elif 'dict' == kind:
|
||||||
# The getval pattern should contain regex named group keys that
|
# The getval pattern should contain regex named group keys that
|
||||||
# match up with the setval named placeholder keys; e.g.
|
# match up with the setval named placeholder keys; e.g.
|
||||||
# getval: my-cmd (?P<foo>\d+) bar (?P<baz>\d+)
|
# getval: my-cmd (?P<foo>\d+) bar (?P<baz>\d+)
|
||||||
# setval: my-cmd {foo} bar {baz}
|
# setval: my-cmd {foo} bar {baz}
|
||||||
ref[k]['existing'] = {}
|
ref[k]['existing'][index] = {}
|
||||||
for key in match.groupdict().keys():
|
for key in item.groupdict().keys():
|
||||||
ref[k]['existing'][key] = str(match.group(key))
|
ref[k]['existing'][index][key] = str(item.group(key))
|
||||||
elif 'str' == kind:
|
elif 'str' == kind:
|
||||||
ref[k]['existing'] = match[0]
|
ref[k]['existing'][index] = item[0]
|
||||||
else:
|
else:
|
||||||
raise ValueError("get_existing: unknown 'kind' value specified for key '{0}'".format(k))
|
raise ValueError("get_existing: unknown 'kind' value specified for key '{0}'".format(k))
|
||||||
|
|
||||||
|
@ -967,27 +1009,12 @@ class NxosCmdRef:
|
||||||
playval[key] = str(v)
|
playval[key] = str(v)
|
||||||
ref[k]['playval'] = playval
|
ref[k]['playval'] = playval
|
||||||
|
|
||||||
def get_proposed(self):
|
def build_cmd_set(self, playval, existing, k):
|
||||||
"""Compare playbook values against existing states and create a list
|
"""Helper function to create list of commands to configure device
|
||||||
of proposed commands.
|
Return a list of commands
|
||||||
Return a list of raw cli command strings.
|
|
||||||
"""
|
"""
|
||||||
ref = self._ref
|
ref = self._ref
|
||||||
# '_proposed' may be empty list or contain initializations; e.g. ['feature foo']
|
|
||||||
proposed = ref['_proposed']
|
proposed = ref['_proposed']
|
||||||
# Create a list of commands that have playbook values
|
|
||||||
play_keys = [k for k in ref['commands'] if 'playval' in ref[k]]
|
|
||||||
|
|
||||||
# Compare against current state
|
|
||||||
for k in play_keys:
|
|
||||||
playval = ref[k]['playval']
|
|
||||||
existing = ref[k].get('existing', ref[k]['default'])
|
|
||||||
if playval == existing and ref['_state'] == 'present':
|
|
||||||
continue
|
|
||||||
if isinstance(existing, dict) and all(x is None for x in existing.values()):
|
|
||||||
existing = None
|
|
||||||
if existing is None and ref['_state'] == 'absent':
|
|
||||||
continue
|
|
||||||
cmd = None
|
cmd = None
|
||||||
kind = ref[k]['kind']
|
kind = ref[k]['kind']
|
||||||
if 'int' == kind:
|
if 'int' == kind:
|
||||||
|
@ -1011,34 +1038,77 @@ class NxosCmdRef:
|
||||||
if cmd:
|
if cmd:
|
||||||
if 'absent' == ref['_state'] and not re.search(r'^no', cmd):
|
if 'absent' == ref['_state'] and not re.search(r'^no', cmd):
|
||||||
cmd = 'no ' + cmd
|
cmd = 'no ' + cmd
|
||||||
# Add processed command to cmd_ref object
|
|
||||||
ref[k]['setcmd'] = cmd
|
|
||||||
|
|
||||||
# Commands may require parent commands for proper context.
|
# Commands may require parent commands for proper context.
|
||||||
# Global _template context is replaced by parameter context
|
# Global _template context is replaced by parameter context
|
||||||
|
[proposed.append(ctx) for ctx in ref['_context']]
|
||||||
|
[proposed.append(ctx) for ctx in ref[k].get('context', [])]
|
||||||
|
proposed.append(cmd)
|
||||||
|
|
||||||
|
def get_proposed(self):
|
||||||
|
"""Compare playbook values against existing states and create a list
|
||||||
|
of proposed commands.
|
||||||
|
Return a list of raw cli command strings.
|
||||||
|
"""
|
||||||
|
ref = self._ref
|
||||||
|
# '_proposed' may be empty list or contain initializations; e.g. ['feature foo']
|
||||||
|
proposed = ref['_proposed']
|
||||||
|
|
||||||
|
if ref['_context'] and ref['_context'][-1].startswith('no'):
|
||||||
|
[proposed.append(ctx) for ctx in ref['_context']]
|
||||||
|
return proposed
|
||||||
|
|
||||||
|
# Create a list of commands that have playbook values
|
||||||
|
play_keys = [k for k in ref['commands'] if 'playval' in ref[k]]
|
||||||
|
|
||||||
|
def compare(playval, existing):
|
||||||
|
if 'present' in ref['_state']:
|
||||||
|
if existing is None:
|
||||||
|
return False
|
||||||
|
elif playval == existing:
|
||||||
|
return True
|
||||||
|
elif isinstance(existing, dict) and playval in existing.values():
|
||||||
|
return True
|
||||||
|
|
||||||
|
if 'absent' in ref['_state']:
|
||||||
|
if isinstance(existing, dict) and all(x is None for x in existing.values()):
|
||||||
|
existing = None
|
||||||
|
if existing is None or playval not in existing.values():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compare against current state
|
||||||
for k in play_keys:
|
for k in play_keys:
|
||||||
if ref[k].get('setcmd') is None:
|
playval = ref[k]['playval']
|
||||||
continue
|
existing = ref[k].get('existing', ref[k]['default'])
|
||||||
parent_context = ref['_template'].get('context', [])
|
multiple = 'multiple' in ref[k].keys()
|
||||||
parent_context = ref[k].get('context', parent_context)
|
|
||||||
if isinstance(parent_context, list):
|
|
||||||
for ctx_cmd in parent_context:
|
|
||||||
if re.search(r'setval::', ctx_cmd):
|
|
||||||
ctx_cmd = ref[ctx_cmd.split('::')[1]].get('setcmd')
|
|
||||||
if ctx_cmd is None:
|
|
||||||
continue
|
|
||||||
proposed.append(ctx_cmd)
|
|
||||||
elif isinstance(parent_context, str):
|
|
||||||
if re.search(r'setval::', parent_context):
|
|
||||||
parent_context = ref[parent_context.split('::')[1]].get('setcmd')
|
|
||||||
if parent_context is None:
|
|
||||||
continue
|
|
||||||
proposed.append(parent_context)
|
|
||||||
|
|
||||||
proposed.append(ref[k]['setcmd'])
|
# Multiple Instances:
|
||||||
|
if isinstance(existing, dict) and multiple:
|
||||||
|
item_found = False
|
||||||
|
for dkey, dvalue in existing.items():
|
||||||
|
if isinstance(dvalue, dict):
|
||||||
|
# Remove values set to string 'None' from dvalue
|
||||||
|
dvalue = dict((k, v) for k, v in dvalue.items() if v != 'None')
|
||||||
|
if compare(playval, dvalue):
|
||||||
|
item_found = True
|
||||||
|
if item_found:
|
||||||
|
continue
|
||||||
|
# Single Instance:
|
||||||
|
else:
|
||||||
|
if compare(playval, existing):
|
||||||
|
continue
|
||||||
|
|
||||||
# Remove duplicate commands from proposed before returning
|
# Multiple Instances:
|
||||||
return OrderedDict.fromkeys(proposed).keys()
|
if isinstance(existing, dict):
|
||||||
|
for dkey, dvalue in existing.items():
|
||||||
|
self.build_cmd_set(playval, dvalue, k)
|
||||||
|
# Single Instance:
|
||||||
|
else:
|
||||||
|
self.build_cmd_set(playval, existing, k)
|
||||||
|
|
||||||
|
# Remove any duplicate commands before returning.
|
||||||
|
# pylint: disable=unnecessary-lambda
|
||||||
|
return sorted(set(proposed), key=lambda x: proposed.index(x))
|
||||||
|
|
||||||
|
|
||||||
def nxosCmdRef_import_check():
|
def nxosCmdRef_import_check():
|
||||||
|
|
Loading…
Reference in a new issue