nxos_snmp_user: platform fixes for get_snmp_user (#55832)

* nxos_snmp_user: platform fixes for get_snmp_user

snmp user output behavior varies quite a bit for the different nxos platforms and required several workarounds:

- N5K/N6k
 - These platforms do not support structured output for `show snmp user`.
 - The current code lands in an `except` clause when the output is not structured; so I added a new `get_non_structured_snmp_user` method to scrape the state from the regular cli output if it's present.

- N9K-F
 - The `group` data in the JSON output is different for this platform; it has a different key (just `group` instead of `TABLE_groups` or `group_names`) and it is not indexed
 - For a single group the value is a string, for multiple groups it's a list

- sanity
 - N5K/N6K/N9K-F platforms will reject `no snmp user <name> <role>` when it's the last role defined for the user.
 - workaround is to use `nxos_user` to remove the user

- Changes validated on:
 - `N3K, N3K-F, N35, N6K, N7K, N9K, N9K-F`
 - `6.0(2)A8`
 - `7.0(3)I2, 7.0(3)I4, 7.0(3)I5, 7.0(3)I6, 7.0(3)I7`
 - `7.3(2)D1`
 - `7.3(3)N1, 7.3(4)N1`
 - `8.3(2)`
 - `9.2(2), 9.2(3)`

* fix lint warning
This commit is contained in:
Chris Van Heuveln 2019-05-10 03:40:49 -04:00 committed by Trishna Guha
parent bd47e64bc7
commit 8c56c116e5
2 changed files with 98 additions and 31 deletions

View file

@ -90,6 +90,7 @@ commands:
sample: ["snmp-server user ntc network-operator auth md5 test_password"]
'''
import re
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, check_args
@ -134,6 +135,7 @@ def get_snmp_groups(module):
def get_snmp_user(user, module):
command = 'show snmp user {0}'.format(user)
body = execute_show_command(command, module, text=True)
body_text = body[0]
if 'No such entry' not in body[0]:
body = execute_show_command(command, module)
@ -177,28 +179,74 @@ def get_snmp_user(user, module):
else:
resource['encrypt'] = 'none'
group_table = resource_table[tablegrpkey][rowgrpkey]
groups = []
try:
for group in group_table:
groups.append(str(group[grpkey]).strip())
except TypeError:
groups.append(str(group_table[grpkey]).strip())
if tablegrpkey in resource_table:
group_table = resource_table[tablegrpkey][rowgrpkey]
try:
for group in group_table:
groups.append(str(group[grpkey]).strip())
except TypeError:
groups.append(str(group_table[grpkey]).strip())
# Now for the platform bug case, get the groups
if isinstance(rt, list):
# remove 1st element from the list as this is parsed already
rt.pop(0)
# iterate through other elements indexed by
# 'user' and add it to groups.
for each in rt:
groups.append(each['user'].strip())
# Now for the platform bug case, get the groups
if isinstance(rt, list):
# remove 1st element from the list as this is parsed already
rt.pop(0)
# iterate through other elements indexed by
# 'user' and add it to groups.
for each in rt:
groups.append(each['user'].strip())
# Some 'F' platforms use 'group' key instead
elif 'group' in resource_table:
# single group is a string, multiple groups in a list
groups = resource_table['group']
if isinstance(groups, str):
groups = [groups]
resource['group'] = groups
except (KeyError, AttributeError, IndexError, TypeError):
if not resource and body_text and 'No such entry' not in body_text:
# 6K and other platforms may not return structured output;
# attempt to get state from text output
resource = get_non_structured_snmp_user(body_text)
return resource
def get_non_structured_snmp_user(body_text):
# This method is a workaround for platforms that don't support structured
# output for 'show snmp user <foo>'. This workaround may not work on all
# platforms. Sample non-struct output:
#
# User Auth Priv(enforce) Groups acl_filter
# ____ ____ _____________ ______ __________
# sample1 no no network-admin ipv4:my_acl
# network-operator
# priv-11
# -OR-
# sample2 md5 des(no) priv-15
# -OR-
# sample3 md5 aes-128(no) network-admin
resource = {}
output = body_text.rsplit('__________')[-1]
pat = re.compile(r'^(?P<user>\S+)\s+'
r'(?P<auth>\S+)\s+'
r'(?P<priv>[\w\d-]+)(?P<enforce>\([\w\d-]+\))*\s+'
r'(?P<group>\S+)',
re.M)
m = re.search(pat, output)
if not m:
return resource
resource['user'] = m.group('user')
resource['auth'] = m.group('auth')
resource['encrypt'] = 'aes-128' if 'aes' in str(m.group('priv')) else 'none'
resource['group'] = [m.group('group')]
more_groups = re.findall(r'^\s+([\w\d-]+)\s*$', output, re.M)
if more_groups:
resource['group'] += more_groups
return resource

View file

@ -4,10 +4,21 @@
when: ansible_connection == "local"
- name: Remove snmp user
nxos_snmp_user: &remove
nxos_snmp_user: &remove_snmp_user
user: ntc
provider: "{{ connection }}"
state: absent
ignore_errors: yes
when: platform is not search('N5K|N6K|N9K-F')
- name: Remove user workaround
# Some platforms will not allow snmp_user to remove the last role
nxos_user: &workaround_remove_user
name: ntc
provider: "{{ connection }}"
state: absent
ignore_errors: yes
when: platform is search('N5K|N6K|N9K-F')
- pause:
seconds: 5
@ -64,27 +75,35 @@
- assert: *false
- name: delete snmp user
nxos_snmp_user: &remove1
user: ntc
group: network-operator
provider: "{{ connection }}"
state: absent
register: result
- block:
# Some platforms will not allow snmp_user to remove the last role
- name: delete snmp user
nxos_snmp_user: &remove1
user: ntc
group: network-operator
provider: "{{ connection }}"
state: absent
register: result
- assert: *true
- assert: *true
- pause:
seconds: 5
- pause:
seconds: 5
- name: "Remove Idempotence"
nxos_snmp_user: *remove1
register: result
- name: "Remove Idempotence"
nxos_snmp_user: *remove1
register: result
- assert: *false
- assert: *false
when: platform is not search('N5K|N6K|N9K-F')
always:
- name: delete snmp user
nxos_snmp_user: *remove
nxos_snmp_user: *remove_snmp_user
when: platform is not search('N5K|N6K|N9K-F')
- name: remove user workaround
nxos_user: *workaround_remove_user
when: platform is search('N5K|N6K|N9K-F')
- debug: msg="END connection={{ ansible_connection }} nxos_snmp_user sanity test"