diff --git a/.bandit.yml b/.bandit.yml index ae781ceb..3bd46e2c 100644 --- a/.bandit.yml +++ b/.bandit.yml @@ -3,4 +3,4 @@ exclude_dirs: - "./tests/" - "./docs/" - - "netutils/oui_mappings.py" + - "netutils/data_files/" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95628584..8bada118 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -204,10 +204,10 @@ jobs: run: "docker image ls" - name: "Run Tests" run: "poetry run invoke pytest" - - name: "Install Napalm" - run: "pip install napalm" + - name: "Install Optional Dependencies" + run: "poetry run poetry install --extras optionals" - name: "Run Optional Tests" - run: "poetry run pytest tests/unit/test_lib_helpers_optionals.py" + run: "poetry run pytest tests/unit/test_lib_helpers_optionals.py tests/unit/test_acl.py" needs: - "pylint" publish_gh: diff --git a/.github/workflows/oui_filepull.yml b/.github/workflows/oui_filepull.yml index ab396a64..bc78d92d 100644 --- a/.github/workflows/oui_filepull.yml +++ b/.github/workflows/oui_filepull.yml @@ -3,7 +3,7 @@ name: "OUI-Updates" on: # yamllint disable-line rule:truthy schedule: - - cron: "0 8 * * 4" + - cron: "0 2 1 * *" jobs: data_gathering: diff --git a/.github/workflows/proto_filepull.yml b/.github/workflows/proto_filepull.yml index bf02c00c..f41c4452 100644 --- a/.github/workflows/proto_filepull.yml +++ b/.github/workflows/proto_filepull.yml @@ -3,7 +3,7 @@ name: "Protocol-Updates" on: # yamllint disable-line rule:truthy schedule: - - cron: "0 8 * * 4" + - cron: "0 2 1 * *" jobs: data_gathering: diff --git a/.readthedocs.yml b/.readthedocs.yml index a9d358ef..b60d928e 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -20,3 +20,5 @@ mkdocs: python: install: - requirements: "docs/requirements.txt" + - method: "pip" + path: "." diff --git a/development_scripts.py b/development_scripts.py index 326633a0..3290f1ba 100644 --- a/development_scripts.py +++ b/development_scripts.py @@ -35,89 +35,113 @@ MAPPER = { "LIB_MAPPER_TEMPLATE": { "ansible": { - "header_src": "NORMALIZED", - "header_dst": "ANSIBLE", + "header_src": "ANSIBLE", + "header_dst": "NORMALIZED", "_dict": lib_mapper.ANSIBLE_LIB_MAPPER, "_file": "docs/user/lib_mapper/ansible.md", }, "ansible_reverse": { - "header_src": "ANSIBLE", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "ANSIBLE", "_dict": lib_mapper.ANSIBLE_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/ansible_reverse.md", }, - "napalm": { + "hierconfig": { + "header_src": "HIERCONFIG", + "header_dst": "NORMALIZED", + "_dict": lib_mapper.HIERCONFIG_LIB_MAPPER, + "_file": "docs/user/lib_mapper/hierconfig.md", + }, + "hierconfig_reverse": { "header_src": "NORMALIZED", - "header_dst": "NAPALM", + "header_dst": "HIERCONFIG", + "_dict": lib_mapper.HIERCONFIG_LIB_MAPPER_REVERSE, + "_file": "docs/user/lib_mapper/hierconfig_reverse.md", + }, + "napalm": { + "header_src": "NAPALM", + "header_dst": "NORMALIZED", "_dict": lib_mapper.NAPALM_LIB_MAPPER, "_file": "docs/user/lib_mapper/napalm.md", }, "napalm_reverse": { - "header_src": "NAPALM", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "NAPALM", "_dict": lib_mapper.NAPALM_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/napalm_reverse.md", }, - "ntctemplates": { + "netmiko": { + "header_src": "NETMIKO", + "header_dst": "NORMALIZED", + "_dict": lib_mapper.NETMIKO_LIB_MAPPER, + "_file": "docs/user/lib_mapper/netmiko.md", + }, + "netmiko_reverse": { "header_src": "NORMALIZED", - "header_dst": "NTCTEMPLATES", + "header_dst": "NETMIKO", + "_dict": lib_mapper.NETMIKO_LIB_MAPPER_REVERSE, + "_file": "docs/user/lib_mapper/netmiko_reverse.md", + }, + "netutilsparser": { + "header_src": "NETUTILSPARSER", + "header_dst": "NORMALIZED", + "_dict": lib_mapper.NETUTILSPARSER_LIB_MAPPER, + "_file": "docs/user/lib_mapper/netutilsparser.md", + }, + "netutilsparser_reverse": { + "header_src": "NORMALIZED", + "header_dst": "NETUTILSPARSER", + "_dict": lib_mapper.NETUTILSPARSER_LIB_MAPPER_REVERSE, + "_file": "docs/user/lib_mapper/netutilsparser_reverse.md", + }, + "ntctemplates": { + "header_src": "NTCTEMPLATES", + "header_dst": "NORMALIZED", "_dict": lib_mapper.NTCTEMPLATES_LIB_MAPPER, "_file": "docs/user/lib_mapper/ntctemplates.md", }, "ntctemplates_reverse": { - "header_src": "NTCTEMPLATES", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "NTCTEMPLATES", "_dict": lib_mapper.NTCTEMPLATES_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/ntctemplates_reverse.md", }, "pyats": { - "header_src": "NORMALIZED", - "header_dst": "PYATS", + "header_src": "PYATS", + "header_dst": "NORMALIZED", "_dict": lib_mapper.PYATS_LIB_MAPPER, "_file": "docs/user/lib_mapper/pyats.md", }, "pyats_reverse": { - "header_src": "PYATS", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "PYATS", "_dict": lib_mapper.PYATS_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/pyats_reverse.md", }, "pyntc": { - "header_src": "NORMALIZED", - "header_dst": "PYNTC", + "header_src": "PYNTC", + "header_dst": "NORMALIZED", "_dict": lib_mapper.PYNTC_LIB_MAPPER, "_file": "docs/user/lib_mapper/pyntc.md", }, "pyntc_reverse": { - "header_src": "PYNTC", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "PYNTC", "_dict": lib_mapper.PYNTC_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/pyntc_reverse.md", }, "scrapli": { - "header_src": "NORMALIZED", - "header_dst": "SCRAPLI", + "header_src": "SCRAPLI", + "header_dst": "NORMALIZED", "_dict": lib_mapper.SCRAPLI_LIB_MAPPER, "_file": "docs/user/lib_mapper/scrapli.md", }, "scrapli_reverse": { - "header_src": "SCRAPLI", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "SCRAPLI", "_dict": lib_mapper.SCRAPLI_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/scrapli_reverse.md", }, - "hierconfig": { - "header_src": "HIERCONFIG", - "header_dst": "NORMALIZED", - "_dict": lib_mapper.HIERCONFIG_LIB_MAPPER, - "_file": "docs/user/lib_mapper/hierconfig.md", - }, - "hierconfig_reverse": { - "header_src": "NORMALIZED", - "header_dst": "HIERCONFIG", - "_dict": lib_mapper.HIERCONFIG_LIB_MAPPER_REVERSE, - "_file": "docs/user/lib_mapper/hierconfig_reverse.md", - }, }, "PARSER_MAPPER_TEMPLATE": { "default": { diff --git a/docs/admin/release_notes/version_1.6.md b/docs/admin/release_notes/version_1.6.md new file mode 100755 index 00000000..d0189296 --- /dev/null +++ b/docs/admin/release_notes/version_1.6.md @@ -0,0 +1,36 @@ +# v1.6 Release Notes + +## Release Overview + +- Added `ubiquiti_airos` parser. +- Added `ACLRule` and `ACLRules` classes. +- Added the `ip` functions `is_ip_range`, `is_ip_within`, `is_network`, and `get_range_ips`. +- Added the `asn` function `int_to_asn` in the function. +- Added `NETUTILSPARSER_LIB_MAPPER` and `NETUTILSPARSER_LIB_MAPPER_REVERSE` to lib mapper. +- Added classy docs to view config parser classes. + + +## [v1.6.0] - 2023-09 + +### Added + +- [#290](https://github.com/networktocode/netutils/pull/290) Added `ACLRule` and `ACLRules` classes. +- [#290](https://github.com/networktocode/netutils/pull/290) Added the `ip` functions `is_ip_range`, `is_ip_within`, `is_network`, and `get_range_ips`. +- [#325](https://github.com/networktocode/netutils/pull/325) Added classy docs to view config parser classes. +- [#329](https://github.com/networktocode/netutils/pull/329) Added `ubiquiti_airos` parser. +- [#332](https://github.com/networktocode/netutils/pull/332) Added the `asn` function `int_to_asn` in the function. +- [#352](https://github.com/networktocode/netutils/pull/352) Added `NETUTILSPARSER_LIB_MAPPER` and `NETUTILSPARSER_LIB_MAPPER_REVERSE` to lib mapper. + +### Changed + +- [#346](https://github.com/networktocode/netutils/pull/346) Updated `mac_to_format` docs. +- [#352](https://github.com/networktocode/netutils/pull/352) Clean up docs and sorting for lib mapper. +- [#358](https://github.com/networktocode/netutils/pull/358) Changed flatbot schedule. + +### Fixed + +- [#339](https://github.com/networktocode/netutils/pull/339) Fix for ASN functions. +- [#340](https://github.com/networktocode/netutils/pull/340) Fixed Netscaler parser slug. +- [#346](https://github.com/networktocode/netutils/pull/346) Fix issue where interface abbreviation not working. +- [#350](https://github.com/networktocode/netutils/pull/350) Update library mappings to be correct order. +- [#356](https://github.com/networktocode/netutils/pull/356) Fixed mkdocs dependencies. diff --git a/docs/assets/acl-workflow.drawio b/docs/assets/acl-workflow.drawio new file mode 100644 index 00000000..ef750b63 --- /dev/null +++ b/docs/assets/acl-workflow.drawio @@ -0,0 +1,367 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/dev/classy/parser.md b/docs/dev/classy/parser.md new file mode 100644 index 00000000..1709fa60 --- /dev/null +++ b/docs/dev/classy/parser.md @@ -0,0 +1,4 @@ +--- +classy_dotted_path: netutils.config.parser.BaseConfigParser +--- +# Classy Doc diff --git a/docs/dev/code_reference/acl.md b/docs/dev/code_reference/acl.md new file mode 100644 index 00000000..d13ca277 --- /dev/null +++ b/docs/dev/code_reference/acl.md @@ -0,0 +1,5 @@ +# ACLs + +::: netutils.acl + options: + show_submodules: True \ No newline at end of file diff --git a/docs/dev/arch_decision.md b/docs/dev/dev_adr.md similarity index 100% rename from docs/dev/arch_decision.md rename to docs/dev/dev_adr.md diff --git a/docs/dev/dev_config.md b/docs/dev/dev_config.md index 86901d56..c612d520 100644 --- a/docs/dev/dev_config.md +++ b/docs/dev/dev_config.md @@ -32,7 +32,7 @@ The conversion from curly to set format via the `paloalto_panos_brace_to_set` fu ### Duplicate Line Detection -In some circumstances replacing lines, such as secrets without uniqueness in the replacement, will result in duplicated lines that are invalid configuration, such as:: +In some circumstances replacing lines, such as secrets without uniqueness in the replacement, will result in duplicated lines that are invalid configuration, such as: ```text snmp-server community <> RO SNMP_ACL_RO @@ -71,6 +71,9 @@ There are a series of considerations documented below, when developing a new par - The class must provide a `self.config_lines` that is a list of `ConfigLine` named tuples. - Build tests for the `tests/unit/mock/config/compliance/compliance/{os_name}/*` and `tests/unit/mock/config/parser/base/{os_name}/*`. - Add to `netutils/config/compliance.py` the `parser_map`, that maps the name of the parser to the Plugin. + - Ensure that the key name is based on `netutils.lib_mapper.MAIN_LIB_MAPPER_LIB_MAPPER` definitions. + - Ensure that it is alpha sorted. + - Ensure that it the key is added to `netutils.lib_mapper.NETUTILSPARSER_LIB_MAPPER` and `netutils.lib_mapper.NETUTILSPARSER_LIB_MAPPER_REVERSE`. - Fill out docstrings in the class and methods within the class that describe the parameters and an Example that compiles. - The following tips will generally be applicable. - Generally a class method should provide a `comment_chars` and `banner_start` as well as sometimes `banner_end`. diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md index 9fabda72..ea607075 100644 --- a/docs/dev/dev_environment.md +++ b/docs/dev/dev_environment.md @@ -51,7 +51,7 @@ Poetry is used in lieu of the "virtualenv" commands and is leveraged in both env The `pyproject.toml` file outlines all of the relevant dependencies for the project: - `tool.poetry.dependencies` - the main list of dependencies. -- `tool.poetry.dev-dependencies` - development dependencies, to facilitate linting, testing, and documentation building. +- `tool.poetry.group.dev.dependencies` - development dependencies, to facilitate linting, testing, and documentation building. The `poetry shell` command is used to create and enable a virtual environment managed by Poetry, so all commands ran going forward are executed within the virtual environment. This is similar to running the `source venv/bin/activate` command with virtualenvs. To install project dependencies in the virtual environment, you should run `poetry install` - this will install **both** project and development dependencies. diff --git a/docs/dev/include_parser_list.md b/docs/dev/include_parser_list.md index 721a2255..0d70a62c 100644 --- a/docs/dev/include_parser_list.md +++ b/docs/dev/include_parser_list.md @@ -15,6 +15,8 @@ | linux | netutils.config.parser.LINUXConfigParser | | mikrotik_routeros | netutils.config.parser.RouterOSConfigParser | | mrv_optiswitch | netutils.config.parser.OptiswitchConfigParser | +| netscaler | netutils.config.parser.NetscalerConfigParser | | nokia_sros | netutils.config.parser.NokiaConfigParser | | paloalto_panos | netutils.config.parser.PaloAltoNetworksConfigParser | -| ruckus_fastiron | netutils.config.parser.FastironConfigParser | \ No newline at end of file +| ruckus_fastiron | netutils.config.parser.FastironConfigParser | +| ubiquiti_airos | netutils.config.parser.UbiquitiAirOSConfigParser | \ No newline at end of file diff --git a/docs/images/acl-workflow.png b/docs/images/acl-workflow.png new file mode 100644 index 00000000..d7a990eb Binary files /dev/null and b/docs/images/acl-workflow.png differ diff --git a/docs/requirements.txt b/docs/requirements.txt index 8e223b74..ff233eef 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,8 +1,9 @@ -mkdocs==1.4.3 +mkdocs==1.5.2 # Material for MkDocs theme -mkdocs-material==8.5.8 +mkdocs-material==9.2.4 +mkdocs-python-classy==0.1.3 # Render custom markdown for version added/changed/remove notes mkdocs-version-annotations==1.0.0 # Automatic documentation from sources, for MkDocs mkdocstrings==0.22.0 -mkdocstrings-python==1.1.2 \ No newline at end of file +mkdocstrings-python==1.6.0 \ No newline at end of file diff --git a/docs/user/include_jinja_list.md b/docs/user/include_jinja_list.md index 4be27d36..156ee645 100644 --- a/docs/user/include_jinja_list.md +++ b/docs/user/include_jinja_list.md @@ -1,6 +1,7 @@ | Filter name | Function | | ---------- | ------ | | asn_to_int | netutils.asn.asn_to_int | +| int_to_asdot | netutils.asn.int_to_asdot | | bits_to_name | netutils.bandwidth.bits_to_name | | bytes_to_name | netutils.bandwidth.bytes_to_name | | name_to_bits | netutils.bandwidth.name_to_bits | @@ -33,6 +34,7 @@ | get_broadcast_address | netutils.ip.get_broadcast_address | | get_first_usable | netutils.ip.get_first_usable | | get_peer_ip | netutils.ip.get_peer_ip | +| get_range_ips | netutils.ip.get_range_ips | | get_usable_range | netutils.ip.get_usable_range | | ip_addition | netutils.ip.ip_addition | | ip_subtract | netutils.ip.ip_subtract | @@ -43,7 +45,10 @@ | ipaddress_network | netutils.ip.ipaddress_network | | is_classful | netutils.ip.is_classful | | is_ip | netutils.ip.is_ip | +| is_ip_range | netutils.ip.is_ip_range | +| is_ip_within | netutils.ip.is_ip_within | | is_netmask | netutils.ip.is_netmask | +| is_network | netutils.ip.is_network | | netmask_to_cidr | netutils.ip.netmask_to_cidr | | get_napalm_getters | netutils.lib_helpers.get_napalm_getters | | get_oui | netutils.mac.get_oui | diff --git a/docs/user/lib_mapper/ansible.md b/docs/user/lib_mapper/ansible.md index 716de08b..0bb17484 100644 --- a/docs/user/lib_mapper/ansible.md +++ b/docs/user/lib_mapper/ansible.md @@ -1,4 +1,4 @@ -| NORMALIZED | | ANSIBLE | +| ANSIBLE | | NORMALIZED | | ---------- | -- | ------ | | arista.eos.eos | → | arista_eos | | arubanetworks.aoscx | → | aruba_aoscx | diff --git a/docs/user/lib_mapper/ansible_reverse.md b/docs/user/lib_mapper/ansible_reverse.md index 907c0dc8..c7c9c873 100644 --- a/docs/user/lib_mapper/ansible_reverse.md +++ b/docs/user/lib_mapper/ansible_reverse.md @@ -1,4 +1,4 @@ -| ANSIBLE | | NORMALIZED | +| NORMALIZED | | ANSIBLE | | ---------- | -- | ------ | | arista_eos | → | arista.eos.eos | | aruba_aoscx | → | arubanetworks.aoscx | diff --git a/docs/user/lib_mapper/napalm.md b/docs/user/lib_mapper/napalm.md index 4216e4c0..9625bd41 100644 --- a/docs/user/lib_mapper/napalm.md +++ b/docs/user/lib_mapper/napalm.md @@ -1,9 +1,10 @@ -| NORMALIZED | | NAPALM | +| NAPALM | | NORMALIZED | | ---------- | -- | ------ | | aoscx | → | aruba_aoscx | | asa | → | cisco_asa | | cisco_wlc_ssh | → | cisco_wlc | | eos | → | arista_eos | +| f5 | → | bigip_f5 | | fortios | → | fortinet | | huawei | → | huawei_vrp | | ios | → | cisco_ios | diff --git a/docs/user/lib_mapper/napalm_reverse.md b/docs/user/lib_mapper/napalm_reverse.md index a4b7b8b3..f929cd41 100644 --- a/docs/user/lib_mapper/napalm_reverse.md +++ b/docs/user/lib_mapper/napalm_reverse.md @@ -1,7 +1,8 @@ -| NAPALM | | NORMALIZED | +| NORMALIZED | | NAPALM | | ---------- | -- | ------ | | arista_eos | → | eos | | aruba_aoscx | → | aoscx | +| bigip_f5 | → | f5 | | brocade_vyos | → | vyos | | cisco_asa | → | asa | | cisco_ios | → | ios | diff --git a/docs/user/lib_mapper/netmiko.md b/docs/user/lib_mapper/netmiko.md new file mode 100644 index 00000000..36610fbf --- /dev/null +++ b/docs/user/lib_mapper/netmiko.md @@ -0,0 +1,110 @@ +| NETMIKO | | NORMALIZED | +| ---------- | -- | ------ | +| a10 | → | a10 | +| accedian | → | accedian | +| adtran_os | → | adtran_os | +| alcatel_aos | → | alcatel_aos | +| alcatel_sros | → | alcatel_sros | +| allied_telesis_awplus | → | allied_telesis_awplus | +| apresia_aeos | → | apresia_aeos | +| arista_eos | → | arista_eos | +| aruba_os | → | aruba_os | +| aruba_osswitch | → | aruba_osswitch | +| aruba_procurve | → | aruba_procurve | +| avaya_ers | → | avaya_ers | +| avaya_vsp | → | avaya_vsp | +| broadcom_icos | → | broadcom_icos | +| brocade_fastiron | → | brocade_fastiron | +| brocade_fos | → | brocade_fos | +| brocade_netiron | → | brocade_netiron | +| brocade_nos | → | brocade_nos | +| brocade_vdx | → | brocade_vdx | +| brocade_vyos | → | brocade_vyos | +| calix_b6 | → | calix_b6 | +| centec_os | → | centec_os | +| checkpoint_gaia | → | checkpoint_gaia | +| ciena_saos | → | ciena_saos | +| cisco_asa | → | cisco_asa | +| cisco_ftd | → | cisco_ftd | +| cisco_ios | → | cisco_ios | +| cisco_nxos | → | cisco_nxos | +| cisco_s300 | → | cisco_s300 | +| cisco_tp | → | cisco_tp | +| cisco_wlc | → | cisco_wlc | +| cisco_xe | → | cisco_xe | +| cisco_xr | → | cisco_xr | +| cloudgenix_ion | → | cloudgenix_ion | +| coriant | → | coriant | +| dell_dnos9 | → | dell_dnos9 | +| dell_force10 | → | dell_force10 | +| dell_isilon | → | dell_isilon | +| dell_os10 | → | dell_os10 | +| dell_os6 | → | dell_os6 | +| dell_os9 | → | dell_os9 | +| dell_powerconnect | → | dell_powerconnect | +| dlink_ds | → | dlink_ds | +| eltex | → | eltex | +| eltex_esr | → | eltex_esr | +| endace | → | endace | +| enterasys | → | enterasys | +| ericsson_ipos | → | ericsson_ipos | +| extreme | → | extreme | +| extreme_ers | → | extreme_ers | +| extreme_exos | → | extreme_exos | +| extreme_netiron | → | extreme_netiron | +| extreme_nos | → | extreme_nos | +| extreme_slx | → | extreme_slx | +| extreme_vdx | → | extreme_vdx | +| extreme_vsp | → | extreme_vsp | +| extreme_wing | → | extreme_wing | +| f5_linux | → | f5_linux | +| f5_ltm | → | f5_ltm | +| f5_tmsh | → | f5_tmsh | +| flexvnf | → | flexvnf | +| fortinet | → | fortinet | +| generic | → | generic | +| generic_termserver | → | generic_termserver | +| hp_comware | → | hp_comware | +| hp_procurve | → | hp_procurve | +| huawei | → | huawei | +| huawei_olt | → | huawei_olt | +| huawei_smartax | → | huawei_smartax | +| huawei_vrpv8 | → | huawei_vrpv8 | +| ipinfusion_ocnos | → | ipinfusion_ocnos | +| juniper | → | juniper | +| juniper_junos | → | juniper_junos | +| juniper_screenos | → | juniper_screenos | +| keymile | → | keymile | +| keymile_nos | → | keymile_nos | +| linux | → | linux | +| mellanox | → | mellanox | +| mellanox_mlnxos | → | mellanox_mlnxos | +| mikrotik_routeros | → | mikrotik_routeros | +| mikrotik_switchos | → | mikrotik_switchos | +| mrv_lx | → | mrv_lx | +| mrv_optiswitch | → | mrv_optiswitch | +| netapp_cdot | → | netapp_cdot | +| netgear_prosafe | → | netgear_prosafe | +| netscaler | → | netscaler | +| nokia_sros | → | nokia_sros | +| oneaccess_oneos | → | oneaccess_oneos | +| ovs_linux | → | ovs_linux | +| paloalto_panos | → | paloalto_panos | +| pluribus | → | pluribus | +| quanta_mesh | → | quanta_mesh | +| rad_etx | → | rad_etx | +| raisecom_roap | → | raisecom_roap | +| ruckus_fastiron | → | ruckus_fastiron | +| ruijie_os | → | ruijie_os | +| sixwind_os | → | sixwind_os | +| sophos_sfos | → | sophos_sfos | +| tplink_jetstream | → | tplink_jetstream | +| ubiquiti_edge | → | ubiquiti_edge | +| ubiquiti_edgerouter | → | ubiquiti_edgerouter | +| ubiquiti_edgeswitch | → | ubiquiti_edgeswitch | +| ubiquiti_unifiswitch | → | ubiquiti_unifiswitch | +| vyatta_vyos | → | vyatta_vyos | +| vyos | → | vyos | +| watchguard_fireware | → | watchguard_fireware | +| yamaha | → | yamaha | +| zte_zxros | → | zte_zxros | \ No newline at end of file diff --git a/docs/user/lib_mapper/netmiko_reverse.md b/docs/user/lib_mapper/netmiko_reverse.md new file mode 100644 index 00000000..9a9bd6c4 --- /dev/null +++ b/docs/user/lib_mapper/netmiko_reverse.md @@ -0,0 +1,110 @@ +| NORMALIZED | | NETMIKO | +| ---------- | -- | ------ | +| a10 | → | a10 | +| accedian | → | accedian | +| adtran_os | → | adtran_os | +| alcatel_aos | → | alcatel_aos | +| alcatel_sros | → | alcatel_sros | +| allied_telesis_awplus | → | allied_telesis_awplus | +| apresia_aeos | → | apresia_aeos | +| arista_eos | → | arista_eos | +| aruba_os | → | aruba_os | +| aruba_osswitch | → | aruba_osswitch | +| aruba_procurve | → | aruba_procurve | +| avaya_ers | → | avaya_ers | +| avaya_vsp | → | avaya_vsp | +| broadcom_icos | → | broadcom_icos | +| brocade_fastiron | → | brocade_fastiron | +| brocade_fos | → | brocade_fos | +| brocade_netiron | → | brocade_netiron | +| brocade_nos | → | brocade_nos | +| brocade_vdx | → | brocade_vdx | +| brocade_vyos | → | brocade_vyos | +| calix_b6 | → | calix_b6 | +| centec_os | → | centec_os | +| checkpoint_gaia | → | checkpoint_gaia | +| ciena_saos | → | ciena_saos | +| cisco_asa | → | cisco_asa | +| cisco_ftd | → | cisco_ftd | +| cisco_ios | → | cisco_ios | +| cisco_nxos | → | cisco_nxos | +| cisco_s300 | → | cisco_s300 | +| cisco_tp | → | cisco_tp | +| cisco_wlc | → | cisco_wlc | +| cisco_xe | → | cisco_xe | +| cisco_xr | → | cisco_xr | +| cloudgenix_ion | → | cloudgenix_ion | +| coriant | → | coriant | +| dell_dnos9 | → | dell_dnos9 | +| dell_force10 | → | dell_force10 | +| dell_isilon | → | dell_isilon | +| dell_os10 | → | dell_os10 | +| dell_os6 | → | dell_os6 | +| dell_os9 | → | dell_os9 | +| dell_powerconnect | → | dell_powerconnect | +| dlink_ds | → | dlink_ds | +| eltex | → | eltex | +| eltex_esr | → | eltex_esr | +| endace | → | endace | +| enterasys | → | enterasys | +| ericsson_ipos | → | ericsson_ipos | +| extreme | → | extreme | +| extreme_ers | → | extreme_ers | +| extreme_exos | → | extreme_exos | +| extreme_netiron | → | extreme_netiron | +| extreme_nos | → | extreme_nos | +| extreme_slx | → | extreme_slx | +| extreme_vdx | → | extreme_vdx | +| extreme_vsp | → | extreme_vsp | +| extreme_wing | → | extreme_wing | +| f5_linux | → | f5_linux | +| f5_ltm | → | f5_ltm | +| f5_tmsh | → | f5_tmsh | +| flexvnf | → | flexvnf | +| fortinet | → | fortinet | +| generic | → | generic | +| generic_termserver | → | generic_termserver | +| hp_comware | → | hp_comware | +| hp_procurve | → | hp_procurve | +| huawei | → | huawei | +| huawei_olt | → | huawei_olt | +| huawei_smartax | → | huawei_smartax | +| huawei_vrpv8 | → | huawei_vrpv8 | +| ipinfusion_ocnos | → | ipinfusion_ocnos | +| juniper | → | juniper | +| juniper_junos | → | juniper_junos | +| juniper_screenos | → | juniper_screenos | +| keymile | → | keymile | +| keymile_nos | → | keymile_nos | +| linux | → | linux | +| mellanox | → | mellanox | +| mellanox_mlnxos | → | mellanox_mlnxos | +| mikrotik_routeros | → | mikrotik_routeros | +| mikrotik_switchos | → | mikrotik_switchos | +| mrv_lx | → | mrv_lx | +| mrv_optiswitch | → | mrv_optiswitch | +| netapp_cdot | → | netapp_cdot | +| netgear_prosafe | → | netgear_prosafe | +| netscaler | → | netscaler | +| nokia_sros | → | nokia_sros | +| oneaccess_oneos | → | oneaccess_oneos | +| ovs_linux | → | ovs_linux | +| paloalto_panos | → | paloalto_panos | +| pluribus | → | pluribus | +| quanta_mesh | → | quanta_mesh | +| rad_etx | → | rad_etx | +| raisecom_roap | → | raisecom_roap | +| ruckus_fastiron | → | ruckus_fastiron | +| ruijie_os | → | ruijie_os | +| sixwind_os | → | sixwind_os | +| sophos_sfos | → | sophos_sfos | +| tplink_jetstream | → | tplink_jetstream | +| ubiquiti_edge | → | ubiquiti_edge | +| ubiquiti_edgerouter | → | ubiquiti_edgerouter | +| ubiquiti_edgeswitch | → | ubiquiti_edgeswitch | +| ubiquiti_unifiswitch | → | ubiquiti_unifiswitch | +| vyatta_vyos | → | vyatta_vyos | +| vyos | → | vyos | +| watchguard_fireware | → | watchguard_fireware | +| yamaha | → | yamaha | +| zte_zxros | → | zte_zxros | \ No newline at end of file diff --git a/docs/user/lib_mapper/netutilsparser.md b/docs/user/lib_mapper/netutilsparser.md new file mode 100644 index 00000000..52f4d9db --- /dev/null +++ b/docs/user/lib_mapper/netutilsparser.md @@ -0,0 +1,22 @@ +| NETUTILSPARSER | | NORMALIZED | +| ---------- | -- | ------ | +| arista_eos | → | arista_eos | +| aruba_aoscx | → | aruba_aoscx | +| bigip_f5 | → | bigip_f5 | +| cisco_aireos | → | cisco_aireos | +| cisco_asa | → | cisco_asa | +| cisco_ios | → | cisco_ios | +| cisco_iosxr | → | cisco_iosxr | +| cisco_nxos | → | cisco_nxos | +| citrix_netscaler | → | citrix_netscaler | +| extreme_netiron | → | extreme_netiron | +| fortinet_fortios | → | fortinet_fortios | +| juniper_junos | → | juniper_junos | +| linux | → | linux | +| mikrotik_routeros | → | mikrotik_routeros | +| mrv_optiswitch | → | mrv_optiswitch | +| netscaler | → | netscaler | +| nokia_sros | → | nokia_sros | +| paloalto_panos | → | paloalto_panos | +| ruckus_fastiron | → | ruckus_fastiron | +| ubiquiti_airos | → | ubiquiti_airos | \ No newline at end of file diff --git a/docs/user/lib_mapper/netutilsparser_reverse.md b/docs/user/lib_mapper/netutilsparser_reverse.md new file mode 100644 index 00000000..c6b2f36c --- /dev/null +++ b/docs/user/lib_mapper/netutilsparser_reverse.md @@ -0,0 +1,22 @@ +| NORMALIZED | | NETUTILSPARSER | +| ---------- | -- | ------ | +| arista_eos | → | arista_eos | +| aruba_aoscx | → | aruba_aoscx | +| bigip_f5 | → | bigip_f5 | +| cisco_aireos | → | cisco_aireos | +| cisco_asa | → | cisco_asa | +| cisco_ios | → | cisco_ios | +| cisco_iosxr | → | cisco_iosxr | +| cisco_nxos | → | cisco_nxos | +| citrix_netscaler | → | citrix_netscaler | +| extreme_netiron | → | extreme_netiron | +| fortinet_fortios | → | fortinet_fortios | +| juniper_junos | → | juniper_junos | +| linux | → | linux | +| mikrotik_routeros | → | mikrotik_routeros | +| mrv_optiswitch | → | mrv_optiswitch | +| netscaler | → | netscaler | +| nokia_sros | → | nokia_sros | +| paloalto_panos | → | paloalto_panos | +| ruckus_fastiron | → | ruckus_fastiron | +| ubiquiti_airos | → | ubiquiti_airos | \ No newline at end of file diff --git a/docs/user/lib_mapper/ntctemplates.md b/docs/user/lib_mapper/ntctemplates.md index e5d2ff86..61524043 100644 --- a/docs/user/lib_mapper/ntctemplates.md +++ b/docs/user/lib_mapper/ntctemplates.md @@ -1,4 +1,4 @@ -| NORMALIZED | | NTCTEMPLATES | +| NTCTEMPLATES | | NORMALIZED | | ---------- | -- | ------ | | a10 | → | a10 | | accedian | → | accedian | diff --git a/docs/user/lib_mapper/ntctemplates_reverse.md b/docs/user/lib_mapper/ntctemplates_reverse.md index 61524043..e5d2ff86 100644 --- a/docs/user/lib_mapper/ntctemplates_reverse.md +++ b/docs/user/lib_mapper/ntctemplates_reverse.md @@ -1,4 +1,4 @@ -| NTCTEMPLATES | | NORMALIZED | +| NORMALIZED | | NTCTEMPLATES | | ---------- | -- | ------ | | a10 | → | a10 | | accedian | → | accedian | diff --git a/docs/user/lib_mapper/pyats.md b/docs/user/lib_mapper/pyats.md index b6e7886f..1ee1d38c 100644 --- a/docs/user/lib_mapper/pyats.md +++ b/docs/user/lib_mapper/pyats.md @@ -1,4 +1,4 @@ -| NORMALIZED | | PYATS | +| PYATS | | NORMALIZED | | ---------- | -- | ------ | | asa | → | cisco_asa | | bigip | → | f5_tmsh | diff --git a/docs/user/lib_mapper/pyats_reverse.md b/docs/user/lib_mapper/pyats_reverse.md index 61acbd08..d26bd04f 100644 --- a/docs/user/lib_mapper/pyats_reverse.md +++ b/docs/user/lib_mapper/pyats_reverse.md @@ -1,4 +1,4 @@ -| PYATS | | NORMALIZED | +| NORMALIZED | | PYATS | | ---------- | -- | ------ | | cisco_asa | → | asa | | cisco_dnac | → | dnac | diff --git a/docs/user/lib_mapper/pyntc.md b/docs/user/lib_mapper/pyntc.md index 5ff18d52..e6bdde4f 100644 --- a/docs/user/lib_mapper/pyntc.md +++ b/docs/user/lib_mapper/pyntc.md @@ -1,4 +1,4 @@ -| NORMALIZED | | PYNTC | +| PYNTC | | NORMALIZED | | ---------- | -- | ------ | | arista_eos_eapi | → | arista_eos | | cisco_aireos_ssh | → | cisco_wlc | diff --git a/docs/user/lib_mapper/pyntc_reverse.md b/docs/user/lib_mapper/pyntc_reverse.md index 46495200..9615063b 100644 --- a/docs/user/lib_mapper/pyntc_reverse.md +++ b/docs/user/lib_mapper/pyntc_reverse.md @@ -1,4 +1,4 @@ -| PYNTC | | NORMALIZED | +| NORMALIZED | | PYNTC | | ---------- | -- | ------ | | arista_eos | → | arista_eos_eapi | | cisco_asa | → | cisco_asa_ssh | diff --git a/docs/user/lib_mapper/scrapli.md b/docs/user/lib_mapper/scrapli.md index bf296069..d08f35a7 100644 --- a/docs/user/lib_mapper/scrapli.md +++ b/docs/user/lib_mapper/scrapli.md @@ -1,4 +1,4 @@ -| NORMALIZED | | SCRAPLI | +| SCRAPLI | | NORMALIZED | | ---------- | -- | ------ | | arista_eos | → | arista_eos | | aruba_aoscx | → | aruba_aoscx | diff --git a/docs/user/lib_mapper/scrapli_reverse.md b/docs/user/lib_mapper/scrapli_reverse.md index 8cfa8840..ffb981a7 100644 --- a/docs/user/lib_mapper/scrapli_reverse.md +++ b/docs/user/lib_mapper/scrapli_reverse.md @@ -1,4 +1,4 @@ -| SCRAPLI | | NORMALIZED | +| NORMALIZED | | SCRAPLI | | ---------- | -- | ------ | | arista_eos | → | arista_eos | | aruba_aoscx | → | aruba_aoscx | diff --git a/docs/user/lib_use_cases.md b/docs/user/lib_use_cases.md index 2e01aa04..b184a21c 100644 --- a/docs/user/lib_use_cases.md +++ b/docs/user/lib_use_cases.md @@ -6,12 +6,13 @@ As the Python library is intended to be a low-level implementation, the primary Functions are grouped with like functions, such as IP or MAC address based functions. Included to date are groupings of: +- ASN - Provides the ability to convert BGP ASN from integer to dot notation and back. - Bandwidth - Provides the ability to convert between various bandwidth values. - Banner - Provides the ability to normalize the various banner delimiters. -- BGP ASN - Provides the ability to convert BGP ASN from integer to dot notation. - Configuration - Cleaning - Provides the ability to remove or replace lines based on regex matches. - Compliance - Provides the ability to compare two configurations to sanely understand the differences. + - Conversion - Provides the ability to convert between different syntax's within the same OS. - Parsing - Provides the ability to parse configuration for the minor differences that are there. - DNS - Provides the ability to work with DNS, such as validating that a FQDN is resolvable. - Interface - Provides the ability to work with interface names, expanding, abbreviating, and splitting the names. @@ -19,11 +20,13 @@ Functions are grouped with like functions, such as IP or MAC address based funct - Library Helpers - Provides helpers to pull useful information, e.g. NAPALM getters. - Library Mapper - Provides mappings in expected vendor names between Netmiko, NAPALM, pyntc, ntc-templates, pyats, and scrapli. - MAC Address - Provides the ability to work with MAC addresses such as validating or converting to integer. +- OS Version - Provides the ability to work with OS version, such as defining an upgrade path. - Password - Provides the ability to compare and encrypt common password schemas such as type5 and type7 Cisco passwords. - Ping - Provides the ability to ping, currently only tcp ping. - Protocol Mapper - Provides a mapping for protocol names to numbers and vice versa. +- Regex - Provide convenience methods for regex to be used in Jinja2. - Route - Provides the ability to provide a list of routes and an IP Address and return the longest prefix matched route. -- Time -Provides the ability to convert between integer time and string times. +- Time - Provides the ability to convert between integer time and string times. - VLANs - Provide the ability to convert configuration into lists or lists into configuration. ## Examples diff --git a/docs/user/lib_use_cases_acl.md b/docs/user/lib_use_cases_acl.md new file mode 100644 index 00000000..49cfe583 --- /dev/null +++ b/docs/user/lib_use_cases_acl.md @@ -0,0 +1,304 @@ +# ACL + +The ACL classes are intended to help guide the ACL conversation. It is not intended to solve every ACL challenge you may have. In essence, it provides sane defaults and welcomes you to extend the logic via supported extension mechanisms. There are three patterns that heavily make up the capabilities: + +- Expanding data to the ["Cartesian product"](#cartesian-product) (or permutations) of each rule, so that each product can be easily evaluated. +- Providing a `f"{type}_*` method pattern, to dynamically find any `validate_*` or `enforce_*` method you provide. +- Providing a `f"{type}_{attr}` method pattern, to dynamically find any `process_{attr}` or `match_{attr}` method you provide for the given attrs. + +Each of these are covered in detail, below in the [core concepts](#core-concepts) section. + +Here you can see how the Python classes work together. There is a lot going on, so I encourage you to review the diagram briefly, and refer back to it often while reviewing the detailed information below. + +![ACL Classes](../images/acl-workflow.png) + +!!! info + It may be helpful to open the diagram in a new tab to view the full size, as an example, in Chrome you can right-click on the image and select "Open Image on New Tab". + +The intention of this page is not to cover every attribute and it's behavior, but a more human (although highly technical) understanding of what is going on. For more detailed information, please see the [test](https://github.com/networktocode/netutils/blob/develop/tests/unit/test_acl.py) and [code docs](../../dev/code_reference/acl/). + +!!! info + In the future the intention is to add features such as better de-duplication, partial match, and path analysis. + +## Core Concepts + +### Cartesian Product + +This ["Cartesian product"](https://en.wikipedia.org/wiki/Cartesian_product) concept is used throughout the page, so I thought it would be good to review. In this example, we have a single `rule`, and like many rules, it has multiple sources, destinations, and protocols. The `_cartesian_product` function creates the permutations, each of which is technically called a product. + + +```python +>>> from netutils.acl import _cartesian_product +>>> rule = dict( +... name="Allow to internal web", +... src_ip=["192.168.0.0/24", "10.0.0.0/16"], +... dst_ip=["172.16.0.0/16", "192.168.250.10-192.168.250.20"], +... dst_port=["tcp/80", "udp/53"], +... action="permit", +... ) +>>> for item in _cartesian_product(rule): +... print(item) +... +{'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '172.16.0.0/16', 'dst_port': 'tcp/80', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '172.16.0.0/16', 'dst_port': 'udp/53', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'tcp/80', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'udp/53', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '172.16.0.0/16', 'dst_port': 'tcp/80', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '172.16.0.0/16', 'dst_port': 'udp/53', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'tcp/80', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'udp/53', 'action': 'permit'} +>>> +``` + +Now that you have the Cartesian products, you can evaluate each one individually. In this example perhaps '192.168.0.0/24' -> '172.16.0.0/16' is allowed, but '192.168.0.0/24' -> '192.168.250.10-192.168.250.20' is denied. Yet another example is the access could be allowed on both sources and destinations IPs, but not for udp/53 (DNS). + +Having the ability to look at each product individually allows you to only have to worry about the check you wish to create, versus custom logic that attempts to understand the permutations. Building a `is_pci_to_non_pci` becomes trivial when looking at each product. This idea applies to validating, enforcing, matching, etc. + + +### Dynamic Method - Attrs + +The methods `process` and `match` both follow this pattern. As an example, the `process` method will dynamically find any method that follows `f"process_{attr}` pattern. This allows a Python class that inherits from `ACLRule` to simply add a `process_src_ip` method and that method would be called. + +### Dynamic Method - Any + +The methods `validate` and `enforce` both follow this pattern. As an example, the `validate` method will dynamically find any method that follows `f"process_*` pattern. This allows a Python class that inherits from `ACLRule` to simply add a `validate_ip_in_network` method and that method would be called. + +In both methods, ordering can be controlled with order_validate and order_enforce respectively. The default ordering will be alphabetical, as returned by the Python dir function. + +## ACLRule + +The `ACLRule` class at a high level: + +- The class `ACLRule` is used for working with Access Control List (ACL) rules. +- It is built with extensibility in mind to allow you to customize how your business operates. +- It contains a list of attributes such as "name," "src_ip," "src_zone," "dst_ip," "dst_port," "dst_zone," and "action" that are commonly used to work with ACLs. +- Provides the ability for you to expand data such as converting an address-group name into detailed addresses with your custom code. +- Provides the Cartesian product (or permutations) of the rules to make evaluation simple. +- It provides options for verifying input data and result data (data expanded) with corresponding JSON schemas. +- Provides the ability to validate data, generally for tech feasibility testing such as "are IPs on our network and in IPAM" or "is NAT IP provided vs actual IP". +- Provides the ability to enforce data, generally for security testing such as "is PCI attempting to talk with a non-PCI environment" or "is IP range not narrowly scoped enough". + - The class supports a matrix feature, which allows users to define custom rule matching based on predefined matrix definitions. +- Provides the ability to match one rule to another, to understand if rule exists already. + +### Initialization & Loading Data + +The initialization process calls on the `load_data` method. This on a high level verifies schema of initial data, allows you to process data (e.g. convert tcp/https -> 80/443), expand data, determine Cartesian product (or permutations) of the firewall rule (traditionally 5-tuple), and verifies schema of result data. + +The Cartesian product (or permutations) is key to the functionality of other steps, this allows you to evaluate each rule based on the smallest view of the data, so pay close attention to those steps, as it is important to other methods as well. + +To provide some ideas on what you may validate: + +- Is one of the source or destination IPs in our network? +- Is one of the source or destination IPs in our IPAM? +- Is the source and destination IPs able to route to each other? +- Is the source and destination IPs on the same network? +- Is one of the destination IPs to the real IP vs the NAT IP? +- Is there routing from the source zone to the destination zone? +- Is the business unit name in the description ? + +Many of validations will be based on IPs, but not all. + +Here you will find a written understanding of what is happening in the code: + +- The init method takes in data and calls `load_data`. +- The method `load_data` processes the input data. + - The `input_data_check` method is called and verifies the input data based on the specified JSON schema. + - This is controlled by the `input_data_verify` attribute and schema defined in `input_data_schema`. + - For each `self.attrs`, a method name matching `f"process_{attr}"`, (e.g. `process_src_ip()`) is called. + - This allows you to inherit from and provide your own custom processes to convert, expand, or otherwise modify data before being evaluated. + - The `process_dst_port` method processes the `dst_port` attribute by converting the protocol and port information, it is enabled by default but controlled with the `dst_port_process` attribute. + - Both a dictionary `self.processed` and attributes (e.g. self.action, self.src_ip, etc.) are created. + - The `result_data_check` method verifies the processed data based on the specified JSON schema. + - This is controlled by the `result_data_verify` attribute which is disabled by default. + - The `validate` method validating the rule using a series of custom methods starting with `validate_` prefixes. + - The ordering can be controlled based on the `order_validate` attribute or defaults to all matches. + - The rules are expanded into `self.expanded_rules` by creating each permutations of the tuple, using a Cartesian product function. + - An example permutations would be converting source: 10.1.1.1, 10.1.1.2, destination: 10.100.100.100, port: 80 -> 10.1.1.1, destination: 10.100.100.100, port: 80 and 10.1.1.2, destination: 10.100.100.100, port: 80. + - This will be key, so that each permutations can be compared individually later. + - Filter out the permutations that have the same source and destination IP, if `self.filter_same_ip` which is on by default. + +### Enforce + +Enforce is generally used for security controls. An `enforce_matrix` is provided but not used by default. You can think of the matrix as an Excel sheet, in which you have source as the rows and destination as the column. You would identify the source/destination IP and find which x & y coordinates in your Excel document, and perform whatever action it states, such as deny, review, approve, etc. + +To provide some ideas on what you may enforce: + +- Is the request for a PCI to non-PCI network? +- Is the request for a security-zone-X to security-zone-Y network? +- Is the protocol a secure protocol? +- Is the request approved? +- Is the IPs requested narrowly scoped? + +Here you will find a written understanding of what is happening in the code: + +- The `enforce` method validating the rule using a series of custom methods starting with `enforce_` prefixes such as `enforce_pci_checks`. + - The ordering can be controlled based on the `order_enforce` attribute or defaults to all matches. + - The `enforce_matrix` method enforces ACL rules based on a predefined matrix feature. + - This is controlled by the `self.matrix_enforced` attribute and is off by default. + - The `enforce_matrix` method runs the enforcement checks for each of the `self.expanded_rules` (or permutations of tuples) + - This matrix definition is very simple and not likely ready to be be used in a production environment, instead used for simple demonstrations and communicating potential ideas. + - Each method should return a dictionary or list of dictionaries as both of these are handled + - In the example there is the `obj` and `action` key. + - This could and should be extended, such as providing obj, action, detail_msg, notification_team, and any other metadata that the tooling using this system would require. + - Catastrophic errors will raise an error. + +While not accurate in all use cases it would be best practice to run any of your custom `enforce_` against `self.expanded_rules`. + +### Match & Match Details + +The `match_details` method provides a verbose way of verifying match details between two ACL rule's, the `match` method uses `match_details` and provides a boolean if there are any rules in `rules_unmatched` which would tell you if you had a full match or not. We will only review in detail the `match_details`. + +Here you will find a written understanding of what is happening in the code: + +- The `self.expanded_rules` is looped over for every permutations. + - For each `self.attrs`, a method name matching `f"match_{attr}"`, (e.g. `match_src_ip()`) is called. + - This allows you to inherit from and provide your own custom equality check or verify with your business logic. + - You do not need to have a `f"match_{attr}"` method for every attr, description as example would not be a good candidate to match on. + - Equality checks are done on `src_zone`, `dst_zone`, `action`, and `port` by default. + - An `is_ip_within` check is done with for `src_ip` and `dst_ip` by default. +- In the process, details are provided for and returned: + - `rules_matched` - Root key that is a list of dictionaries of rules that matched. + - `rules_unmatched` - Root key that is a list of dictionaries of rules that did not match. + - `existing_rule_product` - The original expanded_rule that existed in this item. + - `existing_rule` - The full original rule (not expanded_rule) that existed. + - `match_rule` - The full original rule that tested against, only shown in `rules_matched` root key. + +This data could help you to understand what matched, why it matched, and other metadata. This detail data can be used to in `ACLRules` to aggregate and ask more interesting questions. + +## ACLRules + +The `ACLRules` class at a high level: + +- Loads up multiple `ACLRule` and loads the data from a list of dictionaries. + - This is generally the data that exists on the firewall already, but there are other use cases. +- Allows you to match the multiple `ACLRule` objects, and test against a single `ACLRule` object. + - This is generally to see if the access to the rule you are testing exists already or not. + +Using the `match_details` method, you could as an example, build logic if every product of the rule is matched, just not against a single rule. This is one of many different ways you could use the data. + +## Example Usage + +Here we can test if a rule is matched via the existing ruleset. We can leverage the permit or deny to understand if this exists already or not. + +**Simple Example** + +```python +>>> from netutils.acl import ACLRules +>>> +>>> existing_acls = [ +... dict( +... name="Allow to internal web", +... src_ip=["192.168.0.0/24", "10.0.0.0/16"], +... dst_ip=["172.16.0.0/16", "192.168.250.10-192.168.250.20"], +... dst_port=["tcp/80", "udp/53"], +... action="permit", +... ), +... dict( +... name="Allow to internal dns", +... src_ip=["192.168.1.0/24"], +... dst_ip=["172.16.0.0/16"], +... dst_port=["tcp/80", "udp/53"], +... action="permit", +... ) +... ] +>>> +>>> new_acl_match = dict( +... name="Check multiple sources pass", +... src_ip=["192.168.1.10", "192.168.1.11", "192.168.1.15-192.168.1.20"], +... dst_ip="172.16.0.10", +... dst_port="tcp/www-http", +... action="permit", +... ) +>>> +>>> ACLRules(existing_acls).match(new_acl_match) +'permit' +>>> +>>> +>>> new_acl_non_match = dict( +... name="Check no match", +... src_ip=["10.1.1.1"], +... dst_ip="172.16.0.10", +... dst_port="tcp/www-http", +... action="permit", +... ) +>>> +>>> ACLRules(existing_acls).match(new_acl_non_match) +'deny' +>>> +``` + +**Inherit Example** + +```python + +from netutils.acl import ACLRule + +class ExpandAddrGroups(ACLRule): + address_groups = {"red": ["white", "blue"], "blue": ["cyan"], "yellow": ["orange"]} + addresses = {"white": ["10.1.1.1", "10.2.2.2"], "cyan": ["10.3.3.3"], "orange": ["10.4.4.4"]} + + def __init__(self, data, *args, **kwargs): + self.flattened_addresses = self.flatten_addresses(self.address_groups, self.addresses) + super().__init__(data, *args, **kwargs) + + def flatten_addresses(self, address_groups, addresses): + flattened_addresses = {} + for group, subgroups in address_groups.items(): + if group in addresses: + flattened_addresses.setdefault(group, []).extend(addresses[group]) + for subgroup in subgroups: + if subgroup in addresses: + flattened_addresses.setdefault(group, []).extend(addresses[subgroup]) + if subgroup in address_groups: + subgroup_addresses = self.flatten_addresses({subgroup: address_groups[subgroup]}, addresses) + for sub_group, ips in subgroup_addresses.items(): + flattened_addresses.setdefault(sub_group, []).extend(ips) + if group != sub_group: + flattened_addresses.setdefault(group, []).extend(ips) + return flattened_addresses + + def process_ip(self, ip): + if not isinstance(ip, list): + ip = [ip] + output = [] + for ip_name in ip: + if not ip_name[0].isalpha(): + output.append(ip_name) + elif self.addresses.get(ip_name): + output.extend(self.addresses[ip_name]) + elif self.flattened_addresses.get(ip_name): + output.extend(self.flattened_addresses[ip_name]) + return sorted(list(set(output))) + + def process_src_ip(self, src_ip): + return self.process_ip(src_ip) + + def process_dst_ip(self, dst_ip): + return self.process_ip(dst_ip) +``` + +Using the above object, we can test with: + +```python +>>> rule_data = dict( +... name="Check allow", +... src_ip=["red", "blue", "10.4.4.4"], +... dst_ip=["white"], +... dst_port="6/www-http", +... action="permit", +... ) +>>> +>>> address_object_expanded = ExpandAddrGroups(rule_data) +>>> for item in address_object_expanded.expanded_rules: +... print(item) +... +{'name': 'Check allow', 'src_ip': '10.1.1.1', 'dst_ip': '10.2.2.2', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.2.2.2', 'dst_ip': '10.1.1.1', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.3.3.3', 'dst_ip': '10.1.1.1', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.3.3.3', 'dst_ip': '10.2.2.2', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.4.4.4', 'dst_ip': '10.1.1.1', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.4.4.4', 'dst_ip': '10.2.2.2', 'dst_port': '6/80', 'action': 'permit'} +>>> +``` + +In that example you can see how we expanded `red` -> 10.1.1.1", "10.2.2.2", "10.3.3.3" as an example. diff --git a/docs/user/lib_use_cases_lib_mapper.md b/docs/user/lib_use_cases_lib_mapper.md index 8987d1de..3ae96c22 100644 --- a/docs/user/lib_use_cases_lib_mapper.md +++ b/docs/user/lib_use_cases_lib_mapper.md @@ -47,6 +47,14 @@ Another use case could be using an example like the above in an Ansible filter. --8<-- "docs/user/lib_mapper/ansible_reverse.md" +## Hier Config Mapper + +--8<-- "docs/user/lib_mapper/hierconfig.md" + +## Reverse Hier Config Mapper + +--8<-- "docs/user/lib_mapper/hierconfig_reverse.md" + ## Napalm Mapper --8<-- "docs/user/lib_mapper/napalm.md" @@ -55,6 +63,23 @@ Another use case could be using an example like the above in an Ansible filter. --8<-- "docs/user/lib_mapper/napalm_reverse.md" +## Netmiko Mapper + +--8<-- "docs/user/lib_mapper/netmiko.md" + +## Reverse Netmiko Mapper + +--8<-- "docs/user/lib_mapper/netmiko_reverse.md" + + +## Netutils Parser Mapper + +--8<-- "docs/user/lib_mapper/netutilsparser.md" + +## Reverse Netutils Parser Mapper + +--8<-- "docs/user/lib_mapper/netutilsparser_reverse.md" + ## NTC Templates Mapper --8<-- "docs/user/lib_mapper/ntctemplates.md" @@ -84,10 +109,5 @@ Another use case could be using an example like the above in an Ansible filter. --8<-- "docs/user/lib_mapper/scrapli.md" ## Reverse Scrapli Mapper ---8<-- "docs/user/lib_mapper/scrapli_reverse.md" -## Hier Config Mapper ---8<-- "docs/user/lib_mapper/hierconfig.md" - -## Reverse Hier Config Mapper ---8<-- "docs/user/lib_mapper/hierconfig_reverse.md" \ No newline at end of file +--8<-- "docs/user/lib_mapper/scrapli_reverse.md" \ No newline at end of file diff --git a/docs/user/lib_use_cases_protocol_mappers.md b/docs/user/lib_use_cases_protocol_mappers.md index 747b4c4c..7c071de4 100644 --- a/docs/user/lib_use_cases_protocol_mappers.md +++ b/docs/user/lib_use_cases_protocol_mappers.md @@ -79,11 +79,11 @@ Take the below example. |compressnet| 3| tcp| Compression Process | |compressnet| 3| udp| Compression Process | -As you can see, the service name and port comboniation is not unique. As such, on the second one, a `-secondary` is appended to the name to accomdate. The resulting structure looks something like: +As you can see, the service name and port combination is not unique. As such, on the second one, a `-secondary` is appended to the name to accommodate. The resulting structure looks something like: ```python "compressnet": {"port_number": 2, "protocols": ["tcp", "udp"]}, "compressnet-secondary": {"port_number": 3, "protocols": ["tcp", "udp"]}, ``` -This behaviour is to be expected. \ No newline at end of file +This behavior is to be expected. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 981c82da..932d02ca 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,13 +14,14 @@ theme: - "python" - "yaml" features: - - "navigation.tracking" + - "content.code.copy" + - "navigation.indexes" - "navigation.tabs" - "navigation.tabs.sticky" - - "search.suggest" + - "navigation.tracking" - "search.highlight" - "search.share" - - "navigation.indexes" + - "search.suggest" favicon: "assets/favicon.ico" logo: "assets/networktocode_logo.svg" palette: @@ -82,6 +83,12 @@ markdown_extensions: plugins: - "search" - "mkdocs-version-annotations" + - "mkdocs-python-classy": + classy_strategy: "subclass" + classy_modules: + - "netutils.config.parser" + classy_libraries: + - "netutils" - "mkdocstrings": default_handler: "python" handlers: @@ -100,6 +107,7 @@ nav: - Jinja Filters: "user/lib_use_cases_jinja_filters.md" - Library Mapper: "user/lib_use_cases_lib_mapper.md" - Protocol Mapper: "user/lib_use_cases_protocol_mappers.md" + - ACL: "user/lib_use_cases_acl.md" - Upgrade Paths: "user/lib_upgrade_paths.md" - Getting Started: "user/lib_getting_started.md" - Frequently Asked Questions: "user/faq.md" @@ -116,15 +124,19 @@ nav: - v1.2: "admin/release_notes/version_1.2.md" - v1.3: "admin/release_notes/version_1.3.md" - v1.4: "admin/release_notes/version_1.4.md" + - v1.5: "admin/release_notes/version_1.5.md" + - v1.6: "admin/release_notes/version_1.6.md" - Developer Guide: - Extending the Library: "dev/extending.md" - Contributing to the Library: "dev/contributing.md" - Development Environment: "dev/dev_environment.md" - Config Parser Development: "dev/dev_config.md" + - Arch Decision Records: "dev/dev_adr.md" - Code Attribution to the Library: "dev/attribution.md" - Code Reference: - "dev/code_reference/index.md" - ASN: "dev/code_reference/asn.md" + - ACL: "dev/code_reference/acl.md" - Bandwidth: "dev/code_reference/bandwidth.md" - Banner: "dev/code_reference/banner.md" - Configs: "dev/code_reference/configs.md" @@ -143,3 +155,5 @@ nav: - Time: "dev/code_reference/time.md" - Utils: "dev/code_reference/utils.md" - VLAN: "dev/code_reference/vlan.md" + - Classy Docs: + - Parser: "dev/classy/parser.md" diff --git a/netutils/acl.py b/netutils/acl.py new file mode 100644 index 00000000..88eeba66 --- /dev/null +++ b/netutils/acl.py @@ -0,0 +1,532 @@ +"""Classes to help manage ACLs .""" + +import itertools +import copy +import typing as t +from netutils.protocol_mapper import PROTO_NAME_TO_NUM, TCP_NAME_TO_NUM, UDP_NAME_TO_NUM +from netutils.ip import is_ip_within + +try: + import jsonschema + + HAS_JSON_SCHEMA = True +except ImportError: + HAS_JSON_SCHEMA = False + +INPUT_SCHEMA = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "src_zone": {"type": ["string", "array"]}, + "src_ip": {"$ref": "#/definitions/arrayOrIP"}, + "dst_ip": {"$ref": "#/definitions/arrayOrIP"}, + "dst_port": { + "oneOf": [ + { + "$ref": "#/definitions/port", + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/port", + }, + "minItems": 1, + "uniqueItems": True, + }, + ], + }, + "dst_zone": {"type": ["string", "array"]}, + "action": {"type": "string"}, + }, + "definitions": { + "ipv4": {"type": "string", "pattern": "^(?:\\d{1,3}\\.){3}\\d{1,3}$"}, + "ipv4_cidr": {"type": "string", "pattern": "^(?:\\d{1,3}\\.){3}\\d{1,3}\\/\\d{1,2}$"}, + "ipv4_range": {"type": "string", "pattern": "^(?:\\d{1,3}\\.){3}\\d{1,3}\\-(?:\\d{1,3}\\.){3}\\d{1,3}$"}, + "ipv6": {"type": "string", "pattern": "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"}, + "ipv6_cidr": {"type": "string", "pattern": "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}/[0-9]{1,3}$"}, + "ipv6_range": { + "type": "string", + "pattern": "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}-([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$", + }, + "oneIP": { + "oneOf": [ + { + "$ref": "#/definitions/ipv4", + }, + { + "$ref": "#/definitions/ipv4_cidr", + }, + { + "$ref": "#/definitions/ipv4_range", + }, + { + "$ref": "#/definitions/ipv6", + }, + { + "$ref": "#/definitions/ipv6_cidr", + }, + { + "$ref": "#/definitions/ipv6_range", + }, + ], + }, + "arrayOrIP": { + "oneOf": [ + { + "$ref": "#/definitions/oneIP", + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/oneIP", + }, + }, + ], + }, + "port": {"type": "string", "pattern": "^\\S+\\/\\S+$"}, + }, + "required": [], +} + + +RESULT_SCHEMA = copy.deepcopy(INPUT_SCHEMA) +RESULT_SCHEMA["definitions"]["port"]["pattern"] = "^\\d+\\/\\d+$" # type: ignore + + +def _cartesian_product(data: t.Dict[str, str]) -> t.List[t.Dict[str, t.Any]]: + """Function to create the Cartesian product/permutations from a data dictionary. + + Args: + data: A dictionary with string keys and either string or list of string values + + Returns: + A list of dictionaries that extrapolates the product to individual items. + + Examples: + >>> from netutils.acl import _cartesian_product + >>> rule = dict( + ... name="Allow to internal web", + ... src_ip=["192.168.0.0/24", "10.0.0.0/16"], + ... dst_ip=["172.16.0.0/16", "192.168.250.10-192.168.250.20"], + ... dst_port=["tcp/80", "udp/53"], + ... action="permit", + ... ) + >>> for item in _cartesian_product(rule): + ... print(item) + ... + {'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '172.16.0.0/16', 'dst_port': 'tcp/80', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '172.16.0.0/16', 'dst_port': 'udp/53', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'tcp/80', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'udp/53', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '172.16.0.0/16', 'dst_port': 'tcp/80', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '172.16.0.0/16', 'dst_port': 'udp/53', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'tcp/80', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'udp/53', 'action': 'permit'} + >>> + """ + keys = [] + values = [] + for key, value in data.items(): + keys.append(key) + if isinstance(value, (str, int)): + values.append([value]) + else: + values.append(value) + product = list(itertools.product(*values)) + return [dict(zip(keys, item)) for item in product] + + +def _check_schema(data: t.Any, schema: t.Any, verify: bool) -> None: + """Checks the schema provided.""" + if not verify: + return + if HAS_JSON_SCHEMA: + jsonschema.validate(data, schema) + else: + if not isinstance(data, dict): + raise ValueError() + + +class ACLRule: + """A class that helps you imagine an acl rule via methodologies.""" + + attrs: t.List[str] = ["name", "src_ip", "src_zone", "dst_ip", "dst_port", "dst_zone", "action"] + permit: str = "permit" + deny: str = "deny" + + input_data_verify: bool = False + input_data_schema: t.Any = INPUT_SCHEMA + + result_data_verify: bool = False + result_data_schema: t.Any = RESULT_SCHEMA + + matrix: t.Any = {} + matrix_enforced: bool = False + matrix_definition: t.Any = {} + + dst_port_process: bool = True + + order_validate: t.List[str] = [] + order_enforce: t.List[str] = [] + filter_same_ip: bool = True + + def __init__(self, data: t.Any, *args: t.Any, **kwargs: t.Any): # pylint: disable=unused-argument + """Initialize and load data. + + Args: + data: A dictionary with string keys and either string or list of string values + + Examples: + >>> from netutils.acl import ACLRule + >>> + >>> acl_data = dict( + ... name="Check no match", + ... src_ip=["10.1.1.1"], + ... dst_ip="172.16.0.10", + ... dst_port="tcp/www-http", + ... action="permit", + ... ) + >>> + >>> rule = ACLRule(acl_data) + >>> + >>> rule.expanded_rules + [{'name': 'Check no match', 'src_ip': '10.1.1.1', 'dst_ip': '172.16.0.10', 'dst_port': '6/80', 'action': 'permit'}] + >>> + """ + self.processed: t.Dict[str, str] = {} + self.data = data + self.load_data() + + def load_data(self) -> None: + """Load the data into the rule while verifying input data, result data, and processing data.""" + self.input_data_check() + for attr in self.attrs: + if not self.data.get(attr): + continue + if hasattr(self, f"process_{attr}"): + proccessor = getattr(self, f"process_{attr}") + _attr_data = proccessor(self.data[attr]) + else: + _attr_data = self.data[attr] + self.processed[attr] = _attr_data + setattr(self, attr, _attr_data) + self.result_data_check() + self.validate() + self.expanded_rules = _cartesian_product(self.processed) + if self.filter_same_ip: + self.expanded_rules = [item for item in self.expanded_rules if item["dst_ip"] != item["src_ip"]] + + def input_data_check(self) -> None: + """Verify the input data against the specified JSONSchema or using a simple dictionary check.""" + return _check_schema(self.data, self.input_data_schema, self.input_data_verify) + + def result_data_check(self) -> None: + """Verify the result data against the specified JSONSchema or using a simple dictionary check.""" + return _check_schema(self.processed, self.result_data_schema, self.result_data_verify) + + def validate(self) -> t.Any: + """Run through any method that startswith('validate_') and run that method.""" + if self.order_validate: + method_order = self.order_validate + else: + method_order = dir(self) + results = [] + for name in method_order: + if name.startswith("validate_"): + result = getattr(self, name)() + if not result: + continue + if result and isinstance(result, dict): + results.append(result) + elif result and isinstance(result, list): + results.extend(result) + return results + + def process_dst_port( + self, dst_port: t.Any + ) -> t.Union[t.List[str], None]: # pylint: disable=inconsistent-return-statements + """Convert port and protocol information. + + Method supports a single format of `{protocol}/{port}`, and will translate the + protocol for all IANA defined protocols. The port will be translated for TCP and + UDP ports only. For all other protocols should use port of 0, e.g. `ICMP/0` for ICMP + or `50/0` for ESP. Similarly, IANA defines the port mappings, while these are mostly + staying unchanged, but sourced from + https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.csv. + """ + output = [] + if not self.dst_port_process: + return None + if not isinstance(dst_port, list): + dst_port = [dst_port] + for item in dst_port: + protocol = item.split("/")[0] + port = item.split("/")[1] + if protocol.isalpha(): + if not PROTO_NAME_TO_NUM.get(protocol.upper()): + raise ValueError( + f"Protocol {protocol} was not found in netutils.protocol_mapper.PROTO_NAME_TO_NUM." + ) + protocol = PROTO_NAME_TO_NUM[protocol.upper()] + # test port[0] vs port, since dashes do not count, e.g. www-http + if int(protocol) == 6 and port[0].isalpha(): + if not TCP_NAME_TO_NUM.get(port.upper()): + raise ValueError(f"Port {port} was not found in netutils.protocol_mapper.TCP_NAME_TO_NUM.") + port = TCP_NAME_TO_NUM[port.upper()] + if int(protocol) == 17 and port[0].isalpha(): + if not UDP_NAME_TO_NUM.get(port.upper()): + raise ValueError(f"Port {port} was not found in netutils.protocol_mapper.UDP_NAME_TO_NUM.") + port = UDP_NAME_TO_NUM[port.upper()] + output.append(f"{protocol}/{port}") + return output + + def enforce(self) -> t.List[t.Dict[str, t.Any]]: + """Run through any method that startswith('enforce_') and run that method. + + Returns: + A list of dictionaries that explains the results of the enforcement. + """ + if self.order_enforce: + method_order = self.order_enforce + else: + method_order = dir(self) + results = [] + for name in method_order: + if name.startswith("enforce_"): + result = getattr(self, name)() + if not result: + continue + if result and isinstance(result, dict): + results.append(result) + elif result and isinstance(result, list): + results.extend(result) + return results + + def enforce_matrix(self) -> t.Union[t.List[t.Dict[str, t.Any]], None]: + """A simple `matrix` or grid style check of a rule. + + Returns: + A list of dictionaries that explains the results of the matrix being enforced. + """ + if not self.matrix_enforced: + return None + if not self.matrix: + raise ValueError("You must set a matrix dictionary to use the matrix feature.") + if not self.matrix_definition: + raise ValueError("You must set a matrix definition dictionary to use the matrix feature.") + actions = [] + for rule in self.expanded_rules: + source = rule["src_ip"] + destination = rule["dst_ip"] + port = rule["dst_port"] + src_zone = "" + dst_zone = "" + as_tuple = (source, destination, port) + for zone, ips in self.matrix_definition.items(): + if is_ip_within(source, ips): + src_zone = zone + if is_ip_within(destination, ips): + dst_zone = zone + if port in self.matrix.get(src_zone, {}).get(dst_zone, {}).get("allow", []): + actions.append({"obj": as_tuple, "action": "allow"}) + elif port in self.matrix.get(src_zone, {}).get(dst_zone, {}).get("notify", []): + actions.append({"obj": as_tuple, "action": "notify"}) + else: + actions.append({"obj": as_tuple, "action": "deny"}) + return actions + + def match_action(self, existing_action: str, check_action: str) -> bool: + """Match the action for equality. + + Args: + existing_action: The existing action value to be matched. + check_action: The action value to be checked against the existing action. + + Returns: + True if `existing_action` matches `check_action`, False otherwise. + """ + return existing_action == check_action + + def match_src_ip(self, existing_ip: str, check_ip: str) -> bool: + """Determined if source `check_ip` is within `existing_ip`. + + Args: + existing_ip: The existing source IP address or IP range to be matched against. + check_ip: The source IP address to be checked. + + Returns: + True if `check_ip` is within the range of `existing_ip`, False otherwise. + """ + return is_ip_within(check_ip, existing_ip) + + def match_src_zone(self, existing_src_zone: str, check_src_zone: str) -> bool: + """Match the source zone for equality. + + Args: + existing_src_zone: The existing source zone value to be matched. + check_src_zone: The source zone value to be checked against the existing zone. + + Returns: + True if `existing_src_zone` matches `check_src_zone`, False otherwise. + """ + return existing_src_zone == check_src_zone + + def match_dst_ip(self, existing_ip: str, check_ip: str) -> bool: + """Determined if destination `check_ip` is within `existing_ip. + + Args: + existing_ip: The existing destination IP address or IP range to be matched against. + check_ip: The destination IP address to be checked. + + Returns: + True if `check_ip` is within the range of `existing_ip`, False otherwise. + """ + return is_ip_within(check_ip, existing_ip) + + def match_dst_zone(self, existing_dst_zone: str, check_dst_zone: str) -> bool: + """Match the destination zone for equality. + + Args: + existing_dst_zone: The existing destination zone value to be matched. + check_dst_zone: The destination zone value to be checked against the existing zone. + + Returns: + True if `existing_dst_zone` matches `check_dst_zone`, False otherwise. + """ + return existing_dst_zone == check_dst_zone + + def match_dst_port(self, existing_port: str, check_port: str) -> bool: + """Match the destination port for equality. + + Args: + existing_port: The existing destination port value to be matched. + check_port: The destination port value to be checked against the existing port. + + Returns: + True if `existing_port` matches `check_port`, False otherwise. + """ + return existing_port == check_port + + def match_details(self, match_rule: "ACLRule") -> t.Dict[str, t.Any]: # pylint: disable=too-many-locals + """Verbose way of verifying match details. + + Args: + match_rule: The rule which you are testing against. + + Returns: + A dictionary with root keys of `rules_matched` and `rules_matched`. + """ + attrs = [] + for name in dir(self): + if name.startswith("match_"): + obj_name = name[len("match_") :] # noqa: E203 + # When an attribute is not defined, can skip it + if not hasattr(match_rule, obj_name): + continue + attrs.append(obj_name) + + rules_found: t.List[bool] = [] + rules_unmatched: t.List[t.Dict[str, t.Any]] = [] + rules_matched: t.List[t.Dict[str, t.Any]] = [] + + if not match_rule.expanded_rules: + raise ValueError("There is no expanded rules to test against.") + for rule in match_rule.expanded_rules: + rules_found.append(False) + for existing_rule in self.expanded_rules: + missing = False + for attr in attrs: + # Examples of obj are match_rule.src_ip, match_rule.dst_port + rule_value = rule[attr] + existing_value = existing_rule[attr] + # Examples of getter are self.match_src_ip, self.match_dst_port + getter = getattr(self, f"match_{attr}")(existing_value, rule_value) + if not getter and getter is not None: + missing = True + break + # If the loop gets through with each existing rule not flagging + # the `missing` value, we know everything was matched, and the rule has + # found a complete match, we can break out of the loop at this point. + if not missing: + rules_found[-1] = True + break + detailed_info = { + "existing_rule_product": existing_rule, # pylint: disable=undefined-loop-variable + "match_rule": match_rule.processed, + "existing_rule": self.processed, + } + if rules_found[-1]: + detailed_info["match_rule_product"] = rule + rules_matched.append(detailed_info) + else: + rules_unmatched.append(detailed_info) + return {"rules_matched": rules_matched, "rules_unmatched": rules_unmatched} + + def match(self, match_rule: "ACLRule") -> bool: + """Simple boolean way of verifying match or not. + + Args: + match_rule: The rule which you are testing against. + + Returns: + A boolean if there was a full match or not. + """ + details = self.match_details(match_rule) + return not bool(details["rules_unmatched"]) + + def __repr__(self) -> str: + """Set repr of the object to be sane.""" + output = [] + for attr in self.attrs: + if self.processed.get(attr): + output.append(f"{attr}: {self.processed[attr]}") + return ", ".join(output) + + +class ACLRules: + """Class to help match multiple ACLRule objects.""" + + class_obj = ACLRule + + def __init__(self, data: t.Any, *args: t.Any, **kwargs: t.Any): # pylint: disable=unused-argument + """Class to help match multiple ACLRule. + + Args: + data: A list of `ACLRule` rules. + """ + self.data: t.Any = data + self.rules: t.List[t.Any] = [] + self.load_data() + + def load_data(self) -> None: + """Load the data for multiple rules.""" + for item in self.data: + self.rules.append(self.class_obj(item)) + + def match(self, rule: ACLRule) -> str: + """Check the rules loaded in `load_data` match against a new `rule`. + + Args: + rule: The `ACLRule` rule to test against the list of `ACLRule`s loaded in initiation. + + Returns: + The response from the rule that matched, or `deny` by default. + """ + for item in self.rules: + if item.match(self.class_obj(rule)): + return str(item.action) + return str(item.deny) # pylint: disable=undefined-loop-variable + + def match_details(self, rule: ACLRule) -> t.Any: + """Verbosely check the rules loaded in `load_data` match against a new `rule`. + + Args: + rule: The `ACLRule` rule to test against the list of `ACLRule`s loaded in initiation. + + Returns: + The details from the `ACLRule.match_details` results. + """ + output = [] + for item in self.rules: + output.append(item.match_details(self.class_obj(rule))) + return output diff --git a/netutils/asn.py b/netutils/asn.py index 04323b4e..458dbdd3 100644 --- a/netutils/asn.py +++ b/netutils/asn.py @@ -24,3 +24,31 @@ def asn_to_int(asplain: str) -> int: asn = int(f"{int(asn_list[0]):016b}{int(asn_list[1]):016b}", 2) return asn return int(asplain) + + +def int_to_asdot(asn_int: int) -> str: + """Convert integer to standardized asdot notation for BGP ASN. + + Args: + asn_int: Integer value of the BGP ASN. + + Returns: + `asdot` notated BGP ASN as a string. + + Examples: + >>> from netutils.asn import int_to_asdot + >>> int_to_asdot(65000) + '65000' + >>> int_to_asdot(4259840111) + '65000.111' + >>> + """ + if isinstance(asn_int, str): + asn_int = int(asn_int) + if asn_int > 2**32 - 1 or asn_int < 1: + raise ValueError(f"`{str(asn_int)}` is not within range of 1 - 2^32-1") + if asn_int >= 2**16: + asn1 = asn_int >> 16 + asn2 = asn_int & ((1 << 16) - 1) + return f"{asn1}.{asn2}" + return str(asn_int) diff --git a/netutils/config/compliance.py b/netutils/config/compliance.py index 4cafc783..136fd2ae 100644 --- a/netutils/config/compliance.py +++ b/netutils/config/compliance.py @@ -8,23 +8,25 @@ parser_map: t.Dict[str, t.Type[parser.BaseConfigParser]] = { "arista_eos": parser.EOSConfigParser, + "aruba_aoscx": parser.ArubaConfigParser, + "bigip_f5": parser.F5ConfigParser, + "cisco_aireos": parser.AIREOSConfigParser, + "cisco_asa": parser.ASAConfigParser, "cisco_ios": parser.IOSConfigParser, "cisco_iosxr": parser.IOSXRConfigParser, "cisco_nxos": parser.NXOSConfigParser, - "cisco_aireos": parser.AIREOSConfigParser, - "linux": parser.LINUXConfigParser, - "bigip_f5": parser.F5ConfigParser, - "juniper_junos": parser.JunosConfigParser, - "cisco_asa": parser.ASAConfigParser, - "fortinet_fortios": parser.FortinetConfigParser, - "nokia_sros": parser.NokiaConfigParser, "citrix_netscaler": parser.NetscalerConfigParser, - "aruba_aoscx": parser.ArubaConfigParser, - "mrv_optiswitch": parser.OptiswitchConfigParser, "extreme_netiron": parser.NetironConfigParser, - "paloalto_panos": parser.PaloAltoNetworksConfigParser, + "fortinet_fortios": parser.FortinetConfigParser, + "juniper_junos": parser.JunosConfigParser, + "linux": parser.LINUXConfigParser, "mikrotik_routeros": parser.RouterOSConfigParser, + "mrv_optiswitch": parser.OptiswitchConfigParser, + "netscaler": parser.NetscalerConfigParser, + "nokia_sros": parser.NokiaConfigParser, + "paloalto_panos": parser.PaloAltoNetworksConfigParser, "ruckus_fastiron": parser.FastironConfigParser, + "ubiquiti_airos": parser.UbiquitiAirOSConfigParser, } diff --git a/netutils/config/parser.py b/netutils/config/parser.py index 90c3e139..2bcf2887 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -1575,3 +1575,32 @@ def _build_banner(self, config_line: str) -> t.Optional[str]: except StopIteration: return None raise ValueError("Unable to parse banner end.") + + +class UbiquitiAirOSConfigParser(BaseSpaceConfigParser): + """Ubiquiti airOS config parser.""" + + comment_chars: t.List[str] = ["#", "###"] + banner_start: t.List[str] = [] + + @property + def banner_end(self) -> str: + """Demarcate End of Banner char(s).""" + raise NotImplementedError("Ubiquiti airOS platform doesn't have a banner.") + + @property + def config_lines_only(self) -> str: + """Remove spaces and unwanted lines from config lines. + + Returns: + The non-space and non-comment lines from ``config``. + """ + config_lines = [] + config = self.config.strip() + for line in config.splitlines(): + if line.startswith("##A"): + config_lines.append(line) + if line and line != "##" and not self.is_comment(line): + config_lines.append(line) + + return "\n".join(config_lines) diff --git a/netutils/data_files/oui_mappings.py b/netutils/data_files/oui_mappings.py index 3b24a80c..c3efe54d 100644 --- a/netutils/data_files/oui_mappings.py +++ b/netutils/data_files/oui_mappings.py @@ -414,7 +414,7 @@ "000199": "HeiSei Electronics", "00019a": "LEUNIG GmbH", "00019b": "Kyoto Microcomputer Co., Ltd.", - "00019c": "JDS Uniphase Inc.", + "00019c": "Lumentum", "00019d": "E-Control Systems, Inc.", "00019e": "ESS Technology, Inc.", "00019f": "ReadyNet", @@ -806,7 +806,7 @@ "000321": "Reco Research Co., Ltd.", "000322": "IDIS Co., Ltd.", "000323": "Cornet Technology, Inc.", - "000324": "SANYO Techno Solutions Tottori Co., Ltd.", + "000324": "LIMNO Co., Ltd.", "000325": "Arima Computer Corp.", "000326": "Iwasaki Information Systems Co., Ltd.", "000327": "HMS Industrial Networks", @@ -1589,7 +1589,7 @@ "000630": "Adtranz Sweden", "000631": "Calix Inc.", "000632": "Mesco Engineering GmbH", - "000633": "Cross Match Technologies GmbH", + "000633": "Crossmatch Technologies/HID Global", "000634": "GTE Airfone Inc.", "000635": "PacketAir Networks, Inc.", "000636": "Jedai Broadband Networks", @@ -6868,7 +6868,7 @@ "001b06": "Ateliers R. LAUMONIER", "001b07": "Mendocino Software", "001b08": "Danfoss Drives A/S", - "001b09": "Matrix Telecom Pvt. Ltd.", + "001b09": "MATRIX COMSEC PRIVATE LIMITED", "001b0a": "Intelligent Distributed Controls Ltd", "001b0b": "Phidgets Inc.", "001b0c": "Cisco Systems, Inc", @@ -8031,7 +8031,7 @@ "001f99": "SERONICS co.ltd", "001f9a": "Nortel Networks", "001f9b": "POSBRO", - "001f9c": "LEDCO", + "001f9c": "Havis Inc.", "001f9d": "Cisco Systems, Inc", "001f9e": "Cisco Systems, Inc", "001f9f": "Thomson Telecom Belgium", @@ -10100,7 +10100,7 @@ "00308b": "Brix Networks", "00308c": "Quantum Corporation", "00308d": "Pinnacle Systems, Inc.", - "00308e": "CROSS MATCH TECHNOLOGIES, INC.", + "00308e": "Crossmatch Technologies/HID Global", "00308f": "MICRILOR, Inc.", "003090": "CYRA TECHNOLOGIES, INC.", "003091": "TAIWAN FIRST LINE ELEC. CORP.", @@ -10523,6 +10523,7 @@ "00464b": "HUAWEI TECHNOLOGIES CO.,LTD", "004a77": "zte corporation", "004bf3": "SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD.", + "004ce5": "Sichuan Tianyi Comheart Telecom Co.,LTD", "004d32": "Andon Health Co.,Ltd.", "004e01": "Dell Inc.", "004e35": "Hewlett Packard Enterprise", @@ -12298,6 +12299,7 @@ "00c610": "Apple, Inc.", "00c711": "ITEL MOBILE LIMITED", "00c88b": "Cisco Systems, Inc", + "00c896": "CIG SHANGHAI CO LTD", "00cae5": "Cisco Systems, Inc", "00cb00": "Private", "00cb51": "Sagemcom Broadband SAS", @@ -12573,6 +12575,7 @@ "00d318": "SPG Controls", "00d38d": "Hotel Technology Next Generation", "00d49e": "Intel Corporate", + "00d598": "BOPEL MOBILE TECHNOLOGY CO.,LIMITED", "00d632": "GE Energy", "00d6fe": "Cisco Systems, Inc", "00d76d": "Intel Corporate", @@ -12976,6 +12979,7 @@ "042234": "Wireless Standard Extensions", "0425c5": "HUAWEI TECHNOLOGIES CO.,LTD", "0425e0": "Taicang T&W Electronics", + "0425e8": "Texas Instruments", "0425f0": "Nokia", "042605": "Bosch Building Automation GmbH", "042665": "Apple, Inc.", @@ -13475,6 +13479,7 @@ "082697": "Zyxel Communications Corporation", "0826ae": "IEEE Registration Authority", "082719": "APS systems/electronic AG", + "08276b": "Huawei Device Co., Ltd.", "0827ce": "NAGANO KEIKI CO., LTD.", "082802": "SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD", "082ad0": "SRD Innovations Inc.", @@ -14411,6 +14416,7 @@ "14019c": "Ubyon Inc.", "1402ec": "Hewlett Packard Enterprise", "140467": "SNK Technologies Co.,Ltd.", + "14064c": "Vogl Electronic GmbH", "140708": "CP PLUS GMBH & CO. KG", "1407e0": "Abrantix AG", "1409b4": "zte corporation", @@ -14668,6 +14674,7 @@ "14ca56": "zte corporation", "14caa0": "Hu&Co", "14cb19": "HP Inc.", + "14cb49": "Habolink Technology Co.,LTD", "14cb65": "Microsoft Corporation", "14cc20": "TP-LINK TECHNOLOGIES CO.,LTD.", "14ccb3": "AO GK NATEKS", @@ -14683,6 +14690,7 @@ "14d64d": "D-Link International", "14d76e": "CONCH ELECTRONIC Co.,Ltd", "14d864": "TP-LINK TECHNOLOGIES CO.,LTD.", + "14dab9": "Huawei Device Co., Ltd.", "14dae9": "ASUSTek COMPUTER INC.", "14db85": "S NET MEDIA", "14dc51": "Xiamen Cheerzing IOT Technology Co.,Ltd.", @@ -14804,6 +14812,7 @@ "183da2": "Intel Corporate", "183eef": "Apple, Inc.", "183f47": "Samsung Electronics Co.,Ltd", + "183f70": "Apple, Inc.", "1840a4": "Shenzhen Trylong Smart Science and Technology Co., Ltd.", "1841fe": "Digital 14", "18421d": "Private", @@ -14857,6 +14866,7 @@ "185ae8": "Zenotech.Co.,Ltd", "185b00": "Nokia", "185bb3": "Samsung Electronics Co.,Ltd", + "185d6f": "N3com", "185d9a": "BobjGear LLC", "185e0b": "zte corporation", "185e0f": "Intel Corporate", @@ -15114,6 +15124,7 @@ "1c1fd4": "LifeBEAM Technologies LTD", "1c1ff1": "Huawei Device Co., Ltd.", "1c20db": "HUAWEI TECHNOLOGIES CO.,LTD", + "1c2156": "Smappee NV", "1c21d1": "IEEE Registration Authority", "1c2285": "Serrature Meroni SpA", "1c232c": "Samsung Electronics Co.,Ltd", @@ -15403,6 +15414,7 @@ "2002af": "Murata Manufacturing Co., Ltd.", "2002fe": "Hangzhou Dangbei Network Technology Co., Ltd", "20040f": "Dell Inc.", + "200484": "Apple, Inc.", "2004f3": "Honor Device Co., Ltd.", "200505": "RADMAX COMMUNICATION PRIVATE LIMITED", "2005e8": "OOO InProMedia", @@ -15545,6 +15557,7 @@ "206a94": "Hitron Technologies. Inc", "206aff": "Atlas Elektronik UK Limited", "206be7": "TP-LINK TECHNOLOGIES CO.,LTD.", + "206bf4": "Huawei Device Co., Ltd.", "206c8a": "Extreme Networks Headquarters", "206d31": "FIREWALLA INC", "206e9c": "Samsung Electronics Co.,Ltd", @@ -15722,6 +15735,7 @@ "2400fa": "Information Technology Co., Ltd", "24016f": "Huawei Device Co., Ltd.", "2401c7": "Cisco Systems, Inc", + "240462": "Siemens Energy Global GmbH & Co.KG - GT PRM", "24050f": "MTN Electronic Co. Ltd", "240588": "Google, Inc.", "2405f5": "Sdn. Bhd.", @@ -15784,6 +15798,7 @@ "2429fe": "KYOCERA Corporation ", "242a04": "Cisco Systems, Inc", "242cfe": "Zhejiang Tmall Technology Co., Ltd.", + "242d6c": "eero inc.", "242e02": "HUAWEI TECHNOLOGIES CO.,LTD", "242e90": "PALIT MICROSYSTEMS, LTD", "242ffa": "Toshiba Global Commerce Solutions", @@ -16035,6 +16050,7 @@ "24fd5b": "SmartThings, Inc.", "24fdfa": "Private", "24fe9a": "CyberTAN Technology Inc.", + "2800af": "Dell Inc.", "28011c": "zte corporation", "28022e": "Apple, Inc.", "280244": "Apple, Inc.", @@ -16113,6 +16129,7 @@ "283a4d": "Limited", "283b82": "D-Link International", "283b96": "Cool Control LTD", + "283c90": "Texas Instruments", "283ce4": "HUAWEI TECHNOLOGIES CO.,LTD", "283dc2": "Samsung Electronics Co.,Ltd", "283e0c": "Preferred Robotics, Inc.", @@ -16122,13 +16139,14 @@ "284121": "OptiSense Network, LLC", "2841c6": "HUAWEI TECHNOLOGIES CO.,LTD", "2841ec": "HUAWEI TECHNOLOGIES CO.,LTD", - "284430": "Ltd", + "284430": "Arcade Communications Ltd.", "2847aa": "Nokia Corporation", "284846": "GridCentric Inc.", "2848e7": "Huawei Device Co., Ltd.", "284c53": "Intune Networks", "284d92": "Luminator", "284ed7": "OutSmart Power Systems, Inc.", + "284ee9": "mercury corperation", "284fce": "Liaoning Wontel Science and Technology Development Co.,Ltd.", "285132": "Shenzhen Prayfly Technology Co.,Ltd", "285261": "Cisco Systems, Inc", @@ -16190,6 +16208,7 @@ "2880a2": "Novatel Wireless Solutions, Inc.", "28827c": "Bosch Automative products(Suzhou)Co.,Ltd Changzhou Branch", "288335": "Samsung Electronics Co.,Ltd", + "2883c9": "Apple, Inc.", "28840e": "silicon valley immigration service ", "2884fa": "SHARP Corporation", "28852d": "Touch Networks", @@ -16520,6 +16539,7 @@ "2c67ab": "EZELINK TELECOM", "2c67fb": "ShenZhen Zhengjili Electronics Co., LTD", "2c691d": "IEEE Registration Authority", + "2c693e": "HUAWEI TECHNOLOGIES CO.,LTD", "2c69ba": "RF Controls, LLC", "2c69cc": "Valeo Detection Systems ", "2c6a6f": "IEEE Registration Authority", @@ -16581,6 +16601,7 @@ "2c9d65": "vivo Mobile Communication Co., Ltd.", "2c9e00": "Sony Interactive Entertainment Inc.", "2c9e5f": "ARRIS Group, Inc.", + "2c9ee0": "Cavli Inc.", "2c9eec": "Jabil Circuit Penang", "2c9efc": "CANON INC.", "2c9ffb": "Wistron Neweb Corporation", @@ -16636,6 +16657,7 @@ "2cc5d3": "Ruckus Wireless", "2cc6a0": "Lumacron Technology Ltd.", "2cc81b": "Routerboard.com", + "2cc8f5": "Huawei Device Co., Ltd.", "2cca0c": "WITHUS PLANET", "2cca75": "Robert Bosch GmbH AnP", "2ccc15": "Nokia Corporation", @@ -16675,6 +16697,7 @@ "2ce871": "Alert Metalguard ApS", "2cea7f": "Dell Inc.", "2ceadc": "ASKEY COMPUTER CORP", + "2cedb0": "HUAWEI TECHNOLOGIES CO.,LTD", "2cedeb": "Alpheus Digital Company Limited", "2cee26": "Petroleum Geo-Services", "2cf05d": "Micro-Star INTL CO., LTD.", @@ -16934,6 +16957,7 @@ "30c750": "MIC Technology Group", "30c7ae": "Samsung Electronics Co.,Ltd", "30c82a": "WI-BIZ srl", + "30c922": "Espressif Inc.", "30c9ab": "CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD.", "30cb36": "Belden Singapore Pte. Ltd.", "30cbc7": "Cambium Networks Limited", @@ -16952,7 +16976,9 @@ "30d659": "Merging Technologies SA", "30d6c9": "Samsung Electronics Co.,Ltd", "30d7a1": "Apple, Inc.", + "30d875": "Apple, Inc.", "30d941": "Raydium Semiconductor Corp.", + "30d97f": "Tech4home, Lda", "30d9d9": "Apple, Inc.", "30de4b": "TP-Link Corporation Limited", "30de86": "Cedac Software S.r.l.", @@ -17117,6 +17143,7 @@ "345d9e": "Sagemcom Broadband SAS", "345da8": "Cisco Systems, Inc", "345e08": "Roku, Inc", + "345ee7": "Hangzhou ChengFengErLai Digial Technology Co.,Ltd.", "3460f9": "TP-Link Corporation Limited", "346178": "The Boeing Company", "346288": "Cisco Systems, Inc", @@ -17231,6 +17258,7 @@ "34af2c": "Nintendo Co., Ltd.", "34afa3": "Recogni Inc", "34afb3": "Amazon Technologies Inc.", + "34b1eb": "Apple, Inc.", "34b1f7": "Texas Instruments", "34b20a": "Huawei Device Co., Ltd.", "34b354": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -17513,6 +17541,7 @@ "3886f7": "Google, Inc.", "3887d5": "Intel Corporate", "38881e": "HUAWEI TECHNOLOGIES CO.,LTD", + "388871": "ASKEY COMPUTER CORP", "3888a4": "Apple, Inc.", "38892c": "Apple, Inc.", "3889dc": "Opticon Sensors Europe B.V.", @@ -17712,6 +17741,7 @@ "3c10e6": "PHAZR Inc.", "3c11b2": "Fraunhofer FIT", "3c135a": "Xiaomi Communications Co Ltd", + "3c13bb": "HUAWEI TECHNOLOGIES CO.,LTD", "3c13cc": "Cisco Systems, Inc", "3c1512": "Shenzhen Huanhu Technology Co.,Ltd", "3c15c2": "Apple, Inc.", @@ -17739,6 +17769,7 @@ "3c22fb": "Apple, Inc.", "3c24f0": "IEEE Registration Authority", "3c25d7": "Nokia Corporation", + "3c25f8": "Dell Inc.", "3c26d5": "Sotera Wireless", "3c26e4": "Cisco Systems, Inc", "3c2763": "SLE quality engineering GmbH & Co. KG", @@ -17797,6 +17828,7 @@ "3c53d7": "CEDES AG", "3c5447": "HUAWEI TECHNOLOGIES CO.,LTD", "3c5576": "CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD.", + "3c55db": "Shenzhen Skyworth Digital Technology CO., Ltd", "3c5731": "Cisco Systems, Inc", "3c574f": "China Mobile Group Device Co.,Ltd.", "3c576c": "Samsung Electronics Co.,Ltd", @@ -17921,6 +17953,7 @@ "3cab8e": "Apple, Inc.", "3cae69": "ESA Elektroschaltanlagen Grimma GmbH", "3cb07e": "Arounds Intelligent Equipment Co., Ltd.", + "3cb0ed": "Nothing Technology Limited", "3cb15b": "Avaya Inc", "3cb17f": "Wattwatchers Pty Ld", "3cb233": "Huawei Device Co., Ltd.", @@ -17955,6 +17988,7 @@ "3ccd57": "Beijing Xiaomi Mobile Software Co., Ltd", "3ccd5a": "Technische Alternative GmbH", "3ccd5d": "HUAWEI TECHNOLOGIES CO.,LTD", + "3ccd73": "Nebula Electronic Technology Corporation", "3ccd93": "LG ELECTRONICS INC", "3cce0d": "Shenzhen juduoping Technology Co.,Ltd", "3cce15": "Mercedes-Benz USA, LLC", @@ -18082,6 +18116,7 @@ "40331a": "Apple, Inc.", "40336c": "Godrej & Boyce Mfg. co. ltd", "4035e6": "Samsung Electronics Co.,Ltd", + "403668": "E&B TELECOM", "4037ad": "Macro Image Technology, Inc.", "403b7b": "Huawei Device Co., Ltd.", "403cfc": "Apple, Inc.", @@ -18142,6 +18177,7 @@ "4062b6": "Tele system communication", "4062ea": "China Mobile Group Device Co.,Ltd.", "4064a4": "THE FURUKAWA ELECTRIC CO., LTD", + "4064dc": "X-speed lnformation Technology Co.,Ltd", "4065a3": "Sagemcom Broadband SAS", "40667a": "mediola - connected living AG", "40679b": "Shenzhen Skyworth Digital Technology CO., Ltd", @@ -18169,6 +18205,7 @@ "407d0f": "HUAWEI TECHNOLOGIES CO.,LTD", "407f5f": "Juniper Networks", "407fe0": "Limited", + "4080e1": "FN-LINK TECHNOLOGY Ltd.", "408256": "Continental Automotive GmbH", "40831d": "Apple, Inc.", "4083de": "Zebra Technologies Inc", @@ -18244,6 +18281,7 @@ "40b688": "LEGIC Identsystems AG", "40b6b1": "SUNGSAM CO,.Ltd", "40b6e7": "Huawei Device Co., Ltd.", + "40b70e": "Huawei Device Co., Ltd.", "40b7f3": "ARRIS Group, Inc.", "40b7fc": "Phyplus Microelectronics Limited", "40b837": "Sony Corporation", @@ -18259,6 +18297,7 @@ "40bd9e": "Physio-Control, Inc", "40beee": "Shenzhen Yunding Information Technology Co.,Ltd", "40bf17": "Digistar Telecom. SA", + "40c0ee": "365mesh Pty Ltd", "40c1f6": "Shenzhen Jingxun Technology Co., Ltd.", "40c245": "Shenzhen Hexicom Technology Co., Ltd.", "40c2ba": "CO., LTD. ", @@ -18541,6 +18580,7 @@ "449ef9": "vivo Mobile Communication Co., Ltd.", "449f46": "Huawei Device Co., Ltd.", "449f7f": "DataCore Software Corporation", + "44a038": "Huawei Device Co., Ltd.", "44a191": "HUAWEI TECHNOLOGIES CO.,LTD", "44a3c7": "zte corporation", "44a42d": "TCT mobile ltd", @@ -18587,6 +18627,7 @@ "44c306": "SIFROM Inc.", "44c346": "HUAWEI TECHNOLOGIES CO.,LTD", "44c39b": "OOO RUBEZH NPO", + "44c3b6": "HUAWEI TECHNOLOGIES CO.,LTD", "44c4a9": "Opticom Communication, LLC", "44c56f": "Electronic Co., Ltd", "44c65d": "Apple, Inc.", @@ -18608,6 +18649,7 @@ "44d437": "Inteno Broadband Technology AB", "44d453": "Sagemcom Broadband SAS", "44d454": "Sagemcom Broadband SAS", + "44d47f": "INGENICO TERMINALS SAS", "44d4e0": "Sony Corporation", "44d506": "Sichuan Tianyi Comheart Telecom Co.,LTD", "44d5a5": "AddOn Computer", @@ -18800,10 +18842,12 @@ "485ded": "Sichuan Tianyi Comheart Telecom Co.,LTD", "485f08": "TP-LINK TECHNOLOGIES CO.,LTD.", "485f99": "Limited", + "485fdf": "zte corporation", "4860bc": "Apple, Inc.", "4861a3": "Concern Axion JSC", "4861ee": "Samsung Electronics Co.,Ltd", "486276": "HUAWEI TECHNOLOGIES CO.,LTD", + "486345": "Huawei Device Co., Ltd.", "4865ee": "IEEE Registration Authority", "486834": "Silicon Motion, Inc.", "48684a": "Intel Corporate", @@ -18842,6 +18886,7 @@ "4881d4": "Ruijie Networks Co.,LTD", "488244": "Life Fitness / Div. of Brunswick", "4882f2": "Appel Elektronik GmbH", + "48836f": "EM Microelectronic", "4883b4": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "4883c7": "Sagemcom Broadband SAS", "4886e8": "Microsoft Corporation", @@ -18938,6 +18983,7 @@ "48cac6": "UNION MAN TECHNOLOGY CO.,LTD", "48cb6e": "Ltd", "48cdd3": "HUAWEI TECHNOLOGIES CO.,LTD", + "48d017": "Telecom Infra Project", "48d0cf": "Universal Electronics, Inc.", "48d18e": "Metis Communication Co.,Ltd", "48d224": "Liteon Technology Corporation", @@ -18970,6 +19016,7 @@ "48e1e9": "Chengdu Meross Technology Co., Ltd.", "48e244": "Hon Hai Precision Ind. Co.,Ltd.", "48e3c3": "JENOPTIK Advanced Systems GmbH", + "48e533": "Sunnovo International Limited", "48e695": "Insigma Inc", "48e6c0": "SIMCom Wireless Solutions Co.,Ltd.", "48e729": "Espressif Inc.", @@ -18978,8 +19025,10 @@ "48ea63": "Zhejiang Uniview Technologies Co., Ltd.", "48eb30": "ETERNA TECHNOLOGY, INC.", "48eb62": "Murata Manufacturing Co., Ltd.", + "48eb65": "Henan KunLun Technologies CO.,Ltd.", "48ec5b": "Nokia Solutions and Networks GmbH & Co. KG", "48ed80": "daesung eltec", + "48ede6": "Zyxel Communications Corporation", "48ee07": "Silver Palm Technologies LLC", "48ee0c": "D-Link International", "48ee86": "Co.,Ltd", @@ -19099,6 +19148,7 @@ "4c4e35": "Cisco Systems, Inc", "4c4fee": "Co., Ltd", "4c5077": "Huawei Device Co., Ltd.", + "4c50dd": "Hui Zhou Gaoshengda Technology Co.,LTD", "4c50f1": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "4c5262": "Fujitsu Technology Solutions GmbH", "4c52ec": "SOLARWATT GmbH", @@ -19111,6 +19161,7 @@ "4c55cc": "Zentri Pty Ltd", "4c569d": "Apple, Inc.", "4c56df": "Targus US LLC", + "4c5739": "Samsung Electronics Co.,Ltd", "4c57ca": "Apple, Inc.", "4c5bb3": "Silicon Laboratories", "4c5cdf": "ITEL MOBILE LIMITED", @@ -19120,6 +19171,7 @@ "4c5ed3": "Unisyue Technologies Co; LTD.", "4c5f70": "Intel Corporate", "4c5fd2": "Alcatel-Lucent", + "4c60ba": "AltoBeam Inc.", "4c60d5": "airPointe of New Hampshire", "4c60de": "NETGEAR", "4c617e": "Huawei Device Co., Ltd.", @@ -19301,6 +19353,7 @@ "4ce705": "Siemens Industrial Automation Products Ltd., Chengdu", "4ce933": "RailComm, LLC", "4ce9e4": "New H3C Technologies Co., Ltd", + "4cea41": "IEEE Registration Authority", "4ceaae": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "4ceb42": "Intel Corporate", "4ceb76": "Murrelektronik GmbH", @@ -19565,10 +19618,12 @@ "50a0a4": "Nokia", "50a0bf": "Alba Fiber Systems Inc.", "50a132": "Shenzhen MiaoMing Intelligent Technology Co.,Ltd", + "50a1f3": "Huawei Device Co., Ltd.", "50a4c8": "Samsung Electronics Co.,Ltd", "50a4d0": "IEEE Registration Authority", "50a5dc": "ARRIS Group, Inc.", "50a67f": "Apple, Inc.", + "50a6d8": "Apple, Inc.", "50a6e3": "David Clark Company", "50a715": "Aboundi, Inc.", "50a72b": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -19912,6 +19967,7 @@ "54b7e5": "Rayson Technology Co., Ltd.", "54b802": "Samsung Electronics Co.,Ltd", "54b80a": "D-Link International", + "54b874": "GD Midea Air-Conditioning Equipment Co.,Ltd.", "54bad6": "HUAWEI TECHNOLOGIES CO.,LTD", "54bd79": "Samsung Electronics Co.,Ltd", "54be53": "zte corporation", @@ -19947,6 +20003,7 @@ "54dba2": "Fibrain", "54dc1d": "Co.,Ltd", "54ded0": "Sevio Srl", + "54ded3": "zte corporation", "54df00": "Ulterius Technologies, LLC", "54df1b": "Vestel Elektronik San ve Tic. A.S.", "54df24": "Fiberhome Telecommunication Technologies Co.,LTD", @@ -20207,6 +20264,7 @@ "58b961": "SOLEM Electronique", "58b965": "Apple, Inc.", "58b9e1": "Crystalfontz America, Inc.", + "58bad3": "NANJING CASELA TECHNOLOGIES CORPORATION LIMITED", "58bad4": "HUAWEI TECHNOLOGIES CO.,LTD", "58bc27": "Cisco Systems, Inc", "58bc8f": "Cognitive Systems Corp.", @@ -20245,6 +20303,7 @@ "58d697": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "58d6d3": "Dairy Cheq Inc", "58d759": "HUAWEI TECHNOLOGIES CO.,LTD", + "58d8a7": "Bird Home Automation GmbH", "58d9c3": "Motorola Mobility LLC, a Lenovo Company", "58d9d5": "Tenda Technology Co.,Ltd.Dongguan branch", "58db15": "TECNO MOBILE LIMITED", @@ -20254,6 +20313,7 @@ "58e16c": "Ying Hua Information Technology (Shanghai)Co., LTD", "58e28f": "Apple, Inc.", "58e326": "Compass Technologies Inc.", + "58e359": "Interroll Software & Electronics GmbH", "58e403": "Wistron Neweb Corporation", "58e434": "Juniper Networks", "58e476": "CENTRON COMMUNICATIONS TECHNOLOGIES FUJIAN CO.,LTD", @@ -20386,6 +20446,7 @@ "5c521e": "Nintendo Co.,Ltd", "5c5230": "Apple, Inc.", "5c5284": "Apple, Inc.", + "5c53b4": "EM Microelectronic", "5c53c3": "Ubee Interactive Co., Limited", "5c546d": "HUAWEI TECHNOLOGIES CO.,LTD", "5c5578": "iryx corp", @@ -20531,6 +20592,7 @@ "5cc5d4": "Intel Corporate", "5cc6d0": "Co.,Ltd", "5cc6e9": "Edifier International", + "5cc787": "Huawei Device Co., Ltd.", "5cc7c1": "Silicon Laboratories", "5cc7d7": "AZROAD TECHNOLOGY COMPANY LIMITED", "5cc8e3": "Shintec Hozumi co.ltd.", @@ -20604,6 +20666,7 @@ "5cf7c3": "TECHNOLOGY LIMITED", "5cf7e6": "Apple, Inc.", "5cf821": "Texas Instruments", + "5cf838": "IEEE Registration Authority", "5cf8a1": "Murata Manufacturing Co., Ltd.", "5cf938": "Apple, Inc.", "5cf96a": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -20616,6 +20679,7 @@ "5cfb7c": "Shenzhen Jingxun Software Telecommunication Technology Co.,Ltd", "5cfc66": "Cisco Systems, Inc", "5cfc6e": "Sichuan Tianyi Comheart Telecom Co.,LTD", + "5cfce1": "Resideo", "5cfe9e": "Wiwynn Corporation Tainan Branch", "5cff35": "Wistron Corporation", "5cffff": "Shenzhen Kezhonglong Optoelectronic Technology Co., Ltd", @@ -20671,6 +20735,7 @@ "602232": "Ubiquiti Inc", "6023a4": "Sichuan AI-Link Technology Co., Ltd.", "6024c1": "Jiangsu Zhongxun Electronic Technology Co., Ltd", + "602602": "Texas Instruments", "6026aa": "Cisco Systems, Inc", "6026ef": "Aruba, a Hewlett Packard Enterprise Company", "60271c": "VIDEOR E. Hartig GmbH", @@ -20715,6 +20780,7 @@ "6047d4": "FORICS Electronic Technology Co., Ltd.", "604826": "Newbridge Technologies Int. Ltd.", "60489c": "YIPPEE ELECTRONICS CO.,LIMITED", + "604966": "Shenzhen Dingsheng Technology Co., Ltd.", "6049c1": "Avaya Inc", "604a1c": "SUYIN Corporation", "604baa": "Magic Leap, Inc.", @@ -20733,13 +20799,14 @@ "605718": "Intel Corporate", "605747": "CIG SHANGHAI CO LTD", "60577d": "eero inc.", + "6057c8": "Apple, Inc.", "605801": "Shandong ZTop Microelectronics Co., Ltd.", "605b30": "Dell Inc.", "605bb4": "AzureWave Technology Inc.", "605e4f": "Huawei Device Co., Ltd.", "605f8d": "eero inc.", "60601f": "SZ DJI TECHNOLOGY CO.,LTD", - "606134": "Genesis Technical Systems Corp", + "606134": "Arcade Communications Ltd.", "6061df": "Z-meta Research LLC", "60634c": "D-Link International", "6063f9": "Ciholas, Inc.", @@ -20752,6 +20819,7 @@ "60684e": "Samsung Electronics Co.,Ltd", "606944": "Apple, Inc.", "60699b": "isepos GmbH", + "606b5b": "Arista Network, Inc.", "606bbd": "Samsung Electronics Co.,Ltd", "606bff": "Nintendo Co.,Ltd", "606c63": "Hitron Technologies. Inc", @@ -21044,6 +21112,7 @@ "643e0a": "HUAWEI TECHNOLOGIES CO.,LTD", "643e8c": "HUAWEI TECHNOLOGIES CO.,LTD", "643f5f": "Exablaze", + "6441e6": "Apple, Inc.", "644212": "Shenzhen Water World Information Co.,Ltd.", "644214": "Swisscom Energy Solutions AG", "644346": "GuangDong Quick Network Computer CO.,LTD", @@ -21062,6 +21131,7 @@ "6450d6": "Liquidtool Systems", "645106": "Hewlett Packard", "64517e": "ELECTRONIC TECHNOLOGY CO.,LTD.", + "6451f4": "Huawei Device Co., Ltd.", "645234": "Sichuan Tianyi Comheart Telecom Co.,LTD", "645299": "The Chamberlain Group, Inc", "64535d": "Frauscher Sensortechnik", @@ -21205,6 +21275,7 @@ "64bc58": "Intel Corporate", "64be63": "STORDIS GmbH", "64bf6b": "HUAWEI TECHNOLOGIES CO.,LTD", + "64c17e": "cheilelectric", "64c269": "eero inc.", "64c354": "Avaya Inc", "64c394": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -21366,6 +21437,7 @@ "683e02": "SIEMENS AG, Digital Factory, Motion Control System", "683e26": "Intel Corporate", "683e34": "MEIZU Technology Co., Ltd.", + "683ec0": "Apple, Inc.", "683eec": "ERECA", "683f1e": "EFFECT Photonics B.V.", "683f7d": "INGRAM MICRO SERVICES", @@ -21389,6 +21461,7 @@ "684f64": "Dell Inc.", "68505d": "Halo Technologies", "6851b7": "PowerCloud Systems, Inc.", + "685210": "MCS Logic", "6852d6": "UGame Technology Co.,Ltd", "68536c": "SPnS Co.,Ltd", "685388": "P&S Technology", @@ -21475,6 +21548,7 @@ "689320": "New H3C Technologies Co., Ltd", "689361": "Sdn. Bhd.", "689423": "Hon Hai Precision Ind. Co.,Ltd.", + "68944a": "zte corporation", "68962e": "HUAWEI TECHNOLOGIES CO.,LTD", "68966a": "OHSUNG", "68967b": "Apple, Inc.", @@ -21537,6 +21611,7 @@ "68c8eb": "Rockwell Automation", "68c90b": "Texas Instruments", "68ca00": "Octopus Systems Limited", + "68cac4": "Apple, Inc.", "68cae4": "Cisco Systems, Inc", "68cc6e": "HUAWEI TECHNOLOGIES CO.,LTD", "68cc9c": "Mine Site Technologies", @@ -21637,8 +21712,10 @@ "6c146e": "HUAWEI TECHNOLOGIES CO.,LTD", "6c14f7": "Erhardt+Leimer GmbH", "6c1524": "IEEE Registration Authority", + "6c1544": "Microsoft Corporation", "6c15f9": "Nautronix Limited", "6c160e": "ShotTracker", + "6c1629": "Edifier International", "6c1632": "HUAWEI TECHNOLOGIES CO.,LTD", "6c1811": "Decatur Electronics", "6c198f": "D-Link International", @@ -22158,6 +22235,7 @@ "708cbb": "MIMODISPLAYKOREA", "708d09": "Nokia Corporation", "708f47": "vivo Mobile Communication Co., Ltd.", + "709041": "Mist Systems, Inc.", "7090b7": "Huawei Device Co., Ltd.", "70918f": "Weber-Stephen Products LLC", "7091f3": "Universal Electronics, Inc.", @@ -22218,8 +22296,10 @@ "70b950": "Texas Instruments", "70b9bb": "Shenzhen Hankvision Technology CO.,LTD", "70baef": "Hangzhou H3C Technologies Co., Limited", + "70bb5b": "Apple, Inc.", "70bbe9": "Xiaomi Communications Co Ltd", "70bc10": "Microsoft Corporation", + "70bdd2": "Adva Network Security GmbH", "70bf3e": "Charles River Laboratories", "70bf92": "GN Audio A/S", "70c6ac": "Bosch Automotive Aftermarket", @@ -22311,6 +22391,7 @@ "740abc": "LightwaveRF Technology Ltd", "740ae1": "Huawei Device Co., Ltd.", "740cee": "Huawei Device Co., Ltd.", + "740ea4": "Apple, Inc.", "740edb": "Optowiz Co., Ltd", "7411b2": "Cisco Systems, Inc", "7412b3": "CHONGQING FUGUI ELECTRONICS CO.,LTD.", @@ -22329,6 +22410,7 @@ "741e93": "Fiberhome Telecommunication Technologies Co.,LTD", "741f4a": "Hangzhou H3C Technologies Co., Limited", "741f79": "YOUNGKOOK ELECTRONICS CO.,LTD", + "74205f": "Shenzhen Zhongruixin Intelligent Technology Co., Ltd.", "7422bb": "Huawei Device Co., Ltd.", "742344": "Xiaomi Communications Co Ltd", "74249f": "TIBRO Corp.", @@ -22354,6 +22436,7 @@ "7432c2": "KYOLIS", "743357": "vivo Mobile Communication Co., Ltd.", "7433a6": "Shenzhen SuperElectron Technology Co.,Ltd.", + "7433e9": "zte corporation", "743400": "MTG Co., Ltd.", "74342b": "HUAWEI TECHNOLOGIES CO.,LTD", "7434ae": "this is engineering Inc.", @@ -22507,6 +22590,7 @@ "74978e": "Nova Labs", "749975": "IBM Corporation", "749ac0": "Cachengo, Inc.", + "749b89": "HUAWEI TECHNOLOGIES CO.,LTD", "749be8": "Hitron Technologies. Inc", "749c52": "Huizhou Desay SV Automotive Co., Ltd.", "749ce3": "KodaCloud Canada, Inc", @@ -22780,6 +22864,7 @@ "78593e": "RAFI GmbH & Co.KG", "78595e": "Samsung Electronics Co.,Ltd", "785968": "Hon Hai Precision Ind. Co.,Ltd.", + "785994": "Alif Semiconductor, Inc.", "785b64": "Huawei Device Co., Ltd.", "785c28": "Prime Motion Inc.", "785c5e": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -22787,6 +22872,7 @@ "785dc8": "LG Electronics", "785ea2": "Sunitec Enterprise Co.,Ltd", "785ee8": "IEEE Registration Authority", + "785f28": "EM Microelectronic", "785f36": "Shenzhen Skyworth Digital Technology CO., Ltd", "785f4c": "Argox Information Co., Ltd.", "78605b": "TP-LINK TECHNOLOGIES CO.,LTD.", @@ -22994,6 +23080,7 @@ "78eb46": "HUAWEI TECHNOLOGIES CO.,LTD", "78ec22": "Shanghai Qihui Telecom Technology Co., LTD", "78ec74": "Kyland-USA", + "78edbc": "Co., Ltd", "78ef4c": "Unetconvergence Co., Ltd.", "78f09b": "Huawei Device Co., Ltd.", "78f1c6": "Cisco Systems, Inc", @@ -23113,6 +23200,7 @@ "7c477c": "IEEE Registration Authority", "7c48b2": "Vida Resources Lte Ltd", "7c49b9": "Plexus Manufacturing Sdn Bhd", + "7c49cf": "eero inc.", "7c49eb": "XIAOMI Electronics,CO.,LTD", "7c4a82": "Portsmith LLC", "7c4aa8": "MindTree Wireless PVT Ltd", @@ -23280,6 +23368,7 @@ "7cc3a1": "Apple, Inc.", "7cc4ef": "Devialet", "7cc537": "Apple, Inc.", + "7cc6b6": "Silicon Laboratories", "7cc6c4": "Kolff Computer Supplies b.v.", "7cc709": "SHENZHEN RF-LINK TECHNOLOGY CO.,LTD.", "7cc74a": "Fiberhome Telecommunication Technologies Co.,LTD", @@ -23535,6 +23624,7 @@ "807b1e": "Corsair Memory, Inc.", "807b3e": "Samsung Electronics Co.,Ltd", "807b85": "IEEE Registration Authority", + "807c0a": "zte corporation", "807c62": "Hangzhou Hikvision Digital Technology Co.,Ltd.", "807d14": "HUAWEI TECHNOLOGIES CO.,LTD", "807d1b": "Neosystem Co. Ltd.", @@ -23683,6 +23773,7 @@ "80f3ef": "Meta Platforms Technologies, LLC", "80f503": "ARRIS Group, Inc.", "80f593": "IRCO Sistemas de Telecomunicación S.A.", + "80f5ae": "Hangzhou Hikvision Digital Technology Co.,Ltd.", "80f5b5": "Texas Instruments", "80f62e": "Hangzhou H3C Technologies Co., Limited", "80f7a6": "Shenzhen C-Data Technology Co., Ltd.", @@ -23794,6 +23885,7 @@ "844bf5": "Hon Hai Precision Ind. Co.,Ltd.", "844dbe": "Fiberhome Telecommunication Technologies Co.,LTD", "844f03": "Ablelink Electronics Ltd", + "845075": "Huawei Device Co., Ltd.", "84509a": "Easy Soft TV Co., Ltd", "845181": "Samsung Electronics Co.,Ltd", "8454df": "Huawei Device Co., Ltd.", @@ -23825,6 +23917,7 @@ "846b48": "ShenZhen EepuLink Co., Ltd.", "846eb1": "Park Assist LLC", "846fce": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", + "847051": "ALPSALPINE CO,.LTD", "8470d7": "eero inc.", "847127": "Silicon Laboratories", "84716a": "Huawei Device Co., Ltd.", @@ -23937,6 +24030,7 @@ "84b8b8": "Mobility Technologies Communication Co., Ltd.", "84ba20": "Silicon Laboratories", "84ba3b": "CANON INC.", + "84bb26": "Texas Instruments", "84bb69": "ARRIS Group, Inc.", "84be52": "HUAWEI TECHNOLOGIES CO.,LTD", "84c0ef": "Samsung Electronics Co.,Ltd", @@ -24362,6 +24456,7 @@ "8c04ff": "Vantiva USA LLC", "8c0551": "Koubachi AG", "8c06cb": "Toradex AG", + "8c0734": "Private", "8c078c": "FLOW DATA INC", "8c088b": "Remote Solution", "8c09f4": "ARRIS Group, Inc.", @@ -24408,6 +24503,7 @@ "8c2f39": "IBA Dosimetry GmbH", "8c2fa6": "Solid Optics B.V.", "8c31e2": "DAYOUPLUS", + "8c3223": "JWIPC Technology Co.,Ltd.", "8c3330": "EmFirst Co., Ltd.", "8c3357": "HiteVision Digital Media Technology Co.,Ltd.", "8c3401": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", @@ -24481,6 +24577,7 @@ "8c6078": "Swissbit AG", "8c60e7": "MPGIO CO.,LTD", "8c6102": "Beijing Baofengmojing Technologies Co., Ltd", + "8c6120": "EM Microelectronic", "8c61a3": "ARRIS Group, Inc.", "8c640b": "Beyond Devices d.o.o.", "8c6422": "Sony Corporation", @@ -24548,6 +24645,7 @@ "8c8caa": "Electronics Technology co., ltd", "8c8d28": "Intel Corporate", "8c8e0d": "zte corporation", + "8c8e4e": "Baylan Olcu Aletleri San. ve Tic.A.S.", "8c8e76": "taskit GmbH", "8c8ef2": "Apple, Inc.", "8c8f8b": "China Mobile Chongqing branch", @@ -24567,6 +24665,7 @@ "8c9806": "SHENZHEN SEI ROBOTICS CO.,LTD", "8c986b": "Apple, Inc.", "8c99e6": "TCT mobile ltd", + "8c9b2d": "PLANTRONICS, INC.", "8c9f3b": "Qingdao Hisense Communications Co.,Ltd.", "8ca048": "Beijing NeTopChip Technology Co.,LTD", "8ca2fd": "Starry, Inc.", @@ -24745,6 +24844,7 @@ "902aee": "Xiaomi Communications Co Ltd", "902b34": "GIGA-BYTE TECHNOLOGY CO.,LTD.", "902bd2": "HUAWEI TECHNOLOGIES CO.,LTD", + "902c09": "Apple, Inc.", "902cc7": "C-MAX Asia Limited", "902cfb": "CanTops Co,.Ltd.", "902d77": "Edgecore Americas Networking Corporation", @@ -24779,6 +24879,7 @@ "903fea": "HUAWEI TECHNOLOGIES CO.,LTD", "9043e2": "Cornami, Inc", "904506": "Tokyo Boeki Medisys Inc.", + "904528": "Nintendo Co.,Ltd", "9046a2": "Tedipay UK Ltd", "9046b7": "Vadaro Pte Ltd", "904716": "RORZE CORPORATION", @@ -24911,6 +25012,7 @@ "909c4a": "Apple, Inc.", "909d7d": "ARRIS Group, Inc.", "909de0": "Newland Design + Assoc. Inc.", + "909e24": "ekey biometric systems gmbh", "909f33": "EFM Networks", "909f43": "Accutron Instruments Inc.", "90a137": "Beijing Splendidtel Communication Technology Co,. Ltd", @@ -24921,6 +25023,7 @@ "90a365": "HMD Global Oy", "90a46a": "SISNET CO., LTD", "90a4de": "Wistron Neweb Corporation", + "90a57d": "Huawei Device Co., Ltd.", "90a5af": "HUAWEI TECHNOLOGIES CO.,LTD", "90a62f": "NAVER", "90a6bf": "Quectel Wireless Solutions Co.,Ltd.", @@ -24929,6 +25032,7 @@ "90a7c1": "Pakedge Device and Software Inc.", "90a822": "Amazon Technologies Inc.", "90a935": "JWEntertainment", + "90a9f7": "IEEE Registration Authority", "90aac3": "Hitron Technologies. Inc", "90ab96": "Silicon Laboratories", "90ac3f": "BrightSign LLC", @@ -25068,6 +25172,7 @@ "9411da": "ITF Fröschl GmbH", "941457": "Shenzhen Sundray Technologies Company Limited", "94147a": "vivo Mobile Communication Co., Ltd.", + "9415b2": "Huawei Device Co., Ltd.", "941625": "Apple, Inc.", "941673": "Point Core SARL", "941700": "Xiaomi Communications Co Ltd", @@ -25099,6 +25204,7 @@ "942e63": "Finsécur", "94319b": "Alphatronics BV", "9431cb": "vivo Mobile Communication Co., Ltd.", + "943251": "ASKEY COMPUTER CORP", "9433dd": "Taco Inc", "943469": "Silicon Laboratories", "94350a": "Samsung Electronics Co.,Ltd", @@ -25113,6 +25219,7 @@ "943cc6": "Espressif Inc.", "943dc9": "Asahi Net, Inc.", "943ee4": "WiSA Technologies Inc", + "943f0c": "Genexis B.V.", "943fbb": "JSC RPC Istok named after Shokin", "943fc2": "Hewlett Packard Enterprise", "943fd6": "Apple, Inc.", @@ -25228,6 +25335,7 @@ "94a3ca": "KonnectONE, LLC", "94a408": "Shenzhen Trolink Technology CO, LTD", "94a40c": "Diehl Metering GmbH", + "94a4b5": "Mitsubishi Electric Corporation", "94a4f9": "HUAWEI TECHNOLOGIES CO.,LTD", "94a67e": "NETGEAR", "94a7b7": "zte corporation", @@ -25244,6 +25352,7 @@ "94aef0": "Cisco Systems, Inc", "94b01f": "Apple, Inc.", "94b10a": "Samsung Electronics Co.,Ltd", + "94b216": "Silicon Laboratories", "94b271": "HUAWEI TECHNOLOGIES CO.,LTD", "94b2cc": "PIONEER CORPORATION", "94b34f": "Ruckus Wireless", @@ -25378,6 +25487,7 @@ "98038a": "Texas Instruments", "98039b": "Mellanox Technologies, Inc.", "9803a0": "ABB n.v. Power Quality Products", + "9803cf": "SHENZHEN BILIAN ELECTRONIC CO.,LTD", "9803d8": "Apple, Inc.", "980637": "IEEE Registration Authority", "98063a": "Home Control Singapore Pte Ltd", @@ -25414,6 +25524,7 @@ "98226e": "Amazon Technologies Inc.", "9822ef": "Liteon Technology Corporation", "98234e": "Micromedia AG", + "98254a": "Big Field Global PTE. Ltd.", "98262a": "Applied Research Associates, Inc", "9826ad": "Quectel Wireless Solutions Co.,Ltd.", "982782": "IEEE Registration Authority", @@ -25708,6 +25819,7 @@ "9c1d58": "Texas Instruments", "9c1e95": "Actiontec Electronics, Inc", "9c1ea4": "Sdn. Bhd.", + "9c1ece": "ALT Co., Ltd.", "9c1ecf": "Valeo Telematik und Akustik GmbH", "9c1fca": "Hangzhou AlmightyDigit Technology Co., Ltd", "9c1fdd": "Accupix Inc.", @@ -25745,6 +25857,7 @@ "9c3583": "Nipro Diagnostics, Inc", "9c35eb": "Apple, Inc.", "9c36f8": "Hyundai Kefico", + "9c37cb": "Sony Interactive Entertainment Inc.", "9c37f4": "HUAWEI TECHNOLOGIES CO.,LTD", "9c3928": "Samsung Electronics Co.,Ltd", "9c3a9a": "Shenzhen Sundray Technologies Company Limited", @@ -25912,6 +26025,7 @@ "9caf6f": "ITEL MOBILE LIMITED", "9cafca": "Cisco Systems, Inc", "9cb008": "Ubiquitous Computing Technology Corporation", + "9cb1dc": "Earda Technologies co Ltd", "9cb206": "HMS Industrial Networks", "9cb2b2": "HUAWEI TECHNOLOGIES CO.,LTD", "9cb2e8": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -25991,6 +26105,7 @@ "9cf387": "Apple, Inc.", "9cf48e": "Apple, Inc.", "9cf531": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", + "9cf55f": "Harman/Becker Automotive Systems GmbH", "9cf61a": "Carrier Fire & Security", "9cf67d": "Ricardo Prague, s.r.o.", "9cf6dd": "IEEE Registration Authority", @@ -26118,6 +26233,7 @@ "a04fd4": "ADB Broadband Italia", "a0510b": "Intel Corporate", "a051c6": "Avaya Inc", + "a05272": "Apple, Inc.", "a05394": "Shenzhen zediel co., Ltd.", "a0554f": "Cisco Systems, Inc", "a055de": "ARRIS Group, Inc.", @@ -26244,6 +26360,7 @@ "a0b100": "ShenZhen Cando Electronics Co.,Ltd", "a0b339": "Intel Corporate", "a0b3cc": "Hewlett Packard", + "a0b40f": "Apple, Inc.", "a0b437": "GD Mission Systems", "a0b439": "Cisco Systems, Inc", "a0b4a5": "Samsung Electronics Co.,Ltd", @@ -26339,6 +26456,7 @@ "a0ff0c": "Hangzhou Hikvision Digital Technology Co.,Ltd.", "a0ff22": "SHENZHEN APICAL TECHNOLOGY CO., LTD", "a0ff70": "Vantiva USA LLC", + "a4004e": "Cisco Systems, Inc", "a400e2": "HUAWEI TECHNOLOGIES CO.,LTD", "a40130": "ABIsystems Co., LTD", "a402b9": "Intel Corporate", @@ -26359,6 +26477,7 @@ "a40e2b": "Facebook Inc", "a40e75": "Aruba, a Hewlett Packard Enterprise Company", "a40f98": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", + "a410b6": "Cisco Systems, Inc", "a41115": "Robert Bosch Engineering and Business Solutions pvt. Ltd.", "a41162": "Arlo Technology", "a41163": "IEEE Registration Authority", @@ -26414,6 +26533,7 @@ "a43523": "Guangdong Donyan Network Technologies Co.,Ltd.", "a4352d": "TRIZ Networks corp.", "a436c7": "LG Innotek", + "a4373e": "Huawei Device Co., Ltd.", "a43831": "RF elements s.r.o.", "a438cc": "Nintendo Co.,Ltd", "a438fc": "Plastic Logic", @@ -26536,6 +26656,7 @@ "a494dc": "Infinite Clouds", "a49733": "ASKEY COMPUTER CORP", "a4975c": "VTech Telecommunications Ltd.", + "a4978a": "LEAR", "a497b1": "CHONGQING FUGUI ELECTRONICS CO.,LTD.", "a497bb": "Hitachi Industrial Equipment Systems Co.,Ltd", "a49813": "ARRIS Group, Inc.", @@ -26677,7 +26798,9 @@ "a4f465": "ITEL MOBILE LIMITED", "a4f4c2": "VNPT TECHNOLOGY", "a4f522": "CHOFU SEISAKUSHO CO.,LTD", + "a4f6e8": "Apple, Inc.", "a4f7d0": "LAN Accessories Co., Ltd.", + "a4f841": "Apple, Inc.", "a4f933": "Intel Corporate", "a4f9e4": "AirVine Scientific, Inc.", "a4fa76": "New H3C Technologies Co., Ltd", @@ -26753,6 +26876,7 @@ "a84041": "Dragino Technology Co., Limited", "a8407d": "GD Midea Air-Conditioning Equipment Co.,Ltd.", "a84122": "Information Technology Co.,Ltd.", + "a841f4": "AzureWave Technology Inc.", "a842a1": "TP-Link Corporation Limited", "a842a7": "Jiangsu Huitong Group Co.,Ltd.", "a842e3": "Espressif Inc.", @@ -26834,6 +26958,7 @@ "a8798d": "Samsung Electronics Co.,Ltd", "a87b39": "Nokia Corporation", "a87c01": "Samsung Electronics Co.,Ltd", + "a87c45": "HUAWEI TECHNOLOGIES CO.,LTD", "a87cf8": "Apple, Inc.", "a87d12": "HUAWEI TECHNOLOGIES CO.,LTD", "a87e33": "Nokia Danmark A/S", @@ -27060,6 +27185,7 @@ "ac34cb": "Shanhai GBCOM Communication Technology Co. Ltd", "ac35ee": "FN-LINK TECHNOLOGY LIMITED", "ac3613": "Samsung Electronics Co.,Ltd", + "ac361b": "Hon Hai Precision Industry Co.,LTD", "ac3651": "Jiangsu Hengtong Terahertz Technology Co., Ltd.", "ac3728": "Taicang T&W Electronics", "ac3743": "HTC Corporation", @@ -27304,6 +27430,7 @@ "acdce5": "Procter & Gamble Company", "acde48": "Private", "acdf9f": "Arcadyan Corporation", + "acdfa1": "Apple, Inc.", "ace010": "Liteon Technology Corporation", "ace069": "ISAAC Instruments", "ace0d6": "koreabts", @@ -27392,6 +27519,7 @@ "b0285b": "JUHUA Technology Inc.", "b02a1f": "Wingtech Group (HongKong)Limited", "b02a43": "Google, Inc.", + "b02ee0": "Huawei Device Co., Ltd.", "b03055": "China Mobile IOT Company Limited", "b030c8": "Teal Drones, Inc.", "b03366": "vivo Mobile Communication Co., Ltd.", @@ -27599,6 +27727,7 @@ "b0c952": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "b0c95b": "Beijing Symtech CO.,LTD", "b0ca68": "Apple, Inc.", + "b0cae7": "Huawei Device Co., Ltd.", "b0ccfe": "Huawei Device Co., Ltd.", "b0ce18": "Zhejiang shenghui lighting co.,Ltd", "b0cf0e": "Mellanox Technologies, Inc.", @@ -27992,6 +28121,7 @@ "b80305": "Intel Corporate", "b80415": "Bayan Audio", "b805ab": "zte corporation", + "b8060d": "Tuya Smart Inc.", "b80716": "vivo Mobile Communication Co., Ltd.", "b80756": "Cisco Meraki", "b808cf": "Intel Corporate", @@ -28633,6 +28763,7 @@ "bcf685": "D-Link International", "bcf730": "Samsung Electronics Co.,Ltd", "bcf811": "Xiamen DNAKE Technology Co.,Ltd", + "bcf87e": "Arcadyan Corporation", "bcf88b": "zte corporation", "bcf9f2": "TEKO", "bcfab8": "Guangzhou Shiyuan Electronic Technology Company Limited", @@ -28751,6 +28882,7 @@ "c05d39": "Jiangsu Huitong Group Co.,Ltd.", "c05e6f": "V. Stonkaus firma Kodinis Raktas", "c05e79": "SHENZHEN HUAXUN ARK TECHNOLOGIES CO.,LTD", + "c05f87": "Legrand INTELLIGENT ELECTRICAL(HUIZHOU)CO.,LTD.", "c06118": "TP-LINK TECHNOLOGIES CO.,LTD.", "c0613d": "BioIntelliSense, Inc.", "c0619a": "IEEE Registration Authority", @@ -28804,6 +28936,7 @@ "c08c71": "Motorola Mobility LLC, a Lenovo Company", "c08d51": "Amazon Technologies Inc.", "c08f20": "Shenzhen Skyworth Digital Technology CO., Ltd", + "c09120": "Sichuan Tianyi Comheart Telecom Co.,LTD", "c09132": "Patriot Memory", "c09134": "ProCurve Networking by HP", "c091b9": "Amazon Technologies Inc.", @@ -28896,6 +29029,7 @@ "c0d391": "IEEE Registration Authority", "c0d3c0": "Samsung Electronics Co.,Ltd", "c0d46b": "Huawei Device Co., Ltd.", + "c0d60a": "Texas Instruments", "c0d682": "Arista Networks", "c0d7aa": "Arcadyan Corporation", "c0d834": "xvtec ltd", @@ -29056,6 +29190,7 @@ "c44e1f": "BlueN", "c44eac": "Shenzhen Shiningworth Technology Co., Ltd.", "c44f33": "Espressif Inc.", + "c44f5f": "Huawei Device Co., Ltd.", "c44f96": "Alps Alpine", "c45006": "Samsung Electronics Co.,Ltd", "c4518d": "Shenzhen YOUHUA Technology Co., Ltd", @@ -29068,6 +29203,7 @@ "c456fe": "Lava International Ltd.", "c4571f": "June Life Inc", "c4576e": "Samsung Electronics Co.,Ltd", + "c45781": "Limited", "c458c2": "Shenzhen TATFOOK Technology Co., Ltd.", "c45976": "Fugoo Coorporation", "c45a86": "Huawei Device Co., Ltd.", @@ -29159,6 +29295,7 @@ "c493d9": "Samsung Electronics Co.,Ltd", "c49500": "Amazon Technologies Inc.", "c4954d": "IEEE Registration Authority", + "c4955f": "Anhui Saida Technology Limited Liability Company", "c495a2": "SHENZHEN WEIJIU INDUSTRY AND TRADE DEVELOPMENT CO., LTD", "c49805": "Minieum Networks, Inc", "c4985c": "Hui Zhou Gaoshengda Technology Co.,LTD", @@ -29222,6 +29359,7 @@ "c4cad9": "Hangzhou H3C Technologies Co., Limited", "c4cb54": "Fibocom Auto Inc.", "c4cb6b": "Airista Flow, Inc.", + "c4cb76": "Microsoft Corporation", "c4cbe1": "Dell Inc.", "c4cd45": "Beijing Boomsense Technology CO.,LTD.", "c4cd82": "Hangzhou Lowan Information Technology Co., Ltd.", @@ -29254,6 +29392,7 @@ "c4e510": "Mechatro, Inc.", "c4e532": "Arcadyan Corporation", "c4e5b1": "Suzhou PanKore Integrated Circuit Technology Co. Ltd.", + "c4e733": "Clear Align LLC", "c4e7be": "SCSpro Co.,Ltd", "c4e90a": "D-Link International", "c4e92f": "AB Sciex", @@ -29306,6 +29445,7 @@ "c809a8": "Intel Corporate", "c80a35": "Qingdao Hisense Smart Life Technology Co., Ltd", "c80aa9": "Quanta Computer Inc.", + "c80c53": "China Mobile Group Device Co.,Ltd.", "c80cc8": "HUAWEI TECHNOLOGIES CO.,LTD", "c80d32": "Holoplot GmbH", "c80e14": "AVM Audiovisuelles Marketing und Computersysteme GmbH", @@ -29381,6 +29521,7 @@ "c84529": "IMK Networks Co.,Ltd", "c84544": "Co, Ltd", "c8458f": "Wyler AG", + "c84709": "Cisco Systems, Inc", "c84782": "Areson Technology Corp.", "c8478c": "Beken Corporation", "c848f5": "MEDISON Xray Co., Ltd", @@ -29483,6 +29624,7 @@ "c89bd7": "Realme Chongqing Mobile Telecommunications Corp.,Ltd.", "c89c13": "Inspiremobile", "c89c1d": "Cisco Systems, Inc", + "c89cbb": "Taicang T&W Electronics", "c89cdc": "Elitegroup Computer Systems Co.,Ltd.", "c89d18": "Huawei Device Co., Ltd.", "c89d6d": "ITEL MOBILE LIMITED", @@ -29497,6 +29639,7 @@ "c8a1ba": "Neul Ltd", "c8a2ce": "Oasis Media Systems LLC", "c8a362": "ASIX Electronics Corporation", + "c8a3e8": "CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD.", "c8a40d": "Cooler Master Technology Inc", "c8a608": "Ruckus Wireless", "c8a620": "Nebula, Inc", @@ -29550,6 +29693,7 @@ "c8ca79": "Ciena Corporation", "c8cb9e": "Intel Corporate", "c8cbb8": "Hewlett Packard", + "c8ccb5": "Hunter Douglas", "c8cd72": "Sagemcom Broadband SAS", "c8d019": "Shanghai Tigercel Communication Technology Co.,Ltd", "c8d083": "Apple, Inc.", @@ -29580,6 +29724,7 @@ "c8df84": "Texas Instruments", "c8e0eb": "Apple, Inc.", "c8e130": "Milkyway Group Ltd", + "c8e193": "ITEL MOBILE LIMITED", "c8e1a7": "Vertu Corporation Limited", "c8e265": "Intel Corporate", "c8e306": "eero inc.", @@ -29655,6 +29800,7 @@ "cc19a8": "PT Inovação e Sistemas SA", "cc1aa3": "Arista Networks", "cc1afa": "zte corporation", + "cc1b5a": "Ruckus Wireless", "cc1be0": "IEEE Registration Authority", "cc1e97": "HUAWEI TECHNOLOGIES CO.,LTD", "cc1eff": "Metrological Group BV", @@ -29689,6 +29835,7 @@ "cc34d7": "GEWISS S.P.A.", "cc3540": "Vantiva USA LLC", "cc355a": "SecuGen Corporation", + "cc36cf": "Cisco Systems, Inc", "cc37ab": "Edgecore Networks Corporation", "cc398c": "Shiningtek", "cc3a61": "SAMSUNG ELECTRO MECHANICS CO., LTD.", @@ -29710,6 +29857,7 @@ "cc4210": "Xiaomi Communications Co Ltd", "cc43e3": "Trump s.a.", "cc4463": "Apple, Inc.", + "cc45a5": "Texas Instruments", "cc4639": "WAAV, Inc.", "cc464e": "Samsung Electronics Co.,Ltd", "cc46d6": "Cisco Systems, Inc", @@ -29970,6 +30118,7 @@ "d007ca": "Juniper Networks", "d009c8": "Cisco Systems, Inc", "d00aab": "Yokogawa Digital Computer Corporation", + "d00b27": "Murata Manufacturing Co., Ltd.", "d00df7": "Huawei Device Co., Ltd.", "d00ea4": "Porsche Cars North America", "d00ed9": "Taicang T&W Electronics", @@ -30154,6 +30303,7 @@ "d09d0a": "LINKCOM", "d09dab": "TCT mobile ltd", "d09fd9": "IEEE Registration Authority", + "d0a0bb": "Shenzhen iComm Semiconductor CO.,LTD", "d0a0d6": "ChengDu TD Tech", "d0a311": "Neuberger Gebäudeautomation GmbH", "d0a46f": "China Dragon Technology Limited", @@ -30217,6 +30367,7 @@ "d0d471": "MVTECH co., Ltd", "d0d6cc": "Wintop", "d0d783": "HUAWEI TECHNOLOGIES CO.,LTD", + "d0d7be": "HUAWEI TECHNOLOGIES CO.,LTD", "d0d94f": "IEEE Registration Authority", "d0dad7": "Apple, Inc.", "d0db32": "Nokia Corporation", @@ -30341,6 +30492,7 @@ "d43fcb": "ARRIS Group, Inc.", "d440d0": "OCOSMOS Co., LTD", "d440f0": "HUAWEI TECHNOLOGIES CO.,LTD", + "d4413f": "Gen IV Technology LLC", "d44165": "Sichuan Tianyi Comheart Telecom Co.,LTD", "d4430e": "Zhejiang Dahua Technology Co., Ltd.", "d443a8": "Changzhou Haojie Electric Co., Ltd.", @@ -30440,6 +30592,7 @@ "d47c44": "IEEE Registration Authority", "d47dfc": "TECNO MOBILE LIMITED", "d47ee4": "China Mobile IOT Company Limited", + "d47f78": "Dopple B.V.", "d481ca": "iDevices, LLC", "d481d7": "Dell Inc.", "d4823e": "Argosy Technologies, Ltd.", @@ -30495,6 +30648,7 @@ "d49fdd": "Huawei Device Co., Ltd.", "d4a02a": "Cisco Systems, Inc", "d4a148": "HUAWEI TECHNOLOGIES CO.,LTD", + "d4a23d": "New H3C Technologies Co., Ltd", "d4a33d": "Apple, Inc.", "d4a38b": "ELE(GROUP)CO.,LTD", "d4a3eb": "Shenzhen iComm Semiconductor CO.,LTD", @@ -30617,6 +30771,7 @@ "d80093": "Aurender Inc.", "d8028a": "Shenzhen YOUHUA Technology Co., Ltd", "d8031a": "Laird Connectivity", + "d8032a": "COMMTACT LTD", "d8052e": "Skyviia Corporation", "d806d1": "Co,. Ltd.", "d807b6": "TP-LINK TECHNOLOGIES CO.,LTD.", @@ -30639,6 +30794,7 @@ "d8109f": "HUAWEI TECHNOLOGIES CO.,LTD", "d810cb": "Andrea Informatique", "d81265": "CHONGQING FUGUI ELECTRONICS CO.,LTD.", + "d8132a": "Espressif Inc.", "d81399": "Hui Zhou Gaoshengda Technology Co.,LTD", "d814d6": "SURE SYSTEM Co Ltd", "d814df": "Co., Ltd", @@ -30906,6 +31062,7 @@ "d8d45d": "Orbic North America", "d8d4e6": "Hytec Inter Co., Ltd.", "d8d5b9": "Rainforest Automation, Inc.", + "d8d668": "Tuya Smart Inc.", "d8d67e": "GSK CNC EQUIPMENT CO.,LTD", "d8d6f3": "Sdn. Bhd.", "d8d723": "IDS, Inc", @@ -30983,6 +31140,7 @@ "dc0d30": "Shenzhen Feasycom Technology Co., Ltd.", "dc0e96": "Palo Alto Networks", "dc0ea1": "CO., LTD. ", + "dc1057": "Apple, Inc.", "dc152d": "China Mobile Group Device Co.,Ltd.", "dc15c8": "AVM Audiovisuelles Marketing und Computersysteme GmbH", "dc15db": "Co., Ltd.", @@ -31077,6 +31235,7 @@ "dc58bc": "Thomas-Krenn.AG", "dc5e36": "Paterson Technology", "dc60a1": "Teledyne DALSA Professional Imaging", + "dc621f": "HUAWEI TECHNOLOGIES CO.,LTD", "dc6294": "Guangzhou Lango Electronics Technology Co.,Ltd.", "dc6373": "OBARA KOREA", "dc647c": "C.R.S. iiMotion GmbH", @@ -31148,6 +31307,7 @@ "dc9eab": "Chongqing Yipingfang Technology Co., Ltd.", "dc9fa4": "Nokia Corporation", "dc9fdb": "Ubiquiti Inc", + "dca0d0": "Amazon Technologies Inc.", "dca120": "Nokia", "dca266": "Hon Hai Precision Ind. Co.,Ltd.", "dca313": "Shenzhen Changjin Communication Technology Co.,Ltd", @@ -31248,6 +31408,7 @@ "dce533": "IEEE Registration Authority", "dce55b": "Google, Inc.", "dce578": "Experimental Factory of Scientific Engineering and Special Design Department", + "dce5d8": "zte corporation", "dce650": "Extreme Networks Headquarters", "dce71c": "AUG Elektronik GmbH", "dce838": "Limited", @@ -31784,6 +31945,7 @@ "e482cc": "Jumptronic GmbH", "e48326": "HUAWEI TECHNOLOGIES CO.,LTD", "e48399": "ARRIS Group, Inc.", + "e48429": "New H3C Technologies Co., Ltd", "e4842b": "HANGZHOU SOFTEL OPTIC CO., LTD", "e484d3": "Xiaomi Communications Co Ltd", "e48501": "Geberit International AG", @@ -31919,6 +32081,7 @@ "e4f8ef": "Samsung Electronics Co.,Ltd", "e4f939": "Minxon Hotel Technology INC.", "e4fa1d": "PAD Peripheral Advanced Design Inc.", + "e4fa5b": "Texas Instruments", "e4fac4": "Big Field Global PTE. Ltd.", "e4faed": "Samsung Electronics Co.,Ltd", "e4fafd": "Intel Corporate", @@ -31985,6 +32148,7 @@ "e828d5": "Cots Technology", "e82a44": "Liteon Technology Corporation", "e82aea": "Intel Corporate", + "e82bc5": "Huawei Device Co., Ltd.", "e82c6d": "SmartRG, Inc.", "e82e0c": "NETINT Technologies Inc.", "e82e24": "Out of the Fog Research LLC", @@ -32031,6 +32195,7 @@ "e8516e": "TSMART Inc.", "e85177": "Qingdao Intelligent&Precise Electronics Co.,Ltd.", "e8519d": "Yeonhab Precision Co.,LTD", + "e8519e": "Guangzhou Shiyuan Electronic Technology Company Limited", "e85484": "NEO Information Systems Co., Ltd.", "e855b4": "SAI Technology Inc.", "e85659": "Advanced-Connectek Inc.", @@ -32163,6 +32328,7 @@ "e8b541": "zte corporation", "e8b5d0": "Dell Inc.", "e8b6c2": "Juniper Networks", + "e8b722": "GreenTrol Automation ", "e8b748": "Cisco Systems, Inc", "e8ba70": "Cisco Systems, Inc", "e8bae2": "Xplora Technologies AS", @@ -32256,6 +32422,7 @@ "e8f408": "Intel Corporate", "e8f654": "HUAWEI TECHNOLOGIES CO.,LTD", "e8f724": "Hewlett Packard Enterprise", + "e8f72f": "HUAWEI TECHNOLOGIES CO.,LTD", "e8f791": "Xiaomi Communications Co Ltd", "e8f8d0": "Nokia Shanghai Bell Co., Ltd.", "e8f928": "RFTECH SRL", @@ -32283,6 +32450,7 @@ "ec08e5": "Motorola Mobility LLC, a Lenovo Company", "ec0bae": "Hangzhou BroadLink Technology Co.,Ltd", "ec0c96": "Nokia", + "ec0d51": "Apple, Inc.", "ec0d9a": "Mellanox Technologies, Inc.", "ec0de4": "Amazon Technologies Inc.", "ec0ec4": "Hon Hai Precision Ind. Co.,Ltd.", @@ -32396,6 +32564,7 @@ "ec6c9a": "Arcadyan Corporation", "ec6c9f": "Chengdu Volans Technology CO.,LTD", "ec6cb5": "zte corporation", + "ec6e79": "InHand Networks, INC.", "ec6f0b": "FADU, Inc.", "ec7097": "ARRIS Group, Inc.", "ec71db": "Reolink Innovation Limited", @@ -32440,6 +32609,7 @@ "ec8ead": "DLX", "ec8eae": "Nagravision SA", "ec8eb5": "Hewlett Packard", + "ec9161": "CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD.", "ec9233": "Eddyfi NDT Inc", "ec9327": "MEMMERT GmbH + Co. KG", "ec9365": "Mapper.ai, Inc.", @@ -32454,6 +32624,7 @@ "ec97e0": "Hangzhou Ezviz Software Co.,Ltd.", "ec986c": "Lufft Mess- und Regeltechnik GmbH", "ec98c1": "Beijing Risbo Network Technology Co.,Ltd", + "ec9a34": "Texas Instruments", "ec9a74": "Hewlett Packard", "ec9b2d": "China Mobile Group Device Co.,Ltd.", "ec9b5b": "Nokia Corporation", @@ -32485,6 +32656,7 @@ "ecb0e1": "Ciena Corporation", "ecb106": "Acuro Networks, Inc", "ecb1d7": "Hewlett Packard", + "ecb1e0": "Eltex Enterprise LTD", "ecb313": "SHENZHEN GONGJIN ELECTRONICS CO.,LT", "ecb4e8": "Wistron Mexico SA de CV", "ecb541": "SHINANO E and E Co.Ltd.", @@ -32498,6 +32670,7 @@ "ecbd1d": "Cisco Systems, Inc", "ecbe5f": "Vestel Elektronik San ve Tic. A.S.", "ecbedd": "Sagemcom Broadband SAS", + "ecbfd0": "Texas Instruments", "ecc018": "Cisco Systems, Inc", "ecc01b": "HUAWEI TECHNOLOGIES CO.,LTD", "ecc06a": "PowerChord Group Limited", @@ -32515,6 +32688,7 @@ "eccd6d": "Allied Telesis, Inc.", "ecce13": "Cisco Systems, Inc", "ecced7": "Apple, Inc.", + "eccf70": "FUJIAN STAR-NET COMMUNICATION CO.,LTD", "ecd00e": "MiraeRecognition Co., Ltd.", "ecd040": "GEA Farm Technologies GmbH", "ecd09f": "Xiaomi Communications Co Ltd", @@ -32627,6 +32801,7 @@ "f02e51": "Casa Systems", "f02f4b": "Apple, Inc.", "f02f74": "ASUSTek COMPUTER INC.", + "f02f9e": "Amazon Technologies Inc.", "f02fa7": "HUAWEI TECHNOLOGIES CO.,LTD", "f02fd8": "Bi2-Vision", "f0321a": "Mita-Teknik A/S", @@ -32634,6 +32809,7 @@ "f03404": "TCT mobile ltd", "f03575": "Hui Zhou Gaoshengda Technology Co.,LTD", "f037a1": "CO., LTD.", + "f037cf": "Huawei Device Co., Ltd.", "f03965": "Samsung Electronics Co.,Ltd", "f03a4b": "Bloombase, Inc.", "f03a55": "Omega Elektronik AS", @@ -32882,11 +33058,13 @@ "f0f7b3": "Phorm", "f0f7e7": "HUAWEI TECHNOLOGIES CO.,LTD", "f0f842": "KEEBOX, Inc.", + "f0f84a": "BUFFALO.INC", "f0f8f2": "Texas Instruments", "f0f9f7": "IES GmbH & Co. KG", "f0fac7": "Huawei Device Co., Ltd.", "f0fcc8": "ARRIS Group, Inc.", "f0fda0": "Acurix Networks Pty Ltd", + "f0fddd": "Foxtron Vehicle Technologies Co., Ltd.", "f0fe6b": "Shanghai High-Flying Electronics Technology Co., Ltd", "f0fee7": "Huawei Device Co., Ltd.", "f40046": "ON Semiconductor", @@ -32922,6 +33100,7 @@ "f415fd": "Shanghai Pateo Electronic Equipment Manufacturing Co., Ltd.", "f417b8": "AirTies Wireless Networks", "f419e2": "Volterra", + "f41a79": "IEEE Registration Authority", "f41a9c": "Xiaomi Communications Co Ltd", "f41ab0": "Shenzhen Xingguodu Technology Co., Ltd.", "f41ba1": "Apple, Inc.", @@ -33000,6 +33179,7 @@ "f44fd3": "shenzhen hemuwei technology co.,ltd", "f450eb": "Telechips Inc", "f45214": "Mellanox Technologies, Inc.", + "f45246": "ASKEY COMPUTER CORP", "f45420": "TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO ", "f45424": "Extreme Networks Headquarters", "f45433": "Rockwell Automation", @@ -33059,6 +33239,7 @@ "f47def": "Samsung Electronics Co.,Ltd", "f47f35": "Cisco Systems, Inc", "f48139": "CANON INC.", + "f4832c": "OHSUNG", "f483cd": "TP-LINK TECHNOLOGIES CO.,LTD.", "f483e1": "Shanghai Clouder Semiconductor Co.,Ltd", "f4844c": "Texas Instruments", @@ -33174,6 +33355,7 @@ "f4dca5": "DAWON DNS", "f4dcda": "Zhuhai Jiahe Communication Technology Co., limited", "f4dcf9": "HUAWEI TECHNOLOGIES CO.,LTD", + "f4dd06": "Samsung Electronics Co.,Ltd", "f4dd9e": "GoPro", "f4de0c": "ESPOD Ltd.", "f4deaf": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -33233,6 +33415,7 @@ "f8051c": "DRS Imaging and Targeting Solutions", "f8075d": "Huawei Device Co., Ltd.", "f8084f": "Sagemcom Broadband SAS", + "f809a4": "Henan Thinker Rail Transportation Research Inc.", "f80bbe": "ARRIS Group, Inc.", "f80bcb": "Cisco Systems, Inc", "f80bd0": "Co., Ltd.", @@ -33268,6 +33451,7 @@ "f81f32": "Motorola Mobility LLC, a Lenovo Company", "f82055": "Green Information System", "f820a9": "Huawei Device Co., Ltd.", + "f82111": "HMD Global Oy", "f82229": "Nokia Shanghai Bell Co., Ltd.", "f82285": "Cypress Technology CO., LTD.", "f82387": "Shenzhen Horn Audio Co.,Ltd.", @@ -33386,6 +33570,7 @@ "f86c03": "Shenzhen Teleone Technology Co., Ltd", "f86ce1": "Taicang T&W Electronics", "f86d73": "Zengge Co., Limited", + "f86dcc": "Wistron Neweb Corporation", "f86ecf": "Arcx Inc", "f86eee": "HUAWEI TECHNOLOGIES CO.,LTD", "f86fb0": "TP-LINK TECHNOLOGIES CO.,LTD.", @@ -33410,6 +33595,7 @@ "f87b62": "FASTWEL INTERNATIONAL CO., LTD. Taiwan Branch", "f87b7a": "ARRIS Group, Inc.", "f87b8c": "Amped Wireless", + "f87d3f": "Huawei Device Co., Ltd.", "f87d76": "Apple, Inc.", "f87fa5": "GREATEK", "f88096": "Elsys Equipamentos Eletrônicos Ltda", @@ -33480,6 +33666,7 @@ "f8abe5": "shenzhen worldelite electronics co., LTD", "f8ac65": "Intel Corporate", "f8ac6d": "Deltenna Ltd", + "f8acc1": "InnoXings Co., LTD.", "f8ad24": "Realme Chongqing Mobile Telecommunications Corp.,Ltd.", "f8adcb": "HMD Global Oy", "f8ae27": "John Deere Electronic Solutions", @@ -33709,6 +33896,7 @@ "fc500c": "Sitehop Ltd", "fc5090": "SIMEX Sp. z o.o.", "fc51a4": "ARRIS Group, Inc.", + "fc51b5": "HUAWEI TECHNOLOGIES CO.,LTD", "fc528d": "Vantiva USA LLC", "fc52ce": "Control iD", "fc539e": "Shanghai Wind Technologies Co.,Ltd", @@ -33773,6 +33961,7 @@ "fc8a3d": "zte corporation", "fc8b97": "SHENZHEN GONGJIN ELECTRONICS CO.,LT", "fc8c11": "Microsoft Corporation", + "fc8d13": "FUJIAN STAR-NET COMMUNICATION CO.,LTD", "fc8d3d": "Leapfive Tech. Ltd.", "fc8e5b": "China Mobile Iot Limited company", "fc8e6e": "StreamCCTV, LLC", @@ -33819,6 +34008,7 @@ "fca84a": "Sentinum GmbH", "fca89a": "Sunitec Enterprise Co.,Ltd", "fca89b": "Texas Instruments", + "fca8e0": "THE FURUKAWA ELECTRIC CO., LTD", "fca9b0": "INC.", "fca9dc": "Sdn. Bhd.", "fca9f5": "Xiaomi Communications Co Ltd", @@ -33852,6 +34042,7 @@ "fcbcd1": "HUAWEI TECHNOLOGIES CO.,LTD", "fcbd67": "Arista Networks", "fcbe7b": "vivo Mobile Communication Co., Ltd.", + "fcc0cc": "Yunke China Information Technology Limited", "fcc233": "ASUSTek COMPUTER INC.", "fcc23d": "Atmel Corporation", "fcc2de": "Murata Manufacturing Co., Ltd.", diff --git a/netutils/data_files/protocol_mappings.py b/netutils/data_files/protocol_mappings.py index a78ccb75..282553fa 100644 --- a/netutils/data_files/protocol_mappings.py +++ b/netutils/data_files/protocol_mappings.py @@ -5409,6 +5409,7 @@ "pcsync-http": {"port_number": 8444, "protocols": ["tcp", "udp"]}, "copy": {"port_number": 8445, "protocols": ["tcp"]}, "copy-disc": {"port_number": 8445, "protocols": ["udp"]}, + "matrix-fed": {"port_number": 8448, "protocols": ["tcp"]}, "npmp": {"port_number": 8450, "protocols": ["tcp", "udp"]}, "nexentamv": {"port_number": 8457, "protocols": ["tcp"]}, "cisco-avp": {"port_number": 8470, "protocols": ["tcp"]}, diff --git a/netutils/dns.py b/netutils/dns.py index 965ae879..8eb2b9c1 100644 --- a/netutils/dns.py +++ b/netutils/dns.py @@ -35,10 +35,10 @@ def is_fqdn_resolvable(hostname: str) -> bool: from your machine to the DNS server, an upstream DNS issue, etc. Args: - hostname (str): A FQDN that may or may not be resolvable. + hostname: A FQDN that may or may not be resolvable. Returns: - bool: The result as to whether or not the domain was valid. + The result as to whether or not the domain was valid. Examples: >>> from netutils.dns import is_fqdn_resolvable diff --git a/netutils/interface.py b/netutils/interface.py index 0c50625c..3466b271 100644 --- a/netutils/interface.py +++ b/netutils/interface.py @@ -89,7 +89,7 @@ def split_interface(interface: str) -> t.Tuple[str, str]: ('Eth', '1') >>> """ - head = interface.rstrip(r"/\0123456789. ") + head = interface.rstrip(r"/\0123456789.: ") tail = interface[len(head) :].lstrip() # noqa: E203 return (head, tail) @@ -147,8 +147,8 @@ def canonical_interface_name_list( """Function to return a list of interface's canonical name (fully expanded name). Use of explicit matches used to indicate a clear understanding on any potential - match. Regex and other looser matching methods were not implmented to avoid false - positive matches. As an example, it would make sense to do "[P|p][O|o]" which would + match. Regex and other looser matching methods were not implemented to avoid false + positive matches. As an example, it would make sense to do `[P|p][O|o]` which would incorrectly match PO = POS and Po = Port-channel, leading to a false positive, not easily troubleshot, found, or known. @@ -420,7 +420,7 @@ def _iter_tree(node: t.Dict[CharacterClass, t.Any], parents: t.List[CharacterCla """Walk a tree of interface name parts. Weights are assigned based on domain logic to produce a - 'cannonical' ordering of names. + 'canonical' ordering of names. """ for _, items in itertools.groupby(sorted(node.keys()), lambda t: t.weight): for item in sorted(items): @@ -438,16 +438,16 @@ def sort_interface_list(interfaces: t.List[str]) -> t.List[str]: nodes are removed. Args: - interfaces (list[str]): A list of interfaces to be sorted. The input list is not mutated by this function. + interfaces: A list of interfaces to be sorted. The input list is not mutated by this function. Returns: - list[str]: A **new** sorted, unique list elements from the input. + A **new** sorted, unique list elements from the input. Examples: >>> sort_interface_list(["Gi1/0/1", "Gi1/0/3", "Gi1/0/3.100", "Gi1/0/2", "Gi1/0/2.50", "Gi2/0/2", "Po40", "Po160", "Lo10"]) ['Gi1/0/1', 'Gi1/0/2', 'Gi1/0/2.50', 'Gi1/0/3', 'Gi1/0/3.100', 'Gi2/0/2', 'Lo10', 'Po40', 'Po160'] - >>> sort_interface_list(['GigabitEthernet1/0/1', 'GigabitEthernet1/0/3', 'GigabitEthernet1/0/2', "GigabitEthernett3/0/5", 'GigabitEthernet3/0/7', 'GigabitEthernet2/0/8.5', 'Port-channel40', 'Vlan20', 'Loopback10']) - ['GigabitEthernet1/0/1', 'GigabitEthernet1/0/2', 'GigabitEthernet1/0/3', 'GigabitEthernet2/0/8.5', 'GigabitEthernet3/0/7', 'GigabitEthernett3/0/5', 'Loopback10', 'Port-channel40', 'Vlan20'] + >>> sort_interface_list(['GigabitEthernet1/0/1', 'GigabitEthernet1/0/3', 'GigabitEthernet1/0/2', "GigabitEthernet3/0/5", 'GigabitEthernet3/0/7', 'GigabitEthernet2/0/8.5', 'Port-channel40', 'Vlan20', 'Loopback10']) + ['GigabitEthernet1/0/1', 'GigabitEthernet1/0/2', 'GigabitEthernet1/0/3', 'GigabitEthernet2/0/8.5', 'GigabitEthernet3/0/5', 'GigabitEthernet3/0/7', 'Loopback10', 'Port-channel40', 'Vlan20'] """ root: t.Dict[CharacterClass, t.Any] = {} for ifname in interfaces: diff --git a/netutils/ip.py b/netutils/ip.py index afbe91a2..7926e3c5 100644 --- a/netutils/ip.py +++ b/netutils/ip.py @@ -5,6 +5,8 @@ from netutils.constants import IPV4_MASKS, IPV6_MASKS +IPAddress = t.Union[ipaddress.IPv4Address, ipaddress.IPv6Address] + def ipaddress_address(ip: str, attr: str) -> t.Any: """Convenience function primarily built to expose ipaddress.ip_address to Jinja. @@ -236,6 +238,106 @@ def is_ip(ip: str) -> bool: return False +def is_ip_range(ip_range: str) -> bool: + """Verifies whether or not a string is a valid IP address range. + + An `ip_range` is in the format of `{ip_start}-{ip_end}`, IPs in str format, same IP version, and + ip_start is before ip_end. + + Args: + ip_range: An IP address range in string format that is properly formatted. + + Returns: + The result as to whether or not the string is a valid IP address. + + Examples: + >>> from netutils.ip import is_ip_range + >>> is_ip_range("10.100.100.255") + False + >>> is_ip_range("10.100.100.255-10.100.101.1") + True + >>> + """ + if "-" not in ip_range: + return False + start_ip, end_ip = ip_range.split("-") + if not is_ip(start_ip) or not is_ip(end_ip): + return False + start_ip_obj = ipaddress.ip_address(start_ip) + end_ip_obj = ipaddress.ip_address(end_ip) + if not isinstance(start_ip_obj, type(end_ip_obj)): + return False + # IP version being the same is enforced above, mypy disagrees, can safely ignore + if not start_ip_obj < end_ip_obj: # type: ignore + return False + return True + + +def is_ip_within(ip: str, ip_compare: t.Union[str, t.List[str]]) -> bool: + """ + Check if an IP address, IP subnet, or IP range is within the range of a list of IP addresses, IP subnets, and IP ranges. + + Args: + ip: IP address, IP subnet, or IP range to check. + ip_compare: String or list of IP addresses, IP subnets, and IP ranges to compare against. + + Returns: + True if the IP is in range, False otherwise. + + Examples: + >>> from netutils.ip import is_ip_within + >>> is_ip_within("10.100.100.10", "10.100.100.0/24") + True + >>> is_ip_within("10.100.100.0/25", ["10.100.100.0/24", "10.100.200.0/24"]) + True + >>> + >>> is_ip_within("10.100.100.10", ["10.100.100.8-10.100.100.20", "10.100.200.0/24"]) + True + >>> is_ip_within("10.100.100.8-10.100.100.20", ["10.100.100.0/24"]) + True + >>> is_ip_within("1.1.1.1", ["2.2.2.2", "3.3.3.3"]) + False + >>> + """ + + def _convert_ip(ip: str) -> str: + if is_ip(ip): + if "." in ip: + mask = "32" + if ":" in ip: + mask = "128" + return f"{ip}/{mask}" + return ip + + if "-" in ip: + ip_obj_start, ip_obj_end = get_range_ips(ip) + else: + ip_obj = ipaddress.ip_network(_convert_ip(ip)) + ip_obj_start = ip_obj[0] + ip_obj_end = ip_obj[-1] + + if isinstance(ip_compare, str): + ip_compare = [ip_compare] + + for item in ip_compare: + if "-" in item: + item_obj_start, item_obj_end = get_range_ips(item) + + else: + item_obj = ipaddress.ip_network(_convert_ip(item)) + item_obj_start = item_obj[0] + item_obj_end = item_obj[-1] + if not isinstance(item_obj_start, type(item_obj_end)): + raise ValueError( + f"IP range start `{item_obj_start}` and end `{item_obj_end}` IPs must both be same IPVersion." + ) + # Use this validation method, since it is consitent with ranges + # vs the `.subnet_of` method which is not. + if item_obj_start <= ip_obj_start <= ip_obj_end <= item_obj_end: # type: ignore + return True + return False + + def is_netmask(netmask: str) -> bool: """Verifies whether or not a string is a valid subnet mask. @@ -260,6 +362,32 @@ def is_netmask(netmask: str) -> bool: return False +def is_network(ip_network: str) -> bool: + """Verifies whether or not a string is a valid IP Network with a Mask. + + Args: + ip: An IP network in string format that is able to be converted by `ipaddress` library. + + Returns: + The result as to whether or not the string is a valid IP network. + + Examples: + >>> from netutils.ip import is_network + >>> is_network("10.100.100.0") + False + >>> is_network("10.100.100.0/24") + True + >>> + """ + if "/" not in ip_network: + return False + try: + ipaddress.ip_network(ip_network) + return True + except ValueError: + return False + + def netmask_to_cidr(netmask: str) -> int: """Creates a CIDR notation of a given subnet mask in decimal format. @@ -420,6 +548,31 @@ def get_peer_ip(ip_interface: str) -> str: return val[0] +def get_range_ips(ip_range: str) -> t.Tuple[IPAddress, IPAddress]: + """Get's the two IPs as a tuple of IPAddress objects. + + Args: + ip_range: An IP address range in string format that is properly formatted. + + Returns: + The start and end IP address of the range provided. + + Examples: + >>> from netutils.ip import get_range_ips + >>> get_range_ips("10.100.100.255-10.100.101.1") + (IPv4Address('10.100.100.255'), IPv4Address('10.100.101.1')) + >>> get_range_ips("2001::1-2001::10") + (IPv6Address('2001::1'), IPv6Address('2001::10')) + >>> + """ + if not is_ip_range(ip_range): + raise ValueError(r"Not a valid IP range format of `{start_ip}-{end_ip}`") + start_ip, end_ip = ip_range.split("-") + start_ip_obj = ipaddress.ip_address(start_ip) + end_ip_obj = ipaddress.ip_address(end_ip) + return start_ip_obj, end_ip_obj + + def get_usable_range(ip_network: str) -> str: """Given a network, return the string of usable IP addresses. diff --git a/netutils/lib_mapper.py b/netutils/lib_mapper.py index 076deaff..c51e75dc 100644 --- a/netutils/lib_mapper.py +++ b/netutils/lib_mapper.py @@ -114,70 +114,71 @@ "yamaha": {}, } # netmiko is the base name, so every key is a value, this ensure that. -NETMIKO_LIB_MAPPER = {} -for key in list(_NETMIKO_LIB_MAPPER.keys()): - NETMIKO_LIB_MAPPER[key] = key +NETMIKO_LIB_MAPPER = {key: key for key in sorted(_NETMIKO_LIB_MAPPER)} # ntc templates is primarily based on netmiko, so a copy is in order -NTCTEMPLATES_LIB_MAPPER = copy.deepcopy(NETMIKO_LIB_MAPPER) -NTCTEMPLATES_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox" -NTCTEMPLATES_LIB_MAPPER["huawei_vrp"] = "huawei_vrp" -NTCTEMPLATES_LIB_MAPPER["vmware_nsxv"] = "vmware_nsxv" -NTCTEMPLATES_LIB_MAPPER["aruba_aoscx"] = "aruba_aoscx" +_NTCTEMPLATES_LIB_MAPPER = copy.deepcopy(NETMIKO_LIB_MAPPER) +_NTCTEMPLATES_LIB_MAPPER["aruba_aoscx"] = "aruba_aoscx" +_NTCTEMPLATES_LIB_MAPPER["huawei_vrp"] = "huawei_vrp" +_NTCTEMPLATES_LIB_MAPPER["vmware_nsxv"] = "vmware_nsxv" +_NTCTEMPLATES_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox" +NTCTEMPLATES_LIB_MAPPER = {key: _NTCTEMPLATES_LIB_MAPPER[key] for key in sorted(_NTCTEMPLATES_LIB_MAPPER)} NAPALM_LIB_MAPPER = { + "aoscx": "aruba_aoscx", "asa": "cisco_asa", "cisco_wlc_ssh": "cisco_wlc", "eos": "arista_eos", + "f5": "bigip_f5", "fortios": "fortinet", "huawei": "huawei_vrp", "ios": "cisco_ios", - "nxos_ssh": "cisco_nxos", - "nxos": "cisco_nxos", "iosxr": "cisco_xr", "junos": "juniper_junos", + "nxos": "cisco_nxos", + "nxos_ssh": "cisco_nxos", "panos": "paloalto_panos", + "ros": "mikrotik_routeros", "sros": "nokia_sros", "vyos": "brocade_vyos", - "aoscx": "aruba_aoscx", - "ros": "mikrotik_routeros", } PYNTC_LIB_MAPPER = { - "cisco_asa_ssh": "cisco_asa", "arista_eos_eapi": "arista_eos", - "f5_tmos_icontrol": "f5_tmsh", + "cisco_aireos_ssh": "cisco_wlc", + "cisco_asa_ssh": "cisco_asa", "cisco_ios_ssh": "cisco_ios", - "juniper_junos_netconf": "juniper_junos", "cisco_nxos_nxapi": "cisco_nxos", - "cisco_aireos_ssh": "cisco_wlc", + "f5_tmos_icontrol": "f5_tmsh", + "juniper_junos_netconf": "juniper_junos", } + ANSIBLE_LIB_MAPPER = { "arista.eos.eos": "arista_eos", + "arubanetworks.aoscx": "aruba_aoscx", "ciena.saos6.saos6": "ciena_saos", "cisco.asa.asa": "cisco_asa", "cisco.ios.ios": "cisco_ios", "cisco.iosxr.iosxr": "cisco_xr", "cisco.nxos.nxos": "cisco_nxos", "community.network.ce": "huawei", - "dellemc.os6.os6": "dell_os6", - "dellemc.os9.os9": "dell_os9", - "dellemc.os10.0s10": "dell_os10", + "community.network.cnos": "lenovo_cnos", + "community.network.enos": "lenovo_enos", "community.network.eric_eccli": "ericsson_ipos", "community.network.exos": "extreme_exos", + "community.network.icx": "ruckus_icx", "community.network.ironware": "extreme_netiron", + "community.network.netvisor": "pluribus", "community.network.nos": "extreme_nos", + "community.network.routeros": "mikrotik_routeros", "community.network.slxos": "extreme_slx", + "community.network.sros": "nokia_sros", "community.network.voss": "extreme_vsp", + "dellemc.os10.0s10": "dell_os10", + "dellemc.os6.os6": "dell_os6", + "dellemc.os9.os9": "dell_os9", "junipernetworks.junos.junos": "juniper_junos", - "community.network.cnos": "lenovo_cnos", - "community.network.enos": "lenovo_enos", - "community.network.routeros": "mikrotik_routeros", - "community.network.netvisor": "pluribus", - "community.network.icx": "ruckus_icx", - "community.network.sros": "nokia_sros", "vyos.vyos.vyos": "vyos", - "arubanetworks.aoscx": "aruba_aoscx", } PYATS_LIB_MAPPER = { @@ -195,66 +196,92 @@ } SCRAPLI_LIB_MAPPER = { + "arista_eos": "arista_eos", + "aruba_aoscx": "aruba_aoscx", "cisco_iosxe": "cisco_ios", "cisco_iosxr": "cisco_xr", "cisco_nxos": "cisco_nxos", - "arista_eos": "arista_eos", "juniper_junos": "juniper_junos", - "aruba_aoscx": "aruba_aoscx", } HIERCONFIG_LIB_MAPPER = { + "eos": "arista_eos", + "fastiron": "ruckus_fastiron", "ios": "cisco_ios", "iosxe": "cisco_xe", "iosxr": "cisco_xr", "nxos": "cisco_nxos", - "eos": "arista_eos", - "fastiron": "ruckus_fastiron", +} + +NETUTILSPARSER_LIB_MAPPER = { + "arista_eos": "arista_eos", + "aruba_aoscx": "aruba_aoscx", + "bigip_f5": "bigip_f5", + "cisco_aireos": "cisco_aireos", + "cisco_asa": "cisco_asa", + "cisco_ios": "cisco_ios", + "cisco_iosxr": "cisco_iosxr", + "cisco_nxos": "cisco_nxos", + "citrix_netscaler": "citrix_netscaler", + "extreme_netiron": "extreme_netiron", + "fortinet_fortios": "fortinet_fortios", + "juniper_junos": "juniper_junos", + "linux": "linux", + "mikrotik_routeros": "mikrotik_routeros", + "mrv_optiswitch": "mrv_optiswitch", + "netscaler": "netscaler", + "nokia_sros": "nokia_sros", + "paloalto_panos": "paloalto_panos", + "ruckus_fastiron": "ruckus_fastiron", + "ubiquiti_airos": "ubiquiti_airos", } NAPALM_LIB_MAPPER_REVERSE = { "arista_eos": "eos", + "aruba_aoscx": "aoscx", + "bigip_f5": "f5", "brocade_vyos": "vyos", "cisco_asa": "asa", "cisco_ios": "ios", "cisco_nxos": "nxos", - "cisco_xr": "iosxr", "cisco_wlc": "cisco_wlc_ssh", + "cisco_xr": "iosxr", "fortinet": "fortios", "huawei_vrp": "huawei", "juniper_junos": "junos", - "paloalto_panos": "panos", - "nokia_sros": "sros", - "aruba_aoscx": "aoscx", "mikrotik_routeros": "ros", + "nokia_sros": "sros", + "paloalto_panos": "panos", } PYNTC_LIB_MAPPER_REVERSE = { - "cisco_asa": "cisco_asa_ssh", "arista_eos": "arista_eos_eapi", - "f5_tmsh": "f5_tmos_icontrol", + "cisco_asa": "cisco_asa_ssh", "cisco_ios": "cisco_ios_ssh", - "juniper_junos": "juniper_junos_netconf", "cisco_nxos": "cisco_nxos_nxapi", "cisco_wlc": "cisco_aireos_ssh", + "f5_tmsh": "f5_tmos_icontrol", + "juniper_junos": "juniper_junos_netconf", } + ANSIBLE_LIB_MAPPER_REVERSE = { "arista_eos": "arista.eos.eos", + "aruba_aoscx": "arubanetworks.aoscx", "ciena_saos": "ciena.saos6.saos6", "cisco_asa": "cisco.asa.asa", "cisco_ios": "cisco.ios.ios", - "cisco_xr": "cisco.iosxr.iosxr", "cisco_nxos": "cisco.nxos.nxos", - "huawei": "community.network.ce", + "cisco_xr": "cisco.iosxr.iosxr", + "dell_os10": "dellemc.os10.0s10", "dell_os6": "dellemc.os6.os6", "dell_os9": "dellemc.os9.os9", - "dell_os10": "dellemc.os10.0s10", "ericsson_ipos": "community.network.eric_eccli", "extreme_exos": "community.network.exos", "extreme_netiron": "community.network.ironware", "extreme_nos": "community.network.nos", "extreme_slx": "community.network.slxos", "extreme_vsp": "community.network.voss", + "huawei": "community.network.ce", "juniper_junos": "junipernetworks.junos.junos", "lenovo_cnos": "community.network.cnos", "lenovo_enos": "community.network.enos", @@ -263,53 +290,76 @@ "pluribus": "community.network.netvisor", "ruckus_icx": "community.network.icx", "vyos": "vyos.vyos.vyos", - "aruba_aoscx": "arubanetworks.aoscx", } PYATS_LIB_MAPPER_REVERSE = { "cisco_asa": "asa", - "f5_tmsh": "bigip", "cisco_dnac": "dnac", "cisco_ios": "iosxe", + "cisco_nxos": "nxos", + "cisco_viptella": "viptela", "cisco_xr": "iosxr", + "f5_tmsh": "bigip", "juniper_junos": "junos", "linux": "linux", - "cisco_nxos": "nxos", "nokia_sros": "sros", - "cisco_viptella": "viptela", } SCRAPLI_LIB_MAPPER_REVERSE = { + "arista_eos": "arista_eos", + "aruba_aoscx": "aruba_aoscx", "cisco_ios": "cisco_iosxe", - "cisco_xr": "cisco_iosxr", "cisco_nxos": "cisco_nxos", - "arista_eos": "arista_eos", + "cisco_xr": "cisco_iosxr", "juniper_junos": "juniper_junos", - "aruba_aoscx": "aruba_aoscx", } HIERCONFIG_LIB_MAPPER_REVERSE = { + "arista_eos": "eos", "cisco_ios": "ios", + "cisco_nxos": "nxos", "cisco_xe": "iosxe", "cisco_xr": "iosxr", - "cisco_nxos": "nxos", - "arista_eos": "eos", "ruckus_fastiron": "fastiron", } +NETUTILSPARSER_LIB_MAPPER_REVERSE = { + "arista_eos": "arista_eos", + "aruba_aoscx": "aruba_aoscx", + "bigip_f5": "bigip_f5", + "cisco_aireos": "cisco_aireos", + "cisco_asa": "cisco_asa", + "cisco_ios": "cisco_ios", + "cisco_iosxr": "cisco_iosxr", + "cisco_nxos": "cisco_nxos", + "citrix_netscaler": "citrix_netscaler", + "extreme_netiron": "extreme_netiron", + "fortinet_fortios": "fortinet_fortios", + "juniper_junos": "juniper_junos", + "linux": "linux", + "mikrotik_routeros": "mikrotik_routeros", + "mrv_optiswitch": "mrv_optiswitch", + "netscaler": "netscaler", + "nokia_sros": "nokia_sros", + "paloalto_panos": "paloalto_panos", + "ruckus_fastiron": "ruckus_fastiron", + "ubiquiti_airos": "ubiquiti_airos", +} + # Deep copy the reverse, where there is no actual translation happening. NETMIKO_LIB_MAPPER_REVERSE = copy.deepcopy(NETMIKO_LIB_MAPPER) NTCTEMPLATES_LIB_MAPPER_REVERSE = copy.deepcopy(NTCTEMPLATES_LIB_MAPPER) # Deep copy the reverse, where there is no actual translation happening with special # consideration for OS's not in netmiko. -MAIN_LIB_MAPPER = copy.deepcopy(NETMIKO_LIB_MAPPER) -MAIN_LIB_MAPPER["cisco_dnac"] = "cisco_dnac" -MAIN_LIB_MAPPER["cisco_viptella"] = "cisco_viptella" -MAIN_LIB_MAPPER["huawei_vrp"] = "huawei_vrp" -MAIN_LIB_MAPPER["lenovo_cnos"] = "lenovo_cnos" -MAIN_LIB_MAPPER["lenovo_enos"] = "lenovo_enos" -MAIN_LIB_MAPPER["ruckus_icx"] = "ruckus_icx" -MAIN_LIB_MAPPER["vmware_nsxv"] = "vmware_nsxv" -MAIN_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox" -MAIN_LIB_MAPPER["aruba_aoscx"] = "aruba_aoscx" +_MAIN_LIB_MAPPER = copy.deepcopy(NETMIKO_LIB_MAPPER) +_MAIN_LIB_MAPPER["aruba_aoscx"] = "aruba_aoscx" +_MAIN_LIB_MAPPER["cisco_dnac"] = "cisco_dnac" +_MAIN_LIB_MAPPER["cisco_viptella"] = "cisco_viptella" +_MAIN_LIB_MAPPER["huawei_vrp"] = "huawei_vrp" +_MAIN_LIB_MAPPER["lenovo_cnos"] = "lenovo_cnos" +_MAIN_LIB_MAPPER["lenovo_enos"] = "lenovo_enos" +_MAIN_LIB_MAPPER["ruckus_icx"] = "ruckus_icx" +_MAIN_LIB_MAPPER["vmware_nsxv"] = "vmware_nsxv" +_MAIN_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox" +MAIN_LIB_MAPPER = {key: _MAIN_LIB_MAPPER[key] for key in sorted(_MAIN_LIB_MAPPER)} diff --git a/netutils/mac.py b/netutils/mac.py index 1f29b86e..67f1b6d6 100644 --- a/netutils/mac.py +++ b/netutils/mac.py @@ -51,9 +51,13 @@ def is_valid_mac(mac: str) -> bool: def mac_to_format(mac: str, frmt: str = "MAC_NO_SPECIAL") -> str: """Converts the MAC address to a specific format. + The `frmt` is a combination delimiter (COLON, DASH, DOT) and number + of characters (TWO, FOUR), e.g. f'MAC_{delimiter}_{char_num}' or if no + special characters as `MAC_NO_SPECIAL`. + Args: mac: A MAC address in string format that matches one of the defined regex patterns. - frmt: A format in which the MAC address should be returned in. + frmt: A format in which the MAC address should be returned in, one of MAC_COLON_TWO, MAC_COLON_FOUR, MAC_DASH_TWO, MAC_DASH_FOUR, MAC_DOT_TWO, MAC_DOT_FOUR, MAC_NO_SPECIAL. Returns: A MAC address in the specified format. @@ -65,7 +69,8 @@ def mac_to_format(mac: str, frmt: str = "MAC_NO_SPECIAL") -> str: >>> """ if not MAC_CREATE.get(frmt): - raise ValueError(f"An invalid mac format was provided in: `{frmt}`") + format_choices = ", ".join(MAC_CREATE) + raise ValueError(f"An invalid mac format was provided in: `{frmt}`, not one of [{format_choices}]") mac = mac_normalize(mac) count = MAC_CREATE[frmt]["count"] char = MAC_CREATE[frmt]["char"] diff --git a/netutils/password.py b/netutils/password.py index 02f1bd86..5a9026a7 100644 --- a/netutils/password.py +++ b/netutils/password.py @@ -250,12 +250,12 @@ def encrypt_cisco_type5(unencrypted_password: str, salt: t.Optional[str] = None, if not salt: salt = "".join(secrets.choice(ALPHABET) for _ in range(salt_len)) elif not set(salt) <= set(ALPHABET): - raise ValueError(f"type5_pw salt used inproper characters, must be one of {ALPHABET}") + raise ValueError(f"type5_pw salt used improper characters, must be one of {ALPHABET}") return crypt.crypt(unencrypted_password, f"$1${salt}$") def encrypt_cisco_type7(unencrypted_password: str, salt: t.Optional[int] = None) -> str: - """Given an unencrypted password of Cisco Type 7 password, encypt it. + """Given an unencrypted password of Cisco Type 7 password, encrypt it. Args: unencrypted_password: A password that has not been encrypted, and will be compared against. @@ -397,7 +397,7 @@ def decrypt_juniper_type9(encrypted_password: str) -> str: nibble = stripped_password_characters[0 : len(decode)] # noqa: E203 stripped_password_characters = stripped_password_characters[len(decode) :] # noqa: E203 - # Decode value for nibble and convert to character, append to decryped password + # Decode value for nibble and convert to character, append to decrypted password value = 0 for index, char in enumerate(nibble): gap = ( diff --git a/netutils/regex.py b/netutils/regex.py index 67472ddb..cf851e76 100644 --- a/netutils/regex.py +++ b/netutils/regex.py @@ -109,7 +109,7 @@ def regex_search(pattern: str, string: str) -> t.Union[t.List[str], str, None]: def regex_split(pattern: str, string: str, maxsplit: int = 0) -> t.List[str]: - """Given a regex pattern and string, return the split the object based on the patern a single element or single element of original value if there is no match. + """Given a regex pattern and string, return the split the object based on the pattern a single element or single element of original value if there is no match. The main purpose of this function is provide a Jinja2 filter as this is simply a wrapper around `re.split`. diff --git a/netutils/utils.py b/netutils/utils.py index 9580c278..3c011314 100644 --- a/netutils/utils.py +++ b/netutils/utils.py @@ -5,6 +5,7 @@ _JINJA2_FUNCTION_MAPPINGS = { "asn_to_int": "asn.asn_to_int", + "int_to_asdot": "asn.int_to_asdot", "name_to_bits": "bandwidth.name_to_bits", "name_to_bytes": "bandwidth.name_to_bytes", "bits_to_name": "bandwidth.bits_to_name", @@ -34,7 +35,10 @@ "ip_subtract": "ip.ip_subtract", "is_classful": "ip.is_classful", "is_ip": "ip.is_ip", + "is_ip_range": "ip.is_ip_range", + "is_ip_within": "ip.is_ip_within", "is_netmask": "ip.is_netmask", + "is_network": "ip.is_network", "netmask_to_cidr": "ip.netmask_to_cidr", "cidr_to_netmask": "ip.cidr_to_netmask", "cidr_to_netmaskv6": "ip.cidr_to_netmaskv6", @@ -42,6 +46,7 @@ "get_broadcast_address": "ip.get_broadcast_address", "get_first_usable": "ip.get_first_usable", "get_peer_ip": "ip.get_peer_ip", + "get_range_ips": "ip.get_range_ips", "get_usable_range": "ip.get_usable_range", "ipaddress_address": "ip.ipaddress_address", "ipaddress_interface": "ip.ipaddress_interface", diff --git a/poetry.lock b/poetry.lock index 972da29d..9130579b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. - -[[package]] -name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" -optional = false -python-versions = ">=3.6" -files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, -] +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "astroid" @@ -30,6 +19,39 @@ wrapt = [ {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, ] +[[package]] +name = "astunparse" +version = "1.6.3" +description = "An AST unparser for Python" +optional = false +python-versions = "*" +files = [ + {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, + {file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"}, +] + +[package.dependencies] +six = ">=1.6.1,<2.0" +wheel = ">=0.23.0,<1.0" + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = true +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + [[package]] name = "babel" version = "2.12.1" @@ -101,6 +123,24 @@ files = [ tests = ["pytest (>=3.2.1,!=3.3.0)"] typecheck = ["mypy"] +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "black" version = "23.7.0" @@ -320,13 +360,13 @@ files = [ [[package]] name = "click" -version = "8.1.6" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, - {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -345,71 +385,63 @@ files = [ [[package]] name = "coverage" -version = "7.2.7" +version = "7.3.1" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, ] [package.extras] @@ -417,34 +449,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.2" +version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = true python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, - {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, - {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, - {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, ] [package.dependencies] @@ -461,39 +493,39 @@ test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest test-randomorder = ["pytest-randomly"] [[package]] -name = "dill" -version = "0.3.6" -description = "serialize all of python" +name = "cssselect" +version = "1.2.0" +description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" optional = false python-versions = ">=3.7" files = [ - {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, - {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, + {file = "cssselect-1.2.0-py2.py3-none-any.whl", hash = "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e"}, + {file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"}, ] -[package.extras] -graph = ["objgraph (>=1.7.2)"] - [[package]] -name = "docutils" -version = "0.18.1" -description = "Docutils -- Python Documentation Utilities" +name = "dill" +version = "0.3.7" +description = "serialize all of Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, ] +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "exceptiongroup" -version = "1.1.2" +version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, - {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] @@ -558,13 +590,13 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.32" +version = "3.1.35" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.32-py3-none-any.whl", hash = "sha256:e3d59b1c2c6ebb9dfa7a184daf3b6dd4914237e7488a1730a6d8f6f5d0b4187f"}, - {file = "GitPython-3.1.32.tar.gz", hash = "sha256:8d9b8cb1e80b9735e8717c9362079d3ce4c6e5ddeebedd0361b228c3a67a62f6"}, + {file = "GitPython-3.1.35-py3-none-any.whl", hash = "sha256:c19b4292d7a1d3c0f653858db273ff8a6614100d1eb1528b014ec97286193c09"}, + {file = "GitPython-3.1.35.tar.gz", hash = "sha256:9cbefbd1789a5fe9bcf621bb34d3f441f3a90c8461d377f84eda73e721d9b06b"}, ] [package.dependencies] @@ -572,13 +604,13 @@ gitdb = ">=4.0.1,<5" [[package]] name = "griffe" -version = "0.32.3" +version = "0.36.1" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" files = [ - {file = "griffe-0.32.3-py3-none-any.whl", hash = "sha256:d9471934225818bf8f309822f70451cc6abb4b24e59e0bb27402a45f9412510f"}, - {file = "griffe-0.32.3.tar.gz", hash = "sha256:14983896ad581f59d5ad7b6c9261ff12bdaa905acccc1129341d13e545da8521"}, + {file = "griffe-0.36.1-py3-none-any.whl", hash = "sha256:859b653fcde0a0af0e841a0109bac2b63a2f683132ae1ec8dae5fa81e94617a0"}, + {file = "griffe-0.36.1.tar.gz", hash = "sha256:11df63f1c85f605c73e4485de70ec13784049695d228241b0b582364a20c0536"}, ] [package.dependencies] @@ -595,17 +627,6 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - [[package]] name = "importlib-metadata" version = "6.8.0" @@ -625,6 +646,24 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +[[package]] +name = "importlib-resources" +version = "6.0.1" +description = "Read resources from Python packages" +optional = true +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.0.1-py3-none-any.whl", hash = "sha256:134832a506243891221b88b4ae1213327eea96ceb4e407a00d790bb0626f45cf"}, + {file = "importlib_resources-6.0.1.tar.gz", hash = "sha256:4359457e42708462b9626a04657c6208ad799ceb41e5c58c57ffa0e6a098a5d4"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -681,6 +720,44 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jsonschema" +version = "4.19.0" +description = "An implementation of JSON Schema validation for Python" +optional = true +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.19.0-py3-none-any.whl", hash = "sha256:043dc26a3845ff09d20e4420d6012a9c91c9aa8999fa184e7efcfeccb41e32cb"}, + {file = "jsonschema-4.19.0.tar.gz", hash = "sha256:6e1e7569ac13be8139b2dd2c21a55d350066ee3f80df06c608b398cdc6f30e8f"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +jsonschema-specifications = ">=2023.03.6" +pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.7.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = true +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, + {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, +] + +[package.dependencies] +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +referencing = ">=0.28.0" + [[package]] name = "junos-eznc" version = "2.6.7" @@ -755,7 +832,7 @@ files = [ name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, @@ -860,19 +937,20 @@ source = ["Cython (>=0.29.35)"] [[package]] name = "markdown" -version = "3.3.7" -description = "Python implementation of Markdown." +version = "3.4.4" +description = "Python implementation of John Gruber's Markdown." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, - {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, + {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, + {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, ] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"] testing = ["coverage", "pyyaml"] [[package]] @@ -899,6 +977,22 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "markdown2" +version = "2.4.10" +description = "A fast and complete Python implementation of Markdown" +optional = false +python-versions = ">=3.5, <4" +files = [ + {file = "markdown2-2.4.10-py2.py3-none-any.whl", hash = "sha256:e6105800483783831f5dc54f827aa5b44eb137ecef5a70293d8ecfbb4109ecc6"}, + {file = "markdown2-2.4.10.tar.gz", hash = "sha256:cdba126d90dc3aef6f4070ac342f974d63f415678959329cc7909f96cc235d72"}, +] + +[package.extras] +all = ["pygments (>=2.7.3)", "wavedrom"] +code-syntax-highlighting = ["pygments (>=2.7.3)"] +wavedrom = ["wavedrom"] + [[package]] name = "markupsafe" version = "2.1.3" @@ -926,6 +1020,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -993,13 +1097,13 @@ files = [ [[package]] name = "mkdocs" -version = "1.4.3" +version = "1.5.2" description = "Project documentation with Markdown." optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs-1.4.3-py3-none-any.whl", hash = "sha256:6ee46d309bda331aac915cd24aab882c179a933bd9e77b80ce7d2eaaa3f689dd"}, - {file = "mkdocs-1.4.3.tar.gz", hash = "sha256:5955093bbd4dd2e9403c5afaf57324ad8b04f16886512a3ee6ef828956481c57"}, + {file = "mkdocs-1.5.2-py3-none-any.whl", hash = "sha256:60a62538519c2e96fe8426654a67ee177350451616118a41596ae7c876bb7eac"}, + {file = "mkdocs-1.5.2.tar.gz", hash = "sha256:70d0da09c26cff288852471be03c23f0f521fc15cf16ac89c7a3bfb9ae8d24f9"}, ] [package.dependencies] @@ -1008,26 +1112,29 @@ colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" -markdown = ">=3.2.1,<3.4" +markdown = ">=3.2.1" +markupsafe = ">=2.0.1" mergedeep = ">=1.3.4" packaging = ">=20.5" +pathspec = ">=0.11.1" +platformdirs = ">=2.2.0" pyyaml = ">=5.1" pyyaml-env-tag = ">=0.1" watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" -version = "0.4.1" +version = "0.5.0" description = "Automatically link across pages in MkDocs." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, - {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, + {file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"}, + {file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"}, ] [package.dependencies] @@ -1036,22 +1143,28 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-material" -version = "8.5.8" +version = "9.2.4" description = "Documentation that simply works" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs_material-8.5.8-py3-none-any.whl", hash = "sha256:7ff092299e3a63cef99cd87e4a6cc7e7d9ec31fd190d766fd147c35572e6d593"}, - {file = "mkdocs_material-8.5.8.tar.gz", hash = "sha256:61396251819cf7f547f70a09ce6a7edb2ff5d32e47b9199769020b2d20a83d44"}, + {file = "mkdocs_material-9.2.4-py3-none-any.whl", hash = "sha256:2df876367625ff5e0f7112bc19a57521ed21ce9a2b85656baf9bb7f5dc3cb987"}, + {file = "mkdocs_material-9.2.4.tar.gz", hash = "sha256:25008187b89fc376cb4ed2312b1fea4121bf2bd956442f38afdc6b4dcc21c57d"}, ] [package.dependencies] -jinja2 = ">=3.0.2" +babel = ">=2.10.3" +colorama = ">=0.4" +jinja2 = ">=3.0" +lxml = ">=4.6" markdown = ">=3.2" -mkdocs = ">=1.4.0" +mkdocs = ">=1.5.2" mkdocs-material-extensions = ">=1.1" -pygments = ">=2.12" -pymdown-extensions = ">=9.4" +paginate = ">=0.5.6" +pygments = ">=2.14" +pymdown-extensions = ">=9.9.1" +readtime = ">=2.0" +regex = ">=2022.4.24" requests = ">=2.26" [[package]] @@ -1065,6 +1178,22 @@ files = [ {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, ] +[[package]] +name = "mkdocs-python-classy" +version = "0.1.3" +description = "Mkdocs plugin to view Python Classes." +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "mkdocs_python_classy-0.1.3-py3-none-any.whl", hash = "sha256:fdcfe1cffb39069abdd00873556a88493f463448fad27337ca781e39ad3472a8"}, + {file = "mkdocs_python_classy-0.1.3.tar.gz", hash = "sha256:a6ffdc7ac995b5773eaca97aea88cab43c10bb8e127b238ec052bd92389cc175"}, +] + +[package.dependencies] +astunparse = {version = ">=1.6.3,<2.0.0", markers = "python_version < \"3.9\""} +mkdocs = ">=1.4.0,<2.0.0" +pymdown-extensions = ">=6.3" + [[package]] name = "mkdocs-version-annotations" version = "1.0.0" @@ -1104,52 +1233,53 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.1.2" +version = "1.6.0" description = "A Python handler for mkdocstrings." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocstrings_python-1.1.2-py3-none-any.whl", hash = "sha256:c2b652a850fec8e85034a9cdb3b45f8ad1a558686edc20ed1f40b4e17e62070f"}, - {file = "mkdocstrings_python-1.1.2.tar.gz", hash = "sha256:f28bdcacb9bcdb44b6942a5642c1ea8b36870614d33e29e3c923e204a8d8ed61"}, + {file = "mkdocstrings_python-1.6.0-py3-none-any.whl", hash = "sha256:06f116112b335114372f2554b1bf61b709c74ab72605010e1605c1086932dffe"}, + {file = "mkdocstrings_python-1.6.0.tar.gz", hash = "sha256:6164ccaa6e488abc2a8fbccdfd1f21948c2c344d3f347847783a5d1c6fa2bfbf"}, ] [package.dependencies] -griffe = ">=0.24" +griffe = ">=0.35" mkdocstrings = ">=0.20" [[package]] name = "mypy" -version = "1.4.1" +version = "1.5.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, ] [package.dependencies] @@ -1160,7 +1290,6 @@ typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] @@ -1277,15 +1406,25 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "paginate" +version = "0.5.6" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +files = [ + {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, +] + [[package]] name = "paramiko" -version = "3.2.0" +version = "3.3.1" description = "SSH2 protocol library" optional = true python-versions = ">=3.6" files = [ - {file = "paramiko-3.2.0-py3-none-any.whl", hash = "sha256:df0f9dd8903bc50f2e10580af687f3015bf592a377cd438d2ec9546467a14eb8"}, - {file = "paramiko-3.2.0.tar.gz", hash = "sha256:93cdce625a8a1dc12204439d45033f3261bdb2c201648cfcdc06f9fd0f94ec29"}, + {file = "paramiko-3.3.1-py3-none-any.whl", hash = "sha256:b7bc5340a43de4287bbe22fe6de728aa2c22468b2a849615498dd944c2f275eb"}, + {file = "paramiko-3.3.1.tar.gz", hash = "sha256:6a3777a961ac86dbef375c5f5b8d50014a1a96d0fd7f054a43bc880134b0ff77"}, ] [package.dependencies] @@ -1300,13 +1439,13 @@ invoke = ["invoke (>=2.0)"] [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -1320,30 +1459,41 @@ files = [ {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, ] +[[package]] +name = "pkgutil-resolve-name" +version = "1.3.10" +description = "Resolve a name to an object." +optional = true +python-versions = ">=3.6" +files = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] + [[package]] name = "platformdirs" -version = "3.9.1" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, - {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -1419,13 +1569,13 @@ files = [ [[package]] name = "pygments" -version = "2.15.1" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] @@ -1433,17 +1583,17 @@ plugins = ["importlib-metadata"] [[package]] name = "pylint" -version = "2.17.4" +version = "2.17.5" description = "python code static checker" optional = false python-versions = ">=3.7.2" files = [ - {file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, - {file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, + {file = "pylint-2.17.5-py3-none-any.whl", hash = "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413"}, + {file = "pylint-2.17.5.tar.gz", hash = "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252"}, ] [package.dependencies] -astroid = ">=2.15.4,<=2.17.0-dev0" +astroid = ">=2.15.6,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, @@ -1462,19 +1612,22 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymdown-extensions" -version = "10.1" +version = "10.3" description = "Extension pack for Python Markdown." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.1-py3-none-any.whl", hash = "sha256:ef25dbbae530e8f67575d222b75ff0649b1e841e22c2ae9a20bad9472c2207dc"}, - {file = "pymdown_extensions-10.1.tar.gz", hash = "sha256:508009b211373058debb8247e168de4cbcb91b1bff7b5e961b2c3e864e00b195"}, + {file = "pymdown_extensions-10.3-py3-none-any.whl", hash = "sha256:77a82c621c58a83efc49a389159181d570e370fff9f810d3a4766a75fc678b66"}, + {file = "pymdown_extensions-10.3.tar.gz", hash = "sha256:94a0d8a03246712b64698af223848fd80aaf1ae4c4be29c8c61939b0467b5722"}, ] [package.dependencies] markdown = ">=3.2" pyyaml = "*" +[package.extras] +extra = ["pygments (>=2.12)"] + [[package]] name = "pynacl" version = "1.5.0" @@ -1503,18 +1656,36 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pyparsing" -version = "3.1.0" +version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = true python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.0-py3-none-any.whl", hash = "sha256:d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1"}, - {file = "pyparsing-3.1.0.tar.gz", hash = "sha256:edb662d6fe322d6e990b1594b5feaeadf806803359e3d4d42f11e295e588f0ea"}, + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, ] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyquery" +version = "2.0.0" +description = "A jquery-like library for python" +optional = false +python-versions = "*" +files = [ + {file = "pyquery-2.0.0-py3-none-any.whl", hash = "sha256:8dfc9b4b7c5f877d619bbae74b1898d5743f6ca248cfd5d72b504dd614da312f"}, + {file = "pyquery-2.0.0.tar.gz", hash = "sha256:963e8d4e90262ff6d8dec072ea97285dc374a2f69cad7776f4082abcf6a1d8ae"}, +] + +[package.dependencies] +cssselect = ">=1.2.0" +lxml = ">=2.1" + +[package.extras] +test = ["pytest", "pytest-cov", "requests", "webob", "webtest"] + [[package]] name = "pyserial" version = "3.5" @@ -1531,13 +1702,13 @@ cp2110 = ["hidapi"] [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -1567,13 +1738,13 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2023.3" +version = "2023.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] [[package]] @@ -1588,6 +1759,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1595,8 +1767,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1613,6 +1792,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1620,6 +1800,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1639,6 +1820,133 @@ files = [ [package.dependencies] pyyaml = "*" +[[package]] +name = "readtime" +version = "3.0.0" +description = "Calculates the time some text takes the average human to read, based on Medium's read time forumula" +optional = false +python-versions = "*" +files = [ + {file = "readtime-3.0.0.tar.gz", hash = "sha256:76c5a0d773ad49858c53b42ba3a942f62fbe20cc8c6f07875797ac7dc30963a9"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.0.1" +markdown2 = ">=2.4.3" +pyquery = ">=1.2" + +[[package]] +name = "referencing" +version = "0.30.2" +description = "JSON Referencing + Python" +optional = true +python-versions = ">=3.8" +files = [ + {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, + {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "regex" +version = "2023.8.8" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.6" +files = [ + {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"}, + {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"}, + {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"}, + {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"}, + {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"}, + {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"}, + {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"}, + {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"}, + {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"}, + {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"}, + {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"}, + {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"}, + {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"}, + {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"}, + {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"}, + {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"}, + {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -1662,13 +1970,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.4.2" +version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, - {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, + {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, + {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, ] [package.dependencies] @@ -1679,6 +1987,112 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9 [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "rpds-py" +version = "0.10.2" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = true +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.10.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:9f00d54b18dd837f1431d66b076737deb7c29ce3ebb8412ceaf44d5e1954ac0c"}, + {file = "rpds_py-0.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f4d561f4728f825e3b793a53064b606ca0b6fc264f67d09e54af452aafc5b82"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:013d6c784150d10236a74b4094a79d96a256b814457e388fc5a4ba9efe24c402"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd1142d22fdb183a0fff66d79134bf644401437fed874f81066d314c67ee193c"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a0536ed2b9297c75104e1a3da330828ba1b2639fa53b38d396f98bf7e3c68df"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:41bd430b7b63aa802c02964e331ac0b177148fef5f807d2c90d05ce71a52b4d4"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e8474f7233fe1949ce4e03bea698a600c2d5d6b51dab6d6e6336dbe69acf23e"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d9d7efaad48b859053b90dedd69bc92f2095084251e732e4c57ac9726bcb1e64"}, + {file = "rpds_py-0.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5612b0b1de8d5114520094bd5fc3d04eb8af6f3e10d48ef05b7c8e77c1fd9545"}, + {file = "rpds_py-0.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d5eaf988951f6ecb6854ca3300b87123599c711183c83da7ce39717a7cbdbce"}, + {file = "rpds_py-0.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75c8766734ac0053e1d683567e65e85306c4ec62631b0591caeb287ac8f72e08"}, + {file = "rpds_py-0.10.2-cp310-none-win32.whl", hash = "sha256:8de9b88f0cbac73cfed34220d13c57849e62a7099a714b929142425e926d223a"}, + {file = "rpds_py-0.10.2-cp310-none-win_amd64.whl", hash = "sha256:2275f1a022e2383da5d2d101fe11ccdcbae799148c4b83260a4b9309fa3e1fc2"}, + {file = "rpds_py-0.10.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dd91a7d7a9ce7f4983097c91ce211f3e5569cc21caa16f2692298a07e396f82b"}, + {file = "rpds_py-0.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e82b4a70cc67094f3f3fd77579702f48fcf1de7bdc67d79b8f1e24d089a6162c"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e281b71922208e00886e4b7ffbfcf27874486364f177418ab676f102130e7ec9"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3eb1a0d2b6d232d1bcdfc3fcc5f7b004ab3fbd9203011a3172f051d4527c0b6"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02945ae38fd78efc40900f509890de84cfd5ffe2cd2939eeb3a8800dc68b87cb"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccfb77f6dc8abffa6f1c7e3975ed9070a41ce5fcc11154d2bead8c1baa940f09"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af52078719209bef33e38131486fd784832dd8d1dc9b85f00a44f6e7437dd021"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56ba7c1100ed079527f2b995bf5486a2e557e6d5b733c52e8947476338815b69"}, + {file = "rpds_py-0.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:899b03a3be785a7e1ff84b237da71f0efa2f021512f147dd34ffdf7aa82cb678"}, + {file = "rpds_py-0.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22e6de18f00583f06928cc8d0993104ecc62f7c6da6478db2255de89a30e45d1"}, + {file = "rpds_py-0.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:edd74b760a6bb950397e7a7bd2f38e6700f6525062650b1d77c6d851b82f02c2"}, + {file = "rpds_py-0.10.2-cp311-none-win32.whl", hash = "sha256:18909093944727e068ebfc92e2e6ed1c4fa44135507c1c0555213ce211c53214"}, + {file = "rpds_py-0.10.2-cp311-none-win_amd64.whl", hash = "sha256:9568764e72d85cf7855ca78b48e07ed1be47bf230e2cea8dabda3c95f660b0ff"}, + {file = "rpds_py-0.10.2-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:0fc625059b83695fbb4fc8b7a8b66fa94ff9c7b78c84fb9986cd53ff88a28d80"}, + {file = "rpds_py-0.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c86231c66e4f422e7c13ea6200bb4048b3016c8bfd11b4fd0dabd04d2c8e3501"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56777c57246e048908b550af9b81b0ec9cf804fd47cb7502ccd93238bd6025c2"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4cb372e22e9c879bd9a9cc9b20b7c1fbf30a605ac953da45ecec05d8a6e1c77"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa3b3a43dabc4cc57a7800f526cbe03f71c69121e21b863fdf497b59b462b163"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d222086daa55421d599609b32d0ebe544e57654c4a0a1490c54a7ebaa67561"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:529aab727f54a937085184e7436e1d0e19975cf10115eda12d37a683e4ee5342"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e9b1531d6a898bdf086acb75c41265c7ec4331267d7619148d407efc72bd24"}, + {file = "rpds_py-0.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c2772bb95062e3f9774140205cd65d8997e39620715486cf5f843cf4ad8f744c"}, + {file = "rpds_py-0.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba1b28e44f611f3f2b436bd8290050a61db4b59a8e24be4465f44897936b3824"}, + {file = "rpds_py-0.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5aba767e64b494483ad60c4873bec78d16205a21f8247c99749bd990d9c846c2"}, + {file = "rpds_py-0.10.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:e1954f4b239d1a92081647eecfd51cbfd08ea16eb743b8af1cd0113258feea14"}, + {file = "rpds_py-0.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:de4a2fd524993578fe093044f291b4b24aab134390030b3b9b5f87fd41ab7e75"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e69737bd56006a86fd5a78b2b85447580a6138c930a75eb9ef39fe03d90782b1"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f40abbcc0a7d9a8a80870af839d317e6932533f98682aabd977add6c53beeb23"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29ec8507664f94cc08457d98cfc41c3cdbddfa8952438e644177a29b04937876"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcde80aefe7054fad6277762fb7e9d35c72ea479a485ae1bb14629c640987b30"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a65de5c02884760a14a58304fb6303f9ddfc582e630f385daea871e1bdb18686"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e92e5817eb6bfed23aa5e45bfe30647b83602bdd6f9e25d63524d4e6258458b0"}, + {file = "rpds_py-0.10.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2c8fc6c841ada60a86d29c9ebe2e8757c47eda6553f3596c560e59ca6e9b6fa1"}, + {file = "rpds_py-0.10.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:8557c807388e6617161fe51b1a4747ea8d1133f2d2ad8e79583439abebe58fbd"}, + {file = "rpds_py-0.10.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:00e97d43a36811b78fa9ad9d3329bf34f76a31e891a7031a2ac01450c9b168ab"}, + {file = "rpds_py-0.10.2-cp38-none-win32.whl", hash = "sha256:1ed3d5385d14be894e12a9033be989e012214a9811e7194849c94032ad69682a"}, + {file = "rpds_py-0.10.2-cp38-none-win_amd64.whl", hash = "sha256:02b4a2e28eb24dac4ef43dda4f6a6f7766e355179b143f7d0c76a1c5488a307b"}, + {file = "rpds_py-0.10.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:2a55631b93e47956fbc97d69ba2054a8c6a4016f9a3064ec4e031f5f1030cb90"}, + {file = "rpds_py-0.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ffbf1b38c88d0466de542e91b08225d51782282512f8e2b11715126c41fda48"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213f9ef5c02ec2f883c1075d25a873149daadbaea50d18d622e9db55ec9849c2"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b00150a9a3fd0a8efaa90bc2696c105b04039d50763dd1c95a34c88c5966cb57"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ab0f7aabdbce4a202e013083eeab71afdb85efa405dc4a06fea98cde81204675"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cd0c9fb5d40887500b4ed818770c68ab4fa6e0395d286f9704be6751b1b7d98"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8578fc6c8bdd0201327503720fa581000b4bd3934abbf07e2628d1ad3de157d"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d27d08056fcd61ff47a0cd8407eff4d3e816c82cb6b9c6f0ce9a0ad49225f81"}, + {file = "rpds_py-0.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c8f6526df47953b07c45b95c4d1da6b9a0861c0e5da0271db96bb1d807825412"}, + {file = "rpds_py-0.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177c033e467a66a054dd3a9534167234a3d0b2e41445807b13b626e01da25d92"}, + {file = "rpds_py-0.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c74cbee9e532dc34371127f7686d6953e5153a1f22beab7f953d95ee4a0fe09"}, + {file = "rpds_py-0.10.2-cp39-none-win32.whl", hash = "sha256:05a1382905026bdd560f806c8c7c16e0f3e3fb359ba8868203ca6e5799884968"}, + {file = "rpds_py-0.10.2-cp39-none-win_amd64.whl", hash = "sha256:3fd503c27e7b7034128e30847ecdb4bff4ca5e60f29ad022a9f66ae8940d54ac"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4a96147791e49e84207dd1530109aa0e9eeaf1c8b7a59f150047fc0fcdf9bb64"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:203eb1532d51591d32e8dfafd60b5d31347ea7278c8da02b4b550287f6abe28b"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2f416cdfe92f5fbb77177f5f3f7830059d1582db05f2c7119bf80069d1ab69b"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2660000e1a113869c86eb5cc07f3343467490f3cd9d0299f81da9ddae7137b7"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1adb04e4b4e41bf30aaa77eeb169c1b9ba9e5010e2e6ce8d6c17e1446edc9b68"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bca97521ee786087f0c5ef318fef3eef0266a9c3deff88205523cf353af7394"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4969592e3cdeefa4cbb15a26cec102cbd4a1d6e5b695fac9fa026e19741138c8"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df61f818edf7c8626bfa392f825860fb670b5f8336e238eb0ec7e2a5689cdded"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b589d93a60e78fe55d5bc76ee8c2bf945dbdbb7cd16044c53e0307604e448de1"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:73da69e1f612c3e682e34dcb971272d90d6f27b2c99acff444ca455a89978574"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:89438e8885a186c69fe31f7ef98bb2bf29688c466c3caf9060f404c0be89ae80"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c4ecc4e9a5d73a816cae36ee6b5d8b7a0c72013cae1e101406e832887c3dc2d8"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:907b214da5d2fcff0b6ddb83de1333890ca92abaf4bbf8d9c61dc1b95c87fd6e"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb44644371eaa29a3aba7b69b1862d0d56f073bb7585baa32e4271a71a91ee82"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:80c3cf46511653f94dfe07c7c79ab105c4164d6e1dfcb35b7214fb9af53eaef4"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaba0613c759ebf95988a84f766ca6b7432d55ce399194f95dde588ad1be0878"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0527c97dcd8bb983822ee31d3760187083fd3ba18ac4dd22cf5347c89d5628f4"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cdfd649011ce2d90cb0dd304c5aba1190fac0c266d19a9e2b96b81cfd150a09"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:75eea40355a8690459c7291ce6c8ce39c27bd223675c7da6619f510c728feb97"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1b804cfad04f862d6a84af9d1ad941b06f671878f0f7ecad6c92007d423de6"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:bf77f9017fcfa1232f98598a637406e6c33982ccba8a5922339575c3e2b90ea5"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:46c4c550bf59ce05d6bff2c98053822549aaf9fbaf81103edea325e03350bca1"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:46af4a742b90c7460e94214f923452c2c1d050a9da1d2b8d4c70cbc045e692b7"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2a86d246a160d98d820ee7d02dc18c923c228de095be362e57b9fd8970b2c4a1"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae141c9017f8f473a6ee07a9425da021816a9f8c0683c2e5442f0ccf56b0fc62"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1147bc3d0dd1e549d991110d0a09557ec9f925dbc1ca62871fcdab2ec9d716b"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fce7a8ee8d0f682c953c0188735d823f0fcb62779bf92cd6ba473a8e730e26ad"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c7f9d70f99e1fbcbf57c75328b80e1c0a7f6cad43e75efa90a97221be5efe15"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b309908b6ff5ffbf6394818cb73b5a2a74073acee2c57fe8719046389aeff0d"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ff1f585a0fdc1415bd733b804f33d386064a308672249b14828130dd43e7c31"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:0188b580c490bccb031e9b67e9e8c695a3c44ac5e06218b152361eca847317c3"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:abe081453166e206e3a8c6d8ace57214c17b6d9477d7601ac14a365344dbc1f4"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9118de88c16947eaf5b92f749e65b0501ea69e7c2be7bd6aefc12551622360e1"}, + {file = "rpds_py-0.10.2.tar.gz", hash = "sha256:289073f68452b96e70990085324be7223944c7409973d13ddfe0eea1c1b5663b"}, +] + [[package]] name = "scp" version = "0.14.5" @@ -1695,19 +2109,19 @@ paramiko = "*" [[package]] name = "setuptools" -version = "68.0.0" +version = "68.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = true -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-68.2.0-py3-none-any.whl", hash = "sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8"}, + {file = "setuptools-68.2.0.tar.gz", hash = "sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -1743,162 +2157,16 @@ files = [ ] [[package]] -name = "sphinx" -version = "6.2.1" -description = "Python documentation generator" +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ - {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, - {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, -] - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.20" -imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.13" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] - -[[package]] -name = "sphinx-rtd-theme" -version = "1.2.2" -description = "Read the Docs theme for Sphinx" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "sphinx_rtd_theme-1.2.2-py2.py3-none-any.whl", hash = "sha256:6a7e7d8af34eb8fc57d52a09c6b6b9c46ff44aea5951bc831eeb9245378f3689"}, - {file = "sphinx_rtd_theme-1.2.2.tar.gz", hash = "sha256:01c5c5a72e2d025bd23d1f06c59a4831b06e6ce6c01fdd5ebfe9986c0a880fc7"}, + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] -[package.dependencies] -docutils = "<0.19" -sphinx = ">=1.6,<7" -sphinxcontrib-jquery = ">=4,<5" - -[package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.4" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.1" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -description = "Extension to include jQuery on newer Sphinx releases" -optional = false -python-versions = ">=2.7" -files = [ - {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, - {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, -] - -[package.dependencies] -Sphinx = ">=1.8" - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - [[package]] name = "stevedore" version = "5.1.0" @@ -1952,13 +2220,13 @@ files = [ [[package]] name = "tomlkit" -version = "0.11.8" +version = "0.12.1" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ - {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, - {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, ] [[package]] @@ -2078,6 +2346,20 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] +[[package]] +name = "wheel" +version = "0.41.2" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "wheel-0.41.2-py3-none-any.whl", hash = "sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8"}, + {file = "wheel-0.41.2.tar.gz", hash = "sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + [[package]] name = "wrapt" version = "1.15.0" @@ -2209,9 +2491,9 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] -optionals = ["napalm"] +optionals = ["jsonschema", "napalm"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "31acbdd2552dcd4f4f421b8ec8bc0ba4c39aa2bf2b3b03a557ade07c64478520" +content-hash = "edbe4200862a8ff8d2c55b57de0a0c7b1b58acbe0275bea2d1444bc2059c1728" diff --git a/pyproject.toml b/pyproject.toml index 99b1a0e9..ff78e42e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "netutils" -version = "1.5.0" +version = "1.6.0" description = "Common helper functions useful in network automation." authors = ["Network to Code, LLC "] license = "Apache-2.0" @@ -27,11 +27,12 @@ include = [ [tool.poetry.dependencies] python = "^3.8" napalm = {version = "^4.0.0", optional = true} +jsonschema = {version = "^4.17.3", optional = true} [tool.poetry.extras] -optionals = ["napalm"] +optionals = ["jsonschema", "napalm"] -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] bandit = "*" black = "*" coverage = "*" @@ -41,19 +42,19 @@ pylint = "*" pytest = "*" pyyaml = "*" pydocstyle = "*" -sphinx-rtd-theme = "*" toml = "*" yamllint = "*" mypy = "*" -mkdocs = "1.4.3" -mkdocs-material = "8.5.8" -mkdocstrings = "0.22.0" -mkdocstrings-python = "1.1.2" +mkdocs = "1.5.2" +mkdocs-material = "9.2.4" mkdocs-version-annotations = "1.0.0" +mkdocstrings = "0.22.0" +mkdocstrings-python = "1.6.0" +mkdocs-python-classy = "0.1.3" [tool.black] line-length = 120 -target-version = ['py311'] +target-version = ['py38', 'py39', 'py310', 'py311'] include = '\.pyi?$' exclude = ''' ( diff --git a/tasks.py b/tasks.py index cdbb3296..045025af 100644 --- a/tasks.py +++ b/tasks.py @@ -178,7 +178,7 @@ def pylint(context, local=INVOKE_LOCAL): context (obj): Used to run specific commands local (bool): Define as `True` to execute locally """ - exec_cmd = 'find . -name "*.py" | grep -v "tests/unit/mock" | xargs pylint' + exec_cmd = 'find . -name "*.py" | grep -vE "(tests/unit/mock|netutils/data_files)" | xargs pylint' run_cmd(context, exec_cmd, local) diff --git a/tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_backup.txt b/tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_backup.txt similarity index 100% rename from tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_backup.txt rename to tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_backup.txt diff --git a/tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_feature.py b/tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_feature.py similarity index 100% rename from tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_feature.py rename to tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_feature.py diff --git a/tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_intended.txt b/tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_intended.txt similarity index 100% rename from tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_intended.txt rename to tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_intended.txt diff --git a/tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_received.json b/tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_received.json similarity index 100% rename from tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_received.json rename to tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_received.json diff --git a/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_backup.txt b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_backup.txt new file mode 100644 index 00000000..37b74b82 --- /dev/null +++ b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_backup.txt @@ -0,0 +1,229 @@ +### PART ### +## +##AF.v4.1.0 +#board.sysid=0xaf01 +#board.cpurevision=0x4e840102 +#board.name=airFiber 24G +#board.shortname=AF24 +#board.subtype= +#board.netmodes=16; +#board.hwaddr=802AA8CF8DB6 +#board.reboot=130 +#board.upgrade=330 +#board.phycount=1 +#board.phy.1.maxmtu=1500 +#board.gps=1 +#board.airfiber=1 +#board.portnumber=1 +#board.fdd=1 +#feature.gps=1 +#feature.rssi.leds=0 +#feature.gps.leds=0 +#radio.1.name= +#radio.1.shortname= +#radio.1.bus=spi +#radio.1.rf_revision=0x0000 +#radio.1.subsystemid=0xaf01 +#radio.1.subvendorid=0x0777 +#radio.1.txpower.max=43 +#radio.1.txpower.offset=0 +#radio.1.antennas=1 +#radio.1.def_antenna=4 +#radio.1.antenna.1.id=4 +#radio.1.antenna.1.name=Combined +#radio.1.antenna.1.gain=0 +#radio.1.ccode=840 +#radio.1.ccode.fixed=0 +#radio.1.ccode.locked=1 +#radio.1.ptp_only=1 +#radio.1.ptp_sta=1 +#radio.1.chanbw=100 +#deac9d0212791db5ede667c016876ba4 +### PART ### +afnetconf.1.atpctarget=0 +afnetconf.1.autoneg=enabled +afnetconf.1.carrierdrop=off +afnetconf.1.carrierdropdebounce=0 +afnetconf.1.carrierdropto=5 +afnetconf.1.cdblockafterpulse=disabled +afnetconf.1.cdfirstsessiontimer=0 +afnetconf.1.cdfirstsessionwait=disabled +afnetconf.1.cdtracknosession=disabled +afnetconf.1.devname=af0 +afnetconf.1.flowcntl=disabled +afnetconf.1.mcastfilter=disabled +afnetconf.1.mincapholdoff=0 +afnetconf.1.mincapholdoffup=0 +afnetconf.1.minrxcapacity=0 +afnetconf.1.minrxcapacityup=0 +afnetconf.1.mintxcapacity=0 +afnetconf.1.mintxcapacityup=0 +afnetconf.1.status=enabled +afnetconf.status=enabled +airview.tcp_port=18888 +airview.tcpport=18888 +bridge.1.devname=br0 +bridge.1.fd=1 +bridge.1.port.1.devname=eth0 +bridge.1.port.1.status=enabled +bridge.1.port.2.devname=air0 +bridge.1.port.2.status=enabled +bridge.1.status=enabled +bridge.1.stp.status=disabled +bridge.status=enabled +dhcpc.1.devname=eth0 +dhcpc.1.status=disabled +dhcpc.2.devname=air0 +dhcpc.2.status=disabled +dhcpc.3.devname=br0 +dhcpc.3.status=disabled +dhcpc.status=disabled +discovery.cdp.status=disabled +discovery.status=enabled +dyndns.status=disabled +ebtables.status=enabled +ebtables.sys.arpnat.status=disabled +ebtables.sys.eap.status=disabled +ebtables.sys.status=enabled +gui.language=en_US +httpd.https.port=443 +httpd.https.status=disabled +httpd.port=80 +httpd.session.timeout=300 +httpd.status=enabled +netconf.1.allmulti=disabled +netconf.1.autoip.status=disabled +netconf.1.autoneg=enabled +netconf.1.devname=eth0 +netconf.1.hwaddr.mac= +netconf.1.hwaddr.status=disabled +netconf.1.ip=0.0.0.0 +netconf.1.mtu=1500 +netconf.1.netmask=255.255.255.0 +netconf.1.promisc=enabled +netconf.1.role=bridge_port +netconf.1.status=enabled +netconf.1.up=enabled +netconf.2.allmulti=disabled +netconf.2.autoip.status=disabled +netconf.2.devname=air0 +netconf.2.hwaddr.mac= +netconf.2.hwaddr.status=disabled +netconf.2.ip=0.0.0.0 +netconf.2.mtu=1500 +netconf.2.netmask=255.255.255.0 +netconf.2.promisc=enabled +netconf.2.role=bridge_port +netconf.2.status=enabled +netconf.2.up=enabled +netconf.3.allmulti=disabled +netconf.3.autoip.status=disabled +netconf.3.devname=br0 +netconf.3.hwaddr.mac= +netconf.3.hwaddr.status=disabled +netconf.3.ip=10.10.10.10 +netconf.3.mtu=1500 +netconf.3.netmask=255.255.255.0 +netconf.3.role=mlan +netconf.3.status=enabled +netconf.3.up=enabled +netconf.status=enabled +netmode=airfiber +ntpclient.status=disabled +ppp.status=disabled +pwdog.delay=300 +pwdog.host= +pwdog.period=300 +pwdog.retry=3 +pwdog.status=disabled +radio.1.antenna.gain=33 +radio.1.atpctarget=0 +radio.1.cable.loss=0 +radio.1.countrycode=840 +radio.1.dfsthreshold=0 +radio.1.dualfreqmode=enabled +radio.1.duplex=half +radio.1.dutycycle=50 +radio.1.framelength=-1 +radio.1.gps_sync=disabled +radio.1.key=s:password1 +radio.1.linkname=UBNT +radio.1.mode=slave +radio.1.pwrbackoff=disabled +radio.1.pwrout.10x=0 +radio.1.pwrout.4x=43 +radio.1.pwrout.6x=43 +radio.1.pwrout.8x=33 +radio.1.rate.auto=enabled +radio.1.rate=6x +radio.1.rfrate=6x +radio.1.rx_freq10=null +radio.1.rx_freq2=null +radio.1.rx_freq3=null +radio.1.rx_freq4=null +radio.1.rx_freq5=null +radio.1.rx_freq6=null +radio.1.rx_freq7=null +radio.1.rx_freq8=null +radio.1.rx_freq9=null +radio.1.rx_freq=24.1GHz +radio.1.rxchanbw=2048 +radio.1.rxgain=low +radio.1.sharedant=disabled +radio.1.sisomode=disabled +radio.1.status=enabled +radio.1.streammode=disabled +radio.1.subsystemid=0xaf01 +radio.1.tx_freq2=null +radio.1.tx_freq3=null +radio.1.tx_freq=24.1GHz +radio.1.txchanbw=2048 +radio.1.txpower=33 +radio.countrycode=840 +radio.status=enabled +resolv.host.1.name=UBNT +resolv.host.1.status=enabled +resolv.nameserver.1.ip=1.1.1.1 +resolv.nameserver.1.status=enabled +resolv.nameserver.2.ip= +resolv.nameserver.2.status=disabled +resolv.nameserver.status=enabled +resolv.status=disabled +route.1.comment= +route.1.devname=br0 +route.1.gateway=10.10.10.1 +route.1.ip=0.0.0.0 +route.1.netmask=0 +route.1.status=enabled +route.status=enabled +snmp.community=admin +snmp.contact=lab +snmp.location=lab +snmp.status=enabled +sshd.auth.passwd=enabled +sshd.port=22 +sshd.status=enabled +syslog.remote.port=514 +syslog.remote.status=disabled +syslog.status=disabled +system.button.reset=enabled +system.cfg.version=65540 +system.date.status=disabled +system.date.timestamp= +system.eirp.status=enabled +system.latitude= +system.longitude= +system.timezone=GMT +telnetd.port=23 +telnetd.status=enabled +ucode.1.rxscramblekey=0 +ucode.1.txscramblekey=0 +udapi_bridge.callhome.status=disabled +unms.uri=- +update.check.status=enabled +users.1.name=ubnt +users.1.password=$2$GXWw8TsR$eBl/.jP1ibig7GUV4nn65. +users.1.status=enabled +users.2.status=disabled +users.status=enabled +vlan.status=disabled \ No newline at end of file diff --git a/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_feature.py b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_feature.py new file mode 100644 index 00000000..9cac2e12 --- /dev/null +++ b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_feature.py @@ -0,0 +1,5 @@ +features = [ + {"name": "radio_country_code", "ordered": True, "section": ["radio.1.countrycode"]}, + {"name": "snmp", "ordered": True, "section": ["snmp.community"]}, + {"name": "radio_mode", "ordered": True, "section": ["radio.1.mode"]}, +] diff --git a/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_intended.txt b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_intended.txt new file mode 100644 index 00000000..fd934b8b --- /dev/null +++ b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_intended.txt @@ -0,0 +1,229 @@ +### PART ### +## +##AF.v4.1.0 +#board.sysid=0xaf01 +#board.cpurevision=0x4e840102 +#board.name=airFiber 24G +#board.shortname=AF24 +#board.subtype= +#board.netmodes=16; +#board.hwaddr=802AA8CF8DB6 +#board.reboot=130 +#board.upgrade=330 +#board.phycount=1 +#board.phy.1.maxmtu=1500 +#board.gps=1 +#board.airfiber=1 +#board.portnumber=1 +#board.fdd=1 +#feature.gps=1 +#feature.rssi.leds=0 +#feature.gps.leds=0 +#radio.1.name= +#radio.1.shortname= +#radio.1.bus=spi +#radio.1.rf_revision=0x0000 +#radio.1.subsystemid=0xaf01 +#radio.1.subvendorid=0x0777 +#radio.1.txpower.max=43 +#radio.1.txpower.offset=0 +#radio.1.antennas=1 +#radio.1.def_antenna=4 +#radio.1.antenna.1.id=4 +#radio.1.antenna.1.name=Combined +#radio.1.antenna.1.gain=0 +#radio.1.ccode=840 +#radio.1.ccode.fixed=0 +#radio.1.ccode.locked=1 +#radio.1.ptp_only=1 +#radio.1.ptp_sta=1 +#radio.1.chanbw=100 +#deac9d0212791db5ede667c016876ba4 +### PART ### +afnetconf.1.atpctarget=0 +afnetconf.1.autoneg=enabled +afnetconf.1.carrierdrop=off +afnetconf.1.carrierdropdebounce=0 +afnetconf.1.carrierdropto=5 +afnetconf.1.cdblockafterpulse=disabled +afnetconf.1.cdfirstsessiontimer=0 +afnetconf.1.cdfirstsessionwait=disabled +afnetconf.1.cdtracknosession=disabled +afnetconf.1.devname=af0 +afnetconf.1.flowcntl=disabled +afnetconf.1.mcastfilter=disabled +afnetconf.1.mincapholdoff=0 +afnetconf.1.mincapholdoffup=0 +afnetconf.1.minrxcapacity=0 +afnetconf.1.minrxcapacityup=0 +afnetconf.1.mintxcapacity=0 +afnetconf.1.mintxcapacityup=0 +afnetconf.1.status=enabled +afnetconf.status=enabled +airview.tcp_port=18888 +airview.tcpport=18888 +bridge.1.devname=br0 +bridge.1.fd=1 +bridge.1.port.1.devname=eth0 +bridge.1.port.1.status=enabled +bridge.1.port.2.devname=air0 +bridge.1.port.2.status=enabled +bridge.1.status=enabled +bridge.1.stp.status=disabled +bridge.status=enabled +dhcpc.1.devname=eth0 +dhcpc.1.status=disabled +dhcpc.2.devname=air0 +dhcpc.2.status=disabled +dhcpc.3.devname=br0 +dhcpc.3.status=disabled +dhcpc.status=disabled +discovery.cdp.status=disabled +discovery.status=enabled +dyndns.status=disabled +ebtables.status=enabled +ebtables.sys.arpnat.status=disabled +ebtables.sys.eap.status=disabled +ebtables.sys.status=enabled +gui.language=en_US +httpd.https.port=443 +httpd.https.status=disabled +httpd.port=80 +httpd.session.timeout=300 +httpd.status=enabled +netconf.1.allmulti=disabled +netconf.1.autoip.status=disabled +netconf.1.autoneg=enabled +netconf.1.devname=eth0 +netconf.1.hwaddr.mac= +netconf.1.hwaddr.status=disabled +netconf.1.ip=0.0.0.0 +netconf.1.mtu=1500 +netconf.1.netmask=255.255.255.0 +netconf.1.promisc=enabled +netconf.1.role=bridge_port +netconf.1.status=enabled +netconf.1.up=enabled +netconf.2.allmulti=disabled +netconf.2.autoip.status=disabled +netconf.2.devname=air0 +netconf.2.hwaddr.mac= +netconf.2.hwaddr.status=disabled +netconf.2.ip=0.0.0.0 +netconf.2.mtu=1500 +netconf.2.netmask=255.255.255.0 +netconf.2.promisc=enabled +netconf.2.role=bridge_port +netconf.2.status=enabled +netconf.2.up=enabled +netconf.3.allmulti=disabled +netconf.3.autoip.status=disabled +netconf.3.devname=br0 +netconf.3.hwaddr.mac= +netconf.3.hwaddr.status=disabled +netconf.3.ip=10.10.10.10 +netconf.3.mtu=1500 +netconf.3.netmask=255.255.255.0 +netconf.3.role=mlan +netconf.3.status=enabled +netconf.3.up=enabled +netconf.status=enabled +netmode=airfiber +ntpclient.status=disabled +ppp.status=disabled +pwdog.delay=300 +pwdog.host= +pwdog.period=300 +pwdog.retry=3 +pwdog.status=disabled +radio.1.antenna.gain=33 +radio.1.atpctarget=0 +radio.1.cable.loss=0 +radio.1.countrycode=840 +radio.1.dfsthreshold=0 +radio.1.dualfreqmode=enabled +radio.1.duplex=half +radio.1.dutycycle=50 +radio.1.framelength=-1 +radio.1.gps_sync=disabled +radio.1.key=s:password1 +radio.1.linkname=UBNT +radio.1.mode=master +radio.1.pwrbackoff=disabled +radio.1.pwrout.10x=0 +radio.1.pwrout.4x=43 +radio.1.pwrout.6x=43 +radio.1.pwrout.8x=33 +radio.1.rate.auto=enabled +radio.1.rate=6x +radio.1.rfrate=6x +radio.1.rx_freq10=null +radio.1.rx_freq2=null +radio.1.rx_freq3=null +radio.1.rx_freq4=null +radio.1.rx_freq5=null +radio.1.rx_freq6=null +radio.1.rx_freq7=null +radio.1.rx_freq8=null +radio.1.rx_freq9=null +radio.1.rx_freq=24.1GHz +radio.1.rxchanbw=2048 +radio.1.rxgain=low +radio.1.sharedant=disabled +radio.1.sisomode=disabled +radio.1.status=enabled +radio.1.streammode=disabled +radio.1.subsystemid=0xaf01 +radio.1.tx_freq2=null +radio.1.tx_freq3=null +radio.1.tx_freq=24.1GHz +radio.1.txchanbw=2048 +radio.1.txpower=33 +radio.countrycode=840 +radio.status=enabled +resolv.host.1.name=UBNT +resolv.host.1.status=enabled +resolv.nameserver.1.ip=1.1.1.1 +resolv.nameserver.1.status=enabled +resolv.nameserver.2.ip= +resolv.nameserver.2.status=disabled +resolv.nameserver.status=enabled +resolv.status=disabled +route.1.comment= +route.1.devname=br0 +route.1.gateway=10.10.10.1 +route.1.ip=0.0.0.0 +route.1.netmask=0 +route.1.status=enabled +route.status=enabled +snmp.community=admin +snmp.contact=lab +snmp.location=lab +snmp.status=enabled +sshd.auth.passwd=enabled +sshd.port=22 +sshd.status=enabled +syslog.remote.port=514 +syslog.remote.status=disabled +syslog.status=disabled +system.button.reset=enabled +system.cfg.version=65540 +system.date.status=disabled +system.date.timestamp= +system.eirp.status=enabled +system.latitude= +system.longitude= +system.timezone=GMT +telnetd.port=23 +telnetd.status=enabled +ucode.1.rxscramblekey=0 +ucode.1.txscramblekey=0 +udapi_bridge.callhome.status=disabled +unms.uri=- +update.check.status=enabled +users.1.name=ubnt +users.1.password=$2$GXWw8TsR$eBl/.jP1ibig7GUV4nn65. +users.1.status=enabled +users.2.status=disabled +users.status=enabled +vlan.status=disabled \ No newline at end of file diff --git a/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_received.json b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_received.json new file mode 100644 index 00000000..14ebeeae --- /dev/null +++ b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_received.json @@ -0,0 +1,32 @@ +{ + "radio_country_code": { + "compliant": true, + "missing": "", + "extra": "", + "cannot_parse": true, + "unordered_compliant": true, + "ordered_compliant": true, + "actual": "radio.1.countrycode=840", + "intended": "radio.1.countrycode=840" + }, + "snmp": { + "compliant": true, + "missing": "", + "extra": "", + "cannot_parse": true, + "unordered_compliant": true, + "ordered_compliant": true, + "actual": "snmp.community=admin", + "intended": "snmp.community=admin" + }, + "radio_mode": { + "compliant": false, + "missing": "radio.1.mode=master", + "extra": "radio.1.mode=slave", + "cannot_parse": true, + "unordered_compliant": false, + "ordered_compliant": false, + "actual": "radio.1.mode=slave", + "intended": "radio.1.mode=master" + } +} \ No newline at end of file diff --git a/tests/unit/mock/config/parser/base/citrix_netscaler/netscaler_full_received.py b/tests/unit/mock/config/parser/base/netscaler/netscaler_full_received.py similarity index 100% rename from tests/unit/mock/config/parser/base/citrix_netscaler/netscaler_full_received.py rename to tests/unit/mock/config/parser/base/netscaler/netscaler_full_received.py diff --git a/tests/unit/mock/config/parser/base/citrix_netscaler/netscaler_full_sent.txt b/tests/unit/mock/config/parser/base/netscaler/netscaler_full_sent.txt similarity index 100% rename from tests/unit/mock/config/parser/base/citrix_netscaler/netscaler_full_sent.txt rename to tests/unit/mock/config/parser/base/netscaler/netscaler_full_sent.txt diff --git a/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_received.py b/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_received.py new file mode 100644 index 00000000..fbe3e756 --- /dev/null +++ b/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_received.py @@ -0,0 +1,192 @@ +from netutils.config.parser import ConfigLine + +data = [ + ConfigLine(config_line="##AF.v4.1.0", parents=()), + ConfigLine(config_line="afnetconf.1.atpctarget=0", parents=()), + ConfigLine(config_line="afnetconf.1.autoneg=enabled", parents=()), + ConfigLine(config_line="afnetconf.1.carrierdrop=off", parents=()), + ConfigLine(config_line="afnetconf.1.carrierdropdebounce=0", parents=()), + ConfigLine(config_line="afnetconf.1.carrierdropto=5", parents=()), + ConfigLine(config_line="afnetconf.1.cdblockafterpulse=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.cdfirstsessiontimer=0", parents=()), + ConfigLine(config_line="afnetconf.1.cdfirstsessionwait=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.cdtracknosession=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.devname=af0", parents=()), + ConfigLine(config_line="afnetconf.1.flowcntl=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.mcastfilter=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.mincapholdoff=0", parents=()), + ConfigLine(config_line="afnetconf.1.mincapholdoffup=0", parents=()), + ConfigLine(config_line="afnetconf.1.minrxcapacity=0", parents=()), + ConfigLine(config_line="afnetconf.1.minrxcapacityup=0", parents=()), + ConfigLine(config_line="afnetconf.1.mintxcapacity=0", parents=()), + ConfigLine(config_line="afnetconf.1.mintxcapacityup=0", parents=()), + ConfigLine(config_line="afnetconf.1.status=enabled", parents=()), + ConfigLine(config_line="afnetconf.status=enabled", parents=()), + ConfigLine(config_line="airview.tcp_port=18888", parents=()), + ConfigLine(config_line="airview.tcpport=18888", parents=()), + ConfigLine(config_line="bridge.1.devname=br0", parents=()), + ConfigLine(config_line="bridge.1.fd=1", parents=()), + ConfigLine(config_line="bridge.1.port.1.devname=eth0", parents=()), + ConfigLine(config_line="bridge.1.port.1.status=enabled", parents=()), + ConfigLine(config_line="bridge.1.port.2.devname=air0", parents=()), + ConfigLine(config_line="bridge.1.port.2.status=enabled", parents=()), + ConfigLine(config_line="bridge.1.status=enabled", parents=()), + ConfigLine(config_line="bridge.1.stp.status=disabled", parents=()), + ConfigLine(config_line="bridge.status=enabled", parents=()), + ConfigLine(config_line="dhcpc.1.devname=eth0", parents=()), + ConfigLine(config_line="dhcpc.1.status=disabled", parents=()), + ConfigLine(config_line="dhcpc.2.devname=air0", parents=()), + ConfigLine(config_line="dhcpc.2.status=disabled", parents=()), + ConfigLine(config_line="dhcpc.3.devname=br0", parents=()), + ConfigLine(config_line="dhcpc.3.status=disabled", parents=()), + ConfigLine(config_line="dhcpc.status=disabled", parents=()), + ConfigLine(config_line="discovery.cdp.status=disabled", parents=()), + ConfigLine(config_line="discovery.status=enabled", parents=()), + ConfigLine(config_line="dyndns.status=disabled", parents=()), + ConfigLine(config_line="ebtables.status=enabled", parents=()), + ConfigLine(config_line="ebtables.sys.arpnat.status=disabled", parents=()), + ConfigLine(config_line="ebtables.sys.eap.status=disabled", parents=()), + ConfigLine(config_line="ebtables.sys.status=enabled", parents=()), + ConfigLine(config_line="gui.language=en_US", parents=()), + ConfigLine(config_line="httpd.https.port=443", parents=()), + ConfigLine(config_line="httpd.https.status=disabled", parents=()), + ConfigLine(config_line="httpd.port=80", parents=()), + ConfigLine(config_line="httpd.session.timeout=300", parents=()), + ConfigLine(config_line="httpd.status=enabled", parents=()), + ConfigLine(config_line="netconf.1.allmulti=disabled", parents=()), + ConfigLine(config_line="netconf.1.autoip.status=disabled", parents=()), + ConfigLine(config_line="netconf.1.autoneg=enabled", parents=()), + ConfigLine(config_line="netconf.1.devname=eth0", parents=()), + ConfigLine(config_line="netconf.1.hwaddr.mac=", parents=()), + ConfigLine(config_line="netconf.1.hwaddr.status=disabled", parents=()), + ConfigLine(config_line="netconf.1.ip=0.0.0.0", parents=()), + ConfigLine(config_line="netconf.1.mtu=1500", parents=()), + ConfigLine(config_line="netconf.1.netmask=255.255.255.0", parents=()), + ConfigLine(config_line="netconf.1.promisc=enabled", parents=()), + ConfigLine(config_line="netconf.1.role=bridge_port", parents=()), + ConfigLine(config_line="netconf.1.status=enabled", parents=()), + ConfigLine(config_line="netconf.1.up=enabled", parents=()), + ConfigLine(config_line="netconf.2.allmulti=disabled", parents=()), + ConfigLine(config_line="netconf.2.autoip.status=disabled", parents=()), + ConfigLine(config_line="netconf.2.devname=air0", parents=()), + ConfigLine(config_line="netconf.2.hwaddr.mac=", parents=()), + ConfigLine(config_line="netconf.2.hwaddr.status=disabled", parents=()), + ConfigLine(config_line="netconf.2.ip=0.0.0.0", parents=()), + ConfigLine(config_line="netconf.2.mtu=1500", parents=()), + ConfigLine(config_line="netconf.2.netmask=255.255.255.0", parents=()), + ConfigLine(config_line="netconf.2.promisc=enabled", parents=()), + ConfigLine(config_line="netconf.2.role=bridge_port", parents=()), + ConfigLine(config_line="netconf.2.status=enabled", parents=()), + ConfigLine(config_line="netconf.2.up=enabled", parents=()), + ConfigLine(config_line="netconf.3.allmulti=disabled", parents=()), + ConfigLine(config_line="netconf.3.autoip.status=disabled", parents=()), + ConfigLine(config_line="netconf.3.devname=br0", parents=()), + ConfigLine(config_line="netconf.3.hwaddr.mac=", parents=()), + ConfigLine(config_line="netconf.3.hwaddr.status=disabled", parents=()), + ConfigLine(config_line="netconf.3.ip=10.10.10.10", parents=()), + ConfigLine(config_line="netconf.3.mtu=1500", parents=()), + ConfigLine(config_line="netconf.3.netmask=255.255.255.0", parents=()), + ConfigLine(config_line="netconf.3.role=mlan", parents=()), + ConfigLine(config_line="netconf.3.status=enabled", parents=()), + ConfigLine(config_line="netconf.3.up=enabled", parents=()), + ConfigLine(config_line="netconf.status=enabled", parents=()), + ConfigLine(config_line="netmode=airfiber", parents=()), + ConfigLine(config_line="ntpclient.status=disabled", parents=()), + ConfigLine(config_line="ppp.status=disabled", parents=()), + ConfigLine(config_line="pwdog.delay=300", parents=()), + ConfigLine(config_line="pwdog.host=", parents=()), + ConfigLine(config_line="pwdog.period=300", parents=()), + ConfigLine(config_line="pwdog.retry=3", parents=()), + ConfigLine(config_line="pwdog.status=disabled", parents=()), + ConfigLine(config_line="radio.1.antenna.gain=33", parents=()), + ConfigLine(config_line="radio.1.atpctarget=0", parents=()), + ConfigLine(config_line="radio.1.cable.loss=0", parents=()), + ConfigLine(config_line="radio.1.countrycode=840", parents=()), + ConfigLine(config_line="radio.1.dfsthreshold=0", parents=()), + ConfigLine(config_line="radio.1.dualfreqmode=enabled", parents=()), + ConfigLine(config_line="radio.1.duplex=half", parents=()), + ConfigLine(config_line="radio.1.dutycycle=50", parents=()), + ConfigLine(config_line="radio.1.framelength=-1", parents=()), + ConfigLine(config_line="radio.1.gps_sync=disabled", parents=()), + ConfigLine(config_line="radio.1.key=s:password1", parents=()), + ConfigLine(config_line="radio.1.linkname=UBNT", parents=()), + ConfigLine(config_line="radio.1.mode=slave", parents=()), + ConfigLine(config_line="radio.1.pwrbackoff=disabled", parents=()), + ConfigLine(config_line="radio.1.pwrout.10x=0", parents=()), + ConfigLine(config_line="radio.1.pwrout.4x=43", parents=()), + ConfigLine(config_line="radio.1.pwrout.6x=43", parents=()), + ConfigLine(config_line="radio.1.pwrout.8x=33", parents=()), + ConfigLine(config_line="radio.1.rate.auto=enabled", parents=()), + ConfigLine(config_line="radio.1.rate=6x", parents=()), + ConfigLine(config_line="radio.1.rfrate=6x", parents=()), + ConfigLine(config_line="radio.1.rx_freq10=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq2=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq3=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq4=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq5=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq6=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq7=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq8=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq9=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq=24.1GHz", parents=()), + ConfigLine(config_line="radio.1.rxchanbw=2048", parents=()), + ConfigLine(config_line="radio.1.rxgain=low", parents=()), + ConfigLine(config_line="radio.1.sharedant=disabled", parents=()), + ConfigLine(config_line="radio.1.sisomode=disabled", parents=()), + ConfigLine(config_line="radio.1.status=enabled", parents=()), + ConfigLine(config_line="radio.1.streammode=disabled", parents=()), + ConfigLine(config_line="radio.1.subsystemid=0xaf01", parents=()), + ConfigLine(config_line="radio.1.tx_freq2=null", parents=()), + ConfigLine(config_line="radio.1.tx_freq3=null", parents=()), + ConfigLine(config_line="radio.1.tx_freq=24.1GHz", parents=()), + ConfigLine(config_line="radio.1.txchanbw=2048", parents=()), + ConfigLine(config_line="radio.1.txpower=33", parents=()), + ConfigLine(config_line="radio.countrycode=840", parents=()), + ConfigLine(config_line="radio.status=enabled", parents=()), + ConfigLine(config_line="resolv.host.1.name=UBNT", parents=()), + ConfigLine(config_line="resolv.host.1.status=enabled", parents=()), + ConfigLine(config_line="resolv.nameserver.1.ip=1.1.1.1", parents=()), + ConfigLine(config_line="resolv.nameserver.1.status=enabled", parents=()), + ConfigLine(config_line="resolv.nameserver.2.ip=", parents=()), + ConfigLine(config_line="resolv.nameserver.2.status=disabled", parents=()), + ConfigLine(config_line="resolv.nameserver.status=enabled", parents=()), + ConfigLine(config_line="resolv.status=disabled", parents=()), + ConfigLine(config_line="route.1.comment=", parents=()), + ConfigLine(config_line="route.1.devname=br0", parents=()), + ConfigLine(config_line="route.1.gateway=10.10.10.1", parents=()), + ConfigLine(config_line="route.1.ip=0.0.0.0", parents=()), + ConfigLine(config_line="route.1.netmask=0", parents=()), + ConfigLine(config_line="route.1.status=enabled", parents=()), + ConfigLine(config_line="route.status=enabled", parents=()), + ConfigLine(config_line="snmp.community=admin", parents=()), + ConfigLine(config_line="snmp.contact=lab", parents=()), + ConfigLine(config_line="snmp.location=lab", parents=()), + ConfigLine(config_line="snmp.status=enabled", parents=()), + ConfigLine(config_line="sshd.auth.passwd=enabled", parents=()), + ConfigLine(config_line="sshd.port=22", parents=()), + ConfigLine(config_line="sshd.status=enabled", parents=()), + ConfigLine(config_line="syslog.remote.port=514", parents=()), + ConfigLine(config_line="syslog.remote.status=disabled", parents=()), + ConfigLine(config_line="syslog.status=disabled", parents=()), + ConfigLine(config_line="system.button.reset=enabled", parents=()), + ConfigLine(config_line="system.cfg.version=65540", parents=()), + ConfigLine(config_line="system.date.status=disabled", parents=()), + ConfigLine(config_line="system.date.timestamp=", parents=()), + ConfigLine(config_line="system.eirp.status=enabled", parents=()), + ConfigLine(config_line="system.latitude=", parents=()), + ConfigLine(config_line="system.longitude=", parents=()), + ConfigLine(config_line="system.timezone=GMT", parents=()), + ConfigLine(config_line="telnetd.port=23", parents=()), + ConfigLine(config_line="telnetd.status=enabled", parents=()), + ConfigLine(config_line="ucode.1.rxscramblekey=0", parents=()), + ConfigLine(config_line="ucode.1.txscramblekey=0", parents=()), + ConfigLine(config_line="udapi_bridge.callhome.status=disabled", parents=()), + ConfigLine(config_line="unms.uri=-", parents=()), + ConfigLine(config_line="update.check.status=enabled", parents=()), + ConfigLine(config_line="users.1.name=ubnt", parents=()), + ConfigLine(config_line="users.1.password=$1$YSWw8TsR$eBl/.jP1ibig8DUF4nn65.", parents=()), + ConfigLine(config_line="users.1.status=enabled", parents=()), + ConfigLine(config_line="users.2.status=disabled", parents=()), + ConfigLine(config_line="users.status=enabled", parents=()), + ConfigLine(config_line="vlan.status=disabled", parents=()), +] diff --git a/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_sent.txt b/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_sent.txt new file mode 100644 index 00000000..6178e9ce --- /dev/null +++ b/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_sent.txt @@ -0,0 +1,229 @@ +### PART ### +## +##AF.v4.1.0 +#board.sysid=0xaf01 +#board.cpurevision=0x4e840102 +#board.name=airFiber 24G +#board.shortname=AF24 +#board.subtype= +#board.netmodes=16; +#board.hwaddr=802AA8CF8DB9 +#board.reboot=130 +#board.upgrade=330 +#board.phycount=1 +#board.phy.1.maxmtu=1500 +#board.gps=1 +#board.airfiber=1 +#board.portnumber=1 +#board.fdd=1 +#feature.gps=1 +#feature.rssi.leds=0 +#feature.gps.leds=0 +#radio.1.name= +#radio.1.shortname= +#radio.1.bus=spi +#radio.1.rf_revision=0x0000 +#radio.1.subsystemid=0xaf01 +#radio.1.subvendorid=0x0777 +#radio.1.txpower.max=43 +#radio.1.txpower.offset=0 +#radio.1.antennas=1 +#radio.1.def_antenna=4 +#radio.1.antenna.1.id=4 +#radio.1.antenna.1.name=Combined +#radio.1.antenna.1.gain=0 +#radio.1.ccode=840 +#radio.1.ccode.fixed=0 +#radio.1.ccode.locked=1 +#radio.1.ptp_only=1 +#radio.1.ptp_sta=1 +#radio.1.chanbw=100 +#deac9d0213801db5ede667c016886ba4 +### PART ### +afnetconf.1.atpctarget=0 +afnetconf.1.autoneg=enabled +afnetconf.1.carrierdrop=off +afnetconf.1.carrierdropdebounce=0 +afnetconf.1.carrierdropto=5 +afnetconf.1.cdblockafterpulse=disabled +afnetconf.1.cdfirstsessiontimer=0 +afnetconf.1.cdfirstsessionwait=disabled +afnetconf.1.cdtracknosession=disabled +afnetconf.1.devname=af0 +afnetconf.1.flowcntl=disabled +afnetconf.1.mcastfilter=disabled +afnetconf.1.mincapholdoff=0 +afnetconf.1.mincapholdoffup=0 +afnetconf.1.minrxcapacity=0 +afnetconf.1.minrxcapacityup=0 +afnetconf.1.mintxcapacity=0 +afnetconf.1.mintxcapacityup=0 +afnetconf.1.status=enabled +afnetconf.status=enabled +airview.tcp_port=18888 +airview.tcpport=18888 +bridge.1.devname=br0 +bridge.1.fd=1 +bridge.1.port.1.devname=eth0 +bridge.1.port.1.status=enabled +bridge.1.port.2.devname=air0 +bridge.1.port.2.status=enabled +bridge.1.status=enabled +bridge.1.stp.status=disabled +bridge.status=enabled +dhcpc.1.devname=eth0 +dhcpc.1.status=disabled +dhcpc.2.devname=air0 +dhcpc.2.status=disabled +dhcpc.3.devname=br0 +dhcpc.3.status=disabled +dhcpc.status=disabled +discovery.cdp.status=disabled +discovery.status=enabled +dyndns.status=disabled +ebtables.status=enabled +ebtables.sys.arpnat.status=disabled +ebtables.sys.eap.status=disabled +ebtables.sys.status=enabled +gui.language=en_US +httpd.https.port=443 +httpd.https.status=disabled +httpd.port=80 +httpd.session.timeout=300 +httpd.status=enabled +netconf.1.allmulti=disabled +netconf.1.autoip.status=disabled +netconf.1.autoneg=enabled +netconf.1.devname=eth0 +netconf.1.hwaddr.mac= +netconf.1.hwaddr.status=disabled +netconf.1.ip=0.0.0.0 +netconf.1.mtu=1500 +netconf.1.netmask=255.255.255.0 +netconf.1.promisc=enabled +netconf.1.role=bridge_port +netconf.1.status=enabled +netconf.1.up=enabled +netconf.2.allmulti=disabled +netconf.2.autoip.status=disabled +netconf.2.devname=air0 +netconf.2.hwaddr.mac= +netconf.2.hwaddr.status=disabled +netconf.2.ip=0.0.0.0 +netconf.2.mtu=1500 +netconf.2.netmask=255.255.255.0 +netconf.2.promisc=enabled +netconf.2.role=bridge_port +netconf.2.status=enabled +netconf.2.up=enabled +netconf.3.allmulti=disabled +netconf.3.autoip.status=disabled +netconf.3.devname=br0 +netconf.3.hwaddr.mac= +netconf.3.hwaddr.status=disabled +netconf.3.ip=10.10.10.10 +netconf.3.mtu=1500 +netconf.3.netmask=255.255.255.0 +netconf.3.role=mlan +netconf.3.status=enabled +netconf.3.up=enabled +netconf.status=enabled +netmode=airfiber +ntpclient.status=disabled +ppp.status=disabled +pwdog.delay=300 +pwdog.host= +pwdog.period=300 +pwdog.retry=3 +pwdog.status=disabled +radio.1.antenna.gain=33 +radio.1.atpctarget=0 +radio.1.cable.loss=0 +radio.1.countrycode=840 +radio.1.dfsthreshold=0 +radio.1.dualfreqmode=enabled +radio.1.duplex=half +radio.1.dutycycle=50 +radio.1.framelength=-1 +radio.1.gps_sync=disabled +radio.1.key=s:password1 +radio.1.linkname=UBNT +radio.1.mode=slave +radio.1.pwrbackoff=disabled +radio.1.pwrout.10x=0 +radio.1.pwrout.4x=43 +radio.1.pwrout.6x=43 +radio.1.pwrout.8x=33 +radio.1.rate.auto=enabled +radio.1.rate=6x +radio.1.rfrate=6x +radio.1.rx_freq10=null +radio.1.rx_freq2=null +radio.1.rx_freq3=null +radio.1.rx_freq4=null +radio.1.rx_freq5=null +radio.1.rx_freq6=null +radio.1.rx_freq7=null +radio.1.rx_freq8=null +radio.1.rx_freq9=null +radio.1.rx_freq=24.1GHz +radio.1.rxchanbw=2048 +radio.1.rxgain=low +radio.1.sharedant=disabled +radio.1.sisomode=disabled +radio.1.status=enabled +radio.1.streammode=disabled +radio.1.subsystemid=0xaf01 +radio.1.tx_freq2=null +radio.1.tx_freq3=null +radio.1.tx_freq=24.1GHz +radio.1.txchanbw=2048 +radio.1.txpower=33 +radio.countrycode=840 +radio.status=enabled +resolv.host.1.name=UBNT +resolv.host.1.status=enabled +resolv.nameserver.1.ip=1.1.1.1 +resolv.nameserver.1.status=enabled +resolv.nameserver.2.ip= +resolv.nameserver.2.status=disabled +resolv.nameserver.status=enabled +resolv.status=disabled +route.1.comment= +route.1.devname=br0 +route.1.gateway=10.10.10.1 +route.1.ip=0.0.0.0 +route.1.netmask=0 +route.1.status=enabled +route.status=enabled +snmp.community=admin +snmp.contact=lab +snmp.location=lab +snmp.status=enabled +sshd.auth.passwd=enabled +sshd.port=22 +sshd.status=enabled +syslog.remote.port=514 +syslog.remote.status=disabled +syslog.status=disabled +system.button.reset=enabled +system.cfg.version=65540 +system.date.status=disabled +system.date.timestamp= +system.eirp.status=enabled +system.latitude= +system.longitude= +system.timezone=GMT +telnetd.port=23 +telnetd.status=enabled +ucode.1.rxscramblekey=0 +ucode.1.txscramblekey=0 +udapi_bridge.callhome.status=disabled +unms.uri=- +update.check.status=enabled +users.1.name=ubnt +users.1.password=$1$YSWw8TsR$eBl/.jP1ibig8DUF4nn65. +users.1.status=enabled +users.2.status=disabled +users.status=enabled +vlan.status=disabled \ No newline at end of file diff --git a/tests/unit/test_acl.py b/tests/unit/test_acl.py new file mode 100644 index 00000000..b7ce7f1f --- /dev/null +++ b/tests/unit/test_acl.py @@ -0,0 +1,375 @@ +"""Test for the BGP ASN functions.""" +# pylint: disable=use-dict-literal + +import pytest + +from netutils import acl + +verify_acl = [ + { + "sent": dict( + name="Check multiple sources pass. Check convesion of non-alpha tcp, e.g. with a dash", + src_ip=["192.168.1.10", "192.168.1.11", "192.168.1.15-192.168.1.20"], + dst_ip="172.16.0.10", + dst_port="tcp/www-http", + action="permit", + ), + "received": "permit", + }, + { + "sent": dict( + name="Check with number in port definition", + src_ip="192.168.0.10", + dst_ip="192.168.250.11", + dst_port="6/80", + action="permit", + ), + "received": "permit", + }, + { + "sent": dict( + name="Check with subnets", + src_ip="192.168.0.0/25", + dst_ip="172.16.0.0/24", + dst_port="6/80", + action="permit", + ), + "received": "permit", + }, + { + "sent": dict( + name="Test partial match on Source IP", + src_ip=["192.168.1.10", "192.168.2.10"], + dst_ip="172.16.0.11", + dst_port="tcp/80", + action="permit", + ), + "received": "deny", + }, + { + "sent": dict( + name="Test an entry that is not found", + src_ip="192.168.1.10", + dst_ip="192.168.240.1", + dst_port="tcp/80", + action="permit", + ), + "received": "deny", + }, + { + "sent": dict( + name="Test an action not permit or deny", + src_ip="10.1.1.1", + dst_ip="10.255.255.255", + dst_port="tcp/443", + action="permit", + ), + "received": "deny", + }, +] + +acls = [ + dict( + name="Allow to internal web", + src_ip=["192.168.0.0/24", "10.0.0.0/16"], + dst_ip=["172.16.0.0/16", "192.168.250.10-192.168.250.20"], + dst_port=["tcp/80", "udp/53"], + action="permit", + ), + dict( + name="Allow to internal dns", + src_ip=["192.168.1.0/24"], + dst_ip=["172.16.0.0/16"], + dst_port=["tcp/80", "udp/53"], + action="permit", + ), + dict( + name="Allow to internal https", + src_ip=["10.0.0.0/8"], + dst_ip=["172.16.0.0/16"], + dst_port=["tcp/443"], + action="deny", + ), + dict( + name="Drop (not deny) this specfic packet", + src_ip="10.1.1.1", + dst_ip="10.255.255.255", + dst_port="tcp/443", + action="drop", + ), + dict( + name="Allow External DNS", + src_ip=["0.0.0.0/0"], + dst_ip=["8.8.8.8/32", "8.8.4.4/32"], + dst_port=["udp/53"], + action="permit", + ), +] + +verify_matrix = [ + { + "sent": dict( + name="Check allow", + src_ip="10.1.100.5", + dst_ip="10.1.200.0", + dst_port="tcp/www-http", + action="permit", + ), + "received": [{"obj": ("10.1.100.5", "10.1.200.0", "6/80"), "action": "allow"}], + }, + { + "sent": dict( + name="Check Notify", + src_ip="10.1.100.5", + dst_ip="10.1.200.0", + dst_port="tcp/25", + action="permit", + ), + "received": [{"obj": ("10.1.100.5", "10.1.200.0", "6/25"), "action": "notify"}], + }, + { + "sent": dict( + name="Check not found and denied", + src_ip="10.1.100.5", + dst_ip="10.1.200.0", + dst_port="tcp/53", + action="permit", + ), + "received": [{"obj": ("10.1.100.5", "10.1.200.0", "6/53"), "action": "deny"}], + }, + { + "sent": dict( + name="Check not found and denied", + src_ip=["10.1.100.5", "10.1.100.6"], + dst_ip="10.1.200.0", + dst_port="tcp/80", + action="permit", + ), + "received": [ + {"obj": ("10.1.100.5", "10.1.200.0", "6/80"), "action": "allow"}, + {"obj": ("10.1.100.6", "10.1.200.0", "6/80"), "action": "allow"}, + ], + }, + { + "sent": dict( + name="Nothing found", + src_ip="1.1.1.1", + dst_ip="2.2.2.2", + dst_port="tcp/53", + action="permit", + ), + "received": [{"obj": ("1.1.1.1", "2.2.2.2", "6/53"), "action": "deny"}], + }, +] + +verify_schema = [ + { + "sent": dict( + name="Bad IP", + src_ip="10.1.100.A", + dst_ip="10.1.200.0", + dst_port="tcp/www-http", + action="permit", + ), + }, + { + "sent": dict( + name="Bad port", + src_ip="10.1.100.5", + dst_ip="10.1.200.0", + dst_port="tcp25", + action="permit", + ), + }, + { + "sent": dict( + name="Bad IP in list", + src_ip=["10.1.100.5", "10.1.100.A"], + dst_ip="10.1.200.0", + dst_port="tcp/25", + action="permit", + ), + }, +] + +verify_schema2 = [ + { + "sent": dict( + name="Check allow", + src_ip="10.1.100.1", + dst_ip="10.1.200.0", + dst_port="6/www-http", + action=100, + ), + }, +] + +IP_DEFINITIONS = { + "red": ["10.1.100.0/23", "10.1.102.0/24"], + "blue": ["10.1.200.0/23", "10.1.202.0/24"], + "orange": ["10.1.0.0/23", "10.1.2.0/24"], +} + +MATRIX = { + "red": {"blue": {"allow": ["6/80", "6/443"], "notify": ["6/25"]}, "orange": {"allow": ["6/80"]}}, + "blue": {"red": {"allow": ["6/80"]}}, +} + + +class TestMatrix(acl.ACLRule): + """ACLRule inherited class to test the matrix.""" + + matrix = MATRIX + matrix_enforced = True + matrix_definition = IP_DEFINITIONS + + +class TestSchema(acl.ACLRule): + """ACLRule inherited class to test the schema.""" + + input_data_verify = True + + +class TestSchema2(acl.ACLRule): + """ACLRule inherited class alternate to test the schema.""" + + result_data_verify = True + + +@pytest.mark.parametrize("data", verify_acl) +def test_verify_acl(data): + assert acl.ACLRules(acls).match(data["sent"]) == data["received"] + + +@pytest.mark.parametrize("data", verify_matrix) +def test_matrix(data): + assert TestMatrix(data["sent"]).enforce() == data["received"] + + +@pytest.mark.parametrize("data", verify_schema) +def test_schema(data): + pytest.importorskip("jsonschema") + try: + import jsonschema # pylint: disable=import-outside-toplevel + except ImportError: + pass + + with pytest.raises(jsonschema.exceptions.ValidationError): + TestSchema(data["sent"]) + + +def test_schema_not_enforced_when_option_not_set(): + try: + acl.ACLRule(dict(src_ip="10.1.1.1", dst_ip="10.2.2.2", dst_port="tcp/80", action=100)) + except Exception: # pylint: disable=broad-exception-caught + assert False, "No error should have been raised" + + +def test_schema_valid(): + try: + TestSchema(dict(src_ip="10.1.1.1", dst_ip="10.2.2.2", dst_port="tcp/80", action="permit")) + except Exception: # pylint: disable=broad-exception-caught + assert False, "No error should have been raised" + + +@pytest.mark.parametrize("data", verify_schema2) +def test_schema2(data): + pytest.importorskip("jsonschema") + try: + import jsonschema # pylint: disable=import-outside-toplevel + except ImportError: + pass + + with pytest.raises(jsonschema.exceptions.ValidationError): + TestSchema2(data["sent"]).validate() + + +def test_schema2_valid(): + try: + TestSchema2(dict(src_ip="10.1.1.1", dst_ip="10.2.2.2", dst_port="tcp/80", action="permit")).validate() + except Exception: # pylint: disable=broad-exception-caught + assert False, "No error should have been raised" + + +class TestAddrGroups(acl.ACLRule): + """ACLRule inherited class alternate to test expansions.""" + + address_groups = {"red": ["white", "blue"], "blue": ["cyan"], "yellow": ["orange"]} + + addresses = {"white": ["10.1.1.1", "10.2.2.2"], "cyan": ["10.3.3.3"], "orange": ["10.4.4.4"]} + + def __init__(self, data, *args, **kwargs): + self.flattened_addresses = self.flatten_addresses(self.address_groups, self.addresses) + super().__init__(data, *args, **kwargs) + + def flatten_addresses(self, address_groups, addresses): + """Go through and get the addresses given potential address groups.""" + + flattened_addresses = {} + + for group, subgroups in address_groups.items(): + if group in addresses: + flattened_addresses.setdefault(group, []).extend(addresses[group]) + for subgroup in subgroups: + if subgroup in addresses: + flattened_addresses.setdefault(group, []).extend(addresses[subgroup]) + if subgroup in address_groups: + subgroup_addresses = self.flatten_addresses({subgroup: address_groups[subgroup]}, addresses) + for sub_group, ips in subgroup_addresses.items(): + flattened_addresses.setdefault(sub_group, []).extend(ips) + if group != sub_group: + flattened_addresses.setdefault(group, []).extend(ips) + + return flattened_addresses + + def process_ip(self, ip): + """Test ability to expand IP for both source and destination.""" + + if not isinstance(ip, list): + ip = [ip] + output = [] + for ip_name in ip: + if not ip_name[0].isalpha(): + output.append(ip_name) + elif self.addresses.get(ip_name): + output.extend(self.addresses[ip_name]) + elif self.flattened_addresses.get(ip_name): + output.extend(self.flattened_addresses[ip_name]) + return sorted(list(set(output))) + + def process_src_ip(self, src_ip): + """Test ability to expand IP for both source.""" + + return self.process_ip(src_ip) + + def process_dst_ip(self, dst_ip): + """Test ability to expand IP for both destination.""" + + return self.process_ip(dst_ip) + + +add_group_check = [ + { + "sent": dict( + name="Check allow", + src_ip=["red", "blue", "10.4.4.4"], + dst_ip=["white"], + dst_port="6/www-http", + action="permit", + ), + "received": [ + {"action": "permit", "dst_ip": "10.2.2.2", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.1.1.1"}, + {"action": "permit", "dst_ip": "10.1.1.1", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.2.2.2"}, + {"action": "permit", "dst_ip": "10.1.1.1", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.3.3.3"}, + {"action": "permit", "dst_ip": "10.2.2.2", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.3.3.3"}, + {"action": "permit", "dst_ip": "10.1.1.1", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.4.4.4"}, + {"action": "permit", "dst_ip": "10.2.2.2", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.4.4.4"}, + ], + } +] + + +@pytest.mark.parametrize("data", add_group_check) +def test_custom_address_group(data): + obj = TestAddrGroups(data["sent"]) + assert obj.expanded_rules == data["received"] diff --git a/tests/unit/test_asn.py b/tests/unit/test_asn.py index 045110c2..2fda4741 100644 --- a/tests/unit/test_asn.py +++ b/tests/unit/test_asn.py @@ -12,7 +12,39 @@ {"sent": {"asplain": "0.65526"}, "received": 65526}, ] +int_to_asdot = [ + {"sent": {"asn_int": 6500}, "received": "6500"}, + {"sent": {"asn_int": 425985111}, "received": "6500.1111"}, + {"sent": {"asn_int": 425984001}, "received": "6500.1"}, + {"sent": {"asn_int": 65546}, "received": "1.10"}, + {"sent": {"asn_int": 65526}, "received": "65526"}, + {"sent": {"asn_int": "65526"}, "received": "65526"}, + {"sent": {"asn_int": "65535"}, "received": "65535"}, + {"sent": {"asn_int": "65536"}, "received": "1.0"}, + {"sent": {"asn_int": "425985111"}, "received": "6500.1111"}, + {"sent": {"asn_int": 4294967295}, "received": "65535.65535"}, +] + +int_to_asdot_exceptions = [ + {"asn_int": "one22"}, + {"asn_int": "not_an_int"}, + {"asn_int": "4294967296"}, + {"asn_int": "0"}, + {"asn_int": -1}, +] + @pytest.mark.parametrize("data", asn_to_int) def test_asn_to_int(data): assert asn.asn_to_int(**data["sent"]) == data["received"] + + +@pytest.mark.parametrize("data", int_to_asdot) +def test_int_to_asdot(data): + assert asn.int_to_asdot(**data["sent"]) == data["received"] + + +@pytest.mark.parametrize("data", int_to_asdot_exceptions) +def test_int_to_asdot_exceptions(data): + with pytest.raises(ValueError): + assert asn.int_to_asdot(**data) diff --git a/tests/unit/test_basics.py b/tests/unit/test_basics.py index e5339cf6..451f8869 100644 --- a/tests/unit/test_basics.py +++ b/tests/unit/test_basics.py @@ -8,9 +8,10 @@ class TestDocsPackaging(unittest.TestCase): """Test Version in doc requirements is the same pyproject.""" def test_version(self): - """Verify that pyproject.toml dev dependecies have the same versions as in the docs requirements.txt.""" + """Verify that pyproject.toml dev dependencies have the same versions as in the docs requirements.txt.""" parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) - poetry_details = toml.load(os.path.join(parent_path, "pyproject.toml"))["tool"]["poetry"]["dev-dependencies"] + poetry_path = os.path.join(parent_path, "pyproject.toml") + poetry_details = toml.load(poetry_path)["tool"]["poetry"]["group"]["dev"]["dependencies"] with open(f"{parent_path}/docs/requirements.txt", "r", encoding="utf-8") as file: requirements = [line for line in file.read().splitlines() if (len(line) > 0 and not line.startswith("#"))] for pkg in requirements: diff --git a/tests/unit/test_interface.py b/tests/unit/test_interface.py index 75e624c4..92292b67 100644 --- a/tests/unit/test_interface.py +++ b/tests/unit/test_interface.py @@ -10,6 +10,10 @@ "received": ("GigabitEthernet", "1/0/1"), }, {"sent": {"interface": "Gi1/0/1"}, "received": ("Gi", "1/0/1")}, + {"sent": {"interface": "Serial0/0/0:0"}, "received": ("Serial", "0/0/0:0")}, + {"sent": {"interface": "VLAN101"}, "received": ("VLAN", "101")}, + {"sent": {"interface": "Port-channel40"}, "received": ("Port-channel", "40")}, + {"sent": {"interface": "Gi1/0/3.100"}, "received": ("Gi", "1/0/3.100")}, ] CANONICAL_INTERFACE_NAME = [ @@ -158,6 +162,11 @@ "received": "SupE1/0/1", }, {"sent": {"interface": "Noninterface1/0/1"}, "received": "Noninterface1/0/1"}, + {"sent": {"interface": "Noninterface1/0/1"}, "received": "Noninterface1/0/1"}, + {"sent": {"interface": "Serial0/0/0:0"}, "received": "Se0/0/0:0"}, + {"sent": {"interface": "VLAN101"}, "received": "Vl101"}, + {"sent": {"interface": "Port-channel40"}, "received": "Po40"}, + {"sent": {"interface": "GigabitEthernet1/0/3.100"}, "received": "Gi1/0/3.100"}, ] INTERFACE_EXPANSION = [ diff --git a/tests/unit/test_ip.py b/tests/unit/test_ip.py index 442fe22d..f3898382 100644 --- a/tests/unit/test_ip.py +++ b/tests/unit/test_ip.py @@ -1,4 +1,5 @@ """Test for the IP functions.""" +import ipaddress import pytest from netutils import ip @@ -160,6 +161,145 @@ }, ] +IS_IP_RANGE = [ + { + "sent": { + "ip_range": "10.1.1.1", + }, + "received": False, + }, + { + "sent": { + "ip_range": "10.1.100.10-10.1.100.1", + }, + "received": False, + }, + { + "sent": { + "ip_range": "2001::10-2001::1", + }, + "received": False, + }, + { + "sent": { + "ip_range": "10.500.100.10-10.1.100.1", + }, + "received": False, + }, + { + "sent": { + "ip_range": "NOT AN IP", + }, + "received": False, + }, + { + "sent": { + "ip_range": "255.255.255.256", + }, + "received": False, + }, + { + "sent": { + "ip_range": "10.1.100.10-10.1.100.100", + }, + "received": True, + }, + { + "sent": { + "ip_range": "2001::10-2001::100", + }, + "received": True, + }, +] + +GET_RANGE_IPS = [ + { + "sent": { + "ip_range": "2001::10-2001::100", + }, + "received": (ipaddress.IPv6Address("2001::10"), ipaddress.IPv6Address("2001::100")), + }, + { + "sent": { + "ip_range": "10.1.100.10-10.1.100.100", + }, + "received": (ipaddress.IPv4Address("10.1.100.10"), ipaddress.IPv4Address("10.1.100.100")), + }, +] + +IS_IP_WITHIN = [ + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.1.10", + }, + "received": True, + }, + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.1.0-192.168.1.20", + }, + "received": True, + }, + { + "sent": { + "ip": "192.168.1.0/24", + "ip_compare": ["192.168.1.0-192.168.1.20", "192.168.2.0-192.168.2.20"], + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.10-192.168.1.15", + "ip_compare": ["192.168.1.0-192.168.1.20", "192.168.2.0-192.168.2.20"], + }, + "received": True, + }, + { + "sent": { + "ip": "10.0.0.0/8", + "ip_compare": ["192.168.1.0/24", "172.16.0.0/12"], + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.1.20", + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.2.0-192.168.2.20", + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.0/24", + "ip_compare": ["192.168.2.0-192.168.2.20", "192.168.3.0-192.168.3.20"], + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.50-192.168.1.60", + "ip_compare": ["192.168.2.0-192.168.2.20", "192.168.3.0-192.168.3.20"], + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.2.0/24", + }, + "received": False, + }, +] + GET_BROADCAST_ADDRESS = [ { "sent": {"ip_network": "10.1.1.0/24"}, @@ -357,6 +497,33 @@ def test_is_ip(data): assert ip.is_ip(**data["sent"]) == data["received"] +@pytest.mark.parametrize("data", IS_IP_RANGE) +def test_is_ip_range(data): + assert ip.is_ip_range(**data["sent"]) == data["received"] + + +@pytest.mark.parametrize("data", GET_RANGE_IPS) +def test_get_range_ips(data): + assert ip.get_range_ips(**data["sent"]) == data["received"] + + +def test_get_range_ips_fail(): + with pytest.raises(ValueError, match=r"Not a valid IP range format of .*"): + data = {"ip_range": "10.1.100.10-10.1.100.1"} + ip.get_range_ips(**data) + + +@pytest.mark.parametrize("data", IS_IP_WITHIN) +def test_is_ip_within(data): + assert ip.is_ip_within(**data["sent"]) == data["received"] + + +def test_is_ip_within_fail(): + with pytest.raises(ValueError): + data = {"ip": "10.1.100.100", "ip_compare": "10.1.100.10-2001::1"} + ip.is_ip_within(**data) + + @pytest.mark.parametrize("data", GET_ALL_HOST) def test_get_all_host(data): assert list(ip.get_all_host(**data["sent"])) == data["received"] diff --git a/tests/unit/test_lib_helpers_optionals.py b/tests/unit/test_lib_helpers_optionals.py index f383a16b..9d829966 100755 --- a/tests/unit/test_lib_helpers_optionals.py +++ b/tests/unit/test_lib_helpers_optionals.py @@ -9,21 +9,7 @@ def test_get_napalm_getters_napalm_installed_default(): pytest.importorskip("napalm") with mock.patch("netutils.lib_helpers.get_network_driver"): napalm_getters = get_napalm_getters() - assert napalm_getters == { - "asa": {}, - "cisco_wlc_ssh": {}, - "eos": {}, - "fortios": {}, - "huawei": {}, - "ios": {}, - "iosxr": {}, - "junos": {}, - "nxos": {}, - "nxos_ssh": {}, - "panos": {}, - "sros": {}, - "vyos": {}, - } + assert all(item in napalm_getters.keys() for item in ["asa", "eos", "fortios"]) def test_get_napalm_getters_napalm_installed_nxos_keys(): diff --git a/tests/unit/test_lib_mapper.py b/tests/unit/test_lib_mapper.py index e0fa2e5c..40a9a923 100644 --- a/tests/unit/test_lib_mapper.py +++ b/tests/unit/test_lib_mapper.py @@ -2,6 +2,9 @@ import pytest from netutils import lib_mapper +from netutils.config.compliance import parser_map + +LIBRARIES = ["ANSIBLE", "NETMIKO", "NTCTEMPLATES", "NAPALM", "PYATS", "PYNTC", "HIERCONFIG", "NETUTILSPARSER"] def test_lib_mapper(): @@ -13,6 +16,7 @@ def test_lib_mapper(): assert len(lib_mapper.PYATS_LIB_MAPPER.keys()) > 10 assert len(lib_mapper.PYNTC_LIB_MAPPER.keys()) > 5 assert len(lib_mapper.HIERCONFIG_LIB_MAPPER.keys()) > 4 + assert len(lib_mapper.NETUTILSPARSER_LIB_MAPPER.keys()) > 10 assert lib_mapper.ANSIBLE_LIB_MAPPER["cisco.ios.ios"] == "cisco_ios" assert lib_mapper.NAPALM_LIB_MAPPER["ios"] == "cisco_ios" assert lib_mapper.PYNTC_LIB_MAPPER["arista_eos_eapi"] == "arista_eos" @@ -21,8 +25,27 @@ def test_lib_mapper(): assert lib_mapper.HIERCONFIG_LIB_MAPPER_REVERSE[lib_mapper.HIERCONFIG_LIB_MAPPER["ios"]] == "ios" -@pytest.mark.parametrize("lib", ["ANSIBLE", "NETMIKO", "NTCTEMPLATES", "NAPALM", "PYATS", "PYNTC", "HIERCONFIG"]) +@pytest.mark.parametrize("lib", LIBRARIES) def test_lib_mapper_reverse(lib): - mapper = dict((v, k) for k, v in getattr(lib_mapper, f"{lib}_LIB_MAPPER").items()) + _mapper = getattr(lib_mapper, f"{lib}_LIB_MAPPER").copy() + if lib == "NAPALM": + _mapper.pop("nxos_ssh") + mapper = dict((v, k) for k, v in _mapper.items()) rev_mapper = getattr(lib_mapper, f"{lib}_LIB_MAPPER_REVERSE") assert mapper == rev_mapper + + +@pytest.mark.parametrize("lib", LIBRARIES) +def test_lib_mapper_alpha(lib): + original = list(getattr(lib_mapper, f"{lib}_LIB_MAPPER").keys()) + sorted_keys = sorted(original) + rev_original = list(getattr(lib_mapper, f"{lib}_LIB_MAPPER_REVERSE").keys()) + rev_sorted_keys = sorted(rev_original) + assert original == sorted_keys + assert rev_original == rev_sorted_keys + + +def test_netutils_parser(): + """Test that the paser_map in compliance have been added to NETUTILSPARSER lib mappers.""" + assert parser_map.keys() == lib_mapper.NETUTILSPARSER_LIB_MAPPER.keys() + assert list(parser_map.keys()) == sorted(list(lib_mapper.NETUTILSPARSER_LIB_MAPPER.keys())) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 50d17983..c0e7028a 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -52,7 +52,7 @@ def get_jinja2_function_names(): def test_jinja2_mapping_contains_all_functions(get_jinja2_function_names): # pylint: disable=redefined-outer-name mapping_function_names = [path.split(".")[-1] for path in list(_JINJA2_FUNCTION_MAPPINGS.values())] - sorted_get_jinja2_function_names = sorted(get_jinja2_function_names) + sorted_get_jinja2_function_names = sorted(list(set(get_jinja2_function_names))) sorted_mapping_function_names = sorted(mapping_function_names) assert sorted_get_jinja2_function_names == sorted_mapping_function_names