diff --git a/lib/ansible/module_utils/network/nxos/nxos.py b/lib/ansible/module_utils/network/nxos/nxos.py index ab9ac0b9194..33c4a91c9fa 100644 --- a/lib/ansible/module_utils/network/nxos/nxos.py +++ b/lib/ansible/module_utils/network/nxos/nxos.py @@ -49,6 +49,9 @@ nxos_provider_spec = { 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE'])), + 'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'), + 'auth_pass': dict(no_log=True, fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS'])), + 'use_ssl': dict(type='bool'), 'use_proxy': dict(default=True, type='bool'), 'validate_certs': dict(type='bool'), @@ -68,6 +71,9 @@ nxos_top_spec = { 'password': dict(removed_in_version=2.9, no_log=True), 'ssh_keyfile': dict(removed_in_version=2.9), + 'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'), + 'auth_pass': dict(removed_in_version=2.9, no_log=True), + 'use_ssl': dict(removed_in_version=2.9, type='bool'), 'validate_certs': dict(removed_in_version=2.9, type='bool'), 'timeout': dict(removed_in_version=2.9, type='int'), diff --git a/lib/ansible/plugins/action/nxos.py b/lib/ansible/plugins/action/nxos.py index 00cadbfdc58..44baa65aa19 100644 --- a/lib/ansible/plugins/action/nxos.py +++ b/lib/ansible/plugins/action/nxos.py @@ -63,6 +63,10 @@ class ActionModule(_ActionModule): pc.password = provider['password'] or self._play_context.password pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file pc.timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT) + pc.become = provider['authorize'] or False + if pc.become: + pc.become_method = 'enable' + pc.become_pass = provider['auth_pass'] display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr) connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin) diff --git a/lib/ansible/plugins/terminal/nxos.py b/lib/ansible/plugins/terminal/nxos.py index 46f3264736f..fcccfe155ee 100644 --- a/lib/ansible/plugins/terminal/nxos.py +++ b/lib/ansible/plugins/terminal/nxos.py @@ -20,9 +20,11 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import re +import json from ansible.plugins.terminal import TerminalBase from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_bytes, to_text class TerminalModule(TerminalBase): @@ -48,9 +50,50 @@ class TerminalModule(TerminalBase): re.compile(br"invalid (.+?)at '\^' marker", re.I) ] + def on_become(self, passwd=None): + if self._get_prompt().endswith(b'enable#'): + return + + out = self._exec_cli_command('show privilege') + out = to_text(out, errors='surrogate_then_replace').strip() + if 'Disabled' in out: + raise AnsibleConnectionFailure('Feature privilege is not enabled') + + # if already at privilege level 15 return + if '15' in out: + return + + cmd = {u'command': u'enable'} + if passwd: + cmd[u'prompt'] = to_text(r"(?i)[\r\n]?Password: $", errors='surrogate_or_strict') + cmd[u'answer'] = passwd + cmd[u'prompt_retry_check'] = True + + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + prompt = self._get_prompt() + if prompt is None or not prompt.strip().endswith(b'enable#'): + raise AnsibleConnectionFailure('failed to elevate privilege to enable mode still at prompt [%s]' % prompt) + except AnsibleConnectionFailure as e: + prompt = self._get_prompt() + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode, at prompt [%s] with error: %s' % (prompt, e.message)) + + def on_unbecome(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if b'(config' in prompt: + self._exec_cli_command('end') + self._exec_cli_command('exit') + + elif prompt.endswith(b'enable#'): + self._exec_cli_command('exit') + def on_open_shell(self): try: - for cmd in (b'terminal length 0', b'terminal width 511'): + for cmd in ('terminal length 0', 'terminal width 511'): self._exec_cli_command(cmd) except AnsibleConnectionFailure: raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/test/integration/targets/nxos_become/defaults/main.yaml b/test/integration/targets/nxos_become/defaults/main.yaml new file mode 100644 index 00000000000..9ef5ba51651 --- /dev/null +++ b/test/integration/targets/nxos_become/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "*" +test_items: [] diff --git a/test/integration/targets/nxos_become/meta/main.yml b/test/integration/targets/nxos_become/meta/main.yml new file mode 100644 index 00000000000..ae741cbdc71 --- /dev/null +++ b/test/integration/targets/nxos_become/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - prepare_nxos_tests diff --git a/test/integration/targets/nxos_become/tasks/cli.yaml b/test/integration/targets/nxos_become/tasks/cli.yaml new file mode 100644 index 00000000000..edbff7dfafb --- /dev/null +++ b/test/integration/targets/nxos_become/tasks/cli.yaml @@ -0,0 +1,33 @@ +--- +- name: collect common cli test cases + find: + paths: "{{ role_path }}/tests/common" + patterns: "{{ testcase }}.yaml" + connection: local + register: test_cases + +- name: collect cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + connection: local + register: cli_cases + +- set_fact: + test_cases: + files: "{{ test_cases.files }} + {{ cli_cases.files }}" + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test cases (connection=network_cli) + include: "{{ test_case_to_run }} ansible_connection=network_cli connection={}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run + +- name: run test case (connection=local) + include: "{{ test_case_to_run }} ansible_connection=local connection={{ cli }}" + with_first_found: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/nxos_become/tasks/main.yaml b/test/integration/targets/nxos_become/tasks/main.yaml new file mode 100644 index 00000000000..f9482f77cf1 --- /dev/null +++ b/test/integration/targets/nxos_become/tasks/main.yaml @@ -0,0 +1,3 @@ +--- +- { include: cli.yaml, tags: ['cli'] } +#- { include: nxapi.yaml, tags: ['nxapi'] } diff --git a/test/integration/targets/nxos_become/tasks/nxapi.yaml b/test/integration/targets/nxos_become/tasks/nxapi.yaml new file mode 100644 index 00000000000..68e96a29420 --- /dev/null +++ b/test/integration/targets/nxos_become/tasks/nxapi.yaml @@ -0,0 +1,27 @@ +--- +- name: collect common nxapi test cases + find: + paths: "{{ role_path }}/tests/common" + patterns: "{{ testcase }}.yaml" + connection: local + register: test_cases + +- name: collect nxapi test cases + find: + paths: "{{ role_path }}/tests/nxapi" + patterns: "{{ testcase }}.yaml" + connection: local + register: nxapi_cases + +- set_fact: + test_cases: + files: "{{ test_cases.files }} + {{ nxapi_cases.files }}" + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test cases (connection=local) + include: "{{ test_case_to_run }} ansible_connection=local connection={{ nxapi }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/nxos_become/tests/cli/sanity.yaml b/test/integration/targets/nxos_become/tests/cli/sanity.yaml new file mode 100644 index 00000000000..ad863871e33 --- /dev/null +++ b/test/integration/targets/nxos_become/tests/cli/sanity.yaml @@ -0,0 +1,39 @@ +--- +- debug: msg="START connection={{ ansible_connection }}/sanity.yaml" +- debug: msg="Using provider={{ connection.transport }}/sanity.yaml" + when: ansible_connection == "local" + +- block: + - name: update role to priv-14 + nxos_config: + lines: + - username admin role priv-14 + - no username admin role priv-15 + - enable secret 0 cisco + provider: "{{ cli }}" + + - name: reset_connection + meta: reset_connection + + - name: run commands with become + nxos_command: + commands: 'show privilege' + provider: "{{ cli }}" + become: yes + register: result + + - assert: + that: + - "'Current privilege level: 15' in result['stdout'][0]" + + always: + - name: teardown + nxos_config: + lines: + - username admin role priv-15 + - no username admin role priv-14 + - no enable secret + provider: "{{ cli }}" + ignore_errors: yes + +- debug: msg="END connection={{ ansible_connection }}/sanity.yaml" diff --git a/test/integration/targets/prepare_nxos_tests/tasks/main.yml b/test/integration/targets/prepare_nxos_tests/tasks/main.yml index ef88383ad2b..c27ae749008 100644 --- a/test/integration/targets/prepare_nxos_tests/tasks/main.yml +++ b/test/integration/targets/prepare_nxos_tests/tasks/main.yml @@ -3,6 +3,7 @@ nxos_config: lines: - feature nxapi + - feature privilege connection: network_cli ignore_errors: yes