From 975f66f2eebe9dadba04f275774d4ab83f74cf25 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 14:04:41 +0200 Subject: Adding upstream version 7.7.0+dfsg. Signed-off-by: Daniel Baumann --- .../frr/frr/.config/ansible-lint.yml | 3 + .../frr/frr/.github/workflows/codecoverage.yml | 15 + .../frr/frr/.github/workflows/test.yml | 45 + ansible_collections/frr/frr/.gitignore | 118 +++ ansible_collections/frr/frr/.isort.cfg | 6 + .../frr/frr/.pre-commit-config.yaml | 59 ++ ansible_collections/frr/frr/.prettierignore | 22 + ansible_collections/frr/frr/.yamllint | 15 + ansible_collections/frr/frr/CHANGELOG.rst | 73 ++ ansible_collections/frr/frr/FILES.json | 719 ++++++++++++++ ansible_collections/frr/frr/LICENSE | 674 +++++++++++++ ansible_collections/frr/frr/MANIFEST.json | 35 + ansible_collections/frr/frr/README.md | 139 +++ ansible_collections/frr/frr/bindep.txt | 6 + .../frr/frr/changelogs/changelog.yaml | 62 ++ ansible_collections/frr/frr/changelogs/config.yaml | 30 + ansible_collections/frr/frr/codecov.yml | 10 + .../frr/frr/docs/frr.frr.frr_bgp_module.rst | 1029 ++++++++++++++++++++ .../frr/frr/docs/frr.frr.frr_cliconf.rst | 43 + .../frr/frr/docs/frr.frr.frr_facts_module.rst | 283 ++++++ ansible_collections/frr/frr/meta/runtime.yml | 8 + .../frr/frr/plugins/cliconf/__init__.py | 0 ansible_collections/frr/frr/plugins/cliconf/frr.py | 260 +++++ .../frr/frr/plugins/module_utils/__init__.py | 0 .../plugins/module_utils/network/frr/__init__.py | 0 .../frr/plugins/module_utils/network/frr/frr.py | 45 + .../module_utils/network/frr/providers/__init__.py | 0 .../network/frr/providers/cli/__init__.py | 0 .../network/frr/providers/cli/config/__init__.py | 0 .../network/frr/providers/cli/config/base.py | 84 ++ .../frr/providers/cli/config/bgp/__init__.py | 0 .../frr/providers/cli/config/bgp/address_family.py | 146 +++ .../frr/providers/cli/config/bgp/neighbors.py | 208 ++++ .../frr/providers/cli/config/bgp/process.py | 165 ++++ .../module_utils/network/frr/providers/module.py | 68 ++ .../network/frr/providers/providers.py | 126 +++ .../frr/frr/plugins/modules/__init__.py | 0 .../frr/frr/plugins/modules/frr_bgp.py | 478 +++++++++ .../frr/frr/plugins/modules/frr_facts.py | 406 ++++++++ .../frr/frr/plugins/terminal/__init__.py | 0 .../frr/frr/plugins/terminal/frr.py | 59 ++ ansible_collections/frr/frr/pyproject.toml | 7 + ansible_collections/frr/frr/requirements.txt | 0 ansible_collections/frr/frr/test-requirements.txt | 9 + ansible_collections/frr/frr/tests/.gitignore | 1 + .../frr/frr/tests/sanity/ignore-2.10.txt | 0 .../frr/frr/tests/sanity/ignore-2.12.txt | 0 .../frr/frr/tests/sanity/ignore-2.13.txt | 0 .../frr/frr/tests/sanity/ignore-2.14.txt | 0 .../frr/frr/tests/sanity/ignore-2.15.txt | 0 .../frr/frr/tests/sanity/ignore-2.16.txt | 0 .../frr/frr/tests/sanity/ignore-2.9.txt | 0 ansible_collections/frr/frr/tests/unit/__init__.py | 0 .../frr/frr/tests/unit/compat/__init__.py | 0 .../frr/frr/tests/unit/compat/mock.py | 28 + .../frr/frr/tests/unit/compat/unittest.py | 41 + .../frr/frr/tests/unit/mock/__init__.py | 0 .../frr/frr/tests/unit/mock/loader.py | 117 +++ .../frr/frr/tests/unit/mock/path.py | 12 + .../frr/frr/tests/unit/mock/procenv.py | 97 ++ .../frr/frr/tests/unit/mock/vault_helper.py | 40 + .../frr/frr/tests/unit/mock/yaml_helper.py | 158 +++ .../frr/frr/tests/unit/modules/__init__.py | 0 .../frr/frr/tests/unit/modules/conftest.py | 33 + .../frr/frr/tests/unit/modules/network/__init__.py | 0 .../frr/tests/unit/modules/network/frr/__init__.py | 0 .../unit/modules/network/frr/fixtures/__init__.py | 0 .../modules/network/frr/fixtures/frr_bgp_config | 24 + .../network/frr/fixtures/frr_facts_show_interface | 36 + .../network/frr/fixtures/frr_facts_show_memory | 82 ++ .../network/frr/fixtures/frr_facts_show_version | 16 + .../tests/unit/modules/network/frr/frr_module.py | 90 ++ .../tests/unit/modules/network/frr/test_frr_bgp.py | 362 +++++++ .../unit/modules/network/frr/test_frr_facts.py | 121 +++ .../frr/frr/tests/unit/modules/utils.py | 56 ++ .../frr/frr/tests/unit/requirements.txt | 42 + ansible_collections/frr/frr/tox.ini | 31 + 77 files changed, 6842 insertions(+) create mode 100644 ansible_collections/frr/frr/.config/ansible-lint.yml create mode 100644 ansible_collections/frr/frr/.github/workflows/codecoverage.yml create mode 100644 ansible_collections/frr/frr/.github/workflows/test.yml create mode 100644 ansible_collections/frr/frr/.gitignore create mode 100644 ansible_collections/frr/frr/.isort.cfg create mode 100644 ansible_collections/frr/frr/.pre-commit-config.yaml create mode 100644 ansible_collections/frr/frr/.prettierignore create mode 100644 ansible_collections/frr/frr/.yamllint create mode 100644 ansible_collections/frr/frr/CHANGELOG.rst create mode 100644 ansible_collections/frr/frr/FILES.json create mode 100644 ansible_collections/frr/frr/LICENSE create mode 100644 ansible_collections/frr/frr/MANIFEST.json create mode 100644 ansible_collections/frr/frr/README.md create mode 100644 ansible_collections/frr/frr/bindep.txt create mode 100644 ansible_collections/frr/frr/changelogs/changelog.yaml create mode 100644 ansible_collections/frr/frr/changelogs/config.yaml create mode 100644 ansible_collections/frr/frr/codecov.yml create mode 100644 ansible_collections/frr/frr/docs/frr.frr.frr_bgp_module.rst create mode 100644 ansible_collections/frr/frr/docs/frr.frr.frr_cliconf.rst create mode 100644 ansible_collections/frr/frr/docs/frr.frr.frr_facts_module.rst create mode 100644 ansible_collections/frr/frr/meta/runtime.yml create mode 100644 ansible_collections/frr/frr/plugins/cliconf/__init__.py create mode 100644 ansible_collections/frr/frr/plugins/cliconf/frr.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/__init__.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/__init__.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/frr.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/__init__.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/__init__.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/__init__.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/base.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/__init__.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/address_family.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/neighbors.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/process.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/module.py create mode 100644 ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/providers.py create mode 100644 ansible_collections/frr/frr/plugins/modules/__init__.py create mode 100644 ansible_collections/frr/frr/plugins/modules/frr_bgp.py create mode 100644 ansible_collections/frr/frr/plugins/modules/frr_facts.py create mode 100644 ansible_collections/frr/frr/plugins/terminal/__init__.py create mode 100644 ansible_collections/frr/frr/plugins/terminal/frr.py create mode 100644 ansible_collections/frr/frr/pyproject.toml create mode 100644 ansible_collections/frr/frr/requirements.txt create mode 100644 ansible_collections/frr/frr/test-requirements.txt create mode 100644 ansible_collections/frr/frr/tests/.gitignore create mode 100644 ansible_collections/frr/frr/tests/sanity/ignore-2.10.txt create mode 100644 ansible_collections/frr/frr/tests/sanity/ignore-2.12.txt create mode 100644 ansible_collections/frr/frr/tests/sanity/ignore-2.13.txt create mode 100644 ansible_collections/frr/frr/tests/sanity/ignore-2.14.txt create mode 100644 ansible_collections/frr/frr/tests/sanity/ignore-2.15.txt create mode 100644 ansible_collections/frr/frr/tests/sanity/ignore-2.16.txt create mode 100644 ansible_collections/frr/frr/tests/sanity/ignore-2.9.txt create mode 100644 ansible_collections/frr/frr/tests/unit/__init__.py create mode 100644 ansible_collections/frr/frr/tests/unit/compat/__init__.py create mode 100644 ansible_collections/frr/frr/tests/unit/compat/mock.py create mode 100644 ansible_collections/frr/frr/tests/unit/compat/unittest.py create mode 100644 ansible_collections/frr/frr/tests/unit/mock/__init__.py create mode 100644 ansible_collections/frr/frr/tests/unit/mock/loader.py create mode 100644 ansible_collections/frr/frr/tests/unit/mock/path.py create mode 100644 ansible_collections/frr/frr/tests/unit/mock/procenv.py create mode 100644 ansible_collections/frr/frr/tests/unit/mock/vault_helper.py create mode 100644 ansible_collections/frr/frr/tests/unit/mock/yaml_helper.py create mode 100644 ansible_collections/frr/frr/tests/unit/modules/__init__.py create mode 100644 ansible_collections/frr/frr/tests/unit/modules/conftest.py create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/__init__.py create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/frr/__init__.py create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/__init__.py create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_bgp_config create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_interface create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_memory create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_version create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/frr/frr_module.py create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/frr/test_frr_bgp.py create mode 100644 ansible_collections/frr/frr/tests/unit/modules/network/frr/test_frr_facts.py create mode 100644 ansible_collections/frr/frr/tests/unit/modules/utils.py create mode 100644 ansible_collections/frr/frr/tests/unit/requirements.txt create mode 100644 ansible_collections/frr/frr/tox.ini (limited to 'ansible_collections/frr') diff --git a/ansible_collections/frr/frr/.config/ansible-lint.yml b/ansible_collections/frr/frr/.config/ansible-lint.yml new file mode 100644 index 000000000..a401eae2a --- /dev/null +++ b/ansible_collections/frr/frr/.config/ansible-lint.yml @@ -0,0 +1,3 @@ +--- +skip_list: + - galaxy[version-incorrect] diff --git a/ansible_collections/frr/frr/.github/workflows/codecoverage.yml b/ansible_collections/frr/frr/.github/workflows/codecoverage.yml new file mode 100644 index 000000000..3ed9832a7 --- /dev/null +++ b/ansible_collections/frr/frr/.github/workflows/codecoverage.yml @@ -0,0 +1,15 @@ +--- +name: Code Coverage + +on: + push: + pull_request: + branches: [ main ] + +jobs: + codecoverage: + uses: ansible-network/github_actions/.github/workflows/coverage_network_devices.yml@main + with: + collection_pre_install: >- + git+https://github.com/ansible-collections/ansible.utils.git + git+https://github.com/ansible-collections/ansible.netcommon.git diff --git a/ansible_collections/frr/frr/.github/workflows/test.yml b/ansible_collections/frr/frr/.github/workflows/test.yml new file mode 100644 index 000000000..e77967b34 --- /dev/null +++ b/ansible_collections/frr/frr/.github/workflows/test.yml @@ -0,0 +1,45 @@ +--- +name: Test collection + +concurrency: + group: ${{ github.head_ref }} + cancel-in-progress: true + +on: # yamllint disable-line rule:truthy + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + ansible-lint: + uses: ansible-network/github_actions/.github/workflows/ansible-lint.yml@main + changelog: + uses: ansible-network/github_actions/.github/workflows/changelog.yml@main + sanity: + uses: ansible-network/github_actions/.github/workflows/sanity.yml@main + unit-galaxy: + uses: ansible-network/github_actions/.github/workflows/unit_galaxy.yml@main + unit-source: + uses: ansible-network/github_actions/.github/workflows/unit_source.yml@main + with: + collection_pre_install: >- + git+https://github.com/ansible-collections/ansible.utils.git + git+https://github.com/ansible-collections/ansible.netcommon.git + all_green: + if: ${{ always() }} + needs: + - ansible-lint + - changelog + - sanity + - unit-galaxy + - unit-source + runs-on: ubuntu-latest + steps: + - run: >- + python -c "assert set([ + '${{ needs.ansible-lint.result }}', + '${{ needs.changelog.result }}', + '${{ needs.sanity.result }}', + '${{ needs.unit-galaxy.result }}', + '${{ needs.unit-source.result }}' + ]) == {'success'}" diff --git a/ansible_collections/frr/frr/.gitignore b/ansible_collections/frr/frr/.gitignore new file mode 100644 index 000000000..1f9858c45 --- /dev/null +++ b/ansible_collections/frr/frr/.gitignore @@ -0,0 +1,118 @@ +# CML/virl lab cache +.virl/ + +# A collection directory, resulting from the use of the pytest-ansible-units plugin +collections/ + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# ide +*.code-workspace +.vscode/ +.DS_Store + +changelogs/.plugin-cache.yaml diff --git a/ansible_collections/frr/frr/.isort.cfg b/ansible_collections/frr/frr/.isort.cfg new file mode 100644 index 000000000..00f94dedf --- /dev/null +++ b/ansible_collections/frr/frr/.isort.cfg @@ -0,0 +1,6 @@ +[settings] +known_first_party=ansible_collections.frr.frr +line_length=100 +lines_after_imports=2 +lines_between_types=1 +profile=black diff --git a/ansible_collections/frr/frr/.pre-commit-config.yaml b/ansible_collections/frr/frr/.pre-commit-config.yaml new file mode 100644 index 000000000..f5732d582 --- /dev/null +++ b/ansible_collections/frr/frr/.pre-commit-config.yaml @@ -0,0 +1,59 @@ +--- +repos: + - repo: https://github.com/ansible-network/collection_prep + rev: 1.1.0 + hooks: + - id: autoversion + - id: update-docs + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-merge-conflict + - id: check-symlinks + - id: debug-statements + - id: end-of-file-fixer + - id: no-commit-to-branch + args: [--branch, main] + - id: trailing-whitespace + + - repo: https://github.com/asottile/add-trailing-comma + rev: v2.4.0 + hooks: + - id: add-trailing-comma + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.0.0-alpha.4" + hooks: + - id: prettier + # Original hook implementation is flaky due to *several* bugs described + # in https://github.com/prettier/prettier/issues/12364 + # a) CI=1 needed to avoid incomplete output + # b) two executions are needed because --list-different works correctly + # only when run with --check as with --write the output will also + # include other entries and logging level cannot be used to keep only + # modified files listed (any file is listed using the log level, regardless if + # is modified or not). + # c) We avoid letting pre-commit pass each filename in order to avoid + # running multiple instances in parallel. This also ensures that running + # prettier from the command line behaves identically with the pre-commit + # one. No real performance downsides. + # d) exit with the return code from list-different (0=none, 1=some) + # rather than the write (0=successfully rewrote files). pre-commit.ci + entry: env CI=1 bash -c "prettier --list-different . || ec=$? && prettier --loglevel=error --write . && exit $ec" + pass_filenames: false + args: [] + additional_dependencies: + - prettier + - prettier-plugin-toml + + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + name: Sort import statements using isort + + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black diff --git a/ansible_collections/frr/frr/.prettierignore b/ansible_collections/frr/frr/.prettierignore new file mode 100644 index 000000000..9f980a682 --- /dev/null +++ b/ansible_collections/frr/frr/.prettierignore @@ -0,0 +1,22 @@ +# Stuff we don't want priettier to ever to look into +.*/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# A linked collection directory created by pytest-ansible-units + +collections/ + +# Tracked but not manually edited + +# Tracked but manually formatted + +# WIP +README.md diff --git a/ansible_collections/frr/frr/.yamllint b/ansible_collections/frr/frr/.yamllint new file mode 100644 index 000000000..3adaf90cf --- /dev/null +++ b/ansible_collections/frr/frr/.yamllint @@ -0,0 +1,15 @@ +--- +extends: default + +ignore: | + .tox + changelogs/* + +rules: + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + line-length: disable diff --git a/ansible_collections/frr/frr/CHANGELOG.rst b/ansible_collections/frr/frr/CHANGELOG.rst new file mode 100644 index 000000000..4e139f562 --- /dev/null +++ b/ansible_collections/frr/frr/CHANGELOG.rst @@ -0,0 +1,73 @@ +============================ +Frr Collection Release Notes +============================ + +.. contents:: Topics + + +v2.0.2 +====== + +Release Summary +--------------- + +This release includes README update and assorted sanity fixes. + +v2.0.0 +====== + +Major Changes +------------- + +- Minimum required ansible.netcommon version is 2.5.1. +- Updated base plugin references to ansible.netcommon. + +v1.0.4 +====== + +Release Summary +--------------- + +This release includes sanity fixes that are needed for this collection to be included in Ansible 6. + +v1.0.3 +====== + +Minor Changes +------------- + +- Regenerated docs, add description to galaxy.yml and linked changelog to README (https://github.com/ansible-collections/frr.frr/pull/28) + +v1.0.2 +====== + +Release Summary +--------------- + +Rereleased 1.0.1 with updated changelog. + +v1.0.1 +====== + +Bugfixes +-------- + +- Makes sure that docstring and argspec are in sync and removes sanity ignores (https://github.com/ansible-collections/frr.frr/pull/23). +- Update docs after sanity fixes to modules. + +v1.0.0 +====== + +New Plugins +----------- + +Cliconf +~~~~~~~ + +- frr - Use frr cliconf to run command on Free Range Routing platform + +New Modules +----------- + +- frr_bgp - Configure global BGP settings on Free Range Routing(FRR). +- frr_facts - Collect facts from remote devices running Free Range Routing (FRR). diff --git a/ansible_collections/frr/frr/FILES.json b/ansible_collections/frr/frr/FILES.json new file mode 100644 index 000000000..44d267867 --- /dev/null +++ b/ansible_collections/frr/frr/FILES.json @@ -0,0 +1,719 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".config", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".config/ansible-lint.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fc6a1235051ecb9d79555eb292832443b84844c7b343a43cad74b0f84eeeda64", + "format": 1 + }, + { + "name": ".github", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/workflows", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/workflows/codecoverage.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "de4d28c3068fb7e2cfbd5e4e1b52f4a48462b63825a8552973e4b23fe3efcc19", + "format": 1 + }, + { + "name": ".github/workflows/test.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fb6838827012c333b7aa2b445987ce5aef5727b640e7647846d09e30d32b54b6", + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8c6d9cf9f1a29f303ffd8c6ce16d7298b350fb393a00fca71cf62f9587dc5e5a", + "format": 1 + }, + { + "name": "changelogs/config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9ac674e8b8cca45185e5cf13aac7a547ff81208b4c88665785ed4ed3b4e460b3", + "format": 1 + }, + { + "name": "docs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/frr.frr.frr_bgp_module.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "72eecf80acacd211ede58eb567d5a0bc07c3ba9fdc005e0e0f52ea0b20abed8e", + "format": 1 + }, + { + "name": "docs/frr.frr.frr_cliconf.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "95f1b9bd2fa4810c2bbbeaf0d229b0ad8423d77eeda474b5bf62c6ec51530a44", + "format": 1 + }, + { + "name": "docs/frr.frr.frr_facts_module.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0d6034284e61789b8e9e3f3c0add74706e481908f8a55bc5c92c31b8d623ca1c", + "format": 1 + }, + { + "name": "meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta/runtime.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e412b6b07058319e23e2c4433b08855974d34bf6d292a3431bbca9ddf5dbf2d9", + "format": 1 + }, + { + "name": "plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/cliconf", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/cliconf/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/cliconf/frr.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f1eb4a8671553f5c3ccf978da7e8b11b94f1d793a502d81611c17311d501c8f3", + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/network", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli/config", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli/config/bgp", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli/config/bgp/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli/config/bgp/address_family.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "39b81819f5715c325d2d5fddab5a1b7ee1a7dc4bba6db4a88a927b303031e364", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli/config/bgp/neighbors.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c2b1338e25846e113e664e14779c287f0ed18540ef5f27f680fdca46d06177c7", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli/config/bgp/process.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a5c68ea67d4160252eb0fa109e4f7b9771d758edb5d04a96faa2778b75dc872", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli/config/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli/config/base.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fc0c86ce72effd6cda40a056e5f53a845ee67d474741024fa1690c6b3d7a815c", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/cli/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/module.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "912051f37e04e5b532f747155a843ca9ed445b06d9d8cf6e88c839b528824ba3", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/providers/providers.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "489bf329c51ba985ca2c19e7bdd3ab98df1b83e10b3e7b1db6b14281929fb6cf", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/module_utils/network/frr/frr.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c4614ce0af5aa8853fe2162a5c4c753bc036bfa00f5452a155cf82648883d0d8", + "format": 1 + }, + { + "name": "plugins/module_utils/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/modules/frr_bgp.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "994d4e93ad49d33e7e705580c99f16d5a1e8ee54dcb002f1e99a4af653dd47d7", + "format": 1 + }, + { + "name": "plugins/modules/frr_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f86f1bd58c12804e799d75c781b0215ff341d57cd075d93c27dac4f3741f4147", + "format": 1 + }, + { + "name": "plugins/terminal", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/terminal/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/terminal/frr.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dc9297e01fa05269a745fcbe0cb638c2880afbf4ba3362facd5431a19705d0cc", + "format": 1 + }, + { + "name": "tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.10.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.12.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.13.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.14.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.15.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.16.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.9.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/compat", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/compat/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/compat/mock.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ef8138e4a1226b85dfb3567d9be04aaef39270af32ae62765b745f8f46cb0ec", + "format": 1 + }, + { + "name": "tests/unit/compat/unittest.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "727203a3846be41893b78a4b77852a1658925e936fb19539551958a5d8e8fb81", + "format": 1 + }, + { + "name": "tests/unit/mock", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/mock/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/mock/loader.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4a016afe1252f4ed74089de5a77c64305a1fb5873aeecdaf695a80203ce5fbcc", + "format": 1 + }, + { + "name": "tests/unit/mock/path.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b90326372b53c2f8abe5782254c7d89c224b3bdfeb74ed85ac0017eb1d23ef6", + "format": 1 + }, + { + "name": "tests/unit/mock/procenv.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8ccee2912c9aed481e2a635fb24d555fa1a9fce62c8e824441966ab32d2e9b4", + "format": 1 + }, + { + "name": "tests/unit/mock/vault_helper.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3bf7834d18bd34473d0f4b898253177229a131f14364874efba584ff985e4a41", + "format": 1 + }, + { + "name": "tests/unit/mock/yaml_helper.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "01876cfd13c28db69e1016fb68958259d2697a9e6291841591d46f922bd845d2", + "format": 1 + }, + { + "name": "tests/unit/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/modules/network", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/fixtures", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/fixtures/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/fixtures/frr_bgp_config", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9231e727966bef34467142639673c6e31bb8475f6c777fa7e41be4bb64ae7a3a", + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/fixtures/frr_facts_show_interface", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8a7a814518937a79974afcc08597cb8209adcf0710d218d0fdc2378a0461efce", + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/fixtures/frr_facts_show_memory", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f5548b9b2d91900f1cce2e6ef1eb35273a7aca32460e3a07a1cec16b4b1abdc7", + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/fixtures/frr_facts_show_version", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "de6cda2c8a1c9c1e9f83848ac32319d46d0e270f4aecc28128c7fa6d35bf87da", + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/frr_module.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c66bb4c189e29e04d266daf87628c1603abcb314f8be0827a5e88b2b6b7b9d77", + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/test_frr_bgp.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "562b6a9ce8f5721e7dcd9376cc393a37cedf48b4439f5adbd6cdb4b2bd313d88", + "format": 1 + }, + { + "name": "tests/unit/modules/network/frr/test_frr_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7ab0f5159fbad9ce8160b7afc429161cb758be456979f3a55380ef94409e03ae", + "format": 1 + }, + { + "name": "tests/unit/modules/network/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/modules/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/modules/conftest.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b7d11e3efeff615b87c4698374f6bdd3dff3c6e384e11ceaa0700282917a3626", + "format": 1 + }, + { + "name": "tests/unit/modules/utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d08985970afb53410d7dd30879952ec7421754c213a4a82d92d60c67d4b079e7", + "format": 1 + }, + { + "name": "tests/unit/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "49ba996dc4735c3463e9af561344346dfae14bcc1a68096ce78364b377f0df1f", + "format": 1 + }, + { + "name": "tests/.gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b5726d3ec9335a09c124469eca039523847a6b0f08a083efaefd002b83326600", + "format": 1 + }, + { + "name": ".gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "179a6d0780422c32653a71b9395a3ecb8b3859346ffa3e38ca3c8d90f8063fd2", + "format": 1 + }, + { + "name": ".isort.cfg", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b4dec24ef3ce22b8f47cf6cd0745bc2e4ead138d6fb0c6508753508b5257123b", + "format": 1 + }, + { + "name": ".pre-commit-config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "83458cb034fd1cd1959366f3f10cbf9d6f6106f29fdd06d5b63e3e08dbbf3505", + "format": 1 + }, + { + "name": ".prettierignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9881cacd6494858bc3c50f32917a7971c275f5dbeaa27d438985eacb344f9857", + "format": 1 + }, + { + "name": ".yamllint", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "827ef9e031ecdcaf137be239d33ef93fcbbc3611cbb6b30b0e507d0e03373d0e", + "format": 1 + }, + { + "name": "CHANGELOG.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f40de137ddfddd48462882636753d3d16de71e7dc983af21352fac45364dee98", + "format": 1 + }, + { + "name": "LICENSE", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "917d96a00c89c55cdc87ce73c65c4babae712072c791e49659b56b7b3e2ec896", + "format": 1 + }, + { + "name": "bindep.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "80645079eb025b3a905b4775ac545d080a3d7d35d537c31e04f7197c94315ab5", + "format": 1 + }, + { + "name": "codecov.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d7119b3a96ece7bcd2df6e851e7e19783049f280918a801344d502c6ada4e582", + "format": 1 + }, + { + "name": "pyproject.toml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1cb6a45dfa2625123890b93ad7fdc156b063c16e8ae6dba11511a1d1986b0fcc", + "format": 1 + }, + { + "name": "requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "test-requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60465b242d7c4c5f207d2424aa9dd788d03c7f767c2e09b1c55e3f97d8cfd383", + "format": 1 + }, + { + "name": "tox.ini", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "beb3313789623e5570d0871f6115ba563a0d92ea75e8e89cbd9f79045b4fe279", + "format": 1 + } + ], + "format": 1 +} \ No newline at end of file diff --git a/ansible_collections/frr/frr/LICENSE b/ansible_collections/frr/frr/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/ansible_collections/frr/frr/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ansible_collections/frr/frr/MANIFEST.json b/ansible_collections/frr/frr/MANIFEST.json new file mode 100644 index 000000000..e08d853a3 --- /dev/null +++ b/ansible_collections/frr/frr/MANIFEST.json @@ -0,0 +1,35 @@ +{ + "collection_info": { + "namespace": "frr", + "name": "frr", + "version": "2.0.2", + "authors": [ + "Ansible Network Community (ansible-network)" + ], + "readme": "README.md", + "tags": [ + "routing", + "networking", + "facts", + "frr" + ], + "description": "Ansible Collection for Free Range Routing (FRR).", + "license": [], + "license_file": "LICENSE", + "dependencies": { + "ansible.netcommon": ">=2.6.1" + }, + "repository": "https://github.com/ansible-collections/frr.frr", + "documentation": null, + "homepage": null, + "issues": "https://github.com/ansible-collections/frr.frr/issues" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "67e22b8133695c50927f16214889c46deda90f07a9adc03e3fccbaa953248635", + "format": 1 + }, + "format": 1 +} \ No newline at end of file diff --git a/ansible_collections/frr/frr/README.md b/ansible_collections/frr/frr/README.md new file mode 100644 index 000000000..ab18b5f7b --- /dev/null +++ b/ansible_collections/frr/frr/README.md @@ -0,0 +1,139 @@ + + +# Free Range Routing (FRR) Collection +[![CI](https://zuul-ci.org/gated.svg)](https://dashboard.zuul.ansible.com/t/ansible/project/github.com/ansible-collections/frr.frr) +[![Codecov](https://codecov.io/gh/ansible-collections/frr.frr/branch/main/graph/badge.svg)](https://codecov.io/gh/ansible-collections/frr.frr) + + +The Ansible FRR collection includes a variety of Ansible content to help automate the management of FRR network appliances. + +This collection has been tested against FRR 6.0. + + +## Ansible version compatibility + +This collection has been tested against following Ansible versions: **>=2.9.10**. + +For collections that support Ansible 2.9, please ensure you update your `network_os` to use the +fully qualified collection name (for example, `cisco.ios.ios`). +Plugins and modules within a collection may be tested with only specific Ansible versions. +A collection may contain metadata that identifies these versions. +PEP440 is the schema used to describe the versions of Ansible. + + +### Supported connections +The FRR collection supports ``network_cli`` connections. + +## Included content + +### Cliconf plugins +Name | Description +--- | --- +[frr.frr.frr](https://github.com/ansible-collections/frr.frr/blob/main/docs/frr.frr.frr_cliconf.rst)|Use frr cliconf to run command on Free Range Routing platform + +### Modules +Name | Description +--- | --- +[frr.frr.frr_bgp](https://github.com/ansible-collections/frr.frr/blob/main/docs/frr.frr.frr_bgp_module.rst)|Configure global BGP settings on Free Range Routing(FRR). +[frr.frr.frr_facts](https://github.com/ansible-collections/frr.frr/blob/main/docs/frr.frr.frr_facts_module.rst)|Collect facts from remote devices running Free Range Routing (FRR). + + + +Click the ``Content`` button to see the list of content included in this collection. + +## Installing this collection + +You can install the FRR collection with the Ansible Galaxy CLI: + + ansible-galaxy collection install frr.frr + +You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format: + +```yaml +--- +collections: + - name: frr.frr +``` +## Using this collection + +You can call modules by their Fully Qualified Collection Namespace (FQCN), such as `frr.frr.frr_bgp`. +The following example task replaces configuration changes in the existing configuration on a FRR network device, using the FQCN: + +```yaml +--- + - name: configure global bgp as 64496 + frr.frr.frr_bgp: + config: + bgp_as: 64496 + router_id: 192.0.2.1 + log_neighbor_changes: True + neighbors: + - neighbor: 192.51.100.1 + remote_as: 64497 + timers: + keepalive: 120 + holdtime: 360 + - neighbor: 198.51.100.2 + remote_as: 64498 + networks: + - prefix: 192.0.2.0 + masklen: 24 + route_map: RMAP_1 + - prefix: 198.51.100.0 + masklen: 24 + address_family: + - afi: ipv4 + safi: unicast + redistribute: + - protocol: ospf + id: 223 + metric: 10 + operation: merge +``` + +**NOTE**: For Ansible 2.9, you may not see deprecation warnings when you run your playbooks with this collection. Use this documentation to track when a module is deprecated. + + +### See Also: + +* [FRR Platform Options](https://docs.ansible.com/ansible/latest/network/user_guide/platform_frr.html) +* [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. + +## Contributing to this collection + +We welcome community contributions to this collection. If you find problems, please open an issue or create a PR against the [FRR collection repository](https://github.com/ansible-collections/frr.frr). See [Contributing to Ansible-maintained collections](https://docs.ansible.com/ansible/devel/community/contributing_maintained_collections.html#contributing-maintained-collections) for complete details. + +You can also join us on: + +- IRC - the ``#ansible-network`` [irc.libera.chat](https://libera.chat/) channel +- Slack - https://ansiblenetwork.slack.com + +See the [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html) for details on contributing to Ansible. + +### Code of Conduct +This collection follows the Ansible project's +[Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html). +Please read and familiarize yourself with this document. + + +## Changelogs + +Release notes are available [here](https://github.com/ansible-collections/frr.frr/blob/main/changelogs/CHANGELOG.rst). + +## Roadmap + + + +## More information + +- [Ansible network resources](https://docs.ansible.com/ansible/latest/network/getting_started/network_resources.html) +- [Ansible Collection overview](https://github.com/ansible-collections/overview) +- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html) +- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html) +- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) + +## Licensing + +GNU General Public License v3.0 or later. + +See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. diff --git a/ansible_collections/frr/frr/bindep.txt b/ansible_collections/frr/frr/bindep.txt new file mode 100644 index 000000000..ba9c980fb --- /dev/null +++ b/ansible_collections/frr/frr/bindep.txt @@ -0,0 +1,6 @@ +# This is a cross-platform list tracking distribution packages needed by tests; +# see https://docs.openstack.org/infra/bindep/ for additional information. + +gcc-c++ [doc test platform:rpm] +python3-devel [test platform:rpm] +python3 [test platform:rpm] diff --git a/ansible_collections/frr/frr/changelogs/changelog.yaml b/ansible_collections/frr/frr/changelogs/changelog.yaml new file mode 100644 index 000000000..4a32e05a3 --- /dev/null +++ b/ansible_collections/frr/frr/changelogs/changelog.yaml @@ -0,0 +1,62 @@ +ancestor: null +releases: + 1.0.0: + modules: + - description: Configure global BGP settings on Free Range Routing(FRR). + name: frr_bgp + namespace: "" + - description: Collect facts from remote devices running Free Range Routing (FRR). + name: frr_facts + namespace: "" + plugins: + cliconf: + - description: Use frr cliconf to run command on Free Range Routing platform + name: frr + namespace: null + release_date: "2020-06-23" + 1.0.1: + changes: + bugfixes: + - Makes sure that docstring and argspec are in sync and removes sanity ignores + (https://github.com/ansible-collections/frr.frr/pull/23). + - Update docs after sanity fixes to modules. + fragments: + - remove_ignores.yaml + - update_docs.yaml + release_date: "2020-08-03" + 1.0.2: + changes: + release_summary: Rereleased 1.0.1 with updated changelog. + fragments: + - 1.0.2.yaml + release_date: "2020-08-07" + 1.0.3: + changes: + minor_changes: + - Regenerated docs, add description to galaxy.yml and linked changelog to README + (https://github.com/ansible-collections/frr.frr/pull/28) + fragments: + - fixes_to_readme_and_doc.yaml + release_date: "2020-08-28" + 1.0.4: + changes: + release_summary: + This release includes sanity fixes that are needed for this + collection to be included in Ansible 6. + fragments: + - sanity_fixes.yaml + release_date: "2022-05-04" + 2.0.0: + changes: + major_changes: + - Minimum required ansible.netcommon version is 2.5.1. + - Updated base plugin references to ansible.netcommon. + fragments: + - netcommon_ref_update.yaml + release_date: "2022-05-05" + 2.0.2: + changes: + release_summary: This release includes README update and assorted sanity fixes. + fragments: + - gha_sanity_fix.yml + release_date: "2023-04-14" diff --git a/ansible_collections/frr/frr/changelogs/config.yaml b/ansible_collections/frr/frr/changelogs/config.yaml new file mode 100644 index 000000000..dab258614 --- /dev/null +++ b/ansible_collections/frr/frr/changelogs/config.yaml @@ -0,0 +1,30 @@ +changelog_filename_template: ../CHANGELOG.rst +changelog_filename_version_depth: 0 +changes_file: changelog.yaml +changes_format: combined +keep_fragments: false +mention_ancestor: true +new_plugins_after_name: removed_features +notesdir: fragments +prelude_section_name: release_summary +prelude_section_title: Release Summary +flatmap: true +sections: + - - major_changes + - Major Changes + - - minor_changes + - Minor Changes + - - breaking_changes + - Breaking Changes / Porting Guide + - - deprecated_features + - Deprecated Features + - - removed_features + - Removed Features (previously deprecated) + - - security_fixes + - Security Fixes + - - bugfixes + - Bugfixes + - - known_issues + - Known Issues +title: Frr Collection +trivial_section_name: trivial diff --git a/ansible_collections/frr/frr/codecov.yml b/ansible_collections/frr/frr/codecov.yml new file mode 100644 index 000000000..816cb4409 --- /dev/null +++ b/ansible_collections/frr/frr/codecov.yml @@ -0,0 +1,10 @@ +--- +codecov: + require_ci_to_pass: true +comment: false +coverage: + status: + patch: false + project: + default: + threshold: 0.3% diff --git a/ansible_collections/frr/frr/docs/frr.frr.frr_bgp_module.rst b/ansible_collections/frr/frr/docs/frr.frr.frr_bgp_module.rst new file mode 100644 index 000000000..8e94bd7a5 --- /dev/null +++ b/ansible_collections/frr/frr/docs/frr.frr.frr_bgp_module.rst @@ -0,0 +1,1029 @@ +.. _frr.frr.frr_bgp_module: + + +*************** +frr.frr.frr_bgp +*************** + +**Configure global BGP settings on Free Range Routing(FRR).** + + +Version added: 1.0.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This module provides configuration management of global BGP parameters on devices running Free Range Routing(FRR). + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ config + +
+ dictionary +
+
+ +
Specifies the BGP related configuration.
+
+
+ address_family + +
+ list + / elements=dictionary +
+
+ +
Specifies BGP address family related configurations.
+
+
+ afi + +
+ string + / required +
+
+
    Choices: +
  • ipv4
  • +
  • ipv6
  • +
+
+
Type of address family to configure.
+
+
+ neighbors + +
+ list + / elements=dictionary +
+
+ +
Specifies BGP neighbor related configurations in Address Family configuration mode.
+
+
+ activate + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enable the address family for this neighbor.
+
+
+ maximum_prefix + +
+ integer +
+
+ +
Maximum number of prefixes to accept from this peer.
+
The range is from 1 to 4294967295.
+
+
+ neighbor + +
+ string + / required +
+
+ +
Neighbor router address.
+
+
+ next_hop_self + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enable/disable the next hop calculation for this neighbor.
+
+
+ remove_private_as + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Remove the private AS number from outbound updates.
+
+
+ route_reflector_client + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Specify a neighbor as a route reflector client.
+
+
+ route_server_client + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Specify a neighbor as a route server client.
+
+
+ networks + +
+ list + / elements=dictionary +
+
+ +
Specify networks to announce via BGP.
+
For operation replace, this option is mutually exclusive with root level networks option.
+
+
+ masklen + +
+ integer + / required +
+
+ +
Subnet mask length for the network to announce(e.g, 8, 16, 24, etc.).
+
+
+ prefix + +
+ string + / required +
+
+ +
Network ID to announce via BGP.
+
+
+ route_map + +
+ string +
+
+ +
Route map to modify the attributes.
+
+
+ redistribute + +
+ list + / elements=dictionary +
+
+ +
Specifies the redistribute information from another routing protocol.
+
+
+ id + +
+ string +
+
+ +
Specifies the instance ID/table ID for this protocol
+
Valid for ospf and table
+
+
+ metric + +
+ integer +
+
+ +
Specifies the metric for redistributed routes.
+
+
+ protocol + +
+ string + / required +
+
+
    Choices: +
  • ospf
  • +
  • ospf6
  • +
  • eigrp
  • +
  • isis
  • +
  • table
  • +
  • static
  • +
  • connected
  • +
  • sharp
  • +
  • nhrp
  • +
  • kernel
  • +
  • babel
  • +
  • rip
  • +
+
+
Specifies the protocol for configuring redistribute information.
+
+
+ route_map + +
+ string +
+
+ +
Specifies the route map reference.
+
+
+ safi + +
+ string +
+
+
    Choices: +
  • flowspec
  • +
  • unicast ←
  • +
  • multicast
  • +
  • labeled-unicast
  • +
+
+
Specifies the type of cast for the address family.
+
+
+ bgp_as + +
+ integer + / required +
+
+ +
Specifies the BGP Autonomous System (AS) number to configure on the device.
+
+
+ log_neighbor_changes + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enable/disable logging neighbor up/down and reset reason.
+
+
+ neighbors + +
+ list + / elements=dictionary +
+
+ +
Specifies BGP neighbor related configurations.
+
+
+ advertisement_interval + +
+ integer +
+
+ +
Minimum interval between sending BGP routing updates for this neighbor.
+
+
+ description + +
+ string +
+
+ +
Neighbor specific description.
+
+
+ ebgp_multihop + +
+ integer +
+
+ +
Specifies the maximum hop count for EBGP neighbors not on directly connected networks.
+
The range is from 1 to 255.
+
+
+ enabled + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Administratively shutdown or enable a neighbor.
+
+
+ local_as + +
+ integer +
+
+ +
The local AS number for the neighbor.
+
+
+ neighbor + +
+ string + / required +
+
+ +
Neighbor router address.
+
+
+ password + +
+ string +
+
+ +
Password to authenticate the BGP peer connection.
+
+
+ peer_group + +
+ string +
+
+ +
Name of the peer group that the neighbor is a member of.
+
+
+ port + +
+ integer +
+
+ +
The TCP Port number to use for this neighbor.
+
The range is from 0 to 65535.
+
+
+ remote_as + +
+ integer + / required +
+
+ +
Remote AS of the BGP neighbor to configure.
+
+
+ timers + +
+ dictionary +
+
+ +
Specifies BGP neighbor timer related configurations.
+
+
+ holdtime + +
+ integer + / required +
+
+ +
Interval (in seconds) after not receiving a keepalive message that FRR declares a peer dead.
+
The range is from 0 to 65535.
+
+
+ keepalive + +
+ integer + / required +
+
+ +
Frequency (in seconds) with which the FRR sends keepalive messages to its peer.
+
The range is from 0 to 65535.
+
+
+ update_source + +
+ string +
+
+ +
Source of the routing updates.
+
+
+ networks + +
+ list + / elements=dictionary +
+
+ +
Specify networks to announce via BGP.
+
For operation replace, this option is mutually exclusive with networks option under address_family.
+
For operation replace, if the device already has an address family activated, this option is not allowed.
+
+
+ masklen + +
+ integer + / required +
+
+ +
Subnet mask length for the network to announce(e.g, 8, 16, 24, etc.).
+
+
+ prefix + +
+ string + / required +
+
+ +
Network ID to announce via BGP.
+
+
+ route_map + +
+ string +
+
+ +
Route map to modify the attributes.
+
+
+ router_id + +
+ string +
+
+ +
Configures the BGP routing process router-id value.
+
+
+ operation + +
+ string +
+
+
    Choices: +
  • merge ←
  • +
  • replace
  • +
  • override
  • +
  • delete
  • +
+
+
Specifies the operation to be performed on the BGP process configured on the device.
+
In case of merge, the input configuration will be merged with the existing BGP configuration on the device.
+
In case of replace, if there is a diff between the existing configuration and the input configuration, the existing configuration will be replaced by the input configuration for every option that has the diff.
+
In case of override, all the existing BGP configuration will be removed from the device and replaced with the input configuration.
+
In case of delete the existing BGP configuration will be removed from the device.
+
+
+ + +Notes +----- + +.. note:: + - Tested against FRRouting 6.0. + + + +Examples +-------- + +.. code-block:: yaml + + - name: configure global bgp as 64496 + frr.frr.frr_bgp: + config: + bgp_as: 64496 + router_id: 192.0.2.1 + log_neighbor_changes: true + neighbors: + - neighbor: 192.51.100.1 + remote_as: 64497 + timers: + keepalive: 120 + holdtime: 360 + - neighbor: 198.51.100.2 + remote_as: 64498 + networks: + - prefix: 192.0.2.0 + masklen: 24 + route_map: RMAP_1 + - prefix: 198.51.100.0 + masklen: 24 + address_family: + - afi: ipv4 + safi: unicast + redistribute: + - protocol: ospf + id: 223 + metric: 10 + operation: merge + + - name: Configure BGP neighbors + frr.frr.frr_bgp: + config: + bgp_as: 64496 + neighbors: + - neighbor: 192.0.2.10 + remote_as: 64496 + password: ansible + description: IBGP_NBR_1 + timers: + keepalive: 120 + holdtime: 360 + - neighbor: 192.0.2.15 + remote_as: 64496 + description: IBGP_NBR_2 + advertisement_interval: 120 + operation: merge + + - name: Configure BGP neighbors under address family mode + frr.frr.frr_bgp: + config: + bgp_as: 64496 + address_family: + - afi: ipv4 + safi: multicast + neighbors: + - neighbor: 203.0.113.10 + activate: true + maximum_prefix: 250 + + - neighbor: 192.0.2.15 + activate: true + route_reflector_client: true + operation: merge + + - name: Configure root-level networks for BGP + frr.frr.frr_bgp: + config: + bgp_as: 64496 + networks: + - prefix: 203.0.113.0 + masklen: 27 + route_map: RMAP_1 + - prefix: 203.0.113.32 + masklen: 27 + route_map: RMAP_2 + operation: merge + + - name: remove bgp as 64496 from config + frr.frr.frr_bgp: + config: + bgp_as: 64496 + operation: delete + + + +Return Values +------------- +Common return values are documented `here `_, the following are the fields unique to this module: + +.. raw:: html + + + + + + + + + + + + +
KeyReturnedDescription
+
+ commands + +
+ list +
+
always +
The list of configuration mode commands to send to the device
+
+
Sample:
+
['router bgp 64496', 'bgp router-id 192.0.2.1', 'neighbor 192.51.100.1 remote-as 64497', 'neighbor 192.51.100.1 timers 120 360', 'neighbor 198.51.100.2 remote-as 64498', 'address-family ipv4 unicast', 'redistribute ospf 223 metric 10', 'exit-address-family', 'bgp log-neighbor-changes', 'network 192.0.2.0/24 route-map RMAP_1', 'network 198.51.100.0/24', 'exit']
+
+

+ + +Status +------ + + +Authors +~~~~~~~ + +- Nilashish Chakraborty (@NilashishC) diff --git a/ansible_collections/frr/frr/docs/frr.frr.frr_cliconf.rst b/ansible_collections/frr/frr/docs/frr.frr.frr_cliconf.rst new file mode 100644 index 000000000..66ec2838d --- /dev/null +++ b/ansible_collections/frr/frr/docs/frr.frr.frr_cliconf.rst @@ -0,0 +1,43 @@ +.. _frr.frr.frr_cliconf: + + +*********** +frr.frr.frr +*********** + +**Use frr cliconf to run command on Free Range Routing platform** + + +Version added: 1.0.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This frr plugin provides low level abstraction apis for sending and receiving CLI commands from FRR network devices. + + + + + + + + + + + +Status +------ + + +Authors +~~~~~~~ + +- Ansible Networking Team (@ansible-network) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. diff --git a/ansible_collections/frr/frr/docs/frr.frr.frr_facts_module.rst b/ansible_collections/frr/frr/docs/frr.frr.frr_facts_module.rst new file mode 100644 index 000000000..30ff73b36 --- /dev/null +++ b/ansible_collections/frr/frr/docs/frr.frr.frr_facts_module.rst @@ -0,0 +1,283 @@ +.. _frr.frr.frr_facts_module: + + +***************** +frr.frr.frr_facts +***************** + +**Collect facts from remote devices running Free Range Routing (FRR).** + + +Version added: 1.0.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Collects a base set of device facts from a remote device that is running FRR. This module prepends all of the base network fact keys with ``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. + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ gather_subset + +
+ list + / elements=string +
+
+ Default:
"!config"
+
+
When supplied, this argument restricts the facts collected to a given subset.
+
Possible values for this argument include all, hardware, config, and interfaces.
+
Specify a list of values to include a larger subset.
+
Use a value with an initial ! to collect all facts except that subset.
+
+
+ + +Notes +----- + +.. note:: + - Tested against FRR 6.0. + + + +Examples +-------- + +.. code-block:: yaml + + - name: Collect all facts from the device + frr.frr.frr_facts: + gather_subset: all + + - name: Collect only the config and default facts + frr.frr.frr_facts: + gather_subset: + - config + + - name: Collect the config and hardware facts + frr.frr.frr_facts: + gather_subset: + - config + - hardware + + - name: Do not collect hardware facts + frr.frr.frr_facts: + gather_subset: + - '!hardware' + + + +Return Values +------------- +Common return values are documented `here `_, the following are the fields unique to this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyReturnedDescription
+
+ ansible_net_all_ipv4_addresses + +
+ list +
+
when interfaces is configured +
All IPv4 addresses configured on the device
+
+
+
+ ansible_net_all_ipv6_addresses + +
+ list +
+
when interfaces is configured +
All IPv6 addresses configured on the device
+
+
+
+ ansible_net_api + +
+ string +
+
always +
The name of the transport
+
+
+
+ ansible_net_config + +
+ string +
+
when config is configured +
The current active config from the device
+
+
+
+ ansible_net_gather_subset + +
+ list +
+
always +
The list of fact subsets collected from the device
+
+
+
+ ansible_net_hostname + +
+ string +
+
always +
The configured hostname of the device
+
+
+
+ ansible_net_interfaces + +
+ dictionary +
+
when interfaces is configured +
A hash of all interfaces running on the system
+
+
+
+ ansible_net_mem_stats + +
+ dictionary +
+
when hardware is configured +
The memory statistics fetched from the device
+
+
+
+ ansible_net_mpls_ldp_neighbors + +
+ dictionary +
+
when interfaces is configured and LDP daemon is running on the device +
The list of MPLS LDP neighbors from the remote device
+
+
+
+ ansible_net_python_version + +
+ string +
+
always +
The Python version that the Ansible controller is using
+
+
+
+ ansible_net_version + +
+ string +
+
always +
The FRR version running on the remote device
+
+
+

+ + +Status +------ + + +Authors +~~~~~~~ + +- Nilashish Chakraborty (@NilashishC) diff --git a/ansible_collections/frr/frr/meta/runtime.yml b/ansible_collections/frr/frr/meta/runtime.yml new file mode 100644 index 000000000..a0e0639ba --- /dev/null +++ b/ansible_collections/frr/frr/meta/runtime.yml @@ -0,0 +1,8 @@ +--- +requires_ansible: ">=2.9.10" +plugin_routing: + modules: + bgp: + redirect: frr.frr.frr_bgp + facts: + redirect: frr.frr.frr_facts diff --git a/ansible_collections/frr/frr/plugins/cliconf/__init__.py b/ansible_collections/frr/frr/plugins/cliconf/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/plugins/cliconf/frr.py b/ansible_collections/frr/frr/plugins/cliconf/frr.py new file mode 100644 index 000000000..0b4d2b8d3 --- /dev/null +++ b/ansible_collections/frr/frr/plugins/cliconf/frr.py @@ -0,0 +1,260 @@ +# +# (c) 2018 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +--- +author: Ansible Networking Team (@ansible-network) +name: frr +short_description: Use frr cliconf to run command on Free Range Routing platform +description: + - This frr plugin provides low level abstraction apis for + sending and receiving CLI commands from FRR network devices. +version_added: "1.0.0" +""" + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible.module_utils.common._collections_compat import Mapping +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, + dumps, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible_collections.ansible.netcommon.plugins.plugin_utils.cliconf_base import ( + CliconfBase, + enable_mode, +) + + +class Cliconf(CliconfBase): + def get_supported_protocols(self): + supported_protocols = {} + protocols = [ + "bgp", + "isis", + "ospf", + "ldp", + "ospf6", + "pim", + "rip", + "ripm", + "zebra", + ] + daemons = self.get("show daemons") + data = to_text(daemons, errors="surrogate_or_strict").strip() + + for item in protocols: + supported_protocols[item] = True if item in data else False + return supported_protocols + + def get_device_info(self): + device_info = {} + + device_info["network_os"] = "frr" + reply = self.get("show version") + data = to_text(reply, errors="surrogate_or_strict").strip() + + match = re.search(r"FRRouting (\S+) \((\S+)\)", data) + if match: + device_info["network_os_version"] = match.group(1) + if match.group(2): + device_info["network_os_hostname"] = match.group(2) + + return device_info + + def get_option_values(self): + return { + "format": ["text"], + "diff_match": ["line", "strict", "exact", "none"], + "diff_replace": ["line", "block"], + "output": [], + } + + def get_device_operations(self): + return { + "supports_diff_replace": False, + "supports_commit": False, + "supports_rollback": False, + "supports_defaults": False, + "supports_onbox_diff": False, + "supports_commit_comment": False, + "supports_multiline_delimiter": False, + "supports_diff_match": False, + "supports_diff_ignore_lines": False, + "supports_generate_diff": True, + "supports_replace": False, + } + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + result["rpc"] += ["get_diff", "run_commands"] + result["device_operations"] = self.get_device_operations() + result["supported_protocols"] = self.get_supported_protocols() + result.update(self.get_option_values()) + return json.dumps(result) + + def get_diff( + self, + candidate=None, + running=None, + diff_match="line", + diff_ignore_lines=None, + path=None, + diff_replace="line", + ): + diff = {} + device_operations = self.get_device_operations() + option_values = self.get_option_values() + + if candidate is None and device_operations["supports_generate_diff"]: + raise ValueError("candidate configuration is required to generate diff") + + if diff_match not in option_values["diff_match"]: + raise ValueError( + "'match' value %s in invalid, valid values are %s" + % (diff_match, ", ".join(option_values["diff_match"])), + ) + + if diff_replace not in option_values["diff_replace"]: + raise ValueError( + "'replace' value %s in invalid, valid values are %s" + % (diff_replace, ", ".join(option_values["diff_replace"])), + ) + + # prepare candidate configuration + candidate_obj = NetworkConfig(indent=1) + candidate_obj.load(candidate) + + if running and diff_match != "none": + # running configuration + running_obj = NetworkConfig(indent=1, contents=running, ignore_lines=diff_ignore_lines) + configdiffobjs = candidate_obj.difference( + running_obj, + path=path, + match=diff_match, + replace=diff_replace, + ) + + else: + configdiffobjs = candidate_obj.items + + diff["config_diff"] = dumps(configdiffobjs, "commands") if configdiffobjs else "" + return diff + + @enable_mode + def get_config(self, source="running", flags=None, format=None): + if source not in ("running", "startup"): + raise ValueError("fetching configuration from %s is not supported" % source) + + if format: + raise ValueError("'format' value %s is not supported for get_config" % format) + + if not flags: + flags = [] + if source == "running": + cmd = "show running-config " + else: + cmd = "show startup-config " + + cmd += " ".join(to_list(flags)) + cmd = cmd.strip() + + return self.send_command(cmd) + + @enable_mode + def edit_config(self, candidate=None, commit=True, replace=None, comment=None): + resp = {} + operations = self.get_device_operations() + self.check_edit_config_capability(operations, candidate, commit, replace, comment) + + results = [] + requests = [] + if commit: + self.send_command("configure terminal") + for line in to_list(candidate): + if not isinstance(line, Mapping): + line = {"command": line} + + cmd = line["command"] + if cmd != "end" and cmd[0] != "!": + results.append(self.send_command(**line)) + requests.append(cmd) + + self.send_command("end") + else: + raise ValueError("check mode is not supported") + + resp["request"] = requests + resp["response"] = results + return resp + + def get( + self, + command=None, + prompt=None, + answer=None, + sendonly=False, + newline=True, + output=None, + check_all=False, + ): + if not command: + raise ValueError("must provide value of command to execute") + if output: + raise ValueError("'output' value %s is not supported for get" % output) + + return self.send_command( + command=command, + prompt=prompt, + answer=answer, + sendonly=sendonly, + newline=newline, + check_all=check_all, + ) + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {"command": cmd} + + output = cmd.pop("output", None) + if output: + raise ValueError("'output' value %s is not supported for run_commands" % output) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, "err", to_text(e)) + + responses.append(out) + + return responses diff --git a/ansible_collections/frr/frr/plugins/module_utils/__init__.py b/ansible_collections/frr/frr/plugins/module_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/__init__.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/frr.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/frr.py new file mode 100644 index 000000000..631d2d5a0 --- /dev/null +++ b/ansible_collections/frr/frr/plugins/module_utils/network/frr/frr.py @@ -0,0 +1,45 @@ +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json + +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import Connection, ConnectionError + + +def get_capabilities(module): + if hasattr(module, "_frr_capabilities"): + return module._frr_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + module._frr_capabilities = json.loads(capabilities) + return module._frr_capabilities + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + return connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_connection(module): + if hasattr(module, "_frr_connection"): + return module._frr_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get("network_api") + if network_api == "cliconf": + module._frr_connection = Connection(module._socket_path) + else: + module.fail_json(msg="Invalid connection type %s" % network_api) + + return module._frr_connection diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/__init__.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/__init__.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/__init__.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/base.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/base.py new file mode 100644 index 000000000..f6160e26a --- /dev/null +++ b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/base.py @@ -0,0 +1,84 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + + +class ConfigBase(object): + argument_spec = {} + + mutually_exclusive = [] + + identifier = () + + def __init__(self, **kwargs): + self.values = {} + self._rendered_configuration = {} + self.active_configuration = None + + for item in self.identifier: + self.values[item] = kwargs.pop(item) + + for key, value in iteritems(kwargs): + if key in self.argument_spec: + setattr(self, key, value) + + for key, value in iteritems(self.argument_spec): + if value.get("default"): + if not getattr(self, key, None): + setattr(self, key, value.get("default")) + + def __getattr__(self, key): + if key in self.argument_spec: + return self.values.get(key) + + def __setattr__(self, key, value): + if key in self.argument_spec: + if key in self.identifier: + raise TypeError("cannot set value") + elif value is not None: + self.values[key] = value + else: + super(ConfigBase, self).__setattr__(key, value) + + def context_config(self, cmd): + if "context" not in self._rendered_configuration: + self._rendered_configuration["context"] = list() + self._rendered_configuration["context"].extend(to_list(cmd)) + + def global_config(self, cmd): + if "global" not in self._rendered_configuration: + self._rendered_configuration["global"] = list() + self._rendered_configuration["global"].extend(to_list(cmd)) + + def get_rendered_configuration(self): + config = list() + for section in ("context", "global"): + config.extend(self._rendered_configuration.get(section, [])) + return config + + def set_active_configuration(self, config): + self.active_configuration = config + + def render(self, config=None): + raise NotImplementedError + + def get_section(self, config, section): + if config is not None: + netcfg = NetworkConfig(indent=1, contents=config) + try: + config = netcfg.get_block_config(to_list(section)) + except ValueError: + config = None + return config diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/__init__.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/address_family.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/address_family.py new file mode 100644 index 000000000..e0b4fe578 --- /dev/null +++ b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/address_family.py @@ -0,0 +1,146 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +import re + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers.cli.config.bgp.neighbors import ( + AFNeighbors, +) +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers.providers import ( + CliProvider, +) + + +class AddressFamily(CliProvider): + def render(self, config=None): + commands = list() + safe_list = list() + + router_context = "router bgp %s" % self.get_value("config.bgp_as") + context_config = None + + for item in self.get_value("config.address_family"): + context = "address-family %s %s" % (item["afi"], item["safi"]) + context_commands = list() + + if config: + context_path = [router_context, context] + context_config = self.get_config_context(config, context_path, indent=1) + + for key, value in iteritems(item): + if value is not None: + meth = getattr(self, "_render_%s" % key, None) + if meth: + resp = meth(item, context_config) + if resp: + context_commands.extend(to_list(resp)) + + if context_commands: + commands.append(context) + commands.extend(context_commands) + commands.append("exit-address-family") + + safe_list.append(context) + + if self.params["operation"] == "replace": + if config: + resp = self._negate_config(config, safe_list) + commands.extend(resp) + + return commands + + def _negate_config(self, config, safe_list=None): + commands = list() + matches = re.findall(r"(address-family .+)$", config, re.M) + for item in set(matches).difference(safe_list): + commands.append("no %s" % item) + return commands + + def _render_auto_summary(self, item, config=None): + cmd = "auto-summary" + if item["auto_summary"] is False: + cmd = "no %s" % cmd + if not config or cmd not in config: + return cmd + + def _render_synchronization(self, item, config=None): + cmd = "synchronization" + if item["synchronization"] is False: + cmd = "no %s" % cmd + if not config or cmd not in config: + return cmd + + def _render_networks(self, item, config=None): + commands = list() + safe_list = list() + + for entry in item["networks"]: + network = entry["prefix"] + if entry["masklen"]: + network = "%s/%s" % (entry["prefix"], entry["masklen"]) + safe_list.append(network) + + cmd = "network %s" % network + + if entry["route_map"]: + cmd += " route-map %s" % entry["route_map"] + + if not config or cmd not in config: + commands.append(cmd) + + if self.params["operation"] == "replace": + if config: + matches = re.findall(r"network (\S+)", config, re.M) + for entry in set(matches).difference(safe_list): + commands.append("no network %s" % entry) + + return commands + + def _render_redistribute(self, item, config=None): + commands = list() + safe_list = list() + + for entry in item["redistribute"]: + option = entry["protocol"] + + cmd = "redistribute %s" % entry["protocol"] + + if entry["id"] and entry["protocol"] in ("ospf", "table"): + cmd += " %s" % entry["id"] + option += " %s" % entry["id"] + + if entry["metric"]: + cmd += " metric %s" % entry["metric"] + + if entry["route_map"]: + cmd += " route-map %s" % entry["route_map"] + + if not config or cmd not in config: + commands.append(cmd) + + safe_list.append(option) + + if self.params["operation"] == "replace": + if config: + matches = re.findall(r"redistribute (\S+)(?:\s*)(\d*)", config, re.M) + for i in range(0, len(matches)): + matches[i] = " ".join(matches[i]).strip() + for entry in set(matches).difference(safe_list): + commands.append("no redistribute %s" % entry) + + return commands + + def _render_neighbors(self, item, config): + """generate bgp neighbor configuration""" + return AFNeighbors(self.params).render(config, nbr_list=item["neighbors"]) diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/neighbors.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/neighbors.py new file mode 100644 index 000000000..0b6292e0b --- /dev/null +++ b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/neighbors.py @@ -0,0 +1,208 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +import re + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers.providers import ( + CliProvider, +) + + +class Neighbors(CliProvider): + def render(self, config=None, nbr_list=None): + commands = list() + safe_list = list() + if not nbr_list: + nbr_list = self.get_value("config.neighbors") + + for item in nbr_list: + neighbor_commands = list() + context = "neighbor %s" % item["neighbor"] + cmd = "%s remote-as %s" % (context, item["remote_as"]) + + if not config or cmd not in config: + neighbor_commands.append(cmd) + + for key, value in iteritems(item): + if value is not None: + meth = getattr(self, "_render_%s" % key, None) + if meth: + resp = meth(item, config) + if resp: + neighbor_commands.extend(to_list(resp)) + + commands.extend(neighbor_commands) + safe_list.append(context) + + if self.params["operation"] == "replace": + if config and safe_list: + commands.extend(self._negate_config(config, safe_list)) + + return commands + + def _negate_config(self, config, safe_list=None): + commands = list() + matches = re.findall(r"(neighbor \S+)", config, re.M) + for item in set(matches).difference(safe_list): + commands.append("no %s" % item) + return commands + + def _render_advertisement_interval(self, item, config=None): + cmd = "neighbor %s advertisement-interval %s" % ( + item["neighbor"], + item["advertisement_interval"], + ) + if not config or cmd not in config: + return cmd + + def _render_local_as(self, item, config=None): + cmd = "neighbor %s local-as %s" % (item["neighbor"], item["local_as"]) + if not config or cmd not in config: + return cmd + + def _render_port(self, item, config=None): + cmd = "neighbor %s port %s" % (item["neighbor"], item["port"]) + if not config or cmd not in config: + return cmd + + def _render_description(self, item, config=None): + cmd = "neighbor %s description %s" % ( + item["neighbor"], + item["description"], + ) + if not config or cmd not in config: + return cmd + + def _render_enabled(self, item, config=None): + cmd = "neighbor %s shutdown" % item["neighbor"] + if item["enabled"] is True: + cmd = "no %s" % cmd + if not config or cmd not in config: + return cmd + + def _render_update_source(self, item, config=None): + cmd = "neighbor %s update-source %s" % ( + item["neighbor"], + item["update_source"], + ) + if not config or cmd not in config: + return cmd + + def _render_password(self, item, config=None): + cmd = "neighbor %s password %s" % (item["neighbor"], item["password"]) + if not config or cmd not in config: + return cmd + + def _render_ebgp_multihop(self, item, config=None): + cmd = "neighbor %s ebgp-multihop %s" % ( + item["neighbor"], + item["ebgp_multihop"], + ) + if not config or cmd not in config: + return cmd + + def _render_peer_group(self, item, config=None): + cmd = "neighbor %s peer-group %s" % ( + item["neighbor"], + item["peer_group"], + ) + if not config or cmd not in config: + return cmd + + def _render_timers(self, item, config): + """generate bgp timer related configuration""" + keepalive = item["timers"]["keepalive"] + holdtime = item["timers"]["holdtime"] + neighbor = item["neighbor"] + + if keepalive and holdtime: + cmd = "neighbor %s timers %s %s" % (neighbor, keepalive, holdtime) + if not config or cmd not in config: + return cmd + else: + raise ValueError("required both options for timers: keepalive and holdtime") + + +class AFNeighbors(CliProvider): + def render(self, config=None, nbr_list=None): + commands = list() + if not nbr_list: + return + + for item in nbr_list: + neighbor_commands = list() + for key, value in iteritems(item): + if value is not None: + meth = getattr(self, "_render_%s" % key, None) + if meth: + resp = meth(item, config) + if resp: + neighbor_commands.extend(to_list(resp)) + + commands.extend(neighbor_commands) + + return commands + + def _render_route_reflector_client(self, item, config=None): + cmd = "neighbor %s route-reflector-client" % item["neighbor"] + if item["route_reflector_client"] is False: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_route_server_client(self, item, config=None): + cmd = "neighbor %s route-server-client" % item["neighbor"] + if item["route_server_client"] is False: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_remove_private_as(self, item, config=None): + cmd = "neighbor %s remove-private-AS" % item["neighbor"] + if item["remove_private_as"] is False: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_next_hop_self(self, item, config=None): + cmd = "neighbor %s activate" % item["neighbor"] + if item["next_hop_self"] is False: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_activate(self, item, config=None): + cmd = "neighbor %s activate" % item["neighbor"] + if item["activate"] is False: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_maximum_prefix(self, item, config=None): + cmd = "neighbor %s maximum-prefix %s" % ( + item["neighbor"], + item["maximum_prefix"], + ) + if not config or cmd not in config: + return cmd diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/process.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/process.py new file mode 100644 index 000000000..f5987902a --- /dev/null +++ b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/cli/config/bgp/process.py @@ -0,0 +1,165 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +import re + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers.cli.config.bgp.address_family import ( + AddressFamily, +) +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers.cli.config.bgp.neighbors import ( + Neighbors, +) +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers.providers import ( + CliProvider, + register_provider, +) + + +REDISTRIBUTE_PROTOCOLS = [ + "ospf", + "ospf6", + "eigrp", + "isis", + "table", + "static", + "connected", + "sharp", + "nhrp", + "kernel", + "babel", + "rip", +] + + +@register_provider("frr", "frr_bgp") +class Provider(CliProvider): + def render(self, config=None): + commands = list() + + existing_as = None + if config: + match = re.search(r"router bgp (\d+)", config, re.M) + if match: + existing_as = match.group(1) + + operation = self.params["operation"] + + context = None + + if self.params["config"]: + context = "router bgp %s" % self.get_value("config.bgp_as") + + if operation == "delete": + if existing_as: + commands.append("no router bgp %s" % existing_as) + elif context: + commands.append("no %s" % context) + + else: + self._validate_input(config) + if operation == "replace": + if existing_as and int(existing_as) != self.get_value("config.bgp_as"): + commands.append("no router bgp %s" % existing_as) + config = None + + elif operation == "override": + if existing_as: + commands.append("no router bgp %s" % existing_as) + config = None + + context_commands = list() + + for key, value in iteritems(self.get_value("config")): + if value is not None: + meth = getattr(self, "_render_%s" % key, None) + if meth: + resp = meth(config) + if resp: + context_commands.extend(to_list(resp)) + + if context and context_commands: + commands.append(context) + commands.extend(context_commands) + commands.append("exit") + return commands + + def _render_router_id(self, config=None): + cmd = "bgp router-id %s" % self.get_value("config.router_id") + if not config or cmd not in config: + return cmd + + def _render_log_neighbor_changes(self, config=None): + cmd = "bgp log-neighbor-changes" + log_neighbor_changes = self.get_value("config.log_neighbor_changes") + if log_neighbor_changes is True: + if not config or cmd not in config: + return cmd + elif log_neighbor_changes is False: + if config and cmd in config: + return "no %s" % cmd + + def _render_networks(self, config=None): + commands = list() + safe_list = list() + + for entry in self.get_value("config.networks"): + network = entry["prefix"] + if entry["masklen"]: + network = "%s/%s" % (entry["prefix"], entry["masklen"]) + safe_list.append(network) + + cmd = "network %s" % network + + if entry["route_map"]: + cmd += " route-map %s" % entry["route_map"] + + if not config or cmd not in config: + commands.append(cmd) + + if self.params["operation"] == "replace": + if config: + matches = re.findall(r"network (\S+)", config, re.M) + for entry in set(matches).difference(safe_list): + commands.append("no network %s" % entry) + + return commands + + def _render_neighbors(self, config): + """generate bgp neighbor configuration""" + return Neighbors(self.params).render(config) + + def _render_address_family(self, config): + """generate address-family configuration""" + return AddressFamily(self.params).render(config) + + def _validate_input(self, config): + def device_has_AF(config): + return re.search(r"address-family (?:.*)", config) + + address_family = self.get_value("config.address_family") + root_networks = self.get_value("config.networks") + operation = self.params["operation"] + + if root_networks and operation == "replace": + if address_family: + for item in address_family: + if item["networks"]: + raise ValueError( + "operation is replace but provided both root level networks and networks under %s %s address family" + % (item["afi"], item["safi"]), + ) + if config and device_has_AF(config): + raise ValueError( + "operation is replace and device has one or more address family activated but root level network(s) provided", + ) diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/module.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/module.py new file mode 100644 index 000000000..c22bf9881 --- /dev/null +++ b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/module.py @@ -0,0 +1,68 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection + +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers import providers + + +class NetworkModule(AnsibleModule): + fail_on_missing_provider = True + + def __init__(self, connection=None, *args, **kwargs): + super(NetworkModule, self).__init__(*args, **kwargs) + + if connection is None: + connection = Connection(self._socket_path) + + self.connection = connection + + @property + def provider(self): + if not hasattr(self, "_provider"): + capabilities = self.from_json(self.connection.get_capabilities()) + + network_os = capabilities["device_info"]["network_os"] + network_api = capabilities["network_api"] + + if network_api == "cliconf": + connection_type = "network_cli" + + cls = providers.get(network_os, self._name.split(".")[-1], connection_type) + + if not cls: + msg = "unable to find suitable provider for network os %s" % network_os + if self.fail_on_missing_provider: + self.fail_json(msg=msg) + else: + self.warn(msg) + + obj = cls(self.params, self.connection, self.check_mode) + + setattr(self, "_provider", obj) + + return getattr(self, "_provider") + + def get_facts(self, subset=None): + try: + self.provider.get_facts(subset) + except Exception as exc: + self.fail_json(msg=to_text(exc)) + + def edit_config(self, config_filter=None): + current_config = self.connection.get_config(flags=config_filter) + try: + commands = self.provider.edit_config(current_config) + changed = bool(commands) + return {"commands": commands, "changed": changed} + except Exception as exc: + self.fail_json(msg=to_text(exc)) diff --git a/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/providers.py b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/providers.py new file mode 100644 index 000000000..ba9db041e --- /dev/null +++ b/ansible_collections/frr/frr/plugins/module_utils/network/frr/providers/providers.py @@ -0,0 +1,126 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json + +from threading import RLock + +from ansible.module_utils.six import itervalues +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + + +_registered_providers = {} +_provider_lock = RLock() + + +def register_provider(network_os, module_name): + def wrapper(cls): + _provider_lock.acquire() + try: + if network_os not in _registered_providers: + _registered_providers[network_os] = {} + for ct in cls.supported_connections: + if ct not in _registered_providers[network_os]: + _registered_providers[network_os][ct] = {} + for item in to_list(module_name): + for entry in itervalues(_registered_providers[network_os]): + entry[item] = cls + finally: + _provider_lock.release() + return cls + + return wrapper + + +def get(network_os, module_name, connection_type): + network_os_providers = _registered_providers.get(network_os) + if network_os_providers is None: + raise ValueError("unable to find a suitable provider for this module") + if connection_type not in network_os_providers: + raise ValueError("provider does not support this connection type") + elif module_name not in network_os_providers[connection_type]: + raise ValueError("could not find a suitable provider for this module") + return network_os_providers[connection_type][module_name] + + +class ProviderBase(object): + supported_connections = () + + def __init__(self, params, connection=None, check_mode=False): + self.params = params + self.connection = connection + self.check_mode = check_mode + + @property + def capabilities(self): + if not hasattr(self, "_capabilities"): + resp = self.from_json(self.connection.get_capabilities()) + setattr(self, "_capabilities", resp) + return getattr(self, "_capabilities") + + def get_value(self, path): + params = self.params.copy() + for key in path.split("."): + params = params[key] + return params + + def get_facts(self, subset=None): + raise NotImplementedError(self.__class__.__name__) + + def edit_config(self): + raise NotImplementedError(self.__class__.__name__) + + +class CliProvider(ProviderBase): + supported_connections = ("network_cli",) + + @property + def capabilities(self): + if not hasattr(self, "_capabilities"): + resp = self.from_json(self.connection.get_capabilities()) + setattr(self, "_capabilities", resp) + return getattr(self, "_capabilities") + + def get_config_context(self, config, path, indent=1): + if config is not None: + netcfg = NetworkConfig(indent=indent, contents=config) + try: + config = netcfg.get_block_config(to_list(path)) + except ValueError: + config = None + return config + + def render(self, config=None): + raise NotImplementedError(self.__class__.__name__) + + def cli(self, command): + try: + if not hasattr(self, "_command_output"): + setattr(self, "_command_output", {}) + return self._command_output[command] + except KeyError: + out = self.connection.get(command) + try: + out = json.loads(out) + except ValueError: + pass + self._command_output[command] = out + return out + + def get_facts(self, subset=None): + return self.populate() + + def edit_config(self, config=None): + commands = self.render(config) + if commands and self.check_mode is False: + self.connection.edit_config(commands) + return commands diff --git a/ansible_collections/frr/frr/plugins/modules/__init__.py b/ansible_collections/frr/frr/plugins/modules/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/plugins/modules/frr_bgp.py b/ansible_collections/frr/frr/plugins/modules/frr_bgp.py new file mode 100644 index 000000000..4bef8a35d --- /dev/null +++ b/ansible_collections/frr/frr/plugins/modules/frr_bgp.py @@ -0,0 +1,478 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: frr_bgp +author: Nilashish Chakraborty (@NilashishC) +short_description: Configure global BGP settings on Free Range Routing(FRR). +description: +- This module provides configuration management of global BGP parameters on devices + running Free Range Routing(FRR). +version_added: 1.0.0 +notes: +- Tested against FRRouting 6.0. +options: + config: + description: + - Specifies the BGP related configuration. + type: dict + suboptions: + bgp_as: + description: + - Specifies the BGP Autonomous System (AS) number to configure on the device. + type: int + required: true + router_id: + description: + - Configures the BGP routing process router-id value. + type: str + log_neighbor_changes: + description: + - Enable/disable logging neighbor up/down and reset reason. + type: bool + neighbors: + description: + - Specifies BGP neighbor related configurations. + type: list + elements: dict + suboptions: + neighbor: + description: + - Neighbor router address. + required: true + type: str + remote_as: + description: + - Remote AS of the BGP neighbor to configure. + type: int + required: true + update_source: + description: + - Source of the routing updates. + type: str + password: + description: + - Password to authenticate the BGP peer connection. + type: str + enabled: + description: + - Administratively shutdown or enable a neighbor. + type: bool + description: + description: + - Neighbor specific description. + type: str + ebgp_multihop: + description: + - Specifies the maximum hop count for EBGP neighbors not on directly connected + networks. + - The range is from 1 to 255. + type: int + peer_group: + description: + - Name of the peer group that the neighbor is a member of. + type: str + timers: + description: + - Specifies BGP neighbor timer related configurations. + type: dict + suboptions: + keepalive: + description: + - Frequency (in seconds) with which the FRR sends keepalive messages + to its peer. + - The range is from 0 to 65535. + type: int + required: true + holdtime: + description: + - Interval (in seconds) after not receiving a keepalive message that + FRR declares a peer dead. + - The range is from 0 to 65535. + type: int + required: true + advertisement_interval: + description: + - Minimum interval between sending BGP routing updates for this neighbor. + type: int + local_as: + description: + - The local AS number for the neighbor. + type: int + port: + description: + - The TCP Port number to use for this neighbor. + - The range is from 0 to 65535. + type: int + networks: + description: + - Specify networks to announce via BGP. + - For operation replace, this option is mutually exclusive with networks option + under address_family. + - For operation replace, if the device already has an address family activated, + this option is not allowed. + type: list + elements: dict + suboptions: + prefix: + description: + - Network ID to announce via BGP. + required: true + type: str + masklen: + description: + - Subnet mask length for the network to announce(e.g, 8, 16, 24, etc.). + required: true + type: int + route_map: + description: + - Route map to modify the attributes. + type: str + address_family: + description: + - Specifies BGP address family related configurations. + type: list + elements: dict + suboptions: + afi: + description: + - Type of address family to configure. + choices: + - ipv4 + - ipv6 + required: true + type: str + safi: + description: + - Specifies the type of cast for the address family. + choices: + - flowspec + - unicast + - multicast + - labeled-unicast + default: unicast + type: str + redistribute: + description: + - Specifies the redistribute information from another routing protocol. + type: list + elements: dict + suboptions: + protocol: + description: + - Specifies the protocol for configuring redistribute information. + choices: + - ospf + - ospf6 + - eigrp + - isis + - table + - static + - connected + - sharp + - nhrp + - kernel + - babel + - rip + required: true + type: str + id: + description: + - Specifies the instance ID/table ID for this protocol + - Valid for ospf and table + type: str + metric: + description: + - Specifies the metric for redistributed routes. + type: int + route_map: + description: + - Specifies the route map reference. + type: str + networks: + description: + - Specify networks to announce via BGP. + - For operation replace, this option is mutually exclusive with root level + networks option. + type: list + elements: dict + suboptions: + prefix: + description: + - Network ID to announce via BGP. + required: true + type: str + masklen: + description: + - Subnet mask length for the network to announce(e.g, 8, 16, 24, etc.). + required: true + type: int + route_map: + description: + - Route map to modify the attributes. + type: str + neighbors: + description: + - Specifies BGP neighbor related configurations in Address Family configuration + mode. + type: list + elements: dict + suboptions: + neighbor: + description: + - Neighbor router address. + required: true + type: str + route_reflector_client: + description: + - Specify a neighbor as a route reflector client. + type: bool + route_server_client: + description: + - Specify a neighbor as a route server client. + type: bool + activate: + description: + - Enable the address family for this neighbor. + type: bool + remove_private_as: + description: + - Remove the private AS number from outbound updates. + type: bool + next_hop_self: + description: + - Enable/disable the next hop calculation for this neighbor. + type: bool + maximum_prefix: + description: + - Maximum number of prefixes to accept from this peer. + - The range is from 1 to 4294967295. + type: int + operation: + description: + - Specifies the operation to be performed on the BGP process configured on the + device. + - In case of merge, the input configuration will be merged with the existing BGP + configuration on the device. + - In case of replace, if there is a diff between the existing configuration and + the input configuration, the existing configuration will be replaced by the + input configuration for every option that has the diff. + - In case of override, all the existing BGP configuration will be removed from + the device and replaced with the input configuration. + - In case of delete the existing BGP configuration will be removed from the device. + default: merge + choices: + - merge + - replace + - override + - delete + type: str +""" + +EXAMPLES = """ +- name: configure global bgp as 64496 + frr.frr.frr_bgp: + config: + bgp_as: 64496 + router_id: 192.0.2.1 + log_neighbor_changes: true + neighbors: + - neighbor: 192.51.100.1 + remote_as: 64497 + timers: + keepalive: 120 + holdtime: 360 + - neighbor: 198.51.100.2 + remote_as: 64498 + networks: + - prefix: 192.0.2.0 + masklen: 24 + route_map: RMAP_1 + - prefix: 198.51.100.0 + masklen: 24 + address_family: + - afi: ipv4 + safi: unicast + redistribute: + - protocol: ospf + id: 223 + metric: 10 + operation: merge + +- name: Configure BGP neighbors + frr.frr.frr_bgp: + config: + bgp_as: 64496 + neighbors: + - neighbor: 192.0.2.10 + remote_as: 64496 + password: ansible + description: IBGP_NBR_1 + timers: + keepalive: 120 + holdtime: 360 + - neighbor: 192.0.2.15 + remote_as: 64496 + description: IBGP_NBR_2 + advertisement_interval: 120 + operation: merge + +- name: Configure BGP neighbors under address family mode + frr.frr.frr_bgp: + config: + bgp_as: 64496 + address_family: + - afi: ipv4 + safi: multicast + neighbors: + - neighbor: 203.0.113.10 + activate: true + maximum_prefix: 250 + + - neighbor: 192.0.2.15 + activate: true + route_reflector_client: true + operation: merge + +- name: Configure root-level networks for BGP + frr.frr.frr_bgp: + config: + bgp_as: 64496 + networks: + - prefix: 203.0.113.0 + masklen: 27 + route_map: RMAP_1 + - prefix: 203.0.113.32 + masklen: 27 + route_map: RMAP_2 + operation: merge + +- name: remove bgp as 64496 from config + frr.frr.frr_bgp: + config: + bgp_as: 64496 + operation: delete +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - router bgp 64496 + - bgp router-id 192.0.2.1 + - neighbor 192.51.100.1 remote-as 64497 + - neighbor 192.51.100.1 timers 120 360 + - neighbor 198.51.100.2 remote-as 64498 + - address-family ipv4 unicast + - redistribute ospf 223 metric 10 + - exit-address-family + - bgp log-neighbor-changes + - network 192.0.2.0/24 route-map RMAP_1 + - network 198.51.100.0/24 + - exit +""" +from ansible.module_utils._text import to_text + +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers.cli.config.bgp.process import ( + REDISTRIBUTE_PROTOCOLS, +) +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers.module import ( + NetworkModule, +) + + +def main(): + """main entry point for module execution""" + network_spec = { + "prefix": dict(required=True), + "masklen": dict(type="int", required=True), + "route_map": dict(), + } + + redistribute_spec = { + "protocol": dict(choices=REDISTRIBUTE_PROTOCOLS, required=True), + "id": dict(), + "metric": dict(type="int"), + "route_map": dict(), + } + + timer_spec = { + "keepalive": dict(type="int", required=True), + "holdtime": dict(type="int", required=True), + } + + neighbor_spec = { + "neighbor": dict(required=True), + "remote_as": dict(type="int", required=True), + "advertisement_interval": dict(type="int"), + "local_as": dict(type="int"), + "port": dict(type="int"), + "update_source": dict(), + "password": dict(no_log=True), + "enabled": dict(type="bool"), + "description": dict(), + "ebgp_multihop": dict(type="int"), + "timers": dict(type="dict", options=timer_spec), + "peer_group": dict(), + } + + af_neighbor_spec = { + "neighbor": dict(required=True), + "activate": dict(type="bool"), + "remove_private_as": dict(type="bool"), + "next_hop_self": dict(type="bool"), + "route_reflector_client": dict(type="bool"), + "route_server_client": dict(type="bool"), + "maximum_prefix": dict(type="int"), + } + + address_family_spec = { + "afi": dict(choices=["ipv4", "ipv6"], required=True), + "safi": dict( + choices=["flowspec", "labeled-unicast", "multicast", "unicast"], + default="unicast", + ), + "networks": dict(type="list", elements="dict", options=network_spec), + "redistribute": dict(type="list", elements="dict", options=redistribute_spec), + "neighbors": dict(type="list", elements="dict", options=af_neighbor_spec), + } + + config_spec = { + "bgp_as": dict(type="int", required=True), + "router_id": dict(), + "log_neighbor_changes": dict(type="bool"), + "neighbors": dict(type="list", elements="dict", options=neighbor_spec), + "address_family": dict(type="list", elements="dict", options=address_family_spec), + "networks": dict(type="list", elements="dict", options=network_spec), + } + + argument_spec = { + "config": dict(type="dict", options=config_spec), + "operation": dict(default="merge", choices=["merge", "replace", "override", "delete"]), + } + + module = NetworkModule(argument_spec=argument_spec, supports_check_mode=True) + + try: + result = module.edit_config(config_filter=" bgp") + + except Exception as exc: + module.fail_json(msg=to_text(exc)) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/frr/frr/plugins/modules/frr_facts.py b/ansible_collections/frr/frr/plugins/modules/frr_facts.py new file mode 100644 index 000000000..a9da20e86 --- /dev/null +++ b/ansible_collections/frr/frr/plugins/modules/frr_facts.py @@ -0,0 +1,406 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: frr_facts +author: Nilashish Chakraborty (@NilashishC) +short_description: Collect facts from remote devices running Free Range Routing (FRR). +description: +- Collects a base set of device facts from a remote device that is running FRR. 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. +version_added: 1.0.0 +notes: +- Tested against FRR 6.0. +options: + gather_subset: + description: + - When supplied, this argument restricts the facts collected to a given subset. + - Possible values for this argument include C(all), C(hardware), C(config), and + C(interfaces). + - Specify a list of values to include a larger subset. + - Use a value with an initial C(!) to collect all facts except that subset. + required: false + default: '!config' + type: list + elements: str +""" + +EXAMPLES = """ +- name: Collect all facts from the device + frr.frr.frr_facts: + gather_subset: all + +- name: Collect only the config and default facts + frr.frr.frr_facts: + gather_subset: + - config + +- name: Collect the config and hardware facts + frr.frr.frr_facts: + gather_subset: + - config + - hardware + +- name: Do not collect hardware facts + frr.frr.frr_facts: + gather_subset: + - '!hardware' +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_version: + description: The FRR version running on the remote device + returned: always + type: str +ansible_net_api: + description: The name of the transport + returned: always + type: str +ansible_net_python_version: + description: The Python version that the Ansible controller is using + returned: always + type: str + +# hardware +ansible_net_mem_stats: + description: The memory statistics fetched from the device + returned: when hardware is configured + type: dict + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_mpls_ldp_neighbors: + description: The list of MPLS LDP neighbors from the remote device + returned: when interfaces is configured and LDP daemon is running on the device + type: dict +""" + +import platform +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + +from ansible_collections.frr.frr.plugins.module_utils.network.frr.frr import ( + get_capabilities, + run_commands, +) + + +class FactsBase(object): + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + self._capabilities = get_capabilities(self.module) + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(commands=cmd, check_rc=False) + + def parse_facts(self, pattern, data): + value = None + match = re.search(pattern, data, re.M) + if match: + value = match.group(1) + return value + + +class Default(FactsBase): + COMMANDS = ["show version"] + + def populate(self): + super(Default, self).populate() + self.facts.update(self.platform_facts()) + + def platform_facts(self): + platform_facts = {} + + resp = self._capabilities + device_info = resp["device_info"] + + platform_facts["system"] = device_info["network_os"] + + for item in ("version", "hostname"): + val = device_info.get("network_os_%s" % item) + if val: + platform_facts[item] = val + + platform_facts["api"] = resp["network_api"] + platform_facts["python_version"] = platform.python_version() + + return platform_facts + + +class Hardware(FactsBase): + COMMANDS = ["show memory"] + + def _parse_daemons(self, data): + match = re.search(r"Memory statistics for (\w+)", data, re.M) + if match: + return match.group(1) + + def gather_memory_facts(self, data): + mem_details = data.split("\n\n") + mem_stats = {} + mem_counters = { + "total_heap_allocated": r"Total heap allocated:(?:\s*)(.*)", + "holding_block_headers": r"Holding block headers:(?:\s*)(.*)", + "used_small_blocks": r"Used small blocks:(?:\s*)(.*)", + "used_ordinary_blocks": r"Used ordinary blocks:(?:\s*)(.*)", + "free_small_blocks": r"Free small blocks:(?:\s*)(.*)", + "free_ordinary_blocks": r"Free ordinary blocks:(?:\s*)(.*)", + "ordinary_blocks": r"Ordinary blocks:(?:\s*)(.*)", + "small_blocks": r"Small blocks:(?:\s*)(.*)", + "holding_blocks": r"Holding blocks:(?:\s*)(.*)", + } + + for item in mem_details: + daemon = self._parse_daemons(item) + mem_stats[daemon] = {} + for fact, pattern in iteritems(mem_counters): + mem_stats[daemon][fact] = self.parse_facts(pattern, item) + + return mem_stats + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts["mem_stats"] = self.gather_memory_facts(data) + + +class Config(FactsBase): + COMMANDS = ["show running-config"] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + data = re.sub( + r"^Building configuration...\s+Current configuration:", + "", + data, + flags=re.MULTILINE, + ) + self.facts["config"] = data + + +class Interfaces(FactsBase): + COMMANDS = ["show interface"] + + def populate(self): + ldp_supported = self._capabilities["supported_protocols"]["ldp"] + + if ldp_supported: + self.COMMANDS.append("show mpls ldp discovery") + + super(Interfaces, self).populate() + data = self.responses[0] + + self.facts["all_ipv4_addresses"] = list() + self.facts["all_ipv6_addresses"] = list() + + if data: + interfaces = self.parse_interfaces(data) + self.facts["interfaces"] = self.populate_interfaces(interfaces) + self.populate_ipv4_interfaces(interfaces) + self.populate_ipv6_interfaces(interfaces) + + if ldp_supported: + data = self.responses[1] + if data: + self.facts["mpls_ldp_neighbors"] = self.populate_mpls_ldp_neighbors(data) + + def parse_interfaces(self, data): + parsed = dict() + key = "" + for line in data.split("\n"): + if len(line) == 0: + continue + elif line[0] == " ": + parsed[key] += "\n%s" % line + else: + match = re.match(r"^Interface (\S+)", line) + if match: + key = match.group(1) + parsed[key] = line + return parsed + + def populate_interfaces(self, interfaces): + facts = dict() + counters = { + "description": r"Description: (.+)", + "macaddress": r"HWaddr: (\S+)", + "type": r"Type: (\S+)", + "vrf": r"vrf: (\S+)", + "mtu": r"mtu (\d+)", + "bandwidth": r"bandwidth (\d+)", + "lineprotocol": r"line protocol is (\S+)", + "operstatus": r"^(?:.+) is (.+),", + } + + for key, value in iteritems(interfaces): + intf = dict() + for fact, pattern in iteritems(counters): + intf[fact] = self.parse_facts(pattern, value) + facts[key] = intf + return facts + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + self.facts["interfaces"][key]["ipv4"] = list() + primary_address = addresses = [] + primary_address = re.findall(r"inet (\S+) broadcast (?:\S+)(?:\s{2,})", value, re.M) + addresses = re.findall(r"inet (\S+) broadcast (?:\S+)(?:\s+)secondary", value, re.M) + if len(primary_address) == 0: + continue + addresses.append(primary_address[0]) + for address in addresses: + addr, subnet = address.split("/") + ipv4 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), "ipv4") + self.facts["interfaces"][key]["ipv4"].append(ipv4) + + def populate_ipv6_interfaces(self, data): + for key, value in data.items(): + self.facts["interfaces"][key]["ipv6"] = list() + addresses = re.findall(r"inet6 (\S+)", value, re.M) + for address in addresses: + addr, subnet = address.split("/") + ipv6 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), "ipv6") + self.facts["interfaces"][key]["ipv6"].append(ipv6) + + def add_ip_address(self, address, family): + if family == "ipv4": + self.facts["all_ipv4_addresses"].append(address) + else: + self.facts["all_ipv6_addresses"].append(address) + + def populate_mpls_ldp_neighbors(self, data): + facts = {} + entries = data.splitlines() + for x in entries: + if x.startswith("AF"): + continue + x = x.split() + if len(x) > 0: + ldp = {} + ldp["neighbor"] = x[1] + ldp["source"] = x[3] + facts[ldp["source"]] = [] + facts[ldp["source"]].append(ldp) + + return facts + + +FACT_SUBSETS = dict(default=Default, hardware=Hardware, config=Config, interfaces=Interfaces) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution""" + argument_spec = dict(gather_subset=dict(default=["!config"], type="list", elements="str")) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + gather_subset = module.params["gather_subset"] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == "all": + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith("!"): + subset = subset[1:] + if subset == "all": + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json( + msg="Subset must be one of [%s], got %s" % (", ".join(VALID_SUBSETS), subset), + ) + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add("default") + + facts = dict() + facts["gather_subset"] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = "ansible_net_%s" % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/frr/frr/plugins/terminal/__init__.py b/ansible_collections/frr/frr/plugins/terminal/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/plugins/terminal/frr.py b/ansible_collections/frr/frr/plugins/terminal/frr.py new file mode 100644 index 000000000..ef1b0cb9c --- /dev/null +++ b/ansible_collections/frr/frr/plugins/terminal/frr.py @@ -0,0 +1,59 @@ +# +# (c) 2018 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible_collections.ansible.netcommon.plugins.plugin_utils.terminal_base import TerminalBase + + +class TerminalModule(TerminalBase): + terminal_stdout_re = [re.compile(rb"[\r\n]?[\w\+\-\.:\/\[\]]+(?:\([^\)]+\)){0,3}(?:[>#]) ?$")] + + terminal_stderr_re = [ + re.compile(rb"% Command incomplete", re.I), + re.compile(rb"% Unknown command", re.I), + re.compile(rb"(?:\S+) instance is already running", re.I), + re.compile(rb"% (?:Create|Specify) .* first", re.I), + re.compile(rb"(?:\S+) is not running", re.I), + re.compile(rb"% Can't find .*", re.I), + re.compile(rb"invalid input", re.I), + re.compile(rb"connection timed out", re.I), + re.compile(rb"[^\r\n]+ not found"), + ] + + def on_open_shell(self): + try: + self._exec_cli_command(b"terminal length 0") + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure("unable to set terminal parameters") + + def on_become(self, passwd=None): + # NOTE: For FRR, enable password only takes effect when telnetting to individual daemons + # vtysh will always drop into enable mode since it runs as a privileged process + pass + + def on_unbecome(self): + # NOTE: For FRR, enable password only takes effect when telnetting to individual daemons + # vtysh will always drop into enable mode since it runs as a privileged process + pass diff --git a/ansible_collections/frr/frr/pyproject.toml b/ansible_collections/frr/frr/pyproject.toml new file mode 100644 index 000000000..fa4225f3e --- /dev/null +++ b/ansible_collections/frr/frr/pyproject.toml @@ -0,0 +1,7 @@ +[tool.black] +line-length = 100 + +[tool.pytest.ini_options] +addopts = ["-vvv", "-n", "2", "--log-level", "WARNING", "--color", "yes"] +testpaths = ["tests"] +filterwarnings = ['ignore:AnsibleCollectionFinder has already been configured'] diff --git a/ansible_collections/frr/frr/requirements.txt b/ansible_collections/frr/frr/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/test-requirements.txt b/ansible_collections/frr/frr/test-requirements.txt new file mode 100644 index 000000000..65db78a04 --- /dev/null +++ b/ansible_collections/frr/frr/test-requirements.txt @@ -0,0 +1,9 @@ +black==22.3.0 ; python_version > '3.5' +flake8 +mock ; python_version < '3.5' +pexpect +pytest-xdist +yamllint +coverage==4.5.4 +pytest-forked +git+https://github.com/ansible-community/pytest-ansible-units.git diff --git a/ansible_collections/frr/frr/tests/.gitignore b/ansible_collections/frr/frr/tests/.gitignore new file mode 100644 index 000000000..ea1472ec1 --- /dev/null +++ b/ansible_collections/frr/frr/tests/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/ansible_collections/frr/frr/tests/sanity/ignore-2.10.txt b/ansible_collections/frr/frr/tests/sanity/ignore-2.10.txt new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/sanity/ignore-2.12.txt b/ansible_collections/frr/frr/tests/sanity/ignore-2.12.txt new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/sanity/ignore-2.13.txt b/ansible_collections/frr/frr/tests/sanity/ignore-2.13.txt new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/sanity/ignore-2.14.txt b/ansible_collections/frr/frr/tests/sanity/ignore-2.14.txt new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/sanity/ignore-2.15.txt b/ansible_collections/frr/frr/tests/sanity/ignore-2.15.txt new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/sanity/ignore-2.16.txt b/ansible_collections/frr/frr/tests/sanity/ignore-2.16.txt new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/sanity/ignore-2.9.txt b/ansible_collections/frr/frr/tests/sanity/ignore-2.9.txt new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/unit/__init__.py b/ansible_collections/frr/frr/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/unit/compat/__init__.py b/ansible_collections/frr/frr/tests/unit/compat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/unit/compat/mock.py b/ansible_collections/frr/frr/tests/unit/compat/mock.py new file mode 100644 index 000000000..e25e8381b --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/compat/mock.py @@ -0,0 +1,28 @@ +# pylint: skip-file +# (c) 2014, Toshio Kuratomi +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +Compat module for Python3.x's unittest.mock module +""" +from unittest.mock import * diff --git a/ansible_collections/frr/frr/tests/unit/compat/unittest.py b/ansible_collections/frr/frr/tests/unit/compat/unittest.py new file mode 100644 index 000000000..df4266ec9 --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/compat/unittest.py @@ -0,0 +1,41 @@ +# (c) 2014, Toshio Kuratomi +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# 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 * + except ImportError: + print("You need unittest2 installed on python2.6.x to run tests") +else: + from unittest import * diff --git a/ansible_collections/frr/frr/tests/unit/mock/__init__.py b/ansible_collections/frr/frr/tests/unit/mock/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/unit/mock/loader.py b/ansible_collections/frr/frr/tests/unit/mock/loader.py new file mode 100644 index 000000000..ebff16f63 --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/mock/loader.py @@ -0,0 +1,117 @@ +# (c) 2012-2014, Michael DeHaan +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import os + +from ansible.errors import AnsibleParserError +from ansible.module_utils._text import to_bytes, to_text +from ansible.parsing.dataloader import DataLoader + + +class DictDataLoader(DataLoader): + def __init__(self, file_mapping=None): + file_mapping = {} if file_mapping is None else file_mapping + assert type(file_mapping) == dict + + super(DictDataLoader, self).__init__() + + self._file_mapping = file_mapping + self._build_known_directories() + self._vault_secrets = None + + def load_from_file(self, path, cache=True, unsafe=False): + path = to_text(path) + if path in self._file_mapping: + return self.load(self._file_mapping[path], path) + return None + + # TODO: the real _get_file_contents returns a bytestring, so we actually convert the + # unicode/text it's created with to utf-8 + def _get_file_contents(self, file_name): + file_name = to_text(file_name) + if file_name in self._file_mapping: + return (to_bytes(self._file_mapping[file_name]), False) + else: + raise AnsibleParserError("file not found: %s" % file_name) + + def path_exists(self, path): + path = to_text(path) + return path in self._file_mapping or path in self._known_directories + + def is_file(self, path): + path = to_text(path) + return path in self._file_mapping + + def is_directory(self, path): + path = to_text(path) + return path in self._known_directories + + def list_directory(self, path): + ret = [] + path = to_text(path) + for x in list(self._file_mapping.keys()) + self._known_directories: + if x.startswith(path): + if os.path.dirname(x) == path: + ret.append(os.path.basename(x)) + return ret + + def is_executable(self, path): + # FIXME: figure out a way to make paths return true for this + return False + + def _add_known_directory(self, directory): + if directory not in self._known_directories: + self._known_directories.append(directory) + + def _build_known_directories(self): + self._known_directories = [] + for path in self._file_mapping: + dirname = os.path.dirname(path) + while dirname not in ("/", ""): + self._add_known_directory(dirname) + dirname = os.path.dirname(dirname) + + def push(self, path, content): + rebuild_dirs = False + if path not in self._file_mapping: + rebuild_dirs = True + + self._file_mapping[path] = content + + if rebuild_dirs: + self._build_known_directories() + + def pop(self, path): + if path in self._file_mapping: + del self._file_mapping[path] + self._build_known_directories() + + def clear(self): + self._file_mapping = dict() + self._known_directories = [] + + def get_basedir(self): + return os.getcwd() + + def set_vault_secrets(self, vault_secrets): + self._vault_secrets = vault_secrets diff --git a/ansible_collections/frr/frr/tests/unit/mock/path.py b/ansible_collections/frr/frr/tests/unit/mock/path.py new file mode 100644 index 000000000..cd0bf4ceb --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/mock/path.py @@ -0,0 +1,12 @@ +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +from ansible.utils.path import unfrackpath + +from ansible_collections.frr.frr.tests.unit.compat.mock import MagicMock + + +mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x) diff --git a/ansible_collections/frr/frr/tests/unit/mock/procenv.py b/ansible_collections/frr/frr/tests/unit/mock/procenv.py new file mode 100644 index 000000000..157979e97 --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/mock/procenv.py @@ -0,0 +1,97 @@ +# (c) 2016, Matt Davis +# (c) 2016, Toshio Kuratomi +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json +import sys + +from contextlib import contextmanager +from io import BytesIO, StringIO + +from ansible.module_utils._text import to_bytes +from ansible.module_utils.six import PY3 + +from ansible_collections.frr.frr.tests.unit.compat import unittest + + +@contextmanager +def swap_stdin_and_argv(stdin_data="", argv_data=tuple()): + """ + context manager that temporarily masks the test runner's values for stdin and argv + """ + real_stdin = sys.stdin + real_argv = sys.argv + + if PY3: + fake_stream = StringIO(stdin_data) + fake_stream.buffer = BytesIO(to_bytes(stdin_data)) + else: + fake_stream = BytesIO(to_bytes(stdin_data)) + + try: + sys.stdin = fake_stream + sys.argv = argv_data + + yield + finally: + sys.stdin = real_stdin + sys.argv = real_argv + + +@contextmanager +def swap_stdout(): + """ + context manager that temporarily replaces stdout for tests that need to verify output + """ + old_stdout = sys.stdout + + if PY3: + fake_stream = StringIO() + else: + fake_stream = BytesIO() + + try: + sys.stdout = fake_stream + + yield fake_stream + finally: + sys.stdout = old_stdout + + +class ModuleTestCase(unittest.TestCase): + def setUp(self, module_args=None): + if module_args is None: + module_args = { + "_ansible_remote_tmp": "/tmp", + "_ansible_keep_remote_files": False, + } + + args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args)) + + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap = swap_stdin_and_argv(stdin_data=args) + self.stdin_swap.__enter__() + + def tearDown(self): + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap.__exit__(None, None, None) diff --git a/ansible_collections/frr/frr/tests/unit/mock/vault_helper.py b/ansible_collections/frr/frr/tests/unit/mock/vault_helper.py new file mode 100644 index 000000000..6006ba965 --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/mock/vault_helper.py @@ -0,0 +1,40 @@ +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible.module_utils._text import to_bytes +from ansible.parsing.vault import VaultSecret + + +class TextVaultSecret(VaultSecret): + """A secret piece of text. ie, a password. Tracks text encoding. + + The text encoding of the text may not be the default text encoding so + we keep track of the encoding so we encode it to the same bytes.""" + + def __init__(self, text, encoding=None, errors=None, _bytes=None): + super(TextVaultSecret, self).__init__() + self.text = text + self.encoding = encoding or "utf-8" + self._bytes = _bytes + self.errors = errors or "strict" + + @property + def bytes(self): + """The text encoded with encoding, unless we specifically set _bytes.""" + return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors) diff --git a/ansible_collections/frr/frr/tests/unit/mock/yaml_helper.py b/ansible_collections/frr/frr/tests/unit/mock/yaml_helper.py new file mode 100644 index 000000000..b995de0ca --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/mock/yaml_helper.py @@ -0,0 +1,158 @@ +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import io + +import yaml + +from ansible.module_utils.six import PY3 +from ansible.parsing.yaml.dumper import AnsibleDumper +from ansible.parsing.yaml.loader import AnsibleLoader + + +class YamlTestUtils(object): + """Mixin class to combine with a unittest.TestCase subclass.""" + + def _loader(self, stream): + """Vault related tests will want to override this. + + Vault cases should setup a AnsibleLoader that has the vault password.""" + return AnsibleLoader(stream) + + def _dump_stream(self, obj, stream, dumper=None): + """Dump to a py2-unicode or py3-string stream.""" + if PY3: + return yaml.dump(obj, stream, Dumper=dumper) + else: + return yaml.dump(obj, stream, Dumper=dumper, encoding=None) + + def _dump_string(self, obj, dumper=None): + """Dump to a py2-unicode or py3-string""" + if PY3: + return yaml.dump(obj, Dumper=dumper) + else: + return yaml.dump(obj, Dumper=dumper, encoding=None) + + def _dump_load_cycle(self, obj): + # Each pass though a dump or load revs the 'generation' + # obj to yaml string + string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper) + + # wrap a stream/file like StringIO around that yaml + stream_from_object_dump = io.StringIO(string_from_object_dump) + loader = self._loader(stream_from_object_dump) + # load the yaml stream to create a new instance of the object (gen 2) + obj_2 = loader.get_data() + + # dump the gen 2 objects directory to strings + string_from_object_dump_2 = self._dump_string(obj_2, dumper=AnsibleDumper) + + # The gen 1 and gen 2 yaml strings + self.assertEqual(string_from_object_dump, string_from_object_dump_2) + # the gen 1 (orig) and gen 2 py object + self.assertEqual(obj, obj_2) + + # again! gen 3... load strings into py objects + stream_3 = io.StringIO(string_from_object_dump_2) + loader_3 = self._loader(stream_3) + obj_3 = loader_3.get_data() + + string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper) + + self.assertEqual(obj, obj_3) + # should be transitive, but... + self.assertEqual(obj_2, obj_3) + self.assertEqual(string_from_object_dump, string_from_object_dump_3) + + def _old_dump_load_cycle(self, obj): + """Dump the passed in object to yaml, load it back up, dump again, compare.""" + stream = io.StringIO() + + yaml_string = self._dump_string(obj, dumper=AnsibleDumper) + self._dump_stream(obj, stream, dumper=AnsibleDumper) + + yaml_string_from_stream = stream.getvalue() + + # reset stream + stream.seek(0) + + loader = self._loader(stream) + # loader = AnsibleLoader(stream, vault_password=self.vault_password) + obj_from_stream = loader.get_data() + + stream_from_string = io.StringIO(yaml_string) + loader2 = self._loader(stream_from_string) + # loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password) + obj_from_string = loader2.get_data() + + stream_obj_from_stream = io.StringIO() + stream_obj_from_string = io.StringIO() + + if PY3: + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper) + else: + yaml.dump( + obj_from_stream, + stream_obj_from_stream, + Dumper=AnsibleDumper, + encoding=None, + ) + yaml.dump( + obj_from_stream, + stream_obj_from_string, + Dumper=AnsibleDumper, + encoding=None, + ) + + yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue() + yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue() + + stream_obj_from_stream.seek(0) + stream_obj_from_string.seek(0) + + if PY3: + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper) + else: + yaml_string_obj_from_stream = yaml.dump( + obj_from_stream, + Dumper=AnsibleDumper, + encoding=None, + ) + yaml_string_obj_from_string = yaml.dump( + obj_from_string, + Dumper=AnsibleDumper, + encoding=None, + ) + + assert yaml_string == yaml_string_obj_from_stream + assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string + assert ( + yaml_string + == yaml_string_obj_from_stream + == yaml_string_obj_from_string + == yaml_string_stream_obj_from_stream + == yaml_string_stream_obj_from_string + ) + assert obj == obj_from_stream + assert obj == obj_from_string + assert obj == yaml_string_obj_from_stream + assert obj == yaml_string_obj_from_string + assert ( + obj + == obj_from_stream + == obj_from_string + == yaml_string_obj_from_stream + == yaml_string_obj_from_string + ) + return { + "obj": obj, + "yaml_string": yaml_string, + "yaml_string_from_stream": yaml_string_from_stream, + "obj_from_stream": obj_from_stream, + "obj_from_string": obj_from_string, + "yaml_string_obj_from_string": yaml_string_obj_from_string, + } diff --git a/ansible_collections/frr/frr/tests/unit/modules/__init__.py b/ansible_collections/frr/frr/tests/unit/modules/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/unit/modules/conftest.py b/ansible_collections/frr/frr/tests/unit/modules/conftest.py new file mode 100644 index 000000000..807fd27b8 --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/modules/conftest.py @@ -0,0 +1,33 @@ +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json + +import pytest + +from ansible.module_utils._text import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping +from ansible.module_utils.six import string_types + + +@pytest.fixture +def patch_ansible_module(request, mocker): + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if "ANSIBLE_MODULE_ARGS" not in request.param: + request.param = {"ANSIBLE_MODULE_ARGS": request.param} + if "_ansible_remote_tmp" not in request.param["ANSIBLE_MODULE_ARGS"]: + request.param["ANSIBLE_MODULE_ARGS"]["_ansible_remote_tmp"] = "/tmp" + if "_ansible_keep_remote_files" not in request.param["ANSIBLE_MODULE_ARGS"]: + request.param["ANSIBLE_MODULE_ARGS"]["_ansible_keep_remote_files"] = False + args = json.dumps(request.param) + else: + raise Exception("Malformed data to the patch_ansible_module pytest fixture") + + mocker.patch("ansible.module_utils.basic._ANSIBLE_ARGS", to_bytes(args)) diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/__init__.py b/ansible_collections/frr/frr/tests/unit/modules/network/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/frr/__init__.py b/ansible_collections/frr/frr/tests/unit/modules/network/frr/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/__init__.py b/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_bgp_config b/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_bgp_config new file mode 100644 index 000000000..eeecbe7ae --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_bgp_config @@ -0,0 +1,24 @@ +! +router bgp 64496 + bgp router-id 192.0.2.1 + bgp log-neighbor-changes + neighbor 192.51.100.1 remote-as 64496 + neighbor 192.51.100.1 timers 120 360 + neighbor 198.51.100.3 remote-as 64498 + neighbor 2.2.2.2 remote-as 500 + neighbor 2.2.2.2 description EBGP_PEER + ! + address-family ipv4 unicast + network 192.0.1.0/24 route-map RMAP_1 + network 198.51.100.0/24 route-map RMAP_2 + redistribute static metric 100 + redistribute eigrp metric 10 route-map RMAP_3 + neighbor 2.2.2.2 remove-private-AS + neighbor 2.2.2.2 maximum-prefix 100 + exit-address-family + ! + address-family ipv4 multicast + network 10.0.0.0/8 route-map RMAP_1 + network 20.0.0.0/8 route-map RMAP_2 + exit-address-family +! diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_interface b/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_interface new file mode 100644 index 000000000..62f08881f --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_interface @@ -0,0 +1,36 @@ +Interface eth0 is up, line protocol is up + Link ups: 0 last: (never) + Link downs: 0 last: (never) + vrf: Default-IP-Routing-Table + index 2 metric 0 mtu 1450 speed 0 + flags: + Type: Ethernet + HWaddr: fa:16:3e:d4:32:b2 + bandwidth 4048 Mbps + inet 192.168.1.8/24 broadcast 192.168.1.255 + inet6 fe80::f816:3eff:fed4:32b2/64 + Interface Type Other +Interface eth1 is up, line protocol is up + Link ups: 0 last: (never) + Link downs: 0 last: (never) + vrf: Default-IP-Routing-Table + Description: Secondary Interface + index 3 metric 0 mtu 1500 speed 0 + flags: + Type: Ethernet + HWaddr: fa:16:3e:95:88:f7 + inet 192.168.1.21/24 broadcast 192.168.1.255 + inet 192.168.1.19/24 broadcast 192.168.1.255 secondary + inet 192.168.1.18/24 broadcast 192.168.1.255 secondary + inet6 fe80::f816:3eff:fe95:88f7/64 + inet6 3ffe:506::1/48 + inet6 3ffe:504::1/48 + Interface Type Other +Interface lo is up, line protocol is up + Link ups: 0 last: (never) + Link downs: 0 last: (never) + vrf: Default-IP-Routing-Table + index 1 metric 0 mtu 65536 speed 0 + flags: + Type: Loopback + Interface Type Other diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_memory b/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_memory new file mode 100644 index 000000000..d64a9a4c5 --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_memory @@ -0,0 +1,82 @@ +Memory statistics for zebra: +System allocator statistics: + Total heap allocated: 4200 KiB + Holding block headers: 0 bytes + Used small blocks: 0 bytes + Used ordinary blocks: 2927 KiB + Free small blocks: 2096 bytes + Free ordinary blocks: 1273 KiB + Ordinary blocks: 14 + Small blocks: 60 + Holding blocks: 0 +(see system documentation for 'mallinfo' for meaning) +--- qmem libfrr --- +Buffer : 18 24 448 +Buffer data : 1 4120 4120 +Host config : 4 (variably sized) 96 +Command Tokens : 3162 72 228128 +Command Token Text : 2362 (variably sized) 77344 +Command Token Help : 2362 (variably sized) 56944 +Command Argument : 2 (variably sized) 48 +Command Argument Name : 534 (variably sized) 12912 +FRR POSIX Thread : 28 (variably sized) 2016 +POSIX synchronization primitives: 28 (variably sized) 1344 +Graph : 24 8 576 +Graph Node : 3744 32 150112 +Hash : 2495 (variably sized) 119880 +Hash Bucket : 778 32 31296 +Hash Index : 1248 (variably sized) 363040 +Hook entry : 12 48 672 +Interface : 3 248 744 +Connected : 8 40 320 +Informational Link Parameters : 1 96 104 +Link List : 43 40 1720 +Link Node : 1308 24 31456 +Logging : 1 80 88 +Temporary memory : 23 (variably sized) 42584 +Nexthop : 27 112 3256 +NetNS Context : 2 (variably sized) 128 +NetNS Name : 1 18 24 +Priority queue : 15 32 600 +Priority queue data : 15 256 3960 +Prefix : 12 48 672 +Privilege information : 2 (variably sized) 80 +Stream : 36 (variably sized) 591264 +Stream FIFO : 28 64 2016 +Route table : 14 48 784 +Route node : 43 (variably sized) 4920 +Thread : 51 176 9384 +Thread master : 59 (variably sized) 251016 +Thread Poll Info : 30 8192 246000 +Thread stats : 52 64 3744 +Vector : 7543 16 181432 +Vector index : 7543 (variably sized) 246776 +VRF : 1 184 184 +VRF bit-map : 28 8 672 +VTY : 6 (variably sized) 19440 +Work queue : 2 (variably sized) 224 +Work queue name string : 1 22 24 +Redistribution instance IDs : 1 2 24 +--- qmem Label Manager --- +Label Manager Chunk : 1 16 24 +--- qmem zebra --- +ZEBRA VRF : 1 656 664 +Route Entry : 27 80 2392 +RIB destination : 19 48 1080 +RIB table info : 4 16 96 +Nexthop tracking object : 2 200 400 +Zebra Name Space : 1 312 312 +PTM BFD process registration table.: 3 32 120 +--- qmem Table Manager --- + +Memory statistics for ripd: +System allocator statistics: + Total heap allocated: 936 KiB + Holding block headers: 0 bytes + Used small blocks: 0 bytes + Used ordinary blocks: 901 KiB + Free small blocks: 1504 bytes + Free ordinary blocks: 35 KiB + Ordinary blocks: 4 + Small blocks: 44 + Holding blocks: 0 diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_version b/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_version new file mode 100644 index 000000000..daec039df --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/modules/network/frr/fixtures/frr_facts_show_version @@ -0,0 +1,16 @@ +FRRouting 6.0 (rtr1). +Copyright 1996-2005 Kunihiro Ishiguro, et al. +configured with: + '--build=x86_64-redhat-linux-gnu' '--host=x86_64-redhat-linux-gnu' '--program-prefix=' + '--disable-dependency-tracking' '--prefix=/usr' '--exec-prefix=/usr' '--bindir=/usr/bin' + '--sysconfdir=/etc' '--datadir=/usr/share' '--includedir=/usr/include' '--libdir=/usr/lib64' + '--libexecdir=/usr/libexec' '--localstatedir=/var' '--sharedstatedir=/var/lib' '--mandir=/usr/share/man' + '--infodir=/usr/share/info' '--sbindir=/usr/lib/frr' '--sysconfdir=/etc/frr' '--localstatedir=/var/run/frr' + '--disable-static' '--disable-werror' '--enable-irdp' '--enable-multipath=256' '--enable-vtysh' + '--enable-ospfclient' '--enable-ospfapi' '--enable-rtadv' '--enable-ldpd' '--enable-pimd' + '--enable-pbrd' '--enable-nhrpd' '--enable-eigrpd' '--enable-babeld' '--enable-user=frr' + '--enable-group=frr' '--enable-vty-group=frrvty' '--enable-fpm' '--enable-watchfrr' '--disable-bgp-vnc' + '--enable-isisd' '--enable-systemd' '--disable-rpki' '--enable-bfdd' 'build_alias=x86_64-redhat-linux-gnu' + 'host_alias=x86_64-redhat-linux-gnu' 'PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig' + 'CFLAGS=-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong + --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic' 'LDFLAGS=-Wl,-z,relro ' diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/frr/frr_module.py b/ansible_collections/frr/frr/tests/unit/modules/network/frr/frr_module.py new file mode 100644 index 000000000..e4a8c178d --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/modules/network/frr/frr_module.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json +import os + +from ansible_collections.frr.frr.tests.unit.modules.utils import ( + AnsibleExitJson, + AnsibleFailJson, + ModuleTestCase, +) + + +fixture_path = os.path.join(os.path.dirname(__file__), "fixtures") +fixture_data = {} + + +def load_fixture(name): + path = os.path.join(fixture_path, name) + + if path in fixture_data: + return fixture_data[path] + + with open(path) as f: + data = f.read() + + try: + data = json.loads(data) + except Exception: + pass + + fixture_data[path] = data + return data + + +class TestFrrModule(ModuleTestCase): + def execute_module( + self, + failed=False, + changed=False, + commands=None, + sort=True, + defaults=False, + ): + self.load_fixtures(commands) + + if failed: + result = self.failed() + self.assertTrue(result["failed"], result) + else: + result = self.changed(changed) + self.assertEqual(result["changed"], changed, result) + + if commands is not None: + if sort: + self.assertEqual( + sorted(commands), + sorted(result["commands"]), + result["commands"], + ) + else: + self.assertEqual(commands, result["commands"], result["commands"]) + + return result + + def failed(self): + with self.assertRaises(AnsibleFailJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertTrue(result["failed"], result) + return result + + def changed(self, changed=False): + with self.assertRaises(AnsibleExitJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result["changed"], changed, result) + return result + + def load_fixtures(self, commands=None): + pass diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/frr/test_frr_bgp.py b/ansible_collections/frr/frr/tests/unit/modules/network/frr/test_frr_bgp.py new file mode 100644 index 000000000..43e9304ce --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/modules/network/frr/test_frr_bgp.py @@ -0,0 +1,362 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.frr.frr.plugins.module_utils.network.frr.providers.cli.config.bgp.process import ( + Provider, +) +from ansible_collections.frr.frr.plugins.modules import frr_bgp + +from .frr_module import TestFrrModule, load_fixture + + +class TestFrrBgpModule(TestFrrModule): + module = frr_bgp + + def setUp(self): + super(TestFrrBgpModule, self).setUp() + self._bgp_config = load_fixture("frr_bgp_config") + + def test_frr_bgp(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + router_id="192.0.2.2", + networks=None, + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, ["router bgp 64496", "bgp router-id 192.0.2.2", "exit"]) + + def test_frr_bgp_idempotent(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + router_id="192.0.2.1", + networks=None, + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_frr_bgp_remove(self): + obj = Provider( + params=dict( + config=dict(bgp_as=64496, networks=None, address_family=None), + operation="delete", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, ["no router bgp 64496"]) + + def test_frr_bgp_neighbor(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + neighbors=[dict(neighbor="192.51.100.2", remote_as=64496)], + networks=None, + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual( + commands, + [ + "router bgp 64496", + "neighbor 192.51.100.2 remote-as 64496", + "exit", + ], + ) + + def test_frr_bgp_neighbor_idempotent(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + neighbors=[ + dict( + neighbor="192.51.100.1", + remote_as=64496, + timers=dict(keepalive=120, holdtime=360), + ), + ], + networks=None, + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_frr_bgp_network(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + networks=[dict(prefix="192.0.2.0", masklen=24, route_map="RMAP_1")], + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual( + sorted(commands), + sorted( + [ + "router bgp 64496", + "network 192.0.2.0/24 route-map RMAP_1", + "exit", + ], + ), + ) + + def test_frr_bgp_network_idempotent(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + networks=[ + dict(prefix="192.0.1.0", masklen=24, route_map="RMAP_1"), + dict( + prefix="198.51.100.0", + masklen=24, + route_map="RMAP_2", + ), + ], + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_frr_bgp_address_family_redistribute(self): + rd_1 = dict(protocol="ospf", id="233", metric=90, route_map=None) + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="unicast", redistribute=[rd_1])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + cmd = [ + "router bgp 64496", + "address-family ipv4 unicast", + "redistribute ospf 233 metric 90", + "exit-address-family", + "exit", + ] + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_frr_bgp_address_family_redistribute_idempotent(self): + rd_1 = dict(protocol="eigrp", metric=10, route_map="RMAP_3", id=None) + rd_2 = dict(protocol="static", metric=100, id=None, route_map=None) + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="unicast", redistribute=[rd_1, rd_2])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_frr_bgp_address_family_neighbors(self): + af_nbr_1 = dict(neighbor="192.51.100.1", maximum_prefix=35, activate=True) + af_nbr_2 = dict(neighbor="192.51.100.3", route_reflector_client=True, activate=True) + + config = dict( + bgp_as=64496, + address_family=[ + dict( + afi="ipv4", + safi="multicast", + neighbors=[af_nbr_1, af_nbr_2], + ), + ], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + cmd = [ + "router bgp 64496", + "address-family ipv4 multicast", + "neighbor 192.51.100.1 activate", + "neighbor 192.51.100.1 maximum-prefix 35", + "neighbor 192.51.100.3 activate", + "neighbor 192.51.100.3 route-reflector-client", + "exit-address-family", + "exit", + ] + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_frr_bgp_address_family_neighbors_idempotent(self): + af_nbr_1 = dict(neighbor="2.2.2.2", remove_private_as=True, maximum_prefix=100) + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="unicast", neighbors=[af_nbr_1])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_frr_bgp_address_family_networks(self): + net = dict(prefix="1.0.0.0", masklen=8, route_map="RMAP_1") + net2 = dict(prefix="192.168.1.0", masklen=24, route_map="RMAP_2") + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="multicast", networks=[net, net2])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + cmd = [ + "router bgp 64496", + "address-family ipv4 multicast", + "network 1.0.0.0/8 route-map RMAP_1", + "network 192.168.1.0/24 route-map RMAP_2", + "exit-address-family", + "exit", + ] + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_frr_bgp_address_family_networks_idempotent(self): + net = dict(prefix="10.0.0.0", masklen=8, route_map="RMAP_1") + net2 = dict(prefix="20.0.0.0", masklen=8, route_map="RMAP_2") + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="multicast", networks=[net, net2])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_frr_bgp_operation_override(self): + net_1 = dict(prefix="1.0.0.0", masklen=8, route_map="RMAP_1") + net_2 = dict(prefix="192.168.1.0", masklen=24, route_map="RMAP_2") + nbr_1 = dict( + neighbor="192.51.100.1", + remote_as=64496, + advertisement_interval=120, + ) + nbr_2 = dict( + neighbor="192.51.100.3", + remote_as=64496, + timers=dict(keepalive=300, holdtime=360), + ) + af_nbr_1 = dict(neighbor="192.51.100.1", maximum_prefix=35) + af_nbr_2 = dict(neighbor="192.51.100.3", route_reflector_client=True) + + af_1 = dict(afi="ipv4", safi="unicast", neighbors=[af_nbr_1, af_nbr_2]) + af_2 = dict(afi="ipv4", safi="multicast", networks=[net_1, net_2]) + config = dict( + bgp_as=64496, + neighbors=[nbr_1, nbr_2], + address_family=[af_1, af_2], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="override")) + commands = obj.render(self._bgp_config) + + cmd = [ + "no router bgp 64496", + "router bgp 64496", + "neighbor 192.51.100.1 remote-as 64496", + "neighbor 192.51.100.1 advertisement-interval 120", + "neighbor 192.51.100.3 remote-as 64496", + "neighbor 192.51.100.3 timers 300 360", + "address-family ipv4 unicast", + "neighbor 192.51.100.1 maximum-prefix 35", + "neighbor 192.51.100.3 route-reflector-client", + "exit-address-family", + "address-family ipv4 multicast", + "network 1.0.0.0/8 route-map RMAP_1", + "network 192.168.1.0/24 route-map RMAP_2", + "exit-address-family", + "exit", + ] + + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_frr_bgp_operation_replace(self): + rd = dict(protocol="ospf", id=223, metric=110, route_map=None) + net = dict(prefix="10.0.0.0", masklen=8, route_map="RMAP_1") + net2 = dict(prefix="20.0.0.0", masklen=8, route_map="RMAP_2") + + af_1 = dict(afi="ipv4", safi="unicast", redistribute=[rd]) + af_2 = dict(afi="ipv4", safi="multicast", networks=[net, net2]) + + config = dict(bgp_as=64496, address_family=[af_1, af_2], networks=None) + obj = Provider(params=dict(config=config, operation="replace")) + commands = obj.render(self._bgp_config) + + cmd = [ + "router bgp 64496", + "address-family ipv4 unicast", + "redistribute ospf 223 metric 110", + "no redistribute eigrp", + "no redistribute static", + "exit-address-family", + "exit", + ] + + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_frr_bgp_operation_replace_with_new_as(self): + rd = dict(protocol="ospf", id=223, metric=110, route_map=None) + + af_1 = dict(afi="ipv4", safi="unicast", redistribute=[rd]) + + config = dict(bgp_as=64497, address_family=[af_1], networks=None) + obj = Provider(params=dict(config=config, operation="replace")) + commands = obj.render(self._bgp_config) + + cmd = [ + "no router bgp 64496", + "router bgp 64497", + "address-family ipv4 unicast", + "redistribute ospf 223 metric 110", + "exit-address-family", + "exit", + ] + + self.assertEqual(sorted(commands), sorted(cmd)) diff --git a/ansible_collections/frr/frr/tests/unit/modules/network/frr/test_frr_facts.py b/ansible_collections/frr/frr/tests/unit/modules/network/frr/test_frr_facts.py new file mode 100644 index 000000000..db7e12644 --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/modules/network/frr/test_frr_facts.py @@ -0,0 +1,121 @@ +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.frr.frr.plugins.modules import frr_facts +from ansible_collections.frr.frr.tests.unit.compat.mock import patch +from ansible_collections.frr.frr.tests.unit.modules.utils import set_module_args + +from .frr_module import TestFrrModule, load_fixture + + +class TestFrrFactsModule(TestFrrModule): + module = frr_facts + + def setUp(self): + super(TestFrrFactsModule, self).setUp() + self.mock_run_commands = patch( + "ansible_collections.frr.frr.plugins.modules.frr_facts.run_commands", + ) + self.run_commands = self.mock_run_commands.start() + + self.mock_get_capabilities = patch( + "ansible_collections.frr.frr.plugins.modules.frr_facts.get_capabilities", + ) + self.get_capabilities = self.mock_get_capabilities.start() + self.get_capabilities.return_value = { + "device_info": { + "network_os": "frr", + "network_os_hostname": "rtr1", + "network_os_version": "6.0", + }, + "supported_protocols": {"ldp": False}, + "network_api": "cliconf", + } + + def tearDown(self): + super(TestFrrFactsModule, self).tearDown() + self.mock_run_commands.stop() + self.mock_get_capabilities.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + commands = kwargs["commands"] + output = list() + + for command in commands: + filename = str(command).split(" | ", 1)[0].replace(" ", "_") + output.append(load_fixture("frr_facts_%s" % filename)) + return output + + self.run_commands.side_effect = load_from_file + + def test_frr_facts_default(self): + set_module_args(dict(gather_subset="default")) + result = self.execute_module() + self.assertEqual(result["ansible_facts"]["ansible_net_hostname"], "rtr1") + self.assertEqual(result["ansible_facts"]["ansible_net_version"], "6.0") + self.assertEqual(result["ansible_facts"]["ansible_net_system"], "frr") + + def test_frr_facts_interfaces(self): + set_module_args(dict(gather_subset="interfaces")) + result = self.execute_module() + self.assertEqual( + result["ansible_facts"]["ansible_net_interfaces"]["eth0"]["macaddress"], + "fa:16:3e:d4:32:b2", + ) + self.assertEqual( + result["ansible_facts"]["ansible_net_interfaces"]["eth1"]["macaddress"], + "fa:16:3e:95:88:f7", + ) + self.assertEqual( + result["ansible_facts"]["ansible_net_interfaces"]["eth0"]["ipv4"], + [{"address": "192.168.1.8", "subnet": "24"}], + ) + self.assertEqual( + result["ansible_facts"]["ansible_net_interfaces"]["eth0"]["ipv6"], + [{"address": "fe80::f816:3eff:fed4:32b2", "subnet": "64"}], + ) + self.assertEqual( + sorted(result["ansible_facts"]["ansible_net_all_ipv4_addresses"]), + sorted(["192.168.1.19", "192.168.1.18", "192.168.1.21", "192.168.1.8"]), + ) + self.assertEqual( + sorted(result["ansible_facts"]["ansible_net_all_ipv6_addresses"]), + sorted( + [ + "fe80::f816:3eff:fe95:88f7", + "3ffe:506::1", + "3ffe:504::1", + "fe80::f816:3eff:fed4:32b2", + ], + ), + ) + + def test_frr_facts_hardware(self): + set_module_args(dict(gather_subset="hardware")) + result = self.execute_module() + + self.assertEqual( + result["ansible_facts"]["ansible_net_mem_stats"]["zebra"]["total_heap_allocated"], + "4200 KiB", + ) + + self.assertEqual( + result["ansible_facts"]["ansible_net_mem_stats"]["ripd"]["total_heap_allocated"], + "936 KiB", + ) + + self.assertEqual( + result["ansible_facts"]["ansible_net_mem_stats"]["ripd"]["used_ordinary_blocks"], + "901 KiB", + ) + + self.assertEqual( + result["ansible_facts"]["ansible_net_mem_stats"]["ripd"]["holding_block_headers"], + "0 bytes", + ) diff --git a/ansible_collections/frr/frr/tests/unit/modules/utils.py b/ansible_collections/frr/frr/tests/unit/modules/utils.py new file mode 100644 index 000000000..93f7f9a94 --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/modules/utils.py @@ -0,0 +1,56 @@ +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + +from ansible_collections.frr.frr.tests.unit.compat import unittest +from ansible_collections.frr.frr.tests.unit.compat.mock import patch + + +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/ansible_collections/frr/frr/tests/unit/requirements.txt b/ansible_collections/frr/frr/tests/unit/requirements.txt new file mode 100644 index 000000000..a9772bea1 --- /dev/null +++ b/ansible_collections/frr/frr/tests/unit/requirements.txt @@ -0,0 +1,42 @@ +boto3 +placebo +pycrypto +passlib +pypsrp +python-memcached +pytz +pyvmomi +redis +requests +setuptools > 0.6 # pytest-xdist installed via requirements does not work with very old setuptools (sanity_ok) +unittest2 ; python_version < '2.7' +importlib ; python_version < '2.7' +netaddr +ipaddress +netapp-lib +solidfire-sdk-python + +# requirements for F5 specific modules +f5-sdk ; python_version >= '2.7' +f5-icontrol-rest ; python_version >= '2.7' +deepdiff + +# requirement for Fortinet specific modules +pyFMG + +# requirement for aci_rest module +xmljson + +# requirement for winrm connection plugin tests +pexpect + +# requirement for the linode module +linode-python # APIv3 +linode_api4 ; python_version > '2.6' # APIv4 + +# requirement for the gitlab module +python-gitlab +httmock + +# requirment for kubevirt modules +openshift ; python_version >= '2.7' diff --git a/ansible_collections/frr/frr/tox.ini b/ansible_collections/frr/frr/tox.ini new file mode 100644 index 000000000..6ada631cb --- /dev/null +++ b/ansible_collections/frr/frr/tox.ini @@ -0,0 +1,31 @@ +[tox] +minversion = 1.4.2 +envlist = linters +skipsdist = True + +[testenv] +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +[testenv:black] +install_command = pip install {opts} {packages} +commands = + black -v {toxinidir} + +[testenv:linters] +install_command = pip install {opts} {packages} +commands = + black -v --diff --check {toxinidir} + flake8 {posargs} + +[testenv:venv] +commands = {posargs} + +[flake8] +# E123, E125 skipped as they are invalid PEP-8. + +show-source = True +ignore = E123,E125,E203,E402,E501,E741,F401,F811,F841,W503 +max-line-length = 160 +builtins = _ +exclude = .git,.tox,tests/unit/compat/ -- cgit v1.2.3