├── .github └── workflows │ ├── docs.yaml │ ├── publish-to-pypi.yml │ └── setup.yml ├── .gitignore ├── .isort.cfg ├── .readthedocs.yaml ├── AUTHORS ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── annet.py ├── annet ├── __init__.py ├── adapters │ ├── __init__.py │ ├── deployers │ │ └── stub │ │ │ └── deployer.py │ ├── fetchers │ │ ├── __init__.py │ │ └── stub │ │ │ ├── __init__.py │ │ │ └── fetcher.py │ ├── file │ │ ├── __init__.py │ │ └── provider.py │ └── netbox │ │ ├── __init__.py │ │ ├── common │ │ ├── __init__.py │ │ ├── adapter.py │ │ ├── client.py │ │ ├── manufacturer.py │ │ ├── models.py │ │ ├── query.py │ │ ├── status_client.py │ │ ├── storage_base.py │ │ └── storage_opts.py │ │ ├── provider.py │ │ ├── v24 │ │ ├── __init__.py │ │ ├── models.py │ │ └── storage.py │ │ ├── v37 │ │ ├── __init__.py │ │ ├── models.py │ │ └── storage.py │ │ ├── v41 │ │ ├── __init__.py │ │ ├── models.py │ │ └── storage.py │ │ └── v42 │ │ ├── __init__.py │ │ ├── models.py │ │ └── storage.py ├── annet.py ├── annlib │ ├── __init__.py │ ├── command.py │ ├── diff.py │ ├── errors.py │ ├── filter_acl.py │ ├── jsontools.py │ ├── lib.py │ ├── netdev │ │ ├── __init__.py │ │ ├── db.py │ │ ├── devdb │ │ │ ├── __init__.py │ │ │ └── data │ │ │ │ └── devdb.json │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── dump.py │ │ │ └── hardware.py │ ├── output.py │ ├── patching.py │ ├── rbparser │ │ ├── __init__.py │ │ ├── acl.py │ │ ├── deploying.py │ │ ├── ordering.py │ │ ├── platform.py │ │ └── syntax.py │ ├── rulebook │ │ ├── __init__.py │ │ └── common.py │ └── types.py ├── api │ └── __init__.py ├── argparse.py ├── bgp_models.py ├── cli.py ├── cli_args.py ├── configs │ ├── context.yml │ └── logging.yaml ├── connectors.py ├── deploy.py ├── deploy_ui.py ├── diff.py ├── executor.py ├── filtering.py ├── gen.py ├── generators │ ├── __init__.py │ ├── base.py │ ├── common │ │ ├── __init__.py │ │ └── initial.py │ ├── entire.py │ ├── exceptions.py │ ├── jsonfragment.py │ ├── partial.py │ ├── perf.py │ ├── ref.py │ └── result.py ├── hardware.py ├── implicit.py ├── lib.py ├── mesh │ ├── __init__.py │ ├── basemodel.py │ ├── device_models.py │ ├── executor.py │ ├── match_args.py │ ├── models_converter.py │ ├── peer_models.py │ ├── port_processor.py │ └── registry.py ├── output.py ├── parallel.py ├── patching.py ├── reference.py ├── rpl │ ├── __init__.py │ ├── action.py │ ├── condition.py │ ├── match_builder.py │ ├── policy.py │ ├── result.py │ ├── routemap.py │ └── statement_builder.py ├── rpl_generators │ ├── __init__.py │ ├── aspath.py │ ├── community.py │ ├── cumulus_frr.py │ ├── entities.py │ ├── execute.py │ ├── policy.py │ ├── prefix_lists.py │ └── rd.py ├── rulebook │ ├── __init__.py │ ├── arista │ │ ├── __init__.py │ │ ├── aaa.py │ │ └── iface.py │ ├── aruba │ │ ├── __init__.py │ │ ├── ap_env.py │ │ └── misc.py │ ├── b4com │ │ ├── __init__.py │ │ ├── file.py │ │ └── iface.py │ ├── cisco │ │ ├── __init__.py │ │ ├── iface.py │ │ ├── misc.py │ │ └── vlandb.py │ ├── common.py │ ├── deploying.py │ ├── generic │ │ ├── __init__.py │ │ └── misc.py │ ├── huawei │ │ ├── __init__.py │ │ ├── aaa.py │ │ ├── bgp.py │ │ ├── iface.py │ │ ├── misc.py │ │ └── vlandb.py │ ├── juniper │ │ ├── __init__.py │ │ └── iface.py │ ├── nexus │ │ ├── __init__.py │ │ └── iface.py │ ├── patching.py │ ├── routeros │ │ ├── __init__.py │ │ └── file.py │ └── texts │ │ ├── arista.deploy │ │ ├── arista.order │ │ ├── arista.rul │ │ ├── aruba.deploy │ │ ├── aruba.order │ │ ├── aruba.rul │ │ ├── b4com.deploy │ │ ├── b4com.order │ │ ├── b4com.rul │ │ ├── cisco.deploy │ │ ├── cisco.order │ │ ├── cisco.rul │ │ ├── huawei.deploy │ │ ├── huawei.order │ │ ├── huawei.rul │ │ ├── iosxr.deploy │ │ ├── iosxr.order │ │ ├── iosxr.rul │ │ ├── juniper.order │ │ ├── juniper.rul │ │ ├── nexus.deploy │ │ ├── nexus.order │ │ ├── nexus.rul │ │ ├── nokia.rul │ │ ├── optixtrans.deploy │ │ ├── optixtrans.order │ │ ├── optixtrans.rul │ │ ├── pc.deploy │ │ ├── pc.order │ │ ├── pc.rul │ │ ├── ribbon.deploy │ │ ├── ribbon.rul │ │ ├── routeros.order │ │ └── routeros.rul ├── storage.py ├── text_term_format.py ├── tracing.py ├── types.py └── vendors │ ├── __init__.py │ ├── base.py │ ├── library │ ├── __init__.py │ ├── arista.py │ ├── aruba.py │ ├── b4com.py │ ├── cisco.py │ ├── h3c.py │ ├── huawei.py │ ├── iosxr.py │ ├── juniper.py │ ├── nexus.py │ ├── nokia.py │ ├── optixtrans.py │ ├── pc.py │ ├── ribbon.py │ └── routeros.py │ ├── registry.py │ └── tabparser.py ├── annet_generators ├── __init__.py ├── example │ ├── __init__.py │ ├── hostname.py │ └── lldp.py ├── mesh_example │ ├── __init__.py │ ├── bgp.py │ └── mesh_logic.py └── rpl_example │ ├── __init__.py │ ├── generator.py │ ├── items.py │ ├── mesh.py │ └── route_policy.py ├── docs ├── _static │ ├── annet_demo.gif │ └── favicon.ico ├── _templates │ ├── gh-pages-redirect.html │ ├── page.html │ └── versioning.html ├── conf.py ├── contrib │ └── lab.rst ├── index.rst ├── mesh │ └── index.rst ├── rpl │ └── index.rst └── usage │ ├── acl.rst │ ├── adapters.rst │ ├── cli.rst │ ├── config.rst │ ├── gen.rst │ ├── install.rst │ └── tutorial.rst ├── mypy.ini ├── mypy_strict.ini ├── requirements-doc.txt ├── requirements-mypy.txt ├── requirements-test.txt ├── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── annet │ ├── __init__.py │ ├── conftest.py │ ├── patch_data.py │ ├── test_acl.py │ ├── test_acl │ │ ├── huawei_simple.yaml │ │ ├── huawei_simple_broken_block.yaml │ │ ├── juniper-inactive-match.yaml │ │ └── juniper-two-rules-with-global-children.yaml │ ├── test_api.py │ ├── test_argparse.py │ ├── test_bgp_models.py │ ├── test_cant_delete.py │ ├── test_deploying.py │ ├── test_diff.py │ ├── test_filter_acl.py │ ├── test_formatter.py │ ├── test_fs_storage.py │ ├── test_implicit.py │ ├── test_jsonfragments.py │ ├── test_lib.py │ ├── test_mesh │ │ ├── __init__.py │ │ ├── fakes.py │ │ ├── test_basemodel.py │ │ ├── test_executor.py │ │ ├── test_match_args.py │ │ └── test_registry.py │ ├── test_netbox.py │ ├── test_patch.py │ ├── test_patch │ │ ├── arista_acl.yaml │ │ ├── arista_load_sharing.yaml │ │ ├── arista_prefix_list.yaml │ │ ├── arista_username.yaml │ │ ├── aruba_ap_env.yaml │ │ ├── aruba_syslog.yaml │ │ ├── asr_policy_block_close.yaml │ │ ├── b4com_lldp.yaml │ │ ├── b4com_rpl.yaml │ │ ├── bfd_default.yaml │ │ ├── bfd_peer_param_change.yaml │ │ ├── cisco_bgp_address_family.yaml │ │ ├── cisco_bgp_peer_group.yaml │ │ ├── cisco_iface_ipv4_address.yaml │ │ ├── cisco_iface_ipv6_address.yaml │ │ ├── cisco_physical_iface_delete.yaml │ │ ├── cisco_policy_map.yaml │ │ ├── cisco_port_channel_members_config.yaml │ │ ├── h3_single.yaml │ │ ├── huawei_aaa_privilege_change.yaml │ │ ├── huawei_aaa_privilege_remove.yaml │ │ ├── huawei_aaa_scheme_domain.yaml │ │ ├── huawei_aaa_scheme_patch.yaml │ │ ├── huawei_aaa_tacacs_radius_remove.yaml │ │ ├── huawei_aaa_task.yaml │ │ ├── huawei_apply_cost.yaml │ │ ├── huawei_bfd_empty_block.yaml │ │ ├── huawei_bgp_family_undo.yaml │ │ ├── huawei_bgp_peers_group_merge.yaml │ │ ├── huawei_bgp_route_policy.yaml │ │ ├── huawei_bgp_route_policy2.yaml │ │ ├── huawei_classifier_type_change.yaml │ │ ├── huawei_goto_route_policy.yaml │ │ ├── huawei_iface_ip_vpn_binding.yaml │ │ ├── huawei_iface_isis.yaml │ │ ├── huawei_iface_loopback.yaml │ │ ├── huawei_iface_mpls_ldp_transport_addr.yaml │ │ ├── huawei_iface_route_policy.yaml │ │ ├── huawei_load_balancing.yaml │ │ ├── huawei_local_user_no_order_in_service_type.yaml │ │ ├── huawei_multi.yaml │ │ ├── huawei_multiline_add.yaml │ │ ├── huawei_multiline_modify.yaml │ │ ├── huawei_multiline_remove.yaml │ │ ├── huawei_ndcase.yaml │ │ ├── huawei_ne_cpu_defend.yaml │ │ ├── huawei_netconf.yaml │ │ ├── huawei_physical_iface_delete.yaml │ │ ├── huawei_plist_replace.yaml │ │ ├── huawei_port_queue.yaml │ │ ├── huawei_port_split.yaml │ │ ├── huawei_qos_group.yaml │ │ ├── huawei_rm_eth_trunk.yaml │ │ ├── huawei_single.yaml │ │ ├── huawei_srte_policy.yaml │ │ ├── huawei_undo_bgp.yaml │ │ ├── huawei_undo_ipv6_mtu.yaml │ │ ├── huawei_undo_ntp.yaml │ │ ├── huawei_undo_peer.yaml │ │ ├── huawei_undo_peer2.yaml │ │ ├── huawei_undo_peer_group_as_number.yaml │ │ ├── huawei_undo_vpn_instance.yaml │ │ ├── huawei_vlan_batch.yaml │ │ ├── huawei_vlan_global_and_batch.yaml │ │ ├── huawei_vlan_named.yaml │ │ ├── huawei_xpl.yaml │ │ ├── juniper_asnum_change.yaml │ │ ├── juniper_comments.yaml │ │ ├── juniper_del_forwarding_class.yaml │ │ ├── juniper_inactive.yaml │ │ ├── juniper_ipv6.yaml │ │ ├── juniper_local_as.yaml │ │ ├── juniper_mpls_path.yaml │ │ ├── juniper_quotes_ignore.yaml │ │ ├── juniper_syslog.yaml │ │ ├── juniper_vlan_tags_change.yaml │ │ ├── nexus_lag_member_add.yaml │ │ ├── nexus_lag_member_remove.yaml │ │ ├── nokia_single.yaml │ │ ├── pc_single.yaml │ │ ├── ribbon_single.yaml │ │ ├── routeros_localusers.yaml │ │ └── routeros_single.yaml │ ├── test_patching.py │ ├── test_pc_deploy │ │ ├── __init__.py │ │ ├── common.py │ │ ├── pc │ │ │ └── action.py │ │ ├── test_rulebook.py │ │ └── texts │ │ │ ├── pc.deploy │ │ │ ├── pc.order │ │ │ └── pc.rul │ ├── test_rpl │ │ ├── __init__.py │ │ ├── test_action.py │ │ ├── test_condition.py │ │ ├── test_match_builder.py │ │ ├── test_routemap.py │ │ └── test_statement_builder.py │ ├── test_rpl_generators │ │ ├── __init__.py │ │ ├── helpers.py │ │ ├── test_as_path.py │ │ ├── test_general_policy.py │ │ ├── test_prefixlist.py │ │ ├── test_rd.py │ │ └── test_set_community.py │ └── test_rulebooks │ │ └── test_rulebooks.py └── ci_indent.py └── tox.ini /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | on: 3 | workflow_dispatch: 4 | push: 5 | tags: 6 | - "v*" 7 | branches: 8 | - "main" 9 | 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | jobs: 16 | github_pages: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | - name: Install python 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: 3.x 27 | - name: Build docs 28 | run: | 29 | pip install -r requirements-doc.txt 30 | sphinx-multiversion docs docs-build 31 | cp docs/_templates/gh-pages-redirect.html docs-build/index.html 32 | - uses: actions/configure-pages@v5 33 | - uses: actions/upload-pages-artifact@v3 34 | with: 35 | path: 'docs-build' 36 | - uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.github/workflows/publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish Python wheel to pypi 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | name: Build distribution 📦 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - name: Extract version 13 | uses: kaisugi/action-regex-match@v1.0.1 14 | id: extract-version 15 | with: 16 | text: ${{ github.event.ref }} 17 | regex: ^refs/tags/v([0-9]+\.[0-9]+(\.[0-9]+)?)$ 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: "3.11" 23 | 24 | - name: Install pypa/build 25 | run: python3 -mpip install --user build 26 | 27 | - name: Build a binary wheel and a source tarball 28 | run: python3 -m build 29 | env: 30 | VERSION: "${{ steps.extract-version.outputs.group1 }}" 31 | 32 | - name: Store the distribution packages 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: python-package-distributions 36 | path: dist/ 37 | 38 | 39 | publish-to-pypi: 40 | name: Publish to pypi.org 41 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 42 | needs: [ build ] 43 | runs-on: ubuntu-latest 44 | 45 | environment: 46 | name: pypi 47 | url: https://pypi.org/p/annet 48 | 49 | permissions: 50 | id-token: write 51 | 52 | steps: 53 | - name: Download all the dists 54 | uses: actions/download-artifact@v4 55 | with: 56 | name: python-package-distributions 57 | path: dist/ 58 | 59 | - name: Publish distribution to PyPI 60 | uses: pypa/gh-action-pypi-publish@release/v1 61 | with: 62 | verify-metadata: false 63 | -------------------------------------------------------------------------------- /.github/workflows/setup.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: CI 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | cpython: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: 14 | - ubuntu-latest 15 | python-version: 16 | - "3.10" 17 | - "3.11" 18 | - "3.12" 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up ${{ matrix.python-version }} on ${{ matrix.os }} 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | pip install tox 31 | 32 | - name: Run tests 33 | run: | 34 | tox -e ci 35 | - name: Run mypy 36 | run: | 37 | tox -e mypy_ignore 38 | - name: Run mypy 39 | run: | 40 | tox -e mypy 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | /.coverage 3 | /.idea 4 | /.mypy_cache 5 | /.pytest_cache 6 | /.tox 7 | /.tox-logs 8 | /annet.egg-info 9 | /coverage.xml 10 | /build 11 | venv 12 | .venv 13 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | force_grid_wrap = 0 3 | include_trailing_comma = True 4 | line_length = 88 5 | lines_after_imports = 2 6 | multi_line_output = 3 7 | skip = .tox, .venv, venv 8 | use_parentheses = True 9 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-22.04 4 | tools: 5 | python: "3.10" 6 | python: 7 | install: 8 | - method: pip 9 | path: . 10 | - requirements: requirements-doc.txt 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The following authors have created the source code of "annet" published and distributed by YANDEX LLC as the owner: 2 | 3 | Aleksandr Balezin 4 | Aleksandr Klimenko 5 | Aleksei Lymar 6 | Alexey Andriyanov 7 | Alexey Esin 8 | Anton Egorov 9 | Artem Denisov 10 | Azat Kurbanov 11 | Devaev Maxim 12 | Fedor Zhukov 13 | Grigorii Ozhegov 14 | Grigorii Solovev 15 | Konstantin Sazonov 16 | Roman Glebov 17 | Roman Karaulanov 18 | Sergey Mishchenko 19 | Vlad Starostin 20 | Vladimir Sukhonosov 21 | Vladislav Daniliuk 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [2023] YANDEX LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | -------------------------------------------------------------------------------- /annet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import annet.annet 4 | 5 | annet.annet.main() 6 | -------------------------------------------------------------------------------- /annet/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.config 3 | import os 4 | import pkgutil 5 | import sys 6 | from argparse import SUPPRESS, Namespace 7 | 8 | import colorama 9 | import yaml 10 | from contextlog import patch_logging, patch_threading 11 | from valkit.python import valid_logging_level 12 | 13 | import annet.argparse 14 | from annet.vendors import tabparser # pylint: disable=unused-import 15 | from annet.annlib.errors import ( # pylint: disable=wrong-import-position 16 | DeployCancelled, 17 | ExecError, 18 | ) 19 | 20 | 21 | __all__ = ("DeployCancelled", "ExecError") 22 | 23 | DEBUG2_LEVELV_NUM = 9 24 | 25 | 26 | def fill_base_args(parser: annet.argparse.ArgParser, pkg_name: str, logging_config: str): 27 | parser.add_argument("--log-level", default="WARN", type=valid_logging_level, 28 | help="Уровень детализации логов (DEBUG, DEBUG2 (with comocutor debug), INFO, WARN, CRITICAL)") 29 | parser.add_argument("--pkg_name", default=pkg_name, help=SUPPRESS) 30 | parser.add_argument("--logging_config", default=logging_config, help=SUPPRESS) 31 | 32 | 33 | def init_logging(options: Namespace): 34 | patch_logging() 35 | patch_threading() 36 | logging.captureWarnings(True) 37 | logging_config = yaml.safe_load(pkgutil.get_data(options.pkg_name, options.logging_config)) 38 | if options.log_level is not None: 39 | logging_config.setdefault("root", {}) 40 | logging_config["root"]["level"] = options.log_level 41 | logging.addLevelName(DEBUG2_LEVELV_NUM, "DEBUG2") 42 | logging.config.dictConfig(logging_config) 43 | 44 | 45 | def init(options: Namespace): 46 | init_logging(options) 47 | 48 | # Отключить colorama.init, если стоит env-переменная. Нужно в тестах 49 | if os.environ.get("ANN_FORCE_COLOR", None) not in [None, "", "0", "no"]: 50 | colorama.init = lambda *_, **__: None 51 | colorama.init() 52 | 53 | 54 | def assert_python_version(): 55 | if sys.version_info < (3, 10, 0): 56 | sys.stderr.write("Error: you need python 3.10.0 or higher\n") 57 | sys.exit(1) 58 | -------------------------------------------------------------------------------- /annet/adapters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/__init__.py -------------------------------------------------------------------------------- /annet/adapters/deployers/stub/deployer.py: -------------------------------------------------------------------------------- 1 | from annet.deploy import DeployDriver, DeployOptions, DeployResult, ProgressBar 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | 4 | 5 | class StubDeployDriver(DeployDriver): 6 | async def bulk_deploy(self, deploy_cmds: dict, args: DeployOptions, progress_bar: ProgressBar | None = None) -> DeployResult: 7 | raise NotImplementedError() 8 | 9 | def apply_deploy_rulebook(self, hw: HardwareView, cmd_paths, do_finalize=True, do_commit=True): 10 | raise NotImplementedError() 11 | 12 | def build_configuration_cmdlist(self, hw: HardwareView, do_finalize=True, do_commit=True): 13 | raise NotImplementedError() 14 | 15 | def build_exit_cmdlist(self, hw): 16 | raise NotImplementedError() 17 | -------------------------------------------------------------------------------- /annet/adapters/fetchers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/fetchers/__init__.py -------------------------------------------------------------------------------- /annet/adapters/fetchers/stub/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/fetchers/stub/__init__.py -------------------------------------------------------------------------------- /annet/adapters/fetchers/stub/fetcher.py: -------------------------------------------------------------------------------- 1 | from annet.deploy import Fetcher 2 | from annet.connectors import AdapterWithConfig 3 | from typing import Dict, List, Any 4 | from annet.storage import Device 5 | 6 | 7 | class StubFetcher(Fetcher, AdapterWithConfig): 8 | @classmethod 9 | def with_config(cls, **kwargs: Dict[str, Any]) -> Fetcher: 10 | return cls(**kwargs) 11 | 12 | async def fetch_packages(self, 13 | devices: list[Device], 14 | processes: int = 1, 15 | max_slots: int = 0, 16 | ) -> tuple[dict[Device, str], dict[Device, Any]]: 17 | raise NotImplementedError() 18 | 19 | async def fetch(self, 20 | devices: list[Device], 21 | files_to_download: dict[str, list[str]] | None = None, 22 | processes: int = 1, 23 | max_slots: int = 0, 24 | ): 25 | raise NotImplementedError() 26 | -------------------------------------------------------------------------------- /annet/adapters/file/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/file/__init__.py -------------------------------------------------------------------------------- /annet/adapters/netbox/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/netbox/__init__.py -------------------------------------------------------------------------------- /annet/adapters/netbox/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/netbox/common/__init__.py -------------------------------------------------------------------------------- /annet/adapters/netbox/common/manufacturer.py: -------------------------------------------------------------------------------- 1 | from logging import getLogger 2 | 3 | from annet.annlib.netdev.views.hardware import HardwareView 4 | 5 | logger = getLogger(__name__) 6 | 7 | 8 | def get_hw(manufacturer: str, model: str, platform_name: str): 9 | # By some reason Netbox calls Mellanox SN as MSN, so we fix them here 10 | if manufacturer == "Mellanox" and model.startswith("MSN"): 11 | model = model.replace("MSN", "SN", 1) 12 | 13 | return HardwareView(manufacturer + " " + model, platform_name) 14 | 15 | 16 | def get_breed(manufacturer: str, model: str): 17 | hw = get_hw(manufacturer, model, "") 18 | if hw.Huawei.CE: 19 | return "vrp85" 20 | elif hw.Huawei.NE: 21 | return "vrp85" 22 | elif hw.Huawei: 23 | return "vrp55" 24 | elif hw.H3C: 25 | return "h3c" 26 | elif hw.PC.NVIDIA or hw.PC.Mellanox: 27 | return "cuml2" 28 | elif hw.Juniper: 29 | return "jun10" 30 | elif hw.Cisco.Nexus: 31 | return "nxos" 32 | elif hw.Cisco: 33 | return "ios12" 34 | elif hw.Arista: 35 | return "eos4" 36 | elif hw.B4com: 37 | return "bcom-os" 38 | elif hw.RouterOS: 39 | return "routeros" 40 | elif hw.PC.Moxa or hw.PC.Nebius: 41 | return "moxa" 42 | elif hw.PC: 43 | return "pc" 44 | return "" 45 | -------------------------------------------------------------------------------- /annet/adapters/netbox/common/query.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from dataclasses import dataclass 3 | from typing import cast, List, Union, Iterable, Optional, TypedDict 4 | 5 | from annet.storage import Query 6 | 7 | FIELD_VALUE_SEPARATOR = ":" 8 | ALLOWED_GLOB_GROUPS = ["site", "tag", "role", "device_type", "status", "tenant"] 9 | 10 | 11 | class Filter(TypedDict, total=False): 12 | site: list[str] 13 | tag: list[str] 14 | role: list[str] 15 | name: list[str] 16 | device_type: list[str] 17 | status: list[str] 18 | tenant: list[str] 19 | 20 | 21 | @dataclass 22 | class NetboxQuery(Query): 23 | query: List[str] 24 | 25 | @classmethod 26 | def new( 27 | cls, query: Union[str, Iterable[str]], 28 | hosts_range: Optional[slice] = None, 29 | ) -> "NetboxQuery": 30 | if hosts_range is not None: 31 | raise ValueError("host_range is not supported") 32 | return cls(query=list(query)) 33 | 34 | @property 35 | def globs(self): 36 | # We process every query host as a glob 37 | return self.query 38 | 39 | def parse_query(self) -> Filter: 40 | query_groups = defaultdict(list) 41 | for q in self.globs: 42 | if FIELD_VALUE_SEPARATOR in q: 43 | glob_type, param = q.split(FIELD_VALUE_SEPARATOR, 2) 44 | if glob_type not in ALLOWED_GLOB_GROUPS: 45 | raise Exception(f"unknown query type: '{glob_type}'") 46 | if not param: 47 | raise Exception(f"empty param for '{glob_type}'") 48 | query_groups[glob_type].append(param) 49 | else: 50 | query_groups["name"].append(q) 51 | 52 | query_groups.default_factory = None 53 | return cast(Filter, query_groups) 54 | 55 | def is_empty(self) -> bool: 56 | return len(self.query) == 0 57 | 58 | def is_host_query(self) -> bool: 59 | if not self.globs: 60 | return False 61 | for q in self.globs: 62 | if FIELD_VALUE_SEPARATOR in q: 63 | return False 64 | return True 65 | -------------------------------------------------------------------------------- /annet/adapters/netbox/common/status_client.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Dict 3 | import re 4 | 5 | from adaptix import Retort, name_mapping, NameStyle 6 | from dataclass_rest import get 7 | from dataclass_rest.client_protocol import FactoryProtocol 8 | 9 | from .client import BaseNetboxClient 10 | 11 | 12 | @dataclass 13 | class Status: 14 | netbox_version: str 15 | plugins: Dict[str, str] 16 | 17 | @property 18 | def minor_version(self) -> str: 19 | if match := re.match(r"\d+\.\d+", self.netbox_version): 20 | return match.group(0) 21 | return "" 22 | 23 | 24 | class NetboxStatusClient(BaseNetboxClient): 25 | def _init_response_body_factory(self) -> FactoryProtocol: 26 | return Retort(recipe=[ 27 | name_mapping(name_style=NameStyle.LOWER_KEBAB) 28 | ]) 29 | 30 | @get("status/") 31 | def status(self) -> Status: 32 | ... 33 | -------------------------------------------------------------------------------- /annet/adapters/netbox/common/storage_opts.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any, Optional 3 | 4 | DEFAULT_URL = "http://localhost" 5 | 6 | 7 | class NetboxStorageOpts: 8 | def __init__( 9 | self, 10 | url: str, 11 | token: str, 12 | insecure: bool = False, 13 | exact_host_filter: bool = False, 14 | threads: int = 1, 15 | ): 16 | self.url = url 17 | self.token = token 18 | self.insecure = insecure 19 | self.exact_host_filter = exact_host_filter 20 | self.threads = threads 21 | 22 | @classmethod 23 | def parse_params(cls, conf_params: Optional[dict[str, str]], cli_opts: Any): 24 | url = os.getenv("NETBOX_URL") or conf_params.get("url") or DEFAULT_URL 25 | token = os.getenv("NETBOX_TOKEN", "").strip() or conf_params.get("token") or "" 26 | threads = os.getenv("NETBOX_CLIENT_THREADS", "").strip() or conf_params.get("threads") or "1" 27 | insecure = False 28 | if insecure_env := os.getenv("NETBOX_INSECURE", "").lower(): 29 | insecure = insecure_env in ("true", "1", "t") 30 | else: 31 | insecure = bool(conf_params.get("insecure") or False) 32 | if exact_host_filter_env := os.getenv("NETBOX_EXACT_HOST_FILTER", "").lower(): 33 | exact_host_filter = exact_host_filter_env in ("true", "1", "t") 34 | else: 35 | exact_host_filter = bool(conf_params.get("exact_host_filter") or False) 36 | return cls( 37 | url=url, 38 | token=token, 39 | insecure=insecure, 40 | exact_host_filter=exact_host_filter, 41 | threads=int(threads), 42 | ) 43 | -------------------------------------------------------------------------------- /annet/adapters/netbox/v24/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/netbox/v24/__init__.py -------------------------------------------------------------------------------- /annet/adapters/netbox/v37/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/netbox/v37/__init__.py -------------------------------------------------------------------------------- /annet/adapters/netbox/v37/models.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime, timezone 3 | from typing import Optional 4 | 5 | from annet.adapters.netbox.common.models import ( 6 | IpAddress, NetboxDevice, Entity, Prefix, InterfaceType, Interface, 7 | IpFamily, Label, FHRPGroupAssignment, DeviceIp, FHRPGroup, 8 | ) 9 | 10 | 11 | @dataclass 12 | class DeviceIpV37(DeviceIp): 13 | family: int 14 | 15 | 16 | @dataclass 17 | class PrefixV37(Prefix): 18 | site: Optional[Entity] = None 19 | 20 | 21 | @dataclass 22 | class IpAddressV37(IpAddress[PrefixV37]): 23 | prefix: Optional[PrefixV37] = None 24 | 25 | 26 | @dataclass 27 | class FHRPGroupV37(FHRPGroup[DeviceIpV37]): 28 | pass 29 | 30 | 31 | @dataclass 32 | class FHRPGroupAssignmentV37(FHRPGroupAssignment[FHRPGroupV37]): 33 | pass 34 | 35 | 36 | @dataclass 37 | class InterfaceV37(Interface[IpAddressV37, FHRPGroupAssignmentV37]): 38 | def _add_new_addr(self, address_mask: str, vrf: Entity | None, family: IpFamily) -> None: 39 | self.ip_addresses.append(IpAddressV37( 40 | id=0, 41 | display=address_mask, 42 | address=address_mask, 43 | vrf=vrf, 44 | prefix=None, 45 | family=family, 46 | created=datetime.now(timezone.utc), 47 | last_updated=datetime.now(timezone.utc), 48 | tags=[], 49 | status=Label(value="active", label="Active"), 50 | assigned_object_id=self.id, 51 | )) 52 | 53 | 54 | @dataclass 55 | class NetboxDeviceV37(NetboxDevice[InterfaceV37, DeviceIpV37]): 56 | device_role: Entity 57 | 58 | def __hash__(self): 59 | return hash((self.id, type(self))) 60 | 61 | def _make_interface(self, name: str, type: InterfaceType) -> InterfaceV37: 62 | return InterfaceV37( 63 | name=name, 64 | device=self, 65 | enabled=True, 66 | description="", 67 | type=type, 68 | id=0, 69 | vrf=None, 70 | display=name, 71 | untagged_vlan=None, 72 | tagged_vlans=[], 73 | ip_addresses=[], 74 | connected_endpoints=[], 75 | mode=None, 76 | ) 77 | -------------------------------------------------------------------------------- /annet/adapters/netbox/v41/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/netbox/v41/__init__.py -------------------------------------------------------------------------------- /annet/adapters/netbox/v42/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/adapters/netbox/v42/__init__.py -------------------------------------------------------------------------------- /annet/annet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import logging 3 | import sys 4 | 5 | import annet 6 | from annet import argparse, cli, generators, hardware, lib, rulebook, diff 7 | 8 | 9 | # ===== 10 | @lib.catch_ctrl_c 11 | def main(): 12 | annet.assert_python_version() 13 | parser = argparse.ArgParser() 14 | cli.fill_base_args(parser, annet.__name__, "configs/logging.yaml") 15 | rulebook.rulebook_provider_connector.set(rulebook.DefaultRulebookProvider) 16 | hardware.hardware_connector.set(hardware.AnnetHardwareProvider) 17 | diff.file_differ_connector.set(diff.UnifiedFileDiffer) 18 | 19 | parser.add_commands(parser.find_subcommands(cli.list_subcommands())) 20 | try: 21 | return parser.dispatch(pre_call=annet.init, add_help_command=True) 22 | except (generators.GeneratorError, annet.ExecError) as e: 23 | logging.error(e) 24 | return 1 25 | except BaseException as e: 26 | if formatted_output := getattr(e, "formatted_output", None): 27 | logging.error(formatted_output) 28 | return 1 29 | raise e 30 | 31 | 32 | if __name__ == "__main__": 33 | sys.exit(main()) 34 | -------------------------------------------------------------------------------- /annet/annlib/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import colorama 4 | 5 | # отключить colorama.init, если стоит env-переменная. Нужно в тестах 6 | if os.environ.get("ANN_FORCE_COLOR", None) not in [None, "", "0", "no"]: 7 | colorama.init = lambda *_, **__: None 8 | -------------------------------------------------------------------------------- /annet/annlib/command.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from typing import List, Optional 4 | 5 | 6 | FIRST_EXCEPTION = 1 7 | ALL_COMPLETED = 2 8 | 9 | 10 | @dataclass 11 | class Question: 12 | question: str # frame it using / if it is a regular expression 13 | answer: str 14 | is_regexp: Optional[bool] = False 15 | not_send_nl: bool = False 16 | 17 | 18 | @dataclass 19 | class Command: 20 | cmd: str | bytes 21 | questions: Optional[List[Question]] = None 22 | exc_handler: Optional[List[Question]] = None 23 | timeout: Optional[int] = None # total timeout 24 | read_timeout: Optional[int] = None # timeout between consecutive reads 25 | suppress_nonzero: bool = False 26 | suppress_eof: bool = False 27 | 28 | def __str__(self) -> str: 29 | if isinstance(self.cmd, bytes): 30 | return self.cmd.decode("utf-8") 31 | return self.cmd 32 | 33 | 34 | @dataclass 35 | class CommandList: 36 | cmss: List[Command] = field(default_factory=list) 37 | 38 | def __post_init__(self): 39 | if not self.cmss: 40 | self.cmss = [] 41 | 42 | def __iter__(self): 43 | return iter(self.cmss) 44 | 45 | def __len__(self) -> int: 46 | return len(self.cmss) 47 | 48 | def add_cmd(self, cmd: Command) -> None: 49 | assert isinstance(cmd, Command) 50 | self.cmss.append(cmd) 51 | 52 | def as_list(self) -> List[Command]: # TODO: delete 53 | return self.cmss 54 | -------------------------------------------------------------------------------- /annet/annlib/errors.py: -------------------------------------------------------------------------------- 1 | class ExecError(Exception): 2 | """Обработчик этого exception должен залоггировать ошибку и выйти с exit_code 1""" 3 | pass 4 | 5 | 6 | class DeployCancelled(Exception): 7 | """Деплой на устройство был отменен""" 8 | pass 9 | -------------------------------------------------------------------------------- /annet/annlib/netdev/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/annlib/netdev/__init__.py -------------------------------------------------------------------------------- /annet/annlib/netdev/db.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import functools 3 | 4 | 5 | def get_db(prepared): 6 | allowed = _make_allowed_by_seq(prepared) 7 | return ( 8 | _build_tree(prepared, allowed), 9 | functools.reduce(set.union, allowed.values()), # All sequences 10 | ) 11 | 12 | 13 | def find_true_sequences(hw_model, tree): 14 | sequences = set() 15 | for (regexp, meta) in tree.items(): 16 | if regexp.search(hw_model): 17 | sequences.update(meta["sequences"]) 18 | sequences.update(find_true_sequences(hw_model, meta["children"])) 19 | return sequences 20 | 21 | 22 | def _build_tree(prepared, allowed_by_seq): 23 | tree = {} 24 | for (seq, regexp) in prepared.items(): 25 | sub = tree 26 | for sub_seq in _seq_subs(seq): 27 | regexp = prepared[sub_seq] 28 | if regexp not in sub: 29 | sub[regexp] = { 30 | "sequences": allowed_by_seq[sub_seq], 31 | "children": {}, 32 | } 33 | sub = sub[regexp]["children"] 34 | return tree 35 | 36 | 37 | def _seq_subs(seq): 38 | for index in range(1, len(seq) + 1): 39 | yield seq[:index] 40 | 41 | 42 | def _make_allowed_by_seq(sequences): 43 | all_variants = collections.Counter() 44 | variants_by_seq = {} 45 | 46 | for seq in sequences: 47 | variants = _make_seq_variants(seq) 48 | all_variants.update(variants) 49 | variants_by_seq[seq] = variants 50 | 51 | return { 52 | seq: set(variant for variant in variants if all_variants[variant] <= 1) 53 | for (seq, variants) in variants_by_seq.items() 54 | } 55 | 56 | 57 | def _make_seq_variants(seq): 58 | return set( 59 | seq[left:-right] + (seq[-1],) 60 | for left in range(len(seq)) 61 | for right in range(1, len(seq[left:]) + 1) 62 | ) 63 | -------------------------------------------------------------------------------- /annet/annlib/netdev/devdb/__init__.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import json 3 | import re 4 | from os import path 5 | from typing import Any, Dict 6 | 7 | from annet.annlib.netdev.db import find_true_sequences, get_db 8 | 9 | 10 | @functools.lru_cache(None) 11 | def parse_hw_model(hw_model): 12 | prepared = _prepare_db() 13 | (tree, all_sequences) = get_db(prepared) 14 | true_sequences = find_true_sequences(hw_model, tree) 15 | return ( 16 | sorted(true_sequences), 17 | all_sequences.difference(true_sequences), 18 | ) 19 | 20 | 21 | def _prepare_db() -> Dict[str, Any]: 22 | try: 23 | from library.python import resource 24 | raw = json.loads(resource.resfs_read("contrib/python/annet/annet/annlib/netdev/devdb/data/devdb.json").decode("utf-8")) 25 | except ImportError: 26 | with open(path.join(path.dirname(__file__), "data", "devdb.json"), "r") as f: 27 | raw = json.load(f) 28 | return {tuple(seq.split(".")): re.compile(regexp) for (seq, regexp) in raw.items()} 29 | -------------------------------------------------------------------------------- /annet/annlib/netdev/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/annlib/netdev/views/__init__.py -------------------------------------------------------------------------------- /annet/annlib/rbparser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/annlib/rbparser/__init__.py -------------------------------------------------------------------------------- /annet/annlib/rbparser/deploying.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import re 3 | from collections import OrderedDict as odict 4 | from collections import namedtuple 5 | 6 | Answer = namedtuple("Answer", ("text", "send_nl")) 7 | 8 | 9 | def compile_messages(tree): 10 | ignore = [] 11 | dialogs = odict() 12 | for attrs in tree.values(): 13 | if attrs["type"] == "normal": 14 | match = re.match(r"^ignore:(.+)$", attrs["row"]) 15 | if match: 16 | ignore.append(MakeMessageMatcher(match.group(1))) 17 | continue 18 | 19 | match = re.match(r"^dialog:(.+):::(.+)$", attrs["row"]) 20 | if match: 21 | dialogs[MakeMessageMatcher(match.group(1))] = Answer( 22 | text=match.group(2).strip(), 23 | send_nl=attrs["params"]["send_nl"], 24 | ) 25 | return (ignore, dialogs) 26 | 27 | 28 | class MakeMessageMatcher: 29 | def __init__(self, text): 30 | text = text.strip() 31 | self._text = text 32 | if text.startswith("/") and text.endswith("/"): 33 | regexp = re.compile(text[1:-1].strip(), flags=re.I) 34 | self._fn = regexp.match 35 | else: 36 | self._fn = (lambda arg: _simplify_text(text) in _simplify_text(arg)) 37 | 38 | def __str__(self): 39 | return "%s(%r)" % (self.__class__.__name__, self._text) 40 | 41 | __repr__ = __str__ 42 | 43 | def __call__(self, intext): 44 | return self._fn(intext) 45 | 46 | def __eq__(self, other): 47 | return type(other) is type(self) and self._text == other._text # pylint: disable=protected-access 48 | 49 | def __hash__(self): 50 | return hash("%s_%s" % (self.__class__.__name__, self._text)) 51 | 52 | 53 | @functools.lru_cache() 54 | def _simplify_text(text): 55 | return re.sub(r"\s", "", text).lower() 56 | -------------------------------------------------------------------------------- /annet/annlib/rbparser/platform.py: -------------------------------------------------------------------------------- 1 | VENDOR_ALIASES = { 2 | "h3c": "huawei", 3 | } 4 | -------------------------------------------------------------------------------- /annet/annlib/rulebook/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/annlib/rulebook/__init__.py -------------------------------------------------------------------------------- /annet/annlib/types.py: -------------------------------------------------------------------------------- 1 | import enum 2 | from typing import List, Tuple 3 | 4 | Diff = List[Tuple] 5 | 6 | 7 | # Операции отмечающие роль команды в дифе 8 | # XXX надо отдельно переделать на enum 9 | class Op: 10 | ADDED = "added" 11 | REMOVED = "removed" 12 | AFFECTED = "affected" 13 | MOVED = "moved" 14 | UNCHANGED = "unchanged" 15 | 16 | 17 | class GeneratorType(enum.Enum): 18 | PARTIAL = "partial" 19 | ENTIRE = "entire" 20 | JSON_FRAGMENT = "json_fragment" 21 | 22 | @staticmethod 23 | def fromstring(value: str) -> "GeneratorType": 24 | return GeneratorType(value) 25 | 26 | def tostring(self) -> str: 27 | return self.value 28 | 29 | def __lt__(self, other: "GeneratorType") -> bool: 30 | return self.value < other.value 31 | 32 | def __le__(self, other: "GeneratorType") -> bool: 33 | if self != other: 34 | return self.value < other.value 35 | return True 36 | -------------------------------------------------------------------------------- /annet/configs/context.yml: -------------------------------------------------------------------------------- 1 | generators: 2 | default: 3 | - annet_generators.example 4 | - annet_generators.mesh_example 5 | - annet_generators.rpl_example 6 | 7 | storage: 8 | default: 9 | adapter: netbox 10 | 11 | context: 12 | default: 13 | generators: default 14 | storage: default 15 | 16 | selected_context: default 17 | -------------------------------------------------------------------------------- /annet/configs/logging.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | disable_existing_loggers: false 3 | 4 | formatters: 5 | standard: 6 | (): contextlog.SmartFormatter 7 | style: "{" 8 | datefmt: "%H:%M:%S" 9 | format: "[{asctime}] {log_color}{levelname:>7}{reset} {processName} - {pathname}:{lineno} - {message} -- {cyan}{_extra}{reset}" 10 | max_vars_lines: 20 11 | host_progress: 12 | (): contextlog.SmartFormatter 13 | style: "{" 14 | datefmt: "%H:%M:%S" 15 | format: "[{asctime}] {cyan}[{perc:>3}%]{reset} [{status_color}{bold}{status:^6}{reset}]: {status_color}{fqdn}{reset} - {message} -- {cyan}{_extra}{reset}" 16 | 17 | 18 | handlers: 19 | console: 20 | level: DEBUG 21 | class: logging.StreamHandler 22 | formatter: standard 23 | progress: 24 | level: DEBUG 25 | class: logging.StreamHandler 26 | formatter: host_progress 27 | 28 | root: 29 | level: INFO 30 | handlers: 31 | - console 32 | 33 | loggers: 34 | progress: 35 | level: INFO 36 | propagate: False 37 | handlers: 38 | - progress 39 | rtapi: 40 | level: INFO 41 | -------------------------------------------------------------------------------- /annet/filtering.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Type 3 | 4 | from annet.connectors import Connector 5 | 6 | 7 | class _FiltererConnector(Connector["Filterer"]): 8 | name = "Filterer" 9 | ep_name = "filterer" 10 | ep_by_group_only = "annet.connectors.filterer" 11 | 12 | def _get_default(self) -> Type["Filterer"]: 13 | return NopFilterer 14 | 15 | 16 | filterer_connector = _FiltererConnector() 17 | 18 | 19 | class Filterer(abc.ABC): 20 | @abc.abstractmethod 21 | def for_ifaces(self, device, ifnames) -> str: 22 | pass 23 | 24 | @abc.abstractmethod 25 | def for_peers(self, device, peers_allowed) -> str: 26 | pass 27 | 28 | @abc.abstractmethod 29 | def for_policies(self, device, policies_allowed) -> str: 30 | pass 31 | 32 | 33 | class NopFilterer(Filterer): 34 | def for_ifaces(self, device, ifnames) -> str: 35 | return "" 36 | 37 | def for_peers(self, device, peers_allowed) -> str: 38 | return "" 39 | 40 | def for_policies(self, device, policies_allowed) -> str: 41 | return "" 42 | -------------------------------------------------------------------------------- /annet/generators/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/generators/common/__init__.py -------------------------------------------------------------------------------- /annet/generators/common/initial.py: -------------------------------------------------------------------------------- 1 | from annet.generators import PartialGenerator 2 | 3 | 4 | class InitialConfig(PartialGenerator): 5 | 6 | """ 7 | Конфиги у свежих (еще ни разу не настраиваемых устройств) 8 | на самом деле НЕ пустые. В данном генераторе отображен 9 | такой набор команд, по крайней мере тех, которые могут 10 | изменяться в ходе первичной конфигурации. 11 | 12 | Acl для данного генератора не нужен, он будет генерировать 13 | конфиг целиком. 14 | """ 15 | def __init__(self, storage, do_run: bool = False): 16 | super().__init__(storage=storage) 17 | self._do_run = do_run 18 | 19 | def run_huawei(self, device): 20 | if not self._do_run: 21 | return 22 | if device.hw.CE: 23 | yield """ 24 | telnet server disable 25 | telnet ipv6 server disable 26 | diffserv domain default 27 | aaa 28 | authentication-scheme default 29 | authorization-scheme default 30 | accounting-scheme default 31 | domain default 32 | domain default_admin 33 | """ 34 | -------------------------------------------------------------------------------- /annet/generators/exceptions.py: -------------------------------------------------------------------------------- 1 | class GeneratorError(Exception): 2 | pass 3 | 4 | 5 | class InvalidValueFromGenerator(ValueError): 6 | pass 7 | 8 | 9 | class NotSupportedDevice(GeneratorError): 10 | pass 11 | -------------------------------------------------------------------------------- /annet/generators/ref.py: -------------------------------------------------------------------------------- 1 | from .partial import PartialGenerator 2 | 3 | 4 | class RefGenerator(PartialGenerator): 5 | def __init__(self, storage, groups=None): 6 | super().__init__(storage) 7 | self.groups = groups 8 | 9 | def ref(self, device): 10 | if hasattr(self, "ref_" + device.hw.vendor): 11 | return getattr(self, "ref_" + device.hw.vendor)(device) 12 | return "" 13 | 14 | def with_groups(self, groups): 15 | return type(self)(self.storage, groups) 16 | -------------------------------------------------------------------------------- /annet/hardware.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Any 3 | 4 | from annet.annlib.netdev.views.hardware import HardwareView 5 | from annet.vendors import registry_connector 6 | from annet.connectors import Connector 7 | 8 | 9 | class _HardwareConnector(Connector["HardwareProvider"]): 10 | name = "Hardware" 11 | ep_name = "hardware" 12 | ep_by_group_only = "annet.connectors.hardware" 13 | 14 | 15 | hardware_connector = _HardwareConnector() 16 | 17 | 18 | class HardwareProvider(abc.ABC): 19 | @abc.abstractmethod 20 | def make_hw(self, hw_model: str, sw_version: str) -> Any: 21 | pass 22 | 23 | @abc.abstractmethod 24 | def vendor_to_hw(self, vendor: str) -> Any: 25 | pass 26 | 27 | @abc.abstractmethod 28 | def hw_to_vendor(self, hw: Any) -> str | None: 29 | pass 30 | 31 | 32 | class AnnetHardwareProvider(HardwareProvider): 33 | def make_hw(self, hw_model: str, sw_version: str) -> HardwareView: 34 | return HardwareView(hw_model, sw_version) 35 | 36 | def vendor_to_hw(self, vendor: str) -> HardwareView: 37 | return registry_connector.get().get(vendor.lower()).hardware 38 | 39 | def hw_to_vendor(self, hw: HardwareView) -> str | None: 40 | if vendor := registry_connector.get().match(hw, None): 41 | return vendor.NAME 42 | return None 43 | -------------------------------------------------------------------------------- /annet/mesh/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "GlobalOptions", 3 | "MeshSession", 4 | "DirectPeer", 5 | "IndirectPeer", 6 | "MeshExecutor", 7 | "MeshRulesRegistry", 8 | "Left", 9 | "Right", 10 | "Match", 11 | "VirtualLocal", 12 | "VirtualPeer", 13 | "PortProcessor", 14 | "separate_ports", 15 | "united_ports" 16 | ] 17 | 18 | from .executor import MeshExecutor 19 | from .match_args import Left, Right, Match 20 | from .registry import ( 21 | DirectPeer, IndirectPeer, MeshSession, GlobalOptions, MeshRulesRegistry, VirtualLocal, VirtualPeer, 22 | ) 23 | from .port_processor import PortProcessor, united_ports, separate_ports 24 | -------------------------------------------------------------------------------- /annet/mesh/port_processor.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from typing import Protocol, Sequence 3 | 4 | PortPair = tuple[str, str] 5 | 6 | 7 | class PortProcessor(Protocol): 8 | @abstractmethod 9 | def __call__(self, pairs: Sequence[PortPair]) -> Sequence[list[PortPair]]: 10 | raise NotImplementedError 11 | 12 | 13 | def united_ports(pairs: Sequence[PortPair]) -> Sequence[list[PortPair]]: 14 | return [list(pairs)] 15 | 16 | 17 | def separate_ports(pairs: Sequence[PortPair]) -> Sequence[list[PortPair]]: 18 | return [[pair] for pair in pairs] 19 | -------------------------------------------------------------------------------- /annet/patching.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.patching import ( # pylint: disable=unused-import 2 | AclError, 3 | AclNotExclusiveError, 4 | ) 5 | from annet.annlib.patching import Orderer as BaseOrderer 6 | from annet.annlib.patching import ( # pylint: disable=unused-import 7 | PatchTree, 8 | apply_acl, 9 | apply_diff_rb, 10 | make_diff, 11 | make_patch, 12 | make_pre, 13 | strip_unchanged, 14 | ) 15 | 16 | from annet import rulebook 17 | 18 | 19 | class Orderer(BaseOrderer): 20 | @classmethod 21 | def from_hw(cls, hw): 22 | return cls( 23 | rulebook.get_rulebook(hw)["ordering"], 24 | hw.vendor, 25 | ) 26 | -------------------------------------------------------------------------------- /annet/rpl/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "MatchField", 3 | "ThenField", 4 | "RouteMap", 5 | "Route", 6 | "ResultType", 7 | "ActionType", 8 | "Action", 9 | "SingleAction", 10 | "AndCondition", 11 | "R", 12 | "ConditionOperator", 13 | "Condition", 14 | "SingleCondition", 15 | "RoutingPolicyStatement", 16 | "RoutingPolicy", 17 | "CommunityActionValue", 18 | "PrefixMatchValue", 19 | "OrLonger", 20 | ] 21 | 22 | from .action import Action, ActionType, SingleAction 23 | from .condition import AndCondition, Condition, ConditionOperator, SingleCondition 24 | from .match_builder import R, MatchField, PrefixMatchValue, OrLonger 25 | from .policy import RoutingPolicyStatement, RoutingPolicy 26 | from .result import ResultType 27 | from .routemap import RouteMap, Route 28 | from .statement_builder import ThenField, CommunityActionValue 29 | -------------------------------------------------------------------------------- /annet/rpl/action.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterator, Iterable 2 | from dataclasses import dataclass 3 | from enum import Enum 4 | from typing import Generic, TypeVar, Any 5 | 6 | 7 | class ActionType(Enum): 8 | SET = "set" 9 | ADD = "add" 10 | REMOVE = "delete" 11 | 12 | CUSTOM = "custom" 13 | 14 | 15 | ValueT = TypeVar("ValueT") 16 | 17 | 18 | @dataclass 19 | class SingleAction(Generic[ValueT]): 20 | field: str 21 | type: ActionType 22 | value: ValueT 23 | 24 | 25 | class Action: 26 | def __init__(self) -> None: 27 | self.actions: list[SingleAction[Any]] = [] 28 | 29 | def append(self, action: SingleAction[Any]) -> None: 30 | self.actions.append(action) 31 | 32 | def __repr__(self): 33 | actions = ", ".join(repr(c) for c in self.actions) 34 | return f"Action({actions})" 35 | 36 | def __getitem__(self, item: str) -> SingleAction[Any]: 37 | return next(c for c in self.actions if c.field == item) 38 | 39 | def __contains__(self, item: str) -> bool: 40 | return any(c.field == item for c in self.actions) 41 | 42 | def __len__(self) -> int: 43 | return len(self.actions) 44 | 45 | def __iter__(self) -> Iterator[SingleAction[Any]]: 46 | return iter(self.actions) 47 | 48 | def find_all(self, item: str) -> Iterable[SingleAction[Any]]: 49 | for action in self.actions: 50 | if action.field == item: 51 | yield action 52 | -------------------------------------------------------------------------------- /annet/rpl/policy.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Sequence 2 | from dataclasses import dataclass 3 | from typing import Optional 4 | 5 | from .action import Action 6 | from .condition import AndCondition 7 | from .result import ResultType 8 | 9 | 10 | @dataclass 11 | class RoutingPolicyStatement: 12 | name: Optional[str] 13 | number: Optional[int] 14 | match: AndCondition 15 | then: Action 16 | result: ResultType 17 | 18 | 19 | @dataclass 20 | class RoutingPolicy: 21 | name: str 22 | statements: Sequence[RoutingPolicyStatement] 23 | -------------------------------------------------------------------------------- /annet/rpl/result.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ResultType(str, Enum): 5 | ALLOW = "allow" 6 | DENY = "deny" 7 | NEXT = "next" 8 | NEXT_POLICY = "next_policy" 9 | -------------------------------------------------------------------------------- /annet/rpl_generators/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "AsPathFilter", 3 | "AsPathFilterGenerator", 4 | "CommunityList", 5 | "CommunityType", 6 | "CommunityLogic", 7 | "CommunityListGenerator", 8 | "CumulusPolicyGenerator", 9 | "RoutingPolicyGenerator", 10 | "RDFilterFilterGenerator", 11 | "RDFilter", 12 | "IpPrefixList", 13 | "IpPrefixListMember", 14 | "PrefixListFilterGenerator", 15 | "get_policies", 16 | "ip_prefix_list", 17 | ] 18 | 19 | from .aspath import AsPathFilterGenerator 20 | from .community import CommunityListGenerator 21 | from .cumulus_frr import CumulusPolicyGenerator 22 | from .entities import CommunityList, AsPathFilter, CommunityType, CommunityLogic, RDFilter, IpPrefixList, IpPrefixListMember, ip_prefix_list 23 | from .execute import get_policies 24 | from .policy import RoutingPolicyGenerator 25 | from .prefix_lists import PrefixListFilterGenerator 26 | from .rd import RDFilterFilterGenerator 27 | -------------------------------------------------------------------------------- /annet/rpl_generators/execute.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | from annet.bgp_models import extract_policies 4 | from annet.mesh import MeshExecutor 5 | from annet.rpl import RouteMap, RoutingPolicy 6 | 7 | DeviceT = TypeVar("DeviceT") 8 | 9 | 10 | def get_policies(routemap: RouteMap[DeviceT], mesh_executor: MeshExecutor, device: DeviceT) -> list[RoutingPolicy]: 11 | allowed_policies = extract_policies(mesh_executor.execute_for(device)) 12 | return routemap.apply(device, allowed_policies) 13 | -------------------------------------------------------------------------------- /annet/rpl_generators/rd.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod, ABC 2 | from collections.abc import Sequence 3 | from typing import Any 4 | 5 | from annet.generators import PartialGenerator 6 | from annet.rpl import RouteMap, MatchField, RoutingPolicy 7 | from .entities import RDFilter 8 | 9 | 10 | class RDFilterFilterGenerator(PartialGenerator, ABC): 11 | TAGS = ["policy", "rpl", "routing"] 12 | 13 | @abstractmethod 14 | def get_policies(self, device: Any) -> list[RoutingPolicy]: 15 | raise NotImplementedError() 16 | 17 | @abstractmethod 18 | def get_rd_filters(self, device: Any) -> Sequence[RDFilter]: 19 | raise NotImplementedError() 20 | 21 | def get_used_rd_filters(self, device: Any) -> Sequence[RDFilter]: 22 | filters = {c.name: c for c in self.get_rd_filters(device)} 23 | policies = self.get_policies(device) 24 | used_filters = set() 25 | for policy in policies: 26 | for statement in policy.statements: 27 | for condition in statement.match.find_all(MatchField.rd): 28 | used_filters.update(condition.value) 29 | return [filters[name] for name in sorted(used_filters)] 30 | 31 | def acl_huawei(self, _): 32 | return r""" 33 | ip rd-filter 34 | """ 35 | 36 | def run_huawei(self, device: Any): 37 | for rd_filter in self.get_used_rd_filters(device): 38 | for i, route_distinguisher in enumerate(rd_filter.members): 39 | rd_id = (i + 1) * 10 + 5 40 | yield "ip rd-filter", rd_filter.number, f"index {rd_id}", "permit", route_distinguisher 41 | 42 | def acl_iosxr(self, _): 43 | return r""" 44 | rd-set * 45 | ~ %global=1 46 | """ 47 | 48 | def run_iosxr(self, device: Any): 49 | for rd_filter in self.get_used_rd_filters(device): 50 | with self.block("rd-set", rd_filter.name): 51 | for i, route_distinguisher in enumerate(rd_filter.members): 52 | if i + 1 < len(rd_filter.members): 53 | comma = "," 54 | else: 55 | comma = "" 56 | yield f"{route_distinguisher}{comma}", 57 | -------------------------------------------------------------------------------- /annet/rulebook/arista/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/rulebook/arista/__init__.py -------------------------------------------------------------------------------- /annet/rulebook/arista/iface.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from annet.rulebook import common 4 | 5 | 6 | # Добавление возможности удаления агрегатов, лупбэков, SVI и сабинтерфейсов 7 | def permanent(rule, key, diff, **kwargs): # pylint: disable=redefined-outer-name 8 | ifname = key[0] 9 | # Match group examples: 10 | # Group 01: Port-Channel10, Loopback1, Vlan800 11 | # Group 02: Ethernet2/1.20, Port-Channel10.200 12 | if re.match(r"((?:Port-Channel|Loopback|Vlan)\d+$)|((?:Ethernet|Port-Channel)[\d/]+\.\d+$)", ifname): 13 | # Эти интерфейсы можно удалять 14 | yield from common.default(rule, key, diff, **kwargs) 15 | else: 16 | yield from common.permanent(rule, key, diff, **kwargs) 17 | -------------------------------------------------------------------------------- /annet/rulebook/aruba/__init__.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.rulebook import common 2 | from annet.annlib.types import Op 3 | 4 | 5 | def default_diff(old, new, diff_pre, _pops=(Op.AFFECTED,)): 6 | diff = common.base_diff(old, new, diff_pre, _pops, moved_to_affected=True) 7 | diff[:] = _skip_non_ap_env_affected(diff) 8 | return diff 9 | 10 | 11 | def _skip_non_ap_env_affected(diff): 12 | for x in diff: 13 | if x.op == Op.AFFECTED and not x.children: 14 | if x.diff_pre["attrs"]["context"].get("block") != "ap-env": 15 | continue 16 | yield x 17 | -------------------------------------------------------------------------------- /annet/rulebook/aruba/misc.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.rulebook import common 2 | from annet.annlib.types import Op 3 | 4 | 5 | def syslog_level(rule, key, diff, **_): 6 | # syslog-level can be overwritten only 7 | if diff[Op.ADDED]: 8 | yield from common.default(rule, key, diff) 9 | -------------------------------------------------------------------------------- /annet/rulebook/b4com/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/rulebook/b4com/__init__.py -------------------------------------------------------------------------------- /annet/rulebook/b4com/file.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.types import Op 2 | 3 | 4 | def change(key, diff, **kwargs): 5 | yield from [(True, add["row"], None) for add in diff[Op.ADDED]] 6 | -------------------------------------------------------------------------------- /annet/rulebook/cisco/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/rulebook/cisco/__init__.py -------------------------------------------------------------------------------- /annet/rulebook/cisco/iface.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.types import Op 2 | 3 | from annet.rulebook import common 4 | 5 | 6 | def diff(old, new, diff_pre, _pops=(Op.AFFECTED,)): 7 | for iface_row in old: 8 | _filter_channel_members(old[iface_row]) 9 | for iface_row in new: 10 | _filter_channel_members(new[iface_row]) 11 | 12 | ret = common.default_diff(old, new, diff_pre, _pops) 13 | vpn_changed = False 14 | for (op, cmd, _, _) in ret: 15 | if op in {Op.ADDED, Op.REMOVED}: 16 | vpn_changed |= is_vpn_cmd(cmd) 17 | if vpn_changed: 18 | for cmd in list(old.keys()): 19 | if is_ip_cmd(cmd) and not is_vpn_cmd(cmd): 20 | del old[cmd] 21 | ret = common.default_diff(old, new, diff_pre, _pops) 22 | return ret 23 | 24 | 25 | def is_vpn_cmd(cmd): 26 | return cmd.startswith(("ip vrf forwarding", "vrf forwarding")) 27 | 28 | 29 | def is_ip_cmd(cmd): 30 | return cmd.startswith(("ip ", "ipv6 ")) 31 | 32 | # === 33 | 34 | # Вырезает все команды не разрешенные 35 | # на членах агрегата. В running-config 36 | # листинге они наследуются от самого port-channel 37 | 38 | 39 | def _filter_channel_members(tree): 40 | if any(is_in_channel(x) for x in tree): 41 | for cmd in list(tree.keys()): 42 | if not _is_allowed_on_channel(cmd): 43 | del tree[cmd] 44 | 45 | 46 | def is_in_channel(cmd_line): 47 | """ 48 | Признак того, что это lagg member 49 | """ 50 | return cmd_line.startswith("channel-group") 51 | 52 | 53 | # Возможно тут есть еще какие-то команды 54 | def _is_allowed_on_channel(cmd_line): 55 | return cmd_line.startswith(( 56 | "channel-group", 57 | "cdp", 58 | "description", 59 | "inherit", 60 | "ip port", 61 | "ipv6 port", 62 | "mac port", 63 | "lacp", 64 | "switchport host", 65 | "shutdown", 66 | "rate-limit cpu", 67 | "snmp trap link-status", 68 | )) 69 | -------------------------------------------------------------------------------- /annet/rulebook/cisco/misc.py: -------------------------------------------------------------------------------- 1 | import re 2 | from collections import OrderedDict 3 | from typing import Any 4 | 5 | from annet.annlib.types import Op 6 | 7 | from annet.rulebook import common 8 | 9 | 10 | def ssh_key(rule, key, diff, hw, **_): 11 | """ 12 | При включении ssh надо еще сгенерировать ключ. По конфигу никак не понять есть ли ключ на свитче или нет. 13 | """ 14 | if diff[Op.ADDED]: 15 | added = sorted([x["row"] for x in diff[Op.ADDED]]) 16 | if added == ["ip ssh version 2"]: 17 | # Отсыпаем mpdaemon-у подсказок для дополнительной команды при наливке 18 | comment = rule["comment"] 19 | rule["comment"] = ["!!suppress_errors!!", "!!timeout=240!!"] 20 | if hw.Cisco.C2960: 21 | yield (False, "crypto key generate rsa modulus 2048", None) 22 | else: 23 | yield (False, "crypto key generate rsa general-keys modulus 2048", None) 24 | rule["comment"] = comment 25 | yield from common.default(rule, key, diff) 26 | 27 | 28 | def no_ipv6_nd_suppress_ra(rule, key, diff, **_): 29 | """ 30 | При конфигурации ipv6 nd на нексусах нужно добавлять 31 | no ipv6 nd suppress-ra 32 | иначе RA не будет включен. 33 | К сожалению данной команды не видно в running-config. 34 | Поэтому подмешиваем ее в патч вместо генератора 35 | """ 36 | if diff[Op.ADDED]: 37 | yield (False, "no ipv6 nd suppress-ra", None) 38 | yield from common.default(rule, key, diff) 39 | 40 | 41 | def no_ntp_distribute(rule, key, diff, **_): 42 | """ 43 | Для того, чтобы удалить NTP из CFS, сначала нужно сбросить активные 44 | NTP сессии. 45 | """ 46 | if diff[Op.REMOVED]: 47 | yield (False, "clear ntp session", None) 48 | yield from common.default(rule, key, diff) 49 | 50 | 51 | def banner_login(rule, key, diff, **_): 52 | if diff[Op.REMOVED]: 53 | yield (False, "no banner login", None) 54 | elif diff[Op.ADDED]: 55 | # Убираем дополнительный экранирующий сиимвол 56 | key = re.sub(r"\^C", "^", key[0]) 57 | yield (False, f"banner login {key}", None) 58 | else: 59 | yield from common.default(rule, key, diff) 60 | -------------------------------------------------------------------------------- /annet/rulebook/common.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import importlib 3 | 4 | from annet.annlib.rulebook import common # pylint: disable=unused-import # noqa: F401,F403 5 | from annet.annlib.rulebook.common import * # pylint: disable=wildcard-import,unused-wildcard-import # noqa: F401,F403 6 | 7 | 8 | @functools.lru_cache() 9 | def import_rulebook_function(name): 10 | from . import rulebook_provider_connector 11 | 12 | index = name.rindex(".") 13 | for root in rulebook_provider_connector.get().get_root_modules(): 14 | try: 15 | module = importlib.import_module(f"{root}.{name[:index]}", package=__name__.rsplit(".", 1)[0]) 16 | return getattr(module, name[index + 1:]) 17 | except ImportError: 18 | pass 19 | raise ImportError(f"Could not import {name}") 20 | -------------------------------------------------------------------------------- /annet/rulebook/generic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/rulebook/generic/__init__.py -------------------------------------------------------------------------------- /annet/rulebook/generic/misc.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.types import Op 2 | 3 | from annet.rulebook import common 4 | 5 | 6 | def remove_last_param(rule, key, diff, **_): 7 | if diff[Op.REMOVED]: 8 | for rem in diff[Op.REMOVED]: 9 | # Обрабатывать удаление последнего параметра команды 10 | cmd_parts = rem["row"].split(" ") 11 | cmd_parts.remove(cmd_parts[len(cmd_parts) - 1]) 12 | yield False, "undo %s" % " ".join(cmd_parts), None 13 | else: 14 | yield from common.default(rule, key, diff) 15 | -------------------------------------------------------------------------------- /annet/rulebook/huawei/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/rulebook/huawei/__init__.py -------------------------------------------------------------------------------- /annet/rulebook/huawei/iface.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from annet.annlib.types import Op 4 | 5 | from annet.rulebook import common 6 | 7 | 8 | def permanent(rule, key, diff, **kwargs): # pylint: disable=redefined-outer-name 9 | ifname = key[0] 10 | if re.match(r"(Eth-Trunk|Vlanif|Vbdif|Loop[Bb]ack|Tunnel|.*\.\d+)", ifname): 11 | # эти интерфейсы можно удалять 12 | yield from common.default(rule, key, diff, **kwargs) 13 | else: 14 | yield from common.permanent(rule, key, diff, **kwargs) 15 | 16 | 17 | # [NOCDEV-2180] Хуавей просит переввести ip конфигурацию после изменения vrf 18 | def binding_change(old, new, diff_pre, _pops=(Op.AFFECTED,)): 19 | ret = common.default_diff(old, new, diff_pre, _pops) 20 | vpn_changed = False 21 | for (op, cmd, _, _) in ret: 22 | if op in {Op.ADDED, Op.REMOVED}: 23 | vpn_changed |= _is_vpn_cmd(cmd) 24 | if vpn_changed: 25 | for cmd in list(old.keys()): 26 | if not _is_vpn_cmd(cmd): 27 | del old[cmd] 28 | ret = common.default_diff(old, new, diff_pre, _pops) 29 | return ret 30 | 31 | 32 | def _is_vpn_cmd(cmd): 33 | return cmd.startswith("ip binding vpn-instance") 34 | -------------------------------------------------------------------------------- /annet/rulebook/juniper/iface.py: -------------------------------------------------------------------------------- 1 | import ipaddress 2 | from annet.annlib.types import Op 3 | from annet.rulebook import common 4 | from annet.rulebook.common import DiffItem 5 | 6 | 7 | def ipv6_addr(old, new, diff_pre, _pops): 8 | """ 9 | Приводим все ipv6-адреса в объекты IPv6Interface и далее сравниваем 10 | """ 11 | address_new_line = [a for a in map(_parse_ipv6, new) if a] 12 | address_old_line = [a for a in map(_parse_ipv6, old) if a] 13 | 14 | ret = [] 15 | for item in common.default_diff(old, new, diff_pre, _pops): 16 | # Проверяем адрес помеченный под снос на наличии в новом списке 17 | if item.op == Op.REMOVED and _parse_ipv6(item.row) in address_new_line: 18 | result_item = DiffItem(Op.AFFECTED, item.row, item.children, item.diff_pre) 19 | # Проверяем адрес помеченный для добавления на наличии в старом списке 20 | elif item.op == Op.ADDED and _parse_ipv6(item.row) in address_old_line: 21 | result_item = None 22 | # Остальное без изменений 23 | else: 24 | result_item = item 25 | if result_item: 26 | ret.append(result_item) 27 | return ret 28 | 29 | 30 | def _parse_ipv6(row): 31 | """ 32 | Парсит IPv6-интерфейс из строки, предполагая, что адрес находится на втором месте. 33 | Возвращает объект IPv6Interface или None. 34 | """ 35 | if row: 36 | parts = row.split() 37 | if len(parts) > 1: 38 | return ipaddress.IPv6Interface(parts[1]) 39 | return None 40 | -------------------------------------------------------------------------------- /annet/rulebook/nexus/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/rulebook/nexus/__init__.py -------------------------------------------------------------------------------- /annet/rulebook/routeros/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/rulebook/routeros/__init__.py -------------------------------------------------------------------------------- /annet/rulebook/routeros/file.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.types import Op 2 | 3 | 4 | def change(key, diff, **kwargs): 5 | yield from [(True, add["row"], None) for add in diff[Op.ADDED]] 6 | -------------------------------------------------------------------------------- /annet/rulebook/texts/arista.deploy: -------------------------------------------------------------------------------- 1 | # Рулбук деплоя на устройства 2 | # 3 | # Операторы: 4 | # * Один аргумент 5 | # ~ Несколько аргументов (минимум один) 6 | # 7 | # Параметры: 8 | # %timeout=... Таймаут выполнения команды (в секундах, по умолчанию 30) 9 | # 10 | # Ответы на вопросы 11 | # 12 | # <команда> 13 | # dialog: <вопрос> ::: <ответ> <параметры> 14 | # 15 | # Если <вопрос> заключён в слеши (//), то воспринимается как регулярка, иначе - точное совпадение. 16 | # Параметы: 17 | # %send_nl=... Посылать ли перевод строки после ответа (bool, по умолчанию true) 18 | # ----- 19 | 20 | ~ %global 21 | -------------------------------------------------------------------------------- /annet/rulebook/texts/arista.order: -------------------------------------------------------------------------------- 1 | # vim: set syntax=annrulebook: 2 | # В этом файле определяется порядок команд, в котором их следует подавать на устройство. 3 | # - Если порядок команды не важен - ее можно не писать сюда совсем. 4 | # - Если команда начинается с undo и прописан параметр %order_reverse - команда считается 5 | # обратной, но занимает место между прямыми там, где указано (т.е. undo vlan batch 6 | # будет стоять после блоков interface). 7 | 8 | 9 | alias tmate-arista bash tmate-arista 10 | service 11 | switch 12 | hardware 13 | transceiver 14 | 15 | 16 | logging trap 17 | logging monitor 18 | logging 19 | 20 | 21 | platform 22 | 23 | 24 | hostname 25 | ip name-server 26 | ip domain-name 27 | 28 | 29 | ntp 30 | 31 | 32 | qos profile * 33 | ~ 34 | 35 | 36 | snmp-server 37 | 38 | 39 | spanning-tree 40 | 41 | 42 | service 43 | 44 | 45 | username * 46 | username * ssh-key 47 | 48 | 49 | tacacs-server 50 | 51 | 52 | aaa group 53 | ~ 54 | 55 | 56 | aaa 57 | 58 | 59 | username 60 | 61 | 62 | role 63 | ~ 64 | 65 | 66 | vlan 67 | 68 | 69 | vrf instance 70 | 71 | mpls label range dynamic * 72 | mpls label range isis-sr * 73 | mpls ip 74 | mpls ldp 75 | 76 | router isis 77 | ~ 78 | 79 | interface 80 | ~ 81 | 82 | 83 | (?:ip|ipv6) access-list 84 | ~ 85 | 86 | 87 | (?:ip|ipv6) route 88 | 89 | 90 | (?:ip|ipv6) routing 91 | 92 | ip prefix-list 93 | 94 | ip community-list 95 | 96 | 97 | ip ftp 98 | 99 | 100 | qos rewrite 101 | qos map 102 | 103 | 104 | route-map * (?:permit|deny) * 105 | ~ 106 | 107 | 108 | router bfd 109 | ~ 110 | 111 | 112 | router bgp 113 | ~ 114 | 115 | 116 | router multicast 117 | 118 | 119 | ip tacacs 120 | 121 | 122 | management 123 | ~ 124 | 125 | 126 | ~ 127 | -------------------------------------------------------------------------------- /annet/rulebook/texts/arista.rul: -------------------------------------------------------------------------------- 1 | description %global 2 | 3 | 4 | qos profile * 5 | ~ %global 6 | 7 | username * ssh-key 8 | username * %logic=arista.aaa.user 9 | 10 | aaa group ~ 11 | ~ %global 12 | 13 | 14 | role * 15 | ~ %global 16 | 17 | 18 | vrf instance * 19 | ~ %global 20 | 21 | 22 | interface * %logic=arista.iface.permanent 23 | no switchport 24 | description * 25 | channel-group * 26 | lacp rate * 27 | service-profile * 28 | ~ %global 29 | 30 | 31 | ip access-list standard * 32 | * %logic=common.undo_redo 33 | 34 | ipv6 access-list standard * 35 | * %logic=common.undo_redo 36 | 37 | ipv6 access-list * 38 | * %logic=common.undo_redo 39 | 40 | 41 | # ios-like way of configuring prefix lists 42 | # should be kept above a eos-native one in arista.rul 43 | # since beginnig of commands are the same 44 | # if you use it keep all prefix lists in this format 45 | 46 | ip prefix-list * seq * %logic=common.undo_redo 47 | ipv6 prefix-list * seq * %logic=common.undo_redo 48 | 49 | 50 | # arista eos native way of configuring prefix lists 51 | # should be below to not collide with ios-like one 52 | # only one format is supported at the time for one family 53 | # prefer this one over the ios-like one 54 | 55 | ip prefix-list * 56 | seq * %logic=common.undo_redo 57 | 58 | ipv6 prefix-list * 59 | seq * %logic=common.undo_redo 60 | 61 | 62 | route-map * * * 63 | ~ %global 64 | 65 | router bfd 66 | ~ %global 67 | 68 | 69 | router bgp * 70 | no bgp default * 71 | neighbor * maximum-routes 72 | ! no neighbor * shutdown 73 | ~ %global 74 | 75 | ip load-sharing trident fields * %logic=common.default_instead_undo 76 | 77 | management * 78 | ~ %global 79 | 80 | 81 | no ~ %global 82 | ~ %global 83 | -------------------------------------------------------------------------------- /annet/rulebook/texts/aruba.deploy: -------------------------------------------------------------------------------- 1 | # Рулбук деплоя на устройства 2 | # 3 | # Операторы: 4 | # * Один аргумент 5 | # ~ Несколько аргументов (минимум один) 6 | # 7 | # Параметры: 8 | # %timeout=... Таймаут выполнения команды (в секундах, по умолчанию 30) 9 | # 10 | # Ответы на вопросы 11 | # 12 | # <команда> 13 | # dialog: <вопрос> ::: <ответ> <параметры> 14 | # 15 | # Если <вопрос> заключён в слеши (//), то воспринимается как регулярка, иначе - точное совпадение. 16 | # Параметы: 17 | # %send_nl=... Посылать ли перевод строки после ответа (bool, по умолчанию true) 18 | # ----- 19 | 20 | ~ %ifcontext=block:ap-env %apply_logic=aruba.ap_env.apply 21 | -------------------------------------------------------------------------------- /annet/rulebook/texts/aruba.order: -------------------------------------------------------------------------------- 1 | # В этом файле определяется порядок команд, в котором их следует подавать на устройство. 2 | # - Если порядок команды не важен - ее можно не писать сюда совсем. 3 | # - Если команда начинается с undo и прописан параметр %order_reverse - команда считается 4 | # обратной, но занимает место между прямыми там, где указано. 5 | 6 | 7 | hostname 8 | ip-address 9 | iap-master 10 | iap-conductor 11 | zone 12 | dot11a-radio-disable 13 | dot11g-radio-disable 14 | ap-installation 15 | a-channel 16 | g-channel 17 | a-external-antenna 18 | g-external-antenna 19 | a-ant-pol 20 | g-ant-pol 21 | 22 | 23 | virtual-controller-country * 24 | virtual-controller-key * 25 | name * 26 | virtual-controller-ip * 27 | ip-mode * 28 | syslog-server * 29 | terminal-access 30 | telnet-server 31 | loginsession timeout * 32 | ntp-server * 33 | clock timezone ~ 34 | rf-band * 35 | 36 | 37 | allow-new-aps 38 | allowed-ap * 39 | 40 | 41 | snmp-server 42 | 43 | 44 | arm 45 | ~ 46 | 47 | 48 | rf 49 | ~ 50 | 51 | 52 | syslog-level 53 | 54 | 55 | iot ~ 56 | 57 | 58 | wlan ~ 59 | ~ 60 | 61 | 62 | wired-port-profile * 63 | ~ 64 | 65 | 66 | uplink 67 | ~ 68 | 69 | 70 | airgroup 71 | ~ 72 | 73 | 74 | airgroupservice * 75 | ~ 76 | 77 | 78 | ipm 79 | ~ 80 | 81 | 82 | cluster-security 83 | ~ 84 | -------------------------------------------------------------------------------- /annet/rulebook/texts/b4com.deploy: -------------------------------------------------------------------------------- 1 | # Рулбук деплоя на устройства 2 | # 3 | # Операторы: 4 | # * Один аргумент 5 | # ~ Несколько аргументов (минимум один) 6 | # 7 | # Параметры: 8 | # %timeout=... Таймаут выполнения команды (в секундах, по умолчанию 30) 9 | # 10 | # Ответы на вопросы 11 | # 12 | # <команда> 13 | # dialog: <вопрос> ::: <ответ> <параметры> 14 | # 15 | # Если <вопрос> заключён в слеши (//), то воспринимается как регулярка, иначе - точное совпадение. 16 | # Параметы: 17 | # %send_nl=... Посылать ли перевод строки после ответа (bool, по умолчанию true) 18 | # ----- 19 | -------------------------------------------------------------------------------- /annet/rulebook/texts/b4com.order: -------------------------------------------------------------------------------- 1 | # В этом файле определяется порядок команд, в котором их следует подавать на устройство. 2 | # - Если порядок команды не важен - ее можно не писать сюда совсем. 3 | # - Если команда начинается с undo и прописан параметр %order_reverse - команда считается 4 | # обратной, но занимает место между прямыми там, где указано. 5 | 6 | # Фичи должны быть включены прежде всего 7 | feature 8 | # Далее нужно будет указать команды и их порядок 9 | sflow 10 | ip prefix-list 11 | ipv6 prefix-list 12 | route-map 13 | interface * 14 | description 15 | ip vrf forwarding 16 | ipv6 address 17 | mtu 18 | lldp-agent 19 | set lldp 20 | lldp tlv 21 | dcbx 22 | exit 23 | sflow 24 | ip community-list -------------------------------------------------------------------------------- /annet/rulebook/texts/b4com.rul: -------------------------------------------------------------------------------- 1 | # Операторы: 2 | # * Один аргумент в no 3 | # ~ Несколько аргументов (минимум один) в no 4 | # 5 | # Параметры: 6 | # %global Команда действует на любом уровне ниже 7 | # %logic=... Кастомная функция обработки правила 8 | # %diff_logic=... Кастомная функция построения диффа. 9 | # данная функция работает для подблоков (в отличие от %logic) 10 | # %comment=... Добавить коммент после строки который будет видно с опцией patch --add-comments 11 | # Сделано в основном для того чтобы генерировать специальные команды для наливки 12 | # ----- 13 | # Physical 14 | sflow 15 | aaa group server tacacs\+ * 16 | ~ %global 17 | ip prefix-list * 18 | ~ %global 19 | ipv6 prefix-list * 20 | ~ %global 21 | route-map ~ 22 | ~ %global 23 | interface */(ce|xe|po|eth|vlan1\.)[0-9\/]+$/ %logic=common.permanent %diff_logic=b4com.iface.diff 24 | description 25 | ip vrf forwarding * 26 | ip address * 27 | ipv6 address * 28 | ipv6 nd * 29 | mtu 30 | sflow * %logic=b4com.iface.sflow 31 | lldp-agent 32 | ~ %global %logic=b4com.iface.lldp 33 | !dcbx * 34 | !exit 35 | ip community-list ~ -------------------------------------------------------------------------------- /annet/rulebook/texts/cisco.deploy: -------------------------------------------------------------------------------- 1 | # Рулбук деплоя на устройства 2 | # 3 | # Операторы: 4 | # * Один аргумент 5 | # ~ Несколько аргументов (минимум один) 6 | # 7 | # Параметры: 8 | # %timeout=... Таймаут выполнения команды (в секундах, по умолчанию 30) 9 | # 10 | # Ответы на вопросы 11 | # 12 | # <команда> 13 | # dialog: <вопрос> ::: <ответ> <параметры> 14 | # 15 | # Если <вопрос> заключён в слеши (//), то воспринимается как регулярка, иначе - точное совпадение. 16 | # Параметы: 17 | # %send_nl=... Посылать ли перевод строки после ответа (bool, по умолчанию true) 18 | # ----- 19 | 20 | crypto key generate rsa %timeout=60 21 | dialog: Do you really want to replace them? [yes/no]: ::: no 22 | dialog: How many bits in the modulus [512]: ::: 2048 23 | no username * privilege * secret * * 24 | dialog: This operation will remove all username related configurations with same name.Do you want to continue? [confirm] ::: Y %send_nl=0 25 | 26 | copy running-config startup-config 27 | dialog: Destination filename [startup-config]? ::: startup-config 28 | -------------------------------------------------------------------------------- /annet/rulebook/texts/cisco.order: -------------------------------------------------------------------------------- 1 | # В этом файле определяется порядок команд, в котором их следует подавать на устройство. 2 | # - Если порядок команды не важен - ее можно не писать сюда совсем. 3 | # - Если команда начинается с undo и прописан параметр %order_reverse - команда считается 4 | # обратной, но занимает место между прямыми там, где указано. 5 | 6 | banner login %order_reverse 7 | 8 | # Фичи должны быть включены прежде всего 9 | feature 10 | # За ним сервисы 11 | service 12 | 13 | aaa new-model 14 | aaa ~ 15 | 16 | no password strength-check 17 | username 18 | tacacs-server 19 | 20 | radius 21 | radius-server 22 | dot1x 23 | 24 | privilege 25 | file 26 | 27 | ip access-list 28 | ipv6 access-list 29 | class-map 30 | policy-map 31 | system qos 32 | control-plane 33 | 34 | snmp-server source-interface 35 | snmp-server user 36 | snmp-server host 37 | snmp-server enable 38 | snmp-server context 39 | snmp-server community 40 | snmp-server mib 41 | 42 | ntp distribute 43 | ntp server 44 | ntp commit 45 | 46 | vlan 47 | vlan group 48 | 49 | spanning-tree 50 | 51 | # перед тем, как менять mtu на интерфейсах, надо выставить максимальный 52 | no system jumbomtu %order_reverse 53 | system jumbomtu 54 | 55 | service dhcp 56 | ip dhcp relay 57 | ipv6 dhcp relay 58 | 59 | vrf context 60 | 61 | interface */Vlan\d+/ 62 | interface * 63 | no switchport 64 | encapsulation 65 | ip vrf forwarding 66 | vrf forwarding 67 | ip 68 | ipv6 69 | no ipv6 nd %order_reverse 70 | ipv6 nd 71 | ~ 72 | channel-group 73 | 74 | interface */\S+\.\d+/ 75 | 76 | route-map 77 | 78 | # удалять eth-trunk можно только после того, как вычистим member interfaces 79 | undo interface */port-channel\d+/ %order_reverse 80 | 81 | # remote as of neighbors should be assigned before peer-group, the rule is applied to neighbors, not peer-groups 82 | router bgp 83 | neighbor */[\da-f\.\:]+/ remote-as 84 | neighbor */[\da-f\.\:]+/ peer-group 85 | 86 | line -------------------------------------------------------------------------------- /annet/rulebook/texts/iosxr.deploy: -------------------------------------------------------------------------------- 1 | # Рулбук деплоя на устройства 2 | # 3 | # Операторы: 4 | # * Один аргумент 5 | # ~ Несколько аргументов (минимум один) 6 | # 7 | # Параметры: 8 | # %timeout=... Таймаут выполнения команды (в секундах, по умолчанию 30) 9 | # 10 | # Ответы на вопросы 11 | # 12 | # <команда> 13 | # dialog: <вопрос> ::: <ответ> <параметры> 14 | # 15 | # Если <вопрос> заключён в слеши (//), то воспринимается как регулярка, иначе - точное совпадение. 16 | # Параметы: 17 | # %send_nl=... Посылать ли перевод строки после ответа (bool, по умолчанию true) 18 | # ----- 19 | 20 | crypto key generate rsa %timeout=60 21 | dialog: Do you really want to replace them? [yes/no]: ::: no 22 | dialog: How many bits in the modulus [512]: ::: 2048 23 | no username * privilege * secret * * 24 | dialog: This operation will remove all username related configurations with same name.Do you want to continue? [confirm] ::: Y %send_nl=0 25 | 26 | copy running-config startup-config 27 | dialog: Destination filename [startup-config]? ::: startup-config 28 | -------------------------------------------------------------------------------- /annet/rulebook/texts/iosxr.order: -------------------------------------------------------------------------------- 1 | # В этом файле определяется порядок команд, в котором их следует подавать на устройство. 2 | # - Если порядок команды не важен - ее можно не писать сюда совсем. 3 | # - Если команда начинается с undo и прописан параметр %order_reverse - команда считается 4 | # обратной, но занимает место между прямыми там, где указано. 5 | 6 | banner login %order_reverse 7 | 8 | # Фичи должны быть включены прежде всего 9 | feature 10 | # За ним сервисы 11 | service 12 | 13 | aaa new-model 14 | aaa ~ 15 | 16 | no password strength-check 17 | username 18 | tacacs-server 19 | 20 | radius 21 | radius-server 22 | dot1x 23 | 24 | privilege 25 | file 26 | 27 | ip access-list 28 | ipv6 access-list 29 | class-map 30 | policy-map 31 | system qos 32 | control-plane 33 | 34 | snmp-server source-interface 35 | snmp-server user 36 | snmp-server host 37 | snmp-server enable 38 | snmp-server context 39 | snmp-server community 40 | snmp-server mib 41 | 42 | ntp distribute 43 | ntp server 44 | ntp commit 45 | 46 | vlan 47 | vlan group 48 | 49 | spanning-tree 50 | 51 | # перед тем, как менять mtu на интерфейсах, надо выставить максимальный 52 | no system jumbomtu %order_reverse 53 | system jumbomtu 54 | 55 | service dhcp 56 | ip dhcp relay 57 | ipv6 dhcp relay 58 | 59 | vrf context 60 | 61 | interface */Vlan\d+/ 62 | interface * 63 | no switchport 64 | encapsulation 65 | vrf member 66 | ip 67 | ipv6 68 | no ipv6 nd %order_reverse 69 | ipv6 nd 70 | ~ 71 | channel-group 72 | 73 | interface */\S+\.\d+/ 74 | 75 | 76 | # all referenced in policy 77 | as-path-set * 78 | ~ 79 | prefix-set * 80 | ~ 81 | community-set * 82 | ~ 83 | extcommunity-set * 84 | ~ 85 | 86 | route-policy * 87 | ~ 88 | 89 | # удалять eth-trunk можно только после того, как вычистим member interfaces 90 | undo interface */port-channel\d+/ %order_reverse 91 | 92 | 93 | router bgp 94 | bgp ~ 95 | neighbor-group * 96 | ~ 97 | vrf * 98 | ~ 99 | neighbor * 100 | ~ 101 | 102 | line -------------------------------------------------------------------------------- /annet/rulebook/texts/juniper.order: -------------------------------------------------------------------------------- 1 | # Правило на все команды 2 | * %global 3 | # Добавляем веса комментам, чтобы они катились всегда после команд, но только в патче 4 | */\/\*(?:(?!\*\/).)*\*\// %global %scope=patch 5 | -------------------------------------------------------------------------------- /annet/rulebook/texts/nexus.deploy: -------------------------------------------------------------------------------- 1 | # Рулбук деплоя на устройства 2 | # 3 | # Операторы: 4 | # * Один аргумент 5 | # ~ Несколько аргументов (минимум один) 6 | # 7 | # Параметры: 8 | # %timeout=... Таймаут выполнения команды (в секундах, по умолчанию 30) 9 | # 10 | # Ответы на вопросы 11 | # 12 | # <команда> 13 | # dialog: <вопрос> ::: <ответ> <параметры> 14 | # 15 | # Если <вопрос> заключён в слеши (//), то воспринимается как регулярка, иначе - точное совпадение. 16 | # Параметы: 17 | # %send_nl=... Посылать ли перевод строки после ответа (bool, по умолчанию true) 18 | # ----- 19 | 20 | crypto key generate rsa %timeout=60 21 | dialog: Do you really want to replace them? [yes/no]: ::: no 22 | dialog: How many bits in the modulus [512]: ::: 2048 23 | no username * privilege * secret 5 * 24 | dialog: This operation will remove all username related configurations with same name.Do you want to continue? [confirm] ::: Y %send_nl=0 25 | -------------------------------------------------------------------------------- /annet/rulebook/texts/nexus.order: -------------------------------------------------------------------------------- 1 | # В этом файле определяется порядок команд, в котором их следует подавать на устройство. 2 | # - Если порядок команды не важен - ее можно не писать сюда совсем. 3 | # - Если команда начинается с undo и прописан параметр %order_reverse - команда считается 4 | # обратной, но занимает место между прямыми там, где указано. 5 | 6 | # Фичи должны быть включены прежде всего 7 | feature 8 | # За ним сервисы 9 | service 10 | 11 | interface breakout 12 | 13 | no password strength-check 14 | username 15 | tacacs-server 16 | aaa 17 | 18 | ip access-list 19 | ipv6 access-list 20 | 21 | class-map 22 | policy-map 23 | system qos 24 | control-plane 25 | 26 | no policy-map %order_reverse 27 | no class-map %order_reverse 28 | 29 | snmp-server source-interface 30 | snmp-server user 31 | snmp-server host 32 | snmp-server enable 33 | snmp-server context 34 | snmp-server community 35 | snmp-server mib 36 | 37 | no ntp server 38 | no ntp distribute 39 | clear ntp session 40 | 41 | ntp distribute 42 | ntp server 43 | ntp commit 44 | 45 | 46 | vlan 47 | vlan group 48 | 49 | spanning-tree 50 | 51 | # перед тем, как менять mtu на интерфейсах, надо выставить максимальный 52 | no system jumbomtu %order_reverse 53 | system jumbomtu 54 | 55 | route-map 56 | 57 | service dhcp 58 | ip dhcp relay 59 | ipv6 dhcp relay 60 | 61 | vrf context 62 | 63 | interface */Vlan\d+/ 64 | interface * 65 | no switchport 66 | switchport 67 | switchport access vlan * 68 | encapsulation 69 | vrf member 70 | ip 71 | ipv6 72 | no ipv6 nd %order_reverse 73 | ipv6 nd 74 | ~ 75 | channel-group 76 | 77 | interface */\S+\.\d+/ 78 | 79 | # удалять eth-trunk можно только после того, как вычистим member interfaces 80 | undo interface */port-channel\d+/ %order_reverse 81 | 82 | router bgp 83 | address-family 84 | template 85 | neighbor 86 | -------------------------------------------------------------------------------- /annet/rulebook/texts/nokia.rul: -------------------------------------------------------------------------------- 1 | # Операторы: 2 | # * Один аргумент в undo 3 | # ~ Несколько аргументов (минимум один) в undo 4 | # 5 | # Параметры: 6 | # %global Команда действует на любом уровне ниже 7 | # %logic=... Кастомная функция обработки правила 8 | # %diff_logic=... Кастомная функция построения диффа. 9 | # данная функция работает для подблоков (в отличие от %logic) 10 | # %comment=... Добавить коммент после строки который будет видно с опцией patch --add-comments 11 | # Сделано в основном для того чтобы генерировать специальные команды для наливки 12 | # ----- 13 | 14 | system 15 | name * 16 | dns 17 | ~ 18 | time 19 | ~ 20 | 21 | policy-options 22 | policy-statement * 23 | ~ %ordered 24 | ~ %global 25 | 26 | 27 | router * 28 | isis * 29 | overload 30 | 31 | ~ %global 32 | -------------------------------------------------------------------------------- /annet/rulebook/texts/optixtrans.deploy: -------------------------------------------------------------------------------- 1 | # Рулбук деплоя на устройства 2 | # 3 | # Операторы: 4 | # * Один аргумент 5 | # ~ Несколько аргументов (минимум один) 6 | # 7 | # Параметры: 8 | # %timeout=... Таймаут выполнения команды (в секундах, по умолчанию 30) 9 | # 10 | # Ответы на вопросы 11 | # 12 | # <команда> 13 | # dialog: <вопрос> ::: <ответ> <параметры> 14 | # 15 | # Если <вопрос> заключён в слеши (//), то воспринимается как регулярка, иначе - точное совпадение. 16 | # Параметы: 17 | # %send_nl=... Посылать ли перевод строки после ответа (bool, по умолчанию true) 18 | # ----- 19 | -------------------------------------------------------------------------------- /annet/rulebook/texts/optixtrans.order: -------------------------------------------------------------------------------- 1 | # В этом файле определяется порядок команд, в котором их следует подавать на устройство. 2 | # - Если порядок команды не важен - ее можно не писать сюда совсем. 3 | # - Если команда начинается с undo и прописан параметр %order_reverse - команда считается 4 | # обратной, но занимает место между прямыми там, где указано (т.е. undo vlan batch 5 | # будет стоять после блоков interface). 6 | -------------------------------------------------------------------------------- /annet/rulebook/texts/optixtrans.rul: -------------------------------------------------------------------------------- 1 | # Операторы: 2 | # * Один аргумент в undo 3 | # ~ Несколько аргументов (минимум один) в undo 4 | # 5 | # Параметры: 6 | # %global Команда действует на любом уровне ниже 7 | # %logic=... Кастомная функция обработки правила 8 | # %diff_logic=... Кастомная функция построения диффа. 9 | # данная функция работает для подблоков (в отличие от %logic) 10 | # %comment=... Добавить коммент после строки который будет видно с опцией patch --add-comments 11 | # Сделано в основном для того чтобы генерировать специальные команды для наливки 12 | # ----- 13 | clock timezone 14 | -------------------------------------------------------------------------------- /annet/rulebook/texts/pc.deploy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/rulebook/texts/pc.deploy -------------------------------------------------------------------------------- /annet/rulebook/texts/pc.order: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | ###################################################### 3 | ### THIS IS A STUB FILE PLEASE DO NOT ADD ANYTHING ### 4 | ###################################################### 5 | ###################################################### 6 | -------------------------------------------------------------------------------- /annet/rulebook/texts/pc.rul: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | ###################################################### 3 | ### THIS IS A STUB FILE PLEASE DO NOT ADD ANYTHING ### 4 | ###################################################### 5 | ###################################################### 6 | 7 | 8 | undo ~ %global 9 | ~ %global 10 | -------------------------------------------------------------------------------- /annet/rulebook/texts/ribbon.deploy: -------------------------------------------------------------------------------- 1 | # Рулбук деплоя на устройства 2 | # 3 | # Операторы: 4 | # * Один аргумент 5 | # ~ Несколько аргументов (минимум один) 6 | # 7 | # Параметры: 8 | # %timeout=... Таймаут выполнения команды (в секундах, по умолчанию 30) 9 | # 10 | # Ответы на вопросы 11 | # 12 | # <команда> 13 | # dialog: <вопрос> ::: <ответ> <параметры> 14 | # 15 | # Если <вопрос> заключён в слеши (//), то воспринимается как регулярка, иначе - точное совпадение. 16 | # Параметы: 17 | # %send_nl=... Посылать ли перевод строки после ответа (bool, по умолчанию true) 18 | # ----- 19 | 20 | 21 | exit 22 | dialog: Exit configure exclusive mode? [yes,no] (no) ::: yes %send_nl=1 23 | -------------------------------------------------------------------------------- /annet/rulebook/texts/ribbon.rul: -------------------------------------------------------------------------------- 1 | # Операторы: 2 | # * Один аргумент в undo 3 | # ~ Несколько аргументов (минимум один) в undo 4 | # 5 | # Параметры: 6 | # %global Команда действует на любом уровне ниже 7 | # %logic=... Кастомная функция обработки правила 8 | # %diff_logic=... Кастомная функция построения диффа. 9 | # данная функция работает для подблоков (в отличие от %logic) 10 | # %comment=... Добавить коммент после строки который будет видно с опцией patch --add-comments 11 | # Сделано в основном для того чтобы генерировать специальные команды для наливки 12 | # ----- 13 | 14 | system 15 | host-name 16 | 17 | time-zone 18 | 19 | tacplus-server 20 | * secret 21 | 22 | event-log 23 | files 24 | size 25 | 26 | ntp 27 | server ~ 28 | 29 | services 30 | ssh 31 | root-login 32 | 33 | management-network 34 | network-bridge 35 | allow-6to4-tunnel-packets 36 | 37 | root-authentication 38 | encrypted-password 39 | 40 | login 41 | user * 42 | authentication 43 | user-encrypted-password 44 | class 45 | full-name 46 | 47 | interfaces 48 | * 49 | description 50 | 51 | chassis 52 | slot * * 53 | port * 54 | port-type 55 | otuc-options 56 | line-rate 57 | transceiver-options 58 | exp-transceiver-type 59 | tx-wavelength 60 | fiber-connectivity (internal|external) (uni-in|uni-out|bidirectional) 61 | peer-ne 62 | peer-slot 63 | peer-port 64 | fiber-length 65 | input-fiber-loss 66 | output-fiber-loss 67 | ots-options 68 | los-threshold 69 | oa-options (booster|preamp|amp) 70 | edfa-mode 71 | initial-gain 72 | exp-raman-gain 73 | max-number-channels 74 | output-pwr-per-channel 75 | tilt 76 | 77 | ~ %global 78 | -------------------------------------------------------------------------------- /annet/rulebook/texts/routeros.order: -------------------------------------------------------------------------------- 1 | # В этом файле определяется порядок команд, в котором их следует подавать на устройство. 2 | # - Если порядок команды не важен - ее можно не писать сюда совсем. 3 | # - Если команда начинается с undo и прописан параметр %order_reverse - команда считается 4 | # обратной, но занимает место между прямыми там, где указано (т.е. undo vlan batch 5 | # будет стоять после блоков interface). 6 | 7 | file 8 | print 9 | set 10 | 11 | snmp 12 | community 13 | set 14 | add 15 | 16 | snmp 17 | set 18 | 19 | system 20 | logging 21 | action 22 | set 23 | add 24 | set 25 | add 26 | 27 | interface 28 | gre 29 | add 30 | 31 | interface 32 | list 33 | member 34 | add 35 | 36 | ip 37 | address 38 | add -------------------------------------------------------------------------------- /annet/rulebook/texts/routeros.rul: -------------------------------------------------------------------------------- 1 | # Операторы: 2 | # * Один аргумент в undo 3 | # ~ Несколько аргументов (минимум один) в undo 4 | # 5 | # Параметры: 6 | # %global Команда действует на любом уровне ниже 7 | # %logic=... Кастомная функция обработки правила 8 | # %diff_logic=... Кастомная функция построения диффа. 9 | # данная функция работает для подблоков (в отличие от %logic) 10 | # %comment=... Добавить коммент после строки который будет видно с опцией patch --add-comments 11 | # Сделано в основном для того чтобы генерировать специальные команды для наливки 12 | # ----- 13 | 14 | 15 | user 16 | ~ 17 | group 18 | add ~ 19 | 20 | file 21 | print file=* 22 | set * %logic=routeros.file.change 23 | 24 | system 25 | logging 26 | action 27 | set ~ 28 | add ~ 29 | set ~ 30 | add ~ 31 | 32 | interface 33 | gre 34 | add 35 | 36 | interface 37 | list 38 | member 39 | add 40 | 41 | ip 42 | address 43 | add ~ 44 | 45 | ~ %global 46 | -------------------------------------------------------------------------------- /annet/vendors/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from annet.connectors import Connector 4 | from annet.vendors.base import AbstractVendor 5 | 6 | from .library import ( 7 | arista, 8 | aruba, 9 | b4com, 10 | cisco, 11 | h3c, 12 | huawei, 13 | iosxr, 14 | juniper, 15 | nexus, 16 | nokia, 17 | optixtrans, 18 | pc, 19 | ribbon, 20 | routeros, 21 | ) 22 | from .registry import Registry, registry 23 | 24 | 25 | class _RegistryConnector(Connector[Registry]): 26 | name = "Registry" 27 | ep_name = "vendors" 28 | 29 | def _get_default(self) -> Callable[[], Registry]: 30 | return lambda: registry 31 | 32 | 33 | registry_connector = _RegistryConnector() 34 | -------------------------------------------------------------------------------- /annet/vendors/base.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import ClassVar 3 | 4 | from annet.annlib.command import CommandList 5 | from annet.annlib.netdev.views.hardware import HardwareView 6 | from annet.vendors.tabparser import CommonFormatter 7 | 8 | 9 | class AbstractVendor(abc.ABC): 10 | NAME: ClassVar[str] 11 | 12 | @abc.abstractmethod 13 | def match(self) -> list[str]: 14 | raise NotImplementedError 15 | 16 | def apply( 17 | self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str 18 | ) -> tuple[CommandList, CommandList]: 19 | return CommandList(), CommandList() 20 | 21 | @property 22 | @abc.abstractmethod 23 | def reverse(self) -> str: 24 | raise NotImplementedError 25 | 26 | def diff(self, order: bool) -> str: 27 | return "common.ordered_diff" if order else "common.default_diff" 28 | 29 | @property 30 | @abc.abstractmethod 31 | def exit(self) -> str: 32 | raise NotImplementedError 33 | 34 | @property 35 | @abc.abstractmethod 36 | def hardware(self) -> HardwareView: 37 | raise NotImplementedError 38 | 39 | def svi_name(self, num: int) -> str: 40 | return f"vlan{num}" 41 | 42 | @abc.abstractmethod 43 | def make_formatter(self, **kwargs) -> CommonFormatter: 44 | raise NotImplementedError 45 | -------------------------------------------------------------------------------- /annet/vendors/library/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet/vendors/library/__init__.py -------------------------------------------------------------------------------- /annet/vendors/library/arista.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import AristaFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class AristaVendor(AbstractVendor): 10 | NAME = "arista" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("conf s")) 16 | if do_commit: 17 | after.add_cmd(Command("commit")) 18 | else: 19 | after.add_cmd(Command("abort")) # просто exit оставит висеть configure session 20 | if do_finalize: 21 | after.add_cmd(Command("write memory")) 22 | 23 | return before, after 24 | 25 | def match(self) -> list[str]: 26 | return ["Arista"] 27 | 28 | @property 29 | def reverse(self) -> str: 30 | return "no" 31 | 32 | @property 33 | def hardware(self) -> HardwareView: 34 | return HardwareView("Arista") 35 | 36 | def svi_name(self, num: int) -> str: 37 | return f"Vlan{num}" 38 | 39 | def make_formatter(self, **kwargs) -> AristaFormatter: 40 | return AristaFormatter(**kwargs) 41 | 42 | @property 43 | def exit(self) -> str: 44 | return "exit" 45 | -------------------------------------------------------------------------------- /annet/vendors/library/aruba.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import ArubaFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class ArubaVendor(AbstractVendor): 10 | NAME = "aruba" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("conf t")) 16 | after.add_cmd(Command("end")) 17 | if do_commit: 18 | after.add_cmd(Command("commit apply")) 19 | if do_finalize: 20 | after.add_cmd(Command("write memory")) 21 | 22 | return before, after 23 | 24 | def match(self) -> list[str]: 25 | return ["Aruba"] 26 | 27 | @property 28 | def reverse(self) -> str: 29 | return "no" 30 | 31 | @property 32 | def hardware(self) -> HardwareView: 33 | return HardwareView("Aruba") 34 | 35 | def make_formatter(self, **kwargs) -> ArubaFormatter: 36 | return ArubaFormatter(**kwargs) 37 | 38 | @property 39 | def exit(self) -> str: 40 | return "exit" 41 | 42 | def diff(self, order: bool) -> str: 43 | return "common.ordered_diff" if order else "aruba.default_diff" 44 | -------------------------------------------------------------------------------- /annet/vendors/library/b4com.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import B4comFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class B4ComVendor(AbstractVendor): 10 | NAME = "b4com" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | if hw.B4com.CS2148P: 16 | before.add_cmd(Command("conf t")) 17 | after.add_cmd(Command("end")) 18 | if do_finalize: 19 | after.add_cmd(Command("write", timeout=40)) 20 | else: 21 | before.add_cmd(Command("conf t")) 22 | if do_commit: 23 | after.add_cmd(Command("commit")) 24 | after.add_cmd(Command("end")) 25 | if do_finalize: 26 | after.add_cmd(Command("write", timeout=40)) 27 | 28 | return before, after 29 | 30 | def match(self) -> list[str]: 31 | return ["B4com"] 32 | 33 | @property 34 | def reverse(self) -> str: 35 | return "no" 36 | 37 | @property 38 | def hardware(self) -> HardwareView: 39 | return HardwareView("B4com") 40 | 41 | def make_formatter(self, **kwargs) -> B4comFormatter: 42 | return B4comFormatter(**kwargs) 43 | 44 | @property 45 | def exit(self) -> str: 46 | return "exit" 47 | 48 | def svi_name(self, num: int) -> str: 49 | return f"vlan1.{num}" 50 | -------------------------------------------------------------------------------- /annet/vendors/library/cisco.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import CiscoFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class CiscoVendor(AbstractVendor): 10 | NAME = "cisco" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("conf t")) 16 | after.add_cmd(Command("exit")) 17 | if do_finalize: 18 | after.add_cmd(Command("copy running-config startup-config", timeout=40)) 19 | 20 | return before, after 21 | 22 | def match(self) -> list[str]: 23 | return ["Cisco"] 24 | 25 | @property 26 | def reverse(self) -> str: 27 | return "no" 28 | 29 | @property 30 | def hardware(self) -> HardwareView: 31 | return HardwareView("Cisco") 32 | 33 | def svi_name(self, num: int) -> str: 34 | return f"Vlan{num}" 35 | 36 | def make_formatter(self, **kwargs) -> CiscoFormatter: 37 | return CiscoFormatter(**kwargs) 38 | 39 | @property 40 | def exit(self) -> str: 41 | return "exit" 42 | -------------------------------------------------------------------------------- /annet/vendors/library/h3c.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import HuaweiFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class H3CVendor(AbstractVendor): 10 | NAME = "h3c" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("system-view")) 16 | if do_finalize: 17 | after.add_cmd(Command("save force", timeout=90)) 18 | 19 | return before, after 20 | 21 | def match(self) -> list[str]: 22 | return ["H3C"] 23 | 24 | @property 25 | def reverse(self) -> str: 26 | return "undo" 27 | 28 | @property 29 | def hardware(self) -> HardwareView: 30 | return HardwareView("H3C") 31 | 32 | def make_formatter(self, **kwargs) -> HuaweiFormatter: 33 | return HuaweiFormatter(**kwargs) 34 | 35 | @property 36 | def exit(self) -> str: 37 | return "quit" 38 | -------------------------------------------------------------------------------- /annet/vendors/library/huawei.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import HuaweiFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class HuaweiVendor(AbstractVendor): 10 | NAME = "huawei" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("system-view")) 16 | if do_commit and (hw.Huawei.CE or hw.Huawei.NE): 17 | after.add_cmd(Command("commit")) 18 | after.add_cmd(Command("q")) 19 | if do_finalize: 20 | after.add_cmd(Command("save", timeout=20)) 21 | 22 | return before, after 23 | 24 | def match(self) -> list[str]: 25 | return ["Huawei"] 26 | 27 | @property 28 | def reverse(self) -> str: 29 | return "undo" 30 | 31 | @property 32 | def hardware(self) -> HardwareView: 33 | return HardwareView("Huawei") 34 | 35 | def svi_name(self, num: int) -> str: 36 | return f"Vlanif{num}" 37 | 38 | def make_formatter(self, **kwargs) -> HuaweiFormatter: 39 | return HuaweiFormatter(**kwargs) 40 | 41 | @property 42 | def exit(self) -> str: 43 | return "quit" 44 | -------------------------------------------------------------------------------- /annet/vendors/library/iosxr.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import AsrFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class IosXrVendor(AbstractVendor): 10 | NAME = "iosxr" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("configure exclusive")) 16 | if do_commit: 17 | after.add_cmd(Command("commit show-error")) 18 | after.add_cmd(Command("exit")) 19 | 20 | return before, after 21 | 22 | def match(self) -> list[str]: 23 | return ["Cisco.ASR", "Cisco.XR", "Cisco.XRV"] 24 | 25 | @property 26 | def reverse(self) -> str: 27 | return "no" 28 | 29 | @property 30 | def hardware(self) -> HardwareView: 31 | return HardwareView("Cisco ASR") 32 | 33 | def svi_name(self, num: int) -> str: 34 | return f"Vlan{num}" 35 | 36 | def make_formatter(self, **kwargs) -> AsrFormatter: 37 | return AsrFormatter(**kwargs) 38 | 39 | @property 40 | def exit(self) -> str: 41 | return "exit" 42 | -------------------------------------------------------------------------------- /annet/vendors/library/juniper.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import JuniperFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class JuniperVendor(AbstractVendor): 10 | NAME = "juniper" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("configure exclusive")) 16 | if do_commit: 17 | after.add_cmd(Command("commit", timeout=30)) 18 | after.add_cmd(Command("exit")) 19 | 20 | return before, after 21 | 22 | def match(self) -> list[str]: 23 | return ["Juniper"] 24 | 25 | @property 26 | def reverse(self) -> str: 27 | return "delete" 28 | 29 | @property 30 | def hardware(self) -> HardwareView: 31 | return HardwareView("Juniper") 32 | 33 | def svi_name(self, num: int) -> str: 34 | return f"irb.{num}" 35 | 36 | def make_formatter(self, **kwargs) -> JuniperFormatter: 37 | return JuniperFormatter(**kwargs) 38 | 39 | @property 40 | def exit(self) -> str: 41 | return "" 42 | 43 | def diff(self, order: bool) -> str: 44 | return "juniper.ordered_diff" if order else "juniper.default_diff" 45 | -------------------------------------------------------------------------------- /annet/vendors/library/nexus.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import NexusFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class NexusVendor(AbstractVendor): 10 | NAME = "nexus" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("conf t")) 16 | after.add_cmd(Command("exit")) 17 | if do_finalize: 18 | after.add_cmd(Command("copy running-config startup-config", timeout=40)) 19 | 20 | return before, after 21 | 22 | def match(self) -> list[str]: 23 | return ["Cisco.Nexus"] 24 | 25 | @property 26 | def reverse(self) -> str: 27 | return "no" 28 | 29 | @property 30 | def hardware(self) -> HardwareView: 31 | return HardwareView("Cisco Nexus") 32 | 33 | def make_formatter(self, **kwargs) -> NexusFormatter: 34 | return NexusFormatter(**kwargs) 35 | 36 | @property 37 | def exit(self) -> str: 38 | return "exit" 39 | -------------------------------------------------------------------------------- /annet/vendors/library/nokia.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import NokiaFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class NokiaVendor(AbstractVendor): 10 | NAME = "nokia" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("configure private")) 16 | if do_commit: 17 | after.add_cmd(Command("commit")) 18 | 19 | return before, after 20 | 21 | def match(self) -> list[str]: 22 | return ["Nokia"] 23 | 24 | @property 25 | def reverse(self) -> str: 26 | return "delete" 27 | 28 | @property 29 | def hardware(self) -> HardwareView: 30 | return HardwareView("Nokia") 31 | 32 | def make_formatter(self, **kwargs) -> NokiaFormatter: 33 | return NokiaFormatter(**kwargs) 34 | 35 | @property 36 | def exit(self) -> str: 37 | return "" 38 | 39 | def diff(self, order: bool) -> str: 40 | return "juniper.ordered_diff" if order else "juniper.default_diff" 41 | -------------------------------------------------------------------------------- /annet/vendors/library/optixtrans.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.netdev.views.hardware import HardwareView 2 | from annet.vendors.tabparser import OptixtransFormatter 3 | from annet.vendors.registry import registry 4 | 5 | from .huawei import HuaweiVendor 6 | 7 | 8 | @registry.register 9 | class OptixTransVendor(HuaweiVendor): 10 | NAME = "optixtrans" 11 | 12 | def match(self) -> list[str]: 13 | return ["Huawei.OptiXtrans"] 14 | 15 | @property 16 | def hardware(self) -> HardwareView: 17 | return HardwareView("Huawei OptiXtrans") 18 | 19 | def make_formatter(self, **kwargs) -> OptixtransFormatter: 20 | return OptixtransFormatter(**kwargs) 21 | 22 | def svi_name(self, num: int) -> str: 23 | return f"vlan{num}" 24 | -------------------------------------------------------------------------------- /annet/vendors/library/pc.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from annet.annlib.command import Command, CommandList 4 | from annet.annlib.netdev.views.hardware import HardwareView 5 | from annet.vendors.tabparser import CommonFormatter 6 | from annet.vendors.base import AbstractVendor 7 | from annet.vendors.registry import registry 8 | 9 | 10 | @registry.register 11 | class PCVendor(AbstractVendor): 12 | NAME = "pc" 13 | 14 | def match(self) -> list[str]: 15 | return ["PC"] 16 | 17 | @property 18 | def reverse(self) -> str: 19 | return "-" 20 | 21 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 22 | before, after = CommandList(), CommandList() 23 | 24 | if hw.soft.startswith(("Cumulus", "SwitchDev")): 25 | if os.environ.get("ETCKEEPER_CHECK", False): 26 | before.add_cmd(Command("etckeeper check")) 27 | 28 | return before, after 29 | 30 | @property 31 | def hardware(self) -> HardwareView: 32 | return HardwareView("PC") 33 | 34 | def make_formatter(self, **kwargs) -> CommonFormatter: 35 | return CommonFormatter(**kwargs) 36 | 37 | @property 38 | def exit(self) -> str: 39 | return "" 40 | -------------------------------------------------------------------------------- /annet/vendors/library/ribbon.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import RibbonFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class RibbonVendor(AbstractVendor): 10 | NAME = "ribbon" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | before.add_cmd(Command("configure exclusive")) 16 | if do_commit: 17 | after.add_cmd(Command("commit", timeout=30)) 18 | after.add_cmd(Command("exit")) 19 | 20 | return before, after 21 | 22 | def match(self) -> list[str]: 23 | return ["Ribbon"] 24 | 25 | @property 26 | def reverse(self) -> str: 27 | return "delete" 28 | 29 | @property 30 | def hardware(self) -> HardwareView: 31 | return HardwareView("Ribbon") 32 | 33 | def make_formatter(self, **kwargs) -> RibbonFormatter: 34 | return RibbonFormatter(**kwargs) 35 | 36 | @property 37 | def exit(self) -> str: 38 | return "exit" 39 | 40 | def diff(self, order: bool) -> str: 41 | return "juniper.ordered_diff" if order else "juniper.default_diff" 42 | -------------------------------------------------------------------------------- /annet/vendors/library/routeros.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.command import Command, CommandList 2 | from annet.annlib.netdev.views.hardware import HardwareView 3 | from annet.vendors.tabparser import RosFormatter 4 | from annet.vendors.base import AbstractVendor 5 | from annet.vendors.registry import registry 6 | 7 | 8 | @registry.register 9 | class RouterOSVendor(AbstractVendor): 10 | NAME = "routeros" 11 | 12 | def apply(self, hw: HardwareView, do_commit: bool, do_finalize: bool, path: str) -> tuple[CommandList, CommandList]: 13 | before, after = CommandList(), CommandList() 14 | 15 | # FIXME: пока не удалось победить \x1b[c после включения safe mode 16 | # if len(cmds) > 99: 17 | # raise Exception("RouterOS does not support more 100 actions in safe mode") 18 | # before.add_cmd(RosDevice.SAFE_MODE) 19 | pass 20 | # after.add_cmd(RosDevice.SAFE_MODE) 21 | 22 | return before, after 23 | 24 | def match(self) -> list[str]: 25 | return ["RouterOS"] 26 | 27 | @property 28 | def reverse(self) -> str: 29 | return "remove" 30 | 31 | @property 32 | def hardware(self) -> HardwareView: 33 | return HardwareView("RouterOS") 34 | 35 | def make_formatter(self, **kwargs) -> RosFormatter: 36 | return RosFormatter(**kwargs) 37 | 38 | @property 39 | def exit(self) -> str: 40 | return "" 41 | -------------------------------------------------------------------------------- /annet_generators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/annet_generators/__init__.py -------------------------------------------------------------------------------- /annet_generators/example/__init__.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | from typing import List 3 | 4 | from annet.generators import BaseGenerator 5 | from annet.storage import Storage 6 | 7 | from . import lldp 8 | from . import hostname 9 | 10 | 11 | def get_generators(store: Storage) -> List[BaseGenerator]: 12 | return list(itertools.chain.from_iterable([ 13 | hostname.get_generators(store), 14 | lldp.get_generators(store), 15 | ])) 16 | -------------------------------------------------------------------------------- /annet_generators/mesh_example/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from annet.generators import BaseGenerator 4 | from annet.storage import Storage 5 | from . import bgp 6 | 7 | 8 | def get_generators(store: Storage) -> List[BaseGenerator]: 9 | return bgp.get_generators(store) 10 | -------------------------------------------------------------------------------- /annet_generators/mesh_example/bgp.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from annet.adapters.netbox.common.models import NetboxDevice 4 | from annet.generators import PartialGenerator, BaseGenerator 5 | from annet.mesh import MeshExecutor 6 | from annet.storage import Storage 7 | from .mesh_logic import registry 8 | 9 | 10 | class Bgp(PartialGenerator): 11 | TAGS = ["mgmt", "bgp"] 12 | 13 | def acl_huawei(self, device): 14 | return """ 15 | bgp 16 | peer 17 | """ 18 | 19 | def run_huawei(self, device: NetboxDevice): 20 | executor = MeshExecutor(registry, device.storage) 21 | res = executor.execute_for(device) 22 | yield f"bgp {res.global_options.local_as}" 23 | for peer in res.peers: 24 | yield f" peer {peer.addr} interface {peer.interface}" 25 | if peer.group_name: 26 | yield f" peer {peer.addr} {peer.group_name}" 27 | if peer.remote_as is not None: 28 | yield f" peer {peer.addr} remote-as {peer.remote_as}" 29 | 30 | for group in res.global_options.groups: 31 | yield f" peer {group.name} remote-as {group.remote_as}" 32 | for interface in device.interfaces: 33 | print( 34 | interface.name, 35 | interface.lag_min_links, 36 | interface.lag.name if interface.lag else None, 37 | interface.ip_addresses, 38 | ) 39 | 40 | 41 | def get_generators(store: Storage) -> List[BaseGenerator]: 42 | return [ 43 | Bgp(store), 44 | ] 45 | -------------------------------------------------------------------------------- /annet_generators/mesh_example/mesh_logic.py: -------------------------------------------------------------------------------- 1 | from annet.bgp_models import Redistribute, BFDTimers 2 | from annet.mesh import Right, MeshRulesRegistry, GlobalOptions, MeshSession, DirectPeer, VirtualLocal, VirtualPeer 3 | 4 | registry = MeshRulesRegistry() 5 | 6 | 7 | @registry.device("{name:.*}") 8 | def device_handler(global_opts: GlobalOptions): 9 | global_opts.local_as = 12345 10 | global_opts.ipv4_unicast.redistributes = (Redistribute( 11 | protocol="ipv4", policy="sss", 12 | ),) 13 | global_opts.groups["GROP_NAME"].remote_as = 11111 14 | 15 | 16 | @registry.direct("{name:.*}", "m9-sgw{x}.{domain:.*}") 17 | def direct_handler(device: DirectPeer, neighbor: DirectPeer, session: MeshSession): 18 | session.asnum = 12345 19 | device.addr = "192.168.1.254" 20 | neighbor.addr = f"192.168.1.{neighbor.match.x}" 21 | 22 | 23 | @registry.virtual("{name:.*}", num=[1, 2, 3]) 24 | def virtual_handler(device: VirtualLocal, peer: VirtualPeer, session: MeshSession): 25 | session.asnum = 12345 26 | device.svi = 1 27 | device.addr = "192.168.1.254" 28 | device.listen_network = ["10.0.0.0/8"] 29 | peer.addr = f"192.168.127.{peer.num}" 30 | 31 | 32 | @registry.direct("{name:.*}", "m9-sgw{x}.{domain:.*}", Right.x.in_([0, 1])) 33 | def direct_handler2(device: DirectPeer, neighbor: DirectPeer, session: MeshSession): 34 | session.asnum = 12345 35 | device.addr = "192.168.1.254/24" 36 | device.lag = 1 37 | device.lag_links_min = neighbor.match.x 38 | device.subif = 100 39 | neighbor.families = {"ipv4_unicast"} 40 | neighbor.group_name = "GROUP_NAME" 41 | neighbor.addr = "192.168.1.200/24" 42 | -------------------------------------------------------------------------------- /annet_generators/rpl_example/__init__.py: -------------------------------------------------------------------------------- 1 | from annet.generators import BaseGenerator 2 | from annet.storage import Storage 3 | from . import generator 4 | 5 | 6 | def get_generators(storage: Storage) -> list[BaseGenerator]: 7 | return generator.get_generators(storage) 8 | -------------------------------------------------------------------------------- /annet_generators/rpl_example/items.py: -------------------------------------------------------------------------------- 1 | from ipaddress import IPv6Network 2 | 3 | from annet.rpl_generators import AsPathFilter, CommunityList, CommunityType, RDFilter, ip_prefix_list, CommunityLogic, \ 4 | IpPrefixListMember 5 | 6 | AS_PATH_FILTERS = [ 7 | AsPathFilter("ASP_EXAMPLE", [".*123456.*", ".*22.*"]), 8 | ] 9 | COMMUNITIES = [ 10 | CommunityList("COMMUNITY_EXAMPLE_ADD", ["1234:1000", "1234:1001"], logic=CommunityLogic.AND), 11 | CommunityList("COMMUNITY_EXAMPLE_REMOVE", ["1234:999", "1234:998"], logic=CommunityLogic.OR), 12 | CommunityList("EXTCOMMUNITY_EXAMPLE_ADD", ["12345:1000"], CommunityType.RT), 13 | CommunityList("EXTCOMMUNITY_EXAMPLE_REMOVE", ["12345:999"], CommunityType.RT), 14 | ] 15 | 16 | RD_FILTERS = [ 17 | RDFilter("RD_EXAMPLE1", 1, ["100:1", "200:1"]), 18 | RDFilter("RD_EXAMPLE2", 2, ["10.2.2.2:1", "10.3.3.3:1"]), 19 | ] 20 | 21 | PREFIX_LISTS = [ 22 | ip_prefix_list("IPV6_LIST_EXAMPLE", [ 23 | "2a13:5941::/32", 24 | IpPrefixListMember(IPv6Network("2a13:5942::/32"), or_longer=(32, 48)), 25 | IpPrefixListMember(IPv6Network("2a13:5943::/32"), or_longer=(32, None)), 26 | IpPrefixListMember(IPv6Network("2a13:5944::/32"), or_longer=(32, 32)), 27 | ]), 28 | ip_prefix_list("IPV4_LIST_EXAMPLE", ["0.0.0.0/8", "10.0.0.0/8"]), 29 | ] 30 | -------------------------------------------------------------------------------- /annet_generators/rpl_example/mesh.py: -------------------------------------------------------------------------------- 1 | from annet.mesh import MeshRulesRegistry, GlobalOptions 2 | 3 | registry = MeshRulesRegistry() 4 | 5 | 6 | @registry.device("{name:.*}") 7 | def device_handler(global_opts: GlobalOptions): 8 | global_opts.groups["GROUP1"].import_policy = "example1" 9 | global_opts.groups["GROUP1"].export_policy = "example2" 10 | -------------------------------------------------------------------------------- /docs/_static/annet_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/docs/_static/annet_demo.gif -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_templates/gh-pages-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redirecting to main branch 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/_templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "!page.html" %} 2 | {% block body %} 3 | {% if current_version and latest_version and current_version != latest_version %} 4 |

5 | 6 | {% if current_version.is_released %} 7 | You're reading an old version of this documentation. 8 | If you want up-to-date information, please have a look at {{latest_version.name}}. 9 | {% else %} 10 | You're reading the documentation for a development version. 11 | For the latest released version, please have a look at {{latest_version.name}}. 12 | {% endif %} 13 | 14 |

15 | {% endif %} 16 | {{ super() }} 17 | {% endblock %}% -------------------------------------------------------------------------------- /docs/_templates/versioning.html: -------------------------------------------------------------------------------- 1 | {% if versions %} 2 | 16 | {% endif %} -------------------------------------------------------------------------------- /docs/contrib/lab.rst: -------------------------------------------------------------------------------- 1 | Labs 2 | ========================== 3 | 4 | Learning Labs 5 | ---------------------- 6 | 7 | To learn more about Annet and try it out first-hand, check out our cool laboratory at `GitHub `_ . 8 | In it, you will write some generators, deploy configurations, and understand how changes in the inventory are reflected in configurations through Annet. 9 | -------------------------------------------------------------------------------- /docs/usage/adapters.rst: -------------------------------------------------------------------------------- 1 | Adapters 2 | ========================== 3 | 4 | Extending 5 | Annet uses `Entry Points `_ mechanism for customization. 6 | For example, you can implement the Storage interface on top of your favorite inventory system. 7 | 8 | Storage 9 | ---------------------- 10 | 11 | Must implement `StorageProvider and Storage interface `_ 12 | 13 | Fetcher 14 | ---------------------- 15 | 16 | Deployer 17 | ---------------------- 18 | 19 | -------------------------------------------------------------------------------- /docs/usage/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ================ 3 | 4 | Install Annet from pypi with `gnetcli `_ as a deploy driver 5 | and NetBox as a storage. 6 | 7 | ****************** 8 | 9 | .. code-block:: bash 10 | 11 | mkdir myproject 12 | cd myproject 13 | python3 -m venv venv 14 | source venv/bin/activate 15 | pip install annet[netbox] gnetcli_adapter 16 | 17 | cat > ~/.annet/context.yml<=0.4.6 2 | tabulate>=0.9.0 3 | jsonpatch>=1.33 4 | jsonpointer>=2.4 5 | PyYAML>=6.0.1 6 | Pygments>=2.14.0 7 | Mako>=1.2.4 8 | packaging>=23.2 9 | contextlog>=1.1 10 | valkit>=0.1.4 11 | yarl>=1.8.2 12 | adaptix==3.0.0b7 13 | dataclass-rest==0.4 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import setuptools 4 | 5 | here = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | 8 | def requirements() -> str: 9 | with open(os.path.join(here, "requirements.txt")) as file: 10 | return "".join( 11 | line 12 | for line in file.readlines() 13 | if not line.startswith("-") 14 | ) 15 | 16 | 17 | if __name__ == "__main__": 18 | setuptools.setup( 19 | name="annet", 20 | version=os.getenv("VERSION") or "0.0", 21 | description="annet", 22 | license="MIT", 23 | url="https://github.com/annetutil/annet", 24 | packages=setuptools.find_packages(include=[ 25 | "annet", 26 | "annet.*", 27 | "annet_generators", 28 | "annet_generators.*", 29 | ]), 30 | package_data={ 31 | "annet": ["configs/*"], 32 | "annet.rulebook": ["texts/*.rul", "texts/*.order", "texts/*.deploy"], 33 | "annet.annlib.netdev.devdb": ["data/*.json"], 34 | }, 35 | entry_points={ 36 | "console_scripts": [ 37 | "annet = annet.annet:main", 38 | ], 39 | "annet.connectors": [ 40 | "storage = annet.adapters.netbox.provider:NetboxProvider", 41 | ], 42 | "annet.connectors.storage": [ 43 | "file = annet.adapters.file.provider:Provider", 44 | ], 45 | }, 46 | extras_require={ 47 | "netbox": ["annetbox[sync]>=0.6.0"], 48 | }, 49 | python_requires=">=3.10", 50 | install_requires=requirements(), 51 | include_package_data=True, 52 | ) 53 | -------------------------------------------------------------------------------- /tests/annet/__init__.py: -------------------------------------------------------------------------------- 1 | from annet.annlib.netdev.views import hardware 2 | 3 | 4 | class MockDevice: 5 | def __init__(self, hw_model, sw_version, breed, hostname = "mock-dev1"): 6 | self.hw = hardware.HardwareView(hw_model, sw_version) 7 | self.breed = breed 8 | self.hostname = hostname 9 | -------------------------------------------------------------------------------- /tests/annet/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from annet.hardware import hardware_connector 3 | from annet.rulebook import rulebook_provider_connector 4 | 5 | from annet.hardware import AnnetHardwareProvider 6 | from annet.rulebook import DefaultRulebookProvider 7 | 8 | 9 | @pytest.fixture(scope="session", autouse=True) 10 | def ann_connectors(): 11 | hardware_connector.set(AnnetHardwareProvider) 12 | rulebook_provider_connector.set(DefaultRulebookProvider) 13 | -------------------------------------------------------------------------------- /tests/annet/test_acl/huawei_simple.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | acl: | 3 | block1 4 | block1_sub1 5 | input: | 6 | block1 7 | block1_sub1 8 | block1_sub2 9 | output: | 10 | block1 11 | block1_sub1 12 | -------------------------------------------------------------------------------- /tests/annet/test_acl/huawei_simple_broken_block.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | acl: | 3 | block1 4 | block1_sub 5 | block1_sub_sub1 6 | block.* 7 | block1_sub 8 | block1_sub_sub2 9 | input: | 10 | block1 11 | block1_sub 12 | block1_sub_sub1 13 | block1_sub_sub2 14 | output: | 15 | block1 16 | block1_sub 17 | block1_sub_sub1 18 | block1_sub_sub2 19 | -------------------------------------------------------------------------------- /tests/annet/test_acl/juniper-inactive-match.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | acl: | 3 | protocols 4 | bgp 5 | group TOR 6 | neighbor 7 | ~ 8 | input: | 9 | protocols { 10 | bgp { 11 | group TOR { 12 | type external; 13 | inactive: neighbor fe80::1:b1 { 14 | local-interface ae1.0; 15 | peer-as 65000.65000; 16 | } 17 | neighbor fe80::1:b2 { 18 | local-interface ae2.0; 19 | peer-as 65000.65000; 20 | } 21 | } 22 | group S2 { 23 | type external; 24 | } 25 | } 26 | } 27 | output: | 28 | protocols { 29 | bgp { 30 | group TOR { 31 | inactive: neighbor fe80::1:b1 { 32 | local-interface ae1.0; 33 | peer-as 65000.65000; 34 | } 35 | neighbor fe80::1:b2 { 36 | local-interface ae2.0; 37 | peer-as 65000.65000; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/annet/test_acl/juniper-two-rules-with-global-children.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | acl: | 3 | routing-options %cant_delete=1 4 | rib * 5 | ~ %global=1 6 | routing-options %cant_delete=1 7 | rib inet6.0 %cant_delete=1 8 | static %cant_delete=1 9 | route 127.0.0.1 10 | ~ 11 | input: | 12 | routing-options { 13 | rib inet6.0 { 14 | static { 15 | route ::1/128 discard; 16 | } 17 | } 18 | } 19 | output: | 20 | routing-options { 21 | rib inet6.0 { 22 | static { 23 | route ::1/128 discard; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/annet/test_bgp_models.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from annet.bgp_models import VidCollection, VidRange 4 | 5 | 6 | @pytest.mark.parametrize(["raw", "ranges", "vids"], [ 7 | ( 8 | "1", 9 | [VidRange(1, 1)], 10 | [1], 11 | ), 12 | ( 13 | "1, 2", 14 | [VidRange(1, 1), VidRange(2, 2)], 15 | [1, 2], 16 | ), 17 | ( 18 | "1-4", 19 | [VidRange(1, 4)], 20 | [1, 2, 3, 4], 21 | ), 22 | ( 23 | "1-4,10, 20-22", 24 | [VidRange(1, 4), VidRange(10, 10), VidRange(20, 22)], 25 | [1, 2, 3, 4, 10, 20, 21, 22], 26 | ), 27 | ]) 28 | def test_parse_vid_range(raw, ranges, vids): 29 | collection = VidCollection.parse(raw) 30 | assert collection == VidCollection(ranges) 31 | assert str(collection) == raw.replace(" ", "") 32 | assert list(collection) == vids 33 | -------------------------------------------------------------------------------- /tests/annet/test_cant_delete.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | import pytest 4 | from annet import rulebook 5 | from annet.annlib.rbparser.acl import compile_acl_text 6 | 7 | from annet import patching 8 | from annet.vendors import registry_connector, tabparser 9 | 10 | from . import MockDevice 11 | 12 | 13 | @pytest.fixture 14 | def device(): 15 | return MockDevice("Huawei CE6870-48S6CQ-EI", "VRP V200R001C00SPC700 + V200R001SPH002", "vrp85") 16 | 17 | 18 | @pytest.fixture 19 | def acl(device): 20 | return compile_acl_text( 21 | r""" 22 | interface %cant_delete=1 23 | stays %cant_delete=1 24 | removed 25 | """, 26 | device.hw.vendor 27 | ) 28 | 29 | 30 | @pytest.fixture 31 | def config(device): 32 | return r""" 33 | interface ge1/1/1 34 | stays 35 | removed 36 | """ 37 | 38 | 39 | def test_cant_delete_subblock(config, acl, device): 40 | formatter = registry_connector.get().match(device.hw).make_formatter() 41 | empty_tree = tabparser.parse_to_tree("", formatter.split) 42 | current_tree = tabparser.parse_to_tree(config, formatter.split) 43 | rb = rulebook.get_rulebook(device.hw) 44 | diff = patching.make_diff(current_tree, empty_tree, rb, [acl]) 45 | patch = patching.make_patch(patching.make_pre(diff), rb, device.hw, add_comments=False) 46 | patch = patch.asdict() 47 | assert patch == { 48 | "interface ge1/1/1": OrderedDict([("undo removed", None)]) 49 | } 50 | -------------------------------------------------------------------------------- /tests/annet/test_deploying.py: -------------------------------------------------------------------------------- 1 | import re 2 | from collections import OrderedDict 3 | from unittest import mock 4 | 5 | from annet.annlib.rbparser.deploying import Answer, MakeMessageMatcher 6 | from annet.rulebook.deploying import compile_deploying_text 7 | 8 | 9 | def test_compile_deploying_text_cisco_2_dialogs(ann_connectors): 10 | text = """crypto key generate rsa 11 | dialog: Do you really want to replace them? [yes/no]: ::: no 12 | dialog: How many bits in the modulus [512]: ::: 2048 13 | """ 14 | res = compile_deploying_text(text, "cisco") 15 | expected = OrderedDict([ 16 | ( 17 | "crypto key generate rsa", 18 | { 19 | "attrs": { 20 | "apply_logic": mock.ANY, 21 | "timeout": 30, 22 | "dialogs": OrderedDict([ 23 | ( 24 | MakeMessageMatcher("Do you really want to replace them? [yes/no]:"), 25 | Answer(text="no", send_nl=True) 26 | ), 27 | ( 28 | MakeMessageMatcher("How many bits in the modulus [512]:"), 29 | Answer(text="2048", send_nl=True)) 30 | ]), 31 | 'ifcontext': [], 32 | "ignore": [], 33 | "regexp": re.compile("^crypto\\s+key\\s+generate\\s+rsa(?:\\s|$)") 34 | }, 35 | "children": OrderedDict(), 36 | }, 37 | ), 38 | ]) 39 | 40 | assert res == expected 41 | -------------------------------------------------------------------------------- /tests/annet/test_filter_acl.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | import re 3 | 4 | from annet.annlib import lib 5 | from annet.vendors import tabparser 6 | import annet.annlib.filter_acl 7 | from annet.vendors import registry_connector 8 | 9 | 10 | def test_filter_diff(): 11 | """Specificity of this test: sign `+` in public-key""" 12 | 13 | vendor = "huawei" 14 | diff = textwrap.dedent(""" 15 | - rsa peer-public-key johndoe encoding-type openssh 16 | - public-key-code begin 17 | - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCvdj0k/ptPUbPMXwzPPIBqwMv1MW/xBRlf7Io+hwhV 18 | - rJFIFn88Z9oHdvlvnGWO1R9VR+ZNSkncammcdhDElenqQVndLFnxav77445cLBS/AiyjBOxPv3WI6gxp 19 | - +wtNcbkcJrIixDPTzOy9WRre70FKzvy1eIQK/79C7BSLtSlZgldXEnIrDolImUeMGS/c3KM= rsa-key 20 | - public-key-code end 21 | - foo bar 22 | aaa 23 | local-aaa-user password policy administrator 24 | """).strip() 25 | 26 | fmtr = registry_connector.get()[vendor].make_formatter() 27 | acl = annet.annlib.filter_acl.make_acl("rsa ~\n foo *", vendor) 28 | 29 | assert annet.annlib.filter_acl.filter_diff(acl, fmtr, diff) == textwrap.dedent(""" 30 | - rsa peer-public-key johndoe encoding-type openssh 31 | - foo bar 32 | """).strip() 33 | -------------------------------------------------------------------------------- /tests/annet/test_fs_storage.py: -------------------------------------------------------------------------------- 1 | from annet.adapters.file.provider import FS, Query, StorageOpts, Device 2 | from annet.storage import StorageProvider, Storage 3 | import typing 4 | import tempfile 5 | import platform 6 | import sys 7 | 8 | kwargs = dict() 9 | if platform.system() == "Windows": 10 | if sys.version_info < (3, 12): 11 | kwargs = {"delete": False} 12 | else: 13 | kwargs = {"delete": True, "delete_on_close": False} 14 | 15 | def test_fs(): 16 | Device 17 | with tempfile.NamedTemporaryFile(**kwargs) as f: 18 | f.write(b""" 19 | devices: 20 | - hostname: hostname 21 | fqdn: hostname.domain 22 | vendor: vendor 23 | interfaces: 24 | - name: eth0 25 | description: description 26 | """) 27 | f.flush() 28 | fs = FS(StorageOpts(path=f.name)) 29 | print(fs) 30 | -------------------------------------------------------------------------------- /tests/annet/test_implicit.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict as odict 2 | 3 | import pytest 4 | 5 | from annet import implicit, patching 6 | from annet.annlib import lib 7 | from annet.vendors import tabparser, registry_connector 8 | 9 | from .. import make_hw_stub 10 | 11 | 12 | VENDOR = "huawei" 13 | 14 | 15 | @pytest.fixture 16 | def empty_config(): 17 | formater = registry_connector.get().match(make_hw_stub(VENDOR)).make_formatter() 18 | return tabparser.parse_to_tree("", splitter=formater.split) 19 | 20 | 21 | @pytest.fixture 22 | def config(): 23 | formater = registry_connector.get().match(make_hw_stub(VENDOR)).make_formatter() 24 | return tabparser.parse_to_tree(""" 25 | section_1 26 | section_2 27 | """, splitter=formater.split) 28 | 29 | 30 | @pytest.fixture 31 | def add_nothing(): 32 | return implicit.compile_tree(implicit.parse_text("")) 33 | 34 | 35 | @pytest.fixture 36 | def implicit_rules(): 37 | return implicit.compile_tree(implicit.parse_text(""" 38 | !section_1 39 | added_subcommand 40 | added_command 41 | """)) 42 | 43 | 44 | def text_empty_implicit(config, add_nothing): 45 | assert implicit.config(config, add_nothing) == odict() 46 | 47 | 48 | def test_empty_config(empty_config, implicit_rules): 49 | assert implicit.config(empty_config, implicit_rules) == odict([ 50 | ("added_command", odict()) 51 | ]) 52 | 53 | 54 | def test_subcommand(config, implicit_rules): 55 | assert implicit.config(config, implicit_rules) == odict([ 56 | ("section_1", 57 | odict([("added_subcommand", odict())]) 58 | ), 59 | ("added_command", odict()) 60 | ]) 61 | -------------------------------------------------------------------------------- /tests/annet/test_mesh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/tests/annet/test_mesh/__init__.py -------------------------------------------------------------------------------- /tests/annet/test_netbox.py: -------------------------------------------------------------------------------- 1 | from annet.adapters.netbox.common.query import NetboxQuery 2 | from annet.adapters.netbox.common.storage_base import parse_glob 3 | import pytest 4 | 5 | 6 | def test_parse_glob(): 7 | assert parse_glob(True, NetboxQuery(["host"])) == {"name__ie": ["host"]} 8 | assert parse_glob(False,NetboxQuery(["host"])) == {"name__ic": ["host."]} 9 | assert parse_glob(True, NetboxQuery(["site:mysite"])) == {"site": ["mysite"]} 10 | assert parse_glob(True, NetboxQuery(["tag:mysite", "justhost"])) == {"name__ie": ["justhost"], "tag": ["mysite"]} 11 | with pytest.raises(Exception): 12 | parse_glob(True, NetboxQuery(["host:"])) 13 | with pytest.raises(Exception): 14 | parse_glob(True, NetboxQuery(["NONONO:param"])) 15 | -------------------------------------------------------------------------------- /tests/annet/test_patch.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest import mock 3 | 4 | import pytest 5 | from annet import rulebook 6 | 7 | from annet import deploy, implicit, lib, patching 8 | from annet.vendors import registry_connector 9 | 10 | from .. import make_hw_stub 11 | from . import patch_data 12 | 13 | 14 | @pytest.mark.parametrize( 15 | "name, sample", 16 | patch_data.get_samples(dirname="annet/test_patch") 17 | ) 18 | def test_patch(name, sample, ann_connectors): 19 | vendor = sample.get("vendor", "huawei").lower() 20 | 21 | hw = make_hw_stub(vendor) 22 | if soft := sample.get("soft"): 23 | hw.soft = soft 24 | 25 | rb = rulebook.get_rulebook(hw) 26 | formatter = registry_connector.get().match(hw).make_formatter(indent="") 27 | 28 | old, new, expected_patch = patch_data.get_configs(hw, sample) 29 | 30 | assert old != new 31 | 32 | implicit_rules = implicit.compile_rules(mock.Mock(hw=hw)) 33 | old = lib.merge_dicts(old, implicit.config(old, implicit_rules)) 34 | new = lib.merge_dicts(new, implicit.config(new, implicit_rules)) 35 | 36 | diff = patching.make_diff(old, new, rb, []) 37 | pre = patching.make_pre(diff) 38 | 39 | fake_environ = os.environ.copy() 40 | with mock.patch.object(os, "environ", new=fake_environ): 41 | if sample_environ := sample.get("env"): 42 | fake_environ.update(sample_environ) 43 | 44 | patch_tree = patching.make_patch(pre=pre, rb=rb, hw=hw, add_comments=False) 45 | cmd_paths = formatter.cmd_paths(patch_tree) 46 | cmds = deploy.apply_deploy_rulebook(hw, cmd_paths) 47 | 48 | generated = [] 49 | for cmd in cmds: 50 | generated.append("%s%s\n" % (" " * cmd.level, str(cmd))) 51 | 52 | assert expected_patch == "".join(generated), ("Wrong patch in %s" % name) 53 | -------------------------------------------------------------------------------- /tests/annet/test_patch/arista_acl.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Arista 2 | diff: | 3 | ipv6 access-list RETRANSMIT_RX_lab-dc1-1s2 4 | + 60 permit ipv6 any any dscp 9 5 | + 70 permit ipv6 any any dscp 17 6 | + 80 permit ipv6 any any dscp 25 7 | - 90 permit ipv6 any any dscp 25 8 | + 90 permit ipv6 any any dscp 33 9 | patch: | 10 | conf s 11 | ipv6 access-list RETRANSMIT_RX_lab-dc1-1s2 12 | no 90 13 | 60 permit ipv6 any any dscp 9 14 | 90 permit ipv6 any any dscp 33 15 | 70 permit ipv6 any any dscp 17 16 | 80 permit ipv6 any any dscp 25 17 | exit 18 | commit 19 | write memory 20 | -------------------------------------------------------------------------------- /tests/annet/test_patch/arista_load_sharing.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Arista 2 | diff: '+ ip load-sharing trident fields ipv6 destination-port source-ip ingress-interface 3 | destination-ip source-port 4 | 5 | ' 6 | patch: | 7 | conf s 8 | ip load-sharing trident fields ipv6 destination-port source-ip ingress-interface destination-ip source-port 9 | commit 10 | write memory 11 | 12 | - vendor: Arista 13 | diff: | 14 | - ip load-sharing trident fields ipv6 destination-port source-ip ingress-interface 15 | + ip load-sharing trident fields ipv6 destination-port source-ip ingress-interface destination-ip source-port 16 | patch: | 17 | conf s 18 | ip load-sharing trident fields ipv6 destination-port source-ip ingress-interface destination-ip source-port 19 | commit 20 | write memory 21 | 22 | - vendor: Arista 23 | diff: '- ip load-sharing trident fields ipv6 destination-port source-ip ingress-interface 24 | destination-ip source-port 25 | 26 | ' 27 | patch: | 28 | conf s 29 | default ip load-sharing trident fields ipv6 30 | commit 31 | write memory 32 | -------------------------------------------------------------------------------- /tests/annet/test_patch/aruba_syslog.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Aruba 2 | diff: | 3 | - syslog-level error 4 | + syslog-level critical 5 | patch: | 6 | conf t 7 | syslog-level critical 8 | end 9 | commit apply 10 | write memory 11 | 12 | - vendor: Aruba 13 | diff: | 14 | - syslog-level critical user 15 | + syslog-level error user 16 | - syslog-level warn system 17 | + syslog-level error system 18 | patch: | 19 | conf t 20 | syslog-level error user 21 | syslog-level error system 22 | end 23 | commit apply 24 | write memory 25 | 26 | - vendor: Aruba 27 | diff: "+ syslog-server 172.17.17.254 10.10.205.184 10.8.254.95 \ 28 | \ \n- syslog-server\ 29 | \ 10.10.205.184 \n" 30 | patch: | 31 | conf t 32 | syslog-server 172.17.17.254 10.10.205.184 10.8.254.95 33 | end 34 | commit apply 35 | write memory 36 | -------------------------------------------------------------------------------- /tests/annet/test_patch/b4com_lldp.yaml: -------------------------------------------------------------------------------- 1 | vendor: b4com 2 | before: | 3 | interface xe26 4 | mtu 9216 5 | 6 | after: | 7 | interface xe26 8 | mtu 9216 9 | lldp-agent 10 | set lldp enable txrx 11 | set lldp chassis-id-tlv locally-assigned 12 | set lldp port-id-tlv if-name 13 | lldp tlv basic-mgmt system-name select 14 | lldp tlv basic-mgmt system-description select 15 | 16 | patch: | 17 | conf t 18 | interface xe26 19 | lldp-agent 20 | set lldp enable txrx 21 | set lldp chassis-id-tlv locally-assigned 22 | set lldp port-id-tlv if-name 23 | lldp tlv basic-mgmt system-name select 24 | lldp tlv basic-mgmt system-description select 25 | exit 26 | exit 27 | commit 28 | end 29 | write 30 | -------------------------------------------------------------------------------- /tests/annet/test_patch/b4com_rpl.yaml: -------------------------------------------------------------------------------- 1 | vendor: b4com 2 | before: | 3 | ip prefix-list RFC1918 4 | seq 5 deny 10.0.0.0/8 5 | seq 10 permit 192.168.0.0/16 6 | exit 7 | after: | 8 | ip prefix-list RFC1918 9 | seq 5 deny 10.0.0.0/8 10 | seq 10 permit 192.168.0.0/16 11 | seq 15 permit 172.16.0.0/12 12 | exit 13 | route-map RM_FROM_PEER_IN permit 5 14 | match ip address prefix-list RFC1918 15 | exit 16 | route-map RM_FROM_PEER_IN permit 10 17 | match community CL_FROM_PEER_IN 18 | exit 19 | route-map RM_FROM_PEER_OUT permit 5 20 | match ip address prefix-list RFC1918 21 | exit 22 | ip community-list standard CL_FROM_PEER_IN permit 12345:4321 23 | 24 | patch: | 25 | conf t 26 | ip prefix-list RFC1918 27 | seq 15 permit 172.16.0.0/12 28 | exit 29 | route-map RM_FROM_PEER_IN permit 5 30 | match ip address prefix-list RFC1918 31 | exit 32 | route-map RM_FROM_PEER_IN permit 10 33 | match community CL_FROM_PEER_IN 34 | exit 35 | route-map RM_FROM_PEER_OUT permit 5 36 | match ip address prefix-list RFC1918 37 | exit 38 | ip community-list standard CL_FROM_PEER_IN permit 12345:4321 39 | commit 40 | end 41 | write 42 | -------------------------------------------------------------------------------- /tests/annet/test_patch/bfd_default.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: '- ipv6 route-static default-bfd min-tx-interval 250 min-rx-interval 250 3 | 4 | ' 5 | patch: | 6 | system-view 7 | undo ipv6 route-static default-bfd 8 | q 9 | save 10 | - vendor: Huawei 11 | diff: | 12 | - ipv6 route-static default-bfd min-tx-interval 250 min-rx-interval 250 13 | + ipv6 route-static default-bfd min-tx-interval 250 min-rx-interval 3 14 | patch: | 15 | system-view 16 | ipv6 route-static default-bfd min-tx-interval 250 min-rx-interval 3 17 | q 18 | save 19 | -------------------------------------------------------------------------------- /tests/annet/test_patch/bfd_peer_param_change.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | bgp 1234 4 | - peer SPINE1 bfd enable 5 | - peer SPINE1 bfd min-tx-interval 500 min-rx-interval 500 detect-multiplier 4 6 | patch: | 7 | system-view 8 | bgp 1234 9 | undo peer SPINE1 bfd min-tx-interval 10 | undo peer SPINE1 bfd min-rx-interval 11 | undo peer SPINE1 bfd detect-multiplier 12 | undo peer SPINE1 bfd enable 13 | quit 14 | q 15 | save 16 | - vendor: Huawei 17 | diff: | 18 | bgp 1234 19 | - peer SPINE1 bfd min-tx-interval 1000 min-rx-interval 1000 detect-multiplier 4 20 | + peer SPINE1 bfd min-tx-interval 500 min-rx-interval 500 detect-multiplier 4 21 | patch: | 22 | system-view 23 | bgp 1234 24 | peer SPINE1 bfd min-tx-interval 500 min-rx-interval 500 detect-multiplier 4 25 | quit 26 | q 27 | save 28 | - vendor: Huawei 29 | diff: | 30 | bgp 1234 31 | - peer SPINE1 bfd min-tx-interval 1000 min-rx-interval 1000 detect-multiplier 4 32 | + peer SPINE1 bfd min-tx-interval 500 33 | patch: | 34 | system-view 35 | bgp 1234 36 | undo peer SPINE1 bfd min-rx-interval 37 | undo peer SPINE1 bfd detect-multiplier 38 | peer SPINE1 bfd min-tx-interval 500 39 | quit 40 | q 41 | save 42 | -------------------------------------------------------------------------------- /tests/annet/test_patch/cisco_bgp_peer_group.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Cisco 2 | diff: | 3 | router bgp 65112 4 | + bgp router-id 1.1.1.2 5 | + bgp log-neighbor-changes 6 | + redistribute connected route-map CONNECTED 7 | + maximum-paths 16 8 | + neighbor SPINE peer-group 9 | + neighbor SPINE route-map SPINE_IMPORT in 10 | + neighbor SPINE route-map SPINE_EXPORT out 11 | + neighbor SPINE soft-reconfiguration inbound 12 | + neighbor SPINE send-community both 13 | + neighbor 192.168.22.1 remote-as 65201 14 | + neighbor 192.168.22.1 peer-group SPINE 15 | patch: | 16 | conf t 17 | router bgp 65112 18 | bgp router-id 1.1.1.2 19 | bgp log-neighbor-changes 20 | redistribute connected route-map CONNECTED 21 | maximum-paths 16 22 | neighbor SPINE peer-group 23 | neighbor SPINE route-map SPINE_IMPORT in 24 | neighbor SPINE route-map SPINE_EXPORT out 25 | neighbor SPINE soft-reconfiguration inbound 26 | neighbor SPINE send-community both 27 | neighbor 192.168.22.1 remote-as 65201 28 | neighbor 192.168.22.1 peer-group SPINE 29 | exit 30 | exit 31 | copy running-config startup-config 32 | -------------------------------------------------------------------------------- /tests/annet/test_patch/cisco_iface_ipv4_address.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Cisco 2 | diff: | 3 | interface GigabitEthernet1/0 4 | + ip address 192.168.21.1 255.255.255.0 5 | patch: | 6 | conf t 7 | interface GigabitEthernet1/0 8 | ip address 192.168.21.1 255.255.255.0 9 | exit 10 | exit 11 | copy running-config startup-config 12 | 13 | - vendor: Cisco 14 | diff: | 15 | interface GigabitEthernet1/0 16 | - ip address 192.168.11.2 255.255.255.0 17 | patch: | 18 | conf t 19 | interface GigabitEthernet1/0 20 | no ip address 192.168.11.2 255.255.255.0 21 | exit 22 | exit 23 | copy running-config startup-config 24 | -------------------------------------------------------------------------------- /tests/annet/test_patch/cisco_iface_ipv6_address.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Cisco 2 | diff: | 3 | interface Vlan333 4 | - ipv6 link-local fe80::2/64 5 | + ipv6 link-local fe80::1/64 6 | - ipv6 address fddd::1/64 7 | + ipv6 address fc00::1/64 8 | patch: | 9 | conf t 10 | interface Vlan333 11 | no ipv6 address fddd::1/64 12 | ipv6 address fc00::1/64 13 | ipv6 link-local fe80::1/64 14 | exit 15 | exit 16 | copy running-config startup-config 17 | 18 | - vendor: Cisco 19 | diff: | 20 | interface Vlan333 21 | - ipv6 link-local fe80::2/64 22 | - ipv6 address fddd::1/64 23 | patch: | 24 | conf t 25 | interface Vlan333 26 | no ipv6 address fddd::1/64 27 | no ipv6 link-local 28 | exit 29 | exit 30 | copy running-config startup-config 31 | -------------------------------------------------------------------------------- /tests/annet/test_patch/cisco_physical_iface_delete.yaml: -------------------------------------------------------------------------------- 1 | vendor: Cisco 2 | diff: | 3 | interface Ethernet1/1 4 | description some-description 5 | 6 | -interface Ethernet1/2 7 | -description some-description 8 | patch: | 9 | conf t 10 | interface Ethernet1/2 11 | no description 12 | no mtu 13 | shutdown 14 | exit 15 | exit 16 | copy running-config startup-config 17 | -------------------------------------------------------------------------------- /tests/annet/test_patch/cisco_port_channel_members_config.yaml: -------------------------------------------------------------------------------- 1 | vendor: Cisco 2 | before: | 3 | interface Ethernet1/1 4 | switchport 5 | switchport mode trunk 6 | switchport trunk allowed vlan 2 7 | channel-group 1 mode active 8 | 9 | interface Ethernet1/2 10 | switchport 11 | switchport mode trunk 12 | switchport trunk allowed vlan 2 13 | channel-group 1 mode active 14 | 15 | interface port-channel1 16 | switchport 17 | switchport mode trunk 18 | switchport trunk allowed vlan 2 19 | after: | 20 | interface Ethernet1/1 21 | switchport 22 | switchport mode trunk 23 | switchport trunk allowed vlan 2 24 | mtu 9000 25 | channel-group 1 mode active 26 | 27 | interface Ethernet1/2 28 | switchport 29 | switchport mode trunk 30 | switchport trunk allowed vlan 2 31 | mtu 1500 32 | 33 | interface port-channel1 34 | switchport 35 | switchport mode trunk 36 | switchport trunk allowed vlan 2 37 | mtu 9000 38 | patch: | 39 | conf t 40 | interface port-channel1 41 | mtu 9000 42 | exit 43 | interface Ethernet1/2 44 | no channel-group 45 | switchport mode trunk 46 | switchport trunk allowed vlan add 2 47 | switchport 48 | mtu 1500 49 | no shutdown 50 | exit 51 | exit 52 | copy running-config startup-config 53 | -------------------------------------------------------------------------------- /tests/annet/test_patch/h3_single.yaml: -------------------------------------------------------------------------------- 1 | - vendor: h3c 2 | before: "" 3 | after: | 4 | hostname "test" 5 | patch: | 6 | system-view 7 | hostname "test" 8 | save force 9 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_aaa_privilege_change.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | aaa 4 | local-user test password irreversible-cipher abc123 5 | local-user test privilege level 15 6 | local-user test ftp-directory flash: 7 | local-user test service-type telnet terminal ssh ftp 8 | after: | 9 | aaa 10 | local-user test password irreversible-cipher abc123 11 | local-user test privilege level 3 12 | local-user test ftp-directory flash: 13 | local-user test service-type telnet terminal ssh ftp 14 | patch: | 15 | system-view 16 | aaa 17 | local-user test privilege level 3 18 | quit 19 | q 20 | save 21 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_aaa_privilege_remove.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | aaa 4 | local-user test password irreversible-cipher abc123 5 | local-user test privilege level 15 6 | local-user test ftp-directory flash: 7 | local-user test service-type telnet terminal ssh ftp 8 | after: | 9 | aaa 10 | patch: | 11 | system-view 12 | aaa 13 | undo local-user test ftp-directory 14 | undo local-user test 15 | quit 16 | q 17 | save 18 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_aaa_scheme_domain.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | aaa 4 | domain default 5 | domain default_admin 6 | after: | 7 | aaa 8 | undo local-user policy security-enhance 9 | domain default 10 | domain default_admin 11 | domain localuser 12 | authentication-scheme tacacs 13 | authorization-scheme tacacs 14 | accounting-scheme tacacs 15 | hwtacacs-server tacacs 16 | authentication-scheme default 17 | authentication-mode local 18 | authorization-scheme default 19 | authorization-mode local 20 | accounting-scheme default 21 | accounting-mode none 22 | authentication-scheme tacacs 23 | authentication-mode local hwtacacs 24 | authorization-scheme tacacs 25 | authorization-mode local hwtacacs 26 | accounting-scheme tacacs 27 | accounting-mode hwtacacs 28 | patch: | 29 | system-view 30 | aaa 31 | undo local-user policy security-enhance 32 | authentication-scheme default 33 | authentication-mode local 34 | quit 35 | authorization-scheme default 36 | authorization-mode local 37 | quit 38 | accounting-scheme default 39 | accounting-mode none 40 | quit 41 | authentication-scheme tacacs 42 | authentication-mode local hwtacacs 43 | quit 44 | authorization-scheme tacacs 45 | authorization-mode local hwtacacs 46 | quit 47 | accounting-scheme tacacs 48 | accounting-mode hwtacacs 49 | quit 50 | domain localuser 51 | authentication-scheme tacacs 52 | authorization-scheme tacacs 53 | accounting-scheme tacacs 54 | hwtacacs-server tacacs 55 | quit 56 | quit 57 | q 58 | save 59 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_aaa_scheme_patch.yaml: -------------------------------------------------------------------------------- 1 | # подкоманды объявления aaa-схем всегда просто перезаписываются новым набором 2 | - vendor: huawei 3 | diff: | 4 | aaa 5 | authentication-scheme tacacs 6 | - authentication-mode local none 7 | + authentication-mode hwtacacs local 8 | 9 | authorization-scheme tacacs 10 | - authorization-mode local none 11 | + authorization-mode hwtacacs local 12 | 13 | accounting-scheme tacacs 14 | - accounting-mode none 15 | + accounting-mode hwtacacs 16 | 17 | patch: | 18 | system-view 19 | aaa 20 | authentication-scheme tacacs 21 | authentication-mode hwtacacs local 22 | quit 23 | authorization-scheme tacacs 24 | authorization-mode hwtacacs local 25 | quit 26 | accounting-scheme tacacs 27 | accounting-mode hwtacacs 28 | quit 29 | quit 30 | q 31 | save 32 | 33 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_aaa_tacacs_radius_remove.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | radius-server template test123 4 | hwtacacs-server template test123 5 | aaa 6 | domain default 7 | authentication-scheme default 8 | accounting-scheme default 9 | authorization-scheme default 10 | radius-server test123 11 | hwtacacs-server test123 12 | after: | 13 | aaa 14 | domain default 15 | authentication-scheme default 16 | accounting-scheme default 17 | authorization-scheme default 18 | radius-server default 19 | patch: | 20 | system-view 21 | aaa 22 | domain default 23 | undo hwtacacs-server 24 | undo radius-server 25 | quit 26 | quit 27 | undo radius-server template test123 28 | undo hwtacacs-server template test123 29 | q 30 | save 31 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_aaa_task.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | aaa 4 | task-group user5 5 | task interface-mgr read write execute 6 | task vlan read write execute 7 | after: | 8 | aaa 9 | task-group user5 10 | patch: | 11 | system-view 12 | aaa 13 | task-group user5 14 | undo task interface-mgr 15 | undo task vlan 16 | quit 17 | quit 18 | q 19 | save 20 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_apply_cost.yaml: -------------------------------------------------------------------------------- 1 | vendor: Huawei 2 | diff: | 3 | route-policy RP permit node 10 4 | if-match interface Vlanif802 5 | apply community 64496:1013 6 | - apply cost 10000 7 | patch: | 8 | system-view 9 | route-policy RP permit node 10 10 | undo apply cost 11 | quit 12 | q 13 | save 14 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_bfd_empty_block.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: 'ssh server ip-block disable 3 | 4 | ' 5 | after: | 6 | ssh server ip-block disable 7 | bfd 8 | lldp enable 9 | patch: | 10 | system-view 11 | bfd 12 | quit 13 | lldp enable 14 | q 15 | save 16 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_bgp_family_undo.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | bgp 1234 4 | - ipv6-family unicast 5 | patch: | 6 | system-view 7 | bgp 1234 8 | undo ipv6-family unicast 9 | quit 10 | q 11 | save 12 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_bgp_route_policy.yaml: -------------------------------------------------------------------------------- 1 | vendor: Huawei 2 | diff: | 3 | - route-policy REMOVED_POLICY permit node 10 4 | 5 | bgp 64203.10 6 | group STOR6 external 7 | peer STOR6 as-number 65401 8 | peer fe80::ea1:d2 as-number 65401 9 | peer fe80::ea1:d2 connect-interface Eth-Trunk101.3666 10 | peer fe80::ea1:d2 group STOR6 11 | - peer fe80::ea1:d2 route-policy REMOVED_POLICY export 12 | patch: | 13 | system-view 14 | bgp 64203.10 15 | undo peer fe80::ea1:d2 route-policy REMOVED_POLICY export 16 | quit 17 | undo route-policy REMOVED_POLICY node 10 18 | q 19 | save 20 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_bgp_route_policy2.yaml: -------------------------------------------------------------------------------- 1 | vendor: Huawei 2 | before: "route-policy TOR_EXPORT_S1 deny node 10\n if-match community-filter DONT_ANNOUNCE_COMMUNITY\ 3 | \ \n#\nroute-policy TOR_EXPORT_S1 deny node 20\n if-match community-filter MARKED_COMMUNITY\ 4 | \ \n#\nroute-policy TOR_EXPORT_S1 permit node 30\n" 5 | after: "route-policy TOR_EXPORT_S1 deny node 10\n if-match community-filter DONT_ANNOUNCE_COMMUNITY\ 6 | \ \n#\nroute-policy TOR_EXPORT_S1 permit node 20\n" 7 | patch: | 8 | system-view 9 | route-policy TOR_EXPORT_S1 deny node 20 10 | undo if-match community-filter MARKED_COMMUNITY 11 | quit 12 | route-policy TOR_EXPORT_S1 permit node 20 13 | quit 14 | undo route-policy TOR_EXPORT_S1 node 30 15 | q 16 | save 17 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_classifier_type_change.yaml: -------------------------------------------------------------------------------- 1 | vendor: Huawei 2 | before: | 3 | traffic classifier OR_AND type or 4 | if-match mpls-exp 5 5 | if-match vlan 123 6 | 7 | traffic classifier AND_OR type and 8 | if-match mpls-exp 5 9 | if-match vlan 123 10 | 11 | traffic classifier AND_AND type and 12 | if-match mpls-exp 5 13 | if-match vlan 123 14 | 15 | traffic classifier OR_OR type or 16 | if-match mpls-exp 5 17 | if-match vlan 123 18 | after: | 19 | traffic classifier OR_AND type and 20 | if-match mpls-exp 5 21 | if-match vlan 456 22 | 23 | traffic classifier AND_OR type or 24 | if-match mpls-exp 5 25 | if-match vlan 456 26 | 27 | traffic classifier AND_AND type and 28 | if-match mpls-exp 5 29 | if-match vlan 456 30 | 31 | traffic classifier OR_OR type or 32 | if-match mpls-exp 5 33 | if-match vlan 456 34 | patch: | 35 | system-view 36 | traffic classifier OR_AND type or 37 | undo if-match mpls-exp 38 | undo if-match vlan 123 39 | quit 40 | traffic classifier OR_AND type and 41 | if-match mpls-exp 5 42 | if-match vlan 456 43 | quit 44 | traffic classifier AND_OR type and 45 | undo if-match mpls-exp 46 | undo if-match vlan 123 47 | quit 48 | traffic classifier AND_OR type or 49 | if-match mpls-exp 5 50 | if-match vlan 456 51 | quit 52 | traffic classifier AND_AND type and 53 | undo if-match vlan 123 54 | if-match vlan 456 55 | quit 56 | traffic classifier OR_OR type or 57 | undo if-match vlan 123 58 | if-match vlan 456 59 | quit 60 | q 61 | save 62 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_goto_route_policy.yaml: -------------------------------------------------------------------------------- 1 | vendor: Huawei 2 | diff: | 3 | route-policy IMPORT_DIRECT permit node 3330 4 | if-match interface Vlanif333 5 | apply community 64496:3099 6 | - goto next-node 1000 7 | + goto next-node 2000 8 | 9 | patch: | 10 | system-view 11 | route-policy IMPORT_DIRECT permit node 3330 12 | goto next-node 2000 13 | quit 14 | q 15 | save 16 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_iface_isis.yaml: -------------------------------------------------------------------------------- 1 | vendor: Huawei 2 | before: | 3 | interface 100GE5/0/31:1 4 | undo portswitch 5 | mtu 9000 6 | ip address 10.1.160.247 255.255.255.254 7 | ospf cost 1000 8 | ospf network-type p2p 9 | ospf suppress-reachability 10 | isis enable 1 11 | isis circuit-type p2p 12 | isis cost 1000 13 | isis timer lsp-throttle 10 14 | isis small-hello 15 | isis suppress-reachability 16 | mpls 17 | mpls ldp 18 | qos queue 3 wred WRED_CS3 19 | qos queue 4 wred WRED_CS4 20 | qos wfq 0 to 7 21 | qos queue 0 wfq weight 23 22 | qos queue 1 wfq weight 2 23 | qos queue 2 wfq weight 2 24 | qos queue 3 wfq weight 23 25 | qos queue 4 wfq weight 23 26 | qos queue 5 wfq weight 23 27 | qos queue 6 wfq weight 3 28 | device transceiver 40GBASE-FIBER 29 | 30 | after: | 31 | interface 100GE5/0/31:1 32 | undo portswitch 33 | mtu 9000 34 | ip address 10.1.160.247 255.255.255.254 35 | ospf cost 1000 36 | ospf network-type p2p 37 | ospf suppress-reachability 38 | mpls 39 | mpls ldp 40 | qos queue 3 wred WRED_CS3 41 | qos queue 4 wred WRED_CS4 42 | qos wfq 0 to 7 43 | qos queue 0 wfq weight 23 44 | qos queue 1 wfq weight 2 45 | qos queue 2 wfq weight 2 46 | qos queue 3 wfq weight 23 47 | qos queue 4 wfq weight 23 48 | qos queue 5 wfq weight 23 49 | qos queue 6 wfq weight 3 50 | device transceiver 40GBASE-FIBER 51 | 52 | patch: | 53 | system-view 54 | interface 100GE5/0/31:1 55 | undo isis circuit-type 56 | undo isis cost 57 | undo isis timer lsp-throttle 58 | undo isis small-hello 59 | undo isis suppress-reachability 60 | undo isis enable 61 | quit 62 | q 63 | save 64 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_iface_loopback.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | before: '' 3 | after: 'interface Loopback100 4 | 5 | ' 6 | patch: | 7 | system-view 8 | interface Loopback100 9 | quit 10 | q 11 | save 12 | - vendor: Huawei 13 | before: 'interface Loopback100 14 | 15 | ' 16 | after: '' 17 | patch: | 18 | system-view 19 | undo interface Loopback100 20 | q 21 | save 22 | - vendor: Huawei 23 | before: '' 24 | after: 'interface LoopBack100 25 | 26 | ' 27 | patch: | 28 | system-view 29 | interface LoopBack100 30 | quit 31 | q 32 | save 33 | - vendor: Huawei 34 | before: 'interface LoopBack100 35 | 36 | ' 37 | after: '' 38 | patch: | 39 | system-view 40 | undo interface LoopBack100 41 | q 42 | save 43 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_iface_mpls_ldp_transport_addr.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | interface 10GE1/0/30 4 | + mpls ldp transport-address interface 5 | + mpls 6 | + mpls ldp 7 | patch: | 8 | system-view 9 | interface 10GE1/0/30 10 | mpls 11 | mpls ldp 12 | mpls ldp transport-address interface 13 | quit 14 | q 15 | save 16 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_load_balancing.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | bgp 1234 4 | - maximum load-balancing eibgp 16 5 | + maximum load-balancing 16 6 | patch: | 7 | system-view 8 | bgp 1234 9 | undo maximum load-balancing eibgp 10 | maximum load-balancing 16 11 | quit 12 | q 13 | save 14 | - vendor: Huawei 15 | diff: | 16 | bgp 1234 17 | ipv4-family unicast 18 | - maximum load-balancing ibgp 4 19 | + maximum load-balancing 16 20 | patch: | 21 | system-view 22 | bgp 1234 23 | ipv4-family unicast 24 | undo maximum load-balancing ibgp 25 | maximum load-balancing 16 26 | quit 27 | quit 28 | q 29 | save 30 | - vendor: Huawei 31 | diff: | 32 | bgp 1234 33 | - maximum load-balancing 16 34 | + maximum load-balancing eibgp 16 35 | patch: | 36 | system-view 37 | bgp 1234 38 | undo maximum load-balancing 39 | maximum load-balancing eibgp 16 40 | quit 41 | q 42 | save 43 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_local_user_no_order_in_service_type.yaml: -------------------------------------------------------------------------------- 1 | # У хуавей порядок аргументов в данном месте меняется в зависимости от версии софта 2 | # при этом команда принимается в любом виде, меняется отображение в конфиге 3 | 4 | - vendor: huawei 5 | diff: | 6 | aaa 7 | - local-user user4 service-type telnet terminal ssh ftp 8 | + local-user user4 service-type ftp terminal telnet ssh 9 | 10 | patch: | 11 | 12 | - vendor: huawei 13 | diff: | 14 | aaa 15 | - local-user user5 service-type telnet terminal ssh ftp 16 | + local-user user5 service-type telnet terminal ssh ftp http 17 | 18 | patch: | 19 | system-view 20 | aaa 21 | local-user user5 service-type telnet terminal ssh ftp http 22 | quit 23 | q 24 | save 25 | 26 | - vendor: huawei 27 | diff: | 28 | aaa 29 | - local-user user4 service-type telnet terminal ssh ftp 30 | + local-user user4 service-type ftp terminal telnet ssh 31 | - local-user user5 service-type telnet terminal ssh ftp 32 | + local-user user5 service-type telnet terminal ssh ftp http 33 | 34 | patch: | 35 | system-view 36 | aaa 37 | local-user user5 service-type telnet terminal ssh ftp http 38 | quit 39 | q 40 | save 41 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_multi.yaml: -------------------------------------------------------------------------------- 1 | vendor: Huawei 2 | diff: | 3 | -vlan batch 542 999 1999 4 | -vlan batch 1542 5 | +vlan batch 542 to 599 635 999 6 | +vlan batch 1542 2542 to 2599 2635 2999 7 | +vlan 542 8 | +description nalivka 9 | 10 | interface if1 11 | - port default vlan 542 12 | + port link-type trunk 13 | + undo port trunk allow-pass vlan 1 14 | + port trunk allow-pass vlan 635 700 to 799 15 | 16 | interface if2 17 | port link-type trunk 18 | port trunk pvid vlan 604 19 | undo port trunk allow-pass vlan 1 20 | - port trunk allow-pass vlan 604 761 21 | + port trunk allow-pass vlan 604 to 605 761 22 | 23 | interface if3 24 | - port link-type trunk 25 | - port trunk pvid vlan 604 26 | - undo port trunk allow-pass vlan 1 27 | - port trunk allow-pass vlan 604 to 605 761 28 | + eth-trunk 0 29 | 30 | stp region-configuration 31 | instance 1 vlan 1 2 3 to 4 32 | - instance 2 vlan 1 2 3 to 4 33 | patch: | 34 | system-view 35 | vlan batch 543 to 599 635 2542 to 2599 2635 2999 36 | vlan 542 37 | description nalivka 38 | quit 39 | interface if1 40 | undo port default vlan 41 | port link-type trunk 42 | port trunk allow-pass vlan 635 700 to 799 43 | undo port trunk allow-pass vlan 1 44 | quit 45 | interface if2 46 | port trunk allow-pass vlan 605 47 | quit 48 | interface if3 49 | undo port trunk allow-pass vlan all 50 | port trunk allow-pass vlan 1 51 | undo port trunk pvid vlan 52 | undo port link-type 53 | eth-trunk 0 54 | quit 55 | undo vlan batch 1999 56 | stp region-configuration 57 | undo instance 2 58 | quit 59 | q 60 | save 61 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_ndcase.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | before: | 3 | interface Vlanif333 4 | ipv6 nd ra Min-interval 10 5 | ipv6 nd ra Max-interval 14 6 | after: | 7 | interface Vlanif333 8 | ipv6 nd ra min-interval 10 9 | ipv6 nd ra max-interval 14 10 | 11 | patch: "" 12 | 13 | - vendor: Huawei 14 | before: | 15 | interface Vlanif333 16 | ipv6 nd ra min-interval 10 17 | ipv6 nd ra max-interval 14 18 | after: | 19 | interface Vlanif333 20 | ipv6 nd ra Min-interval 10 21 | ipv6 nd ra Max-interval 14 22 | 23 | patch: "" -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_ne_cpu_defend.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | cpu-defend policy 30 4 | car bgp cir 513 5 | slot 1 6 | cpu-defend-policy 30 7 | after: | 8 | cpu-defend policy 1 9 | car bgp cir 513 10 | slot 1 11 | cpu-defend-policy 1 12 | patch: | 13 | system-view 14 | cpu-defend policy 1 15 | car bgp cir 513 16 | quit 17 | slot 1 18 | cpu-defend-policy 1 19 | quit 20 | undo cpu-defend policy 30 21 | q 22 | save 23 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_netconf.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | netconf 4 | protocol inbound ssh port 830 5 | after: '' 6 | patch: | 7 | system-view 8 | netconf 9 | undo protocol inbound ssh port 830 10 | quit 11 | q 12 | save 13 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_physical_iface_delete.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | interface GE1/0/1 4 | description some-description 5 | 6 | interface GE1/0/2 7 | description some-description 8 | after: | 9 | interface GE1/0/1 10 | description some-description 11 | patch: | 12 | system-view 13 | interface GE1/0/2 14 | undo description 15 | quit 16 | q 17 | save 18 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_port_queue.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | interface 100GE0/1/39 4 | - port-queue af3 wfq weight 30 outbound 5 | + port-queue af3 wfq weight 30 port-wred WRED outbound 6 | patch: | 7 | system-view 8 | interface 100GE0/1/39 9 | undo port-queue af3 wfq outbound 10 | port-queue af3 wfq weight 30 port-wred WRED outbound 11 | quit 12 | q 13 | save 14 | - vendor: Huawei 15 | diff: | 16 | interface 100GE0/1/39 17 | - port-queue ef wfq weight 3 outbound 18 | + port-queue ef wfq weight 3 port-wred WRED outbound 19 | patch: | 20 | system-view 21 | interface 100GE0/1/39 22 | undo port-queue ef wfq outbound 23 | port-queue ef wfq weight 3 port-wred WRED outbound 24 | quit 25 | q 26 | save 27 | - vendor: Huawei 28 | diff: | 29 | interface 100GE0/1/39 30 | - port-queue ef wfq weight 3 outbound 31 | + port-queue ef wfq weight 5 outbound 32 | patch: | 33 | system-view 34 | interface 100GE0/1/39 35 | undo port-queue ef wfq outbound 36 | port-queue ef wfq weight 5 outbound 37 | quit 38 | q 39 | save 40 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_rm_eth_trunk.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: |+ 3 | interface Eth-Trunk159 4 | description dc21-s159 Eth-Trunk2 5 | set flow-stat interval 10 6 | port link-type trunk 7 | undo port trunk allow-pass vlan 1 8 | port trunk allow-pass vlan 3000 to 3100 9 | stp edged-port enable 10 | mode lacp-static 11 | jumboframe enable 9712 12 | 13 | interface 10GE1/0/30 14 | eth-trunk 159 15 | qos wfq 0 to 7 16 | qos queue 0 wfq weight 30 17 | qos queue 1 wfq weight 30 18 | qos queue 2 wfq weight 10 19 | qos queue 3 wfq weight 30 20 | qos queue 4 wfq weight 5 21 | qos queue 6 wfq weight 5 22 | device transceiver 40GBASE-FIBER 23 | 24 | after: |+ 25 | interface 10GE1/0/30 26 | qos wfq 0 to 7 27 | qos queue 0 wfq weight 30 28 | qos queue 1 wfq weight 30 29 | qos queue 2 wfq weight 10 30 | qos queue 3 wfq weight 30 31 | qos queue 4 wfq weight 5 32 | qos queue 6 wfq weight 5 33 | device transceiver 40GBASE-FIBER 34 | 35 | patch: | 36 | system-view 37 | interface 10GE1/0/30 38 | undo eth-trunk 39 | quit 40 | undo interface Eth-Trunk159 41 | q 42 | save 43 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_single.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | vlan batch 542 999 1999 4 | 5 | interface if1 6 | port default vlan 542 7 | 8 | interface if2 9 | port link-type trunk 10 | port trunk pvid vlan 604 11 | undo port trunk allow-pass vlan 1 12 | port trunk allow-pass vlan 604 761 13 | 14 | interface if3 15 | port link-type trunk 16 | port trunk pvid vlan 604 17 | undo port trunk allow-pass vlan 1 18 | port trunk allow-pass vlan 604 to 605 761 19 | after: | 20 | vlan batch 542 to 599 635 999 21 | 22 | vlan 542 23 | description nalivka 24 | 25 | interface if1 26 | port link-type trunk 27 | undo port trunk allow-pass vlan 1 28 | port trunk allow-pass vlan 635 700 to 799 29 | 30 | interface if2 31 | port link-type trunk 32 | port trunk pvid vlan 604 33 | undo port trunk allow-pass vlan 1 34 | port trunk allow-pass vlan 604 to 605 761 35 | 36 | interface if3 37 | eth-trunk 0 38 | patch: | 39 | system-view 40 | vlan batch 543 to 599 635 41 | vlan 542 42 | description nalivka 43 | quit 44 | interface if1 45 | undo port default vlan 46 | port link-type trunk 47 | port trunk allow-pass vlan 635 700 to 799 48 | undo port trunk allow-pass vlan 1 49 | quit 50 | interface if2 51 | port trunk allow-pass vlan 605 52 | quit 53 | interface if3 54 | undo port trunk allow-pass vlan all 55 | port trunk allow-pass vlan 1 56 | undo port trunk pvid vlan 57 | undo port link-type 58 | eth-trunk 0 59 | quit 60 | undo vlan batch 1999 61 | q 62 | save 63 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_srte_policy.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | before: | 3 | segment-routing 4 | segment-list dc232z2_to_labdc132z1 5 | index 2 sid label 173321 6 | sr-te policy dc232z2_to_labdc132z1_color_10 endpoint 10.255.0.67 color 10 7 | candidate-path preference 100 8 | segment-list dc232z2_to_labdc132z1 9 | after: | 10 | segment-routing 11 | segment-list dc232z2_to_labdc132z1_via_m9p2_ 12 | index 1 sid label 160992 13 | index 2 sid label 173321 14 | sr-te policy dc232z2_to_labdc132z1_color_10 endpoint 10.255.0.67 color 10 15 | candidate-path preference 100 16 | segment-list dc232z2_to_labdc132z1_via_m9p2_ 17 | patch: | 18 | system-view 19 | segment-routing 20 | segment-list dc232z2_to_labdc132z1_via_m9p2_ 21 | index 1 sid label 160992 22 | index 2 sid label 173321 23 | quit 24 | sr-te policy dc232z2_to_labdc132z1_color_10 endpoint 10.255.0.67 color 10 25 | candidate-path preference 100 26 | segment-list dc232z2_to_labdc132z1_via_m9p2_ 27 | undo segment-list dc232z2_to_labdc132z1 28 | quit 29 | quit 30 | undo segment-list dc232z2_to_labdc132z1 31 | quit 32 | q 33 | save 34 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_undo_bgp.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | - ip route-static 0.0.0.0 0.0.0.0 172.24.207.254 4 | + ip route-static vpn-instance MEth0/0/0 0.0.0.0 0.0.0.0 172.24.207.254 5 | interface MEth0/0/0 6 | + ip address 172.24.206.189 255.255.252.0 7 | + ip binding vpn-instance MEth0/0/0 8 | - bgp 65001 9 | - peer fe80::c1:d1 as-number 64496 10 | - peer fe80::c1:d1 connect-interface 100GE1/0/1.3000 11 | + bgp 64496 12 | + peer fe80::c1:d1 as-number 64496 13 | + peer fe80::c1:d1 connect-interface 100GE1/0/1.3000 14 | patch: | 15 | system-view 16 | undo bgp 17 | commit 18 | undo ip route-static 0.0.0.0 0.0.0.0 172.24.207.254 19 | interface MEth0/0/0 20 | ip binding vpn-instance MEth0/0/0 21 | ip address 172.24.206.189 255.255.252.0 22 | quit 23 | ip route-static vpn-instance MEth0/0/0 0.0.0.0 0.0.0.0 172.24.207.254 24 | bgp 64496 25 | peer fe80::c1:d1 as-number 64496 26 | peer fe80::c1:d1 connect-interface 100GE1/0/1.3000 27 | quit 28 | q 29 | save 30 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_undo_ipv6_mtu.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | interface Vlanif3001 4 | ipv6 mtu 9000 5 | after: 'interface Vlanif3001 6 | 7 | ' 8 | patch: | 9 | system-view 10 | interface Vlanif3001 11 | undo ipv6 mtu 12 | quit 13 | q 14 | save 15 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_undo_ntp.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | ntp unicast-server 10.10.10.10 preferred 4 | after: '' 5 | patch: | 6 | system-view 7 | undo ntp unicast-server 10.10.10.10 8 | q 9 | save 10 | 11 | - vendor: huawei 12 | before: | 13 | ntp unicast-server domain test 14 | after: '' 15 | patch: | 16 | system-view 17 | undo ntp unicast-server domain test 18 | q 19 | save 20 | 21 | - vendor: huawei 22 | before: | 23 | ntp unicast-server ipv6 2001:4860:4860::8888 preferred 24 | after: '' 25 | patch: | 26 | system-view 27 | undo ntp unicast-server ipv6 2001:4860:4860::8888 28 | q 29 | save 30 | 31 | - vendor: huawei 32 | before: | 33 | ntp-service unicast-server 10.10.10.10 preference 34 | after: '' 35 | patch: | 36 | system-view 37 | undo ntp-service unicast-server 10.10.10.10 38 | q 39 | save 40 | 41 | - vendor: huawei 42 | before: | 43 | ntp-service unicast-server ipv6 2001:4860:4860::8888 preference 44 | after: '' 45 | patch: | 46 | system-view 47 | undo ntp-service unicast-server ipv6 2001:4860:4860::8888 48 | q 49 | save 50 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_undo_peer.yaml: -------------------------------------------------------------------------------- 1 | - vendor: huawei 2 | before: | 3 | bgp 64496 4 | group REMOVED_GROUP external 5 | peer 10.1.1.1 as-number 1 6 | peer 10.1.1.1 description some description for 10.1.1.1 7 | after: | 8 | bgp 64496 9 | peer 10.1.1.1 as-number 1 10 | patch: | 11 | system-view 12 | bgp 64496 13 | undo peer 10.1.1.1 description 14 | undo group REMOVED_GROUP 15 | quit 16 | q 17 | save 18 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_undo_peer2.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | bgp 64215.89 4 | ipv6-family vpn-instance Vpn1 5 | - peer fe80::c1:d1 as-number 64496 6 | - peer fe80::c1:d1 connect-interface 100GE1/0/1.3000 7 | - peer fe80::c1:d1 group SPINE 8 | - peer fe80::c1:d1 description dc1-5d1@et-9/0/9.3000 @ SPINE 9 | patch: | 10 | system-view 11 | bgp 64215.89 12 | undo peer fe80::c1:d1 description 13 | undo peer fe80::c1:d1 14 | quit 15 | q 16 | save 17 | - vendor: Huawei 18 | diff: | 19 | bgp 64215.89 20 | ipv6-family vpn-instance Vpn1 21 | - peer fe80::c1:d1 as-number 64496 22 | - peer fe80::c1:d1 connect-interface 100GE1/0/1.3000 23 | - peer fe80::c1:d1 bfd enable 24 | patch: | 25 | system-view 26 | bgp 64215.89 27 | undo peer fe80::c1:d1 bfd enable 28 | undo peer fe80::c1:d1 29 | quit 30 | q 31 | save 32 | - vendor: Huawei 33 | diff: | 34 | bgp 64215.89 35 | ipv6-family vpn-instance Vpn1 36 | - peer fe80::c1:d1 connect-interface 100GE1/0/1.3000 37 | - peer fe80::c1:d1 group SPINE 38 | - peer fe80::c1:d1 description dc1-5d1@et-9/0/9.3000 @ SPINE 39 | patch: | 40 | system-view 41 | bgp 64215.89 42 | undo peer fe80::c1:d1 description 43 | undo peer fe80::c1:d1 group SPINE 44 | undo peer fe80::c1:d1 connect-interface 45 | quit 46 | q 47 | save 48 | - vendor: Huawei 49 | diff: | 50 | bgp 65000.65001 51 | ipv6-family unicast 52 | - peer fe80::e999:d1 enable 53 | - peer fe80::e999:d1 group TOR 54 | - peer fe80::e999:d1 as-number 111 55 | - peer fe80::e999:d1 group TOR 56 | patch: | 57 | system-view 58 | bgp 65000.65001 59 | ipv6-family unicast 60 | undo peer fe80::e999:d1 group TOR 61 | undo peer fe80::e999:d1 enable 62 | quit 63 | undo peer fe80::e999:d1 64 | quit 65 | q 66 | save 67 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_undo_peer_group_as_number.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | bgp 64514.0 4 | - peer CD_LU as-number 64514.2 5 | ipv4-family vpn-instance EXAMPLE3 6 | - peer CD_V4_EXAMPLE3 as-number 64514.2 7 | ipv6-family vpn-instance EXAMPLE3 8 | - peer CD_V6_EXAMPLE3 as-number 64514.2 9 | 10 | patch: | 11 | system-view 12 | bgp 64514.0 13 | ipv4-family vpn-instance EXAMPLE3 14 | undo peer CD_V4_EXAMPLE3 as-number 15 | quit 16 | ipv6-family vpn-instance EXAMPLE3 17 | undo peer CD_V6_EXAMPLE3 as-number 18 | quit 19 | undo peer CD_LU as-number 20 | quit 21 | q 22 | save 23 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_undo_vpn_instance.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | - route-policy VRF_SomeVrf_EXPORT permit node 10 4 | - ip vpn-instance SomeVrf 5 | - ipv6-family 6 | - route-distinguisher 10.5.226.96:1114 7 | - export route-policy VRF_SomeVrf_EXPORT 8 | - vpn-target 64496:1114 import-extcommunity 9 | bgp 1234 10 | - ipv6-family vpn-instance SomeVrf 11 | patch: | 12 | system-view 13 | bgp 1234 14 | undo ipv6-family vpn-instance SomeVrf 15 | quit 16 | undo ip vpn-instance SomeVrf 17 | undo route-policy VRF_SomeVrf_EXPORT node 10 18 | q 19 | save 20 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_vlan_batch.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: '+ vlan batch 2 to 611 613 to 621 623 to 664 666 to 699 800 to 810 1300 to 3 | 1326 1329 to 1369 1371 to 1599 1601 1603 to 1604 1606 1613 1640 1643 to 1644 1648 4 | 1651 to 1653 1657 1661 1664 to 1665 1676 2000 5 | 6 | ' 7 | patch: | 8 | system-view 9 | vlan batch 2 to 611 613 to 621 623 to 664 666 to 699 800 to 810 1300 to 1326 1329 to 1369 1371 to 1599 1601 1603 to 1604 10 | vlan batch 1606 1613 1640 1643 to 1644 1648 1651 to 1653 1657 1661 1664 to 1665 1676 11 | vlan batch 2000 12 | q 13 | save 14 | - vendor: Huawei 15 | diff: '- vlan batch 2 to 611 613 to 621 623 to 664 666 to 699 800 to 810 1300 to 16 | 1326 1329 to 1369 1371 to 1599 1601 1603 to 1604 1606 1613 1640 1643 to 1644 1648 17 | 1651 to 1653 1657 1661 1664 to 1665 1676 2000 18 | 19 | ' 20 | patch: | 21 | system-view 22 | undo vlan batch 2 to 611 613 to 621 623 to 664 666 to 699 800 to 810 1300 to 1326 1329 to 1369 1371 to 1599 1601 1603 to 1604 23 | undo vlan batch 1606 1613 1640 1643 to 1644 1648 1651 to 1653 1657 1661 1664 to 1665 1676 24 | undo vlan batch 2000 25 | q 26 | save 27 | -------------------------------------------------------------------------------- /tests/annet/test_patch/huawei_vlan_named.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Huawei 2 | diff: | 3 | - vlan 50 4 | - description "vlan50" 5 | - vlan 51 6 | - description "vlan51" 7 | patch: | 8 | system-view 9 | undo vlan 50 10 | undo vlan 51 11 | q 12 | save 13 | - vendor: Huawei 14 | diff: | 15 | + vlan batch 51 16 | - vlan 50 17 | - description "vlan50" 18 | - vlan 51 19 | - description "vlan51" 20 | patch: | 21 | system-view 22 | vlan batch 51 23 | vlan 51 24 | undo description 25 | quit 26 | undo vlan 50 27 | q 28 | save 29 | - vendor: Huawei 30 | diff: | 31 | - vlan batch 50 51 32 | + vlan batch 50 33 | + vlan 51 34 | + description "vlan51" 35 | patch: | 36 | system-view 37 | vlan 51 38 | description "vlan51" 39 | quit 40 | undo vlan batch 51 41 | q 42 | save 43 | -------------------------------------------------------------------------------- /tests/annet/test_patch/juniper_asnum_change.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | before: | 3 | routing-options { 4 | router-id 169.254.255.255; 5 | autonomous-system 200355 independent-domain no-attrset; 6 | } 7 | after: | 8 | routing-options { 9 | router-id 169.254.255.255; 10 | autonomous-system 200356 independent-domain no-attrset; 11 | } 12 | patch: | 13 | configure exclusive 14 | delete routing-options autonomous-system 200355 15 | set routing-options autonomous-system 200356 independent-domain no-attrset 16 | commit 17 | exit 18 | 19 | - vendor: juniper 20 | before: | 21 | routing-instances { 22 | bla-bla { 23 | routing-options { 24 | router-id 169.254.255.255; 25 | autonomous-system 200355 independent-domain no-attrset; 26 | } 27 | } 28 | } 29 | after: | 30 | routing-instances { 31 | bla-bla { 32 | routing-options { 33 | router-id 169.254.255.255; 34 | autonomous-system 200356 independent-domain no-attrset; 35 | } 36 | } 37 | } 38 | patch: | 39 | configure exclusive 40 | delete routing-instances bla-bla routing-options autonomous-system 200355 41 | set routing-instances bla-bla routing-options autonomous-system 200356 independent-domain no-attrset 42 | commit 43 | exit 44 | -------------------------------------------------------------------------------- /tests/annet/test_patch/juniper_comments.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | before: | 3 | system { 4 | hostname "foo" 5 | } 6 | after: |- 7 | system { 8 | /* bar */ 9 | hostname "foo" 10 | } 11 | patch: | 12 | configure exclusive 13 | edit system 14 | annotate hostname "bar" 15 | exit 16 | commit 17 | exit 18 | - vendor: juniper 19 | before: | 20 | system { 21 | /* bar */ 22 | hostname "foo" 23 | } 24 | after: |- 25 | system { 26 | hostname "foo" 27 | } 28 | patch: | 29 | configure exclusive 30 | edit system 31 | annotate hostname "" 32 | exit 33 | commit 34 | exit 35 | - vendor: juniper 36 | before: "" 37 | after: |- 38 | system { 39 | /* bar */ 40 | hostname "foo" 41 | } 42 | patch: | 43 | configure exclusive 44 | set system hostname "foo" 45 | edit system 46 | annotate hostname "bar" 47 | exit 48 | commit 49 | exit 50 | - vendor: juniper 51 | before: |- 52 | system { 53 | /* bar */ 54 | hostname "foo" 55 | } 56 | after: "" 57 | patch: | 58 | configure exclusive 59 | delete system 60 | commit 61 | exit 62 | - vendor: juniper 63 | before: |- 64 | system { 65 | /* bar */ 66 | hostname "foo" 67 | test 1 68 | } 69 | after: |- 70 | system { 71 | test 1 72 | } 73 | patch: | 74 | configure exclusive 75 | edit system 76 | annotate hostname "" 77 | exit 78 | delete system hostname "foo" 79 | commit 80 | exit 81 | -------------------------------------------------------------------------------- /tests/annet/test_patch/juniper_del_forwarding_class.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | diff: | 3 | class-of-service 4 | forwarding-classes 5 | + class CLASS_0 queue-num 0 6 | + class CLASS_1 queue-num 1 7 | + class CLASS_2 queue-num 2 8 | + class CLASS_3 queue-num 3 9 | + class CLASS_4 queue-num 4 10 | + class CLASS_5 queue-num 5 11 | + class CLASS_6 queue-num 6 12 | + class CLASS_7 queue-num 7 13 | - class CS1 queue-num 0 priority low 14 | - class CS2 queue-num 1 priority low 15 | - class CS3 queue-num 2 priority high 16 | - class CS4 queue-num 4 priority low 17 | - class CS5 queue-num 5 priority low 18 | - class CS6 queue-num 3 priority high 19 | - class EF queue-num 1 priority low 20 | patch: | 21 | configure exclusive 22 | delete class-of-service forwarding-classes class CS1 23 | delete class-of-service forwarding-classes class CS2 24 | delete class-of-service forwarding-classes class CS3 25 | delete class-of-service forwarding-classes class CS4 26 | delete class-of-service forwarding-classes class CS5 27 | delete class-of-service forwarding-classes class CS6 28 | delete class-of-service forwarding-classes class EF 29 | set class-of-service forwarding-classes class CLASS_0 queue-num 0 30 | set class-of-service forwarding-classes class CLASS_1 queue-num 1 31 | set class-of-service forwarding-classes class CLASS_2 queue-num 2 32 | set class-of-service forwarding-classes class CLASS_3 queue-num 3 33 | set class-of-service forwarding-classes class CLASS_4 queue-num 4 34 | set class-of-service forwarding-classes class CLASS_5 queue-num 5 35 | set class-of-service forwarding-classes class CLASS_6 queue-num 6 36 | set class-of-service forwarding-classes class CLASS_7 queue-num 7 37 | commit 38 | exit 39 | -------------------------------------------------------------------------------- /tests/annet/test_patch/juniper_local_as.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | before: |- 3 | routing-instances { 4 | some-instance { 5 | protocols { 6 | bgp { 7 | group some-group { 8 | peer-as 65530; 9 | } 10 | } 11 | } 12 | } 13 | } 14 | after: |- 15 | routing-instances { 16 | some-instance { 17 | protocols { 18 | bgp { 19 | group some-group { 20 | peer-as 65530; 21 | local-as 65500 no-prepend-global-as; 22 | } 23 | } 24 | } 25 | } 26 | } 27 | patch: | 28 | configure exclusive 29 | set routing-instances some-instance protocols bgp group some-group local-as 65500 no-prepend-global-as 30 | commit 31 | exit 32 | 33 | - vendor: juniper 34 | before: |- 35 | routing-instances { 36 | some-instance { 37 | protocols { 38 | bgp { 39 | group some-group { 40 | peer-as 65530; 41 | local-as 65500 no-prepend-global-as; 42 | } 43 | } 44 | } 45 | } 46 | } 47 | after: |- 48 | routing-instances { 49 | some-instance { 50 | protocols { 51 | bgp { 52 | group some-group { 53 | peer-as 65530; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | patch: | 60 | configure exclusive 61 | delete routing-instances some-instance protocols bgp group some-group local-as 62 | commit 63 | exit 64 | -------------------------------------------------------------------------------- /tests/annet/test_patch/juniper_mpls_path.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | before: | 3 | protocols { 4 | mpls { 5 | path stdp2_via_dc21e2 { 6 | 10.9.136.239 strict; 7 | 10.10.213.92 strict; 8 | 10.9.136.129 strict; 9 | 10.10.213.32 strict; 10 | 10.5.234.2 strict; 11 | 10.1.172.40 strict; 12 | } 13 | } 14 | } 15 | after: |- 16 | protocols { 17 | mpls { 18 | path stdp2_via_dc21e2 { 19 | 10.1.172.40 strict; 20 | 10.9.136.239 strict; 21 | 10.10.213.92 strict; 22 | 10.9.136.129 strict; 23 | 10.10.213.32 strict; 24 | 10.5.234.2 strict; 25 | } 26 | } 27 | } 28 | patch: | 29 | configure exclusive 30 | delete protocols mpls path stdp2_via_dc21e2 10.1.172.40 31 | delete protocols mpls path stdp2_via_dc21e2 10.9.136.239 32 | delete protocols mpls path stdp2_via_dc21e2 10.10.213.92 33 | delete protocols mpls path stdp2_via_dc21e2 10.9.136.129 34 | delete protocols mpls path stdp2_via_dc21e2 10.10.213.32 35 | delete protocols mpls path stdp2_via_dc21e2 10.5.234.2 36 | set protocols mpls path stdp2_via_dc21e2 10.1.172.40 strict 37 | set protocols mpls path stdp2_via_dc21e2 10.9.136.239 strict 38 | set protocols mpls path stdp2_via_dc21e2 10.10.213.92 strict 39 | set protocols mpls path stdp2_via_dc21e2 10.9.136.129 strict 40 | set protocols mpls path stdp2_via_dc21e2 10.10.213.32 strict 41 | set protocols mpls path stdp2_via_dc21e2 10.5.234.2 strict 42 | commit 43 | exit 44 | -------------------------------------------------------------------------------- /tests/annet/test_patch/juniper_quotes_ignore.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | before: |- 3 | interfaces { 4 | lo0 { 5 | unit 0 { 6 | description "loopbacks"; 7 | } 8 | } 9 | et-0/0/0 { 10 | unit 0 { 11 | description "old description"; 12 | } 13 | } 14 | } 15 | after: |- 16 | interfaces { 17 | lo0 { 18 | unit 0 { 19 | description loopbacks; 20 | } 21 | } 22 | et-0/0/0 { 23 | unit 0 { 24 | description "new description"; 25 | } 26 | } 27 | } 28 | patch: | 29 | configure exclusive 30 | delete interfaces et-0/0/0 unit 0 description "old description" 31 | set interfaces et-0/0/0 unit 0 description "new description" 32 | commit 33 | exit 34 | -------------------------------------------------------------------------------- /tests/annet/test_patch/juniper_syslog.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | before: |- 3 | system { 4 | syslog { 5 | host 10.10.10.10 { 6 | match "!(.*requires a license.*)|(.*LICENSE_EXPIRED:.*)"; 7 | } 8 | } 9 | } 10 | after: |- 11 | system { 12 | syslog { 13 | host 10.10.10.10; 14 | } 15 | } 16 | patch: | 17 | configure exclusive 18 | delete system syslog host 10.10.10.10 match 19 | commit 20 | exit 21 | 22 | - vendor: juniper 23 | before: |- 24 | system { 25 | syslog { 26 | host 10.10.10.10 { 27 | match "!(.*requires a license.*)|(.*LICENSE_EXPIRED:.*)"; 28 | port 10001; 29 | } 30 | } 31 | } 32 | after: |- 33 | system { 34 | syslog { 35 | host 10.10.10.10; 36 | } 37 | } 38 | patch: | 39 | configure exclusive 40 | delete system syslog host 10.10.10.10 match 41 | delete system syslog host 10.10.10.10 port 10001 42 | commit 43 | exit 44 | 45 | - vendor: juniper 46 | before: |- 47 | system { 48 | syslog { 49 | file messages { 50 | any notice; 51 | match "!(.*requires a license.*)|(.*LICENSE_EXPIRED:.*)"; 52 | } 53 | } 54 | } 55 | after: |- 56 | system { 57 | syslog { 58 | file messages { 59 | any notice; 60 | } 61 | } 62 | } 63 | patch: | 64 | configure exclusive 65 | delete system syslog file messages match 66 | commit 67 | exit 68 | -------------------------------------------------------------------------------- /tests/annet/test_patch/juniper_vlan_tags_change.yaml: -------------------------------------------------------------------------------- 1 | - vendor: juniper 2 | before: |- 3 | interfaces { 4 | et-0/0/0 { 5 | unit 0 { 6 | description "old description"; 7 | } 8 | } 9 | } 10 | after: |- 11 | interfaces { 12 | et-0/0/0 { 13 | unit 0 { 14 | description "old description"; 15 | vlan-tags outer 6 inner 28; 16 | } 17 | } 18 | } 19 | patch: | 20 | configure exclusive 21 | set interfaces et-0/0/0 unit 0 vlan-tags outer 6 inner 28 22 | commit 23 | exit 24 | 25 | - vendor: juniper 26 | before: |- 27 | interfaces { 28 | et-0/0/0 { 29 | unit 0 { 30 | description "old description"; 31 | vlan-tags outer 6 inner 28; 32 | } 33 | } 34 | } 35 | after: |- 36 | interfaces { 37 | et-0/0/0 { 38 | unit 0 { 39 | description "old description"; 40 | vlan-tags outer 7 inner 29; 41 | } 42 | } 43 | } 44 | patch: | 45 | configure exclusive 46 | set interfaces et-0/0/0 unit 0 vlan-tags outer 7 inner 29 47 | commit 48 | exit 49 | 50 | - vendor: juniper 51 | before: |- 52 | interfaces { 53 | et-0/0/0 { 54 | unit 0 { 55 | description "old description"; 56 | vlan-tags outer 6 inner 28; 57 | } 58 | } 59 | } 60 | after: |- 61 | interfaces { 62 | et-0/0/0 { 63 | unit 0 { 64 | description "old description"; 65 | } 66 | } 67 | } 68 | patch: | 69 | configure exclusive 70 | delete interfaces et-0/0/0 unit 0 vlan-tags 71 | commit 72 | exit 73 | -------------------------------------------------------------------------------- /tests/annet/test_patch/nexus_lag_member_add.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Nexus 2 | before: | 3 | interface Ethernet1/58 4 | switchport 5 | switchport access vlan 10 6 | mtu 9216 7 | 8 | after: | 9 | interface port-channel999 10 | switchport 11 | switchport access vlan 10 12 | mtu 9216 13 | no shutdown 14 | interface Ethernet1/58 15 | description test 16 | lacp rate fast 17 | switchport 18 | switchport access vlan 10 19 | mtu 9216 20 | channel-group 999 mode active 21 | 22 | patch: | 23 | conf t 24 | interface port-channel999 25 | switchport 26 | switchport access vlan 10 27 | mtu 9216 28 | no shutdown 29 | exit 30 | interface Ethernet1/58 31 | description test 32 | lacp rate fast 33 | channel-group 999 mode active 34 | exit 35 | exit 36 | copy running-config startup-config 37 | -------------------------------------------------------------------------------- /tests/annet/test_patch/nexus_lag_member_remove.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Nexus 2 | before: | 3 | interface port-channel101 4 | description dc1-1x8 po55 5 | mtu 9000 6 | ipv6 traffic-filter RETRANSMIT_RX_dc1-1x8 in 7 | service-policy type qos input CLASSIFIER 8 | no ip redirects 9 | ipv6 link-local fe80::55:c8 10 | ipv6 nd ra-lifetime 0 11 | ipv6 nd suppress-ra 12 | no ipv6 redirects 13 | 14 | interface Ethernet1/9/1 15 | description dc1-1x8 e5/8/1 16 | mtu 9000 17 | channel-group 101 mode active 18 | no shutdown 19 | after: | 20 | interface Ethernet1/9/1 21 | no shutdown 22 | no ipv6 redirects 23 | no ip redirects 24 | ipv6 link-local fe80::55:c8 25 | ipv6 traffic-filter RETRANSMIT_RX_dc1-1x8 in 26 | ipv6 nd suppress-ra 27 | ipv6 nd ra-lifetime 0 28 | description dc1-1x8 e5/8/1 29 | mtu 9000 30 | service-policy type qos input CLASSIFIER 31 | patch: | 32 | conf t 33 | no interface port-channel101 34 | interface Ethernet1/9/1 35 | no channel-group 36 | no ip redirects 37 | ipv6 link-local fe80::55:c8 38 | no ipv6 redirects 39 | ipv6 traffic-filter RETRANSMIT_RX_dc1-1x8 in 40 | ipv6 nd suppress-ra 41 | ipv6 nd ra-lifetime 0 42 | mtu 9000 43 | no shutdown 44 | service-policy type qos input CLASSIFIER 45 | exit 46 | exit 47 | copy running-config startup-config 48 | -------------------------------------------------------------------------------- /tests/annet/test_patch/nokia_single.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Nokia 2 | before: "" 3 | after: | 4 | hostname "test" 5 | patch: | 6 | configure private 7 | /configure hostname "test" 8 | commit 9 | -------------------------------------------------------------------------------- /tests/annet/test_patch/pc_single.yaml: -------------------------------------------------------------------------------- 1 | - vendor: PC 2 | soft: Cumulus 3 | env: 4 | ETCKEEPER_CHECK: "true" 5 | before: "" 6 | after: | 7 | hostname "test" 8 | patch: | 9 | etckeeper check 10 | hostname "test" 11 | -------------------------------------------------------------------------------- /tests/annet/test_patch/ribbon_single.yaml: -------------------------------------------------------------------------------- 1 | - vendor: Ribbon 2 | before: "" 3 | after: | 4 | hostname "test" 5 | patch: | 6 | configure exclusive 7 | set hostname "test" 8 | commit 9 | exit 10 | -------------------------------------------------------------------------------- /tests/annet/test_patch/routeros_single.yaml: -------------------------------------------------------------------------------- 1 | - vendor: RouterOS 2 | before: "" 3 | after: | 4 | system 5 | hostname "test" 6 | patch: | 7 | /system 8 | hostname "test" 9 | -------------------------------------------------------------------------------- /tests/annet/test_pc_deploy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/tests/annet/test_pc_deploy/__init__.py -------------------------------------------------------------------------------- /tests/annet/test_pc_deploy/common.py: -------------------------------------------------------------------------------- 1 | import annet.rulebook.common 2 | 3 | 4 | default = annet.rulebook.common.default 5 | default_diff = annet.rulebook.common.default_diff 6 | -------------------------------------------------------------------------------- /tests/annet/test_pc_deploy/pc/action.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from annet.api import HardwareView 4 | from annet.deploy import CommandList, Command 5 | 6 | 7 | def apply(hw: HardwareView, do_commit: bool, do_finalize: bool, path: Optional[str] = None, **_): 8 | before_cmd = getattr(hw, "__before", "") 9 | after_cmd = getattr(hw, "__after", "") 10 | before, after = CommandList(cmss=[Command(before_cmd)]), CommandList(cmss=[Command(after_cmd)]) 11 | 12 | return before, after 13 | -------------------------------------------------------------------------------- /tests/annet/test_pc_deploy/texts/pc.deploy: -------------------------------------------------------------------------------- 1 | ~ %apply_logic=pc.action.apply 2 | -------------------------------------------------------------------------------- /tests/annet/test_pc_deploy/texts/pc.order: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | ###################################################### 3 | ### THIS IS A STUB FILE PLEASE DO NOT ADD ANYTHING ### 4 | ###################################################### 5 | ###################################################### 6 | -------------------------------------------------------------------------------- /tests/annet/test_pc_deploy/texts/pc.rul: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | ###################################################### 3 | ### THIS IS A STUB FILE PLEASE DO NOT ADD ANYTHING ### 4 | ###################################################### 5 | ###################################################### 6 | -------------------------------------------------------------------------------- /tests/annet/test_rpl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/tests/annet/test_rpl/__init__.py -------------------------------------------------------------------------------- /tests/annet/test_rpl/test_action.py: -------------------------------------------------------------------------------- 1 | from annet.rpl import Action, SingleAction, ActionType 2 | 3 | FIELD1 = "field1" 4 | FIELD2 = "field2" 5 | FIELD3 = "field3" 6 | 7 | 8 | def test_action_list(): 9 | items = [ 10 | SingleAction( 11 | field=FIELD1, 12 | type=ActionType.ADD, 13 | value=1, 14 | ), SingleAction( 15 | field=FIELD2, 16 | type=ActionType.ADD, 17 | value=3, 18 | ), SingleAction( 19 | field=FIELD1, 20 | type=ActionType.ADD, 21 | value=3, 22 | ), 23 | ] 24 | 25 | action = Action() 26 | for item in items: 27 | action.append(item) 28 | 29 | assert repr(action) 30 | assert len(action) == 3 31 | assert action[FIELD1] == SingleAction( 32 | field=FIELD1, 33 | type=ActionType.ADD, 34 | value=1, 35 | ) 36 | assert FIELD1 in action 37 | assert FIELD2 in action 38 | assert FIELD3 not in action 39 | assert list(action) == items 40 | assert list(action.find_all(FIELD1)) == [items[0], items[2]] 41 | assert list(action.find_all(FIELD2)) == [items[1]] 42 | assert list(action.find_all(FIELD3)) == [] 43 | -------------------------------------------------------------------------------- /tests/annet/test_rpl/test_condition.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from annet.rpl import SingleCondition, ConditionOperator 3 | 4 | FIELD1 = "field1" 5 | FIELD2 = "field2" 6 | FIELD3 = "field3" 7 | 8 | 9 | def test_and_condition(): 10 | c1 = SingleCondition( 11 | field=FIELD1, 12 | operator=ConditionOperator.EQ, 13 | value=1, 14 | ) 15 | c2 = SingleCondition( 16 | field=FIELD2, 17 | operator=ConditionOperator.EQ, 18 | value=3, 19 | ) 20 | c3 = SingleCondition( 21 | field=FIELD1, 22 | operator=ConditionOperator.EQ, 23 | value=3, 24 | ) 25 | 26 | condition = c1 & c2 & c3 27 | 28 | assert repr(condition) 29 | assert len(condition) == 3 30 | assert condition[FIELD1] == c1 31 | assert FIELD1 in condition 32 | assert FIELD2 in condition 33 | assert FIELD3 not in condition 34 | assert list(condition) == [c1, c2, c3] 35 | assert list(condition.find_all(FIELD1)) == [c1, c3] 36 | assert list(condition.find_all(FIELD2)) == [c2] 37 | assert list(condition.find_all(FIELD3)) == [] 38 | 39 | 40 | def test_merge_other_field(): 41 | c1 = SingleCondition("f1", ConditionOperator.CUSTOM, "") 42 | c2 = SingleCondition("f2", ConditionOperator.CUSTOM, "") 43 | with pytest.raises(ValueError): 44 | c1.merge(c2) 45 | 46 | 47 | @pytest.mark.parametrize(["v1", "op", "v2", "res"], [ 48 | (1, ConditionOperator.LT, 2, 1), 49 | (1, ConditionOperator.LE, 2, 1), 50 | (1, ConditionOperator.GE, 2, 2), 51 | (1, ConditionOperator.GT, 2, 2), 52 | ]) 53 | def test_cmp(v1, op, v2, res): 54 | c1 = SingleCondition("f1", op, v1) 55 | c2 = SingleCondition("f1", op, v2) 56 | cres = c1.merge(c2) 57 | assert cres.field == c1.field 58 | assert cres.operator == op 59 | assert cres.value == res 60 | 61 | 62 | def test_between(): 63 | c1 = SingleCondition("f1", ConditionOperator.GE, 10) 64 | c2 = SingleCondition("f1", ConditionOperator.LE, 20) 65 | cres = c1.merge(c2) 66 | assert cres.field == c1.field 67 | assert cres.operator == ConditionOperator.BETWEEN_INCLUDED 68 | assert cres.value == (10, 20) 69 | assert c2.merge(c1) == cres -------------------------------------------------------------------------------- /tests/annet/test_rpl/test_match_builder.py: -------------------------------------------------------------------------------- 1 | from annet.rpl import R, ConditionOperator, PrefixMatchValue 2 | 3 | 4 | def test_community(): 5 | c1 = R.community.has("A", "B") 6 | assert c1.field == "community" 7 | assert c1.operator == ConditionOperator.HAS 8 | assert c1.value == ("A", "B") 9 | 10 | 11 | def test_match_v4(): 12 | c1 = R.match_v4("n1", "n2", or_longer=(1, 3)) 13 | assert c1.field == "ip_prefix" 14 | assert c1.operator == ConditionOperator.CUSTOM 15 | assert c1.value == PrefixMatchValue(names=("n1", "n2"), or_longer=(1,3)) 16 | 17 | c1 = R.match_v4("n1", "n2") 18 | assert c1.field == "ip_prefix" 19 | assert c1.operator == ConditionOperator.CUSTOM 20 | assert c1.value == PrefixMatchValue(names=("n1", "n2")) 21 | 22 | 23 | def test_match_v6(): 24 | c1 = R.match_v6("n1", "n2", or_longer=(1, 3)) 25 | assert c1.field == "ipv6_prefix" 26 | assert c1.operator == ConditionOperator.CUSTOM 27 | assert c1.value == PrefixMatchValue(names=("n1", "n2"), or_longer=(1,3)) 28 | 29 | c1 = R.match_v6("n1", "n2") 30 | assert c1.field == "ipv6_prefix" 31 | assert c1.operator == ConditionOperator.CUSTOM 32 | assert c1.value == PrefixMatchValue(names=("n1", "n2")) 33 | -------------------------------------------------------------------------------- /tests/annet/test_rpl_generators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annetutil/annet/565c31ed50cdf837dd0049bd985e369c363ddba2/tests/annet/test_rpl_generators/__init__.py -------------------------------------------------------------------------------- /tests/annet/test_rpl_generators/test_rd.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock 2 | 3 | from annet.rpl import R, RouteMap, Route 4 | from annet.rpl_generators import RDFilter 5 | from .helpers import scrub, iosxr, generate 6 | 7 | 8 | def test_iosxr_rd_match(): 9 | routemaps = RouteMap[Mock]() 10 | 11 | rd_filters = [ 12 | RDFilter("rd1", 1, ["172.16.0.0/16:*", "172.16.0.0/16:100"]), 13 | RDFilter("rd2", 2, ["192:*", "192:100"]), 14 | ] 15 | 16 | @routemaps 17 | def policy(device: Mock, route: Route): 18 | with route(R.rd.has("rd1", "rd2"), name="n20", number=20) as rule: 19 | rule.allow() 20 | with route(R.rd.has_any("rd1", "rd2"), name="n20", number=20) as rule: 21 | rule.allow() 22 | 23 | result = generate(routemaps=routemaps, rd_filters=rd_filters, dev=iosxr()) 24 | expected = scrub(""" 25 | rd-set rd1 26 | 172.16.0.0/16:*, 27 | 172.16.0.0/16:100 28 | rd-set rd2 29 | 192:*, 30 | 192:100 31 | 32 | route-policy policy 33 | if rd in rd1 and rd in rd2 then 34 | done 35 | if (rd in rd1 or rd in rd2) then 36 | done 37 | 38 | """) 39 | assert result == expected -------------------------------------------------------------------------------- /tests/annet/test_rulebooks/test_rulebooks.py: -------------------------------------------------------------------------------- 1 | import pytest as pytest 2 | from annet import rulebook 3 | 4 | from tests import make_hw_stub 5 | from annet.vendors import registry 6 | 7 | 8 | @pytest.fixture(params=list(registry)) 9 | def vendor(request): 10 | return request.param 11 | 12 | 13 | def test_rulebooks(vendor): 14 | """ 15 | Проходимся по всем возможным вендорам и пытаемся получить рулбуки 16 | Если в рулбуке будет синтаксическая ошибка в шаблоне (например как в NOCDEV-12134 %else вместо %else:), тест упадет 17 | """ 18 | hw = make_hw_stub(vendor) 19 | rulebook.get_rulebook(hw) 20 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = ci 3 | skipsdist = True 4 | 5 | [testenv:ci] 6 | envlogdir = .tox-logs 7 | commands = 8 | python tests/ci_indent.py annet/rulebook/texts 9 | flake8 annet annet_generators 10 | pytest -p no:warnings --cov --cov-report xml:coverage.xml -sv --benchmark-skip tests 11 | ignore_errors = True 12 | deps = 13 | -rrequirements-test.txt 14 | -rrequirements.txt 15 | 16 | [testenv:benchmark] 17 | commands = py.test -vv --benchmark-only --benchmark-sort=max tests 18 | deps = 19 | -rrequirements-test.txt 20 | -rrequirements.txt 21 | 22 | [testenv:mypy_ignore] 23 | description = mypy info 24 | ignore_errors = True 25 | 26 | deps = 27 | mypy 28 | -rrequirements-mypy.txt 29 | commands = 30 | - python3 -m mypy --config-file mypy_strict.ini . 31 | 32 | [testenv:mypy] 33 | description = mypy 34 | deps = 35 | mypy 36 | -rrequirements-mypy.txt 37 | commands = 38 | python3 -m mypy --config-file mypy.ini . 39 | 40 | [flake8] 41 | # W503 line break before binary operator 42 | # W504 line break after binary operator 43 | # E125 continuation line with same indent as next logical line 44 | # E241 multiple spaces after 45 | # E272 multiple spaces before keyword 46 | # E126 continuation line over-indented for hanging indent 47 | # E121 continuation line under-indented for hanging indent 48 | # F401 imported but unused (already checked by pylint) 49 | # E402 module level import not at top of file (already checked by pylint) 50 | ignore = W503,W504,E125,E241,E272,E126,E121,F401,E402 51 | 52 | max-line-length = 140 53 | inline-quotes = " 54 | --------------------------------------------------------------------------------