diff --git a/.ansible-lint b/.ansible-lint index c97d3bfc0..7062cb6dc 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -2,6 +2,7 @@ profile: production exclude_paths: - changelogs/changelog.yaml + - tests/unit/plugins/action/network/test_network.py skip_list: # ansible-lint does not like the `import-3.11` ignore in tests/sanity/ignore-*.txt - sanity[cannot-ignore] diff --git a/.flake8 b/.flake8 index ed9e1b403..0cf5d7554 100644 --- a/.flake8 +++ b/.flake8 @@ -7,39 +7,63 @@ count = true # Don't even try to analyze these: extend-exclude = - # vendored files - compat + # No need to traverse egg info dir + *.egg-info, + # tool cache dirs + *_cache # project env vars .env, # GitHub configs .github, - .mypy_cache + # Cache files of MyPy + .mypy_cache, # Cache files of pytest .pytest_cache, - tests/output/ - .tox - # Occasional virtualenv dirs + # Temp dir of pytest-testmon + .tmontmp, + # Occasional virtualenv dir .venv - venv # VS Code .vscode, + # Temporary build dir + build, + # This contains sdists and wheels of ansible-navigator that we don't want to check + dist, + # Metadata of `pip wheel` cmd is autogenerated + pip-wheel-metadata, + # adjacent venv + venv + # ansible won't let me + __init__.py # IMPORTANT: avoid using ignore option, always use extend-ignore instead # Completely and unconditionally ignore the following errors: extend-ignore = - # E123, E125 skipped as they are invalid PEP-8. - E123, - E125, - # annoy black by allowing white space before : https://github.com/psf/black/issues/315 - E203, - # ansible requires import after doc strings - E402, - # given the ansible_collections this is nearly impossible + F841, + # line-length E501, - W503, + # module level import not at top of file + E402, + # white space before : + E203, # Accessibility/large fonts and PEP8 unfriendly: -max-line-length = 100 +max-line-length = 120 + +# Allow certain violations in certain files: +# Please keep both sections of this list sorted, as it will be easier for others to find and add entries in the future +per-file-ignores = + # The following ignores have been researched and should be considered permanent + # each should be preceeded with an explanation of each of the error codes + # If other ignores are added for a specific file in the section following this, + # these will need to be added to that line as well. + + + # S101: Allow the use of assert within the tests directory, since tests require it. + tests/**.py: S101 + + # The following were present during the initial implementation. + # They are expected to be fixed and unignored over time. # Count the number of occurrences of each error/warning code and print a report: statistics = true diff --git a/.github/workflows/ack.yml b/.github/workflows/check_label.yml similarity index 60% rename from .github/workflows/ack.yml rename to .github/workflows/check_label.yml index fda595dc5..b120bfa32 100644 --- a/.github/workflows/ack.yml +++ b/.github/workflows/check_label.yml @@ -1,15 +1,11 @@ --- -# See https://github.com/ansible-community/devtools/blob/main/.github/workflows/ack.yml -name: ack - +name: "Check label" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true - on: # yamllint disable-line rule:truthy pull_request_target: types: [opened, labeled, unlabeled, synchronize] - jobs: - ack: - uses: ansible/devtools/.github/workflows/ack.yml@main + check_label: + uses: ansible/ansible-content-actions/.github/workflows/check_label.yaml@main diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml index 6cc005fd2..2ac57db02 100644 --- a/.github/workflows/codecoverage.yml +++ b/.github/workflows/codecoverage.yml @@ -3,6 +3,7 @@ name: code_coverage on: # yamllint disable-line rule:truthy push: + branches: [ main ] pull_request: branches: [ main ] diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml new file mode 100644 index 000000000..cb1a77f1c --- /dev/null +++ b/.github/workflows/draft_release.yml @@ -0,0 +1,18 @@ +--- +name: "Draft release" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +on: # yamllint disable-line rule:truthy + workflow_dispatch: +env: + NAMESPACE: ${{ github.repository_owner }} + COLLECTION_NAME: netcommon + ANSIBLE_COLLECTIONS_PATHS: ./ +jobs: + update_release_draft: + uses: ansible/ansible-content-actions/.github/workflows/draft_release.yaml@main + with: + repo: ${{ github.event.pull_request.head.repo.full_name }} + secrets: + BOT_PAT: ${{ secrets.BOT_PAT }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 6890c6a8d..000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: ansible-lint -on: # yamllint disable-line rule:truthy - pull_request: - branches: ["main"] -jobs: - build: - name: Ansible Lint # Naming the build is important to use it as a status check - runs-on: ubuntu-latest - steps: - - name: Run ansible-lint - uses: ansible/ansible-lint@main # or version tag instead of 'main' diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index bed724d98..000000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -# push workflow is shared and expected to perform actions after a merge happens -# on a maintenance branch (default or release). For example updating the -# draft release-notes. -# based on great work from -# https://github.com/T-Systems-MMS/ansible-collection-icinga-director -name: push - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -on: # yamllint disable-line rule:truthy - workflow_dispatch: - -env: - NAMESPACE: ansible - COLLECTION_NAME: netcommon - ANSIBLE_COLLECTIONS_PATHS: ./ - -jobs: - update_release_draft: - uses: ansible/devtools/.github/workflows/push_network.yml@main - with: - repo: ansible-collections/ansible.netcommon - secrets: - BOT_PAT: ${{ secrets.BOT_PAT }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb04259d1..6dbb1aa39 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,12 +1,12 @@ --- -name: release +name: "Release collection" on: # yamllint disable-line rule:truthy release: types: [published] jobs: release: - uses: ansible/devtools/.github/workflows/release_collection.yml@main + uses: ansible/ansible-content-actions/.github/workflows/release.yaml@main with: environment: release secrets: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6cd6ec7b6..b8d06999b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,22 +1,29 @@ --- -name: test_collection +name: "CI" concurrency: - group: ${{ github.head_ref }} + group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true on: # yamllint disable-line rule:truthy pull_request: branches: [main] workflow_dispatch: + schedule: + - cron: '0 0 * * *' jobs: changelog: - uses: ansible-network/github_actions/.github/workflows/changelog.yml@main + uses: ansible/ansible-content-actions/.github/workflows/changelog.yaml@main + if: github.event_name == 'pull_request' + build-import: + uses: ansible/ansible-content-actions/.github/workflows/build_import.yaml@main + ansible-lint: + uses: ansible/ansible-content-actions/.github/workflows/ansible_lint.yaml@main sanity: - uses: ansible-network/github_actions/.github/workflows/sanity.yml@main + uses: ansible/ansible-content-actions/.github/workflows/sanity.yaml@main unit-galaxy: - uses: ansible-network/github_actions/.github/workflows/unit_galaxy.yml@main + uses: ansible/ansible-content-actions/.github/workflows/unit.yaml@main unit-source: uses: ansible-network/github_actions/.github/workflows/unit_source.yml@main with: @@ -27,6 +34,7 @@ jobs: needs: - changelog - sanity + - ansible-lint - unit-galaxy - unit-source runs-on: ubuntu-latest @@ -37,5 +45,6 @@ jobs: '${{ needs.changelog.result }}', '${{ needs.sanity.result }}', '${{ needs.unit-galaxy.result }}', - '${{ needs.unit-source.result }}' + '${{ needs.unit-source.result }}', + '${{ needs.ansible-lint.result }}' ])" diff --git a/.github/workflows/token_refresh.yml b/.github/workflows/token_refresh.yml index 3fd76b1c2..ca93cb817 100644 --- a/.github/workflows/token_refresh.yml +++ b/.github/workflows/token_refresh.yml @@ -7,7 +7,7 @@ on: # yamllint disable-line rule:truthy jobs: refresh: - uses: ansible/devtools/.github/workflows/ah_token_refresh.yml@main + uses: ansible/ansible-content-actions/.github/workflows/refresh_ah_token.yaml@main with: environment: release secrets: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1470d21ef..2e7006bdf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: - id: update-docs - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v5.0.0 hooks: - id: check-merge-conflict - id: debug-statements @@ -14,8 +14,8 @@ repos: - id: no-commit-to-branch - id: trailing-whitespace - - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.3" + - repo: https://github.com/pycontribs/mirrors-prettier + rev: "v3.3.3" hooks: - id: prettier additional_dependencies: @@ -23,12 +23,17 @@ repos: - prettier-plugin-toml - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort args: ["--filter-files"] - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 24.10.0 hooks: - id: black + + - repo: https://github.com/pycqa/flake8 + rev: 7.1.1 + hooks: + - id: flake8 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bbb0e5beb..29c1649a4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,104 @@ Ansible Netcommon Collection Release Notes .. contents:: Topics +v7.1.0 +====== + +Minor Changes +------------- + +- ansible.netcommon.persistent - Connection local is marked deprecated and all dependent collections are advised to move to a proper connection plugin, complete support of connection local will be removed in a release after 01-01-2027. + +Bugfixes +-------- + +- Updated the error message for the content_templates parser to include the correct parser name and detailed error information. + +Documentation Changes +--------------------- + +- Add a simple regexp match example for multiple prompt with multiple answers. This example could be used to for restarting a network device with a delay. + +v7.0.0 +====== + +Release Summary +--------------- + +Starting from this release, the minimum `ansible-core` version this collection requires is `2.15.0`. The last known version compatible with ansible-core<2.15 is v6.1.3. + +Major Changes +------------- + +- Bumping `requires_ansible` to `>=2.15.0`, since previous ansible-core versions are EoL now. + +Bugfixes +-------- + +- Fix get api call during scp with libssh. +- Handle sftp error messages for file not present for routerOS. + +Known Issues +------------ + +- libssh - net_put and net_get fail when the destination file intended to be fetched is not present. + +v6.1.3 +====== + +Bugfixes +-------- + +- The v6.1.2 release introduced a change in cliconfbase's edit_config() signature which broke many platform cliconfs. This patch release reverts that change. + +v6.1.2 +====== + +Documentation Changes +--------------------- + +- Fixed module name and log consistency in parse_cli_textfsm filter doc. + +v6.1.1 +====== + +Bugfixes +-------- + +- Added guidance for users to open an issue for the respective platform if plugin support is needed. +- Improved module execution to gracefully handle cases where plugin support is required, providing a clear error message to the user. + +v6.1.0 +====== + +Minor Changes +------------- + +- Add new module cli_restore that exclusively handles restoring of backup configuration to target applaince. + +Bugfixes +-------- + +- libssh connection plugin - stop using deprecated ``PlayContext.verbosity`` property that is no longer present in ansible-core 2.18 (https://github.com/ansible-collections/ansible.netcommon/pull/626). +- network_cli - removed deprecated play_context.verbosity property. + +New Modules +----------- + +- cli_restore - Restore device configuration to network devices over network_cli + +v6.0.0 +====== + +Release Summary +--------------- + +Starting from this release, the minimum `ansible-core` version this collection requires is `2.14.0`. That last known version compatible with ansible-core<2.14 is `v5.3.0`. + +Major Changes +------------- + +- Bumping `requires_ansible` to `>=2.14.0`, since previous ansible-core versions are EoL now. v5.3.0 ====== diff --git a/README.md b/README.md index e3213e478..61100facc 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,39 @@ # Ansible Network Collection for Common Code (netcommon) [![CI](https://zuul-ci.org/gated.svg)](https://ansible.softwarefactory-project.io/zuul/builds?project=ansible-collections%2Fansible.netcommon) +[![Codecov](https://codecov.io/gh/ansible-collections/ansible.netcommon/branch/main/graph/badge.svg)](https://codecov.io/gh/ansible-collections/ansible.netcommon) +[![CI](https://github.com/ansible-collections/ansible.netcommon/actions/workflows/tests.yml/badge.svg?branch=main&event=schedule)](https://github.com/ansible-collections/ansible.netcommon/actions/workflows/tests.yml) The Ansible ``ansible.netcommon`` collection includes common content to help automate the management of network, security, and cloud devices. -This includes connection plugins, such as ``network_cli``, ``httpapi``, and ``netconf``. +This includes connection plugins, such as ``network_cli``, ``httpapi``, and ``netconf``. + +## Support + +As a Red Hat Ansible [Certified Content](https://catalog.redhat.com/software/search?target_platforms=Red%20Hat%20Ansible%20Automation%20Platform), this collection is entitled to [support](https://access.redhat.com/support/) through [Ansible Automation Platform](https://www.redhat.com/en/technologies/management/ansible) (AAP). + +If a support case cannot be opened with Red Hat and the collection has been obtained either from [Galaxy](https://galaxy.ansible.com/ui/) or [GitHub](https://github.com/ansible-collections/ansible.netcommon), there is community support available at no charge. + +You can join us on [#network:ansible.com](https://matrix.to/#/#network:ansible.com) room or the [Ansible Forum Network Working Group](https://forum.ansible.com/g/network-wg). + +For more information you can check the communication section below. + +## Communication + +* Join the Ansible forum: + * [Get Help](https://forum.ansible.com/c/help/6): get help or help others. + * [Posts tagged with 'network'](https://forum.ansible.com/tag/network): subscribe to participate in collection-related conversations. + * [Ansible Network Automation Working Group](https://forum.ansible.com/g/network-wg/): by joining the team you will automatically get subscribed to the posts tagged with [network](https://forum.ansible.com/tags/network). + * [Social Spaces](https://forum.ansible.com/c/chat/4): gather and interact with fellow enthusiasts. + * [News & Announcements](https://forum.ansible.com/c/news/5): track project-wide announcements including social events. + +* The Ansible [Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn): used to announce releases and important changes. + +For more information about communication, see the [Ansible communication guide](https://docs.ansible.com/ansible/devel/community/communication.html). ## Ansible version compatibility -This collection has been tested against following Ansible versions: **>=2.9.10**. +This collection has been tested against following Ansible versions: **>=2.15.0**. For collections that support Ansible 2.9, please ensure you update your `network_os` to use the fully qualified collection name (for example, `cisco.ios.ios`). @@ -70,6 +95,7 @@ Name | Description [ansible.netcommon.cli_backup](https://github.com/ansible-collections/ansible.netcommon/blob/main/docs/ansible.netcommon.cli_backup_module.rst)|Back up device configuration from network devices over network_cli [ansible.netcommon.cli_command](https://github.com/ansible-collections/ansible.netcommon/blob/main/docs/ansible.netcommon.cli_command_module.rst)|Run a cli command on cli-based network devices [ansible.netcommon.cli_config](https://github.com/ansible-collections/ansible.netcommon/blob/main/docs/ansible.netcommon.cli_config_module.rst)|Push text based configuration to network devices over network_cli +[ansible.netcommon.cli_restore](https://github.com/ansible-collections/ansible.netcommon/blob/main/docs/ansible.netcommon.cli_restore_module.rst)|Restore device configuration to network devices over network_cli [ansible.netcommon.grpc_config](https://github.com/ansible-collections/ansible.netcommon/blob/main/docs/ansible.netcommon.grpc_config_module.rst)|Fetch configuration/state data from gRPC enabled target hosts. [ansible.netcommon.grpc_get](https://github.com/ansible-collections/ansible.netcommon/blob/main/docs/ansible.netcommon.grpc_get_module.rst)|Fetch configuration/state data from gRPC enabled target hosts. [ansible.netcommon.net_get](https://github.com/ansible-collections/ansible.netcommon/blob/main/docs/ansible.netcommon.net_get_module.rst)|Copy a file from a network device to Ansible Controller @@ -103,10 +129,7 @@ collections: The most common use case for this collection is to include it as a dependency in a network device-specific collection. Use the Fully Qualified Collection Name (FQCN) when referring to content in this collection (for example, `ansible.netcommon.network_cli`). -See the [Vyos collection](https://github.com/ansible-collections/vyos) for an example of this. - - -**NOTE**: For Ansible 2.9, you may not see deprecation warnings when you run your playbooks with this collection. Use this documentation to track when a module is deprecated. +See the [Vyos collection](https://github.com/ansible-collections/vyos.vyos) for an example of this. ### See Also: diff --git a/bindep.txt b/bindep.txt index 5e5275acb..31812743c 100644 --- a/bindep.txt +++ b/bindep.txt @@ -4,8 +4,6 @@ gcc-c++ [doc test platform:rpm] libyaml-devel [test platform:rpm] libyaml-dev [test platform:dpkg] -python3-devel [test platform:rpm] -python3 [test platform:rpm] # ansible-pylibssh gcc [compile platform:rpm] @@ -26,8 +24,6 @@ python39-lxml [platform:centos-8 platform:rhel-8] findutils [compile platform:centos-8 platform:rhel-8] gcc [compile platform:centos-8 platform:rhel-8] make [compile platform:centos-8 platform:rhel-8] -python3-devel [compile platform:centos-9 platform:rhel-9] -python39-devel [compile platform:centos-8 platform:rhel-8] python3-cffi [platform:centos-9 platform:rhel-9] python39-cffi [platform:centos-8 platform:rhel-8] python3-cryptography [platform:centos-9 platform:rhel-9] diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 7948f12b4..0293d817e 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -719,3 +719,96 @@ releases: - sanity_ignores.yaml - trivial_lint.yaml release_date: "2023-10-17" + 6.0.0: + changes: + major_changes: + - Bumping `requires_ansible` to `>=2.14.0`, since previous ansible-core versions + are EoL now. + release_summary: + Starting from this release, the minimum `ansible-core` version + this collection requires is `2.14.0`. That last known version compatible with + ansible-core<2.14 is `v5.3.0`. + fragments: + - major_600.yml + release_date: "2023-11-30" + 6.1.0: + changes: + bugfixes: + - libssh connection plugin - stop using deprecated ``PlayContext.verbosity`` + property that is no longer present in ansible-core 2.18 (https://github.com/ansible-collections/ansible.netcommon/pull/626). + - network_cli - removed deprecated play_context.verbosity property. + minor_changes: + - Add new module cli_restore that exclusively handles restoring of backup configuration + to target applaince. + fragments: + - 626-verbosity.yml + - add_cli_restore.yaml + - verbosity.yml + modules: + - description: Restore device configuration to network devices over network_cli + name: cli_restore + namespace: "" + release_date: "2024-04-11" + 6.1.1: + changes: + bugfixes: + - Added guidance for users to open an issue for the respective platform if plugin + support is needed. + - Improved module execution to gracefully handle cases where plugin support + is required, providing a clear error message to the user. + fragments: + - update_not_supported_exception.yaml + release_date: "2024-04-19" + 6.1.2: + changes: + doc_changes: + - Fixed module name and log consistency in parse_cli_textfsm filter doc. + fragments: + - 614-fix-parse_cli_textfsm-doc.yaml + release_date: "2024-05-22" + 6.1.3: + changes: + bugfixes: + - The v6.1.2 release introduced a change in cliconfbase's edit_config() signature + which broke many platform cliconfs. This patch release reverts that change. + fragments: + - bug_653.yaml + release_date: "2024-05-29" + 7.0.0: + changes: + bugfixes: + - Fix get api call during scp with libssh. + - Handle sftp error messages for file not present for routerOS. + known_issues: + - libssh - net_put and net_get fail when the destination file intended to be + fetched is not present. + major_changes: + - Bumping `requires_ansible` to `>=2.15.0`, since previous ansible-core versions + are EoL now. + release_summary: + Starting from this release, the minimum `ansible-core` version + this collection requires is `2.15.0`. The last known version compatible with + ansible-core<2.15 is v6.1.3. + fragments: + - fix-routeros-net_put.yaml + - libssh_get.yaml + - min_215.yaml + release_date: "2024-06-10" + 7.1.0: + changes: + bugfixes: + - Updated the error message for the content_templates parser to include the + correct parser name and detailed error information. + doc_changes: + - Add a simple regexp match example for multiple prompt with multiple answers. + This example could be used to for restarting a network device with a delay. + minor_changes: + - ansible.netcommon.persistent - Connection local is marked deprecated and all + dependent collections are advised to move to a proper connection plugin, complete + support of connection local will be removed in a release after 01-01-2027. + fragments: + - bye_connection_local.yaml + - cli-command-module.yaml + - readme_communication.yml + - update_error_msg.yaml + release_date: "2024-08-29" diff --git a/changelogs/fragments/add_support_section.yaml b/changelogs/fragments/add_support_section.yaml new file mode 100644 index 000000000..754cf19c4 --- /dev/null +++ b/changelogs/fragments/add_support_section.yaml @@ -0,0 +1,3 @@ +--- +doc_changes: + - "Includes a new support related section in the README." diff --git a/changelogs/fragments/ignore_219.yaml b/changelogs/fragments/ignore_219.yaml new file mode 100644 index 000000000..d051c4211 --- /dev/null +++ b/changelogs/fragments/ignore_219.yaml @@ -0,0 +1,3 @@ +--- +trivial: + - Add ignore-2.19.txt. diff --git a/docs/ansible.netcommon.cli_backup_module.rst b/docs/ansible.netcommon.cli_backup_module.rst index 6785696c9..11e337f28 100644 --- a/docs/ansible.netcommon.cli_backup_module.rst +++ b/docs/ansible.netcommon.cli_backup_module.rst @@ -146,4 +146,4 @@ Status Authors ~~~~~~~ -- Kate Case (@Qalthos) +- Katherine Case (@Qalthos) diff --git a/docs/ansible.netcommon.cli_command_module.rst b/docs/ansible.netcommon.cli_command_module.rst index 8e69a80f9..e2b2e247e 100644 --- a/docs/ansible.netcommon.cli_command_module.rst +++ b/docs/ansible.netcommon.cli_command_module.rst @@ -167,7 +167,7 @@ Examples ansible.netcommon.cli_command: command: commit replace prompt: This commit will replace or remove the entire running configuration - answer: yes + answer: "yes" - name: run command expecting user confirmation ansible.netcommon.cli_command: @@ -178,27 +178,38 @@ Examples - name: run config mode command and handle prompt/answer ansible.netcommon.cli_command: - command: '{{ item }}' + command: "{{ item }}" prompt: - - Exit with uncommitted changes + - Exit with uncommitted changes answer: y loop: - - configure - - set system syslog file test any any - - exit + - configure + - set system syslog file test any any + - exit - name: multiple prompt, multiple answer (mandatory check for all prompts) ansible.netcommon.cli_command: command: copy sftp sftp://user@host//user/test.img check_all: true prompt: - - Confirm download operation - - Password - - Do you want to change that to the standby image + - Confirm download operation + - Password + - Do you want to change that to the standby image answer: - - y - - - - y + - y + - + - y + + - name: Simple regexp match for multiple prompt, multiple answer(mandatory check for all prompts) + ansible.netcommon.cli_command: + command: reload in 5 + check_all: true + prompt: + - Save\? + - confirm + answer: + - n + - y @@ -265,4 +276,4 @@ Status Authors ~~~~~~~ -- Nathaniel Case (@Qalthos) +- Katherine Case (@Qalthos) diff --git a/docs/ansible.netcommon.cli_config_module.rst b/docs/ansible.netcommon.cli_config_module.rst index 60048550e..1af9b2010 100644 --- a/docs/ansible.netcommon.cli_config_module.rst +++ b/docs/ansible.netcommon.cli_config_module.rst @@ -302,7 +302,7 @@ Examples - name: configure device with config with defaults enabled ansible.netcommon.cli_config: config: "{{ lookup('template', 'basic/config.j2') }}" - defaults: yes + defaults: "yes" - name: Use diff_match ansible.netcommon.cli_config: @@ -325,7 +325,7 @@ Examples - name: configurable backup path ansible.netcommon.cli_config: config: "{{ lookup('template', 'basic/config.j2') }}" - backup: yes + backup: "yes" backup_options: filename: backup.cfg dir_path: /home/user diff --git a/docs/ansible.netcommon.cli_restore_module.rst b/docs/ansible.netcommon.cli_restore_module.rst new file mode 100644 index 000000000..d13d87efb --- /dev/null +++ b/docs/ansible.netcommon.cli_restore_module.rst @@ -0,0 +1,128 @@ +.. _ansible.netcommon.cli_restore_module: + + +***************************** +ansible.netcommon.cli_restore +***************************** + +**Restore device configuration to network devices over network_cli** + + +Version added: 6.1.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This module provides platform agnostic way of restore text based configuration to network devices over network_cli connection plugin. +- The module uses the platforms `config replace` commands to restore backup configuration that is already copied over to the appliance. + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ filename + +
+ string +
+
+ +
Filename of the backup file, present in the appliance where the restore operation is to be performed. Check appliance for the configuration backup file name.
+
+
+ path + +
+ string +
+
+ +
The location in the target appliance where the file containing the backup exists. The path and the filename together create the input to the config replace command,
+
For an IOSXE appliance the path pattern is flash://<filename>
+
+
+ + +Notes +----- + +.. note:: + - This module is supported on ``ansible_network_os`` network platforms. See the :ref:`Network Platform Options ` for details. + + + +Examples +-------- + +.. code-block:: yaml + + - name: Restore IOS-XE configuration + ansible.netcommon.cli_restore: + filename: backupDday.cfg + path: flash:// + + # Command fired + # ------------- + # config replace flash://backupDday.cfg force + + # Task Output + # ----------- + # + # ok: [BATMON] => changed=false + # __restore__: |- + # The rollback configlet from the last pass is listed below: + # ******** + # !List of Rollback Commands: + # Building configuration... + # Current configuration : 3781 bytes + # end + # ******** + # + # + # Rollback aborted after 5 passes + # The following commands are failed to apply to the IOS image. + # ******** + # Building configuration... + # Current configuration : 3781 bytes + # ******** + # invocation: + # module_args: + # filename: backupDday.cfg + + + + +Status +------ + + +Authors +~~~~~~~ + +- Sagar Paul (@KB-perByte) diff --git a/docs/ansible.netcommon.grpc_config_module.rst b/docs/ansible.netcommon.grpc_config_module.rst index 5e418250d..9e751804b 100644 --- a/docs/ansible.netcommon.grpc_config_module.rst +++ b/docs/ansible.netcommon.grpc_config_module.rst @@ -159,38 +159,37 @@ Examples .. code-block:: yaml - name: Merge static route config - ansible.netcommon.grpc_config: - config: - Cisco-IOS-XR-ip-static-cfg:router-static: - default-vrf: - address-family: - vrfipv4: - vrf-unicast: - vrf-prefixes: - vrf-prefix: - - prefix: "1.2.3.6" - prefix-length: 32 - vrf-route: - vrf-next-hop-table: - vrf-next-hop-next-hop-address: - - next-hop-address: "10.0.2.2" - - state: merged - - - name: Merge bgp config - ansible.netcommon.grpc_config: - config: "{{ lookup('file', 'bgp.json') }}" - state: merged - - - name: Find diff - diff: True - ansible.netcommon.grpc_config: - config: "{{ lookup('file', 'bgp_start.yml') }}" - state: merged - - - name: Backup running config - ansible.netcommon.grpc_config: - backup: yes + ansible.netcommon.grpc_config: + config: + Cisco-IOS-XR-ip-static-cfg:router-static: + default-vrf: + address-family: + vrfipv4: + vrf-unicast: + vrf-prefixes: + vrf-prefix: + - prefix: "1.2.3.6" + prefix-length: 32 + vrf-route: + vrf-next-hop-table: + vrf-next-hop-next-hop-address: + - next-hop-address: "10.0.2.2" + state: merged + + - name: Merge bgp config + ansible.netcommon.grpc_config: + config: "{{ lookup('file', 'bgp.json') }}" + state: merged + + - name: Find diff + diff: true + ansible.netcommon.grpc_config: + config: "{{ lookup('file', 'bgp_start.yml') }}" + state: merged + + - name: Backup running config + ansible.netcommon.grpc_config: + backup: true diff --git a/docs/ansible.netcommon.grpc_get_module.rst b/docs/ansible.netcommon.grpc_get_module.rst index 607cdf186..480b5db11 100644 --- a/docs/ansible.netcommon.grpc_get_module.rst +++ b/docs/ansible.netcommon.grpc_get_module.rst @@ -123,14 +123,14 @@ Examples .. code-block:: yaml - name: Get bgp configuration data - grpc_get: - section: - Cisco-IOS-XR-ip-static-cfg:router-static: - - null - - name: run cli command - grpc_get: - command: 'show version' - display: text + grpc_get: + section: + Cisco-IOS-XR-ip-static-cfg:router-static: + - null + - name: run cli command + grpc_get: + command: "show version" + display: text diff --git a/docs/ansible.netcommon.netconf_config_module.rst b/docs/ansible.netcommon.netconf_config_module.rst index 53b1dfebb..f2c836686 100644 --- a/docs/ansible.netcommon.netconf_config_module.rst +++ b/docs/ansible.netcommon.netconf_config_module.rst @@ -431,14 +431,14 @@ Examples - name: configure interface while providing different private key file path (for connection=netconf) ansible.netcommon.netconf_config: - backup: yes + backup: true register: backup_junos_location vars: ansible_private_key_file: /home/admin/.ssh/newprivatekeyfile - name: configurable backup path ansible.netcommon.netconf_config: - backup: yes + backup: true backup_options: filename: backup.cfg dir_path: /home/user @@ -446,49 +446,54 @@ Examples - name: "configure using direct native format configuration (cisco iosxr)" ansible.netcommon.netconf_config: format: json - content: { - "config": { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": { - "active": "act", - "description": "test for ansible Loopback999", - "interface-name": "Loopback999" - } - } - } - } - get_filter: { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null - } - } + content: + { + "config": + { + "interface-configurations": + { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": + { + "active": "act", + "description": "test for ansible Loopback999", + "interface-name": "Loopback999", + }, + }, + }, + } + get_filter: + { + "interface-configurations": + { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null, + }, + } - name: "configure using json string format configuration (cisco iosxr)" ansible.netcommon.netconf_config: format: json content: | - { - "config": { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": { - "active": "act", - "description": "test for ansible Loopback999", - "interface-name": "Loopback999" - } - } - } - } - get_filter: | - { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null + { + "config": { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": { + "active": "act", + "description": "test for ansible Loopback999", + "interface-name": "Loopback999" } } - + } + } + get_filter: | + { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null + } + } # Make a round-trip interface description change, diff the before and after # this demonstrates the use of the native display format and several utilities @@ -512,8 +517,8 @@ Examples - name: Update the description ansible.utils.update_fact: updates: - - path: pre.output.data.interfaces.interface.config.description - value: "Configured by ansible {{ 100 | random }}" + - path: pre.output.data.interfaces.interface.config.description + value: "Configured by ansible {{ 100 | random }}" register: updated - name: Apply the new configuration @@ -533,7 +538,6 @@ Examples ansible.utils.fact_diff: before: "{{ pre.output.data|ansible.utils.to_paths }}" after: "{{ post.output.data|ansible.utils.to_paths }}" - # TASK [Show the differences between the pre and post configurations] ******** # --- before # +++ after diff --git a/docs/ansible.netcommon.netconf_get_module.rst b/docs/ansible.netcommon.netconf_get_module.rst index a0581c681..69edcec5d 100644 --- a/docs/ansible.netcommon.netconf_get_module.rst +++ b/docs/ansible.netcommon.netconf_get_module.rst @@ -188,12 +188,12 @@ Examples - name: "get configuration with json filter string and native output (using xmltodict)" netconf_get: filter: | - { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null - } - } + { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null + } + } display: native - name: Define the Cisco IOSXR interface filter @@ -211,16 +211,17 @@ Examples - name: "get configuration with direct native filter type" ansible.netcommon.netconf_get: - filter: { - "interface-configurations": { + filter: + { + "interface-configurations": + { "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null + "interface-configuration": null, + }, } - } display: native register: result - # Make a round-trip interface description change, diff the before and after # this demonstrates the use of the native display format and several utilities # from the ansible.utils collection @@ -243,8 +244,8 @@ Examples - name: Update the description ansible.utils.update_fact: updates: - - path: pre.output.data.interfaces.interface.config.description - value: "Configured by ansible {{ 100 | random }}" + - path: pre.output.data.interfaces.interface.config.description + value: "Configured by ansible {{ 100 | random }}" register: updated - name: Apply the new configuration @@ -264,7 +265,6 @@ Examples ansible.utils.fact_diff: before: "{{ pre.output.data|ansible.utils.to_paths }}" after: "{{ post.output.data|ansible.utils.to_paths }}" - # TASK [Show the differences between the pre and post configurations] ******** # --- before # +++ after diff --git a/docs/ansible.netcommon.parse_cli_textfsm_filter.rst b/docs/ansible.netcommon.parse_cli_textfsm_filter.rst index 9f2283f5d..56a07e315 100644 --- a/docs/ansible.netcommon.parse_cli_textfsm_filter.rst +++ b/docs/ansible.netcommon.parse_cli_textfsm_filter.rst @@ -101,14 +101,14 @@ Examples device_neighbors: "{{ lldp_output.stdout[0] | parse_cli_textfsm('~/ntc-templates/templates/cisco_ios_show_lldp_neighbors.textfsm') }}" - name: "Debug" - ansible.builtindebug: + ansible.builtin.debug: msg: "{{ device_neighbors }}" # Task Output # ----------- # # TASK [Fetch command output] - # ok: [rtr-2] + # ok: [rtr-1] # TASK [Invoke parse_cli_textfsm] # ok: [rtr-1] diff --git a/docs/ansible.netcommon.pop_ace_filter.rst b/docs/ansible.netcommon.pop_ace_filter.rst index 721ae9a74..a8dfe8452 100644 --- a/docs/ansible.netcommon.pop_ace_filter.rst +++ b/docs/ansible.netcommon.pop_ace_filter.rst @@ -303,7 +303,7 @@ Examples .. code-block:: yaml - ##Playbook with filter plugin example + ## Playbook with filter plugin example vars: filter_options: match_all: true @@ -438,7 +438,7 @@ Examples ansible.builtin.debug: msg: "{{ acls_data | ansible.netcommon.pop_ace(filter_options=filter_options, match_criteria=match_criteria) }}" - ##Output + ## Output # PLAY [Filter plugin example pop_ace] ****************************************************************************************************************** # TASK [Remove ace entries from a provided data] *********************************************************************************************************** @@ -572,8 +572,8 @@ Examples # afi: ipv6 - ##Playbook with workflow example - tasks: + ## Playbook with workflow example + _tasks: - name: Gather ACLs config from device existing ACLs config cisco.ios.ios_acls: state: gathered @@ -598,8 +598,7 @@ Examples state: overridden config: "{{ clean_acls['clean_acls']['acls'] | from_yaml }}" - - ##Output + ## Output # PLAYBOOK: pop_ace_example.yml *********************************************** diff --git a/docs/ansible.netcommon.telnet_module.rst b/docs/ansible.netcommon.telnet_module.rst index e28190bdc..18a9769db 100644 --- a/docs/ansible.netcommon.telnet_module.rst +++ b/docs/ansible.netcommon.telnet_module.rst @@ -255,24 +255,24 @@ Examples ansible.netcommon.telnet: user: cisco password: cisco - login_prompt: 'Username: ' + login_prompt: "Username: " prompts: - - '[>#]' + - "[>#]" command: - - terminal length 0 - - configure terminal - - hostname ios01 + - terminal length 0 + - configure terminal + - hostname ios01 - name: run show commands ansible.netcommon.telnet: user: cisco password: cisco - login_prompt: 'Username: ' + login_prompt: "Username: " prompts: - - '[>#]' + - "[>#]" command: - - terminal length 0 - - show version + - terminal length 0 + - show version diff --git a/galaxy.yml b/galaxy.yml index 1c1387d9f..0536f1472 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ authors: - Ansible Network Community (ansible-network) dependencies: - "ansible.utils": ">=2.7.0" + "ansible.utils": ">=3.0.0" license_file: LICENSE name: netcommon namespace: ansible @@ -13,4 +13,4 @@ readme: README.md repository: https://github.com/ansible-collections/ansible.netcommon issues: https://github.com/ansible-collections/ansible.netcommon/issues tags: [networking, security, cloud, network_cli, netconf, httpapi, grpc] -version: 5.3.0 +version: 7.1.0 diff --git a/meta/runtime.yml b/meta/runtime.yml index 757189034..be0d8febb 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -1,5 +1,5 @@ --- -requires_ansible: ">=2.9.10" +requires_ansible: ">=2.15.0" plugin_routing: action: grpc_config: diff --git a/plugins/action/net_put.py b/plugins/action/net_put.py index e3db728bc..9ec7c2610 100644 --- a/plugins/action/net_put.py +++ b/plugins/action/net_put.py @@ -150,7 +150,7 @@ def _handle_existing_file(self, conn, source, dest, proto, timeout): ) except ConnectionError as exc: error = to_text(exc) - if error.endswith("No such file or directory"): + if error.endswith("No such file or directory") or "File doesn't exist" in error: if os.path.exists(tmp_source_file): os.remove(tmp_source_file) return True diff --git a/plugins/action/netconf.py b/plugins/action/netconf.py index d1b17f374..8e23d4351 100644 --- a/plugins/action/netconf.py +++ b/plugins/action/netconf.py @@ -8,9 +8,6 @@ __metaclass__ = type -import copy -import sys - from ansible.utils.display import Display from ansible_collections.ansible.netcommon.plugins.action.network import ( @@ -23,86 +20,18 @@ class ActionModule(ActionNetworkModule): def run(self, tmp=None, task_vars=None): - del tmp # tmp no longer has any effect - module_name = self._task.action.split(".")[-1] self._config_module = True if module_name == "netconf_config" else False persistent_connection = self._play_context.connection.split(".")[-1] warnings = [] - if persistent_connection not in ["netconf", "local"] and module_name == "netconf_config": - return { - "failed": True, - "msg": "Connection type %s is not valid for netconf_config module. " - "Valid connection type is netconf or local (deprecated)" - % self._play_context.connection, - } - elif persistent_connection not in ["netconf"] and module_name != "netconf_config": + if persistent_connection != "netconf": return { "failed": True, "msg": "Connection type %s is not valid for %s module. " "Valid connection type is netconf." % (self._play_context.connection, module_name), } - if self._play_context.connection == "local" and module_name == "netconf_config": - args = self._task.args - pc = copy.deepcopy(self._play_context) - pc.connection = "ansible.netcommon.netconf" - pc.port = int(args.get("port") or self._play_context.port or 830) - - pc.remote_user = args.get("username") or self._play_context.connection_user - pc.password = args.get("password") or self._play_context.password - pc.private_key_file = args.get("ssh_keyfile") or self._play_context.private_key_file - - connection = self._shared_loader_obj.connection_loader.get( - "ansible.netcommon.persistent", - pc, - sys.stdin, - task_uuid=self._task._uuid, - ) - - # TODO: Remove below code after ansible minimal is cut out - if connection is None: - pc.connection = "netconf" - connection = self._shared_loader_obj.connection_loader.get( - "persistent", pc, sys.stdin, task_uuid=self._task._uuid - ) - - display.vvv( - "using connection plugin %s (was local)" % pc.connection, - pc.remote_addr, - ) - - timeout = args.get("timeout") - command_timeout = ( - int(timeout) if timeout else connection.get_option("persistent_command_timeout") - ) - connection.set_options( - direct={ - "persistent_command_timeout": command_timeout, - "look_for_keys": args.get("look_for_keys"), - "hostkey_verify": args.get("hostkey_verify"), - "allow_agent": args.get("allow_agent"), - } - ) - - socket_path = connection.run() - display.vvvv("socket_path: %s" % socket_path, pc.remote_addr) - if not socket_path: - return { - "failed": True, - "msg": "unable to open shell. Please see: " - + "https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell", - } - - task_vars["ansible_socket"] = socket_path - warnings.append( - [ - "connection local support for this module is deprecated and will be removed in version 2.14, use connection %s" - % pc.connection - ] - ) - result = super(ActionModule, self).run(task_vars=task_vars) if warnings: if "warnings" in result: diff --git a/plugins/connection/libssh.py b/plugins/connection/libssh.py index a5e9d9817..9fffb3d3f 100644 --- a/plugins/connection/libssh.py +++ b/plugins/connection/libssh.py @@ -396,7 +396,7 @@ def _connect_uncached(self): self.ssh = Session() - if self._play_context.verbosity > 3: + if display.verbosity > 3: self.ssh.set_log_level(logging.INFO) self.keyfile = os.path.expanduser("~/.ssh/known_hosts") @@ -623,7 +623,10 @@ def fetch_file(self, in_path, out_path, proto="sftp"): elif proto == "scp": scp = self.ssh.scp() try: - scp.get(out_path, in_path) + # this abruptly closes the connection when + # scp.get fails only when the file is not there + # it works fine if the file is actually present + scp.get(in_path, out_path) except LibsshSCPException as exc: raise AnsibleError("Error transferring file from %s: %s" % (out_path, to_text(exc))) else: diff --git a/plugins/connection/network_cli.py b/plugins/connection/network_cli.py index 5063b43e0..da4c1d4de 100644 --- a/plugins/connection/network_cli.py +++ b/plugins/connection/network_cli.py @@ -318,6 +318,7 @@ from ansible.module_utils.six.moves import cPickle from ansible.playbook.play_context import PlayContext from ansible.plugins.loader import cache_loader, cliconf_loader, connection_loader, terminal_loader +from ansible.utils.display import Display from ansible_collections.ansible.netcommon.plugins.connection.libssh import HAS_PYLIBSSH from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list @@ -332,6 +333,7 @@ HAS_SCP = True except ImportError: HAS_SCP = False +display = Display() def ensure_connect(func): @@ -488,10 +490,8 @@ def get_prompt(self): return self._matched_prompt def exec_command(self, cmd, in_data=None, sudoable=True): - # this try..except block is just to handle the transition to supporting - # network_cli as a toplevel connection. Once connection=local is gone, - # this block can be removed as well and all calls passed directly to - # the local connection + # not accessible code alert can be taken out at around 01-01-2027, + # when connection local is removed if self._ssh_shell: try: cmd = json.loads(to_text(cmd, errors="surrogate_or_strict")) @@ -597,7 +597,7 @@ def _connect(self): """ Connects to the remote device and starts the terminal """ - if self._play_context.verbosity > 3: + if display.verbosity > 3: logging.getLogger(self.ssh_type).setLevel(logging.DEBUG) self.queue_message("vvvv", "invoked shell using ssh_type: %s" % self.ssh_type) diff --git a/plugins/connection/persistent.py b/plugins/connection/persistent.py index 549299f3f..0982cdebc 100644 --- a/plugins/connection/persistent.py +++ b/plugins/connection/persistent.py @@ -72,6 +72,11 @@ def run(self): "starting connection from persistent connection plugin", host=self._play_context.remote_addr, ) + display.deprecated( + msg="support for connection local has been deprecated", + date="2027-01-01", + collection_name="ansible.netcommon", + ) variables = {"ansible_command_timeout": self.get_option("persistent_command_timeout")} socket_path = start_connection(self._play_context, variables, self._task_uuid) display.vvvv( diff --git a/plugins/filter/parse_cli_textfsm.py b/plugins/filter/parse_cli_textfsm.py index 8b7a9612f..1319c0c8a 100644 --- a/plugins/filter/parse_cli_textfsm.py +++ b/plugins/filter/parse_cli_textfsm.py @@ -52,14 +52,14 @@ device_neighbors: "{{ lldp_output.stdout[0] | parse_cli_textfsm('~/ntc-templates/templates/cisco_ios_show_lldp_neighbors.textfsm') }}" - name: "Debug" - ansible.builtindebug: + ansible.builtin.debug: msg: "{{ device_neighbors }}" # Task Output # ----------- # # TASK [Fetch command output] -# ok: [rtr-2] +# ok: [rtr-1] # TASK [Invoke parse_cli_textfsm] # ok: [rtr-1] diff --git a/plugins/filter/pop_ace.py b/plugins/filter/pop_ace.py index 27d4d8469..eefe07ca6 100644 --- a/plugins/filter/pop_ace.py +++ b/plugins/filter/pop_ace.py @@ -84,7 +84,7 @@ """ EXAMPLES = r""" -##Playbook with filter plugin example +## Playbook with filter plugin example vars: filter_options: match_all: true @@ -219,7 +219,7 @@ ansible.builtin.debug: msg: "{{ acls_data | ansible.netcommon.pop_ace(filter_options=filter_options, match_criteria=match_criteria) }}" -##Output +## Output # PLAY [Filter plugin example pop_ace] ****************************************************************************************************************** # TASK [Remove ace entries from a provided data] *********************************************************************************************************** @@ -353,8 +353,8 @@ # afi: ipv6 -##Playbook with workflow example -tasks: +## Playbook with workflow example +_tasks: - name: Gather ACLs config from device existing ACLs config cisco.ios.ios_acls: state: gathered @@ -379,8 +379,7 @@ state: overridden config: "{{ clean_acls['clean_acls']['acls'] | from_yaml }}" - -##Output +## Output # PLAYBOOK: pop_ace_example.yml *********************************************** @@ -989,7 +988,6 @@ # - ip access-list extended 110 # - no 10 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 traceroute dscp ef ttl eq 10 # - no ip access-list extended test - """ from ansible.errors import AnsibleFilterError diff --git a/plugins/module_utils/network/common/network.py b/plugins/module_utils/network/common/network.py index 9b5a2fc66..8d9316eb0 100644 --- a/plugins/module_utils/network/common/network.py +++ b/plugins/module_utils/network/common/network.py @@ -202,8 +202,8 @@ def get_resource_connection(module): if network_api == "netconf": module._connection = NetconfConnection(module._socket_path) elif network_api == "local": - # This isn't supported, but we shouldn't fail here. - # Set the connection to a fake connection so it fails sensibly. + # not accessible code alert can be taken out at around 01-01-2027, + # when connection local is removed module._connection = LocalResourceConnection(module) else: module._connection = Connection(module._socket_path) diff --git a/plugins/module_utils/network/common/rm_base/resource_module.py b/plugins/module_utils/network/common/rm_base/resource_module.py index e3002572b..7fd97c132 100644 --- a/plugins/module_utils/network/common/rm_base/resource_module.py +++ b/plugins/module_utils/network/common/rm_base/resource_module.py @@ -149,5 +149,5 @@ def run_commands(self): """Send commands to the device""" if self.commands and self.state in self.ACTION_STATES: if not self._module.check_mode: - self._connection.edit_config(self.commands) + self._connection.edit_config(candidate=self.commands) self.changed = True diff --git a/plugins/module_utils/network/common/utils.py b/plugins/module_utils/network/common/utils.py index 6f7a50b5d..5576e7edf 100644 --- a/plugins/module_utils/network/common/utils.py +++ b/plugins/module_utils/network/common/utils.py @@ -24,6 +24,7 @@ from copy import deepcopy from functools import reduce # forward compatibility for Python 3 +from io import StringIO from itertools import chain from ansible.module_utils import basic @@ -719,7 +720,7 @@ def extract_argspec(doc_obj, argpsec): # TODO: Support extends_documentation_fragment def convert_doc_to_ansible_module_kwargs(doc): - doc_obj = yaml.load(str(doc), SafeLoader) + doc_obj = yaml.load(StringIO(doc), SafeLoader) argspec = {} spec = {} extract_argspec(doc_obj, argspec) diff --git a/plugins/modules/cli_backup.py b/plugins/modules/cli_backup.py index b793fb204..033743b8d 100644 --- a/plugins/modules/cli_backup.py +++ b/plugins/modules/cli_backup.py @@ -13,7 +13,7 @@ DOCUMENTATION = """ module: cli_backup -author: Kate Case (@Qalthos) +author: Katherine Case (@Qalthos) short_description: Back up device configuration from network devices over network_cli description: - This module provides platform agnostic way of backing up text based configuration from diff --git a/plugins/modules/cli_command.py b/plugins/modules/cli_command.py index e8505226b..a4f47c858 100644 --- a/plugins/modules/cli_command.py +++ b/plugins/modules/cli_command.py @@ -11,7 +11,7 @@ DOCUMENTATION = """ module: cli_command -author: Nathaniel Case (@Qalthos) +author: Katherine Case (@Qalthos) short_description: Run a cli command on cli-based network devices description: - Sends a command to a network device and returns the result read from the device. @@ -79,7 +79,7 @@ ansible.netcommon.cli_command: command: commit replace prompt: This commit will replace or remove the entire running configuration - answer: yes + answer: "yes" - name: run command expecting user confirmation ansible.netcommon.cli_command: @@ -90,27 +90,38 @@ - name: run config mode command and handle prompt/answer ansible.netcommon.cli_command: - command: '{{ item }}' + command: "{{ item }}" prompt: - - Exit with uncommitted changes + - Exit with uncommitted changes answer: y loop: - - configure - - set system syslog file test any any - - exit + - configure + - set system syslog file test any any + - exit - name: multiple prompt, multiple answer (mandatory check for all prompts) ansible.netcommon.cli_command: command: copy sftp sftp://user@host//user/test.img check_all: true prompt: - - Confirm download operation - - Password - - Do you want to change that to the standby image + - Confirm download operation + - Password + - Do you want to change that to the standby image answer: - - y - - - - y + - y + - + - y + +- name: Simple regexp match for multiple prompt, multiple answer(mandatory check for all prompts) + ansible.netcommon.cli_command: + command: reload in 5 + check_all: true + prompt: + - Save\\? + - confirm + answer: + - n + - y """ RETURN = """ diff --git a/plugins/modules/cli_config.py b/plugins/modules/cli_config.py index 97190f539..abc5f84e4 100644 --- a/plugins/modules/cli_config.py +++ b/plugins/modules/cli_config.py @@ -167,7 +167,7 @@ - name: configure device with config with defaults enabled ansible.netcommon.cli_config: config: "{{ lookup('template', 'basic/config.j2') }}" - defaults: yes + defaults: "yes" - name: Use diff_match ansible.netcommon.cli_config: @@ -190,7 +190,7 @@ - name: configurable backup path ansible.netcommon.cli_config: config: "{{ lookup('template', 'basic/config.j2') }}" - backup: yes + backup: "yes" backup_options: filename: backup.cfg dir_path: /home/user diff --git a/plugins/modules/cli_restore.py b/plugins/modules/cli_restore.py new file mode 100644 index 000000000..c5ed62d64 --- /dev/null +++ b/plugins/modules/cli_restore.py @@ -0,0 +1,128 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2024, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: cli_restore +author: Sagar Paul (@KB-perByte) +short_description: Restore device configuration to network devices over network_cli +description: +- This module provides platform agnostic way of restore text based configuration to + network devices over network_cli connection plugin. +- The module uses the platforms `config replace` commands to restore + backup configuration that is already copied over to the appliance. +version_added: 6.1.0 +extends_documentation_fragment: +- ansible.netcommon.network_agnostic +options: + filename: + description: + - Filename of the backup file, present in the appliance where the restore operation + is to be performed. Check appliance for the configuration backup file name. + type: str + path: + description: + - The location in the target appliance where the file containing the backup exists. + The path and the filename together create the input to the config replace command, + - For an IOSXE appliance the path pattern is flash:// + type: str +""" + +EXAMPLES = """ +- name: Restore IOS-XE configuration + ansible.netcommon.cli_restore: + filename: backupDday.cfg + path: flash:// + +# Command fired +# ------------- +# config replace flash://backupDday.cfg force + +# Task Output +# ----------- +# +# ok: [BATMON] => changed=false +# __restore__: |- +# The rollback configlet from the last pass is listed below: +# ******** +# !List of Rollback Commands: +# Building configuration... +# Current configuration : 3781 bytes +# end +# ******** +# +# +# Rollback aborted after 5 passes +# The following commands are failed to apply to the IOS image. +# ******** +# Building configuration... +# Current configuration : 3781 bytes +# ******** +# invocation: +# module_args: +# filename: backupDday.cfg +""" + +RETURN = """ +""" + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection, ConnectionError + + +def validate_args(module, device_operations): + """validate param if it is supported on the platform""" + feature_list = [] + + for feature in feature_list: + if module.params[feature]: + supports_feature = device_operations.get(f"supports_{feature}") + if supports_feature is None: + module.fail_json( + msg=f"This platform does not specify whether {feature} is supported or not. " + "Please report an issue against this platform's cliconf plugin." + ) + elif not supports_feature: + module.fail_json(msg=f"Option {feature} is not supported on this platform") + + +def main(): + """main entry point for execution""" + argument_spec = dict( + filename=dict(type="str"), + path=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + ) + + result = {"changed": False} + connection = Connection(module._socket_path) + try: + running = connection.restore( + filename=module.params["filename"], + path=module.params["path"], + ) + except ConnectionError as exc: + if exc.code == -32601: # Method not found + msg = "This platform is not supported with cli_restore. Please report an issue against this platform's cliconf plugin." + module.fail_json(msg, code=exc.code) + else: + module.fail_json(msg=to_text(exc, errors="surrogate_then_replace").strip()) + result["__restore__"] = running + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/grpc_config.py b/plugins/modules/grpc_config.py index fbaa4deff..b289db981 100644 --- a/plugins/modules/grpc_config.py +++ b/plugins/modules/grpc_config.py @@ -78,39 +78,38 @@ """ EXAMPLES = """ - - name: Merge static route config - ansible.netcommon.grpc_config: - config: - Cisco-IOS-XR-ip-static-cfg:router-static: - default-vrf: - address-family: - vrfipv4: - vrf-unicast: - vrf-prefixes: - vrf-prefix: - - prefix: "1.2.3.6" - prefix-length: 32 - vrf-route: - vrf-next-hop-table: - vrf-next-hop-next-hop-address: - - next-hop-address: "10.0.2.2" - - state: merged - - - name: Merge bgp config - ansible.netcommon.grpc_config: - config: "{{ lookup('file', 'bgp.json') }}" - state: merged - - - name: Find diff - diff: True - ansible.netcommon.grpc_config: - config: "{{ lookup('file', 'bgp_start.yml') }}" - state: merged - - - name: Backup running config - ansible.netcommon.grpc_config: - backup: yes +- name: Merge static route config + ansible.netcommon.grpc_config: + config: + Cisco-IOS-XR-ip-static-cfg:router-static: + default-vrf: + address-family: + vrfipv4: + vrf-unicast: + vrf-prefixes: + vrf-prefix: + - prefix: "1.2.3.6" + prefix-length: 32 + vrf-route: + vrf-next-hop-table: + vrf-next-hop-next-hop-address: + - next-hop-address: "10.0.2.2" + state: merged + +- name: Merge bgp config + ansible.netcommon.grpc_config: + config: "{{ lookup('file', 'bgp.json') }}" + state: merged + +- name: Find diff + diff: true + ansible.netcommon.grpc_config: + config: "{{ lookup('file', 'bgp_start.yml') }}" + state: merged + +- name: Backup running config + ansible.netcommon.grpc_config: + backup: true """ RETURN = """ diff --git a/plugins/modules/grpc_get.py b/plugins/modules/grpc_get.py index 09547b6b0..528d0ce6a 100644 --- a/plugins/modules/grpc_get.py +++ b/plugins/modules/grpc_get.py @@ -63,15 +63,15 @@ """ EXAMPLES = """ - - name: Get bgp configuration data - grpc_get: - section: - Cisco-IOS-XR-ip-static-cfg:router-static: - - null - - name: run cli command - grpc_get: - command: 'show version' - display: text +- name: Get bgp configuration data + grpc_get: + section: + Cisco-IOS-XR-ip-static-cfg:router-static: + - null +- name: run cli command + grpc_get: + command: "show version" + display: text """ RETURN = """ diff --git a/plugins/modules/netconf_config.py b/plugins/modules/netconf_config.py index 23fe32b00..87c1c9c01 100644 --- a/plugins/modules/netconf_config.py +++ b/plugins/modules/netconf_config.py @@ -13,203 +13,204 @@ DOCUMENTATION = """ module: netconf_config author: -- Leandro Lisboa Penz (@lpenz) -- Ganesh Nalawade (@ganeshrn) + - Leandro Lisboa Penz (@lpenz) + - Ganesh Nalawade (@ganeshrn) short_description: netconf device configuration description: -- Netconf is a network management protocol developed and standardized by the IETF. - It is documented in RFC 6241. -- This module allows the user to send a configuration XML file to a netconf device, - and detects if there was a configuration change. + - Netconf is a network management protocol developed and standardized by the IETF. + It is documented in RFC 6241. + - This module allows the user to send a configuration XML file to a netconf device, + and detects if there was a configuration change. version_added: 1.0.0 extends_documentation_fragment: -- ansible.netcommon.network_agnostic + - ansible.netcommon.network_agnostic options: content: description: - - The configuration data as defined by the device's data models, the value can - be either in xml string format or text format or python dictionary representation of JSON format. - - In case of json string format it will be converted to the corresponding xml string using - xmltodict library before pushing onto the remote host. - - In case the value of this option isn I(text) format the format should be supported by remote Netconf server. - - If the value of C(content) option is in I(xml) format in that case the xml value should - have I(config) as root tag. + - The configuration data as defined by the device's data models, the value can + be either in xml string format or text format or python dictionary representation of JSON format. + - In case of json string format it will be converted to the corresponding xml string using + xmltodict library before pushing onto the remote host. + - In case the value of this option isn I(text) format the format should be supported by remote Netconf server. + - If the value of C(content) option is in I(xml) format in that case the xml value should + have I(config) as root tag. type: raw aliases: - - xml + - xml target: - description: Name of the configuration datastore to be edited. - auto, uses candidate + description: + Name of the configuration datastore to be edited. - auto, uses candidate and fallback to running - candidate, edit datastore and then commit - running, edit datastore directly default: auto type: str choices: - - auto - - candidate - - running + - auto + - candidate + - running aliases: - - datastore + - datastore source_datastore: description: - - Name of the configuration datastore to use as the source to copy the configuration - to the datastore mentioned by C(target) option. The values can be either I(running), - I(candidate), I(startup) or a remote URL + - Name of the configuration datastore to use as the source to copy the configuration + to the datastore mentioned by C(target) option. The values can be either I(running), + I(candidate), I(startup) or a remote URL type: str aliases: - - source + - source format: description: - - The format of the configuration provided as value of C(content). - - In case of json string format it will be converted to the corresponding xml string using - xmltodict library before pushing onto the remote host. - - In case of I(text) format of the configuration should be supported by remote Netconf server. - - If the value of C(format) options is not given it tries to guess the data format of - C(content) option as one of I(xml) or I(json) or I(text). - - If the data format is not identified it is set to I(xml) by default. + - The format of the configuration provided as value of C(content). + - In case of json string format it will be converted to the corresponding xml string using + xmltodict library before pushing onto the remote host. + - In case of I(text) format of the configuration should be supported by remote Netconf server. + - If the value of C(format) options is not given it tries to guess the data format of + C(content) option as one of I(xml) or I(json) or I(text). + - If the data format is not identified it is set to I(xml) by default. type: str choices: - - xml - - text - - json + - xml + - text + - json lock: description: - - Instructs the module to explicitly lock the datastore specified as C(target). - By setting the option value I(always) is will explicitly lock the datastore - mentioned in C(target) option. It the value is I(never) it will not lock the - C(target) datastore. The value I(if-supported) lock the C(target) datastore - only if it is supported by the remote Netconf server. + - Instructs the module to explicitly lock the datastore specified as C(target). + By setting the option value I(always) is will explicitly lock the datastore + mentioned in C(target) option. It the value is I(never) it will not lock the + C(target) datastore. The value I(if-supported) lock the C(target) datastore + only if it is supported by the remote Netconf server. type: str default: always choices: - - never - - always - - if-supported + - never + - always + - if-supported default_operation: description: - - The default operation for rpc, valid values are I(merge), I(replace) - and I(none). If the default value is merge, the configuration data in the C(content) - option is merged at the corresponding level in the C(target) datastore. If the - value is replace the data in the C(content) option completely replaces the configuration - in the C(target) datastore. If the value is none the C(target) datastore is - unaffected by the configuration in the config option, unless and until the incoming - configuration data uses the C(operation) operation to request a different operation. + - The default operation for rpc, valid values are I(merge), I(replace) + and I(none). If the default value is merge, the configuration data in the C(content) + option is merged at the corresponding level in the C(target) datastore. If the + value is replace the data in the C(content) option completely replaces the configuration + in the C(target) datastore. If the value is none the C(target) datastore is + unaffected by the configuration in the config option, unless and until the incoming + configuration data uses the C(operation) operation to request a different operation. type: str choices: - - merge - - replace - - none + - merge + - replace + - none confirm: description: - - This argument will configure a timeout value for the commit to be confirmed - before it is automatically rolled back. If the C(confirm_commit) argument is - set to False, this argument is silently ignored. If the value of this argument - is set to 0, the commit is confirmed immediately. The remote host MUST support - :candidate and :confirmed-commit capability for this option to . + - This argument will configure a timeout value for the commit to be confirmed + before it is automatically rolled back. If the C(confirm_commit) argument is + set to False, this argument is silently ignored. If the value of this argument + is set to 0, the commit is confirmed immediately. The remote host MUST support + :candidate and :confirmed-commit capability for this option to . type: int default: 0 confirm_commit: description: - - This argument will execute commit operation on remote device. It can be used - to confirm a previous commit. + - This argument will execute commit operation on remote device. It can be used + to confirm a previous commit. type: bool default: no error_option: description: - - This option controls the netconf server action after an error occurs while editing - the configuration. - - If I(error_option=stop-on-error), abort the config edit on first error. - - If I(error_option=continue-on-error), continue to process configuration data - on error. The error is recorded and negative response is generated if any errors - occur. - - If I(error_option=rollback-on-error), rollback to the original configuration - if any error occurs. This requires the remote Netconf server to support the - I(error_option=rollback-on-error) capability. + - This option controls the netconf server action after an error occurs while editing + the configuration. + - If I(error_option=stop-on-error), abort the config edit on first error. + - If I(error_option=continue-on-error), continue to process configuration data + on error. The error is recorded and negative response is generated if any errors + occur. + - If I(error_option=rollback-on-error), rollback to the original configuration + if any error occurs. This requires the remote Netconf server to support the + I(error_option=rollback-on-error) capability. default: stop-on-error type: str choices: - - stop-on-error - - continue-on-error - - rollback-on-error + - stop-on-error + - continue-on-error + - rollback-on-error save: description: - - The C(save) argument instructs the module to save the configuration in C(target) - datastore to the startup-config if changed and if :startup capability is supported - by Netconf server. + - The C(save) argument instructs the module to save the configuration in C(target) + datastore to the startup-config if changed and if :startup capability is supported + by Netconf server. default: false type: bool backup: description: - - This argument will cause the module to create a full backup of the current C(running-config) - from the remote device before any changes are made. If the C(backup_options) - value is not given, the backup file is written to the C(backup) folder in the - playbook root directory or role root directory, if playbook is part of an ansible - role. If the directory does not exist, it is created. + - This argument will cause the module to create a full backup of the current C(running-config) + from the remote device before any changes are made. If the C(backup_options) + value is not given, the backup file is written to the C(backup) folder in the + playbook root directory or role root directory, if playbook is part of an ansible + role. If the directory does not exist, it is created. type: bool default: no delete: description: - - It instructs the module to delete the configuration from value mentioned in - C(target) datastore. + - It instructs the module to delete the configuration from value mentioned in + C(target) datastore. type: bool default: no commit: description: - - This boolean flag controls if the configuration changes should be committed - or not after editing the candidate datastore. This option is supported only - if remote Netconf server supports :candidate capability. If the value is set - to I(False) commit won't be issued after edit-config operation and user needs - to handle commit or discard-changes explicitly. + - This boolean flag controls if the configuration changes should be committed + or not after editing the candidate datastore. This option is supported only + if remote Netconf server supports :candidate capability. If the value is set + to I(False) commit won't be issued after edit-config operation and user needs + to handle commit or discard-changes explicitly. type: bool default: true validate: description: - - This boolean flag if set validates the content of datastore given in C(target) - option. For this option to work remote Netconf server should support :validate - capability. + - This boolean flag if set validates the content of datastore given in C(target) + option. For this option to work remote Netconf server should support :validate + capability. type: bool default: false backup_options: description: - - This is a dict object containing configurable options related to backup file - path. The value of this option is read only when C(backup) is set to I(yes), - if C(backup) is set to I(no) this option will be silently ignored. + - This is a dict object containing configurable options related to backup file + path. The value of this option is read only when C(backup) is set to I(yes), + if C(backup) is set to I(no) this option will be silently ignored. suboptions: filename: description: - - The filename to be used to store the backup configuration. If the filename - is not given it will be generated based on the hostname, current time and - date in format defined by _config.@ + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and + date in format defined by _config.@ type: str dir_path: description: - - This option provides the path ending with directory name in which the backup - configuration file will be stored. If the directory does not exist it will - be first created and the filename is either the value of C(filename) or - default filename as described in C(filename) options description. If the - path value is not given in that case a I(backup) directory will be created - in the current working directory and backup configuration will be copied - in C(filename) within I(backup) directory. + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will + be first created and the filename is either the value of C(filename) or + default filename as described in C(filename) options description. If the + path value is not given in that case a I(backup) directory will be created + in the current working directory and backup configuration will be copied + in C(filename) within I(backup) directory. type: path type: dict get_filter: description: - - This argument specifies the XML string which acts as a filter to restrict the - portions of the data retrieved from the remote device when comparing the before - and after state of the device following calls to edit_config. When not specified, - the entire configuration or state data is returned for comparison depending - on the value of C(source) option. The C(get_filter) value can be either XML - string or XPath or JSON string or native python dictionary, if the filter is - in XPath format the NETCONF server running on remote host should support xpath - capability else it will result in an error. + - This argument specifies the XML string which acts as a filter to restrict the + portions of the data retrieved from the remote device when comparing the before + and after state of the device following calls to edit_config. When not specified, + the entire configuration or state data is returned for comparison depending + on the value of C(source) option. The C(get_filter) value can be either XML + string or XPath or JSON string or native python dictionary, if the filter is + in XPath format the NETCONF server running on remote host should support xpath + capability else it will result in an error. type: raw requirements: -- ncclient + - ncclient notes: -- This module requires the netconf system service be enabled on the remote device - being managed. -- This module supports devices with and without the candidate and confirmed-commit - capabilities. It will always use the safer feature. -- This module supports the use of connection=netconf + - This module requires the netconf system service be enabled on the remote device + being managed. + - This module supports devices with and without the candidate and confirmed-commit + capabilities. It will always use the safer feature. + - This module supports the use of connection=netconf """ EXAMPLES = """ @@ -248,14 +249,14 @@ - name: configure interface while providing different private key file path (for connection=netconf) ansible.netcommon.netconf_config: - backup: yes + backup: true register: backup_junos_location vars: ansible_private_key_file: /home/admin/.ssh/newprivatekeyfile - name: configurable backup path ansible.netcommon.netconf_config: - backup: yes + backup: true backup_options: filename: backup.cfg dir_path: /home/user @@ -263,49 +264,54 @@ - name: "configure using direct native format configuration (cisco iosxr)" ansible.netcommon.netconf_config: format: json - content: { - "config": { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": { - "active": "act", - "description": "test for ansible Loopback999", - "interface-name": "Loopback999" - } - } - } - } - get_filter: { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null - } - } + content: + { + "config": + { + "interface-configurations": + { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": + { + "active": "act", + "description": "test for ansible Loopback999", + "interface-name": "Loopback999", + }, + }, + }, + } + get_filter: + { + "interface-configurations": + { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null, + }, + } - name: "configure using json string format configuration (cisco iosxr)" ansible.netcommon.netconf_config: format: json content: | - { - "config": { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": { - "active": "act", - "description": "test for ansible Loopback999", - "interface-name": "Loopback999" - } - } - } - } - get_filter: | - { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null + { + "config": { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": { + "active": "act", + "description": "test for ansible Loopback999", + "interface-name": "Loopback999" } } - + } + } + get_filter: | + { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null + } + } # Make a round-trip interface description change, diff the before and after # this demonstrates the use of the native display format and several utilities @@ -329,8 +335,8 @@ - name: Update the description ansible.utils.update_fact: updates: - - path: pre.output.data.interfaces.interface.config.description - value: "Configured by ansible {{ 100 | random }}" + - path: pre.output.data.interfaces.interface.config.description + value: "Configured by ansible {{ 100 | random }}" register: updated - name: Apply the new configuration @@ -350,7 +356,6 @@ ansible.utils.fact_diff: before: "{{ pre.output.data|ansible.utils.to_paths }}" after: "{{ post.output.data|ansible.utils.to_paths }}" - # TASK [Show the differences between the pre and post configurations] ******** # --- before # +++ after @@ -368,28 +373,34 @@ # "interfaces.interface.config.enabled": "true", # "interfaces.interface.config.mtu": "0", # "interfaces.interface.config.name": "Ethernet2", - """ RETURN = """ server_capabilities: - description: list of capabilities of the server - returned: success - type: list - sample: ['urn:ietf:params:netconf:base:1.1','urn:ietf:params:netconf:capability:confirmed-commit:1.0','urn:ietf:params:netconf:capability:candidate:1.0'] + description: list of capabilities of the server + returned: success + type: list + sample: + [ + "urn:ietf:params:netconf:base:1.1", + "urn:ietf:params:netconf:capability:confirmed-commit:1.0", + "urn:ietf:params:netconf:capability:candidate:1.0", + ] backup_path: description: The full path to the backup file returned: when backup is yes type: str sample: /playbooks/ansible/backup/config.2016-07-16@22:28:34 diff: - description: If --diff option in enabled while running, the before and after configuration change are - returned as part of before and after key. + description: + If --diff option in enabled while running, the before and after configuration change are + returned as part of before and after key. returned: when diff is enabled type: dict sample: "after": "\n\n\n17.3R1.10...<--snip-->" "before": "\n\n\n 17.3R1.10...<--snip-->" + """ from ansible.module_utils._text import to_text diff --git a/plugins/modules/netconf_get.py b/plugins/modules/netconf_get.py index 590cc7a9a..1795bf54e 100644 --- a/plugins/modules/netconf_get.py +++ b/plugins/modules/netconf_get.py @@ -14,77 +14,77 @@ DOCUMENTATION = """ module: netconf_get author: -- Ganesh Nalawade (@ganeshrn) -- Sven Wisotzky (@wisotzky) + - Ganesh Nalawade (@ganeshrn) + - Sven Wisotzky (@wisotzky) short_description: Fetch configuration/state data from NETCONF enabled network devices. description: -- NETCONF is a network management protocol developed and standardized by the IETF. - It is documented in RFC 6241. -- This module allows the user to fetch configuration and state data from NETCONF enabled - network devices. + - NETCONF is a network management protocol developed and standardized by the IETF. + It is documented in RFC 6241. + - This module allows the user to fetch configuration and state data from NETCONF enabled + network devices. version_added: 1.0.0 extends_documentation_fragment: -- ansible.netcommon.network_agnostic + - ansible.netcommon.network_agnostic options: source: description: - - This argument specifies the datastore from which configuration data should be - fetched. Valid values are I(running), I(candidate) and I(startup). If the C(source) - value is not set both configuration and state information are returned in response - from running datastore. + - This argument specifies the datastore from which configuration data should be + fetched. Valid values are I(running), I(candidate) and I(startup). If the C(source) + value is not set both configuration and state information are returned in response + from running datastore. type: str choices: - - running - - candidate - - startup + - running + - candidate + - startup filter: description: - - This argument specifies the string which acts as a filter to restrict the - portions of the data to be are retrieved from the remote device. If this option - is not specified entire configuration or state data is returned in result depending - on the value of C(source) option. The C(filter) value can be either XML string - or XPath or JSON string or native python dictionary, if the filter is in XPath - format the NETCONF server running on remote host should support xpath capability - else it will result in an error. If the filter is in JSON format the xmltodict library - should be installed on the control node for JSON to XML conversion. + - This argument specifies the string which acts as a filter to restrict the + portions of the data to be are retrieved from the remote device. If this option + is not specified entire configuration or state data is returned in result depending + on the value of C(source) option. The C(filter) value can be either XML string + or XPath or JSON string or native python dictionary, if the filter is in XPath + format the NETCONF server running on remote host should support xpath capability + else it will result in an error. If the filter is in JSON format the xmltodict library + should be installed on the control node for JSON to XML conversion. type: raw display: description: - - Encoding scheme to use when serializing output from the device. The option I(json) - will serialize the output as JSON data. If the option value is I(json) it requires - jxmlease to be installed on control node. The option I(pretty) is similar to - received XML response but is using human readable format (spaces, new lines). - The option value I(xml) is similar to received XML response but removes all - XML namespaces. + - Encoding scheme to use when serializing output from the device. The option I(json) + will serialize the output as JSON data. If the option value is I(json) it requires + jxmlease to be installed on control node. The option I(pretty) is similar to + received XML response but is using human readable format (spaces, new lines). + The option value I(xml) is similar to received XML response but removes all + XML namespaces. type: str choices: - - json - - pretty - - xml - - native + - json + - pretty + - xml + - native lock: description: - - Instructs the module to explicitly lock the datastore specified as C(source). - If no I(source) is defined, the I(running) datastore will be locked. By setting - the option value I(always) is will explicitly lock the datastore mentioned in - C(source) option. By setting the option value I(never) it will not lock the - C(source) datastore. The value I(if-supported) allows better interworking with - NETCONF servers, which do not support the (un)lock operation for all supported - datastores. + - Instructs the module to explicitly lock the datastore specified as C(source). + If no I(source) is defined, the I(running) datastore will be locked. By setting + the option value I(always) is will explicitly lock the datastore mentioned in + C(source) option. By setting the option value I(never) it will not lock the + C(source) datastore. The value I(if-supported) allows better interworking with + NETCONF servers, which do not support the (un)lock operation for all supported + datastores. type: str default: never choices: - - never - - always - - if-supported + - never + - always + - if-supported requirements: -- ncclient (>=v0.5.2) -- jxmlease (for display=json) -- xmltodict (for display=native) + - ncclient (>=v0.5.2) + - jxmlease (for display=json) + - xmltodict (for display=native) notes: -- This module requires the NETCONF system service be enabled on the remote device - being managed. -- This module supports the use of connection=netconf + - This module requires the NETCONF system service be enabled on the remote device + being managed. + - This module supports the use of connection=netconf """ EXAMPLES = """ @@ -139,12 +139,12 @@ - name: "get configuration with json filter string and native output (using xmltodict)" netconf_get: filter: | - { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null - } - } + { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null + } + } display: native - name: Define the Cisco IOSXR interface filter @@ -162,16 +162,17 @@ - name: "get configuration with direct native filter type" ansible.netcommon.netconf_get: - filter: { - "interface-configurations": { + filter: + { + "interface-configurations": + { "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null + "interface-configuration": null, + }, } - } display: native register: result - # Make a round-trip interface description change, diff the before and after # this demonstrates the use of the native display format and several utilities # from the ansible.utils collection @@ -194,8 +195,8 @@ - name: Update the description ansible.utils.update_fact: updates: - - path: pre.output.data.interfaces.interface.config.description - value: "Configured by ansible {{ 100 | random }}" + - path: pre.output.data.interfaces.interface.config.description + value: "Configured by ansible {{ 100 | random }}" register: updated - name: Apply the new configuration @@ -215,7 +216,6 @@ ansible.utils.fact_diff: before: "{{ pre.output.data|ansible.utils.to_paths }}" after: "{{ post.output.data|ansible.utils.to_paths }}" - # TASK [Show the differences between the pre and post configurations] ******** # --- before # +++ after @@ -238,25 +238,27 @@ RETURN = """ stdout: description: The raw XML string containing configuration or state data - received from the underlying ncclient library. + received from the underlying ncclient library. returned: always apart from low-level errors (such as action plugin) type: str - sample: '...' + sample: "..." stdout_lines: description: The value of stdout split into a list returned: always apart from low-level errors (such as action plugin) type: list - sample: ['...', '...'] + sample: ["...", "..."] output: - description: Based on the value of display option will return either the set of - transformed XML to JSON format from the RPC response with type dict - or pretty XML string response (human-readable) or response with - namespace removed from XML string. - returned: If the display format is selected as I(json) it is returned as dict type - and the conversion is done using jxmlease python library. If the display - format is selected as I(native) it is returned as dict type and the conversion - is done using xmltodict python library. If the display format is xml or pretty - it is returned as a string apart from low-level errors (such as action plugin). + description: + Based on the value of display option will return either the set of + transformed XML to JSON format from the RPC response with type dict + or pretty XML string response (human-readable) or response with + namespace removed from XML string. + returned: + If the display format is selected as I(json) it is returned as dict type + and the conversion is done using jxmlease python library. If the display + format is selected as I(native) it is returned as dict type and the conversion + is done using xmltodict python library. If the display format is xml or pretty + it is returned as a string apart from low-level errors (such as action plugin). type: complex contains: formatted_output: diff --git a/plugins/modules/telnet.py b/plugins/modules/telnet.py index 65311eede..41abf23bb 100644 --- a/plugins/modules/telnet.py +++ b/plugins/modules/telnet.py @@ -13,87 +13,87 @@ module: telnet short_description: Executes a low-down and dirty telnet command description: -- Executes a low-down and dirty telnet command, not going through the module subsystem. -- This is mostly to be used for enabling ssh on devices that only have telnet enabled - by default. + - Executes a low-down and dirty telnet command, not going through the module subsystem. + - This is mostly to be used for enabling ssh on devices that only have telnet enabled + by default. version_added: 1.0.0 options: command: description: - - List of commands to be executed in the telnet session. + - List of commands to be executed in the telnet session. required: true type: list elements: str aliases: - - commands + - commands host: description: - - The host/target on which to execute the command + - The host/target on which to execute the command required: false type: str default: remote_addr user: description: - - The user for login + - The user for login required: false type: str default: remote_user password: description: - - The password for login + - The password for login type: str port: description: - - Remote port to use + - Remote port to use type: int default: 23 timeout: description: - - timeout for remote operations + - timeout for remote operations type: int default: 120 prompts: description: - - List of prompts expected before sending next command + - List of prompts expected before sending next command required: false type: list elements: str default: - - $ + - $ login_prompt: description: - - Login or username prompt to expect + - Login or username prompt to expect required: false type: str - default: 'login: ' + default: "login: " password_prompt: description: - - Login or username prompt to expect + - Login or username prompt to expect required: false type: str - default: 'Password: ' + default: "Password: " pause: description: - - Seconds to pause between each command issued + - Seconds to pause between each command issued required: false type: int default: 1 send_newline: description: - - Sends a newline character upon successful connection to start the terminal session. + - Sends a newline character upon successful connection to start the terminal session. required: false type: bool default: false crlf: description: - - Sends a CRLF (Carrage Return) instead of just a LF (Line Feed). + - Sends a CRLF (Carrage Return) instead of just a LF (Line Feed). required: false type: bool default: false notes: -- The C(environment) keyword does not work with this task + - The C(environment) keyword does not work with this task author: -- Ansible Core Team + - Ansible Core Team """ EXAMPLES = """ @@ -101,30 +101,30 @@ ansible.netcommon.telnet: user: cisco password: cisco - login_prompt: 'Username: ' + login_prompt: "Username: " prompts: - - '[>#]' + - "[>#]" command: - - terminal length 0 - - configure terminal - - hostname ios01 + - terminal length 0 + - configure terminal + - hostname ios01 - name: run show commands ansible.netcommon.telnet: user: cisco password: cisco - login_prompt: 'Username: ' + login_prompt: "Username: " prompts: - - '[>#]' + - "[>#]" command: - - terminal length 0 - - show version + - terminal length 0 + - show version """ RETURN = """ output: - description: output of each command is an element in this list - type: list - returned: always - sample: [ 'success', 'success', '', 'warning .. something' ] + description: output of each command is an element in this list + type: list + returned: always + sample: ["success", "success", "", "warning .. something"] """ diff --git a/plugins/plugin_utils/cliconf_base.py b/plugins/plugin_utils/cliconf_base.py index 30b4aa550..c48873391 100644 --- a/plugins/plugin_utils/cliconf_base.py +++ b/plugins/plugin_utils/cliconf_base.py @@ -230,10 +230,10 @@ def edit_config( configuration should be pushed in the running configuration or discarded. :param replace: If the value is True/False it indicates if running configuration should be completely - replace by candidate configuration. If can also take configuration file path as value, + replace by candidate configuration. It can also take configuration file path as value, the file in this case should be present on the remote host in the mentioned path as a prerequisite. - :param comment: Commit comment provided it is supported by remote host + :param comment: Commit comment provided it is supported by remote host. :return: Returns a json string with contains configuration applied on remote host, the returned response on executing configuration commands and platform relevant data. { diff --git a/plugins/plugin_utils/compat/telnetlib.py b/plugins/plugin_utils/compat/telnetlib.py index 766ddf018..f237b541b 100644 --- a/plugins/plugin_utils/compat/telnetlib.py +++ b/plugins/plugin_utils/compat/telnetlib.py @@ -34,7 +34,6 @@ """ - # Imported modules import selectors import socket @@ -52,99 +51,98 @@ TELNET_PORT = 23 # Telnet protocol characters (don't change) -IAC = bytes([255]) # "Interpret As Command" +IAC = bytes([255]) # "Interpret As Command" DONT = bytes([254]) -DO = bytes([253]) +DO = bytes([253]) WONT = bytes([252]) WILL = bytes([251]) theNULL = bytes([0]) -SE = bytes([240]) # Subnegotiation End +SE = bytes([240]) # Subnegotiation End NOP = bytes([241]) # No Operation -DM = bytes([242]) # Data Mark +DM = bytes([242]) # Data Mark BRK = bytes([243]) # Break -IP = bytes([244]) # Interrupt process -AO = bytes([245]) # Abort output +IP = bytes([244]) # Interrupt process +AO = bytes([245]) # Abort output AYT = bytes([246]) # Are You There -EC = bytes([247]) # Erase Character -EL = bytes([248]) # Erase Line -GA = bytes([249]) # Go Ahead -SB = bytes([250]) # Subnegotiation Begin +EC = bytes([247]) # Erase Character +EL = bytes([248]) # Erase Line +GA = bytes([249]) # Go Ahead +SB = bytes([250]) # Subnegotiation Begin # Telnet protocol options code (don't change) # These ones all come from arpa/telnet.h -BINARY = bytes([0]) # 8-bit data path -ECHO = bytes([1]) # echo -RCP = bytes([2]) # prepare to reconnect -SGA = bytes([3]) # suppress go ahead -NAMS = bytes([4]) # approximate message size -STATUS = bytes([5]) # give status -TM = bytes([6]) # timing mark -RCTE = bytes([7]) # remote controlled transmission and echo -NAOL = bytes([8]) # negotiate about output line width -NAOP = bytes([9]) # negotiate about output page size -NAOCRD = bytes([10]) # negotiate about CR disposition -NAOHTS = bytes([11]) # negotiate about horizontal tabstops -NAOHTD = bytes([12]) # negotiate about horizontal tab disposition -NAOFFD = bytes([13]) # negotiate about formfeed disposition -NAOVTS = bytes([14]) # negotiate about vertical tab stops -NAOVTD = bytes([15]) # negotiate about vertical tab disposition -NAOLFD = bytes([16]) # negotiate about output LF disposition -XASCII = bytes([17]) # extended ascii character set -LOGOUT = bytes([18]) # force logout -BM = bytes([19]) # byte macro -DET = bytes([20]) # data entry terminal -SUPDUP = bytes([21]) # supdup protocol -SUPDUPOUTPUT = bytes([22]) # supdup output -SNDLOC = bytes([23]) # send location -TTYPE = bytes([24]) # terminal type -EOR = bytes([25]) # end or record -TUID = bytes([26]) # TACACS user identification -OUTMRK = bytes([27]) # output marking -TTYLOC = bytes([28]) # terminal location number -VT3270REGIME = bytes([29]) # 3270 regime -X3PAD = bytes([30]) # X.3 PAD -NAWS = bytes([31]) # window size -TSPEED = bytes([32]) # terminal speed -LFLOW = bytes([33]) # remote flow control -LINEMODE = bytes([34]) # Linemode option -XDISPLOC = bytes([35]) # X Display Location -OLD_ENVIRON = bytes([36]) # Old - Environment variables -AUTHENTICATION = bytes([37]) # Authenticate -ENCRYPT = bytes([38]) # Encryption option -NEW_ENVIRON = bytes([39]) # New - Environment variables +BINARY = bytes([0]) # 8-bit data path +ECHO = bytes([1]) # echo +RCP = bytes([2]) # prepare to reconnect +SGA = bytes([3]) # suppress go ahead +NAMS = bytes([4]) # approximate message size +STATUS = bytes([5]) # give status +TM = bytes([6]) # timing mark +RCTE = bytes([7]) # remote controlled transmission and echo +NAOL = bytes([8]) # negotiate about output line width +NAOP = bytes([9]) # negotiate about output page size +NAOCRD = bytes([10]) # negotiate about CR disposition +NAOHTS = bytes([11]) # negotiate about horizontal tabstops +NAOHTD = bytes([12]) # negotiate about horizontal tab disposition +NAOFFD = bytes([13]) # negotiate about formfeed disposition +NAOVTS = bytes([14]) # negotiate about vertical tab stops +NAOVTD = bytes([15]) # negotiate about vertical tab disposition +NAOLFD = bytes([16]) # negotiate about output LF disposition +XASCII = bytes([17]) # extended ascii character set +LOGOUT = bytes([18]) # force logout +BM = bytes([19]) # byte macro +DET = bytes([20]) # data entry terminal +SUPDUP = bytes([21]) # supdup protocol +SUPDUPOUTPUT = bytes([22]) # supdup output +SNDLOC = bytes([23]) # send location +TTYPE = bytes([24]) # terminal type +EOR = bytes([25]) # end or record +TUID = bytes([26]) # TACACS user identification +OUTMRK = bytes([27]) # output marking +TTYLOC = bytes([28]) # terminal location number +VT3270REGIME = bytes([29]) # 3270 regime +X3PAD = bytes([30]) # X.3 PAD +NAWS = bytes([31]) # window size +TSPEED = bytes([32]) # terminal speed +LFLOW = bytes([33]) # remote flow control +LINEMODE = bytes([34]) # Linemode option +XDISPLOC = bytes([35]) # X Display Location +OLD_ENVIRON = bytes([36]) # Old - Environment variables +AUTHENTICATION = bytes([37]) # Authenticate +ENCRYPT = bytes([38]) # Encryption option +NEW_ENVIRON = bytes([39]) # New - Environment variables # the following ones come from # http://www.iana.org/assignments/telnet-options # Unfortunately, that document does not assign identifiers # to all of them, so we are making them up -TN3270E = bytes([40]) # TN3270E -XAUTH = bytes([41]) # XAUTH -CHARSET = bytes([42]) # CHARSET -RSP = bytes([43]) # Telnet Remote Serial Port -COM_PORT_OPTION = bytes([44]) # Com Port Control Option -SUPPRESS_LOCAL_ECHO = bytes([45]) # Telnet Suppress Local Echo -TLS = bytes([46]) # Telnet Start TLS -KERMIT = bytes([47]) # KERMIT -SEND_URL = bytes([48]) # SEND-URL -FORWARD_X = bytes([49]) # FORWARD_X -PRAGMA_LOGON = bytes([138]) # TELOPT PRAGMA LOGON -SSPI_LOGON = bytes([139]) # TELOPT SSPI LOGON -PRAGMA_HEARTBEAT = bytes([140]) # TELOPT PRAGMA HEARTBEAT -EXOPL = bytes([255]) # Extended-Options-List +TN3270E = bytes([40]) # TN3270E +XAUTH = bytes([41]) # XAUTH +CHARSET = bytes([42]) # CHARSET +RSP = bytes([43]) # Telnet Remote Serial Port +COM_PORT_OPTION = bytes([44]) # Com Port Control Option +SUPPRESS_LOCAL_ECHO = bytes([45]) # Telnet Suppress Local Echo +TLS = bytes([46]) # Telnet Start TLS +KERMIT = bytes([47]) # KERMIT +SEND_URL = bytes([48]) # SEND-URL +FORWARD_X = bytes([49]) # FORWARD_X +PRAGMA_LOGON = bytes([138]) # TELOPT PRAGMA LOGON +SSPI_LOGON = bytes([139]) # TELOPT SSPI LOGON +PRAGMA_HEARTBEAT = bytes([140]) # TELOPT PRAGMA HEARTBEAT +EXOPL = bytes([255]) # Extended-Options-List NOOPT = bytes([0]) # poll/select have the advantage of not requiring any extra file descriptor, # contrarily to epoll/kqueue (also, they require a single syscall). -if hasattr(selectors, 'PollSelector'): +if hasattr(selectors, "PollSelector"): _TelnetSelector = selectors.PollSelector else: _TelnetSelector = selectors.SelectSelector class Telnet: - """Telnet interface class. An instance of this class represents a connection to a telnet @@ -198,8 +196,7 @@ class Telnet: """ - def __init__(self, host=None, port=0, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT): + def __init__(self, host=None, port=0, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): """Constructor. When called without arguments, create an unconnected instance. @@ -211,13 +208,13 @@ def __init__(self, host=None, port=0, self.port = port self.timeout = timeout self.sock = None - self.rawq = b'' + self.rawq = b"" self.irawq = 0 - self.cookedq = b'' + self.cookedq = b"" self.eof = 0 - self.iacseq = b'' # Buffer for IAC sequence. - self.sb = 0 # flag for SB and SE sequence. - self.sbdataq = b'' + self.iacseq = b"" # Buffer for IAC sequence. + self.sb = 0 # flag for SB and SE sequence. + self.sbdataq = b"" self.option_callback = None if host is not None: self.open(host, port, timeout) @@ -251,7 +248,7 @@ def msg(self, msg, *args): """ if self.debuglevel > 0: - print('Telnet(%s,%s):' % (self.host, self.port), end=' ') + print("Telnet(%s,%s):" % (self.host, self.port), end=" ") if args: print(msg % args) else: @@ -270,7 +267,7 @@ def close(self): sock = self.sock self.sock = None self.eof = True - self.iacseq = b'' + self.iacseq = b"" self.sb = 0 if sock: sock.close() @@ -291,7 +288,7 @@ def write(self, buffer): """ if IAC in buffer: - buffer = buffer.replace(IAC, IAC+IAC) + buffer = buffer.replace(IAC, IAC + IAC) sys.audit("telnetlib.Telnet.write", self, buffer) self.msg("send %r", buffer) self.sock.sendall(buffer) @@ -308,7 +305,7 @@ def read_until(self, match, timeout=None): self.process_rawq() i = self.cookedq.find(match) if i >= 0: - i = i+n + i = i + n buf = self.cookedq[:i] self.cookedq = self.cookedq[i:] return buf @@ -318,12 +315,12 @@ def read_until(self, match, timeout=None): selector.register(self, selectors.EVENT_READ) while not self.eof: if selector.select(timeout): - i = max(0, len(self.cookedq)-n) + i = max(0, len(self.cookedq) - n) self.fill_rawq() self.process_rawq() i = self.cookedq.find(match, i) if i >= 0: - i = i+n + i = i + n buf = self.cookedq[:i] self.cookedq = self.cookedq[i:] return buf @@ -340,7 +337,7 @@ def read_all(self): self.fill_rawq() self.process_rawq() buf = self.cookedq - self.cookedq = b'' + self.cookedq = b"" return buf def read_some(self): @@ -355,7 +352,7 @@ def read_some(self): self.fill_rawq() self.process_rawq() buf = self.cookedq - self.cookedq = b'' + self.cookedq = b"" return buf def read_very_eager(self): @@ -405,9 +402,9 @@ def read_very_lazy(self): """ buf = self.cookedq - self.cookedq = b'' + self.cookedq = b"" if not buf and self.eof and not self.rawq: - raise EOFError('telnet connection closed') + raise EOFError("telnet connection closed") return buf def read_sb_data(self): @@ -419,7 +416,7 @@ def read_sb_data(self): """ buf = self.sbdataq - self.sbdataq = b'' + self.sbdataq = b"" return buf def set_option_negotiation_callback(self, callback): @@ -433,7 +430,7 @@ def process_rawq(self): the midst of an IAC sequence. """ - buf = [b'', b''] + buf = [b"", b""] try: while self.rawq: c = self.rawq_getchar() @@ -453,17 +450,17 @@ def process_rawq(self): self.iacseq += c continue - self.iacseq = b'' + self.iacseq = b"" if c == IAC: buf[self.sb] = buf[self.sb] + c else: - if c == SB: # SB ... SE start. + if c == SB: # SB ... SE start. self.sb = 1 - self.sbdataq = b'' + self.sbdataq = b"" elif c == SE: self.sb = 0 self.sbdataq = self.sbdataq + buf[1] - buf[1] = b'' + buf[1] = b"" if self.option_callback: # Callback is supposed to look into # the sbdataq @@ -472,27 +469,27 @@ def process_rawq(self): # We can't offer automatic processing of # suboptions. Alas, we should not get any # unless we did a WILL/DO before. - self.msg('IAC %d not recognized' % ord(c)) + self.msg("IAC %d not recognized" % ord(c)) elif len(self.iacseq) == 2: cmd = self.iacseq[1:2] - self.iacseq = b'' + self.iacseq = b"" opt = c if cmd in (DO, DONT): - self.msg('IAC %s %d', - cmd == DO and 'DO' or 'DONT', ord(opt)) + self.msg("IAC %s %d", cmd == DO and "DO" or "DONT", ord(opt)) if self.option_callback: self.option_callback(self.sock, cmd, opt) else: self.sock.sendall(IAC + WONT + opt) elif cmd in (WILL, WONT): - self.msg('IAC %s %d', - cmd == WILL and 'WILL' or 'WONT', ord(opt)) + self.msg( + "IAC %s %d", cmd == WILL and "WILL" or "WONT", ord(opt) + ) if self.option_callback: self.option_callback(self.sock, cmd, opt) else: self.sock.sendall(IAC + DONT + opt) - except EOFError: # raised by self.rawq_getchar() - self.iacseq = b'' # Reset on EOF + except EOFError: # raised by self.rawq_getchar() + self.iacseq = b"" # Reset on EOF self.sb = 0 self.cookedq = self.cookedq + buf[0] self.sbdataq = self.sbdataq + buf[1] @@ -508,10 +505,10 @@ def rawq_getchar(self): self.fill_rawq() if self.eof: raise EOFError - c = self.rawq[self.irawq:self.irawq+1] + c = self.rawq[self.irawq : self.irawq + 1] self.irawq = self.irawq + 1 if self.irawq >= len(self.rawq): - self.rawq = b'' + self.rawq = b"" self.irawq = 0 return c @@ -523,13 +520,13 @@ def fill_rawq(self): """ if self.irawq >= len(self.rawq): - self.rawq = b'' + self.rawq = b"" self.irawq = 0 # The buffer size should be fairly small so as to avoid quadratic # behavior in process_rawq() above buf = self.sock.recv(50) self.msg("recv %r", buf) - self.eof = (not buf) + self.eof = not buf self.rawq = self.rawq + buf def sock_avail(self): @@ -553,13 +550,13 @@ def interact(self): try: text = self.read_eager() except EOFError: - print('*** Connection closed by remote host ***') + print("*** Connection closed by remote host ***") return if text: - sys.stdout.write(text.decode('ascii')) + sys.stdout.write(text.decode("ascii")) sys.stdout.flush() elif key.fileobj is sys.stdin: - line = sys.stdin.readline().encode('ascii') + line = sys.stdin.readline().encode("ascii") if not line: return self.write(line) @@ -567,12 +564,13 @@ def interact(self): def mt_interact(self): """Multithreaded version of interact().""" import _thread + _thread.start_new_thread(self.listener, ()) while 1: line = sys.stdin.readline() if not line: break - self.write(line.encode('ascii')) + self.write(line.encode("ascii")) def listener(self): """Helper for mt_interact() -- this executes in the other thread.""" @@ -580,10 +578,10 @@ def listener(self): try: data = self.read_eager() except EOFError: - print('*** Connection closed by remote host ***') + print("*** Connection closed by remote host ***") return if data: - sys.stdout.write(data.decode('ascii')) + sys.stdout.write(data.decode("ascii")) else: sys.stdout.flush() @@ -614,7 +612,8 @@ def expect(self, list, timeout=None): indices = range(len(list)) for i in indices: if not hasattr(list[i], "search"): - if not re: import re + if not re: + import re list[i] = re.compile(list[i]) if timeout is not None: deadline = _time() + timeout @@ -659,10 +658,10 @@ def test(): """ debuglevel = 0 - while sys.argv[1:] and sys.argv[1] == '-d': - debuglevel = debuglevel+1 + while sys.argv[1:] and sys.argv[1] == "-d": + debuglevel = debuglevel + 1 del sys.argv[1] - host = 'localhost' + host = "localhost" if sys.argv[1:]: host = sys.argv[1] port = 0 @@ -671,11 +670,12 @@ def test(): try: port = int(portstr) except ValueError: - port = socket.getservbyname(portstr, 'tcp') + port = socket.getservbyname(portstr, "tcp") with Telnet() as tn: tn.set_debuglevel(debuglevel) tn.open(host, port, timeout=0.5) tn.interact() -if __name__ == '__main__': + +if __name__ == "__main__": test() diff --git a/plugins/plugin_utils/pop_ace.py b/plugins/plugin_utils/pop_ace.py index d15f30e72..e55a15387 100644 --- a/plugins/plugin_utils/pop_ace.py +++ b/plugins/plugin_utils/pop_ace.py @@ -36,29 +36,56 @@ def check_match(ace, match_criteria, match_all, name, afi): for k, v in match_criteria.items(): if v: if k not in ["source", "destination", "acl_name", "afi"]: - check_arr.append(True) if ace.get(k, "NA") == match_criteria.get( - k, - ) else check_arr.append(False) + ( + check_arr.append(True) + if ace.get(k, "NA") + == match_criteria.get( + k, + ) + else check_arr.append(False) + ) elif k == "acl_name": - check_arr.append(True) if name == match_criteria.get( - k, - ) else check_arr.append(False) + ( + check_arr.append(True) + if name + == match_criteria.get( + k, + ) + else check_arr.append(False) + ) elif k == "afi": - check_arr.append(True) if afi == match_criteria.get( - k, - ) else check_arr.append(False) + ( + check_arr.append(True) + if afi + == match_criteria.get( + k, + ) + else check_arr.append(False) + ) else: # for source and destination address _sub = "source" if "source" in k else "destination" _valid = [] - _valid.append(True) if ace.get(_sub, {}).get("address", "NA") == match_criteria.get( - k, - ) else _valid.append(False) - _valid.append(True) if ace.get(_sub, {}).get("host", "NA") == match_criteria.get( - k, - ) else _valid.append(False) - _valid.append(True) if ace.get(_sub, {}).get("any", "NA") == ( - match_criteria.get(k) == "any" - ) else _valid.append(False) + ( + _valid.append(True) + if ace.get(_sub, {}).get("address", "NA") + == match_criteria.get( + k, + ) + else _valid.append(False) + ) + ( + _valid.append(True) + if ace.get(_sub, {}).get("host", "NA") + == match_criteria.get( + k, + ) + else _valid.append(False) + ) + ( + _valid.append(True) + if ace.get(_sub, {}).get("any", "NA") == (match_criteria.get(k) == "any") + else _valid.append(False) + ) check_arr.append(any(_valid)) if match_all: # forces all criteria to match diff --git a/plugins/sub_plugins/cli_parser/content_templates_parser.py b/plugins/sub_plugins/cli_parser/content_templates_parser.py index 8bc47f781..aa43b04cb 100644 --- a/plugins/sub_plugins/cli_parser/content_templates_parser.py +++ b/plugins/sub_plugins/cli_parser/content_templates_parser.py @@ -5,6 +5,7 @@ The parser functionality used by the network resource modules is leveraged here. """ + from __future__ import absolute_import, division, print_function @@ -22,7 +23,7 @@ """ EXAMPLES = """ -- name: "Run command and parse with native" +- name: "Run command and parse with content_templates" ansible.utils.cli_parse: command: "show bgp summary" parser: @@ -40,7 +41,7 @@ class CliParser(CliParserBase): - """The native parser class + """The content_templates parser class Convert raw text to structured data using the resource module parser """ @@ -71,8 +72,7 @@ def parse(self, *_args, **kwargs): try: parser.PARSERS = template_obj out = {"parsed": parser.parse()} - print(out) return out except Exception as exc: - msg = "Native parser returned an error while parsing. Error: {err}" + msg = "An error occurred during content_templates parsing. Error: {err}" return {"errors": [msg.format(err=to_native(exc))]} diff --git a/requirements.txt b/requirements.txt index 7ff91870f..873f5ddc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ ansible-pylibssh >= 0.2.0 jxmlease ncclient -netaddr paramiko xmltodict grpcio diff --git a/test-requirements.txt b/test-requirements.txt index ec27173ee..518b54423 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,15 +1,19 @@ # For ansible-tox-linters -black==23.3.0 ; python_version > '3.7' +black==23.3.0 flake8 yamllint # Unit test runner -pytest-ansible ; python_version >= '3.7' -git+https://github.com/ansible-community/pytest-ansible-units.git ; python_version < '3.7' +pytest-ansible pytest-xdist +pytest-cov # The following are 3rd party libs for cli_parse ntc_templates -# 21.4 changed the output of an error message we check in tests +# 21.4 changed the output of an error message we checked in the tests pyats >= 21.4 ; python_version < '3.11' -genie >= 21.4 ; python_version < '3.11' +# fix genie version for python version less than 3.11 +genie == 24.3 ; python_version < '3.11' +# use the latest genie version for every Python version greater than 3.11 +genie +passlib diff --git a/tests/config.yml b/tests/config.yml new file mode 100644 index 000000000..c26ea5966 --- /dev/null +++ b/tests/config.yml @@ -0,0 +1,3 @@ +--- +modules: + python_requires: ">=3.9" diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.18.txt similarity index 100% rename from tests/sanity/ignore-2.12.txt rename to tests/sanity/ignore-2.18.txt diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.19.txt similarity index 100% rename from tests/sanity/ignore-2.13.txt rename to tests/sanity/ignore-2.19.txt diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt deleted file mode 100644 index 27c170503..000000000 --- a/tests/sanity/ignore-2.9.txt +++ /dev/null @@ -1,7 +0,0 @@ -plugins/action/netconf.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -plugins/action/network.py action-plugin-docs # base class for network action plugins -plugins/action/grpc.py action-plugin-docs # base class for grpc action plugins -plugins/plugin_utils/compat/telnetlib.py future-import-boilerplate!skip # vendored file -plugins/plugin_utils/compat/telnetlib.py metaclass-boilerplate!skip # vendored file -plugins/plugin_utils/compat/telnetlib.py pep8!skip # vendored file -plugins/plugin_utils/compat/telnetlib.py pylint!skip # vendored file diff --git a/tests/unit/mock/procenv.py b/tests/unit/mock/procenv.py index d6f69f4d6..f480dbd57 100644 --- a/tests/unit/mock/procenv.py +++ b/tests/unit/mock/procenv.py @@ -11,10 +11,10 @@ import json import sys -import unittest from contextlib import contextmanager from io import BytesIO, StringIO +from unittest import TestCase from ansible.module_utils._text import to_bytes from ansible.module_utils.six import PY3 @@ -64,7 +64,7 @@ def swap_stdout(): sys.stdout = old_stdout -class ModuleTestCase(unittest.TestCase): +class ModuleTestCase(TestCase): def setUp(self, module_args=None): if module_args is None: module_args = { diff --git a/tests/unit/module_utils/network/common/test_parsing.py b/tests/unit/module_utils/network/common/test_parsing.py index dac54188e..b5aafd443 100644 --- a/tests/unit/module_utils/network/common/test_parsing.py +++ b/tests/unit/module_utils/network/common/test_parsing.py @@ -9,7 +9,7 @@ __metaclass__ = type -import unittest +from unittest import TestCase from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import ( Conditional, @@ -22,7 +22,7 @@ c3 = Conditional("result[0] neq not result_1") -class TestNotKeyword(unittest.TestCase): +class TestNotKeyword(TestCase): def test_negate_instance_variable_assignment(self): assert c1.negate is False and c2.negate is True diff --git a/tests/unit/modules/utils.py b/tests/unit/modules/utils.py index c524e2ab2..92639df70 100644 --- a/tests/unit/modules/utils.py +++ b/tests/unit/modules/utils.py @@ -3,8 +3,8 @@ __metaclass__ = type import json -import unittest +from unittest import TestCase from unittest.mock import patch from ansible.module_utils import basic @@ -40,7 +40,7 @@ def fail_json(*args, **kwargs): raise AnsibleFailJson(kwargs) -class ModuleTestCase(unittest.TestCase): +class ModuleTestCase(TestCase): def setUp(self): self.mock_module = patch.multiple( basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json diff --git a/tests/unit/plugins/cli_parsers/test_pyats_parser.py b/tests/unit/plugins/cli_parsers/test_pyats_parser.py index d980f25d0..50bfd2574 100644 --- a/tests/unit/plugins/cli_parsers/test_pyats_parser.py +++ b/tests/unit/plugins/cli_parsers/test_pyats_parser.py @@ -8,7 +8,8 @@ __metaclass__ = type import os -import unittest + +from unittest import TestCase import pytest @@ -20,7 +21,7 @@ pyats = pytest.importorskip("pyats") -class TestPyatsParser(unittest.TestCase): +class TestPyatsParser(TestCase): _nxos_parsed_output = { "platform": { "hardware": { diff --git a/tests/unit/plugins/filter/comp_type5.py b/tests/unit/plugins/filter/comp_type5.py index 9bcc0ba35..0a71bfafa 100644 --- a/tests/unit/plugins/filter/comp_type5.py +++ b/tests/unit/plugins/filter/comp_type5.py @@ -8,12 +8,12 @@ __metaclass__ = type -import unittest +from unittest import TestCase from ansible_collections.ansible.netcommon.plugins.plugin_utils.comp_type5 import comp_type5 -class TestComp_type5(unittest.TestCase): +class TestComp_type5(TestCase): def setUp(self): pass diff --git a/tests/unit/plugins/filter/test_hash_salt.py b/tests/unit/plugins/filter/test_hash_salt.py index 54ffbfb19..b69a76e7f 100644 --- a/tests/unit/plugins/filter/test_hash_salt.py +++ b/tests/unit/plugins/filter/test_hash_salt.py @@ -8,12 +8,12 @@ __metaclass__ = type -import unittest +from unittest import TestCase from ansible_collections.ansible.netcommon.plugins.plugin_utils.hash_salt import hash_salt -class Testhash_salt(unittest.TestCase): +class Testhash_salt(TestCase): def setUp(self): pass diff --git a/tests/unit/plugins/filter/test_network.py b/tests/unit/plugins/filter/test_network.py index a3b50ef83..29d035c78 100644 --- a/tests/unit/plugins/filter/test_network.py +++ b/tests/unit/plugins/filter/test_network.py @@ -20,8 +20,8 @@ __metaclass__ = type import os -import sys -import unittest + +from unittest import TestCase from ansible_collections.ansible.netcommon.plugins.plugin_utils.comp_type5 import comp_type5 from ansible_collections.ansible.netcommon.plugins.plugin_utils.hash_salt import hash_salt @@ -37,11 +37,7 @@ output_xml = f.read() -class TestNetworkParseFilter(unittest.TestCase): - @unittest.skipIf( - sys.version_info[:2] == (2, 6), - "XPath expression not supported in this version", - ) +class TestNetworkParseFilter(TestCase): def test_parse_xml_to_list_of_dict(self): spec_file_path = os.path.join(fixture_path, "show_vlans_xml_spec.yml") parsed = parse_xml(output_xml, spec_file_path) @@ -91,10 +87,6 @@ def test_parse_xml_to_list_of_dict(self): } self.assertEqual(parsed, expected) - @unittest.skipIf( - sys.version_info[:2] == (2, 6), - "XPath expression not supported in this version", - ) def test_parse_xml_to_dict(self): spec_file_path = os.path.join(fixture_path, "show_vlans_xml_with_key_spec.yml") parsed = parse_xml(output_xml, spec_file_path) @@ -144,10 +136,6 @@ def test_parse_xml_to_dict(self): } self.assertEqual(parsed, expected) - @unittest.skipIf( - sys.version_info[:2] == (2, 6), - "XPath expression not supported in this version", - ) def test_parse_xml_with_condition_spec(self): spec_file_path = os.path.join(fixture_path, "show_vlans_xml_with_condition_spec.yml") parsed = parse_xml(output_xml, spec_file_path) @@ -187,7 +175,7 @@ def test_parse_xml_validate_input(self): ) -class TestNetworkType5(unittest.TestCase): +class TestNetworkType5(TestCase): def test_defined_salt_success(self): password = "cisco" salt = "nTc1" @@ -247,7 +235,7 @@ def test_bad_salt_char(self): ) -class TestHashSalt(unittest.TestCase): +class TestHashSalt(TestCase): def test_retrieve_salt(self): password = "$1$nTc1$Z28sUTcWfXlvVe2x.3XAa." parsed = hash_salt(password) @@ -267,7 +255,7 @@ def test_unparseable_salt(self): ) -class TestCompareType5(unittest.TestCase): +class TestCompareType5(TestCase): def test_compare_type5_boolean(self): unencrypted_password = "cisco" encrypted_password = "$1$nTc1$Z28sUTcWfXlvVe2x.3XAa." @@ -287,7 +275,7 @@ def test_compare_type5_fail(self): self.assertEqual(parsed, False) -class TestVlanExapander(unittest.TestCase): +class TestVlanExapander(TestCase): def test_single_range(self): raw_list = "1-3" expanded_list = [1, 2, 3] @@ -305,7 +293,7 @@ def test_no_ranges(self): self.assertEqual(vlan_expander(raw_list), expanded_list) -class TestVlanParser(unittest.TestCase): +class TestVlanParser(TestCase): def test_compression(self): raw_list = [1, 2, 3] parsed_list = ["1-3"] diff --git a/tests/unit/plugins/filter/test_pop_ace.py b/tests/unit/plugins/filter/test_pop_ace.py index adc747038..33dbc44b6 100644 --- a/tests/unit/plugins/filter/test_pop_ace.py +++ b/tests/unit/plugins/filter/test_pop_ace.py @@ -8,14 +8,14 @@ __metaclass__ = type -import unittest +from unittest import TestCase from ansible.errors import AnsibleFilterError from ansible_collections.ansible.netcommon.plugins.plugin_utils.pop_ace import pop_ace -class TestPopAce(unittest.TestCase): +class TestPopAce(TestCase): def setUp(self): pass diff --git a/tests/unit/plugins/filter/test_type5_pw.py b/tests/unit/plugins/filter/test_type5_pw.py index a1ea8c905..4f12a9f68 100644 --- a/tests/unit/plugins/filter/test_type5_pw.py +++ b/tests/unit/plugins/filter/test_type5_pw.py @@ -8,12 +8,12 @@ __metaclass__ = type -import unittest +from unittest import TestCase from ansible_collections.ansible.netcommon.plugins.plugin_utils.type5_pw import type5_pw -class TestType5_pw(unittest.TestCase): +class TestType5_pw(TestCase): def setUp(self): pass diff --git a/tests/unit/plugins/filter/test_vlan_extender.py b/tests/unit/plugins/filter/test_vlan_extender.py index 4537e1eed..3f3d5f089 100644 --- a/tests/unit/plugins/filter/test_vlan_extender.py +++ b/tests/unit/plugins/filter/test_vlan_extender.py @@ -8,12 +8,12 @@ __metaclass__ = type -import unittest +from unittest import TestCase from ansible_collections.ansible.netcommon.plugins.plugin_utils.vlan_expander import vlan_expander -class TestVlanExtender(unittest.TestCase): +class TestVlanExtender(TestCase): def setUp(self): pass diff --git a/tests/unit/plugins/filter/test_vlan_parser.py b/tests/unit/plugins/filter/test_vlan_parser.py index 3765bdf28..a2d234e35 100644 --- a/tests/unit/plugins/filter/test_vlan_parser.py +++ b/tests/unit/plugins/filter/test_vlan_parser.py @@ -8,14 +8,14 @@ __metaclass__ = type -import unittest +from unittest import TestCase from ansible.errors import AnsibleFilterError from ansible_collections.ansible.netcommon.plugins.plugin_utils.vlan_parser import vlan_parser -class TestVlanParser(unittest.TestCase): +class TestVlanParser(TestCase): def setUp(self): pass diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt index 6a2f2eb7a..5e1e0a931 100644 --- a/tests/unit/requirements.txt +++ b/tests/unit/requirements.txt @@ -5,7 +5,6 @@ ansible-pylibssh jxmlease ncclient selectors2 ; python_version == '3.5' -netaddr # The follow are 3rd party libs for cli_parse ntc_templates diff --git a/tox-ansible.ini b/tox-ansible.ini new file mode 100644 index 000000000..f1c7fce60 --- /dev/null +++ b/tox-ansible.ini @@ -0,0 +1,2 @@ +[ansible] +skip = "" diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 14db05a79..000000000 --- a/tox.ini +++ /dev/null @@ -1,23 +0,0 @@ -[tox] -minversion = 1.4.2 -envlist = linters -skipsdist = True - -[testenv] -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - -[testenv:black] -install_command = pip install {opts} {packages} -commands = - black --exclude '\.git|\.mypy_cache|\.tox|tests/output' {toxinidir} - -[testenv:linters] -install_command = pip install {opts} {packages} -commands = - black --exclude '\.git|\.mypy_cache|\.tox|tests/output' --check {toxinidir} - flake8 {posargs} - yamllint -s . - -[testenv:venv] -commands = {posargs}