From bf8a838702bb69f258a5517571826a2a9933ba74 Mon Sep 17 00:00:00 2001 From: Chris Van Heuveln Date: Fri, 10 May 2019 03:40:49 -0400 Subject: [PATCH] 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 ` 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 (cherry picked from commit 8c56c116e5eed1726b6012640d05d70ab108350b) --- changelogs/fragments/nxos_snmp_user28.yaml | 2 + .../modules/network/nxos/nxos_snmp_user.py | 78 +++++++++++++++---- .../nxos_snmp_user/tests/common/sanity.yaml | 51 ++++++++---- 3 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 changelogs/fragments/nxos_snmp_user28.yaml diff --git a/changelogs/fragments/nxos_snmp_user28.yaml b/changelogs/fragments/nxos_snmp_user28.yaml new file mode 100644 index 00000000000..63c3a625efb --- /dev/null +++ b/changelogs/fragments/nxos_snmp_user28.yaml @@ -0,0 +1,2 @@ +bugfixes: +- nxos_snmp_user fix platform fixes for get_snmp_user (https://github.com/ansible/ansible/pull/55832). diff --git a/lib/ansible/modules/network/nxos/nxos_snmp_user.py b/lib/ansible/modules/network/nxos/nxos_snmp_user.py index 4264197abb6..c98051e5892 100644 --- a/lib/ansible/modules/network/nxos/nxos_snmp_user.py +++ b/lib/ansible/modules/network/nxos/nxos_snmp_user.py @@ -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 '. 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\S+)\s+' + r'(?P\S+)\s+' + r'(?P[\w\d-]+)(?P\([\w\d-]+\))*\s+' + r'(?P\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 diff --git a/test/integration/targets/nxos_snmp_user/tests/common/sanity.yaml b/test/integration/targets/nxos_snmp_user/tests/common/sanity.yaml index b9a78b9f820..6ac8291b476 100644 --- a/test/integration/targets/nxos_snmp_user/tests/common/sanity.yaml +++ b/test/integration/targets/nxos_snmp_user/tests/common/sanity.yaml @@ -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"