annet 1.0.4__tar.gz → 1.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of annet might be problematic. Click here for more details.
- {annet-1.0.4/annet.egg-info → annet-1.1.1}/PKG-INFO +2 -3
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/query.py +3 -1
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/patching.py +17 -8
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/ordering.py +11 -1
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/platform.py +2 -2
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/tabparser.py +91 -30
- annet-1.1.1/annet/executor.py +172 -0
- annet-1.1.1/annet/rulebook/juniper/__init__.py +156 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/arista.order +12 -12
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/huawei.order +2 -2
- annet-1.1.1/annet/rulebook/texts/juniper.order +4 -0
- {annet-1.0.4 → annet-1.1.1/annet.egg-info}/PKG-INFO +2 -3
- {annet-1.0.4 → annet-1.1.1}/annet.egg-info/SOURCES.txt +1 -1
- {annet-1.0.4 → annet-1.1.1}/annet.egg-info/requires.txt +1 -2
- {annet-1.0.4 → annet-1.1.1}/requirements.txt +0 -1
- {annet-1.0.4 → annet-1.1.1}/setup.py +1 -1
- annet-1.0.4/annet/executor.py +0 -551
- annet-1.0.4/annet/rulebook/juniper/__init__.py +0 -107
- annet-1.0.4/annet/rulebook/ribbon/__init__.py +0 -12
- {annet-1.0.4 → annet-1.1.1}/AUTHORS +0 -0
- {annet-1.0.4 → annet-1.1.1}/LICENSE +0 -0
- {annet-1.0.4 → annet-1.1.1}/MANIFEST.in +0 -0
- {annet-1.0.4 → annet-1.1.1}/README.md +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/fetchers/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/fetchers/stub/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/fetchers/stub/fetcher.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/file/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/file/provider.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/client.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/manufacturer.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/models.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/status_client.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/storage_opts.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/provider.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/v24/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/v24/storage.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/v37/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/v37/storage.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annet.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/command.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/diff.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/errors.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/filter_acl.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/jsontools.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/lib.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/db.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/views/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/views/dump.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/views/hardware.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/output.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/acl.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/syntax.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/rulebook/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/rulebook/common.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/annlib/types.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/api/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/argparse.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/bgp_models.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/cli.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/cli_args.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/configs/context.yml +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/configs/logging.yaml +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/connectors.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/deploy.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/deploy_ui.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/diff.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/filtering.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/gen.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/base.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/common/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/common/initial.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/entire.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/exceptions.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/jsonfragment.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/partial.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/perf.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/ref.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/generators/result.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/hardware.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/implicit.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/lib.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/mesh/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/mesh/basemodel.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/mesh/device_models.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/mesh/executor.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/mesh/match_args.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/mesh/models_converter.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/mesh/peer_models.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/mesh/port_processor.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/mesh/registry.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/output.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/parallel.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/patching.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/reference.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl/action.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl/condition.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl/match_builder.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl/policy.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl/result.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl/routemap.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl/statement_builder.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/aspath.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/community.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/cumulus_frr.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/entities.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/execute.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/policy.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/prefix_lists.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/rd.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/arista/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/arista/aaa.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/arista/iface.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/aruba/ap_env.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/aruba/misc.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/b4com/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/b4com/file.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/b4com/iface.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/cisco/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/cisco/iface.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/cisco/misc.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/common.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/deploying.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/iface.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/misc.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/nexus/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/nexus/iface.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/patching.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/routeros/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/routeros/file.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/arista.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/aruba.order +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/b4com.deploy +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/b4com.order +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/b4com.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/cisco.deploy +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/cisco.order +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/cisco.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/huawei.deploy +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/nexus.order +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/optixtrans.deploy +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/optixtrans.order +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/optixtrans.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/pc.deploy +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/pc.order +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/pc.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/routeros.order +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/storage.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/tabparser.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/text_term_format.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/tracing.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet/types.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet.egg-info/dependency_links.txt +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet.egg-info/entry_points.txt +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet.egg-info/top_level.txt +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/example/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/example/lldp.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/mesh_example/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/mesh_example/bgp.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/mesh_example/mesh_logic.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/__init__.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/generator.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/items.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/mesh.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/route_policy.py +0 -0
- {annet-1.0.4 → annet-1.1.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: annet
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: annet
|
|
5
5
|
Home-page: https://github.com/annetutil/annet
|
|
6
6
|
License: MIT
|
|
@@ -15,7 +15,6 @@ Requires-Dist: PyYAML>=6.0.1
|
|
|
15
15
|
Requires-Dist: Pygments>=2.14.0
|
|
16
16
|
Requires-Dist: Mako>=1.2.4
|
|
17
17
|
Requires-Dist: Jinja2>=3.1.2
|
|
18
|
-
Requires-Dist: psutil>=5.8.0
|
|
19
18
|
Requires-Dist: packaging>=23.2
|
|
20
19
|
Requires-Dist: contextlog>=1.1
|
|
21
20
|
Requires-Dist: valkit>=0.1.4
|
|
@@ -23,7 +22,7 @@ Requires-Dist: yarl>=1.8.2
|
|
|
23
22
|
Requires-Dist: adaptix==3.0.0b7
|
|
24
23
|
Requires-Dist: dataclass-rest==0.4
|
|
25
24
|
Provides-Extra: netbox
|
|
26
|
-
Requires-Dist: annetbox[sync]>=0.2.
|
|
25
|
+
Requires-Dist: annetbox[sync]>=0.2.1; extra == "netbox"
|
|
27
26
|
Dynamic: home-page
|
|
28
27
|
Dynamic: license
|
|
29
28
|
Dynamic: provides-extra
|
|
@@ -5,7 +5,7 @@ from typing import cast, List, Union, Iterable, Optional, TypedDict
|
|
|
5
5
|
from annet.storage import Query
|
|
6
6
|
|
|
7
7
|
FIELD_VALUE_SEPARATOR = ":"
|
|
8
|
-
ALLOWED_GLOB_GROUPS = ["site", "tag", "role", "device_type"]
|
|
8
|
+
ALLOWED_GLOB_GROUPS = ["site", "tag", "role", "device_type", "status", "tenant"]
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Filter(TypedDict, total=False):
|
|
@@ -14,6 +14,8 @@ class Filter(TypedDict, total=False):
|
|
|
14
14
|
role: list[str]
|
|
15
15
|
name: list[str]
|
|
16
16
|
device_type: list[str]
|
|
17
|
+
status: list[str]
|
|
18
|
+
tenant: list[str]
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
@dataclass
|
|
@@ -2,7 +2,7 @@ import copy
|
|
|
2
2
|
import operator
|
|
3
3
|
import textwrap
|
|
4
4
|
from collections import OrderedDict as odict
|
|
5
|
-
from typing import (
|
|
5
|
+
from typing import (
|
|
6
6
|
Any,
|
|
7
7
|
Dict,
|
|
8
8
|
Iterator,
|
|
@@ -177,7 +177,7 @@ class Orderer:
|
|
|
177
177
|
def rule_weight(self, row, rule, regexp_key):
|
|
178
178
|
return len(set(row).intersection(set(rule["attrs"][regexp_key].pattern))) / len(row)
|
|
179
179
|
|
|
180
|
-
def get_order(self, row, cmd_direct):
|
|
180
|
+
def get_order(self, row, cmd_direct, scope: str | None = None):
|
|
181
181
|
f_order = None
|
|
182
182
|
f_weight = 0
|
|
183
183
|
f_rule = ""
|
|
@@ -186,6 +186,15 @@ class Orderer:
|
|
|
186
186
|
block_exit = platform.VENDOR_EXIT[self.vendor]
|
|
187
187
|
|
|
188
188
|
for (order, (raw_rule, rule)) in enumerate(ordering.items()):
|
|
189
|
+
if (
|
|
190
|
+
(rule_scope := rule["attrs"]["scope"]) is not None
|
|
191
|
+
and scope not in rule_scope
|
|
192
|
+
):
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
if rule["attrs"]["global"]:
|
|
196
|
+
children.append((raw_rule, rule))
|
|
197
|
+
|
|
189
198
|
direct_matched = bool(rule["attrs"]["direct_regexp"].match(row))
|
|
190
199
|
if not rule["attrs"]["order_reverse"] and (direct_matched or rule["attrs"]["reverse_regexp"].match(row)):
|
|
191
200
|
# если не указано order_reverse - правило считается прямым
|
|
@@ -395,7 +404,7 @@ def make_patch(pre, rb, hw, add_comments, orderer=None, _root_pre=None, do_commi
|
|
|
395
404
|
for (key, diff) in content["items"].items():
|
|
396
405
|
# чтобы logic не мог поменять атрибуты
|
|
397
406
|
rule_pre = content.copy()
|
|
398
|
-
attrs = rule_pre["attrs"]
|
|
407
|
+
attrs = copy.deepcopy(rule_pre["attrs"])
|
|
399
408
|
|
|
400
409
|
iterable = attrs["logic"](
|
|
401
410
|
rule=attrs,
|
|
@@ -416,7 +425,7 @@ def make_patch(pre, rb, hw, add_comments, orderer=None, _root_pre=None, do_commi
|
|
|
416
425
|
patch_row = "%s %s" % (row, comments)
|
|
417
426
|
|
|
418
427
|
# pylint: disable=unused-variable
|
|
419
|
-
(order, order_direct, ordering, order_rule) = orderer.get_order(row, direct)
|
|
428
|
+
(order, order_direct, ordering, order_rule) = orderer.get_order(row, direct, scope="patch")
|
|
420
429
|
fmt_row = patch_row
|
|
421
430
|
# fmt_row += " # %s" % str(order_rule) # uncomment to debug ordering
|
|
422
431
|
|
|
@@ -544,8 +553,7 @@ def _select_match(matches, rules):
|
|
|
544
553
|
for (rule, is_cr_allowed) in map(operator.itemgetter(0), matches):
|
|
545
554
|
if is_cr_allowed:
|
|
546
555
|
local_children = merge_dicts(local_children, rule["children"]["local"])
|
|
547
|
-
|
|
548
|
-
|
|
556
|
+
# optional break on is_cr_allowed==False?
|
|
549
557
|
global_children = merge_dicts(global_children, rule["children"]["global"])
|
|
550
558
|
|
|
551
559
|
global_children = merge_dicts(global_children, rules["global"])
|
|
@@ -555,9 +563,10 @@ def _select_match(matches, rules):
|
|
|
555
563
|
"global": global_children,
|
|
556
564
|
}
|
|
557
565
|
|
|
558
|
-
match = {"attrs": f_rule["attrs"]}
|
|
566
|
+
match = {"attrs": copy.deepcopy(f_rule["attrs"])}
|
|
559
567
|
match.update(f_other)
|
|
560
|
-
|
|
568
|
+
|
|
569
|
+
return match, children_rules
|
|
561
570
|
|
|
562
571
|
|
|
563
572
|
def _rules_local_global(rules):
|
|
@@ -2,7 +2,7 @@ import functools
|
|
|
2
2
|
import re
|
|
3
3
|
from collections import OrderedDict as odict
|
|
4
4
|
|
|
5
|
-
from valkit.common import valid_bool
|
|
5
|
+
from valkit.common import valid_bool, valid_string_list
|
|
6
6
|
|
|
7
7
|
from . import platform, syntax
|
|
8
8
|
|
|
@@ -16,6 +16,14 @@ def compile_ordering_text(text, vendor):
|
|
|
16
16
|
"validator": valid_bool,
|
|
17
17
|
"default": False,
|
|
18
18
|
},
|
|
19
|
+
"global": {
|
|
20
|
+
"validator": valid_bool,
|
|
21
|
+
"default": False,
|
|
22
|
+
},
|
|
23
|
+
"scope": {
|
|
24
|
+
"validator": valid_string_list,
|
|
25
|
+
"default": None,
|
|
26
|
+
}
|
|
19
27
|
}),
|
|
20
28
|
reverse_prefix=platform.VENDOR_REVERSES[vendor],
|
|
21
29
|
)
|
|
@@ -44,6 +52,8 @@ def _compile_ordering(tree, reverse_prefix):
|
|
|
44
52
|
syntax.compile_row_regexp(re.sub(r"^%s\s+" % (reverse_prefix), "", attrs["row"]))
|
|
45
53
|
),
|
|
46
54
|
"order_reverse": attrs["params"]["order_reverse"],
|
|
55
|
+
"global": attrs["params"]["global"],
|
|
56
|
+
"scope": attrs["params"]["scope"],
|
|
47
57
|
"raw_rule": attrs["raw_rule"],
|
|
48
58
|
"context": attrs["context"],
|
|
49
59
|
},
|
|
@@ -25,7 +25,7 @@ VENDOR_DIFF = {
|
|
|
25
25
|
"routeros": "common.default_diff",
|
|
26
26
|
"aruba": "aruba.default_diff",
|
|
27
27
|
"pc": "common.default_diff",
|
|
28
|
-
"ribbon": "
|
|
28
|
+
"ribbon": "common.default_diff",
|
|
29
29
|
"b4com": "common.default_diff",
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -40,7 +40,7 @@ VENDOR_DIFF_ORDERED = {
|
|
|
40
40
|
"routeros": "common.ordered_diff",
|
|
41
41
|
"aruba": "common.ordered_diff",
|
|
42
42
|
"pc": "common.ordered_diff",
|
|
43
|
-
"ribbon": "
|
|
43
|
+
"ribbon": "common.ordered_diff",
|
|
44
44
|
"b4com": "common.ordered_diff",
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import itertools
|
|
3
|
+
import json
|
|
3
4
|
import re
|
|
5
|
+
import textwrap
|
|
4
6
|
from collections import OrderedDict as odict
|
|
5
7
|
from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple, Union, List
|
|
6
8
|
|
|
@@ -70,10 +72,10 @@ class CommonFormatter:
|
|
|
70
72
|
self._block_end = ""
|
|
71
73
|
self._statement_end = ""
|
|
72
74
|
|
|
73
|
-
def split(self, text):
|
|
75
|
+
def split(self, text: str):
|
|
74
76
|
return list(filter(None, text.split("\n")))
|
|
75
77
|
|
|
76
|
-
def join(self, config):
|
|
78
|
+
def join(self, config: "PatchTree"):
|
|
77
79
|
return "\n".join(
|
|
78
80
|
_filtered_block_marks(
|
|
79
81
|
self._indent_blocks(self._blocks(config, is_patch=False))
|
|
@@ -86,14 +88,14 @@ class CommonFormatter:
|
|
|
86
88
|
def diff(self, diff):
|
|
87
89
|
return list(self.diff_generator(diff))
|
|
88
90
|
|
|
89
|
-
def patch(self, patch):
|
|
91
|
+
def patch(self, patch: "PatchTree") -> str:
|
|
90
92
|
return "\n".join(
|
|
91
93
|
_filtered_block_marks(
|
|
92
94
|
self._indent_blocks(self._blocks(patch, is_patch=True))
|
|
93
95
|
)
|
|
94
96
|
)
|
|
95
97
|
|
|
96
|
-
def cmd_paths(self, patch):
|
|
98
|
+
def cmd_paths(self, patch: "PatchTree") -> odict:
|
|
97
99
|
ret = odict()
|
|
98
100
|
path = []
|
|
99
101
|
for row, context in self.blocks_and_context(patch, is_patch=True):
|
|
@@ -175,7 +177,7 @@ class CommonFormatter:
|
|
|
175
177
|
)
|
|
176
178
|
yield BlockEnd, None
|
|
177
179
|
|
|
178
|
-
def _blocks(self, tree, is_patch):
|
|
180
|
+
def _blocks(self, tree: "PatchTree", is_patch: bool):
|
|
179
181
|
for row, _context in self.blocks_and_context(tree, is_patch):
|
|
180
182
|
yield row
|
|
181
183
|
|
|
@@ -386,7 +388,32 @@ class AsrFormatter(BlockExitFormatter):
|
|
|
386
388
|
|
|
387
389
|
|
|
388
390
|
class JuniperFormatter(CommonFormatter):
|
|
389
|
-
patch_set_prefix = "set
|
|
391
|
+
patch_set_prefix = "set"
|
|
392
|
+
|
|
393
|
+
@dataclasses.dataclass
|
|
394
|
+
class Comment:
|
|
395
|
+
begin = "/*"
|
|
396
|
+
end = "*/"
|
|
397
|
+
|
|
398
|
+
row: str
|
|
399
|
+
comment: str
|
|
400
|
+
|
|
401
|
+
def __post_init__(self):
|
|
402
|
+
self.row = self.row.strip()
|
|
403
|
+
self.comment = self.comment.strip()
|
|
404
|
+
|
|
405
|
+
@classmethod
|
|
406
|
+
def loads(cls, value: str):
|
|
407
|
+
return cls(
|
|
408
|
+
**json.loads(
|
|
409
|
+
value.removeprefix(cls.begin)
|
|
410
|
+
.removesuffix(cls.end)
|
|
411
|
+
.strip()
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def dumps(self):
|
|
416
|
+
return json.dumps({"row": self.row, "comment": self.comment})
|
|
390
417
|
|
|
391
418
|
def __init__(self, indent=" "):
|
|
392
419
|
super().__init__(indent)
|
|
@@ -395,20 +422,32 @@ class JuniperFormatter(CommonFormatter):
|
|
|
395
422
|
self._statement_end = ";"
|
|
396
423
|
self._endofline_comment = "; ##"
|
|
397
424
|
|
|
398
|
-
|
|
399
|
-
sub_regexs = (
|
|
425
|
+
self._sub_regexs = (
|
|
400
426
|
(re.compile(self._block_begin + r"\s*" + self._block_end + r"$"), ""), # collapse empty blocks
|
|
401
427
|
(re.compile(self._block_begin + "(\t# .+)?$"), ""),
|
|
402
428
|
(re.compile(self._statement_end + r"$"), ""),
|
|
403
429
|
(re.compile(r"\s*" + self._block_end + "(\t# .+)?$"), ""),
|
|
404
430
|
(re.compile(self._endofline_comment + r".*$"), ""),
|
|
405
431
|
)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
432
|
+
|
|
433
|
+
def sub_regexs(self, value: str) -> str:
|
|
434
|
+
for (regex, repl_line) in self._sub_regexs:
|
|
435
|
+
value = regex.sub(repl_line, value)
|
|
436
|
+
return value
|
|
437
|
+
|
|
438
|
+
def split(self, text: str) -> list[str]:
|
|
439
|
+
comment_begin, comment_end = map(re.escape, (self.Comment.begin, self.Comment.end))
|
|
440
|
+
comment_regexp = re.compile(fr"(\s+{comment_begin})((?:(?!{comment_end}).)*)({comment_end})")
|
|
441
|
+
|
|
442
|
+
result = []
|
|
443
|
+
lines = text.split("\n")
|
|
444
|
+
for i, line in enumerate(lines):
|
|
445
|
+
line = self.sub_regexs(line)
|
|
446
|
+
if i + 1 < len(lines) and (m := comment_regexp.match(line)):
|
|
447
|
+
line = f"{m.group(1)} {self.Comment(self.sub_regexs(lines[i + 1]), m.group(2)).dumps()} {m.group(3)}"
|
|
448
|
+
result.append(line)
|
|
449
|
+
|
|
450
|
+
return list(filter(None, result))
|
|
412
451
|
|
|
413
452
|
def join(self, config):
|
|
414
453
|
return "\n".join(_filtered_block_marks(self._formatted_blocks(self._indented_blocks(config))))
|
|
@@ -419,6 +458,13 @@ class JuniperFormatter(CommonFormatter):
|
|
|
419
458
|
def patch_plain(self, patch):
|
|
420
459
|
return list(self.cmd_paths(patch).keys())
|
|
421
460
|
|
|
461
|
+
def _blocks(self, tree: "PatchTree", is_patch: bool):
|
|
462
|
+
for row in super()._blocks(tree, is_patch):
|
|
463
|
+
if isinstance(row, str) and row.startswith(self.Comment.begin):
|
|
464
|
+
yield f"{self.Comment.begin} {self.Comment.loads(row).comment} {self.Comment.end}"
|
|
465
|
+
else:
|
|
466
|
+
yield row
|
|
467
|
+
|
|
422
468
|
def _formatted_blocks(self, blocks):
|
|
423
469
|
level = 0
|
|
424
470
|
line = None
|
|
@@ -430,33 +476,48 @@ class JuniperFormatter(CommonFormatter):
|
|
|
430
476
|
elif new_line is BlockEnd:
|
|
431
477
|
level -= 1
|
|
432
478
|
if isinstance(line, str):
|
|
433
|
-
yield line + self._statement_end
|
|
479
|
+
yield line + ("" if line.endswith(self.Comment.end) else self._statement_end)
|
|
434
480
|
yield self._indent * level + self._block_end
|
|
435
481
|
elif isinstance(line, str):
|
|
436
|
-
yield line + self._statement_end
|
|
482
|
+
yield line + ("" if line.endswith(self.Comment.end) else self._statement_end)
|
|
437
483
|
line = new_line
|
|
438
484
|
if isinstance(line, str):
|
|
439
485
|
yield line + self._statement_end
|
|
440
486
|
|
|
441
|
-
def cmd_paths(self, patch, _prev=
|
|
487
|
+
def cmd_paths(self, patch, _prev=tuple()):
|
|
442
488
|
commands = odict()
|
|
443
489
|
for item in patch.itms:
|
|
444
490
|
key, childs, context = item.row, item.child, item.context
|
|
491
|
+
|
|
445
492
|
if childs:
|
|
446
|
-
for k, v in self.cmd_paths(childs, _prev
|
|
493
|
+
for k, v in self.cmd_paths(childs, (*_prev, key.strip())).items():
|
|
447
494
|
commands[k] = v
|
|
448
495
|
else:
|
|
449
|
-
if
|
|
450
|
-
|
|
496
|
+
if "comment" in context:
|
|
497
|
+
value = (
|
|
498
|
+
""
|
|
499
|
+
if key.startswith("delete")
|
|
500
|
+
else context["comment"]
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
cmd = "\n".join(
|
|
504
|
+
(
|
|
505
|
+
"edit " + " ".join(_prev),
|
|
506
|
+
" ".join(("annotate", context["row"].split(" ")[0], f'"{value}"')),
|
|
507
|
+
"exit"
|
|
508
|
+
)
|
|
509
|
+
)
|
|
510
|
+
elif key.startswith("delete"):
|
|
511
|
+
cmd = " ".join(("delete", *_prev, key.replace("delete", "", 1).strip()))
|
|
451
512
|
elif key.startswith("activate"):
|
|
452
|
-
cmd = "activate"
|
|
513
|
+
cmd = " ".join(("activate", *_prev, key.replace("activate", "", 1).strip()))
|
|
453
514
|
elif key.startswith("deactivate"):
|
|
454
|
-
cmd = "deactivate"
|
|
515
|
+
cmd = " ".join(("deactivate", *_prev, key.replace("deactivate", "", 1).strip()))
|
|
455
516
|
else:
|
|
456
|
-
cmd = (self.patch_set_prefix
|
|
517
|
+
cmd = " ".join((self.patch_set_prefix, *_prev, key.strip()))
|
|
518
|
+
|
|
457
519
|
# Expanding [ a b c ] junipers list of arguments
|
|
458
|
-
matches
|
|
459
|
-
if matches:
|
|
520
|
+
if matches := re.search(r"^(.*)\s+\[(.+)\]$", cmd):
|
|
460
521
|
for c in matches.group(2).split(" "):
|
|
461
522
|
if c.strip():
|
|
462
523
|
cmd = " ".join([matches.group(1), c])
|
|
@@ -490,7 +551,7 @@ class JuniperList:
|
|
|
490
551
|
|
|
491
552
|
|
|
492
553
|
class NokiaFormatter(JuniperFormatter):
|
|
493
|
-
patch_set_prefix = "/configure
|
|
554
|
+
patch_set_prefix = "/configure"
|
|
494
555
|
|
|
495
556
|
def __init__(self, *args, **kwargs):
|
|
496
557
|
super().__init__(*args, **kwargs)
|
|
@@ -517,18 +578,18 @@ class NokiaFormatter(JuniperFormatter):
|
|
|
517
578
|
finish = finish if finish is not None else len(ret)
|
|
518
579
|
return ret[start:finish]
|
|
519
580
|
|
|
520
|
-
def cmd_paths(self, patch, _prev=
|
|
581
|
+
def cmd_paths(self, patch, _prev=tuple()):
|
|
521
582
|
commands = odict()
|
|
522
583
|
for item in patch.itms:
|
|
523
584
|
key, childs, context = item.row, item.child, item.context
|
|
524
585
|
if childs:
|
|
525
|
-
for k, v in self.cmd_paths(childs, _prev
|
|
586
|
+
for k, v in self.cmd_paths(childs, (*_prev, key.strip())).items():
|
|
526
587
|
commands[k] = v
|
|
527
588
|
else:
|
|
528
589
|
if key.startswith("delete"):
|
|
529
|
-
cmd = "
|
|
590
|
+
cmd = " ".join((self.patch_set_prefix, "delete", *_prev, key.replace("delete", "", 1).strip()))
|
|
530
591
|
else:
|
|
531
|
-
cmd = self.patch_set_prefix
|
|
592
|
+
cmd = " ".join((self.patch_set_prefix, *_prev, key.strip()))
|
|
532
593
|
# Expanding [ a b c ] junipers list of arguments
|
|
533
594
|
matches = re.search(r"^(.*)\s+\[(.+)\]$", cmd)
|
|
534
595
|
if matches:
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import statistics
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from functools import partial
|
|
6
|
+
from operator import itemgetter
|
|
7
|
+
from typing import Any, List, Optional
|
|
8
|
+
|
|
9
|
+
import colorama
|
|
10
|
+
from annet.annlib.command import Command, CommandList, Question # noqa: F401
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CommandResult(ABC):
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def get_out(self) -> str:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ExecutorException(Exception):
|
|
20
|
+
def __init__(self, *args: List[Any], auxiliary: Optional[Any] = None, **kwargs: object):
|
|
21
|
+
self.auxiliary = auxiliary
|
|
22
|
+
super().__init__(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
def __repr__(self) -> str:
|
|
25
|
+
return "%s(args=%r,auxiliary=%s)" % (self.__class__.__name__, self.args, self.auxiliary)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ExecException(ExecutorException):
|
|
29
|
+
def __init__(self, msg: str, cmd: str, res: str, **kwargs):
|
|
30
|
+
super().__init__(**kwargs)
|
|
31
|
+
self.args = msg, cmd, res
|
|
32
|
+
self.kwargs = kwargs
|
|
33
|
+
self.msg = msg
|
|
34
|
+
self.cmd = cmd
|
|
35
|
+
self.res = res
|
|
36
|
+
|
|
37
|
+
def __str__(self) -> str:
|
|
38
|
+
return str(self.msg)
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
return "%s<%s, %s>" % (self.__class__.__name__, self.msg, self.cmd)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BadCommand(ExecException):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NonzeroRetcode(ExecException):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CommitException(ExecException):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _show_type_summary(caption, items, total, stat_items=None):
|
|
57
|
+
if items:
|
|
58
|
+
if not stat_items:
|
|
59
|
+
stat = ""
|
|
60
|
+
else:
|
|
61
|
+
avg = statistics.mean(stat_items)
|
|
62
|
+
stat = " %(min).1f/%(max).1f/%(avg).1f/%(stdev)s (min/max/avg/stdev)" % dict(
|
|
63
|
+
min=min(stat_items),
|
|
64
|
+
max=max(stat_items),
|
|
65
|
+
avg=avg,
|
|
66
|
+
stdev="-" if len(stat_items) < 2 else "%.1f" % statistics.stdev(stat_items, xbar=avg)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
print("%-8s %d of %d%s" % (caption, len(items), total, stat))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def show_bulk_report(hostnames, res, durations, log_dir):
|
|
73
|
+
total = len(hostnames)
|
|
74
|
+
if not total:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
colorama.init()
|
|
78
|
+
|
|
79
|
+
print("\n====== bulk deploy report ======")
|
|
80
|
+
|
|
81
|
+
done = [host for (host, hres) in res.items() if not isinstance(hres, Exception)]
|
|
82
|
+
cancelled = [host for (host, hres) in res.items() if isinstance(hres, asyncio.CancelledError)]
|
|
83
|
+
failed = [host for (host, hres) in res.items() if isinstance(hres, Exception) and host not in cancelled]
|
|
84
|
+
lost = [host for host in hostnames if host not in res]
|
|
85
|
+
limit = 30
|
|
86
|
+
|
|
87
|
+
_show_type_summary("Done :", done, total, [durations[h] for h in done])
|
|
88
|
+
_print_limit(done, partial(_print_hostname, style=colorama.Fore.GREEN), limit, total)
|
|
89
|
+
|
|
90
|
+
_show_type_summary("Failed :", failed, total, [durations[h] for h in failed])
|
|
91
|
+
|
|
92
|
+
_print_limit(failed, partial(_print_failed, res=res), limit, total)
|
|
93
|
+
|
|
94
|
+
_show_type_summary("Cancelled :", cancelled, total, [durations[h] for h in cancelled if durations[h] is not None])
|
|
95
|
+
_print_limit(cancelled, partial(_print_hostname, style=colorama.Fore.RED), limit, total)
|
|
96
|
+
|
|
97
|
+
_show_type_summary("Lost :", lost, total)
|
|
98
|
+
_print_limit(lost, _print_hostname, limit, total)
|
|
99
|
+
|
|
100
|
+
err_limit = 5
|
|
101
|
+
if failed:
|
|
102
|
+
errs = {}
|
|
103
|
+
for hostname in failed:
|
|
104
|
+
fmt_err = _format_exc(res[hostname])
|
|
105
|
+
if fmt_err in errs:
|
|
106
|
+
errs[fmt_err] += 1
|
|
107
|
+
else:
|
|
108
|
+
errs[fmt_err] = 1
|
|
109
|
+
print("Top errors :")
|
|
110
|
+
for fmt_err, n in sorted(errs.items(), key=itemgetter(1), reverse=True)[:err_limit]:
|
|
111
|
+
print(" %-4d %s" % (n, fmt_err))
|
|
112
|
+
print("\n", end="")
|
|
113
|
+
|
|
114
|
+
if log_dir:
|
|
115
|
+
print("See deploy logs in %s/\n" % os.path.relpath(log_dir))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _format_exc(exc):
|
|
119
|
+
if isinstance(exc, ExecException):
|
|
120
|
+
cmd = str(exc.cmd)
|
|
121
|
+
if len(cmd) > 50:
|
|
122
|
+
cmd = cmd[:50] + "~.."
|
|
123
|
+
return "'%s', cmd '%s'" % (exc.msg, cmd)
|
|
124
|
+
elif isinstance(exc, ExecutorException):
|
|
125
|
+
return "%s%r" % (exc.__class__.__name__, exc.args) # исключить многословный auxiliary
|
|
126
|
+
else:
|
|
127
|
+
return repr(exc)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _print_hostname(host, style=None):
|
|
131
|
+
if style:
|
|
132
|
+
host = style + host + colorama.Style.RESET_ALL
|
|
133
|
+
print(" %s" % host)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _print_limit(items, printer, limit, total, end="\n"):
|
|
137
|
+
if not items:
|
|
138
|
+
return
|
|
139
|
+
if len(items) > limit and len(items) > total * 0.7:
|
|
140
|
+
print(" ... %d hosts" % len(items))
|
|
141
|
+
for host in items[:limit]:
|
|
142
|
+
printer(host)
|
|
143
|
+
if len(items) > limit:
|
|
144
|
+
print(" ... %d more hosts" % (len(items) - limit))
|
|
145
|
+
|
|
146
|
+
print(end, end="")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _print_failed(host, res):
|
|
150
|
+
exc = res[host]
|
|
151
|
+
color = colorama.Fore.YELLOW if isinstance(exc, Warning) else colorama.Fore.RED
|
|
152
|
+
print(" %s - %s" % (color + host + colorama.Style.RESET_ALL, _format_exc(exc)))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class DeferredFileWrite:
|
|
156
|
+
def __init__(self, file, mode="r"):
|
|
157
|
+
self._file = file
|
|
158
|
+
wrapper = {"w": "a", "wb": "ab"}
|
|
159
|
+
if mode in wrapper:
|
|
160
|
+
self._mode = wrapper[mode]
|
|
161
|
+
else:
|
|
162
|
+
raise Exception()
|
|
163
|
+
|
|
164
|
+
def write(self, data):
|
|
165
|
+
with open(self._file, self._mode) as fh:
|
|
166
|
+
fh.write(data)
|
|
167
|
+
|
|
168
|
+
def close(self):
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
def flush(self):
|
|
172
|
+
pass
|