diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..9623a78 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# 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 + +# Reformat YAML: https://github.com/ansible-collections/community.routeros/pull/369 +08152376de116e7d933d19ee25318f7a2eb222ae diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2f4ff90..f71b322 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,3 +9,7 @@ updates: directory: "/" schedule: interval: "weekly" + groups: + ci: + patterns: + - "*" diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml deleted file mode 100644 index 4431bea..0000000 --- a/.github/workflows/ansible-test.yml +++ /dev/null @@ -1,146 +0,0 @@ ---- -# Copyright (c) Ansible Project -# 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 - -# For the comprehensive list of the inputs supported by the ansible-community/ansible-test-gh-action GitHub Action, see -# https://github.com/marketplace/actions/ansible-test - -name: CI -on: - # Run CI against all pushes (direct commits, also merged PRs), Pull Requests - push: - branches: - - main - - stable-* - pull_request: - # Run CI once per day (at 05:15 UTC) - schedule: - - cron: '15 5 * * *' - -jobs: - sanity: - name: Sanity (Ⓐ${{ matrix.ansible }}) - strategy: - matrix: - ansible: - # It's important that Sanity is tested against all stable-X.Y branches - # Testing against `devel` may fail as new tests are added. - - stable-2.15 - - stable-2.16 - - stable-2.17 - - stable-2.18 - - devel - # Ansible-test on various stable branches does not yet work well with cgroups v2. - # Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04 - # image for these stable branches. The list of branches where this is necessary will - # shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28 - # for the latest list. - runs-on: >- - ${{ contains(fromJson( - '["stable-2.9", "stable-2.10", "stable-2.11"]' - ), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }} - steps: - - name: Perform sanity testing - uses: felixfontein/ansible-test-gh-action@main - with: - ansible-core-github-repository-slug: ${{ contains(fromJson('["stable-2.9", "stable-2.10", "stable-2.11"]'), matrix.ansible) && 'ansible-community/eol-ansible' || 'ansible/ansible' }} - ansible-core-version: ${{ matrix.ansible }} - codecov-token: ${{ secrets.CODECOV_TOKEN }} - testing-type: sanity - test-deps: >- - git+https://github.com/ansible-collections/ansible.utils.git,main - git+https://github.com/ansible-collections/ansible.netcommon.git,main - - units: - # Ansible-test on various stable branches does not yet work well with cgroups v2. - # Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04 - # image for these stable branches. The list of branches where this is necessary will - # shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28 - # for the latest list. - runs-on: >- - ${{ contains(fromJson( - '["stable-2.9", "stable-2.10", "stable-2.11"]' - ), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }} - name: Units (Ⓐ${{ matrix.ansible }}) - strategy: - # As soon as the first unit test fails, cancel the others to free up the CI queue - fail-fast: true - matrix: - ansible: - - stable-2.16 - - stable-2.17 - - stable-2.18 - - devel - - steps: - - name: >- - Perform unit testing against - Ansible version ${{ matrix.ansible }} - uses: felixfontein/ansible-test-gh-action@main - with: - ansible-core-github-repository-slug: ${{ contains(fromJson('["stable-2.9", "stable-2.10", "stable-2.11"]'), matrix.ansible) && 'ansible-community/eol-ansible' || 'ansible/ansible' }} - ansible-core-version: ${{ matrix.ansible }} - codecov-token: ${{ secrets.CODECOV_TOKEN }} - testing-type: units - test-deps: >- - git+https://github.com/ansible-collections/ansible.utils.git,main - git+https://github.com/ansible-collections/ansible.netcommon.git,main - - integration: - # Ansible-test on various stable branches does not yet work well with cgroups v2. - # Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04 - # image for these stable branches. The list of branches where this is necessary will - # shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28 - # for the latest list. - runs-on: >- - ${{ contains(fromJson( - '["stable-2.9", "stable-2.10", "stable-2.11"]' - ), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }} - name: I (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }}) - strategy: - fail-fast: false - matrix: - ansible: - - devel - python: - - "3.10" - - "3.11" - - "3.12" - - "3.13" - include: - # 2.15 - - ansible: stable-2.15 - python: "2.7" - - ansible: stable-2.15 - python: "3.6" - - ansible: stable-2.15 - python: "3.7" - # 2.16 - - ansible: stable-2.16 - python: "3.10" - # 2.17 - - ansible: stable-2.17 - python: "3.8" - # 2.18 - - ansible: stable-2.18 - python: "3.9" - - steps: - - name: >- - Perform integration testing against - Ansible version ${{ matrix.ansible }} - under Python ${{ matrix.python }} - uses: felixfontein/ansible-test-gh-action@main - with: - ansible-core-github-repository-slug: ${{ contains(fromJson('["stable-2.9", "stable-2.10", "stable-2.11"]'), matrix.ansible) && 'ansible-community/eol-ansible' || 'ansible/ansible' }} - ansible-core-version: ${{ matrix.ansible }} - codecov-token: ${{ secrets.CODECOV_TOKEN }} - integration-continue-on-error: 'false' - integration-diff: 'false' - integration-retry-on-error: 'true' - test-deps: >- - git+https://github.com/ansible-collections/ansible.utils.git,main - git+https://github.com/ansible-collections/ansible.netcommon.git,main - target-python-version: ${{ matrix.python }} - testing-type: integration diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 7d35e5c..63135a1 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -7,7 +7,7 @@ name: Collection Docs concurrency: group: docs-pr-${{ github.head_ref }} cancel-in-progress: true -on: +'on': pull_request_target: types: [opened, synchronize, reopened, closed] diff --git a/.github/workflows/docs-push.yml b/.github/workflows/docs-push.yml index 80f62f5..3d2c2b1 100644 --- a/.github/workflows/docs-push.yml +++ b/.github/workflows/docs-push.yml @@ -7,7 +7,7 @@ name: Collection Docs concurrency: group: docs-push-${{ github.sha }} cancel-in-progress: true -on: +'on': push: branches: - main diff --git a/.github/workflows/ee.yml b/.github/workflows/ee.yml deleted file mode 100644 index 9d6f3fd..0000000 --- a/.github/workflows/ee.yml +++ /dev/null @@ -1,158 +0,0 @@ ---- -# Copyright (c) Ansible Project -# 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 - -name: execution environment -on: - # Run CI against all pushes (direct commits, also merged PRs), Pull Requests - push: - branches: - - main - - stable-* - pull_request: - # Run CI once per day (at 05:15 UTC) - # This ensures that even if there haven't been commits that we are still testing against latest version of ansible-builder - schedule: - - cron: '15 5 * * *' - -env: - NAMESPACE: community - COLLECTION_NAME: routeros - -jobs: - build: - name: Build and test EE (${{ matrix.name }}) - strategy: - fail-fast: false - matrix: - name: - - '' - ansible_core: - - '' - ansible_runner: - - '' - base_image: - - '' - pre_base: - - '' - extra_vars: - - '' - other_deps: - - '' - exclude: - - ansible_core: '' - include: - - name: ansible-core devel @ RHEL UBI 9 - ansible_core: https://github.com/ansible/ansible/archive/devel.tar.gz - ansible_runner: ansible-runner - other_deps: |2 - python_interpreter: - package_system: python3.11 python3.11-pip python3.11-wheel python3.11-cryptography - python_path: "/usr/bin/python3.11" - base_image: docker.io/redhat/ubi9:latest - pre_base: '"#"' - - name: ansible-core 2.15 @ Rocky Linux 9 - ansible_core: https://github.com/ansible/ansible/archive/stable-2.15.tar.gz - ansible_runner: ansible-runner - base_image: quay.io/rockylinux/rockylinux:9 - pre_base: '"#"' - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - path: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }} - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install ansible-builder and ansible-navigator - run: pip install ansible-builder ansible-navigator - - - name: Verify requirements - run: ansible-builder introspect --sanitize . - - - name: Make sure galaxy.yml has version entry - run: >- - python -c - 'import yaml ; - f = open("galaxy.yml", "rb") ; - data = yaml.safe_load(f) ; - f.close() ; - data["version"] = data.get("version") or "0.0.1" ; - f = open("galaxy.yml", "wb") ; - f.write(yaml.dump(data).encode("utf-8")) ; - f.close() ; - ' - working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }} - - - name: Build collection - run: | - ansible-galaxy collection build --output-path ../../../ - working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }} - - - name: Create files for building execution environment - run: | - COLLECTION_FILENAME="$(ls "${{ env.NAMESPACE }}-${{ env.COLLECTION_NAME }}"-*.tar.gz)" - - # EE config - cat > execution-environment.yml < requirements.yml < - ansible-navigator run - --mode stdout - --container-engine docker - --pull-policy never - --set-environment-variable ANSIBLE_PRIVATE_ROLE_VARS=true - --execution-environment-image test-ee:latest - -v - all.yml - ${{ matrix.extra_vars }} - working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}/tests/ee diff --git a/.github/workflows/extra-tests.yml b/.github/workflows/extra-tests.yml deleted file mode 100644 index 54872aa..0000000 --- a/.github/workflows/extra-tests.yml +++ /dev/null @@ -1,51 +0,0 @@ ---- -# Copyright (c) Ansible Project -# 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 - -name: extra-tests -on: - # Run CI against all pushes (direct commits, also merged PRs), Pull Requests - push: - branches: - - main - - stable-* - pull_request: - # Run CI once per day (at 05:15 UTC) - # This ensures that even if there haven't been commits that we are still testing against latest version of ansible-test for each ansible-base version - schedule: - - cron: '15 5 * * *' -env: - NAMESPACE: community - COLLECTION_NAME: routeros - -jobs: - extra-sanity: - name: Extra Sanity - runs-on: ubuntu-latest - steps: - - - name: Check out code - uses: actions/checkout@v4 - with: - path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install ansible-core - run: pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check - - - name: Install collection dependencies - run: | - git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git ./ansible_collections/community/internal_test_tools - git clone --depth=1 --single-branch https://github.com/ansible-collections/ansible.netcommon.git ./ansible_collections/ansible/netcommon - git clone --depth=1 --single-branch https://github.com/ansible-collections/ansible.utils.git ./ansible_collections/ansible/utils - # NOTE: we're installing with git to work around Galaxy being a huge PITA (https://github.com/ansible/galaxy/issues/2429) - # run: ansible-galaxy collection install community.internal_test_tools ansible.netcommon -p . - - - name: Run sanity tests - run: ../../community/internal_test_tools/tools/run.py --color - working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} diff --git a/.github/workflows/import-galaxy.yml b/.github/workflows/import-galaxy.yml deleted file mode 100644 index 0c0ee40..0000000 --- a/.github/workflows/import-galaxy.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -# Copyright (c) Ansible Project -# 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 - -name: import-galaxy -'on': - # Run CI against all pushes (direct commits, also merged PRs) to main, and all Pull Requests - push: - branches: - - main - - stable-* - pull_request: - -jobs: - import-galaxy: - permissions: - contents: read - name: Test to import built collection artifact with Galaxy importer - uses: ansible-community/github-action-test-galaxy-import/.github/workflows/test-galaxy-import.yml@main diff --git a/.github/workflows/nox.yml b/.github/workflows/nox.yml new file mode 100644 index 0000000..43cce99 --- /dev/null +++ b/.github/workflows/nox.yml @@ -0,0 +1,35 @@ +--- +# Copyright (c) Ansible Project +# 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 + +name: nox +'on': + push: + branches: + - main + - stable-* + pull_request: + # Run CI once per day (at 05:15 UTC) + schedule: + - cron: '15 5 * * *' + workflow_dispatch: + +jobs: + nox: + runs-on: ubuntu-latest + name: "Run extra sanity tests" + steps: + - name: Check out collection + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Run nox + uses: ansible-community/antsibull-nox@main + + ansible-test: + uses: ansible-community/antsibull-nox/.github/workflows/reusable-nox-matrix.yml@main + with: + upload-codecov: true + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml deleted file mode 100644 index b552396..0000000 --- a/.github/workflows/reuse.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -# Copyright (c) Ansible Project -# 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 - -name: Verify REUSE - -on: - push: - branches: - - main - - stable-* - pull_request: - branches: - - main - - stable-* - # Run CI once per day (at 05:15 UTC) - schedule: - - cron: '15 5 * * *' - -jobs: - check: - permissions: - contents: read - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: REUSE Compliance Check - uses: fsfe/reuse-action@v4 diff --git a/.gitignore b/.gitignore index 8e398ff..728531b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /tests/output/ /changelogs/.plugin-cache.yaml +/tests/integration/inventory # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/.reuse/dep5 b/.reuse/dep5 deleted file mode 100644 index 0c3745e..0000000 --- a/.reuse/dep5 +++ /dev/null @@ -1,5 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ - -Files: changelogs/fragments/* -Copyright: Ansible Project -License: GPL-3.0-or-later diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..a6707e2 --- /dev/null +++ b/.yamllint @@ -0,0 +1,53 @@ +--- +# 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 +# SPDX-FileCopyrightText: 2025 Felix Fontein + +extends: default + +ignore: | + /changelogs/ + +rules: + line-length: + max: 300 + level: error + document-start: + present: true + document-end: false + truthy: + level: error + allowed-values: + - 'true' + - 'false' + indentation: + spaces: 2 + indent-sequences: true + key-duplicates: enable + trailing-spaces: enable + new-line-at-end-of-file: disable + hyphens: + max-spaces-after: 1 + empty-lines: + max: 2 + max-start: 0 + max-end: 0 + commas: + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + colons: + max-spaces-before: 0 + max-spaces-after: 1 + brackets: + min-spaces-inside: 0 + max-spaces-inside: 0 + braces: + min-spaces-inside: 0 + max-spaces-inside: 1 + octal-values: + forbid-implicit-octal: true + forbid-explicit-octal: true + comments: + min-spaces-from-content: 1 + comments-indentation: false diff --git a/.yamllint-docs b/.yamllint-docs new file mode 100644 index 0000000..de8947d --- /dev/null +++ b/.yamllint-docs @@ -0,0 +1,54 @@ +--- +# 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 +# SPDX-FileCopyrightText: 2025 Felix Fontein + +extends: default + +ignore: | + /changelogs/ + +rules: + line-length: + max: 160 + level: error + document-start: + present: false + document-end: + present: false + truthy: + level: error + allowed-values: + - 'true' + - 'false' + indentation: + spaces: 2 + indent-sequences: true + key-duplicates: enable + trailing-spaces: enable + new-line-at-end-of-file: disable + hyphens: + max-spaces-after: 1 + empty-lines: + max: 2 + max-start: 0 + max-end: 0 + commas: + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + colons: + max-spaces-before: 0 + max-spaces-after: 1 + brackets: + min-spaces-inside: 0 + max-spaces-inside: 0 + braces: + min-spaces-inside: 0 + max-spaces-inside: 1 + octal-values: + forbid-implicit-octal: true + forbid-explicit-octal: true + comments: + min-spaces-from-content: 1 + comments-indentation: false diff --git a/.yamllint-examples b/.yamllint-examples new file mode 100644 index 0000000..062ac5a --- /dev/null +++ b/.yamllint-examples @@ -0,0 +1,54 @@ +--- +# 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 +# SPDX-FileCopyrightText: 2025 Felix Fontein + +extends: default + +ignore: | + /changelogs/ + +rules: + line-length: + max: 160 + level: error + document-start: + present: true + document-end: + present: false + truthy: + level: error + allowed-values: + - 'true' + - 'false' + indentation: + spaces: 2 + indent-sequences: true + key-duplicates: enable + trailing-spaces: enable + new-line-at-end-of-file: disable + hyphens: + max-spaces-after: 1 + empty-lines: + max: 2 + max-start: 0 + max-end: 0 + commas: + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + colons: + max-spaces-before: 0 + max-spaces-after: 1 + brackets: + min-spaces-inside: 0 + max-spaces-inside: 0 + braces: + min-spaces-inside: 0 + max-spaces-inside: 1 + octal-values: + forbid-implicit-octal: true + forbid-explicit-octal: true + comments: + min-spaces-from-content: 1 + comments-indentation: false diff --git a/.yamllint-extra-docs b/.yamllint-extra-docs new file mode 100644 index 0000000..7e24c0f --- /dev/null +++ b/.yamllint-extra-docs @@ -0,0 +1,53 @@ +--- +# 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 +# SPDX-FileCopyrightText: 2025 Felix Fontein + +extends: default + +ignore: | + /changelogs/ + +rules: + line-length: + max: 160 + level: error + document-start: disable + document-end: + present: false + truthy: + level: error + allowed-values: + - 'true' + - 'false' + indentation: + spaces: 2 + indent-sequences: true + key-duplicates: enable + trailing-spaces: enable + new-line-at-end-of-file: disable + hyphens: + max-spaces-after: 1 + empty-lines: + max: 2 + max-start: 0 + max-end: 0 + commas: + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + colons: + max-spaces-before: 0 + max-spaces-after: 1 + brackets: + min-spaces-inside: 0 + max-spaces-inside: 0 + braces: + min-spaces-inside: 0 + max-spaces-inside: 1 + octal-values: + forbid-implicit-octal: true + forbid-explicit-octal: true + comments: + min-spaces-from-content: 1 + comments-indentation: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 55853f0..88a7efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,132 +2,297 @@ **Topics** -- v3\.0\.0 +- v3\.8\.1 - Release Summary + - Bugfixes +- v3\.8\.0 + - Release Summary + - Minor Changes +- v3\.7\.0 + - Release Summary + - Minor Changes +- v3\.6\.0 + - Release Summary + - Minor Changes +- v3\.5\.0 + - Release Summary + - Minor Changes +- v3\.4\.0 + - Release Summary + - Minor Changes + - Bugfixes +- v3\.3\.0 + - Release Summary + - Minor Changes +- v3\.2\.0 + - Release Summary + - Minor Changes +- v3\.1\.0 + - Release Summary + - Minor Changes + - Bugfixes +- v3\.0\.0 + - Release Summary - Breaking Changes / Porting Guide - Removed Features \(previously deprecated\) - v2\.20\.0 - - Release Summary - - Minor Changes -- v2\.19\.0 - - Release Summary - - Minor Changes -- v2\.18\.0 - - Release Summary - - Minor Changes - - Deprecated Features - - Bugfixes -- v2\.17\.0 - - Release Summary - - Minor Changes -- v2\.16\.0 - - Release Summary - - Minor Changes -- v2\.15\.0 - - Release Summary - - Minor Changes -- v2\.14\.0 - - Release Summary - - Minor Changes -- v2\.13\.0 - - Release Summary - - Minor Changes - - Bugfixes -- v2\.12\.0 - - Release Summary - - Minor Changes -- v2\.11\.0 - Release Summary - - Minor Changes -- v2\.10\.0 + - Minor Changes +- v2\.19\.0 - Release Summary - - Minor Changes - - Bugfixes -- v2\.9\.0 + - Minor Changes +- v2\.18\.0 - Release Summary - - Minor Changes + - Minor Changes + - Deprecated Features - Bugfixes -- v2\.8\.3 +- v2\.17\.0 - Release Summary + - Minor Changes +- v2\.16\.0 + - Release Summary + - Minor Changes +- v2\.15\.0 + - Release Summary + - Minor Changes +- v2\.14\.0 + - Release Summary + - Minor Changes +- v2\.13\.0 + - Release Summary + - Minor Changes + - Bugfixes +- v2\.12\.0 + - Release Summary + - Minor Changes +- v2\.11\.0 + - Release Summary + - Minor Changes +- v2\.10\.0 + - Release Summary + - Minor Changes + - Bugfixes +- v2\.9\.0 + - Release Summary + - Minor Changes + - Bugfixes +- v2\.8\.3 + - Release Summary - Known Issues - v2\.8\.2 - - Release Summary - - Bugfixes -- v2\.8\.1 - - Release Summary - - Bugfixes -- v2\.8\.0 - - Release Summary - - Minor Changes - - Bugfixes -- v2\.7\.0 - - Release Summary - - Minor Changes + - Release Summary - Bugfixes -- v2\.6\.0 - - Release Summary - - Minor Changes +- v2\.8\.1 + - Release Summary - Bugfixes -- v2\.5\.0 - - Release Summary - - Minor Changes +- v2\.8\.0 + - Release Summary + - Minor Changes - Bugfixes -- v2\.4\.0 - - Release Summary - - Minor Changes +- v2\.7\.0 + - Release Summary + - Minor Changes - Bugfixes +- v2\.6\.0 + - Release Summary + - Minor Changes + - Bugfixes +- v2\.5\.0 + - Release Summary + - Minor Changes + - Bugfixes +- v2\.4\.0 + - Release Summary + - Minor Changes + - Bugfixes - Known Issues - v2\.3\.1 - - Release Summary + - Release Summary - Known Issues - v2\.3\.0 - - Release Summary - - Minor Changes - - Bugfixes + - Release Summary + - Minor Changes + - Bugfixes - v2\.2\.1 - - Release Summary - - Bugfixes + - Release Summary + - Bugfixes - v2\.2\.0 - - Release Summary - - Minor Changes - - Bugfixes + - Release Summary + - Minor Changes + - Bugfixes - New Modules - v2\.1\.0 - - Release Summary - - Minor Changes - - Bugfixes + - Release Summary + - Minor Changes + - Bugfixes - New Modules - v2\.0\.0 - - Release Summary - - Minor Changes + - Release Summary + - Minor Changes - Breaking Changes / Porting Guide - - Bugfixes + - Bugfixes - New Plugins - Filter - v1\.2\.0 - - Release Summary - - Minor Changes - - Bugfixes -- v1\.1\.0 - - Release Summary - - Minor Changes -- v1\.0\.1 - - Release Summary - - Bugfixes -- v1\.0\.0 - - Release Summary - - Bugfixes -- v0\.1\.1 - - Release Summary + - Release Summary + - Minor Changes - Bugfixes +- v1\.1\.0 + - Release Summary + - Minor Changes +- v1\.0\.1 + - Release Summary + - Bugfixes +- v1\.0\.0 + - Release Summary + - Bugfixes +- v0\.1\.1 + - Release Summary + - Bugfixes - v0\.1\.0 - - Release Summary - - Minor Changes + - Release Summary + - Minor Changes + + +## v3\.8\.1 + + +### Release Summary + +Bugfix release\. + + +### Bugfixes + +* facts and api\_facts modules \- prevent deprecation warnings when used with ansible\-core 2\.19 \([https\://github\.com/ansible\-collections/community\.routeros/pull/384](https\://github\.com/ansible\-collections/community\.routeros/pull/384)\)\. + + +## v3\.8\.0 + + +### Release Summary + +Feature release\. + + +### Minor Changes + +* api\_info\, api\_modify \- add interface ethernet switch port\-isolation which is supported since RouterOS 6\.43 \([https\://github\.com/ansible\-collections/community\.routeros/pull/375](https\://github\.com/ansible\-collections/community\.routeros/pull/375)\)\. +* api\_info\, api\_modify \- add routing bfd configuration\. Officially stabilized BFD support for BGP and OSPF is available since RouterOS 7\.11 + \([https\://github\.com/ansible\-collections/community\.routeros/pull/375](https\://github\.com/ansible\-collections/community\.routeros/pull/375)\)\. +* api\_modify\, api\_info \- support API path ip ipsec mode\-config \([https\://github\.com/ansible\-collections/community\.routeros/pull/376](https\://github\.com/ansible\-collections/community\.routeros/pull/376)\)\. + + +## v3\.7\.0 + + +### Release Summary + +Feature release\. + + +### Minor Changes + +* api\_find\_and\_modify \- allow to control whether dynamic and/or builtin entries are ignored with the new ignore\_dynamic and ignore\_builtin options \([https\://github\.com/ansible\-collections/community\.routeros/issues/372](https\://github\.com/ansible\-collections/community\.routeros/issues/372)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/373](https\://github\.com/ansible\-collections/community\.routeros/pull/373)\)\. +* api\_info\, api\_modify \- add port\-cost\-mode to interface bridge which is supported since RouterOS 7\.13 \([https\://github\.com/ansible\-collections/community\.routeros/pull/371](https\://github\.com/ansible\-collections/community\.routeros/pull/371)\)\. + + +## v3\.6\.0 + + +### Release Summary + +Feature release\. + + +### Minor Changes + +* api\_info\, api\_modify \- add mdns\-repeat\-ifaces to ip dns for RouterOS 7\.16 and newer \([https\://github\.com/ansible\-collections/community\.routeros/pull/358](https\://github\.com/ansible\-collections/community\.routeros/pull/358)\)\. +* api\_info\, api\_modify \- field name change in routing bgp connection path implemented by RouterOS 7\.19 and newer \([https\://github\.com/ansible\-collections/community\.routeros/pull/360](https\://github\.com/ansible\-collections/community\.routeros/pull/360)\)\. +* api\_info\, api\_modify \- rename is\-responder property in interface wireguard peers to responder for RouterOS 7\.17 and newer \([https\://github\.com/ansible\-collections/community\.routeros/pull/364](https\://github\.com/ansible\-collections/community\.routeros/pull/364)\)\. + + +## v3\.5\.0 + + +### Release Summary + +Feature release\. + + +### Minor Changes + +* api\_info\, api\_modify \- change default for /ip/cloud/ddns\-enabled for RouterOS 7\.17 and newer from yes to auto \([https\://github\.com/ansible\-collections/community\.routeros/pull/350](https\://github\.com/ansible\-collections/community\.routeros/pull/350)\)\. + + +## v3\.4\.0 + + +### Release Summary + +Feature and bugfix release\. + + +### Minor Changes + +* api\_info\, api\_modify \- add support for the ip dns forwarders path implemented by RouterOS 7\.17 and newer \([https\://github\.com/ansible\-collections/community\.routeros/pull/343](https\://github\.com/ansible\-collections/community\.routeros/pull/343)\)\. + + +### Bugfixes + +* api\_info\, api\_modify \- remove the primary key action from the interface wifi provisioning path\, since RouterOS also allows to create completely duplicate entries \([https\://github\.com/ansible\-collections/community\.routeros/issues/344](https\://github\.com/ansible\-collections/community\.routeros/issues/344)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/345](https\://github\.com/ansible\-collections/community\.routeros/pull/345)\)\. + + +## v3\.3\.0 + + +### Release Summary + +Feature release\. + + +### Minor Changes + +* api\_info\, api\_modify \- add missing attribute require\-message\-auth for the radius path which exists since RouterOS version 7\.15 \([https\://github\.com/ansible\-collections/community\.routeros/issues/338](https\://github\.com/ansible\-collections/community\.routeros/issues/338)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/339](https\://github\.com/ansible\-collections/community\.routeros/pull/339)\)\. +* api\_info\, api\_modify \- add the interface 6to4 path\. Used to manage IPv6 tunnels via tunnel\-brokers like HE\, where native IPv6 is not provided \([https\://github\.com/ansible\-collections/community\.routeros/pull/342](https\://github\.com/ansible\-collections/community\.routeros/pull/342)\)\. +* api\_info\, api\_modify \- add the interface wireless access\-list and interface wireless connect\-list paths \([https\://github\.com/ansible\-collections/community\.routeros/issues/284](https\://github\.com/ansible\-collections/community\.routeros/issues/284)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/340](https\://github\.com/ansible\-collections/community\.routeros/pull/340)\)\. +* api\_info\, api\_modify \- add the use\-interface\-duid option for ipv6 dhcp\-client path\. This option prevents issues with Fritzbox modems and routers\, when using virtual interfaces \(like VLANs\) may create duplicated records in hosts config\, this breaks original \"expose\-host\" function\. Also add the script\, custom\-duid and validate\-server\-duid as backport from 7\.15 version update \([https\://github\.com/ansible\-collections/community\.routeros/pull/341](https\://github\.com/ansible\-collections/community\.routeros/pull/341)\)\. + + +## v3\.2\.0 + + +### Release Summary + +Feature release\. + + +### Minor Changes + +* api\_info\, api\_modify \- add support for the routing filter community\-list path implemented by RouterOS 7 and newer \([https\://github\.com/ansible\-collections/community\.routeros/pull/331](https\://github\.com/ansible\-collections/community\.routeros/pull/331)\)\. + + +## v3\.1\.0 + + +### Release Summary + +Bugfix and feature release\. + + +### Minor Changes + +* api\_info\, api\_modify \- add missing fields comment\, next\-pool to ip pool path \([https\://github\.com/ansible\-collections/community\.routeros/pull/327](https\://github\.com/ansible\-collections/community\.routeros/pull/327)\)\. + + +### Bugfixes + +* api\_info\, api\_modify \- fields log and log\-prefix in paths ip firewall filter\, ip firewall mangle\, ip firewall nat\, ip firewall raw now have the correct default values \([https\://github\.com/ansible\-collections/community\.routeros/pull/324](https\://github\.com/ansible\-collections/community\.routeros/pull/324)\)\. ## v3\.0\.0 - + ### Release Summary Major release that drops support for End of Life Python versions and fixes check mode for community\.routeros\.command\. @@ -145,12 +310,12 @@ Major release that drops support for End of Life Python versions and fixes check ## v2\.20\.0 - + ### Release Summary Feature release\. - + ### Minor Changes * api\_info\, api\_modify \- add new parameters from the RouterOS 7\.16 release \([https\://github\.com/ansible\-collections/community\.routeros/pull/323](https\://github\.com/ansible\-collections/community\.routeros/pull/323)\)\. @@ -161,12 +326,12 @@ Feature release\. ## v2\.19\.0 - + ### Release Summary Feature release\. - + ### Minor Changes * api\_info\, api\_modify \- add support for the ip dns adlist path implemented by RouterOS 7\.15 and newer \([https\://github\.com/ansible\-collections/community\.routeros/pull/310](https\://github\.com/ansible\-collections/community\.routeros/pull/310)\)\. @@ -178,12 +343,12 @@ Feature release\. ## v2\.18\.0 - + ### Release Summary Feature release\. - + ### Minor Changes * api\_info \- allow to restrict the output by limiting fields to specific values with the new restrict option \([https\://github\.com/ansible\-collections/community\.routeros/pull/305](https\://github\.com/ansible\-collections/community\.routeros/pull/305)\)\. @@ -199,7 +364,7 @@ Feature release\. * The collection deprecates support for all Ansible/ansible\-base/ansible\-core versions that are currently End of Life\, [according to the ansible\-core support matrix](https\://docs\.ansible\.com/ansible\-core/devel/reference\_appendices/release\_and\_maintenance\.html\#ansible\-core\-support\-matrix)\. This means that the next major release of the collection will no longer support Ansible 2\.9\, ansible\-base 2\.10\, ansible\-core 2\.11\, ansible\-core 2\.12\, ansible\-core 2\.13\, and ansible\-core 2\.14\. - + ### Bugfixes * api\_modify\, api\_info \- change the default of ingress\-filtering in paths interface bridge and interface bridge port back to false for RouterOS before version 7 \([https\://github\.com/ansible\-collections/community\.routeros/pull/305](https\://github\.com/ansible\-collections/community\.routeros/pull/305)\)\. @@ -207,12 +372,12 @@ Feature release\. ## v2\.17\.0 - + ### Release Summary Feature release\. - + ### Minor Changes * api\_info\, api\_modify \- add system health settings path \([https\://github\.com/ansible\-collections/community\.routeros/pull/294](https\://github\.com/ansible\-collections/community\.routeros/pull/294)\)\. @@ -222,12 +387,12 @@ Feature release\. ## v2\.16\.0 - + ### Release Summary Feature release\. - + ### Minor Changes * api\_info\, api\_modify \- add missing path /ppp secret \([https\://github\.com/ansible\-collections/community\.routeros/pull/286](https\://github\.com/ansible\-collections/community\.routeros/pull/286)\)\. @@ -236,12 +401,12 @@ Feature release\. ## v2\.15\.0 - + ### Release Summary Feature release\. - + ### Minor Changes * api\_info\, api\_modify \- Add RouterOS 7\.x support to /mpls ldp path \([https\://github\.com/ansible\-collections/community\.routeros/pull/271](https\://github\.com/ansible\-collections/community\.routeros/pull/271)\)\. @@ -258,12 +423,12 @@ Feature release\. ## v2\.14\.0 - + ### Release Summary Feature release\. - + ### Minor Changes * api\_info\, api\_modify \- add read\-only fields installed\-version\, latest\-version and status in system package update \([https\://github\.com/ansible\-collections/community\.routeros/pull/263](https\://github\.com/ansible\-collections/community\.routeros/pull/263)\)\. @@ -273,18 +438,18 @@ Feature release\. ## v2\.13\.0 - + ### Release Summary Bugfix and feature release\. - + ### Minor Changes * api\_info\, api\_modify \- make path user group modifiable and add comment attribute \([https\://github\.com/ansible\-collections/community\.routeros/issues/256](https\://github\.com/ansible\-collections/community\.routeros/issues/256)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/257](https\://github\.com/ansible\-collections/community\.routeros/pull/257)\)\. * api\_modify\, api\_info \- add support for the ip vrf path in RouterOS 7 \([https\://github\.com/ansible\-collections/community\.routeros/pull/259](https\://github\.com/ansible\-collections/community\.routeros/pull/259)\) - + ### Bugfixes * facts \- fix date not getting removed for idempotent config export \([https\://github\.com/ansible\-collections/community\.routeros/pull/262](https\://github\.com/ansible\-collections/community\.routeros/pull/262)\)\. @@ -292,12 +457,12 @@ Bugfix and feature release\. ## v2\.12\.0 - + ### Release Summary Feature release\. - + ### Minor Changes * api\_info\, api\_modify \- add interface ovpn\-client path \([https\://github\.com/ansible\-collections/community\.routeros/issues/242](https\://github\.com/ansible\-collections/community\.routeros/issues/242)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/244](https\://github\.com/ansible\-collections/community\.routeros/pull/244)\)\. @@ -311,12 +476,12 @@ Feature release\. ## v2\.11\.0 - + ### Release Summary Feature and bugfix release\. - + ### Minor Changes * api\_info\, api\_modify \- add missing DoH parameters doh\-max\-concurrent\-queries\, doh\-max\-server\-connections\, and doh\-timeout to the ip dns path \([https\://github\.com/ansible\-collections/community\.routeros/issues/230](https\://github\.com/ansible\-collections/community\.routeros/issues/230)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/235](https\://github\.com/ansible\-collections/community\.routeros/pull/235)\) @@ -331,12 +496,12 @@ Feature and bugfix release\. ## v2\.10\.0 - + ### Release Summary Bugfix and feature release\. - + ### Minor Changes * api\_info \- add new include\_read\_only option to select behavior for read\-only values\. By default these are not returned \([https\://github\.com/ansible\-collections/community\.routeros/pull/213](https\://github\.com/ansible\-collections/community\.routeros/pull/213)\)\. @@ -360,7 +525,7 @@ Bugfix and feature release\. * api\_modify \- add new handle\_read\_only and handle\_write\_only options to handle the module\'s behavior for read\-only and write\-only fields \([https\://github\.com/ansible\-collections/community\.routeros/pull/213](https\://github\.com/ansible\-collections/community\.routeros/pull/213)\)\. * api\_modify\, api\_info \- support API paths routing id\, routing bgp connection \([https\://github\.com/ansible\-collections/community\.routeros/pull/220](https\://github\.com/ansible\-collections/community\.routeros/pull/220)\)\. - + ### Bugfixes * api\_info\, api\_modify \- in the snmp path\, ensure that engine\-id\-suffix is only available on RouterOS 7\.10\+\, and that engine\-id is read\-only on RouterOS 7\.10\+ \([https\://github\.com/ansible\-collections/community\.routeros/issues/208](https\://github\.com/ansible\-collections/community\.routeros/issues/208)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/218](https\://github\.com/ansible\-collections/community\.routeros/pull/218)\)\. @@ -368,18 +533,18 @@ Bugfix and feature release\. ## v2\.9\.0 - + ### Release Summary Bugfix and feature release\. - + ### Minor Changes * api\_info\, api\_modify \- add path caps\-man channel and enable path caps\-man manager interface \([https\://github\.com/ansible\-collections/community\.routeros/issues/193](https\://github\.com/ansible\-collections/community\.routeros/issues/193)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/194](https\://github\.com/ansible\-collections/community\.routeros/pull/194)\)\. * api\_info\, api\_modify \- add path ip traffic\-flow target \([https\://github\.com/ansible\-collections/community\.routeros/issues/191](https\://github\.com/ansible\-collections/community\.routeros/issues/191)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/192](https\://github\.com/ansible\-collections/community\.routeros/pull/192)\)\. - + ### Bugfixes * api\_modify\, api\_info \- add missing parameter engine\-id\-suffix for the snmp path \([https\://github\.com/ansible\-collections/community\.routeros/issues/189](https\://github\.com/ansible\-collections/community\.routeros/issues/189)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/190](https\://github\.com/ansible\-collections/community\.routeros/pull/190)\)\. @@ -387,7 +552,7 @@ Bugfix and feature release\. ## v2\.8\.3 - + ### Release Summary Maintenance release with updated documentation\. @@ -408,12 +573,12 @@ for the rendered HTML version of the documentation of the latest release\. ## v2\.8\.2 - + ### Release Summary Bugfix release\. - + ### Bugfixes * api\_modify\, api\_info \- add missing parameter tls for the tool e\-mail path \([https\://github\.com/ansible\-collections/community\.routeros/issues/179](https\://github\.com/ansible\-collections/community\.routeros/issues/179)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/180](https\://github\.com/ansible\-collections/community\.routeros/pull/180)\)\. @@ -421,12 +586,12 @@ Bugfix release\. ## v2\.8\.1 - + ### Release Summary Bugfix release\. - + ### Bugfixes * facts \- do not crash in CLI output preprocessing in unexpected situations during line unwrapping \([https\://github\.com/ansible\-collections/community\.routeros/issues/170](https\://github\.com/ansible\-collections/community\.routeros/issues/170)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/177](https\://github\.com/ansible\-collections/community\.routeros/pull/177)\)\. @@ -434,12 +599,12 @@ Bugfix release\. ## v2\.8\.0 - + ### Release Summary Bugfix and feature release\. - + ### Minor Changes * api\_modify \- adapt data for API paths ip dhcp\-server network \([https\://github\.com/ansible\-collections/community\.routeros/pull/156](https\://github\.com/ansible\-collections/community\.routeros/pull/156)\)\. @@ -449,7 +614,7 @@ Bugfix and feature release\. * api\_modify \- support API paths ip firewall layer7\-protocol \([https\://github\.com/ansible\-collections/community\.routeros/pull/153](https\://github\.com/ansible\-collections/community\.routeros/pull/153)\)\. * command \- workaround for extra characters in stdout in RouterOS versions between 6\.49 and 7\.1\.5 \([https\://github\.com/ansible\-collections/community\.routeros/issues/62](https\://github\.com/ansible\-collections/community\.routeros/issues/62)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/161](https\://github\.com/ansible\-collections/community\.routeros/pull/161)\)\. - + ### Bugfixes * api\_info\, api\_modify \- fix default and remove behavior for dhcp\-options in path ip dhcp\-client \([https\://github\.com/ansible\-collections/community\.routeros/issues/148](https\://github\.com/ansible\-collections/community\.routeros/issues/148)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/154](https\://github\.com/ansible\-collections/community\.routeros/pull/154)\)\. @@ -459,17 +624,17 @@ Bugfix and feature release\. ## v2\.7\.0 - + ### Release Summary Bugfix and feature release\. - + ### Minor Changes * api\_modify\, api\_info \- support API paths ip arp\, ip firewall raw\, ipv6 firewall raw \([https\://github\.com/ansible\-collections/community\.routeros/pull/144](https\://github\.com/ansible\-collections/community\.routeros/pull/144)\)\. - + ### Bugfixes * api\_modify\, api\_info \- defaults corrected for fields in interface wireguard peers API path \([https\://github\.com/ansible\-collections/community\.routeros/pull/144](https\://github\.com/ansible\-collections/community\.routeros/pull/144)\)\. @@ -477,18 +642,18 @@ Bugfix and feature release\. ## v2\.6\.0 - + ### Release Summary Regular bugfix and feature release\. - + ### Minor Changes * api\_modify\, api\_info \- add field regexp to ip dns static \([https\://github\.com/ansible\-collections/community\.routeros/issues/141](https\://github\.com/ansible\-collections/community\.routeros/issues/141)\)\. * api\_modify\, api\_info \- support API paths interface wireguard\, interface wireguard peers \([https\://github\.com/ansible\-collections/community\.routeros/pull/143](https\://github\.com/ansible\-collections/community\.routeros/pull/143)\)\. - + ### Bugfixes * api\_modify \- do not use name as a unique key in ip dns static \([https\://github\.com/ansible\-collections/community\.routeros/issues/141](https\://github\.com/ansible\-collections/community\.routeros/issues/141)\)\. @@ -497,17 +662,17 @@ Regular bugfix and feature release\. ## v2\.5\.0 - + ### Release Summary Feature and bugfix release\. - + ### Minor Changes * api\_info\, api\_modify \- support API paths interface ethernet poe\, interface gre6\, interface vrrp and also support all previously missing fields of entries in ip dhcp\-server \([https\://github\.com/ansible\-collections/community\.routeros/pull/137](https\://github\.com/ansible\-collections/community\.routeros/pull/137)\)\. - + ### Bugfixes * api\_modify \- address\-pool field of entries in API path ip dhcp\-server is not required anymore \([https\://github\.com/ansible\-collections/community\.routeros/pull/137](https\://github\.com/ansible\-collections/community\.routeros/pull/137)\)\. @@ -515,12 +680,12 @@ Feature and bugfix release\. ## v2\.4\.0 - + ### Release Summary Feature release improving the api\* modules\. - + ### Minor Changes * api\* modules \- Add new option force\_no\_cert to connect with ADH ciphers \([https\://github\.com/ansible\-collections/community\.routeros/pull/124](https\://github\.com/ansible\-collections/community\.routeros/pull/124)\)\. @@ -541,7 +706,7 @@ Feature release improving the api\* modules\. * api\_modify\, api\_info \- support for fields blackhole\, pref\-src\, routing\-table\, suppress\-hw\-offload\, type\, vrf\-interface in ip route path \([https\://github\.com/ansible\-collections/community\.routeros/pull/131](https\://github\.com/ansible\-collections/community\.routeros/pull/131)\)\. * api\_modify\, api\_info \- support paths system ntp client servers and system ntp server available in ROS7\, as well as new fields servers\, mode\, and vrf for system ntp client \([https\://github\.com/ansible\-collections/community\.routeros/pull/122](https\://github\.com/ansible\-collections/community\.routeros/pull/122)\)\. - + ### Bugfixes * api\_modify \- ip route entry can be defined without the need of gateway field\, which is correct for unreachable/blackhole type of routes \([https\://github\.com/ansible\-collections/community\.routeros/pull/131](https\://github\.com/ansible\-collections/community\.routeros/pull/131)\)\. @@ -559,7 +724,7 @@ Feature release improving the api\* modules\. ## v2\.3\.1 - + ### Release Summary Maintenance release with improved documentation\. @@ -572,19 +737,19 @@ Maintenance release with improved documentation\. ## v2\.3\.0 - + ### Release Summary Feature and bugfix release\. - + ### Minor Changes * The collection repository conforms to the [REUSE specification](https\://reuse\.software/spec/) except for the changelog fragments \([https\://github\.com/ansible\-collections/community\.routeros/pull/108](https\://github\.com/ansible\-collections/community\.routeros/pull/108)\)\. * api\* modules \- added timeout parameter \([https\://github\.com/ansible\-collections/community\.routeros/pull/109](https\://github\.com/ansible\-collections/community\.routeros/pull/109)\)\. * api\_modify\, api\_info \- support API path ip firewall mangle \([https\://github\.com/ansible\-collections/community\.routeros/pull/110](https\://github\.com/ansible\-collections/community\.routeros/pull/110)\)\. - + ### Bugfixes * api\_modify\, api\_info \- make API path ip dhcp\-server support script\, and ip firewall nat support in\-interface and in\-interface\-list \([https\://github\.com/ansible\-collections/community\.routeros/pull/110](https\://github\.com/ansible\-collections/community\.routeros/pull/110)\)\. @@ -592,12 +757,12 @@ Feature and bugfix release\. ## v2\.2\.1 - + ### Release Summary Bugfix release\. - + ### Bugfixes * api\_modify\, api\_info \- make API path ip dhcp\-server lease support server\=all \([https\://github\.com/ansible\-collections/community\.routeros/issues/104](https\://github\.com/ansible\-collections/community\.routeros/issues/104)\, [https\://github\.com/ansible\-collections/community\.routeros/pull/107](https\://github\.com/ansible\-collections/community\.routeros/pull/107)\)\. @@ -606,17 +771,17 @@ Bugfix release\. ## v2\.2\.0 - + ### Release Summary New feature release\. - + ### Minor Changes * All software licenses are now in the LICENSES/ directory of the collection root\. Moreover\, SPDX\-License\-Identifier\: is used to declare the applicable license for every file that is not automatically generated \([https\://github\.com/ansible\-collections/community\.routeros/pull/101](https\://github\.com/ansible\-collections/community\.routeros/pull/101)\)\. - + ### Bugfixes * Include LICENSES/BSD\-2\-Clause\.txt file for the routeros module utils \([https\://github\.com/ansible\-collections/community\.routeros/pull/101](https\://github\.com/ansible\-collections/community\.routeros/pull/101)\)\. @@ -630,12 +795,12 @@ New feature release\. ## v2\.1\.0 - + ### Release Summary Feature and bugfix release with new modules\. - + ### Minor Changes * Added a community\.routeros\.api module defaults group\. Use with group/community\.routeros\.api to provide options for all API\-based modules \([https\://github\.com/ansible\-collections/community\.routeros/pull/89](https\://github\.com/ansible\-collections/community\.routeros/pull/89)\)\. @@ -644,7 +809,7 @@ Feature and bugfix release with new modules\. * api \- update query to accept symbolic parameters \([https\://github\.com/ansible\-collections/community\.routeros/pull/63](https\://github\.com/ansible\-collections/community\.routeros/pull/63)\)\. * api\* modules \- allow to set an encoding other than the default ASCII for communicating with the API \([https\://github\.com/ansible\-collections/community\.routeros/pull/95](https\://github\.com/ansible\-collections/community\.routeros/pull/95)\)\. - + ### Bugfixes * query \- fix query function check for \.id vs\. id arguments to not conflict with routeros arguments like identity \([https\://github\.com/ansible\-collections/community\.routeros/pull/68](https\://github\.com/ansible\-collections/community\.routeros/pull/68)\, [https\://github\.com/ansible\-collections/community\.routeros/issues/67](https\://github\.com/ansible\-collections/community\.routeros/issues/67)\)\. @@ -659,12 +824,12 @@ Feature and bugfix release with new modules\. ## v2\.0\.0 - + ### Release Summary A new major release with breaking changes in the behavior of community\.routeros\.api and community\.routeros\.command\. - + ### Minor Changes * api \- make validation of WHERE for query more strict \([https\://github\.com/ansible\-collections/community\.routeros/pull/53](https\://github\.com/ansible\-collections/community\.routeros/pull/53)\)\. @@ -678,7 +843,7 @@ A new major release with breaking changes in the behavior of community\.ro * api \- splitting commands no longer uses a naive split by whitespace\, but a more RouterOS CLI compatible splitting algorithm \([https\://github\.com/ansible\-collections/community\.routeros/pull/45](https\://github\.com/ansible\-collections/community\.routeros/pull/45)\)\. * command \- the module now always indicates that a change happens\. If this is not correct\, please use changed\_when to determine the correct changed status for a task \([https\://github\.com/ansible\-collections/community\.routeros/pull/50](https\://github\.com/ansible\-collections/community\.routeros/pull/50)\)\. - + ### Bugfixes * api \- improve splitting of WHERE queries \([https\://github\.com/ansible\-collections/community\.routeros/pull/47](https\://github\.com/ansible\-collections/community\.routeros/pull/47)\)\. @@ -700,12 +865,12 @@ A new major release with breaking changes in the behavior of community\.ro ## v1\.2\.0 - + ### Release Summary Bugfix and feature release\. - + ### Minor Changes * Avoid internal ansible\-core module\_utils in favor of equivalent public API available since at least Ansible 2\.9 \([https\://github\.com/ansible\-collections/community\.routeros/pull/38](https\://github\.com/ansible\-collections/community\.routeros/pull/38)\)\. @@ -713,7 +878,7 @@ Bugfix and feature release\. * api \- rename option ssl to tls\, and keep the old name as an alias \([https\://github\.com/ansible\-collections/community\.routeros/pull/37](https\://github\.com/ansible\-collections/community\.routeros/pull/37)\)\. * fact \- add fact ansible\_net\_config\_nonverbose to get idempotent config \(no date\, no verbose\) \([https\://github\.com/ansible\-collections/community\.routeros/pull/23](https\://github\.com/ansible\-collections/community\.routeros/pull/23)\)\. - + ### Bugfixes * api \- when using TLS/SSL\, remove explicit cipher configuration to insecure values\, which also makes it impossible to connect to newer RouterOS versions \([https\://github\.com/ansible\-collections/community\.routeros/pull/34](https\://github\.com/ansible\-collections/community\.routeros/pull/34)\)\. @@ -721,12 +886,12 @@ Bugfix and feature release\. ## v1\.1\.0 - + ### Release Summary This release allow dashes in usernames for SSH\-based modules\. - + ### Minor Changes * command \- added support for a dash \(\-\) in username \([https\://github\.com/ansible\-collections/community\.routeros/pull/18](https\://github\.com/ansible\-collections/community\.routeros/pull/18)\)\. @@ -735,12 +900,12 @@ This release allow dashes in usernames for SSH\-based modules\. ## v1\.0\.1 - + ### Release Summary Maintenance release with a bugfix for api\. - + ### Bugfixes * api \- remove id to \.id as default requirement which conflicts with RouterOS id configuration parameter \([https\://github\.com/ansible\-collections/community\.routeros/pull/15](https\://github\.com/ansible\-collections/community\.routeros/pull/15)\)\. @@ -748,12 +913,12 @@ Maintenance release with a bugfix for api\. ## v1\.0\.0 - + ### Release Summary This is the first production \(non\-prerelease\) release of community\.routeros\. - + ### Bugfixes * routeros terminal plugin \- allow slashes in hostnames for terminal detection\. Without this\, slashes in hostnames will result in connection timeouts \([https\://github\.com/ansible\-collections/community\.network/pull/138](https\://github\.com/ansible\-collections/community\.network/pull/138)\)\. @@ -761,12 +926,12 @@ This is the first production \(non\-prerelease\) release of community\.rou ## v0\.1\.1 - + ### Release Summary Small improvements and bugfixes over the initial release\. - + ### Bugfixes * api \- fix crash when the ssl parameter is used \([https\://github\.com/ansible\-collections/community\.routeros/pull/3](https\://github\.com/ansible\-collections/community\.routeros/pull/3)\)\. @@ -774,12 +939,12 @@ Small improvements and bugfixes over the initial release\. ## v0\.1\.0 - + ### Release Summary The community\.routeros continues the work on the Ansible RouterOS modules from their state in community\.network 1\.2\.0\. The changes listed here are thus relative to the modules community\.network\.routeros\_\*\. - + ### Minor Changes * facts \- now also collecting data about BGP and OSPF \([https\://github\.com/ansible\-collections/community\.network/pull/101](https\://github\.com/ansible\-collections/community\.network/pull/101)\)\. diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4b511c3..2e73ef4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,142 @@ Community RouterOS Release Notes .. contents:: Topics +v3.8.1 +====== + +Release Summary +--------------- + +Bugfix release. + +Bugfixes +-------- + +- facts and api_facts modules - prevent deprecation warnings when used with ansible-core 2.19 (https://github.com/ansible-collections/community.routeros/pull/384). + +v3.8.0 +====== + +Release Summary +--------------- + +Feature release. + +Minor Changes +------------- + +- api_info, api_modify - add ``interface ethernet switch port-isolation`` which is supported since RouterOS 6.43 (https://github.com/ansible-collections/community.routeros/pull/375). +- api_info, api_modify - add ``routing bfd configuration``. Officially stabilized BFD support for BGP and OSPF is available since RouterOS 7.11 + (https://github.com/ansible-collections/community.routeros/pull/375). +- api_modify, api_info - support API path ``ip ipsec mode-config`` (https://github.com/ansible-collections/community.routeros/pull/376). + +v3.7.0 +====== + +Release Summary +--------------- + +Feature release. + +Minor Changes +------------- + +- api_find_and_modify - allow to control whether ``dynamic`` and/or ``builtin`` entries are ignored with the new ``ignore_dynamic`` and ``ignore_builtin`` options (https://github.com/ansible-collections/community.routeros/issues/372, https://github.com/ansible-collections/community.routeros/pull/373). +- api_info, api_modify - add ``port-cost-mode`` to ``interface bridge`` which is supported since RouterOS 7.13 (https://github.com/ansible-collections/community.routeros/pull/371). + +v3.6.0 +====== + +Release Summary +--------------- + +Feature release. + +Minor Changes +------------- + +- api_info, api_modify - add ``mdns-repeat-ifaces`` to ``ip dns`` for RouterOS 7.16 and newer (https://github.com/ansible-collections/community.routeros/pull/358). +- api_info, api_modify - field name change in ``routing bgp connection`` path implemented by RouterOS 7.19 and newer (https://github.com/ansible-collections/community.routeros/pull/360). +- api_info, api_modify - rename ``is-responder`` property in ``interface wireguard peers`` to ``responder`` for RouterOS 7.17 and newer (https://github.com/ansible-collections/community.routeros/pull/364). + +v3.5.0 +====== + +Release Summary +--------------- + +Feature release. + +Minor Changes +------------- + +- api_info, api_modify - change default for ``/ip/cloud/ddns-enabled`` for RouterOS 7.17 and newer from ``yes`` to ``auto`` (https://github.com/ansible-collections/community.routeros/pull/350). + +v3.4.0 +====== + +Release Summary +--------------- + +Feature and bugfix release. + +Minor Changes +------------- + +- api_info, api_modify - add support for the ``ip dns forwarders`` path implemented by RouterOS 7.17 and newer (https://github.com/ansible-collections/community.routeros/pull/343). + +Bugfixes +-------- + +- api_info, api_modify - remove the primary key ``action`` from the ``interface wifi provisioning`` path, since RouterOS also allows to create completely duplicate entries (https://github.com/ansible-collections/community.routeros/issues/344, https://github.com/ansible-collections/community.routeros/pull/345). + +v3.3.0 +====== + +Release Summary +--------------- + +Feature release. + +Minor Changes +------------- + +- api_info, api_modify - add missing attribute ``require-message-auth`` for the ``radius`` path which exists since RouterOS version 7.15 (https://github.com/ansible-collections/community.routeros/issues/338, https://github.com/ansible-collections/community.routeros/pull/339). +- api_info, api_modify - add the ``interface 6to4`` path. Used to manage IPv6 tunnels via tunnel-brokers like HE, where native IPv6 is not provided (https://github.com/ansible-collections/community.routeros/pull/342). +- api_info, api_modify - add the ``interface wireless access-list`` and ``interface wireless connect-list`` paths (https://github.com/ansible-collections/community.routeros/issues/284, https://github.com/ansible-collections/community.routeros/pull/340). +- api_info, api_modify - add the ``use-interface-duid`` option for ``ipv6 dhcp-client`` path. This option prevents issues with Fritzbox modems and routers, when using virtual interfaces (like VLANs) may create duplicated records in hosts config, this breaks original "expose-host" function. Also add the ``script``, ``custom-duid`` and ``validate-server-duid`` as backport from 7.15 version update (https://github.com/ansible-collections/community.routeros/pull/341). + +v3.2.0 +====== + +Release Summary +--------------- + +Feature release. + +Minor Changes +------------- + +- api_info, api_modify - add support for the ``routing filter community-list`` path implemented by RouterOS 7 and newer (https://github.com/ansible-collections/community.routeros/pull/331). + +v3.1.0 +====== + +Release Summary +--------------- + +Bugfix and feature release. + +Minor Changes +------------- + +- api_info, api_modify - add missing fields ``comment``, ``next-pool`` to ``ip pool`` path (https://github.com/ansible-collections/community.routeros/pull/327). + +Bugfixes +-------- + +- api_info, api_modify - fields ``log`` and ``log-prefix`` in paths ``ip firewall filter``, ``ip firewall mangle``, ``ip firewall nat``, ``ip firewall raw`` now have the correct default values (https://github.com/ansible-collections/community.routeros/pull/324). + v3.0.0 ====== diff --git a/README.md b/README.md index f7b488d..7d4f4c3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ SPDX-License-Identifier: GPL-3.0-or-later --> # Community RouterOS Collection -[![CI](https://github.com/ansible-collections/community.routeros/workflows/CI/badge.svg?event=push)](https://github.com/ansible-collections/community.routeros/actions) +[![Documentation](https://img.shields.io/badge/docs-brightgreen.svg)](https://docs.ansible.com/ansible/devel/collections/community/routeros/) +[![CI](https://github.com/ansible-collections/community.routeros/actions/workflows/nox.yml/badge.svg?branch=main)](https://github.com/ansible-collections/community.routeros/actions) [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.routeros)](https://codecov.io/gh/ansible-collections/community.routeros) [![REUSE status](https://api.reuse.software/badge/github.com/ansible-collections/community.routeros)](https://api.reuse.software/info/github.com/ansible-collections/community.routeros) @@ -33,11 +34,11 @@ For more information about communication, see the [Ansible communication guide]( ## Tested with Ansible -Tested with the current ansible-core 2.15, ansible-core 2.16, ansible-core 2.17, and ansible-core 2.18 releases and the current development version of ansible-core. Ansible 2.9, ansible-base 2.10, and ansible-core versions before 2.15.0 are not supported. +Tested with the current ansible-core 2.15, ansible-core 2.16, ansible-core 2.17, ansible-core 2.18, and ansible-core 2.19 releases and the current development version of ansible-core. Ansible 2.9, ansible-base 2.10, and ansible-core versions before 2.15.0 are not supported. ## External requirements -The exact requirements for every module are listed in the module documentation. +The exact requirements for every module are listed in the module documentation. ### Supported connections @@ -207,4 +208,4 @@ See [LICENSES/GPL-3.0-or-later.txt](https://github.com/ansible-collections/commu Parts of the collection are licensed under the [BSD 2-Clause license](https://github.com/ansible-collections/community.routeros/blob/main/LICENSES/BSD-2-Clause.txt). -All files have a machine readable `SDPX-License-Identifier:` comment denoting its respective license(s) or an equivalent entry in an accompanying `.license` file. Only changelog fragments (which will not be part of a release) are covered by a blanket statement in `.reuse/dep5`. This conforms to the [REUSE specification](https://reuse.software/spec/). +All files have a machine readable `SDPX-License-Identifier:` comment denoting its respective license(s) or an equivalent entry in an accompanying `.license` file. Only changelog fragments (which will not be part of a release) are covered by a blanket statement in `REUSE.toml`. This conforms to the [REUSE specification](https://reuse.software/spec/). diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 0000000..ff95bb8 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,11 @@ +# Copyright (c) Ansible Project +# 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 + +version = 1 + +[[annotations]] +path = "changelogs/fragments/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "Ansible Project" +SPDX-License-Identifier = "GPL-3.0-or-later" diff --git a/antsibull-nox.toml b/antsibull-nox.toml new file mode 100644 index 0000000..72982fa --- /dev/null +++ b/antsibull-nox.toml @@ -0,0 +1,97 @@ +# Copyright (c) Ansible Project +# 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 + +[collection_sources] +"community.internal_test_tools" = "git+https://github.com/ansible-collections/community.internal_test_tools.git,main" +"community.netcommon" = "git+https://github.com/ansible-collections/ansible.netcommon.git,main" +"community.utils" = "git+https://github.com/ansible-collections/ansible.utils.git,main" + +[sessions] + +[sessions.lint] +run_isort = false +run_black = false +run_flake8 = false +run_pylint = false +run_yamllint = true +yamllint_config = ".yamllint" +yamllint_config_plugins = ".yamllint-docs" +yamllint_config_plugins_examples = ".yamllint-examples" +yamllint_config_extra_docs = ".yamllint-extra-docs" +run_mypy = false + +[sessions.docs_check] +validate_collection_refs="all" +codeblocks_restrict_types = [ + "ansible-output", + "ini", + "yaml", + "yaml+jinja", +] +codeblocks_restrict_type_exact_case = true +codeblocks_allow_without_type = false +codeblocks_allow_literal_blocks = false + +[sessions.license_check] + +[sessions.extra_checks] +run_no_unwanted_files = true +no_unwanted_files_module_extensions = [".py"] +no_unwanted_files_yaml_extensions = [".yml"] +run_action_groups = true +run_no_trailing_whitespace = true +no_trailing_whitespace_skip_directories = [ + "tests/unit/plugins/modules/fixtures/", +] +run_avoid_characters = true + +[[sessions.extra_checks.action_groups_config]] +name = "api" +pattern = "^api.*$" +exclusions = [] +doc_fragment = "community.routeros.attributes.actiongroup_api" + +[[sessions.extra_checks.avoid_character_group]] +name = "tab" +regex = "\\x09" + +[sessions.build_import_check] +run_galaxy_importer = true + +[sessions.ansible_test_sanity] +include_devel = true + +[sessions.ansible_test_units] +include_devel = true + +[sessions.ansible_test_integration_w_default_container] +include_devel = true +controller_python_versions_only = true + +[sessions.ansible_test_integration_w_default_container.core_python_versions] +"2.15" = ["2.7", "3.6", "3.7"] +"2.16" = ["3.10"] +"2.17" = ["3.8"] +"2.18" = ["3.9"] +"2.19" = ["3.11"] + +[[sessions.ee_check.execution_environments]] +name = "devel-ubi-9" +description = "ansible-core devel @ RHEL UBI 9" +test_playbooks = ["tests/ee/all.yml"] +config.images.base_image.name = "docker.io/redhat/ubi9:latest" +config.dependencies.ansible_core.package_pip = "https://github.com/ansible/ansible/archive/devel.tar.gz" +config.dependencies.ansible_runner.package_pip = "ansible-runner" +config.dependencies.python_interpreter.package_system = "python3.12 python3.12-pip python3.12-wheel python3.12-cryptography" +config.dependencies.python_interpreter.python_path = "/usr/bin/python3.12" +runtime_environment = {"ANSIBLE_PRIVATE_ROLE_VARS" = "true"} + +[[sessions.ee_check.execution_environments]] +name = "2.15-rocky-9" +description = "ansible-core 2.15 @ Rocky Linux 9" +test_playbooks = ["tests/ee/all.yml"] +config.images.base_image.name = "quay.io/rockylinux/rockylinux:9" +config.dependencies.ansible_core.package_pip = "https://github.com/ansible/ansible/archive/stable-2.15.tar.gz" +config.dependencies.ansible_runner.package_pip = "ansible-runner" +runtime_environment = {"ANSIBLE_PRIVATE_ROLE_VARS" = "true"} diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 09b5b6d..df9d0e0 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -809,3 +809,138 @@ releases: fragments: - 3.0.0.yml release_date: '2024-10-20' + 3.1.0: + changes: + bugfixes: + - api_info, api_modify - fields ``log`` and ``log-prefix`` in paths ``ip firewall + filter``, ``ip firewall mangle``, ``ip firewall nat``, ``ip firewall raw`` + now have the correct default values (https://github.com/ansible-collections/community.routeros/pull/324). + minor_changes: + - api_info, api_modify - add missing fields ``comment``, ``next-pool`` to + ``ip pool`` path (https://github.com/ansible-collections/community.routeros/pull/327). + release_summary: Bugfix and feature release. + fragments: + - 3.1.0.yml + - 324-fix-firewall-log-and-log-prefix.yaml + - 327-add-missing-ip-pool-fields.yml + release_date: '2024-12-02' + 3.2.0: + changes: + minor_changes: + - api_info, api_modify - add support for the ``routing filter community-list`` + path implemented by RouterOS 7 and newer (https://github.com/ansible-collections/community.routeros/pull/331). + release_summary: Feature release. + fragments: + - 3.2.0.yml + - 331-add-routing-filter-community-list.yml + release_date: '2024-12-30' + 3.3.0: + changes: + minor_changes: + - api_info, api_modify - add missing attribute ``require-message-auth`` for + the ``radius`` path which exists since RouterOS version 7.15 (https://github.com/ansible-collections/community.routeros/issues/338, + https://github.com/ansible-collections/community.routeros/pull/339). + - api_info, api_modify - add the ``interface 6to4`` path. Used to manage IPv6 + tunnels via tunnel-brokers like HE, where native IPv6 is not provided (https://github.com/ansible-collections/community.routeros/pull/342). + - api_info, api_modify - add the ``interface wireless access-list`` and ``interface + wireless connect-list`` paths (https://github.com/ansible-collections/community.routeros/issues/284, + https://github.com/ansible-collections/community.routeros/pull/340). + - api_info, api_modify - add the ``use-interface-duid`` option for ``ipv6 + dhcp-client`` path. This option prevents issues with Fritzbox modems and + routers, when using virtual interfaces (like VLANs) may create duplicated + records in hosts config, this breaks original "expose-host" function. Also + add the ``script``, ``custom-duid`` and ``validate-server-duid`` as backport + from 7.15 version update (https://github.com/ansible-collections/community.routeros/pull/341). + release_summary: Feature release. + fragments: + - 3.3.0.yml + - 339-add-require-message-auth-for-radius.yml + - 340-add-interface-wireless-access-and-connect-list.yml + - 341-add-dhcpv6-client-use-interface-duid.yml + - 342-add-interface-6to4.yml + release_date: '2025-01-27' + 3.4.0: + changes: + bugfixes: + - api_info, api_modify - remove the primary key ``action`` from the ``interface + wifi provisioning`` path, since RouterOS also allows to create completely + duplicate entries (https://github.com/ansible-collections/community.routeros/issues/344, + https://github.com/ansible-collections/community.routeros/pull/345). + minor_changes: + - api_info, api_modify - add support for the ``ip dns forwarders`` path implemented + by RouterOS 7.17 and newer (https://github.com/ansible-collections/community.routeros/pull/343). + release_summary: Feature and bugfix release. + fragments: + - 3.4.0.yml + - 343-add-ip-dns-forwarders.yml + - 345-interface-wifi-provisioning.yml + release_date: '2025-02-24' + 3.5.0: + changes: + minor_changes: + - api_info, api_modify - change default for ``/ip/cloud/ddns-enabled`` for + RouterOS 7.17 and newer from ``yes`` to ``auto`` (https://github.com/ansible-collections/community.routeros/pull/350). + release_summary: Feature release. + fragments: + - 3.5.0.yml + - 350-ip-cloud-ddns-enabled-auto.yml + release_date: '2025-03-22' + 3.6.0: + changes: + minor_changes: + - api_info, api_modify - add ``mdns-repeat-ifaces`` to ``ip dns`` for RouterOS + 7.16 and newer (https://github.com/ansible-collections/community.routeros/pull/358). + - api_info, api_modify - field name change in ``routing bgp connection`` path + implemented by RouterOS 7.19 and newer (https://github.com/ansible-collections/community.routeros/pull/360). + - api_info, api_modify - rename ``is-responder`` property in ``interface wireguard + peers`` to ``responder`` for RouterOS 7.17 and newer (https://github.com/ansible-collections/community.routeros/pull/364). + release_summary: Feature release. + fragments: + - 3.6.0.yml + - 358-mdns-repeat-ifaces.yml + - 360-bgp-connection-afi.yml + - 364-wireguard-responder.yml + release_date: '2025-04-21' + 3.7.0: + changes: + minor_changes: + - api_find_and_modify - allow to control whether ``dynamic`` and/or ``builtin`` + entries are ignored with the new ``ignore_dynamic`` and ``ignore_builtin`` + options (https://github.com/ansible-collections/community.routeros/issues/372, + https://github.com/ansible-collections/community.routeros/pull/373). + - api_info, api_modify - add ``port-cost-mode`` to ``interface bridge`` which + is supported since RouterOS 7.13 (https://github.com/ansible-collections/community.routeros/pull/371). + release_summary: Feature release. + fragments: + - 3.7.0.yml + - 371-add-bridge-port-cost-mode.yml + - 373-api_find_and_modify-dynamic-builtin.yml + release_date: '2025-05-31' + 3.8.0: + changes: + minor_changes: + - api_info, api_modify - add ``interface ethernet switch port-isolation`` + which is supported since RouterOS 6.43 (https://github.com/ansible-collections/community.routeros/pull/375). + - 'api_info, api_modify - add ``routing bfd configuration``. Officially stabilized + BFD support for BGP and OSPF is available since RouterOS 7.11 + + (https://github.com/ansible-collections/community.routeros/pull/375). + + ' + - api_modify, api_info - support API path ``ip ipsec mode-config`` (https://github.com/ansible-collections/community.routeros/pull/376). + release_summary: Feature release. + fragments: + - 3.8.0.yml + - 375-port_isolation-and-routing_bfd_configuration.yml + - 376-ipsec-mode-config.yml + release_date: '2025-06-14' + 3.8.1: + changes: + bugfixes: + - facts and api_facts modules - prevent deprecation warnings when used with + ansible-core 2.19 (https://github.com/ansible-collections/community.routeros/pull/384). + release_summary: Bugfix release. + fragments: + - 3.8.1.yml + - 384-warnings.yml + release_date: '2025-07-26' diff --git a/changelogs/config.yaml b/changelogs/config.yaml index a02e530..39b7120 100644 --- a/changelogs/config.yaml +++ b/changelogs/config.yaml @@ -7,9 +7,9 @@ changelog_filename_template: ../CHANGELOG.rst changelog_filename_version_depth: 0 changes_file: changelog.yaml changes_format: combined +ignore_other_fragment_extensions: true keep_fragments: false mention_ancestor: true -flatmap: true new_plugins_after_name: removed_features notesdir: fragments output_formats: @@ -40,3 +40,4 @@ use_fqcn: true add_plugin_period: true changelog_nice_yaml: true changelog_sort: version +vcs: auto diff --git a/changelogs/fragments/3.9.0.yml b/changelogs/fragments/3.9.0.yml new file mode 100644 index 0000000..63e0654 --- /dev/null +++ b/changelogs/fragments/3.9.0.yml @@ -0,0 +1 @@ +release_summary: Feature release. \ No newline at end of file diff --git a/changelogs/fragments/380-ipv6-settings.yml b/changelogs/fragments/380-ipv6-settings.yml new file mode 100644 index 0000000..a8c6e9b --- /dev/null +++ b/changelogs/fragments/380-ipv6-settings.yml @@ -0,0 +1,3 @@ +minor_changes: + - api_info, api_modify - add ``disable-link-local-address`` and ``stale-neighbor-timeout`` fields to ``ipv6 settings`` (https://github.com/ansible-collections/community.routeros/pull/380). + - api_info, api_modify - adjust neighbor limit fields in ``ipv6 settings`` to match RouterOS 7.18 and newer (https://github.com/ansible-collections/community.routeros/pull/380). diff --git a/changelogs/fragments/381-logging-cef.yml b/changelogs/fragments/381-logging-cef.yml new file mode 100644 index 0000000..849f82a --- /dev/null +++ b/changelogs/fragments/381-logging-cef.yml @@ -0,0 +1,2 @@ +minor_changes: + - api_info, api modify - add ``remote-log-format``, ``remote-protocol``, and ``event-delimiter`` to ``system logging action`` (https://github.com/ansible-collections/community.routeros/pull/381). diff --git a/changelogs/fragments/382-mangle-passthrough.yml b/changelogs/fragments/382-mangle-passthrough.yml new file mode 100644 index 0000000..2123dfa --- /dev/null +++ b/changelogs/fragments/382-mangle-passthrough.yml @@ -0,0 +1,2 @@ +minor_changes: + - api_info, api_modify - set ``passthrough`` default in ``ip firewall mangle`` to ``true`` for RouterOS 7.19 and newer (https://github.com/ansible-collections/community.routeros/pull/382). diff --git a/changelogs/fragments/385-vrf-support-for-ovpn-server.yml b/changelogs/fragments/385-vrf-support-for-ovpn-server.yml new file mode 100644 index 0000000..4790a9d --- /dev/null +++ b/changelogs/fragments/385-vrf-support-for-ovpn-server.yml @@ -0,0 +1,7 @@ +--- +minor_changes: + - api_info, api_modify - since RouterOS 7.17 VRF is supported for OVPN server. + It now supports multiple entries, while ``api_modify`` so far only accepted a single entry. + The ``interface ovpn-server server`` path now allows multiple entries + on RouterOS 7.17 and newer + (https://github.com/ansible-collections/community.routeros/pull/383). diff --git a/changelogs/fragments/386-fix-pattern-to-handle-long-identity.yml b/changelogs/fragments/386-fix-pattern-to-handle-long-identity.yml new file mode 100644 index 0000000..b9a749f --- /dev/null +++ b/changelogs/fragments/386-fix-pattern-to-handle-long-identity.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - routeros terminal plugin - fix ``terminal_stdout_re`` pattern to handle long system identities when connecting to RouterOS through SSH (https://github.com/ansible-collections/community.routeros/pull/386). diff --git a/docs/docsite/rst/api-guide.rst b/docs/docsite/rst/api-guide.rst index 6140d81..9df17fc 100644 --- a/docs/docsite/rst/api-guide.rst +++ b/docs/docsite/rst/api-guide.rst @@ -57,7 +57,7 @@ This results in the following output: } PLAY RECAP ******************************************************************************************************* - localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 + localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Check out the documentation of the :ansplugin:`community.routeros.api module ` for details on the options. @@ -191,7 +191,7 @@ When this playbook completed successfully, you should be able to use the HTTPS a .. code-block:: yaml+jinja - community.routeros.api: - ... + # ... tls: true validate_certs: true validate_cert_hostname: true diff --git a/docs/docsite/rst/ssh-guide.rst b/docs/docsite/rst/ssh-guide.rst index 4b0eb20..ac1f65b 100644 --- a/docs/docsite/rst/ssh-guide.rst +++ b/docs/docsite/rst/ssh-guide.rst @@ -66,22 +66,22 @@ With the above inventory, you can use the following playbook to execute ``/syste gather_facts: false tasks: - - name: Gather system resources - community.routeros.command: - commands: - - /system resource print - register: system_resource_print + - name: Gather system resources + community.routeros.command: + commands: + - /system resource print + register: system_resource_print - - name: Show system resources - debug: - var: system_resource_print.stdout_lines + - name: Show system resources + debug: + var: system_resource_print.stdout_lines - - name: Gather facts - community.routeros.facts: + - name: Gather facts + community.routeros.facts: - - name: Show a fact - debug: - msg: "First IP address: {{ ansible_net_all_ipv4_addresses[0] }}" + - name: Show a fact + debug: + msg: "First IP address: {{ ansible_net_all_ipv4_addresses[0] }}" This results in the following output: @@ -126,4 +126,4 @@ This results in the following output: } PLAY RECAP ******************************************************************************************************* - router : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 + router : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 diff --git a/galaxy.yml b/galaxy.yml index 1508467..f2ba31a 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -7,7 +7,7 @@ namespace: community name: routeros -version: 3.0.0 +version: 3.9.0 readme: README.md authors: - Egor Zaitsev (github.com/heuels) @@ -16,7 +16,7 @@ authors: description: Modules and plugins for MikroTik RouterOS license: - GPL-3.0-or-later -#license_file: COPYING +# license_file: COPYING tags: - network - mikrotik diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..16187f9 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,53 @@ +# Copyright (c) Ansible Project +# 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 + +# The following metadata allows Python runners and nox to install the required +# dependencies for running this Python script: +# +# /// script +# dependencies = ["nox>=2025.02.09", "antsibull-nox"] +# /// + +import os +import sys + +import nox + + +# We try to import antsibull-nox, and if that doesn't work, provide a more useful +# error message to the user. +try: + import antsibull_nox +except ImportError: + print("You need to install antsibull-nox in the same Python environment as nox.") + sys.exit(1) + + +IN_CI = os.environ.get("CI") == "true" + + +antsibull_nox.load_antsibull_nox_toml() + + +@nox.session(name="update-docs", default=True) +def update_docs_fragments(session: nox.Session) -> None: + """ + Update/check auto-generated parts of docs fragments. + """ + session.install("ansible-core") + prepare = antsibull_nox.sessions.prepare_collections( + session, install_in_site_packages=True + ) + if not prepare: + return + data = ["python", "tests/update-docs.py"] + if IN_CI: + data.append("--lint") + session.run(*data) + + +# Allow to run the noxfile with `python noxfile.py`, `pipx run noxfile.py`, or similar. +# Requires nox >= 2025.02.09 +if __name__ == "__main__": + nox.main() diff --git a/plugins/cliconf/routeros.py b/plugins/cliconf/routeros.py index 412627b..dc2f4a9 100644 --- a/plugins/cliconf/routeros.py +++ b/plugins/cliconf/routeros.py @@ -5,15 +5,14 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' ---- +DOCUMENTATION = r""" author: "Egor Zaitsev (@heuels)" name: routeros short_description: Use routeros cliconf to run command on MikroTik RouterOS platform description: - - This routeros plugin provides low level abstraction apis for - sending and receiving CLI commands from MikroTik RouterOS network devices. -''' + - This routeros plugin provides low level abstraction APIs for sending and receiving CLI commands from MikroTik RouterOS + network devices. +""" import re import json diff --git a/plugins/doc_fragments/api.py b/plugins/doc_fragments/api.py index b0ad979..5b0b411 100644 --- a/plugins/doc_fragments/api.py +++ b/plugins/doc_fragments/api.py @@ -10,7 +10,7 @@ __metaclass__ = type class ModuleDocFragment(object): - DOCUMENTATION = r''' + DOCUMENTATION = r""" options: hostname: description: @@ -43,17 +43,16 @@ options: - ssl port: description: - - RouterOS api port. If O(tls) is set, port will apply to TLS/SSL connection. + - RouterOS API port. If O(tls) is set, port will apply to TLS/SSL connection. - Defaults are V(8728) for the HTTP API, and V(8729) for the HTTPS API. type: int force_no_cert: description: - Set to V(true) to connect without a certificate when O(tls=true). - See also O(validate_certs). - - B(Note:) this forces the use of anonymous Diffie-Hellman (ADH) ciphers. The protocol is susceptible - to Man-in-the-Middle attacks, because the keys used in the exchange are not authenticated. - Instead of simply connecting without a certificate to "make things work" have a look at - O(validate_certs) and O(ca_path). + - B(Note:) this forces the use of anonymous Diffie-Hellman (ADH) ciphers. The protocol is susceptible to Man-in-the-Middle + attacks, because the keys used in the exchange are not authenticated. Instead of simply connecting without a certificate + to "make things work" have a look at O(validate_certs) and O(ca_path). type: bool default: false version_added: 2.4.0 @@ -61,10 +60,9 @@ options: description: - Set to V(false) to skip validation of TLS certificates. - See also O(validate_cert_hostname). Only used when O(tls=true). - - B(Note:) instead of simply deactivating certificate validations to "make things work", - please consider creating your own CA certificate and using it to sign certificates used - for your router. You can tell the module about your CA certificate with the O(ca_path) - option. + - B(Note:) instead of simply deactivating certificate validations to "make things work", please consider creating your + own CA certificate and using it to sign certificates used for your router. You can tell the module about your CA certificate + with the O(ca_path) option. type: bool default: true version_added: 1.2.0 @@ -93,10 +91,10 @@ requirements: - Python >= 3.6 (for librouteros) seealso: - ref: ansible_collections.community.routeros.docsite.api-guide - description: How to connect to RouterOS devices with the RouterOS API -''' + description: How to connect to RouterOS devices with the RouterOS API. +""" - RESTRICT = r''' + RESTRICT = r""" options: restrict: type: list @@ -115,24 +113,21 @@ options: values: description: - The values of the field to limit to. - - >- - Note that the types of the values are important. If you provide a string V("0"), - and librouteros converts the value returned by the API to the integer V(0), - then this will not match. If you are not sure, better include both variants: - both the string and the integer. + - 'Note that the types of the values are important. If you provide a string V("0"), and librouteros converts the + value returned by the API to the integer V(0), then this will not match. If you are not sure, better include both + variants: both the string and the integer.' type: list elements: raw regex: description: - A regular expression matching values of the field to limit to. - Note that all values will be converted to strings before matching. - - It is not possible to match disabled values with regular expressions. - Set O(restrict[].match_disabled=true) if you also want to match disabled values. + - It is not possible to match disabled values with regular expressions. Set O(restrict[].match_disabled=true) if + you also want to match disabled values. type: str invert: description: - - Invert the condition. This affects O(restrict[].match_disabled), O(restrict[].values), - and O(restrict[].regex). + - Invert the condition. This affects O(restrict[].match_disabled), O(restrict[].values), and O(restrict[].regex). type: bool default: false -''' +""" diff --git a/plugins/doc_fragments/attributes.py b/plugins/doc_fragments/attributes.py index e18a48f..b8e68bf 100644 --- a/plugins/doc_fragments/attributes.py +++ b/plugins/doc_fragments/attributes.py @@ -11,88 +11,102 @@ __metaclass__ = type class ModuleDocFragment(object): # Standard documentation fragment - DOCUMENTATION = r''' + DOCUMENTATION = r""" options: {} attributes: - check_mode: - description: Can run in C(check_mode) and return changed status prediction without modifying target. - diff_mode: - description: Will return details on what has changed (or possibly needs changing in C(check_mode)), when in diff mode. - platform: - description: Target OS/families that can be operated against. - support: N/A -''' + check_mode: + description: Can run in C(check_mode) and return changed status prediction without modifying target. + diff_mode: + description: Will return details on what has changed (or possibly needs changing in C(check_mode)), when in diff mode. + platform: + description: Target OS/families that can be operated against. + support: N/A + idempotent: + description: + - When run twice in a row outside check mode, with the same arguments, the second invocation indicates no change. + - This assumes that the system controlled/queried by the module has not changed in a relevant way. +""" + + # Should be used together with the standard fragment + IDEMPOTENT_NOT_MODIFY_STATE = r""" +options: {} +attributes: + idempotent: + support: full + details: + - This action does not modify state. +""" # Should be used together with the standard fragment INFO_MODULE = r''' options: {} attributes: - check_mode: - support: full - details: - - This action does not modify state. - diff_mode: - support: N/A - details: - - This action does not modify state. + check_mode: + support: full + details: + - This action does not modify state. + diff_mode: + support: N/A + details: + - This action does not modify state. ''' ACTIONGROUP_API = r''' options: {} attributes: - action_group: - description: Use C(group/community.routeros.api) in C(module_defaults) to set defaults for this module. - support: full - membership: - - community.routeros.api + action_group: + description: Use C(group/community.routeros.api) in C(module_defaults) to set defaults for this module. + support: full + membership: + - community.routeros.api ''' - CONN = r''' + CONN = r""" options: {} attributes: - become: - description: Is usable alongside C(become) keywords. - connection: - description: Uses the target's configured connection information to execute code on it. - delegation: - description: Can be used in conjunction with C(delegate_to) and related keywords. -''' + become: + description: Is usable alongside C(become) keywords. + connection: + description: Uses the target's configured connection information to execute code on it. + delegation: + description: Can be used in conjunction with C(delegate_to) and related keywords. +""" - FACTS = r''' + FACTS = r""" options: {} attributes: - facts: - description: Action returns an C(ansible_facts) dictionary that will update existing host facts. -''' + facts: + description: Action returns an C(ansible_facts) dictionary that will update existing host facts. +""" # Should be used together with the standard fragment and the FACTS fragment FACTS_MODULE = r''' options: {} attributes: - check_mode: - support: full - details: - - This action does not modify state. - diff_mode: - support: N/A - details: - - This action does not modify state. - facts: - support: full + check_mode: + support: full + details: + - This action does not modify state. + diff_mode: + support: N/A + details: + - This action does not modify state. + facts: + support: full ''' - FILES = r''' + FILES = r""" options: {} attributes: - safe_file_operations: - description: Uses Ansible's strict file operation functions to ensure proper permissions and avoid data corruption. -''' + safe_file_operations: + description: Uses Ansible's strict file operation functions to ensure proper permissions and avoid data corruption. +""" - FLOW = r''' + FLOW = r""" options: {} attributes: - action: - description: Indicates this has a corresponding action plugin so some parts of the options can be executed on the controller. - async: - description: Supports being used with the C(async) keyword. -''' + action: + description: Indicates this has a corresponding action plugin so some parts of the options can be executed on the controller. + async: + description: Supports being used with the C(async) keyword. +""" diff --git a/plugins/filter/join.yml b/plugins/filter/join.yml index 9ff8a50..f25d739 100644 --- a/plugins/filter/join.yml +++ b/plugins/filter/join.yml @@ -20,6 +20,7 @@ DOCUMENTATION: - Felix Fontein (@felixfontein) EXAMPLES: | + --- - name: Join arguments for a RouterOS CLI command ansible.builtin.set_fact: arguments: "{{ ['foo=bar', 'comment=foo is bar'] | community.routeros.join }}" diff --git a/plugins/filter/list_to_dict.yml b/plugins/filter/list_to_dict.yml index 6e7992d..7b7c5b1 100644 --- a/plugins/filter/list_to_dict.yml +++ b/plugins/filter/list_to_dict.yml @@ -30,6 +30,7 @@ DOCUMENTATION: - Felix Fontein (@felixfontein) EXAMPLES: | + --- - name: Convert a list to a dictionary ansible.builtin.set_fact: dictionary: "{{ ['foo=bar', 'comment=foo is bar'] | community.routeros.list_to_dict }}" diff --git a/plugins/filter/quote_argument.yml b/plugins/filter/quote_argument.yml index 26a1f04..477a15e 100644 --- a/plugins/filter/quote_argument.yml +++ b/plugins/filter/quote_argument.yml @@ -19,9 +19,11 @@ DOCUMENTATION: - Felix Fontein (@felixfontein) EXAMPLES: | + --- - name: Quote a RouterOS CLI command argument ansible.builtin.set_fact: - quoted: "{{ 'comment=this is a "comment"' | community.routeros.quote_argument }}" + quoted: >- + {{ 'comment=this is a "comment"' | community.routeros.quote_argument }} # Should result in 'comment="this is a \"comment\""' RETURN: diff --git a/plugins/filter/quote_argument_value.yml b/plugins/filter/quote_argument_value.yml index 839895b..b4da246 100644 --- a/plugins/filter/quote_argument_value.yml +++ b/plugins/filter/quote_argument_value.yml @@ -19,9 +19,11 @@ DOCUMENTATION: - Felix Fontein (@felixfontein) EXAMPLES: | + --- - name: Quote a RouterOS CLI command argument's value ansible.builtin.set_fact: - quoted: "{{ 'this is a "comment"' | community.routeros.quote_argument_value }}" + quoted: >- + {{ 'this is a "comment"' | community.routeros.quote_argument_value }} # Should result in '"this is a \"comment\""' RETURN: diff --git a/plugins/filter/split.yml b/plugins/filter/split.yml index 5fc4b30..cb4ba88 100644 --- a/plugins/filter/split.yml +++ b/plugins/filter/split.yml @@ -19,9 +19,11 @@ DOCUMENTATION: - Felix Fontein (@felixfontein) EXAMPLES: | + --- - name: Split command into list of arguments ansible.builtin.set_fact: - argument_list: "{{ 'foo=bar comment="foo is bar" baz' | community.routeros.split }}" + argument_list: >- + {{ 'foo=bar comment="foo is bar" baz' | community.routeros.split }} # Should result in ['foo=bar', 'comment=foo is bar', 'baz'] RETURN: diff --git a/plugins/module_utils/_api_data.py b/plugins/module_utils/_api_data.py index 847f5b1..2b44b8b 100644 --- a/plugins/module_utils/_api_data.py +++ b/plugins/module_utils/_api_data.py @@ -226,6 +226,25 @@ def join_path(path): # 3. All bold attributes go into the `primary_keys` list -- this is not always true! PATHS = { + ('interface', '6to4'): APIData( + unversioned=VersionedAPIData( + fully_understood=True, + primary_keys=('name', ), + fields={ + 'clamp-tcp-mss': KeyInfo(default=True), + 'comment': KeyInfo(can_disable=True, remove_value=''), + 'disabled': KeyInfo(default=False), + 'dont-fragment': KeyInfo(default=False), + 'dscp': KeyInfo(default='inherit'), + 'ipsec-secret': KeyInfo(can_disable=True), + 'keepalive': KeyInfo(default='10s,10', can_disable=True), + 'local-address': KeyInfo(default='0.0.0.0'), + 'mtu': KeyInfo(default='auto'), + 'name': KeyInfo(), + 'remote-address': KeyInfo(required=True), + } + ), + ), ('interface', 'bonding'): APIData( unversioned=VersionedAPIData( fully_understood=True, @@ -262,6 +281,7 @@ PATHS = { versioned_fields=[ ([('7.0', '<')], 'ingress-filtering', KeyInfo(default=False)), ([('7.0', '>=')], 'ingress-filtering', KeyInfo(default=True)), + ([('7.13', '>=')], 'port-cost-mode', KeyInfo(default='long')), ([('7.16', '>=')], 'forward-reserved-addresses', KeyInfo(default=False)), ([('7.16', '>=')], 'max-learned-entries', KeyInfo(default='auto')), ], @@ -632,13 +652,22 @@ PATHS = { ), ('ip', 'ipsec', 'mode-config'): APIData( unversioned=VersionedAPIData( - unknown_mechanism=True, - # primary_keys=('default', ), + fully_understood=True, + primary_keys=('name', ), + versioned_fields=[ + ([('6.43', '>=')], 'responder', KeyInfo(default=False)), + ([('6.44', '>=')], 'address', KeyInfo(can_disable=True, remove_value='0.0.0.0')), + ], fields={ - 'default': KeyInfo(), + 'address-pool': KeyInfo(can_disable=True, remove_value='none'), + 'address-prefix-length': KeyInfo(), + 'comment': KeyInfo(can_disable=True, remove_value=''), 'name': KeyInfo(), - 'responder': KeyInfo(), - 'use-responder-dns': KeyInfo(), + 'split-dns': KeyInfo(can_disable=True, remove_value=''), + 'split-include': KeyInfo(can_disable=True, remove_value=''), + 'src-address-list': KeyInfo(can_disable=True, remove_value=''), + 'static-dns': KeyInfo(can_disable=True, remove_value=''), + 'system-dns': KeyInfo(default=False), }, ), ), @@ -708,7 +737,9 @@ PATHS = { fully_understood=True, primary_keys=('name', ), fields={ + 'comment': KeyInfo(), 'name': KeyInfo(), + 'next-pool': KeyInfo(), 'ranges': KeyInfo(), }, ), @@ -893,6 +924,20 @@ PATHS = { )), ], ), + ('routing', 'filter', 'community-list'): APIData( + versioned=[ + ('7', '>=', VersionedAPIData( + fully_understood=True, + fields={ + 'list': KeyInfo(required=True), + 'comment': KeyInfo(can_disable=True, remove_value=''), + 'disabled': KeyInfo(can_disable=True), + 'communities': KeyInfo(can_disable=True), + 'regexp': KeyInfo(can_disable=True), + }, + )), + ], + ), ('routing', 'ospf', 'instance'): APIData( unversioned=VersionedAPIData( fully_understood=True, @@ -1508,13 +1553,19 @@ PATHS = { fully_understood=True, versioned_fields=[ ([('7.16', '>=')], 'multipath-hash-policy', KeyInfo(default='l3')), + ([('7.17', '>=')], 'disable-link-local-address', KeyInfo(default=False)), + ([('7.17', '>=')], 'stale-neighbor-timeout', KeyInfo(default=60)), + ([('7.18', '>=')], 'allow-fast-path', KeyInfo(default=True)), + ([('7.18', '<')], 'max-neighbor-entries', KeyInfo(default=8192)), + ([('7.18', '>=')], 'min-neighbor-entries', KeyInfo()), + ([('7.18', '>=')], 'soft-max-neighbor-entries', KeyInfo()), + ([('7.18', '>=')], 'max-neighbor-entries', KeyInfo()), ], fields={ 'accept-redirects': KeyInfo(default='yes-if-forwarding-disabled'), 'accept-router-advertisements': KeyInfo(default='yes-if-forwarding-disabled'), 'disable-ipv6': KeyInfo(default=False), 'forward': KeyInfo(default=True), - 'max-neighbor-entries': KeyInfo(default=8192), }, ), ), @@ -1613,23 +1664,46 @@ PATHS = { ), ), ('interface', 'ovpn-server', 'server'): APIData( - unversioned=VersionedAPIData( - single_value=True, - fully_understood=True, - fields={ - 'auth': KeyInfo(), - 'cipher': KeyInfo(), - 'default-profile': KeyInfo(default='default'), - 'enabled': KeyInfo(default=False), - 'keepalive-timeout': KeyInfo(default=60), - 'mac-address': KeyInfo(), - 'max-mtu': KeyInfo(default=1500), - 'mode': KeyInfo(default='ip'), - 'netmask': KeyInfo(default=24), - 'port': KeyInfo(default=1194), - 'require-client-certificate': KeyInfo(default=False), - }, - ), + versioned=[ + ('7.17', '>=', VersionedAPIData( + fully_understood=True, + fields={ + 'auth': KeyInfo(), + 'cipher': KeyInfo(), + 'default-profile': KeyInfo(default='default'), + 'enabled': KeyInfo(default=False), + 'keepalive-timeout': KeyInfo(default=60), + 'mac-address': KeyInfo(), + 'max-mtu': KeyInfo(default=1500), + 'mode': KeyInfo(default='ip'), + 'name': KeyInfo(default=''), + 'netmask': KeyInfo(default=24), + 'port': KeyInfo(default=1194), + 'protocol': KeyInfo(default='tcp'), + 'require-client-certificate': KeyInfo(default=False), + 'vrf': KeyInfo(default='main'), + }, + )), + ('7.17', '<', VersionedAPIData( + single_value=True, + fully_understood=True, + fields={ + 'auth': KeyInfo(), + 'cipher': KeyInfo(), + 'default-profile': KeyInfo(default='default'), + 'enabled': KeyInfo(default=False), + 'keepalive-timeout': KeyInfo(default=60), + 'mac-address': KeyInfo(), + 'max-mtu': KeyInfo(default=1500), + 'mode': KeyInfo(default='ip'), + 'name': KeyInfo(default=''), + 'netmask': KeyInfo(default=24), + 'port': KeyInfo(default=1194), + 'protocol': KeyInfo(default='tcp'), + 'require-client-certificate': KeyInfo(default=False), + }, + )) + ] ), ('interface', 'pppoe-server', 'server'): APIData( unversioned=VersionedAPIData( @@ -2002,7 +2076,6 @@ PATHS = { versioned=[ ('7.13', '>=', VersionedAPIData( fully_understood=True, - primary_keys=('action', ), fields={ 'action': KeyInfo(default='none'), 'address-ranges': KeyInfo(can_disable=True), @@ -2407,7 +2480,8 @@ PATHS = { }, versioned_fields=[ ([('7.15', '>=')], 'name', KeyInfo()), - ([('7.15', '>=')], 'is-responder', KeyInfo()), + ([('7.15', '>='), ('7.17', '<')], 'is-responder', KeyInfo()), + ([('7.17', '>=')], 'responder', KeyInfo()), ], ), ), @@ -2529,6 +2603,30 @@ PATHS = { }, ), ), + ('interface', 'wireless', 'access-list'): APIData( + unversioned=VersionedAPIData( + fully_understood=True, + fields={ + 'allow-signal-out-of-range': KeyInfo(default='10s'), + 'ap-tx-limit': KeyInfo(default=0), + 'authentication': KeyInfo(default=True), + 'client-tx-limit': KeyInfo(default=0), + 'comment': KeyInfo(can_disable=True, remove_value=''), + 'disabled': KeyInfo(default=False), + 'forwarding': KeyInfo(default=True), + 'interface': KeyInfo(default='any'), + 'mac-address': KeyInfo(default='00:00:00:00:00:00'), + 'management-protection-key': KeyInfo(default=''), + 'private-algo': KeyInfo(default='none'), + 'private-key': KeyInfo(default=''), + 'private-pre-shared-key': KeyInfo(default=''), + 'signal-range': KeyInfo(default='-120..120'), + 'time': KeyInfo(), + 'vlan-id': KeyInfo(default=1), + 'vlan-mode': KeyInfo(default='default'), + }, + ), + ), ('interface', 'wireless', 'cap'): APIData( unversioned=VersionedAPIData( single_value=True, @@ -2547,6 +2645,41 @@ PATHS = { }, ), ), + ('interface', 'wireless', 'connect-list'): APIData( + unversioned=VersionedAPIData( + fully_understood=True, + fields={ + '3gpp': KeyInfo(default=''), + 'allow-signal-out-of-range': KeyInfo(default='10s'), + 'area-prefix': KeyInfo(default=''), + 'comment': KeyInfo(can_disable=True, remove_value=''), + 'connect': KeyInfo(default=True), + 'disabled': KeyInfo(default=False), + 'interface': KeyInfo(required=True), + 'interworking': KeyInfo(default='any'), + 'iw-asra': KeyInfo(default='any'), + 'iw-authentication-types': KeyInfo(), + 'iw-connection-capabilities': KeyInfo(), + 'iw-esr': KeyInfo(default='any'), + 'iw-hessid': KeyInfo(default='00:00:00:00:00:00'), + 'iw-hotspot20': KeyInfo(default='any'), + 'iw-hotspot20-dgaf': KeyInfo(default='any'), + 'iw-internet': KeyInfo(default='any'), + 'iw-ipv4-availability': KeyInfo(default='any'), + 'iw-ipv6-availability': KeyInfo(default='any'), + 'iw-network-type': KeyInfo(default='wildcard'), + 'iw-realms': KeyInfo(), + 'iw-roaming-ois': KeyInfo(default=''), + 'iw-uesa': KeyInfo(default='any'), + 'iw-venue': KeyInfo(default='any'), + 'mac-address': KeyInfo(default='00:00:00:00:00:00'), + 'security-profile': KeyInfo(default='none'), + 'signal-range': KeyInfo(default='-120..120'), + 'ssid': KeyInfo(default=''), + 'wireless-protocol': KeyInfo(default='any'), + }, + ), + ), ('interface', 'wireless', 'security-profiles'): APIData( unversioned=VersionedAPIData( fully_understood=True, @@ -2684,8 +2817,11 @@ PATHS = { unversioned=VersionedAPIData( single_value=True, fully_understood=True, + versioned_fields=[ + ([('7.17', '<')], 'ddns-enabled', KeyInfo(default=False)), + ([('7.17', '>=')], 'ddns-enabled', KeyInfo(default='auto')), + ], fields={ - 'ddns-enabled': KeyInfo(default=False), 'ddns-update-interval': KeyInfo(default='none'), 'update-time': KeyInfo(default=True), }, @@ -2871,6 +3007,7 @@ PATHS = { ([('7.8', '>=')], 'doh-max-concurrent-queries', KeyInfo(default=50)), ([('7.8', '>=')], 'doh-max-server-connections', KeyInfo(default=5)), ([('7.8', '>=')], 'doh-timeout', KeyInfo(default='5s')), + ([('7.16', '>=')], 'mdns-repeat-ifaces', KeyInfo()), ], fields={ 'allow-remote-requests': KeyInfo(), @@ -2903,6 +3040,22 @@ PATHS = { )), ], ), + ('ip', 'dns', 'forwarders'): APIData( + versioned=[ + ('7.17', '>=', VersionedAPIData( + fully_understood=True, + required_one_of=[['dns-servers', 'doh-servers']], + fields={ + 'comment': KeyInfo(can_disable=True, remove_value=''), + 'disabled': KeyInfo(default=False), + 'dns-servers': KeyInfo(default=''), + 'doh-servers': KeyInfo(default=''), + 'name': KeyInfo(required=True), + 'verify-doh-cert': KeyInfo(default=True), + }, + )), + ], + ), ('ip', 'dns', 'static'): APIData( unversioned=VersionedAPIData( fully_understood=True, @@ -2984,8 +3137,8 @@ PATHS = { 'jump-target': KeyInfo(can_disable=True), 'layer7-protocol': KeyInfo(can_disable=True), 'limit': KeyInfo(can_disable=True), - 'log': KeyInfo(can_disable=True), - 'log-prefix': KeyInfo(can_disable=True), + 'log': KeyInfo(default=False), + 'log-prefix': KeyInfo(default=''), 'nth': KeyInfo(can_disable=True), 'out-bridge-port': KeyInfo(can_disable=True), 'out-bridge-port-list': KeyInfo(can_disable=True), @@ -3021,6 +3174,10 @@ PATHS = { unversioned=VersionedAPIData( fully_understood=True, stratify_keys=('chain', ), + versioned_fields=[ + ([('7.19', '<')], 'passthrough', KeyInfo(can_disable=True)), + ([('7.19', '>=')], 'passthrough', KeyInfo(default=True)), + ], fields={ 'action': KeyInfo(), 'address-list': KeyInfo(can_disable=True), @@ -3055,8 +3212,8 @@ PATHS = { 'jump-target': KeyInfo(can_disable=True), 'layer7-protocol': KeyInfo(can_disable=True), 'limit': KeyInfo(can_disable=True), - 'log': KeyInfo(can_disable=True), - 'log-prefix': KeyInfo(can_disable=True), + 'log': KeyInfo(default=False), + 'log-prefix': KeyInfo(default=''), 'new-connection-mark': KeyInfo(can_disable=True), 'new-dscp': KeyInfo(can_disable=True), 'new-mss': KeyInfo(can_disable=True), @@ -3072,7 +3229,6 @@ PATHS = { 'p2p': KeyInfo(can_disable=True), 'packet-mark': KeyInfo(can_disable=True), 'packet-size': KeyInfo(can_disable=True), - 'passthrough': KeyInfo(can_disable=True), 'per-connection-classifier': KeyInfo(can_disable=True), 'port': KeyInfo(can_disable=True), 'priority': KeyInfo(can_disable=True), @@ -3135,8 +3291,8 @@ PATHS = { 'jump-target': KeyInfo(can_disable=True), 'layer7-protocol': KeyInfo(can_disable=True), 'limit': KeyInfo(can_disable=True), - 'log': KeyInfo(can_disable=True), - 'log-prefix': KeyInfo(can_disable=True), + 'log': KeyInfo(default=False), + 'log-prefix': KeyInfo(default=''), 'nth': KeyInfo(can_disable=True), 'out-bridge-port': KeyInfo(can_disable=True), 'out-bridge-port-list': KeyInfo(can_disable=True), @@ -3198,8 +3354,8 @@ PATHS = { 'ipv4-options': KeyInfo(can_disable=True), 'jump-target': KeyInfo(can_disable=True), 'limit': KeyInfo(can_disable=True), - 'log': KeyInfo(can_disable=True), - 'log-prefix': KeyInfo(can_disable=True), + 'log': KeyInfo(default=False), + 'log-prefix': KeyInfo(default=''), 'nth': KeyInfo(can_disable=True), 'out-bridge-port': KeyInfo(can_disable=True), 'out-bridge-port-list': KeyInfo(can_disable=True), @@ -3469,6 +3625,14 @@ PATHS = { 'request': KeyInfo(), 'use-peer-dns': KeyInfo(default=True), }, + versioned_fields=[ + # Mikrotik does not provide exact version in official changelogs. + # The 7.15 version is the earliest, found option in router config backups: + ([('7.15', '>=')], 'script', KeyInfo(default='')), + ([('7.15', '>=')], 'custom-duid', KeyInfo(default='')), + ([('7.15', '>=')], 'use-interface-duid', KeyInfo(default=False)), + ([('7.15', '>=')], 'validate-server-duid', KeyInfo(default=True)), + ], ), ), ('ipv6', 'dhcp-server'): APIData( @@ -4002,6 +4166,9 @@ PATHS = { 'src-address': KeyInfo(default='0.0.0.0'), 'timeout': KeyInfo(default='300ms'), }, + versioned_fields=[ + ([('7.15', '>=')], 'require-message-auth', KeyInfo(default='yes-for-request-resp')), + ], ), ), ('radius', 'incoming'): APIData( @@ -4052,6 +4219,28 @@ PATHS = { }, ), ), + ('routing', 'bfd', 'configuration'): APIData( + versioned=[ + ('7.11', '>=', VersionedAPIData( + fully_understood=True, + fields={ + 'address-list': KeyInfo(), + 'addresses': KeyInfo(), + 'comment': KeyInfo(can_disable=True, remove_value=''), + 'copy-from': KeyInfo(), + 'disabled': KeyInfo(default=False), + 'forbid-bfd': KeyInfo(), + 'interfaces': KeyInfo(), + 'min-echo-rx': KeyInfo(), + 'min-rx': KeyInfo(), + 'min-tx': KeyInfo(), + 'multiplier': KeyInfo(), + 'place-before': KeyInfo(), + 'vrf': KeyInfo(), + }, + )) + ], + ), ('routing', 'bfd', 'interface'): APIData( unversioned=VersionedAPIData( unknown_mechanism=True, @@ -4768,6 +4957,18 @@ PATHS = { }, ), ), + ('interface', 'ethernet', 'switch', 'port-isolation'): APIData( + versioned=[ + ('6.43', '>=', VersionedAPIData( + primary_keys=('name', ), + fully_understood=True, + fields={ + 'forwarding-override': KeyInfo(), + 'name': KeyInfo(), + }, + )), + ], + ), ('ip', 'dhcp-client', 'option'): APIData( unversioned=VersionedAPIData( fixed_entries=True, @@ -4858,10 +5059,13 @@ PATHS = { ('routing', 'bgp', 'connection'): APIData( unversioned=VersionedAPIData( fully_understood=True, + versioned_fields=[ + ([('7.19', '<')], 'address-families', KeyInfo()), + ([('7.19', '>=')], 'afi', KeyInfo()), + ], fields={ 'as': KeyInfo(), 'add-path-out': KeyInfo(), - 'address-families': KeyInfo(), 'cisco-vpls-nlri-len-fmt': KeyInfo(), 'cluster-id': KeyInfo(), 'comment': KeyInfo(), @@ -5040,6 +5244,11 @@ PATHS = { unversioned=VersionedAPIData( fully_understood=True, primary_keys=('name',), + versioned_fields=[ + ([('7.18', '>=')], 'remote-log-format', KeyInfo(default='default')), + ([('7.18', '>=')], 'remote-protocol', KeyInfo(default='udp')), + ([('7.18', '>=')], 'cef-event-delimiter', KeyInfo(default='\r\n')), + ], fields={ 'bsd-syslog': KeyInfo(default=False), 'comment': KeyInfo(can_disable=True, remove_value=''), @@ -5142,7 +5351,7 @@ PATHS = { 'protocol': KeyInfo(default='all'), 'src-address': KeyInfo(), 'src-port': KeyInfo(default='any'), - # The template field can't really be changed once the item is + # The template field ca not really be changed once the item is # created. This config captures the behavior best as it can # i.e. template=yes is shown, template=no is hidden. 'template': KeyInfo(can_disable=True, remove_value=False), diff --git a/plugins/module_utils/api.py b/plugins/module_utils/api.py index 6c276d7..aa3aa2e 100644 --- a/plugins/module_utils/api.py +++ b/plugins/module_utils/api.py @@ -77,7 +77,7 @@ def _ros_api_connect(module, username, password, host, port, use_tls, force_no_c elif not validate_cert_hostname: ctx.check_hostname = False else: - # Since librouteros doesn't pass server_hostname, + # Since librouteros does not pass server_hostname, # we have to do this ourselves: def wrap_context(*args, **kwargs): kwargs.pop('server_hostname', None) diff --git a/plugins/modules/api.py b/plugins/modules/api.py index 4857a3c..d3ec089 100644 --- a/plugins/modules/api.py +++ b/plugins/modules/api.py @@ -8,19 +8,17 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' ---- +DOCUMENTATION = r""" module: api author: "Nikolay Dachev (@NikolayDachev)" short_description: Ansible module for RouterOS API description: - Ansible module for RouterOS API with the Python C(librouteros) library. - - This module can add, remove, update, query and execute arbitrary command in RouterOS via API. + - This module can add, remove, update, query, and execute arbitrary command in RouterOS through the API. notes: - O(add), O(remove), O(update), O(cmd), and O(query) are mutually exclusive. - - Use the M(community.routeros.api_modify) and M(community.routeros.api_find_and_modify) modules - for more specific modifications, and the M(community.routeros.api_info) module for a more controlled - way of returning all entries for a path. + - Use the M(community.routeros.api_modify) and M(community.routeros.api_find_and_modify) modules for more specific modifications, + and the M(community.routeros.api_info) module for a more controlled way of returning all entries for a path. extends_documentation_fragment: - community.routeros.api - community.routeros.attributes @@ -35,11 +33,15 @@ attributes: platforms: RouterOS action_group: version_added: 2.1.0 + idempotent: + support: N/A + details: + - Whether the executed command is idempotent depends on the operation performed. options: path: description: - Main path for all other arguments. - - If other arguments are not set, api will return all items in selected path. + - If other arguments are not set, the module will return all items in selected path. - Example V(ip address). Equivalent of RouterOS CLI C(/ip address print). required: true type: str @@ -59,20 +61,21 @@ options: update: description: - Update config/value in RouterOS by '.id' in selected path. - - Example V(.id=*03 address=1.1.1.3/32) and path V(ip address) will replace existing ip address with C(.id=*03). + - Example V(.id=*03 address=1.1.1.3/32) and path V(ip address) will replace the existing IP address with C(.id=*03). - Equivalent in RouterOS CLI C(/ip address set address=1.1.1.3/32 numbers=1). - Note C(number) in RouterOS CLI is different from C(.id). type: str query: description: - - Query given path for selected query attributes from RouterOS aip. + - Query given path for selected query attributes from RouterOS API. - WHERE is key word which extend query. WHERE format is key operator value - with spaces. - WHERE valid operators are V(==) or V(eq), V(!=) or V(not), V(>) or V(more), V(<) or V(less). - - Example path V(ip address) and query V(.id address) will return only C(.id) and C(address) for all items in V(ip address) path. - - Example path V(ip address) and query V(.id address WHERE address == 1.1.1.3/32). - will return only C(.id) and C(address) for items in V(ip address) path, where address is eq to 1.1.1.3/32. - - Example path V(interface) and query V(mtu name WHERE mut > 1400) will - return only interfaces C(mtu,name) where mtu is bigger than 1400. + - Example path V(ip address) and query V(.id address) will return only C(.id) and C(address) for all items in V(ip address) + path. + - Example path V(ip address) and query V(.id address WHERE address == 1.1.1.3/32). will return only C(.id) and C(address) + for items in V(ip address) path, where address is eq to 1.1.1.3/32. + - Example path V(interface) and query V(mtu name WHERE mut > 1400) will return only interfaces C(mtu,name) where mtu + is bigger than 1400. - Equivalent in RouterOS CLI C(/interface print where mtu > 1400). type: str extended_query: @@ -91,7 +94,8 @@ options: where: description: - Allows to restrict the objects returned. - - The conditions here must all match. An O(extended_query.where[].or) condition needs at least one of its conditions to match. + - The conditions here must all match. An O(extended_query.where[].or) condition needs at least one of its conditions + to match. type: list elements: dict suboptions: @@ -105,7 +109,8 @@ options: description: - The operator to use for matching. - For equality use V(==) or V(eq). For less use V(<) or V(less). For more use V(>) or V(more). - - Use V(in) to check whether the value is part of a list. In that case, O(extended_query.where[].value) must be a list. + - Use V(in) to check whether the value is part of a list. In that case, O(extended_query.where[].value) must + be a list. - Either O(extended_query.where[].or) or all of O(extended_query.where[].attribute), O(extended_query.where[].is), and O(extended_query.where[].value) have to be specified. type: str @@ -133,7 +138,8 @@ options: description: - The operator to use for matching. - For equality use V(==) or V(eq). For less use V(<) or V(less). For more use V(>) or V(more). - - Use V(in) to check whether the value is part of a list. In that case, O(extended_query.where[].or[].value) must be a list. + - Use V(in) to check whether the value is part of a list. In that case, O(extended_query.where[].or[].value) + must be a list. type: str choices: ["==", "!=", ">", "<", "in", "eq", "not", "more", "less"] required: true @@ -150,14 +156,15 @@ options: type: str seealso: - ref: ansible_collections.community.routeros.docsite.quoting - description: How to quote and unquote commands and arguments + description: How to quote and unquote commands and arguments. - module: community.routeros.api_facts - module: community.routeros.api_find_and_modify - module: community.routeros.api_info - module: community.routeros.api_modify -''' +""" -EXAMPLES = ''' +EXAMPLES = r""" +--- - name: Get example - ip address print community.routeros.api: hostname: "{{ hostname }}" @@ -216,8 +223,8 @@ EXAMPLES = ''' - attribute: "network" is: "in" value: - - "10.20.36.0" - - "192.168.255.0" + - "10.20.36.0" + - "192.168.255.0" register: extended_queryout - name: Dump "Extended query example" output @@ -231,9 +238,9 @@ EXAMPLES = ''' username: "{{ username }}" path: "ip address" update: >- - .id=*14 - address=192.168.255.20/24 - comment={{ 'Update 192.168.255.10/24 to 192.168.255.20/24 on ether2' | community.routeros.quote_argument_value }} + .id=*14 + address=192.168.255.20/24 + comment={{ 'Update 192.168.255.10/24 to 192.168.255.20/24 on ether2' | community.routeros.quote_argument_value }} - name: Remove example - ether2 ip 192.168.255.20/24 with ".id = *14" community.routeros.api: @@ -255,18 +262,17 @@ EXAMPLES = ''' - name: Dump "Arbitrary command example" output ansible.builtin.debug: msg: '{{ arbitraryout }}' -''' +""" -RETURN = ''' ---- +RETURN = r""" message: - description: All outputs are in list with dictionary elements returned from RouterOS api. - sample: - - address: 1.2.3.4 - - address: 2.3.4.5 - type: list - returned: always -''' + description: All outputs are in list with dictionary elements returned from RouterOS API. + sample: + - address: 1.2.3.4 + - address: 2.3.4.5 + type: list + returned: always +""" from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_native diff --git a/plugins/modules/api_facts.py b/plugins/modules/api_facts.py index 296621e..09c0fbb 100644 --- a/plugins/modules/api_facts.py +++ b/plugins/modules/api_facts.py @@ -9,29 +9,27 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' ---- +DOCUMENTATION = r""" module: api_facts author: - - "Egor Zaitsev (@heuels)" - - "Nikolay Dachev (@NikolayDachev)" - - "Felix Fontein (@felixfontein)" + - "Egor Zaitsev (@heuels)" + - "Nikolay Dachev (@NikolayDachev)" + - "Felix Fontein (@felixfontein)" version_added: 2.1.0 short_description: Collect facts from remote devices running MikroTik RouterOS using the API description: - - Collects a base set of device facts from a remote device that - is running RouterOS. This module prepends all of the - base network fact keys with C(ansible_net_). The facts - module will always collect a base set of facts from the device + - Collects a base set of device facts from a remote device that is running RouterOS. This module prepends all of the base + network fact keys with C(ansible_net_). The facts module will always collect a base set of facts from the device and can enable or disable collection of additional facts. - - As opposed to the M(community.routeros.facts) module, it uses the - RouterOS API, similar to the M(community.routeros.api) module. + - As opposed to the M(community.routeros.facts) module, it uses the RouterOS API, similar to the M(community.routeros.api) + module. extends_documentation_fragment: - community.routeros.api - community.routeros.attributes - community.routeros.attributes.actiongroup_api - community.routeros.attributes.facts - community.routeros.attributes.facts_module + - community.routeros.attributes.idempotent_not_modify_state attributes: platform: support: full @@ -39,12 +37,10 @@ attributes: options: gather_subset: description: - - When supplied, this argument will restrict the facts collected - to a given subset. Possible values for this argument include - V(all), V(hardware), V(interfaces), and V(routing). - - Can specify a list of values to include a larger subset. - Values can also be used with an initial V(!) to specify that a - specific subset should not be collected. + - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument + include V(all), V(hardware), V(interfaces), and V(routing). + - Can specify a list of values to include a larger subset. Values can also be used with an initial V(!) to specify that + a specific subset should not be collected. required: false default: - all @@ -56,9 +52,10 @@ seealso: - module: community.routeros.api_find_and_modify - module: community.routeros.api_info - module: community.routeros.api_modify -''' +""" -EXAMPLES = """ +EXAMPLES = r""" +--- - name: Collect all facts from the device community.routeros.api_facts: hostname: 192.168.88.1 @@ -75,7 +72,7 @@ EXAMPLES = """ - "!hardware" """ -RETURN = """ +RETURN = r""" ansible_facts: description: "Dictionary of IP geolocation facts for a host's IP address." returned: always @@ -422,8 +419,6 @@ FACT_SUBSETS = dict( VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) -warnings = [] - def main(): argument_spec = dict( @@ -488,7 +483,7 @@ def main(): key = 'ansible_net_%s' % key ansible_facts[key] = value - module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + module.exit_json(ansible_facts=ansible_facts) if __name__ == '__main__': diff --git a/plugins/modules/api_find_and_modify.py b/plugins/modules/api_find_and_modify.py index 176c943..acdd125 100644 --- a/plugins/modules/api_find_and_modify.py +++ b/plugins/modules/api_find_and_modify.py @@ -8,8 +8,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' ---- +DOCUMENTATION = r""" module: api_find_and_modify author: - "Felix Fontein (@felixfontein)" @@ -17,13 +16,13 @@ short_description: Find and modify information using the API version_added: 2.1.0 description: - Allows to find entries for a path by conditions and modify the values of these entries. - - Use the M(community.routeros.api_find_and_modify) module to set all entries of a path to specific values, - or change multiple entries in different ways in one step. + - Use the M(community.routeros.api_find_and_modify) module to set all entries of a path to specific values, or change multiple + entries in different ways in one step. notes: - - "If you want to change values based on their old values (like change all comments 'foo' to 'bar') and make sure that - there are at least N such values, you can use O(require_matches_min=N) together with O(allow_no_matches=true). - This will make the module fail if there are less than N such entries, but not if there is no match. The latter case - is needed for idempotency of the task: once the values have been changed, there should be no further match." + - "If you want to change values based on their old values (like change all comments 'foo' to 'bar') and make sure that there + are at least N such values, you can use O(require_matches_min=N) together with O(allow_no_matches=true). This will make + the module fail if there are less than N such entries, but not if there is no match. The latter case is needed for idempotency + of the task: once the values have been changed, there should be no further match." extends_documentation_fragment: - community.routeros.api - community.routeros.attributes @@ -36,6 +35,8 @@ attributes: platform: support: full platforms: RouterOS + idempotent: + support: full options: path: description: @@ -74,14 +75,30 @@ options: - Whether to allow that no match is found. - If not specified, this value is induced from whether O(require_matches_min) is 0 or larger. type: bool + ignore_dynamic: + description: + - Whether to ignore dynamic entries. + - By default, they are considered. If set to V(true), they are not considered. + - It is generally recommended to set this to V(true) unless when you really need to modify dynamic entries. + type: bool + default: false + version_added: 3.7.0 + ignore_builtin: + description: + - Whether to ignore builtin entries. + - By default, they are considered. If set to V(true), they are not considered. + - It is generally recommended to set this to V(true) unless when you really need to modify builtin entries. + type: bool + default: false + version_added: 3.7.0 seealso: - module: community.routeros.api - module: community.routeros.api_facts - module: community.routeros.api_modify - module: community.routeros.api_info -''' +""" -EXAMPLES = ''' +EXAMPLES = r""" --- - name: Rename bridge from 'bridge' to 'my-bridge' community.routeros.api_find_and_modify: @@ -93,6 +110,10 @@ EXAMPLES = ''' name: bridge values: name: my-bridge + # Always ignore dynamic and builtin entries + # (not relevant for this path, but generally recommended) + ignore_dynamic: true + ignore_builtin: true - name: Change IP address to 192.168.1.1 for interface bridge - assuming there is only one community.routeros.api_find_and_modify: @@ -108,55 +129,58 @@ EXAMPLES = ''' # exactly one is configured. require_matches_min: 1 require_matches_max: 1 -''' + # Always ignore dynamic and builtin entries + # (not relevant for this path, but generally recommended) + ignore_dynamic: true + ignore_builtin: true +""" -RETURN = ''' ---- +RETURN = r""" old_data: - description: - - A list of all elements for the current path before a change was made. - sample: - - '.id': '*1' - actual-interface: bridge - address: "192.168.88.1/24" - comment: defconf - disabled: false - dynamic: false - interface: bridge - invalid: false - network: 192.168.88.0 - type: list - elements: dict - returned: success + description: + - A list of all elements for the current path before a change was made. + sample: + - '.id': '*1' + actual-interface: bridge + address: "192.168.88.1/24" + comment: defconf + disabled: false + dynamic: false + interface: bridge + invalid: false + network: 192.168.88.0 + type: list + elements: dict + returned: success new_data: - description: - - A list of all elements for the current path after a change was made. - sample: - - '.id': '*1' - actual-interface: bridge - address: "192.168.1.1/24" - comment: awesome - disabled: false - dynamic: false - interface: bridge - invalid: false - network: 192.168.1.0 - type: list - elements: dict - returned: success + description: + - A list of all elements for the current path after a change was made. + sample: + - '.id': '*1' + actual-interface: bridge + address: "192.168.1.1/24" + comment: awesome + disabled: false + dynamic: false + interface: bridge + invalid: false + network: 192.168.1.0 + type: list + elements: dict + returned: success match_count: - description: - - The number of entries that matched the criteria in O(find). - sample: 1 - type: int - returned: success + description: + - The number of entries that matched the criteria in O(find). + sample: 1 + type: int + returned: success modify__count: - description: - - The number of entries that were modified. - sample: 1 - type: int - returned: success -''' + description: + - The number of entries that were modified. + sample: 1 + type: int + returned: success +""" from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_native @@ -185,6 +209,17 @@ def compose_api_path(api, path): return api_path +def filter_entries(entries, ignore_dynamic=False, ignore_builtin=False): + result = [] + for entry in entries: + if ignore_dynamic and entry.get('dynamic', False): + continue + if ignore_builtin and entry.get('builtin', False): + continue + result.append(entry) + return result + + DISABLED_MEANS_EMPTY_STRING = ('comment', ) @@ -196,6 +231,8 @@ def main(): require_matches_min=dict(type='int', default=0), require_matches_max=dict(type='int'), allow_no_matches=dict(type='bool'), + ignore_dynamic=dict(type='bool', default=False), + ignore_builtin=dict(type='bool', default=False), ) module_args.update(api_argument_spec()) @@ -223,6 +260,9 @@ def main(): if key in values: module.fail_json(msg='`values` must not contain both "{key}" and "!{key}"!'.format(key=key)) + ignore_dynamic = module.params['ignore_dynamic'] + ignore_builtin = module.params['ignore_builtin'] + check_has_library(module) api = create_api(module) @@ -230,7 +270,7 @@ def main(): api_path = compose_api_path(api, path) - old_data = list(api_path) + old_data = filter_entries(list(api_path), ignore_dynamic=ignore_dynamic, ignore_builtin=ignore_builtin) new_data = [entry.copy() for entry in old_data] # Find matching entries @@ -299,7 +339,7 @@ def main(): error=to_native(e), ) ) - new_data = list(api_path) + new_data = filter_entries(list(api_path), ignore_dynamic=ignore_dynamic, ignore_builtin=ignore_builtin) # Produce return value more = {} diff --git a/plugins/modules/api_info.py b/plugins/modules/api_info.py index 829fc17..1eb8a94 100644 --- a/plugins/modules/api_info.py +++ b/plugins/modules/api_info.py @@ -8,8 +8,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' ---- +DOCUMENTATION = r""" module: api_info author: - "Felix Fontein (@felixfontein)" @@ -18,17 +17,18 @@ version_added: 2.2.0 description: - Allows to retrieve information for a path using the API. - This can be used to backup a path to restore it with the M(community.routeros.api_modify) module. - - Entries are normalized, dynamic and builtin entries are not returned. Use the O(handle_disabled) and - O(hide_defaults) options to control normalization, the O(include_dynamic) and O(include_builtin) options to also return - dynamic resp. builtin entries, and use O(unfiltered) to return all fields including counters. - - B(Note) that this module is still heavily in development, and only supports B(some) paths. - If you want to support new paths, or think you found problems with existing paths, please first - L(create an issue in the community.routeros Issue Tracker,https://github.com/ansible-collections/community.routeros/issues/). + - Entries are normalized, dynamic and builtin entries are not returned. Use the O(handle_disabled) and O(hide_defaults) + options to control normalization, the O(include_dynamic) and O(include_builtin) options to also return dynamic resp. builtin + entries, and use O(unfiltered) to return all fields including counters. + - B(Note) that this module is still heavily in development, and only supports B(some) paths. If you want to support new + paths, or think you found problems with existing paths, please first L(create an issue in the community.routeros Issue + Tracker,https://github.com/ansible-collections/community.routeros/issues/). extends_documentation_fragment: - community.routeros.api - community.routeros.api.restrict - community.routeros.attributes - community.routeros.attributes.actiongroup_api + - community.routeros.attributes.idempotent_not_modify_state - community.routeros.attributes.info_module attributes: platform: @@ -43,224 +43,232 @@ options: type: str choices: # BEGIN PATH LIST - - caps-man aaa - - caps-man access-list - - caps-man channel - - caps-man configuration - - caps-man datapath - - caps-man manager - - caps-man manager interface - - caps-man provisioning - - caps-man security - - certificate settings - - interface bonding - - interface bridge - - interface bridge mlag - - interface bridge port - - interface bridge port-controller - - interface bridge port-extender - - interface bridge settings - - interface bridge vlan - - interface detect-internet - - interface eoip - - interface ethernet - - interface ethernet poe - - interface ethernet switch - - interface ethernet switch port - - interface gre - - interface gre6 - - interface l2tp-client - - interface l2tp-server server - - interface list - - interface list member - - interface ovpn-client - - interface ovpn-server server - - interface ppp-client - - interface pppoe-client - - interface pppoe-server server - - interface pptp-server server - - interface sstp-server server - - interface vlan - - interface vrrp - - interface wifi - - interface wifi aaa - - interface wifi access-list - - interface wifi cap - - interface wifi capsman - - interface wifi channel - - interface wifi configuration - - interface wifi datapath - - interface wifi interworking - - interface wifi provisioning - - interface wifi security - - interface wifi steering - - interface wifiwave2 - - interface wifiwave2 aaa - - interface wifiwave2 access-list - - interface wifiwave2 cap - - interface wifiwave2 capsman - - interface wifiwave2 channel - - interface wifiwave2 configuration - - interface wifiwave2 datapath - - interface wifiwave2 interworking - - interface wifiwave2 provisioning - - interface wifiwave2 security - - interface wifiwave2 steering - - interface wireguard - - interface wireguard peers - - interface wireless - - interface wireless align - - interface wireless cap - - interface wireless security-profiles - - interface wireless sniffer - - interface wireless snooper - - iot modbus - - ip accounting - - ip accounting web-access - - ip address - - ip arp - - ip cloud - - ip cloud advanced - - ip dhcp-client - - ip dhcp-client option - - ip dhcp-relay - - ip dhcp-server - - ip dhcp-server config - - ip dhcp-server lease - - ip dhcp-server matcher - - ip dhcp-server network - - ip dhcp-server option - - ip dhcp-server option sets - - ip dns - - ip dns adlist - - ip dns static - - ip firewall address-list - - ip firewall connection tracking - - ip firewall filter - - ip firewall layer7-protocol - - ip firewall mangle - - ip firewall nat - - ip firewall raw - - ip firewall service-port - - ip hotspot service-port - - ip ipsec identity - - ip ipsec peer - - ip ipsec policy - - ip ipsec profile - - ip ipsec proposal - - ip ipsec settings - - ip neighbor discovery-settings - - ip pool - - ip proxy - - ip route - - ip route rule - - ip route vrf - - ip service - - ip settings - - ip smb - - ip socks - - ip ssh - - ip tftp settings - - ip traffic-flow - - ip traffic-flow ipfix - - ip traffic-flow target - - ip upnp - - ip upnp interfaces - - ip vrf - - ipv6 address - - ipv6 dhcp-client - - ipv6 dhcp-server - - ipv6 dhcp-server option - - ipv6 firewall address-list - - ipv6 firewall filter - - ipv6 firewall mangle - - ipv6 firewall nat - - ipv6 firewall raw - - ipv6 nd - - ipv6 nd prefix - - ipv6 nd prefix default - - ipv6 route - - ipv6 settings - - mpls - - mpls interface - - mpls ldp - - mpls ldp accept-filter - - mpls ldp advertise-filter - - mpls ldp interface - - port firmware - - port remote-access - - ppp aaa - - ppp profile - - ppp secret - - queue interface - - queue simple - - queue tree - - queue type - - radius - - radius incoming - - routing bgp aggregate - - routing bgp connection - - routing bgp instance - - routing bgp network - - routing bgp peer - - routing bgp template - - routing filter - - routing filter num-list - - routing filter rule - - routing filter select-rule - - routing id - - routing igmp-proxy - - routing igmp-proxy interface - - routing mme - - routing ospf area - - routing ospf area range - - routing ospf instance - - routing ospf interface-template - - routing ospf static-neighbor - - routing pimsm instance - - routing pimsm interface-template - - routing rip - - routing ripng - - routing rule - - routing table - - snmp - - snmp community - - system clock - - system clock manual - - system health settings - - system identity - - system leds settings - - system logging - - system logging action - - system note - - system ntp client - - system ntp client servers - - system ntp server - - system package update - - system resource irq rps - - system routerboard settings - - system scheduler - - system script - - system upgrade mirror - - system ups - - system watchdog - - tool bandwidth-server - - tool e-mail - - tool graphing - - tool graphing interface - - tool graphing resource - - tool mac-server - - tool mac-server mac-winbox - - tool mac-server ping - - tool netwatch - - tool romon - - tool sms - - tool sniffer - - tool traffic-generator - - user - - user aaa - - user group - - user settings + - caps-man aaa + - caps-man access-list + - caps-man channel + - caps-man configuration + - caps-man datapath + - caps-man manager + - caps-man manager interface + - caps-man provisioning + - caps-man security + - certificate settings + - interface 6to4 + - interface bonding + - interface bridge + - interface bridge mlag + - interface bridge port + - interface bridge port-controller + - interface bridge port-extender + - interface bridge settings + - interface bridge vlan + - interface detect-internet + - interface eoip + - interface ethernet + - interface ethernet poe + - interface ethernet switch + - interface ethernet switch port + - interface ethernet switch port-isolation + - interface gre + - interface gre6 + - interface l2tp-client + - interface l2tp-server server + - interface list + - interface list member + - interface ovpn-client + - interface ovpn-server server + - interface ppp-client + - interface pppoe-client + - interface pppoe-server server + - interface pptp-server server + - interface sstp-server server + - interface vlan + - interface vrrp + - interface wifi + - interface wifi aaa + - interface wifi access-list + - interface wifi cap + - interface wifi capsman + - interface wifi channel + - interface wifi configuration + - interface wifi datapath + - interface wifi interworking + - interface wifi provisioning + - interface wifi security + - interface wifi steering + - interface wifiwave2 + - interface wifiwave2 aaa + - interface wifiwave2 access-list + - interface wifiwave2 cap + - interface wifiwave2 capsman + - interface wifiwave2 channel + - interface wifiwave2 configuration + - interface wifiwave2 datapath + - interface wifiwave2 interworking + - interface wifiwave2 provisioning + - interface wifiwave2 security + - interface wifiwave2 steering + - interface wireguard + - interface wireguard peers + - interface wireless + - interface wireless access-list + - interface wireless align + - interface wireless cap + - interface wireless connect-list + - interface wireless security-profiles + - interface wireless sniffer + - interface wireless snooper + - iot modbus + - ip accounting + - ip accounting web-access + - ip address + - ip arp + - ip cloud + - ip cloud advanced + - ip dhcp-client + - ip dhcp-client option + - ip dhcp-relay + - ip dhcp-server + - ip dhcp-server config + - ip dhcp-server lease + - ip dhcp-server matcher + - ip dhcp-server network + - ip dhcp-server option + - ip dhcp-server option sets + - ip dns + - ip dns adlist + - ip dns forwarders + - ip dns static + - ip firewall address-list + - ip firewall connection tracking + - ip firewall filter + - ip firewall layer7-protocol + - ip firewall mangle + - ip firewall nat + - ip firewall raw + - ip firewall service-port + - ip hotspot service-port + - ip ipsec identity + - ip ipsec mode-config + - ip ipsec peer + - ip ipsec policy + - ip ipsec profile + - ip ipsec proposal + - ip ipsec settings + - ip neighbor discovery-settings + - ip pool + - ip proxy + - ip route + - ip route rule + - ip route vrf + - ip service + - ip settings + - ip smb + - ip socks + - ip ssh + - ip tftp settings + - ip traffic-flow + - ip traffic-flow ipfix + - ip traffic-flow target + - ip upnp + - ip upnp interfaces + - ip vrf + - ipv6 address + - ipv6 dhcp-client + - ipv6 dhcp-server + - ipv6 dhcp-server option + - ipv6 firewall address-list + - ipv6 firewall filter + - ipv6 firewall mangle + - ipv6 firewall nat + - ipv6 firewall raw + - ipv6 nd + - ipv6 nd prefix + - ipv6 nd prefix default + - ipv6 route + - ipv6 settings + - mpls + - mpls interface + - mpls ldp + - mpls ldp accept-filter + - mpls ldp advertise-filter + - mpls ldp interface + - port firmware + - port remote-access + - ppp aaa + - ppp profile + - ppp secret + - queue interface + - queue simple + - queue tree + - queue type + - radius + - radius incoming + - routing bfd configuration + - routing bgp aggregate + - routing bgp connection + - routing bgp instance + - routing bgp network + - routing bgp peer + - routing bgp template + - routing filter + - routing filter community-list + - routing filter num-list + - routing filter rule + - routing filter select-rule + - routing id + - routing igmp-proxy + - routing igmp-proxy interface + - routing mme + - routing ospf area + - routing ospf area range + - routing ospf instance + - routing ospf interface-template + - routing ospf static-neighbor + - routing pimsm instance + - routing pimsm interface-template + - routing rip + - routing ripng + - routing rule + - routing table + - snmp + - snmp community + - system clock + - system clock manual + - system health settings + - system identity + - system leds settings + - system logging + - system logging action + - system note + - system ntp client + - system ntp client servers + - system ntp server + - system package update + - system resource irq rps + - system routerboard settings + - system scheduler + - system script + - system upgrade mirror + - system ups + - system watchdog + - tool bandwidth-server + - tool e-mail + - tool graphing + - tool graphing interface + - tool graphing resource + - tool mac-server + - tool mac-server mac-winbox + - tool mac-server ping + - tool netwatch + - tool romon + - tool sms + - tool sniffer + - tool traffic-generator + - user + - user aaa + - user group + - user settings # END PATH LIST unfiltered: description: @@ -316,9 +324,9 @@ seealso: - module: community.routeros.api_facts - module: community.routeros.api_find_and_modify - module: community.routeros.api_modify -''' +""" -EXAMPLES = ''' +EXAMPLES = r""" --- - name: Get IP addresses community.routeros.api_info: @@ -343,26 +351,25 @@ EXAMPLES = ''' - name: Print data for IP addresses ansible.builtin.debug: var: ip_addresses.result -''' +""" -RETURN = ''' ---- +RETURN = r""" result: - description: A list of all elements for the current path. - sample: - - '.id': '*1' - actual-interface: bridge - address: "192.168.88.1/24" - comment: defconf - disabled: false - dynamic: false - interface: bridge - invalid: false - network: 192.168.88.0 - type: list - elements: dict - returned: always -''' + description: A list of all elements for the current path. + sample: + - '.id': '*1' + actual-interface: bridge + address: "192.168.88.1/24" + comment: defconf + disabled: false + dynamic: false + interface: bridge + invalid: false + network: 192.168.88.0 + type: list + elements: dict + returned: always +""" from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_native diff --git a/plugins/modules/api_modify.py b/plugins/modules/api_modify.py index 4975dc4..dfa6fd5 100644 --- a/plugins/modules/api_modify.py +++ b/plugins/modules/api_modify.py @@ -8,8 +8,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' ---- +DOCUMENTATION = r""" module: api_modify author: - "Felix Fontein (@felixfontein)" @@ -17,17 +16,17 @@ short_description: Modify data at paths with API version_added: 2.2.0 description: - Allows to modify information for a path using the API. - - Use the M(community.routeros.api_find_and_modify) module to modify one or multiple entries in a controlled way - depending on some search conditions. + - Use the M(community.routeros.api_find_and_modify) module to modify one or multiple entries in a controlled way depending + on some search conditions. - To make a backup of a path that can be restored with this module, use the M(community.routeros.api_info) module. - The module ignores dynamic and builtin entries. - - B(Note) that this module is still heavily in development, and only supports B(some) paths. - If you want to support new paths, or think you found problems with existing paths, please first - L(create an issue in the community.routeros Issue Tracker,https://github.com/ansible-collections/community.routeros/issues/). + - B(Note) that this module is still heavily in development, and only supports B(some) paths. If you want to support new + paths, or think you found problems with existing paths, please first L(create an issue in the community.routeros Issue + Tracker,https://github.com/ansible-collections/community.routeros/issues/). notes: - - If write-only fields are present in the path, the module is B(not idempotent) in a strict sense, - since it is not able to verify the current value of these fields. The behavior the module should - assume can be controlled with the O(handle_write_only) option. + - If write-only fields are present in the path, the module is B(not idempotent) in a strict sense, since it is not able + to verify the current value of these fields. The behavior the module should assume can be controlled with the O(handle_write_only) + option. requirements: - Needs L(ordereddict,https://pypi.org/project/ordereddict) for Python 2.6 extends_documentation_fragment: @@ -43,233 +42,244 @@ attributes: platform: support: full platforms: RouterOS + idempotent: + support: full options: path: description: - Path to query. - - An example value is V(ip address). This is equivalent to running modification commands in C(/ip address) in the RouterOS CLI. + - An example value is V(ip address). This is equivalent to running modification commands in C(/ip address) in the RouterOS + CLI. required: true type: str choices: # BEGIN PATH LIST - - caps-man aaa - - caps-man access-list - - caps-man channel - - caps-man configuration - - caps-man datapath - - caps-man manager - - caps-man manager interface - - caps-man provisioning - - caps-man security - - certificate settings - - interface bonding - - interface bridge - - interface bridge mlag - - interface bridge port - - interface bridge port-controller - - interface bridge port-extender - - interface bridge settings - - interface bridge vlan - - interface detect-internet - - interface eoip - - interface ethernet - - interface ethernet poe - - interface ethernet switch - - interface ethernet switch port - - interface gre - - interface gre6 - - interface l2tp-client - - interface l2tp-server server - - interface list - - interface list member - - interface ovpn-client - - interface ovpn-server server - - interface ppp-client - - interface pppoe-client - - interface pppoe-server server - - interface pptp-server server - - interface sstp-server server - - interface vlan - - interface vrrp - - interface wifi - - interface wifi aaa - - interface wifi access-list - - interface wifi cap - - interface wifi capsman - - interface wifi channel - - interface wifi configuration - - interface wifi datapath - - interface wifi interworking - - interface wifi provisioning - - interface wifi security - - interface wifi steering - - interface wifiwave2 - - interface wifiwave2 aaa - - interface wifiwave2 access-list - - interface wifiwave2 cap - - interface wifiwave2 capsman - - interface wifiwave2 channel - - interface wifiwave2 configuration - - interface wifiwave2 datapath - - interface wifiwave2 interworking - - interface wifiwave2 provisioning - - interface wifiwave2 security - - interface wifiwave2 steering - - interface wireguard - - interface wireguard peers - - interface wireless - - interface wireless align - - interface wireless cap - - interface wireless security-profiles - - interface wireless sniffer - - interface wireless snooper - - iot modbus - - ip accounting - - ip accounting web-access - - ip address - - ip arp - - ip cloud - - ip cloud advanced - - ip dhcp-client - - ip dhcp-client option - - ip dhcp-relay - - ip dhcp-server - - ip dhcp-server config - - ip dhcp-server lease - - ip dhcp-server matcher - - ip dhcp-server network - - ip dhcp-server option - - ip dhcp-server option sets - - ip dns - - ip dns adlist - - ip dns static - - ip firewall address-list - - ip firewall connection tracking - - ip firewall filter - - ip firewall layer7-protocol - - ip firewall mangle - - ip firewall nat - - ip firewall raw - - ip firewall service-port - - ip hotspot service-port - - ip ipsec identity - - ip ipsec peer - - ip ipsec policy - - ip ipsec profile - - ip ipsec proposal - - ip ipsec settings - - ip neighbor discovery-settings - - ip pool - - ip proxy - - ip route - - ip route rule - - ip route vrf - - ip service - - ip settings - - ip smb - - ip socks - - ip ssh - - ip tftp settings - - ip traffic-flow - - ip traffic-flow ipfix - - ip traffic-flow target - - ip upnp - - ip upnp interfaces - - ip vrf - - ipv6 address - - ipv6 dhcp-client - - ipv6 dhcp-server - - ipv6 dhcp-server option - - ipv6 firewall address-list - - ipv6 firewall filter - - ipv6 firewall mangle - - ipv6 firewall nat - - ipv6 firewall raw - - ipv6 nd - - ipv6 nd prefix - - ipv6 nd prefix default - - ipv6 route - - ipv6 settings - - mpls - - mpls interface - - mpls ldp - - mpls ldp accept-filter - - mpls ldp advertise-filter - - mpls ldp interface - - port firmware - - port remote-access - - ppp aaa - - ppp profile - - ppp secret - - queue interface - - queue simple - - queue tree - - queue type - - radius - - radius incoming - - routing bgp aggregate - - routing bgp connection - - routing bgp instance - - routing bgp network - - routing bgp peer - - routing bgp template - - routing filter - - routing filter num-list - - routing filter rule - - routing filter select-rule - - routing id - - routing igmp-proxy - - routing igmp-proxy interface - - routing mme - - routing ospf area - - routing ospf area range - - routing ospf instance - - routing ospf interface-template - - routing ospf static-neighbor - - routing pimsm instance - - routing pimsm interface-template - - routing rip - - routing ripng - - routing rule - - routing table - - snmp - - snmp community - - system clock - - system clock manual - - system health settings - - system identity - - system leds settings - - system logging - - system logging action - - system note - - system ntp client - - system ntp client servers - - system ntp server - - system package update - - system resource irq rps - - system routerboard settings - - system scheduler - - system script - - system upgrade mirror - - system ups - - system watchdog - - tool bandwidth-server - - tool e-mail - - tool graphing - - tool graphing interface - - tool graphing resource - - tool mac-server - - tool mac-server mac-winbox - - tool mac-server ping - - tool netwatch - - tool romon - - tool sms - - tool sniffer - - tool traffic-generator - - user - - user aaa - - user group - - user settings + - caps-man aaa + - caps-man access-list + - caps-man channel + - caps-man configuration + - caps-man datapath + - caps-man manager + - caps-man manager interface + - caps-man provisioning + - caps-man security + - certificate settings + - interface 6to4 + - interface bonding + - interface bridge + - interface bridge mlag + - interface bridge port + - interface bridge port-controller + - interface bridge port-extender + - interface bridge settings + - interface bridge vlan + - interface detect-internet + - interface eoip + - interface ethernet + - interface ethernet poe + - interface ethernet switch + - interface ethernet switch port + - interface ethernet switch port-isolation + - interface gre + - interface gre6 + - interface l2tp-client + - interface l2tp-server server + - interface list + - interface list member + - interface ovpn-client + - interface ovpn-server server + - interface ppp-client + - interface pppoe-client + - interface pppoe-server server + - interface pptp-server server + - interface sstp-server server + - interface vlan + - interface vrrp + - interface wifi + - interface wifi aaa + - interface wifi access-list + - interface wifi cap + - interface wifi capsman + - interface wifi channel + - interface wifi configuration + - interface wifi datapath + - interface wifi interworking + - interface wifi provisioning + - interface wifi security + - interface wifi steering + - interface wifiwave2 + - interface wifiwave2 aaa + - interface wifiwave2 access-list + - interface wifiwave2 cap + - interface wifiwave2 capsman + - interface wifiwave2 channel + - interface wifiwave2 configuration + - interface wifiwave2 datapath + - interface wifiwave2 interworking + - interface wifiwave2 provisioning + - interface wifiwave2 security + - interface wifiwave2 steering + - interface wireguard + - interface wireguard peers + - interface wireless + - interface wireless access-list + - interface wireless align + - interface wireless cap + - interface wireless connect-list + - interface wireless security-profiles + - interface wireless sniffer + - interface wireless snooper + - iot modbus + - ip accounting + - ip accounting web-access + - ip address + - ip arp + - ip cloud + - ip cloud advanced + - ip dhcp-client + - ip dhcp-client option + - ip dhcp-relay + - ip dhcp-server + - ip dhcp-server config + - ip dhcp-server lease + - ip dhcp-server matcher + - ip dhcp-server network + - ip dhcp-server option + - ip dhcp-server option sets + - ip dns + - ip dns adlist + - ip dns forwarders + - ip dns static + - ip firewall address-list + - ip firewall connection tracking + - ip firewall filter + - ip firewall layer7-protocol + - ip firewall mangle + - ip firewall nat + - ip firewall raw + - ip firewall service-port + - ip hotspot service-port + - ip ipsec identity + - ip ipsec mode-config + - ip ipsec peer + - ip ipsec policy + - ip ipsec profile + - ip ipsec proposal + - ip ipsec settings + - ip neighbor discovery-settings + - ip pool + - ip proxy + - ip route + - ip route rule + - ip route vrf + - ip service + - ip settings + - ip smb + - ip socks + - ip ssh + - ip tftp settings + - ip traffic-flow + - ip traffic-flow ipfix + - ip traffic-flow target + - ip upnp + - ip upnp interfaces + - ip vrf + - ipv6 address + - ipv6 dhcp-client + - ipv6 dhcp-server + - ipv6 dhcp-server option + - ipv6 firewall address-list + - ipv6 firewall filter + - ipv6 firewall mangle + - ipv6 firewall nat + - ipv6 firewall raw + - ipv6 nd + - ipv6 nd prefix + - ipv6 nd prefix default + - ipv6 route + - ipv6 settings + - mpls + - mpls interface + - mpls ldp + - mpls ldp accept-filter + - mpls ldp advertise-filter + - mpls ldp interface + - port firmware + - port remote-access + - ppp aaa + - ppp profile + - ppp secret + - queue interface + - queue simple + - queue tree + - queue type + - radius + - radius incoming + - routing bfd configuration + - routing bgp aggregate + - routing bgp connection + - routing bgp instance + - routing bgp network + - routing bgp peer + - routing bgp template + - routing filter + - routing filter community-list + - routing filter num-list + - routing filter rule + - routing filter select-rule + - routing id + - routing igmp-proxy + - routing igmp-proxy interface + - routing mme + - routing ospf area + - routing ospf area range + - routing ospf instance + - routing ospf interface-template + - routing ospf static-neighbor + - routing pimsm instance + - routing pimsm interface-template + - routing rip + - routing ripng + - routing rule + - routing table + - snmp + - snmp community + - system clock + - system clock manual + - system health settings + - system identity + - system leds settings + - system logging + - system logging action + - system note + - system ntp client + - system ntp client servers + - system ntp server + - system package update + - system resource irq rps + - system routerboard settings + - system scheduler + - system script + - system upgrade mirror + - system ups + - system watchdog + - tool bandwidth-server + - tool e-mail + - tool graphing + - tool graphing interface + - tool graphing resource + - tool mac-server + - tool mac-server mac-winbox + - tool mac-server ping + - tool netwatch + - tool romon + - tool sms + - tool sniffer + - tool traffic-generator + - user + - user aaa + - user group + - user settings # END PATH LIST data: description: @@ -297,12 +307,12 @@ options: default: ignore handle_entries_content: description: - - For a single entry in O(data), this describes how to handle fields that are not mentioned - in that entry, but appear in the actual config. + - For a single entry in O(data), this describes how to handle fields that are not mentioned in that entry, but appear + in the actual config. - If V(ignore), they are not modified. - If V(remove), they are removed. If at least one cannot be removed, the module will fail. - - If V(remove_as_much_as_possible), all that can be removed will be removed. The ones that - cannot be removed will be kept. + - If V(remove_as_much_as_possible), all that can be removed will be removed. The ones that cannot be removed will be + kept. - Note that V(remove) and V(remove_as_much_as_possible) do not apply to write-only fields. type: str choices: @@ -314,8 +324,8 @@ options: description: - How to handle values passed in for read-only fields. - If V(ignore), they are not passed to the API. - - If V(validate), the values are not passed for creation, and for updating they are compared to the value returned for the object. - If they differ, the module fails. + - If V(validate), the values are not passed for creation, and for updating they are compared to the value returned for + the object. If they differ, the module fails. - If V(error), the module will fail if read-only fields are provided. type: str choices: @@ -328,9 +338,8 @@ options: description: - How to handle values passed in for write-only fields. - If V(create_only), they are passed on creation, and ignored for updating. - - If V(always_update), they are always passed to the API. This means that if such a value is present, - the module will always result in C(changed) since there is no way to validate whether the value - actually changed. + - If V(always_update), they are always passed to the API. This means that if such a value is present, the module will + always result in C(changed) since there is no way to validate whether the value actually changed. - If V(error), the module will fail if write-only fields are provided. type: str choices: @@ -342,20 +351,18 @@ options: restrict: description: - Restrict operation to entries matching the following criteria. - - This can be useful together with O(handle_absent_entries=remove) to operate on a subset of - the values. - - For example, for O(path=ip firewall filter), you can set O(restrict[].field=chain) and - O(restrict[].values=input) to restrict operation to the input chain, and ignore the - forward and output chains. + - This can be useful together with O(handle_absent_entries=remove) to operate on a subset of the values. + - For example, for O(path=ip firewall filter), you can set O(restrict[].field=chain) and O(restrict[].values=input) + to restrict operation to the input chain, and ignore the forward and output chains. version_added: 2.18.0 seealso: - module: community.routeros.api - module: community.routeros.api_facts - module: community.routeros.api_find_and_modify - module: community.routeros.api_info -''' +""" -EXAMPLES = ''' +EXAMPLES = r""" --- - name: Setup DHCP server networks # Ensures that we have exactly two DHCP server networks (in the specified order) @@ -410,43 +417,42 @@ EXAMPLES = ''' data: - action: drop chain: input -''' +""" -RETURN = ''' ---- +RETURN = r""" old_data: - description: - - A list of all elements for the current path before a change was made. - sample: - - '.id': '*1' - actual-interface: bridge - address: "192.168.88.1/24" - comment: defconf - disabled: false - dynamic: false - interface: bridge - invalid: false - network: 192.168.88.0 - type: list - elements: dict - returned: always + description: + - A list of all elements for the current path before a change was made. + sample: + - '.id': '*1' + actual-interface: bridge + address: "192.168.88.1/24" + comment: defconf + disabled: false + dynamic: false + interface: bridge + invalid: false + network: 192.168.88.0 + type: list + elements: dict + returned: always new_data: - description: - - A list of all elements for the current path after a change was made. - sample: - - '.id': '*1' - actual-interface: bridge - address: "192.168.1.1/24" - comment: awesome - disabled: false - dynamic: false - interface: bridge - invalid: false - network: 192.168.1.0 - type: list - elements: dict - returned: always -''' + description: + - A list of all elements for the current path after a change was made. + sample: + - '.id': '*1' + actual-interface: bridge + address: "192.168.1.1/24" + comment: awesome + disabled: false + dynamic: false + interface: bridge + invalid: false + network: 192.168.1.0 + type: list + elements: dict + returned: always +""" from collections import defaultdict diff --git a/plugins/modules/command.py b/plugins/modules/command.py index bf74d47..50abe49 100644 --- a/plugins/modules/command.py +++ b/plugins/modules/command.py @@ -7,87 +7,77 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' ---- +DOCUMENTATION = r""" module: command author: "Egor Zaitsev (@heuels)" short_description: Run commands on remote devices running MikroTik RouterOS description: - - Sends arbitrary commands to an RouterOS node and returns the results - read from the device. This module includes an - argument that will cause the module to wait for a specific condition - before returning or timing out if the condition is not met. - - The module always indicates a (changed) status. You can use - R(the changed_when task property,override_the_changed_result) to determine - whether a command task actually resulted in a change or not. + - Sends arbitrary commands to an RouterOS node and returns the results read from the device. This module includes an argument + that will cause the module to wait for a specific condition before returning or timing out if the condition is not met. + - The module always indicates a (changed) status. You can use R(the changed_when task property,override_the_changed_result) + to determine whether a command task actually resulted in a change or not. extends_documentation_fragment: - community.routeros.attributes attributes: check_mode: support: none details: - - Before community.routeros 3.0.0, the module claimed to support check mode. - It simply executed the command in check mode. + - Before community.routeros 3.0.0, the module claimed to support check mode. It simply executed the command in check + mode. diff_mode: support: none platform: support: full platforms: RouterOS + idempotent: + support: N/A + details: + - Whether the executed command is idempotent depends on the command. options: commands: description: - - List of commands to send to the remote RouterOS device over the - configured provider. The resulting output from the command - is returned. If the O(wait_for) argument is provided, the - module is not returned until the condition is satisfied or - the number of retries has expired. + - List of commands to send to the remote RouterOS device over the configured provider. The resulting output from the + command is returned. If the O(wait_for) argument is provided, the module is not returned until the condition is satisfied + or the number of retries has expired. required: true type: list elements: str wait_for: description: - - List of conditions to evaluate against the output of the - command. The task will wait for each condition to be true - before moving forward. If the conditional is not true - within the configured number of retries, the task fails. - See examples. + - List of conditions to evaluate against the output of the command. The task will wait for each condition to be true + before moving forward. If the conditional is not true within the configured number of retries, the task fails. See + examples. type: list elements: str match: description: - - The O(match) argument is used in conjunction with the - O(wait_for) argument to specify the match policy. Valid - values are V(all) or V(any). If the value is set to V(all) - then all conditionals in the wait_for must be satisfied. If - the value is set to V(any) then only one of the values must be - satisfied. + - The O(match) argument is used in conjunction with the O(wait_for) argument to specify the match policy. Valid values + are V(all) or V(any). If the value is set to V(all) then all conditionals in the wait_for must be satisfied. If the + value is set to V(any) then only one of the values must be satisfied. default: all choices: ['any', 'all'] type: str retries: description: - - Specifies the number of retries a command should by tried - before it is considered failed. The command is run on the - target device every retry and evaluated against the - O(wait_for) conditions. + - Specifies the number of retries a command should by tried before it is considered failed. The command is run on the + target device every retry and evaluated against the O(wait_for) conditions. default: 10 type: int interval: description: - - Configures the interval in seconds to wait between retries - of the command. If the command does not pass the specified - conditions, the interval indicates how long to wait before - trying the command again. + - Configures the interval in seconds to wait between retries of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before trying the command again. default: 1 type: int seealso: - ref: ansible_collections.community.routeros.docsite.ssh-guide - description: How to connect to RouterOS devices with SSH + description: How to connect to RouterOS devices with SSH. - ref: ansible_collections.community.routeros.docsite.quoting - description: How to quote and unquote commands and arguments -''' + description: How to quote and unquote commands and arguments. +""" -EXAMPLES = """ +EXAMPLES = r""" +--- - name: Run command on remote devices community.routeros.command: commands: /system routerboard print @@ -113,19 +103,19 @@ EXAMPLES = """ - result[1] contains ether1 """ -RETURN = """ +RETURN = r""" stdout: - description: The set of responses from the commands + description: The set of responses from the commands. returned: always apart from low level errors (such as action plugin) type: list sample: ['...', '...'] stdout_lines: - description: The value of stdout split into a list + description: The value of stdout split into a list. returned: always apart from low level errors (such as action plugin) type: list sample: [['...', '...'], ['...'], ['...']] failed_conditions: - description: The list of conditionals that have failed + description: The list of conditionals that have failed. returned: failed type: list sample: ['...', '...'] diff --git a/plugins/modules/facts.py b/plugins/modules/facts.py index 50f9a21..e80143c 100644 --- a/plugins/modules/facts.py +++ b/plugins/modules/facts.py @@ -7,21 +7,19 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' ---- +DOCUMENTATION = r""" module: facts author: "Egor Zaitsev (@heuels)" short_description: Collect facts from remote devices running MikroTik RouterOS description: - - Collects a base set of device facts from a remote device that - is running RouterOS. This module prepends all of the - base network fact keys with C(ansible_net_). The facts - module will always collect a base set of facts from the device + - Collects a base set of device facts from a remote device that is running RouterOS. This module prepends all of the base + network fact keys with C(ansible_net_). The facts module will always collect a base set of facts from the device and can enable or disable collection of additional facts. extends_documentation_fragment: - community.routeros.attributes - community.routeros.attributes.facts - community.routeros.attributes.facts_module + - community.routeros.attributes.idempotent_not_modify_state attributes: platform: support: full @@ -29,12 +27,10 @@ attributes: options: gather_subset: description: - - When supplied, this argument will restrict the facts collected - to a given subset. Possible values for this argument include - V(all), V(hardware), V(config), V(interfaces), and V(routing). - - Can specify a list of values to include a larger subset. - Values can also be used with an initial V(!) to specify that a - specific subset should not be collected. + - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument + include V(all), V(hardware), V(config), V(interfaces), and V(routing). + - Can specify a list of values to include a larger subset. Values can also be used with an initial V(!) to specify that + a specific subset should not be collected. required: false default: - '!config' @@ -42,10 +38,11 @@ options: elements: str seealso: - ref: ansible_collections.community.routeros.docsite.ssh-guide - description: How to connect to RouterOS devices with SSH -''' + description: How to connect to RouterOS devices with SSH. +""" -EXAMPLES = """ +EXAMPLES = r""" +--- - name: Collect all facts from the device community.routeros.facts: gather_subset: all @@ -61,7 +58,7 @@ EXAMPLES = """ - "!hardware" """ -RETURN = """ +RETURN = r""" ansible_facts: description: "Dictionary of IP geolocation facts for a host's IP address." returned: always @@ -129,9 +126,9 @@ ansible_facts: ansible_net_config_nonverbose: description: - The current active config from the device in minimal form. - - This value is idempotent in the sense that if the facts module is run twice and the device's config - was not changed between the runs, the value is identical. This is achieved by running C(/export) - and stripping the timestamp from the comment in the first line. + - This value is idempotent in the sense that if the facts module is run twice and the device's config was not changed + between the runs, the value is identical. This is achieved by running C(/export) and stripping the timestamp from + the comment in the first line. returned: O(gather_subset) contains V(config) type: str version_added: 1.2.0 @@ -592,8 +589,6 @@ FACT_SUBSETS = dict( VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) -warnings = list() - def main(): """main entry point for module execution @@ -656,7 +651,7 @@ def main(): key = 'ansible_net_%s' % key ansible_facts[key] = value - module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + module.exit_json(ansible_facts=ansible_facts) if __name__ == '__main__': diff --git a/plugins/terminal/routeros.py b/plugins/terminal/routeros.py index 9d50fa2..8a39561 100644 --- a/plugins/terminal/routeros.py +++ b/plugins/terminal/routeros.py @@ -31,7 +31,9 @@ class TerminalModule(TerminalBase): terminal_stdout_re = [ re.compile(br"\x1b<"), - re.compile(br"\[[\w\-\.]+\@[\w\s\-\.\/]+\] ?( ?$"), + re.compile( + br"((\[[\w\-\.]+\@)|(\r\<(([\w\-\.]*\@)|)))" + br"[\w\s\-\.\/]+\] ?( ?$"), re.compile(br"Please press \"Enter\" to continue!"), re.compile(br"Do you want to see the software license\? \[Y\/n\]: ?"), ] diff --git a/tests/ee/roles/filter_quoting/tasks/main.yml b/tests/ee/roles/filter_quoting/tasks/main.yml index e7a2d29..c80af59 100644 --- a/tests/ee/roles/filter_quoting/tasks/main.yml +++ b/tests/ee/roles/filter_quoting/tasks/main.yml @@ -22,7 +22,7 @@ assert: that: - >- - result.msg == "Unexpected end of string during escaped parameter" + "Unexpected end of string during escaped parameter" in result.msg - name: "Test quote_argument filter" assert: diff --git a/tests/integration/requirements.yml b/tests/integration/requirements.yml index 6a22736..559c301 100644 --- a/tests/integration/requirements.yml +++ b/tests/integration/requirements.yml @@ -4,4 +4,4 @@ # SPDX-License-Identifier: GPL-3.0-or-later collections: -- ansible.netcommon + - ansible.netcommon diff --git a/tests/integration/targets/filter_quoting/tasks/main.yml b/tests/integration/targets/filter_quoting/tasks/main.yml index e7a2d29..c80af59 100644 --- a/tests/integration/targets/filter_quoting/tasks/main.yml +++ b/tests/integration/targets/filter_quoting/tasks/main.yml @@ -22,7 +22,7 @@ assert: that: - >- - result.msg == "Unexpected end of string during escaped parameter" + "Unexpected end of string during escaped parameter" in result.msg - name: "Test quote_argument filter" assert: diff --git a/tests/sanity/extra/extra-docs.json b/tests/sanity/extra/extra-docs.json deleted file mode 100644 index 9a28d17..0000000 --- a/tests/sanity/extra/extra-docs.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "include_symlinks": false, - "prefixes": [ - "docs/docsite/", - "plugins/", - "roles/" - ], - "output": "path-line-column-message", - "requirements": [ - "ansible-core", - "antsibull-docs" - ] -} diff --git a/tests/sanity/extra/extra-docs.py b/tests/sanity/extra/extra-docs.py deleted file mode 100755 index 251e6d7..0000000 --- a/tests/sanity/extra/extra-docs.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) Ansible Project -# 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 -"""Check extra collection docs with antsibull-docs.""" -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import os -import sys -import subprocess - - -def main(): - """Main entry point.""" - env = os.environ.copy() - suffix = ':{env}'.format(env=env["ANSIBLE_COLLECTIONS_PATH"]) if 'ANSIBLE_COLLECTIONS_PATH' in env else '' - env['ANSIBLE_COLLECTIONS_PATH'] = '{root}{suffix}'.format(root=os.path.dirname(os.path.dirname(os.path.dirname(os.getcwd()))), suffix=suffix) - p = subprocess.run( - ['antsibull-docs', 'lint-collection-docs', '--plugin-docs', '--skip-rstcheck', '.'], - env=env, - check=False, - ) - if p.returncode not in (0, 3): - print('{0}:0:0: unexpected return code {1}'.format(sys.argv[0], p.returncode)) - - -if __name__ == '__main__': - main() diff --git a/tests/sanity/extra/licenses.json b/tests/sanity/extra/licenses.json deleted file mode 100644 index 50e47ca..0000000 --- a/tests/sanity/extra/licenses.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "include_symlinks": false, - "output": "path-message" -} diff --git a/tests/sanity/extra/licenses.json.license b/tests/sanity/extra/licenses.json.license deleted file mode 100644 index edff8c7..0000000 --- a/tests/sanity/extra/licenses.json.license +++ /dev/null @@ -1,3 +0,0 @@ -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 -SPDX-FileCopyrightText: Ansible Project diff --git a/tests/sanity/extra/licenses.py b/tests/sanity/extra/licenses.py deleted file mode 100755 index 80eb795..0000000 --- a/tests/sanity/extra/licenses.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2022, Felix Fontein -# 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 -"""Prevent files without a correct license identifier from being added to the source tree.""" -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import os -import glob -import sys - - -def format_license_list(licenses): - if not licenses: - return '(empty)' - return ', '.join(['"%s"' % license for license in licenses]) - - -def find_licenses(filename, relax=False): - spdx_license_identifiers = [] - other_license_identifiers = [] - has_copyright = False - try: - with open(filename, 'r', encoding='utf-8') as f: - for line in f: - line = line.rstrip() - if 'Copyright ' in line: - has_copyright = True - if 'Copyright: ' in line: - print('%s: found copyright line with "Copyright:". Please remove the colon.' % (filename, )) - if 'SPDX-FileCopyrightText: ' in line: - has_copyright = True - idx = line.find('SPDX-License-Identifier: ') - if idx >= 0: - lic_id = line[idx + len('SPDX-License-Identifier: '):] - spdx_license_identifiers.extend(lic_id.split(' OR ')) - if 'GNU General Public License' in line: - if 'v3.0+' in line: - other_license_identifiers.append('GPL-3.0-or-later') - if 'version 3 or later' in line: - other_license_identifiers.append('GPL-3.0-or-later') - if 'Simplified BSD License' in line: - other_license_identifiers.append('BSD-2-Clause') - if 'Apache License 2.0' in line: - other_license_identifiers.append('Apache-2.0') - if 'PSF License' in line or 'Python-2.0' in line: - other_license_identifiers.append('PSF-2.0') - if 'MIT License' in line: - other_license_identifiers.append('MIT') - except Exception as exc: - print('%s: error while processing file: %s' % (filename, exc)) - if len(set(spdx_license_identifiers)) < len(spdx_license_identifiers): - print('%s: found identical SPDX-License-Identifier values' % (filename, )) - if other_license_identifiers and set(other_license_identifiers) != set(spdx_license_identifiers): - print('%s: SPDX-License-Identifier yielded the license list %s, while manual guessing yielded the license list %s' % ( - filename, format_license_list(spdx_license_identifiers), format_license_list(other_license_identifiers))) - if not has_copyright and not relax: - print('%s: found no copyright notice' % (filename, )) - return sorted(spdx_license_identifiers) - - -def main(): - """Main entry point.""" - paths = sys.argv[1:] or sys.stdin.read().splitlines() - - # The following paths are allowed to have no license identifier - no_comments_allowed = [ - 'changelogs/fragments/*.yml', - 'changelogs/fragments/*.yaml', - ] - - # These files are completely ignored - ignore_paths = [ - '.ansible-test-timeout.json', - '.reuse/dep5', - 'LICENSES/*.txt', - 'COPYING', - ] - - no_comments_allowed = [fn for pattern in no_comments_allowed for fn in glob.glob(pattern)] - ignore_paths = [fn for pattern in ignore_paths for fn in glob.glob(pattern)] - - valid_licenses = [license_file[len('LICENSES/'):-len('.txt')] for license_file in glob.glob('LICENSES/*.txt')] - - for path in paths: - if path.startswith('./'): - path = path[2:] - if path in ignore_paths or path.startswith('tests/output/'): - continue - if os.stat(path).st_size == 0: - continue - if not path.endswith('.license') and os.path.exists(path + '.license'): - path = path + '.license' - valid_licenses_for_path = valid_licenses - if path.startswith('plugins/') and not path.startswith(('plugins/modules/', 'plugins/module_utils/')): - valid_licenses_for_path = [license for license in valid_licenses if license == 'GPL-3.0-or-later'] - licenses = find_licenses(path, relax=path in no_comments_allowed) - if not licenses: - if path not in no_comments_allowed: - print('%s: must have at least one license' % (path, )) - else: - for license in licenses: - if license not in valid_licenses_for_path: - print('%s: found not allowed license "%s", must be one of %s' % ( - path, license, format_license_list(valid_licenses_for_path))) - - -if __name__ == '__main__': - main() diff --git a/tests/sanity/extra/licenses.py.license b/tests/sanity/extra/licenses.py.license deleted file mode 100644 index 6c4958f..0000000 --- a/tests/sanity/extra/licenses.py.license +++ /dev/null @@ -1,3 +0,0 @@ -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 -SPDX-FileCopyrightText: 2022, Felix Fontein diff --git a/tests/sanity/extra/no-unwanted-files.json b/tests/sanity/extra/no-unwanted-files.json deleted file mode 100644 index c789a7f..0000000 --- a/tests/sanity/extra/no-unwanted-files.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "include_symlinks": true, - "prefixes": [ - "plugins/" - ], - "output": "path-message" -} diff --git a/tests/sanity/extra/no-unwanted-files.json.license b/tests/sanity/extra/no-unwanted-files.json.license deleted file mode 100644 index edff8c7..0000000 --- a/tests/sanity/extra/no-unwanted-files.json.license +++ /dev/null @@ -1,3 +0,0 @@ -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 -SPDX-FileCopyrightText: Ansible Project diff --git a/tests/sanity/extra/no-unwanted-files.py b/tests/sanity/extra/no-unwanted-files.py deleted file mode 100755 index b39df83..0000000 --- a/tests/sanity/extra/no-unwanted-files.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) Ansible Project -# 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 -"""Prevent unwanted files from being added to the source tree.""" -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import os -import os.path -import sys - - -def main(): - """Main entry point.""" - paths = sys.argv[1:] or sys.stdin.read().splitlines() - - allowed_extensions = ( - '.cs', - '.ps1', - '.psm1', - '.py', - ) - - skip_paths = set([ - ]) - - skip_directories = ( - ) - - yaml_directories = ( - 'plugins/test/', - 'plugins/filter/', - ) - - for path in paths: - if path in skip_paths: - continue - - if any(path.startswith(skip_directory) for skip_directory in skip_directories): - continue - - if os.path.islink(path): - print('%s: is a symbolic link' % (path, )) - elif not os.path.isfile(path): - print('%s: is not a regular file' % (path, )) - - ext = os.path.splitext(path)[1] - - if ext in ('.yml', ) and any(path.startswith(yaml_directory) for yaml_directory in yaml_directories): - continue - - if ext not in allowed_extensions: - print('%s: extension must be one of: %s' % (path, ', '.join(allowed_extensions))) - - -if __name__ == '__main__': - main() diff --git a/tests/sanity/extra/update-docs.json b/tests/sanity/extra/update-docs.json deleted file mode 100644 index 3a63af8..0000000 --- a/tests/sanity/extra/update-docs.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "include_symlinks": false, - "prefixes": [ - "docs/docsite/rst/api-guide.rst", - "plugins/modules/" - ], - "output": "path-line-column-message", - "requirements": [ - "ansible-core" - ] -} diff --git a/tests/sanity/extra/update-docs.json.license b/tests/sanity/extra/update-docs.json.license deleted file mode 100644 index edff8c7..0000000 --- a/tests/sanity/extra/update-docs.json.license +++ /dev/null @@ -1,3 +0,0 @@ -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 -SPDX-FileCopyrightText: Ansible Project diff --git a/tests/sanity/extra/update-docs.py b/tests/sanity/extra/update-docs.py deleted file mode 100644 index 6015512..0000000 --- a/tests/sanity/extra/update-docs.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) Ansible Project -# 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 -"""Check whether update-docs.py modifies something.""" -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import sys -import subprocess - - -def main(): - """Main entry point.""" - p = subprocess.run([sys.executable, 'update-docs.py'], check=False) - if p.returncode not in (0, 1): - print('{0}:0:0: unexpected return code {1}'.format(sys.argv[0], p.returncode)) - - -if __name__ == '__main__': - main() diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index ece5b29..50c92fb 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -1,9 +1,9 @@ docs/docsite/rst/api-guide.rst rstcheck docs/docsite/rst/quoting.rst rstcheck docs/docsite/rst/ssh-guide.rst rstcheck -update-docs.py compile-2.6 -update-docs.py compile-2.7 -update-docs.py compile-3.5 -update-docs.py future-import-boilerplate -update-docs.py metaclass-boilerplate -update-docs.py shebang +tests/update-docs.py compile-2.6 +tests/update-docs.py compile-2.7 +tests/update-docs.py compile-3.5 +tests/update-docs.py future-import-boilerplate +tests/update-docs.py metaclass-boilerplate +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 876765a..1c7f5da 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -1,6 +1,6 @@ -update-docs.py compile-2.6 -update-docs.py compile-2.7 -update-docs.py compile-3.5 -update-docs.py future-import-boilerplate -update-docs.py metaclass-boilerplate -update-docs.py shebang +tests/update-docs.py compile-2.6 +tests/update-docs.py compile-2.7 +tests/update-docs.py compile-3.5 +tests/update-docs.py future-import-boilerplate +tests/update-docs.py metaclass-boilerplate +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index ce635c3..65e5bca 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -1 +1 @@ -update-docs.py shebang +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt index ce635c3..65e5bca 100644 --- a/tests/sanity/ignore-2.13.txt +++ b/tests/sanity/ignore-2.13.txt @@ -1 +1 @@ -update-docs.py shebang +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index ce635c3..65e5bca 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -1 +1 @@ -update-docs.py shebang +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index ce635c3..65e5bca 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -1 +1 @@ -update-docs.py shebang +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index ce635c3..65e5bca 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -1 +1 @@ -update-docs.py shebang +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index 0a5234b..65e5bca 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -1,2 +1 @@ -update-docs.py shebang -tests/unit/compat/mock.py pylint:use-yield-from # suggested construct does not work with Python 2 +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt index 0a5234b..65e5bca 100644 --- a/tests/sanity/ignore-2.18.txt +++ b/tests/sanity/ignore-2.18.txt @@ -1,2 +1 @@ -update-docs.py shebang -tests/unit/compat/mock.py pylint:use-yield-from # suggested construct does not work with Python 2 +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt index 0a5234b..65e5bca 100644 --- a/tests/sanity/ignore-2.19.txt +++ b/tests/sanity/ignore-2.19.txt @@ -1,2 +1 @@ -update-docs.py shebang -tests/unit/compat/mock.py pylint:use-yield-from # suggested construct does not work with Python 2 +tests/update-docs.py shebang diff --git a/tests/sanity/ignore-2.20.txt b/tests/sanity/ignore-2.20.txt new file mode 100644 index 0000000..65e5bca --- /dev/null +++ b/tests/sanity/ignore-2.20.txt @@ -0,0 +1 @@ +tests/update-docs.py shebang diff --git a/tests/sanity/extra/extra-docs.json.license b/tests/sanity/ignore-2.20.txt.license similarity index 100% rename from tests/sanity/extra/extra-docs.json.license rename to tests/sanity/ignore-2.20.txt.license diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index ece5b29..50c92fb 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -1,9 +1,9 @@ docs/docsite/rst/api-guide.rst rstcheck docs/docsite/rst/quoting.rst rstcheck docs/docsite/rst/ssh-guide.rst rstcheck -update-docs.py compile-2.6 -update-docs.py compile-2.7 -update-docs.py compile-3.5 -update-docs.py future-import-boilerplate -update-docs.py metaclass-boilerplate -update-docs.py shebang +tests/update-docs.py compile-2.6 +tests/update-docs.py compile-2.7 +tests/update-docs.py compile-3.5 +tests/update-docs.py future-import-boilerplate +tests/update-docs.py metaclass-boilerplate +tests/update-docs.py shebang diff --git a/tests/unit/compat/__init__.py b/tests/unit/compat/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/compat/builtins.py b/tests/unit/compat/builtins.py deleted file mode 100644 index d548601..0000000 --- a/tests/unit/compat/builtins.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2014, Toshio Kuratomi -# 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 - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -# -# Compat for python2.7 -# - -# One unittest needs to import builtins via __import__() so we need to have -# the string that represents it -try: - import __builtin__ # noqa: F401, pylint: disable=unused-import -except ImportError: - BUILTINS = 'builtins' -else: - BUILTINS = '__builtin__' diff --git a/tests/unit/compat/mock.py b/tests/unit/compat/mock.py deleted file mode 100644 index bdbea94..0000000 --- a/tests/unit/compat/mock.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (c) 2014, Toshio Kuratomi -# 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 - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -''' -Compat module for Python3.x's unittest.mock module -''' -import sys - -# Python 2.7 - -# Note: Could use the pypi mock library on python3.x as well as python2.x. It -# is the same as the python3 stdlib mock library - -try: - # Allow wildcard import because we really do want to import all of mock's - # symbols into this compat shim - # pylint: disable=wildcard-import,unused-wildcard-import - from unittest.mock import * # noqa: F401, pylint: disable=unused-import -except ImportError: - # Python 2 - # pylint: disable=wildcard-import,unused-wildcard-import - try: - from mock import * # noqa: F401, pylint: disable=unused-import - except ImportError: - print('You need the mock library installed on python2.x to run tests') - - -# Prior to 3.4.4, mock_open cannot handle binary read_data -if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): - file_spec = None - - def _iterate_read_data(read_data): - # Helper for mock_open: - # Retrieve lines from read_data via a generator so that separate calls to - # readline, read, and readlines are properly interleaved - sep = b'\n' if isinstance(read_data, bytes) else '\n' - data_as_list = [l + sep for l in read_data.split(sep)] - - if data_as_list[-1] == sep: - # If the last line ended in a newline, the list comprehension will have an - # extra entry that's just a newline. Remove this. - data_as_list = data_as_list[:-1] - else: - # If there wasn't an extra newline by itself, then the file being - # emulated doesn't have a newline to end the last line remove the - # newline that our naive format() added - data_as_list[-1] = data_as_list[-1][:-1] - - for line in data_as_list: - yield line - - def mock_open(mock=None, read_data=''): - """ - A helper function to create a mock to replace the use of `open`. It works - for `open` called directly or used as a context manager. - - The `mock` argument is the mock object to configure. If `None` (the - default) then a `MagicMock` will be created for you, with the API limited - to methods or attributes available on standard file handles. - - `read_data` is a string for the `read` methoddline`, and `readlines` of the - file handle to return. This is an empty string by default. - """ - def _readlines_side_effect(*args, **kwargs): - if handle.readlines.return_value is not None: - return handle.readlines.return_value - return list(_data) - - def _read_side_effect(*args, **kwargs): - if handle.read.return_value is not None: - return handle.read.return_value - return type(read_data)().join(_data) - - def _readline_side_effect(): - if handle.readline.return_value is not None: - while True: - yield handle.readline.return_value - for line in _data: - yield line - - global file_spec - if file_spec is None: - import _io - file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) - - if mock is None: - mock = MagicMock(name='open', spec=open) - - handle = MagicMock(spec=file_spec) - handle.__enter__.return_value = handle - - _data = _iterate_read_data(read_data) - - handle.write.return_value = None - handle.read.return_value = None - handle.readline.return_value = None - handle.readlines.return_value = None - - handle.read.side_effect = _read_side_effect - handle.readline.side_effect = _readline_side_effect() - handle.readlines.side_effect = _readlines_side_effect - - mock.return_value = handle - return mock diff --git a/tests/unit/compat/unittest.py b/tests/unit/compat/unittest.py deleted file mode 100644 index d50bab8..0000000 --- a/tests/unit/compat/unittest.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2014, Toshio Kuratomi -# 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 - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -''' -Compat module for Python2.7's unittest module -''' - -import sys - -# Allow wildcard import because we really do want to import all of -# unittests's symbols into this compat shim -# pylint: disable=wildcard-import,unused-wildcard-import -if sys.version_info < (2, 7): - try: - # Need unittest2 on python2.6 - from unittest2 import * # noqa: F401, pylint: disable=unused-import - except ImportError: - print('You need unittest2 installed on python2.6.x to run tests') -else: - from unittest import * # noqa: F401, pylint: disable=unused-import diff --git a/tests/unit/plugins/modules/routeros_module.py b/tests/unit/plugins/modules/routeros_module.py index 0ec44f7..4786da6 100644 --- a/tests/unit/plugins/modules/routeros_module.py +++ b/tests/unit/plugins/modules/routeros_module.py @@ -9,7 +9,7 @@ __metaclass__ = type import os import json -from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase +from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') diff --git a/tests/unit/plugins/modules/test_api.py b/tests/unit/plugins/modules/test_api.py index 4cfdeef..9a03825 100644 --- a/tests/unit/plugins/modules/test_api.py +++ b/tests/unit/plugins/modules/test_api.py @@ -6,9 +6,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase + from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import FakeLibRouterosError, Key, Or, fake_ros_api -from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase from ansible_collections.community.routeros.plugins.modules import api @@ -34,8 +35,8 @@ class TestRouterosApiModule(ModuleTestCase): def test_module_fail_when_required_args_missing(self): with self.assertRaises(AnsibleFailJson) as exc: - set_module_args({}) - self.module.main() + with set_module_args({}): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -43,8 +44,8 @@ class TestRouterosApiModule(ModuleTestCase): @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api.path) def test_api_path(self): with self.assertRaises(AnsibleExitJson) as exc: - set_module_args(self.config_module_args.copy()) - self.module.main() + with set_module_args(self.config_module_args.copy()): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -54,8 +55,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleExitJson) as exc: module_args = self.config_module_args.copy() module_args['add'] = "name=unit_test_brige" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -65,8 +66,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleFailJson) as exc: module_args = self.config_module_args.copy() module_args['add'] = "name=unit_test_brige_exist" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -77,8 +78,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleExitJson) as exc: module_args = self.config_module_args.copy() module_args['remove'] = "*A1" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -88,8 +89,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleFailJson) as exc: module_args = self.config_module_args.copy() module_args['remove'] = "*A2" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -100,8 +101,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleExitJson) as exc: module_args = self.config_module_args.copy() module_args['cmd'] = "add name=unit_test_brige_arbitrary" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -111,8 +112,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleFailJson) as exc: module_args = self.config_module_args.copy() module_args['cmd'] = "add NONE_EXIST=unit_test_brige_arbitrary" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -123,8 +124,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleExitJson) as exc: module_args = self.config_module_args.copy() module_args['update'] = ".id=*A1 name=unit_test_brige" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -134,8 +135,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleFailJson) as exc: module_args = self.config_module_args.copy() module_args['update'] = ".id=*A2 name=unit_test_brige" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -146,8 +147,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleExitJson) as exc: module_args = self.config_module_args.copy() module_args['query'] = ".id name" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -162,8 +163,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleExitJson) as exc: module_args = self.config_module_args.copy() module_args['query'] = ".id other" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -174,8 +175,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleExitJson) as exc: module_args = self.config_module_args.copy() module_args['query'] = ".id name WHERE name == dummy_bridge_A2" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -188,8 +189,8 @@ class TestRouterosApiModule(ModuleTestCase): with self.assertRaises(AnsibleExitJson) as exc: module_args = self.config_module_args.copy() module_args['query'] = ".id name WHERE name != dummy_bridge_A2" - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -204,8 +205,8 @@ class TestRouterosApiModule(ModuleTestCase): module_args['extended_query'] = { 'attributes': ['.id', 'name'], } - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -222,8 +223,8 @@ class TestRouterosApiModule(ModuleTestCase): module_args['extended_query'] = { 'attributes': ['.id', 'other'], } - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -243,8 +244,8 @@ class TestRouterosApiModule(ModuleTestCase): }, ], } - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -266,8 +267,8 @@ class TestRouterosApiModule(ModuleTestCase): }, ], } - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -298,8 +299,8 @@ class TestRouterosApiModule(ModuleTestCase): }, ], } - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) diff --git a/tests/unit/plugins/modules/test_api_facts.py b/tests/unit/plugins/modules/test_api_facts.py index 64985f8..7b019f9 100644 --- a/tests/unit/plugins/modules/test_api_facts.py +++ b/tests/unit/plugins/modules/test_api_facts.py @@ -6,9 +6,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase + from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import FakeLibRouterosError, Key, fake_ros_api -from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase from ansible_collections.community.routeros.plugins.modules import api_facts @@ -437,8 +438,8 @@ class TestRouterosApiFactsModule(ModuleTestCase): def test_module_fail_when_required_args_missing(self): with self.assertRaises(AnsibleFailJson) as exc: - set_module_args({}) - self.module.main() + with set_module_args({}): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -447,8 +448,8 @@ class TestRouterosApiFactsModule(ModuleTestCase): with self.assertRaises(AnsibleFailJson) as exc: module_args = self.config_module_args.copy() module_args['gather_subset'] = ['!foobar'] - set_module_args(module_args) - self.module.main() + with set_module_args(module_args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -456,8 +457,8 @@ class TestRouterosApiFactsModule(ModuleTestCase): def test_full_run(self): with self.assertRaises(AnsibleExitJson) as exc: - set_module_args(self.config_module_args.copy()) - self.module.main() + with set_module_args(self.config_module_args.copy()): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) diff --git a/tests/unit/plugins/modules/test_api_find_and_modify.py b/tests/unit/plugins/modules/test_api_find_and_modify.py index 2f47af4..e700f9d 100644 --- a/tests/unit/plugins/modules/test_api_find_and_modify.py +++ b/tests/unit/plugins/modules/test_api_find_and_modify.py @@ -6,11 +6,12 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase + from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import ( FakeLibRouterosError, fake_ros_api, massage_expected_result_data, create_fake_path, ) -from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase from ansible_collections.community.routeros.plugins.modules import api_find_and_modify @@ -93,6 +94,52 @@ START_IP_FIREWALL_FILTER = [ START_IP_FIREWALL_FILTER_OLD_DATA = massage_expected_result_data(START_IP_FIREWALL_FILTER, ('ip', 'firewall', 'filter'), keep_all=True) +START_IP_SERVICE = [ + # I removed all entryes not for 'api' and 'api-ssl' + { + "certificate": None, + "tls-version": None, + ".id": "*7", + "address": "", + "disabled": True, + "dynamic": False, + "invalid": True, + "name": "api", + "port": 8728, + "proto": "tcp", + "vrf": "main" + }, + { + ".id": "*9", + "address": "192.168.1.0/24", + "certificate": "mycert", + "dynamic": False, + "invalid": False, + "name": "api-ssl", + "port": 8729, + "proto": "tcp", + "tls-version": "only-1.2", + "vrf": "main" + }, + { + "address": None, + "certificate": None, + "max-sessions": None, + "tls-version": None, + ".id": "*13", + "connection": True, + "dynamic": True, + "invalid": False, + "local": "192.168.1.1", + "name": "api-ssl", + "port": 8729, + "proto": "tcp", + "remote": "192.168.1.2:12346" + } +] + +START_IP_SERVICE_OLD_DATA = massage_expected_result_data(START_IP_SERVICE, ('ip', 'service'), keep_all=True) + class TestRouterosApiFindAndModifyModule(ModuleTestCase): @@ -117,8 +164,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): def test_module_fail_when_required_args_missing(self): with self.assertRaises(AnsibleFailJson) as exc: - set_module_args({}) - self.module.main() + with set_module_args({}): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -136,8 +183,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': 'bar', }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -155,8 +202,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': 'bar', }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -173,8 +220,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): '!comment': None, }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -190,8 +237,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): '!comment': 'gone', }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -212,8 +259,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): }, 'require_matches_min': 10, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -234,8 +281,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): }, 'require_matches_min': 10, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -256,8 +303,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): }, 'require_matches_max': 1, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -277,8 +324,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'name': 'bam', }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -303,8 +350,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'require_matches_min': 2, 'allow_no_matches': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -325,8 +372,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'values': { }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -349,8 +396,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': None, }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -374,8 +421,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): }, '_ansible_diff': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -450,8 +497,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': None, }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -502,8 +549,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': '', }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -553,8 +600,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): '!comment': None, }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -606,8 +653,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): '!connection-state': None, }, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -620,6 +667,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': 'defconf', 'protocol': 'icmp', 'disabled': False, + 'log': False, + 'log-prefix': '', }, { '.id': '*3', @@ -627,6 +676,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'chain': 'input', 'comment': 'defconf', 'disabled': False, + 'log': False, + 'log-prefix': '', }, { '.id': '*4', @@ -634,6 +685,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'chain': 'input', 'comment': 'defconf', 'disabled': False, + 'log': False, + 'log-prefix': '', }, { '.id': '*7', @@ -642,6 +695,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': 'defconf', 'disabled': False, 'in-interface': 'wan', + 'log': False, + 'log-prefix': '', }, { '.id': '*8', @@ -650,6 +705,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': 'defconf', 'connection-state': 'established', 'disabled': False, + 'log': False, + 'log-prefix': '', }, { '.id': '*9', @@ -658,6 +715,8 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': 'defconf', 'connection-state': 'related', 'disabled': False, + 'log': False, + 'log-prefix': '', }, { '.id': '*A', @@ -666,7 +725,35 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase): 'comment': 'defconf', 'connection-status': 'invalid', 'disabled': False, + 'log': False, + 'log-prefix': '', }, ]) self.assertEqual(result['match_count'], 3) self.assertEqual(result['modify_count'], 2) + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'service'), START_IP_SERVICE)) + def test_change_ignore_dynamic(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip service', + 'find': { + 'name': 'api-ssl', + }, + 'values': { + 'address': '192.168.1.0/24', + }, + 'ignore_dynamic': True, + '_ansible_diff': True, + }) + with set_module_args(args): + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], [entry for entry in START_IP_SERVICE_OLD_DATA if entry["dynamic"] is False]) + self.assertEqual(result['new_data'], [entry for entry in START_IP_SERVICE_OLD_DATA if entry["dynamic"] is False]) + self.assertEqual(result['match_count'], 1) + self.assertEqual(result['modify_count'], 0) diff --git a/tests/unit/plugins/modules/test_api_info.py b/tests/unit/plugins/modules/test_api_info.py index 1c9d014..967a854 100644 --- a/tests/unit/plugins/modules/test_api_info.py +++ b/tests/unit/plugins/modules/test_api_info.py @@ -6,11 +6,12 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase + from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import ( FAKE_ROS_VERSION, FakeLibRouterosError, Key, fake_ros_api, ) -from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase from ansible_collections.community.routeros.plugins.modules import api_info @@ -41,8 +42,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): def test_module_fail_when_required_args_missing(self): with self.assertRaises(AnsibleFailJson) as exc: - set_module_args({}) - self.module.main() + with set_module_args({}): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -53,8 +54,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): args.update({ 'path': 'something invalid' }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -68,8 +69,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): args.update({ 'path': 'ip dns static' }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -93,8 +94,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): args.update({ 'path': 'caps-man aaa', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -122,8 +123,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'path': 'caps-man aaa', 'hide_defaults': False, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -155,8 +156,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'path': 'caps-man aaa', 'unfiltered': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -189,8 +190,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'path': 'ip firewall filter', 'handle_disabled': 'exclamation', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -278,8 +279,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'path': 'ip firewall filter', 'handle_disabled': 'null-value', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -367,8 +368,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'path': 'ip firewall filter', 'handle_disabled': 'omit', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -402,8 +403,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'handle_disabled': 'omit', 'include_dynamic': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -460,8 +461,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'path': 'interface list', 'handle_disabled': 'omit', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -513,8 +514,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'handle_disabled': 'omit', 'include_builtin': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -605,8 +606,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'path': 'ip dhcp-server lease', 'handle_disabled': 'omit', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -688,8 +689,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): args.update({ 'path': 'interface gre', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -776,8 +777,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'handle_disabled': 'omit', 'hide_defaults': False, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -847,8 +848,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'handle_disabled': 'omit', 'restrict': [], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -899,8 +900,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): 'values': ['forward'], }], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -970,8 +971,8 @@ class TestRouterosApiInfoModule(ModuleTestCase): }, ], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) diff --git a/tests/unit/plugins/modules/test_api_modify.py b/tests/unit/plugins/modules/test_api_modify.py index f4aa777..6edc046 100644 --- a/tests/unit/plugins/modules/test_api_modify.py +++ b/tests/unit/plugins/modules/test_api_modify.py @@ -6,11 +6,12 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase + from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import ( FAKE_ROS_VERSION, FakeLibRouterosError, fake_ros_api, massage_expected_result_data, create_fake_path, ) -from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase from ansible_collections.community.routeros.plugins.modules import api_modify @@ -318,8 +319,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): def test_module_fail_when_required_args_missing(self): with self.assertRaises(AnsibleFailJson) as exc: - set_module_args({}) - self.module.main() + with set_module_args({}): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -331,8 +332,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'path': 'something invalid', 'data': [], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -348,8 +349,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'foo': 'bar', }], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -366,8 +367,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): '!comment': None, }], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -383,8 +384,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): '!disabled': None, }], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -400,8 +401,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): '!comment': 'foo', }], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -416,8 +417,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'name': None, }], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -432,8 +433,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'interface': 'eth0', }], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -448,8 +449,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'address': '192.168.88.1', }], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -466,8 +467,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'address': '192.168.88.1', }], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['failed'], True) @@ -498,8 +499,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): }, ], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -533,8 +534,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_absent_entries': 'remove', 'handle_entries_content': 'remove', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -556,8 +557,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): }, ], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -591,8 +592,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): }, ], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -656,8 +657,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): }, ], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -722,8 +723,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): ], '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -785,8 +786,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_absent_entries': 'remove', 'handle_entries_content': 'remove', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -844,8 +845,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -903,8 +904,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_absent_entries': 'remove', 'handle_entries_content': 'remove', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -963,8 +964,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1017,8 +1018,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_absent_entries': 'remove', 'handle_entries_content': 'remove', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1077,8 +1078,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1128,8 +1129,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_absent_entries': 'remove', 'handle_entries_content': 'remove', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1164,8 +1165,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1212,8 +1213,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', 'ensure_order': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1285,8 +1286,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'ensure_order': True, '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1340,8 +1341,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): }, ], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -1363,8 +1364,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): ], 'handle_entries_content': 'remove', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -1386,8 +1387,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): }, ], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1426,8 +1427,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): ], '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1466,8 +1467,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): ], 'handle_entries_content': 'remove', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1507,8 +1508,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1551,8 +1552,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): }, ], }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -1587,8 +1588,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_absent_entries': 'remove', 'handle_entries_content': 'remove', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -1621,8 +1622,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_absent_entries': 'remove', 'handle_entries_content': 'remove', }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1676,8 +1677,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1729,8 +1730,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', 'ensure_order': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1785,8 +1786,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'ensure_order': True, '_ansible_check_mode': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1847,8 +1848,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', 'ensure_order': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -1879,8 +1880,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', 'ensure_order': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -1911,8 +1912,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_entries_content': 'remove', 'ensure_order': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], True) @@ -1969,8 +1970,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_absent_entries': 'remove', 'ensure_order': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) @@ -2005,8 +2006,8 @@ class TestRouterosApiModifyModule(ModuleTestCase): 'handle_absent_entries': 'remove', 'ensure_order': True, }) - set_module_args(args) - self.module.main() + with set_module_args(args): + self.module.main() result = exc.exception.args[0] self.assertEqual(result['changed'], False) diff --git a/tests/unit/plugins/modules/test_command.py b/tests/unit/plugins/modules/test_command.py index 3fc5865..06153f0 100644 --- a/tests/unit/plugins/modules/test_command.py +++ b/tests/unit/plugins/modules/test_command.py @@ -8,9 +8,10 @@ __metaclass__ = type import json -from ansible_collections.community.routeros.tests.unit.compat.mock import patch +from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch +from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args + from ansible_collections.community.routeros.plugins.modules import command -from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args from .routeros_module import TestRouterosModule, load_fixture @@ -47,54 +48,54 @@ class TestRouterosCommandModule(TestRouterosModule): self.run_commands.side_effect = load_from_file def test_command_simple(self): - set_module_args(dict(commands=['/system resource print'])) - result = self.execute_module(changed=True) + with set_module_args(dict(commands=['/system resource print'])): + result = self.execute_module(changed=True) self.assertEqual(len(result['stdout']), 1) self.assertTrue('platform: "MikroTik"' in result['stdout'][0]) def test_command_multiple(self): - set_module_args(dict(commands=['/system resource print', '/system resource print'])) - result = self.execute_module(changed=True) + with set_module_args(dict(commands=['/system resource print', '/system resource print'])): + result = self.execute_module(changed=True) self.assertEqual(len(result['stdout']), 2) self.assertTrue('platform: "MikroTik"' in result['stdout'][0]) def test_command_wait_for(self): wait_for = 'result[0] contains "MikroTik"' - set_module_args(dict(commands=['/system resource print'], wait_for=wait_for)) - self.execute_module(changed=True) + with set_module_args(dict(commands=['/system resource print'], wait_for=wait_for)): + self.execute_module(changed=True) def test_command_wait_for_fails(self): wait_for = 'result[0] contains "test string"' - set_module_args(dict(commands=['/system resource print'], wait_for=wait_for)) - self.execute_module(failed=True) + with set_module_args(dict(commands=['/system resource print'], wait_for=wait_for)): + self.execute_module(failed=True) self.assertEqual(self.run_commands.call_count, 10) def test_command_retries(self): wait_for = 'result[0] contains "test string"' - set_module_args(dict(commands=['/system resource print'], wait_for=wait_for, retries=2)) - self.execute_module(failed=True) + with set_module_args(dict(commands=['/system resource print'], wait_for=wait_for, retries=2)): + self.execute_module(failed=True) self.assertEqual(self.run_commands.call_count, 2) def test_command_match_any(self): wait_for = ['result[0] contains "MikroTik"', 'result[0] contains "test string"'] - set_module_args(dict(commands=['/system resource print'], wait_for=wait_for, match='any')) - self.execute_module(changed=True) + with set_module_args(dict(commands=['/system resource print'], wait_for=wait_for, match='any')): + self.execute_module(changed=True) def test_command_match_all(self): wait_for = ['result[0] contains "MikroTik"', 'result[0] contains "RB1100"'] - set_module_args(dict(commands=['/system resource print'], wait_for=wait_for, match='all')) - self.execute_module(changed=True) + with set_module_args(dict(commands=['/system resource print'], wait_for=wait_for, match='all')): + self.execute_module(changed=True) def test_command_match_all_failure(self): wait_for = ['result[0] contains "MikroTik"', 'result[0] contains "test string"'] commands = ['/system resource print', '/system resource print'] - set_module_args(dict(commands=commands, wait_for=wait_for, match='all')) - self.execute_module(failed=True) + with set_module_args(dict(commands=commands, wait_for=wait_for, match='all')): + self.execute_module(failed=True) def test_command_wait_for_2(self): wait_for = 'result[0] contains "wireless"' - set_module_args(dict(commands=['/system package print'], wait_for=wait_for)) - self.execute_module(changed=True) + with set_module_args(dict(commands=['/system package print'], wait_for=wait_for)): + self.execute_module(changed=True) diff --git a/tests/unit/plugins/modules/test_facts.py b/tests/unit/plugins/modules/test_facts.py index 322fdda..918f378 100644 --- a/tests/unit/plugins/modules/test_facts.py +++ b/tests/unit/plugins/modules/test_facts.py @@ -6,9 +6,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible_collections.community.routeros.tests.unit.compat.mock import patch +from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch +from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args + from ansible_collections.community.routeros.plugins.modules import facts -from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args from .routeros_module import TestRouterosModule, load_fixture @@ -39,8 +40,8 @@ class TestRouterosFactsModule(TestRouterosModule): self.run_commands.side_effect = load_from_file def test_facts_default(self): - set_module_args(dict(gather_subset='default')) - result = self.execute_module() + with set_module_args(dict(gather_subset='default')): + result = self.execute_module() self.assertEqual( result['ansible_facts']['ansible_net_hostname'], 'MikroTik' ) @@ -61,8 +62,8 @@ class TestRouterosFactsModule(TestRouterosModule): ) def test_facts_hardware(self): - set_module_args(dict(gather_subset='hardware')) - result = self.execute_module() + with set_module_args(dict(gather_subset='hardware')): + result = self.execute_module() self.assertEqual( result['ansible_facts']['ansible_net_spacefree_mb'], 64921.6 ) @@ -77,8 +78,8 @@ class TestRouterosFactsModule(TestRouterosModule): ) def test_facts_config(self): - set_module_args(dict(gather_subset='config')) - result = self.execute_module() + with set_module_args(dict(gather_subset='config')): + result = self.execute_module() self.assertIsInstance( result['ansible_facts']['ansible_net_config'], str ) @@ -88,8 +89,8 @@ class TestRouterosFactsModule(TestRouterosModule): ) def test_facts_interfaces(self): - set_module_args(dict(gather_subset='interfaces')) - result = self.execute_module() + with set_module_args(dict(gather_subset='interfaces')): + result = self.execute_module() self.assertIn( result['ansible_facts']['ansible_net_all_ipv4_addresses'][0], ['10.37.129.3', '10.37.0.0', '192.168.88.1'] ) @@ -118,8 +119,8 @@ class TestRouterosFactsModule(TestRouterosModule): self.assertEqual(result, None) def test_facts_routing(self): - set_module_args(dict(gather_subset='routing')) - result = self.execute_module() + with set_module_args(dict(gather_subset='routing')): + result = self.execute_module() self.assertIn( result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['name'], ['iBGP_BRAS.TYRMA'] ) diff --git a/tests/unit/plugins/modules/utils.py b/tests/unit/plugins/modules/utils.py deleted file mode 100644 index 419eef1..0000000 --- a/tests/unit/plugins/modules/utils.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) Ansible Project -# 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 - -import json - -from ansible_collections.community.routeros.tests.unit.compat import unittest -from ansible_collections.community.routeros.tests.unit.compat.mock import patch -from ansible.module_utils import basic -from ansible.module_utils.common.text.converters import to_bytes - - -def set_module_args(args): - if '_ansible_remote_tmp' not in args: - args['_ansible_remote_tmp'] = '/tmp' - if '_ansible_keep_remote_files' not in args: - args['_ansible_keep_remote_files'] = False - - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) - - -class AnsibleExitJson(Exception): - pass - - -class AnsibleFailJson(Exception): - pass - - -def exit_json(*args, **kwargs): - if 'changed' not in kwargs: - kwargs['changed'] = False - raise AnsibleExitJson(kwargs) - - -def fail_json(*args, **kwargs): - kwargs['failed'] = True - raise AnsibleFailJson(kwargs) - - -class ModuleTestCase(unittest.TestCase): - - def setUp(self): - self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json) - self.mock_module.start() - self.mock_sleep = patch('time.sleep') - self.mock_sleep.start() - set_module_args({}) - self.addCleanup(self.mock_module.stop) - self.addCleanup(self.mock_sleep.stop) diff --git a/tests/unit/requirements.yml b/tests/unit/requirements.yml index 6a22736..107fe12 100644 --- a/tests/unit/requirements.yml +++ b/tests/unit/requirements.yml @@ -4,4 +4,4 @@ # SPDX-License-Identifier: GPL-3.0-or-later collections: -- ansible.netcommon + - community.internal_test_tools diff --git a/update-docs.py b/tests/update-docs.py old mode 100755 new mode 100644 similarity index 62% rename from update-docs.py rename to tests/update-docs.py index 05a9ee7..5f28f8e --- a/update-docs.py +++ b/tests/update-docs.py @@ -11,9 +11,6 @@ Updates DOCUMENTATION of modules using module_utils._api_data with the correct l import sys -# Ensure that we can import things from ansible_collections -sys.path.append('../../..') - from ansible_collections.community.routeros.plugins.module_utils._api_data import ( PATHS, join_path, @@ -26,24 +23,34 @@ MODULES = [ ] -def update_file(file, begin_line, end_line, choice_line, path_choices): +def update_file(file: str, begin_line: str, end_line: str, choice_line: str, path_choices: list[str]) -> bool: with open(file, 'r', encoding='utf-8') as f: lines = f.read().splitlines() begin_index = lines.index(begin_line) end_index = lines.index(end_line, begin_index + 1) new_lines = lines[:begin_index + 1] + [choice_line.format(choice=choice) for choice in path_choices] + lines[end_index:] - if lines != new_lines: - print(f'{file} has been updated') - with open(file, 'w', encoding='utf-8') as f: - f.write('\n'.join(new_lines) + '\n') + if lines == new_lines: + return False + print(f'{file} has been updated') + with open(file, 'w', encoding='utf-8') as f: + f.write('\n'.join(new_lines) + '\n') + return True -def main(): +def main(args: list[str]) -> int: path_choices = sorted([join_path(path) for path, path_info in PATHS.items() if path_info.fully_understood]) + changes = False for file in MODULES: - update_file(file, ' # BEGIN PATH LIST', ' # END PATH LIST', ' - {choice}', path_choices) + changes |= update_file(file, ' # BEGIN PATH LIST', ' # END PATH LIST', ' - {choice}', path_choices) + + lint = "--lint" in args + if not lint or not changes: + return 0 + + print("Run 'nox -Re update-docs'!") + return 1 if __name__ == '__main__': - main() + sys.exit(main(sys.argv[1:]))