annet 0.16.34__tar.gz → 1.0.0__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-0.16.34/annet.egg-info → annet-1.0.0}/PKG-INFO +1 -1
- annet-1.0.0/annet/adapters/fetchers/stub/fetcher.py +25 -0
- annet-1.0.0/annet/adapters/netbox/common/query.py +61 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/v37/storage.py +79 -42
- {annet-0.16.34 → annet-1.0.0}/annet/api/__init__.py +32 -8
- annet-1.0.0/annet/deploy.py +223 -0
- annet-1.0.0/annet/deploy_ui.py +774 -0
- {annet-0.16.34 → annet-1.0.0}/annet/gen.py +5 -5
- {annet-0.16.34 → annet-1.0.0}/annet/lib.py +19 -3
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/huawei.deploy +1 -1
- {annet-0.16.34 → annet-1.0.0/annet.egg-info}/PKG-INFO +1 -1
- {annet-0.16.34 → annet-1.0.0}/annet.egg-info/SOURCES.txt +1 -0
- annet-0.16.34/annet/adapters/fetchers/stub/fetcher.py +0 -19
- annet-0.16.34/annet/adapters/netbox/common/query.py +0 -26
- annet-0.16.34/annet/deploy.py +0 -529
- {annet-0.16.34 → annet-1.0.0}/AUTHORS +0 -0
- {annet-0.16.34 → annet-1.0.0}/LICENSE +0 -0
- {annet-0.16.34 → annet-1.0.0}/MANIFEST.in +0 -0
- {annet-0.16.34 → annet-1.0.0}/README.md +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/fetchers/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/fetchers/stub/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/file/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/file/provider.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/common/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/common/client.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/common/manufacturer.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/common/models.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/common/status_client.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/common/storage_opts.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/provider.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/v24/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/v24/storage.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/adapters/netbox/v37/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annet.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/command.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/diff.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/errors.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/filter_acl.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/jsontools.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/lib.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/netdev/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/netdev/db.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/netdev/views/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/netdev/views/dump.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/netdev/views/hardware.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/output.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/patching.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/rbparser/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/rbparser/acl.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/rbparser/ordering.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/rbparser/platform.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/rbparser/syntax.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/rulebook/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/rulebook/common.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/tabparser.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/annlib/types.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/argparse.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/bgp_models.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/cli.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/cli_args.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/configs/context.yml +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/configs/logging.yaml +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/connectors.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/diff.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/executor.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/filtering.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/base.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/common/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/common/initial.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/entire.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/exceptions.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/jsonfragment.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/partial.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/perf.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/ref.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/generators/result.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/hardware.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/implicit.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/mesh/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/mesh/basemodel.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/mesh/device_models.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/mesh/executor.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/mesh/match_args.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/mesh/models_converter.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/mesh/peer_models.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/mesh/port_processor.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/mesh/registry.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/output.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/parallel.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/patching.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/reference.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl/action.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl/condition.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl/match_builder.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl/policy.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl/result.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl/routemap.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl/statement_builder.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl_generators/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl_generators/aspath.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl_generators/community.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl_generators/cumulus_frr.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl_generators/entities.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl_generators/execute.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl_generators/policy.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl_generators/prefix_lists.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rpl_generators/rd.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/arista/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/arista/iface.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/aruba/ap_env.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/aruba/misc.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/b4com/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/b4com/file.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/b4com/iface.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/cisco/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/cisco/iface.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/cisco/misc.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/common.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/deploying.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/huawei/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/huawei/iface.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/huawei/misc.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/juniper/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/nexus/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/nexus/iface.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/patching.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/ribbon/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/routeros/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/routeros/file.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/arista.order +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/arista.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/aruba.order +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/b4com.deploy +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/b4com.order +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/b4com.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/cisco.deploy +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/cisco.order +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/cisco.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/huawei.order +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/nexus.order +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/optixtrans.deploy +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/optixtrans.order +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/optixtrans.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/pc.deploy +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/pc.order +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/pc.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/routeros.order +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/storage.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/tabparser.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/text_term_format.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/tracing.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet/types.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet.egg-info/dependency_links.txt +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet.egg-info/entry_points.txt +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet.egg-info/requires.txt +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet.egg-info/top_level.txt +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/example/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/example/lldp.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/mesh_example/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/mesh_example/bgp.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/mesh_example/mesh_logic.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/rpl_example/__init__.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/rpl_example/generator.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/rpl_example/items.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/rpl_example/mesh.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/annet_generators/rpl_example/route_policy.py +0 -0
- {annet-0.16.34 → annet-1.0.0}/requirements.txt +0 -0
- {annet-0.16.34 → annet-1.0.0}/setup.cfg +0 -0
- {annet-0.16.34 → annet-1.0.0}/setup.py +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from annet.deploy import Fetcher
|
|
2
|
+
from annet.connectors import AdapterWithConfig
|
|
3
|
+
from typing import Dict, List, Any
|
|
4
|
+
from annet.storage import Device
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StubFetcher(Fetcher, AdapterWithConfig):
|
|
8
|
+
@classmethod
|
|
9
|
+
def with_config(cls, **kwargs: Dict[str, Any]) -> Fetcher:
|
|
10
|
+
return cls(**kwargs)
|
|
11
|
+
|
|
12
|
+
async def fetch_packages(self,
|
|
13
|
+
devices: list[Device],
|
|
14
|
+
processes: int = 1,
|
|
15
|
+
max_slots: int = 0,
|
|
16
|
+
) -> tuple[dict[Device, str], dict[Device, Any]]:
|
|
17
|
+
raise NotImplementedError()
|
|
18
|
+
|
|
19
|
+
async def fetch(self,
|
|
20
|
+
devices: list[Device],
|
|
21
|
+
files_to_download: dict[str, list[str]] | None = None,
|
|
22
|
+
processes: int = 1,
|
|
23
|
+
max_slots: int = 0,
|
|
24
|
+
):
|
|
25
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import cast, List, Union, Iterable, Optional, TypedDict
|
|
4
|
+
|
|
5
|
+
from annet.storage import Query
|
|
6
|
+
|
|
7
|
+
FIELD_VALUE_SEPARATOR = ":"
|
|
8
|
+
ALLOWED_GLOB_GROUPS = ["site", "tag", "role"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Filter(TypedDict, total=False):
|
|
12
|
+
site: list[str]
|
|
13
|
+
tag: list[str]
|
|
14
|
+
role: list[str]
|
|
15
|
+
name: list[str]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class NetboxQuery(Query):
|
|
20
|
+
query: List[str]
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def new(
|
|
24
|
+
cls, query: Union[str, Iterable[str]],
|
|
25
|
+
hosts_range: Optional[slice] = None,
|
|
26
|
+
) -> "NetboxQuery":
|
|
27
|
+
if hosts_range is not None:
|
|
28
|
+
raise ValueError("host_range is not supported")
|
|
29
|
+
return cls(query=list(query))
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def globs(self):
|
|
33
|
+
# We process every query host as a glob
|
|
34
|
+
return self.query
|
|
35
|
+
|
|
36
|
+
def parse_query(self) -> Filter:
|
|
37
|
+
query_groups = defaultdict(list)
|
|
38
|
+
for q in self.globs:
|
|
39
|
+
if FIELD_VALUE_SEPARATOR in q:
|
|
40
|
+
glob_type, param = q.split(FIELD_VALUE_SEPARATOR, 2)
|
|
41
|
+
if glob_type not in ALLOWED_GLOB_GROUPS:
|
|
42
|
+
raise Exception(f"unknown query type: '{glob_type}'")
|
|
43
|
+
if not param:
|
|
44
|
+
raise Exception(f"empty param for '{glob_type}'")
|
|
45
|
+
query_groups[glob_type].append(param)
|
|
46
|
+
else:
|
|
47
|
+
query_groups["name"].append(q)
|
|
48
|
+
|
|
49
|
+
query_groups.default_factory = None
|
|
50
|
+
return cast(Filter, query_groups)
|
|
51
|
+
|
|
52
|
+
def is_empty(self) -> bool:
|
|
53
|
+
return len(self.query) == 0
|
|
54
|
+
|
|
55
|
+
def is_host_query(self) -> bool:
|
|
56
|
+
if not self.globs:
|
|
57
|
+
return False
|
|
58
|
+
for q in self.globs:
|
|
59
|
+
if FIELD_VALUE_SEPARATOR in q:
|
|
60
|
+
return False
|
|
61
|
+
return True
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from logging import getLogger
|
|
2
|
-
from typing import Any, Optional, List, Union, Dict
|
|
3
|
-
from ipaddress import ip_interface
|
|
4
|
-
from collections import defaultdict
|
|
5
1
|
import ssl
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from ipaddress import ip_interface
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
from typing import Any, Optional, List, Union, Dict, cast
|
|
6
6
|
|
|
7
7
|
from adaptix import P
|
|
8
8
|
from adaptix.conversion import impl_converter, link, link_constant
|
|
@@ -13,7 +13,7 @@ from annet.adapters.netbox.common import models
|
|
|
13
13
|
from annet.adapters.netbox.common.manufacturer import (
|
|
14
14
|
get_hw, get_breed,
|
|
15
15
|
)
|
|
16
|
-
from annet.adapters.netbox.common.query import NetboxQuery
|
|
16
|
+
from annet.adapters.netbox.common.query import NetboxQuery, FIELD_VALUE_SEPARATOR
|
|
17
17
|
from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
|
|
18
18
|
from annet.annlib.netdev.views.hardware import HardwareView
|
|
19
19
|
from annet.storage import Storage, Device, Interface
|
|
@@ -101,6 +101,9 @@ class NetboxStorageV37(Storage):
|
|
|
101
101
|
self.exact_host_filter = opts.exact_host_filter
|
|
102
102
|
self.netbox = NetboxV37(url=url, token=token, ssl_context=ctx)
|
|
103
103
|
self._all_fqdns: Optional[list[str]] = None
|
|
104
|
+
self._id_devices: dict[int, models.NetboxDevice] = {}
|
|
105
|
+
self._name_devices: dict[str, models.NetboxDevice] = {}
|
|
106
|
+
self._short_name_devices: dict[str, models.NetboxDevice] = {}
|
|
104
107
|
|
|
105
108
|
def __enter__(self):
|
|
106
109
|
return self
|
|
@@ -136,6 +139,37 @@ class NetboxStorageV37(Storage):
|
|
|
136
139
|
) -> List[models.NetboxDevice]:
|
|
137
140
|
if isinstance(query, list):
|
|
138
141
|
query = NetboxQuery.new(query)
|
|
142
|
+
|
|
143
|
+
devices = []
|
|
144
|
+
if query.is_host_query():
|
|
145
|
+
globs = []
|
|
146
|
+
for glob in query.globs:
|
|
147
|
+
if glob in self._name_devices:
|
|
148
|
+
devices.append(self._name_devices[glob])
|
|
149
|
+
if glob in self._short_name_devices:
|
|
150
|
+
devices.append(self._short_name_devices[glob])
|
|
151
|
+
else:
|
|
152
|
+
globs.append(glob)
|
|
153
|
+
if not globs:
|
|
154
|
+
return devices
|
|
155
|
+
query = NetboxQuery.new(globs)
|
|
156
|
+
|
|
157
|
+
return devices + self._make_devices(
|
|
158
|
+
query=query,
|
|
159
|
+
preload_neighbors=preload_neighbors,
|
|
160
|
+
use_mesh=use_mesh,
|
|
161
|
+
preload_extra_fields=preload_extra_fields,
|
|
162
|
+
**kwargs
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _make_devices(
|
|
166
|
+
self,
|
|
167
|
+
query: NetboxQuery,
|
|
168
|
+
preload_neighbors=False,
|
|
169
|
+
use_mesh=None,
|
|
170
|
+
preload_extra_fields=False,
|
|
171
|
+
**kwargs,
|
|
172
|
+
) -> List[models.NetboxDevice]:
|
|
139
173
|
device_ids = {
|
|
140
174
|
device.id: extend_device(
|
|
141
175
|
device=device,
|
|
@@ -148,6 +182,9 @@ class NetboxStorageV37(Storage):
|
|
|
148
182
|
if not device_ids:
|
|
149
183
|
return []
|
|
150
184
|
|
|
185
|
+
for device in device_ids.values():
|
|
186
|
+
self._record_device(device)
|
|
187
|
+
|
|
151
188
|
interfaces = self._load_interfaces(list(device_ids))
|
|
152
189
|
neighbours = {x.id: x for x in self._load_neighbours(interfaces)}
|
|
153
190
|
neighbours_seen: dict[str, set] = defaultdict(set)
|
|
@@ -162,32 +199,22 @@ class NetboxStorageV37(Storage):
|
|
|
162
199
|
|
|
163
200
|
return list(device_ids.values())
|
|
164
201
|
|
|
202
|
+
def _record_device(self, device: models.NetboxDevice):
|
|
203
|
+
self._id_devices[device.id] = device
|
|
204
|
+
self._short_name_devices[device.name] = device
|
|
205
|
+
if not self.exact_host_filter:
|
|
206
|
+
short_name = device.name.split(".")[0]
|
|
207
|
+
self._short_name_devices[short_name] = device
|
|
208
|
+
|
|
165
209
|
def _load_devices(self, query: NetboxQuery) -> List[api_models.Device]:
|
|
166
210
|
if not query.globs:
|
|
167
211
|
return []
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
for grp, params in query_groups.items():
|
|
175
|
-
if not params:
|
|
176
|
-
continue
|
|
177
|
-
try:
|
|
178
|
-
new_devices = self.netbox.dcim_all_devices(**{grp: params}).results
|
|
179
|
-
except Exception as e:
|
|
180
|
-
# tag and site lookup returns 400 in case of unknown tag or site
|
|
181
|
-
if "is not one of the available choices" in str(e):
|
|
182
|
-
continue
|
|
183
|
-
raise
|
|
184
|
-
if grp == "name__ic":
|
|
185
|
-
new_devices = [device for device in new_devices if _match_query(query, device)]
|
|
186
|
-
for device in new_devices:
|
|
187
|
-
if device.id not in device_ids:
|
|
188
|
-
device_ids.add(device.id)
|
|
189
|
-
devices.extend(new_devices)
|
|
190
|
-
return devices
|
|
212
|
+
query_groups = parse_glob(self.exact_host_filter, query)
|
|
213
|
+
return [
|
|
214
|
+
device
|
|
215
|
+
for device in self.netbox.dcim_all_devices(**query_groups).results
|
|
216
|
+
if _match_query(self.exact_host_filter, query, device)
|
|
217
|
+
]
|
|
191
218
|
|
|
192
219
|
def _extend_interfaces(self, interfaces: List[models.Interface]) -> List[models.Interface]:
|
|
193
220
|
extended_ifaces = {
|
|
@@ -238,6 +265,9 @@ class NetboxStorageV37(Storage):
|
|
|
238
265
|
self, obj_id, preload_neighbors=False, use_mesh=None,
|
|
239
266
|
**kwargs,
|
|
240
267
|
) -> models.NetboxDevice:
|
|
268
|
+
if obj_id in self._id_devices:
|
|
269
|
+
return self._id_devices[obj_id]
|
|
270
|
+
|
|
241
271
|
device = self.netbox.dcim_device(obj_id)
|
|
242
272
|
interfaces = self._load_interfaces([device.id])
|
|
243
273
|
neighbours = self._load_neighbours(interfaces)
|
|
@@ -248,6 +278,7 @@ class NetboxStorageV37(Storage):
|
|
|
248
278
|
interfaces=interfaces,
|
|
249
279
|
neighbours=neighbours,
|
|
250
280
|
)
|
|
281
|
+
self._record_device(res)
|
|
251
282
|
return res
|
|
252
283
|
|
|
253
284
|
def flush_perf(self):
|
|
@@ -272,9 +303,18 @@ class NetboxStorageV37(Storage):
|
|
|
272
303
|
return res
|
|
273
304
|
|
|
274
305
|
|
|
275
|
-
def _match_query(query: NetboxQuery, device_data: api_models.Device) -> bool:
|
|
276
|
-
|
|
277
|
-
|
|
306
|
+
def _match_query(exact_host_filter: bool, query: NetboxQuery, device_data: api_models.Device) -> bool:
|
|
307
|
+
"""
|
|
308
|
+
Additional filtering after netbox due to limited backend logic.
|
|
309
|
+
"""
|
|
310
|
+
if exact_host_filter:
|
|
311
|
+
return True # nothing to check, all filtering is done by netbox
|
|
312
|
+
hostnames = [subquery.strip() for subquery in query.globs if FIELD_VALUE_SEPARATOR not in subquery]
|
|
313
|
+
if not hostnames:
|
|
314
|
+
return True # no hostnames to check
|
|
315
|
+
short_name = device_data.name.split(".")[0]
|
|
316
|
+
for hostname in hostnames:
|
|
317
|
+
if short_name == hostname or device_data.name == hostname:
|
|
278
318
|
return True
|
|
279
319
|
return False
|
|
280
320
|
|
|
@@ -294,20 +334,17 @@ def _hostname_dot_hack(raw_query: str) -> str:
|
|
|
294
334
|
if isinstance(raw_query, list):
|
|
295
335
|
for i, name in enumerate(raw_query):
|
|
296
336
|
raw_query[i] = add_dot(name)
|
|
337
|
+
elif isinstance(raw_query, str):
|
|
338
|
+
raw_query = add_dot(raw_query)
|
|
297
339
|
|
|
298
340
|
return raw_query
|
|
299
341
|
|
|
300
342
|
|
|
301
|
-
def parse_glob(
|
|
302
|
-
query_groups
|
|
303
|
-
|
|
304
|
-
if
|
|
305
|
-
|
|
306
|
-
if glob_type not in query_groups:
|
|
307
|
-
raise Exception(f"unknown query type: '{glob_type}'")
|
|
308
|
-
if not param:
|
|
309
|
-
raise Exception(f"empty param for '{glob_type}'")
|
|
310
|
-
query_groups[glob_type].append(param)
|
|
343
|
+
def parse_glob(exact_host_filter: bool, query: NetboxQuery) -> dict[str, list[str]]:
|
|
344
|
+
query_groups = cast(dict[str, list[str]], query.parse_query())
|
|
345
|
+
if names := query_groups.pop("name", None):
|
|
346
|
+
if exact_host_filter:
|
|
347
|
+
query_groups["name__ie"] = names
|
|
311
348
|
else:
|
|
312
|
-
query_groups["name__ic"]
|
|
349
|
+
query_groups["name__ic"] = [_hostname_dot_hack(name) for name in names]
|
|
313
350
|
return query_groups
|
|
@@ -22,6 +22,8 @@ from typing import (
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
import colorama
|
|
25
|
+
import annet.deploy
|
|
26
|
+
import annet.deploy_ui
|
|
25
27
|
import annet.lib
|
|
26
28
|
from annet.annlib import jsontools
|
|
27
29
|
from annet.annlib.netdev.views.hardware import HardwareView
|
|
@@ -262,7 +264,7 @@ def patch(args: cli_args.ShowPatchOptions, loader: ann_gen.Loader):
|
|
|
262
264
|
global live_configs # pylint: disable=global-statement
|
|
263
265
|
if args.config == "running":
|
|
264
266
|
fetcher = annet.deploy.get_fetcher()
|
|
265
|
-
live_configs = fetcher.fetch(loader.devices, processes=args.parallel)
|
|
267
|
+
live_configs = annet.lib.do_async(fetcher.fetch(loader.devices, processes=args.parallel))
|
|
266
268
|
stdin = args.stdin(filter_acl=args.filter_acl, config=args.config)
|
|
267
269
|
|
|
268
270
|
filterer = filtering.filterer_connector.get()
|
|
@@ -567,7 +569,7 @@ class Deployer:
|
|
|
567
569
|
if not diff_obj:
|
|
568
570
|
self.empty_diff_hostnames.update(dev.hostname for dev in devices)
|
|
569
571
|
if not self.args.no_ask_deploy:
|
|
570
|
-
#
|
|
572
|
+
# разобьём список устройств на несколько линий
|
|
571
573
|
dest_name = ""
|
|
572
574
|
try:
|
|
573
575
|
_, term_columns_str = os.popen("stty size", "r").read().split()
|
|
@@ -596,18 +598,18 @@ class Deployer:
|
|
|
596
598
|
return diff_lines
|
|
597
599
|
|
|
598
600
|
def ask_deploy(self) -> str:
|
|
599
|
-
return self._ask("y", annet.
|
|
601
|
+
return self._ask("y", annet.deploy_ui.AskConfirm(
|
|
600
602
|
text="\n".join(self.diff_lines()),
|
|
601
603
|
alternative_text="\n".join(self.cmd_lines),
|
|
602
604
|
))
|
|
603
605
|
|
|
604
606
|
def ask_rollback(self) -> str:
|
|
605
|
-
return self._ask("n", annet.
|
|
607
|
+
return self._ask("n", annet.deploy_ui.AskConfirm(
|
|
606
608
|
text="Execute rollback?\n",
|
|
607
609
|
alternative_text="",
|
|
608
610
|
))
|
|
609
611
|
|
|
610
|
-
def _ask(self, default_ans: str, ask: annet.
|
|
612
|
+
def _ask(self, default_ans: str, ask: annet.deploy_ui.AskConfirm) -> str:
|
|
611
613
|
# если filter_acl из stdin то с ним уже не получится работать как с терминалом
|
|
612
614
|
ans = default_ans
|
|
613
615
|
if not self.args.no_ask_deploy:
|
|
@@ -666,11 +668,22 @@ def deploy(
|
|
|
666
668
|
filterer: Filterer,
|
|
667
669
|
fetcher: Fetcher,
|
|
668
670
|
deploy_driver: DeployDriver,
|
|
671
|
+
) -> ExitCode:
|
|
672
|
+
return annet.lib.do_async(adeploy(args, loader, deployer, filterer, fetcher, deploy_driver))
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
async def adeploy(
|
|
676
|
+
args: cli_args.DeployOptions,
|
|
677
|
+
loader: ann_gen.Loader,
|
|
678
|
+
deployer: Deployer,
|
|
679
|
+
filterer: Filterer,
|
|
680
|
+
fetcher: Fetcher,
|
|
681
|
+
deploy_driver: DeployDriver,
|
|
669
682
|
) -> ExitCode:
|
|
670
683
|
""" Сгенерировать конфиг для устройств и задеплоить его """
|
|
671
684
|
ret: ExitCode = 0
|
|
672
685
|
global live_configs # pylint: disable=global-statement
|
|
673
|
-
live_configs = fetcher.fetch(devices=loader.devices, processes=args.parallel)
|
|
686
|
+
live_configs = await fetcher.fetch(devices=loader.devices, processes=args.parallel)
|
|
674
687
|
pool = ann_gen.OldNewParallel(args, loader, filterer)
|
|
675
688
|
|
|
676
689
|
for res in pool.generated_configs(loader.devices):
|
|
@@ -687,7 +700,18 @@ def deploy(
|
|
|
687
700
|
ans = deployer.ask_deploy()
|
|
688
701
|
if ans != "y":
|
|
689
702
|
return 2 ** 2
|
|
690
|
-
|
|
703
|
+
progress_bar = None
|
|
704
|
+
if sys.stdout.isatty() and not args.no_progress:
|
|
705
|
+
progress_bar = annet.deploy_ui.ProgressBars(odict([(device.fqdn, {}) for device in deploy_cmds]))
|
|
706
|
+
progress_bar.init()
|
|
707
|
+
with progress_bar:
|
|
708
|
+
progress_bar.start_terminal_refresher()
|
|
709
|
+
result = await deploy_driver.bulk_deploy(deploy_cmds, args, progress_bar=progress_bar)
|
|
710
|
+
await progress_bar.wait_for_exit()
|
|
711
|
+
progress_bar.screen.clear()
|
|
712
|
+
progress_bar.stop_terminal_refresher()
|
|
713
|
+
else:
|
|
714
|
+
result = await deploy_driver.bulk_deploy(deploy_cmds, args)
|
|
691
715
|
|
|
692
716
|
rolled_back = False
|
|
693
717
|
rollback_cmds = {deployer.fqdn_to_device[x]: cc for x, cc in result.original_states.items() if cc}
|
|
@@ -695,7 +719,7 @@ def deploy(
|
|
|
695
719
|
ans = deployer.ask_rollback()
|
|
696
720
|
if rollback_cmds and ans == "y":
|
|
697
721
|
rolled_back = True
|
|
698
|
-
|
|
722
|
+
await deploy_driver.bulk_deploy(rollback_cmds, args)
|
|
699
723
|
|
|
700
724
|
if not args.no_check_diff and not rolled_back:
|
|
701
725
|
deployer.check_diff(result, loader)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# pylint: disable=unused-argument
|
|
2
|
+
import abc
|
|
3
|
+
import itertools
|
|
4
|
+
from collections import namedtuple
|
|
5
|
+
from typing import Dict, List, Optional, Any, OrderedDict, Tuple, Type
|
|
6
|
+
|
|
7
|
+
from contextlog import get_logger
|
|
8
|
+
|
|
9
|
+
from annet.annlib.command import Command, Question, CommandList
|
|
10
|
+
from annet.annlib.netdev.views.hardware import HardwareView
|
|
11
|
+
from annet.annlib.rbparser.deploying import MakeMessageMatcher, Answer
|
|
12
|
+
from annet.cli_args import DeployOptions
|
|
13
|
+
from annet.connectors import Connector, get_connector_from_config
|
|
14
|
+
from annet.rulebook import get_rulebook, deploying
|
|
15
|
+
from annet.storage import Device
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_DeployResultBase = namedtuple("_DeployResultBase", ("hostnames", "results", "durations", "original_states"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ProgressBar(abc.ABC):
|
|
22
|
+
@abc.abstractmethod
|
|
23
|
+
def set_content(self, tile_name: str, content: str):
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
@abc.abstractmethod
|
|
27
|
+
def set_progress(self,
|
|
28
|
+
tile_name: str,
|
|
29
|
+
iteration: int,
|
|
30
|
+
total: int,
|
|
31
|
+
prefix: str = "",
|
|
32
|
+
suffix: str = "",
|
|
33
|
+
fill: str = "",
|
|
34
|
+
error: bool = False,
|
|
35
|
+
):
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def set_exception(self, tile_name: str, cmd_exc: str, last_cmd: str, progress_max: int):
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DeployResult(_DeployResultBase): # noqa: E302
|
|
44
|
+
def add_results(self, results: dict[str, tuple[list[str], list[Exception]]]) -> None:
|
|
45
|
+
for hostname, (excs, result) in results.items():
|
|
46
|
+
self.hostnames.append(hostname)
|
|
47
|
+
self.results[hostname] = excs
|
|
48
|
+
self.durations[hostname] = 0.0
|
|
49
|
+
self.original_states[hostname] = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class _FetcherConnector(Connector["Fetcher"]):
|
|
53
|
+
name = "Fetcher"
|
|
54
|
+
ep_name = "deploy_fetcher"
|
|
55
|
+
ep_by_group_only = "annet.connectors.fetcher"
|
|
56
|
+
|
|
57
|
+
def _get_default(self) -> Type["Fetcher"]:
|
|
58
|
+
# if entry points are broken, try to use direct import
|
|
59
|
+
import annet.adapters.fetchers.stub.fetcher as stub_fetcher
|
|
60
|
+
return stub_fetcher.StubFetcher
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _DriverConnector(Connector["DeployDriver"]):
|
|
64
|
+
name = "DeployDriver"
|
|
65
|
+
ep_name = "deploy_driver"
|
|
66
|
+
ep_by_group_only = "annet.connectors.deployer"
|
|
67
|
+
|
|
68
|
+
def _get_default(self) -> Type["DeployDriver"]:
|
|
69
|
+
# if entry points are broken, try to use direct import
|
|
70
|
+
import annet.adapters.deployers.stub.deployer as stub_deployer
|
|
71
|
+
return stub_deployer.StubDeployDriver
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
fetcher_connector = _FetcherConnector()
|
|
75
|
+
driver_connector = _DriverConnector()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Fetcher(abc.ABC):
|
|
79
|
+
@abc.abstractmethod
|
|
80
|
+
async def fetch_packages(self,
|
|
81
|
+
devices: list[Device],
|
|
82
|
+
processes: int = 1,
|
|
83
|
+
max_slots: int = 0,
|
|
84
|
+
) -> tuple[dict[Device, str], dict[Device, Any]]:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
@abc.abstractmethod
|
|
88
|
+
async def fetch(self,
|
|
89
|
+
devices: list[Device],
|
|
90
|
+
files_to_download: dict[str, list[str]] | None = None,
|
|
91
|
+
processes: int = 1,
|
|
92
|
+
max_slots: int = 0,
|
|
93
|
+
):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_fetcher() -> Fetcher:
|
|
98
|
+
connectors = fetcher_connector.get_all()
|
|
99
|
+
fetcher, _ = get_connector_from_config("fetcher", connectors)
|
|
100
|
+
return fetcher
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class DeployDriver(abc.ABC):
|
|
104
|
+
@abc.abstractmethod
|
|
105
|
+
async def bulk_deploy(self, deploy_cmds: dict, args: DeployOptions, progress_bar: ProgressBar | None = None) -> DeployResult:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
@abc.abstractmethod
|
|
109
|
+
def apply_deploy_rulebook(self, hw: HardwareView, cmd_paths, do_finalize=True, do_commit=True):
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
@abc.abstractmethod
|
|
113
|
+
def build_configuration_cmdlist(self, hw: HardwareView, do_finalize=True, do_commit=True):
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
@abc.abstractmethod
|
|
117
|
+
def build_exit_cmdlist(self, hw):
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_deployer() -> DeployDriver:
|
|
122
|
+
connectors = driver_connector.get_all()
|
|
123
|
+
deployer, _ = get_connector_from_config("deployer", connectors)
|
|
124
|
+
return deployer
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ===
|
|
128
|
+
def scrub_config(text: str, breed: str) -> str:
|
|
129
|
+
return text
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def show_bulk_report(hostnames, results, durations, log_dir):
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class RulebookQuestionHandler:
|
|
137
|
+
def __init__(self, dialogs):
|
|
138
|
+
self._dialogs = dialogs
|
|
139
|
+
|
|
140
|
+
def __call__(self, dev: Connector, cmd: Command, match_content: bytes):
|
|
141
|
+
content = match_content.strip()
|
|
142
|
+
content = content.decode()
|
|
143
|
+
for matcher, answer in self._dialogs.items():
|
|
144
|
+
if matcher(content):
|
|
145
|
+
return Command(answer.text)
|
|
146
|
+
|
|
147
|
+
get_logger().info("no answer in rulebook. dialogs=%s match_content=%s", self._dialogs, match_content)
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def rb_question_to_question(q: MakeMessageMatcher, a: Answer) -> Question: # TODO: drop MakeMessageMatcher
|
|
152
|
+
if not a.send_nl:
|
|
153
|
+
raise Exception("not supported false send_nl")
|
|
154
|
+
text: str = q._text # pylint: disable=protected-access
|
|
155
|
+
is_regexp = False
|
|
156
|
+
if text.startswith("/") and text.endswith("/"):
|
|
157
|
+
is_regexp = True
|
|
158
|
+
text = text[1:-1]
|
|
159
|
+
res = Question(question=text, answer=a.text, is_regexp=is_regexp)
|
|
160
|
+
return res
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def make_cmd_params(rule: Dict[str, Any]) -> Dict[str, Any]:
|
|
164
|
+
if rule:
|
|
165
|
+
qa_handler = RulebookQuestionHandler(rule["attrs"]["dialogs"])
|
|
166
|
+
qa_list: List[Question] = []
|
|
167
|
+
for matcher, answer in qa_handler._dialogs.items(): # pylint: disable=protected-access
|
|
168
|
+
qa_list.append(rb_question_to_question(matcher, answer))
|
|
169
|
+
return {
|
|
170
|
+
"questions": qa_list,
|
|
171
|
+
"timeout": rule["attrs"]["timeout"],
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
"timeout": 30,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def make_apply_commands(rule: dict, hw: HardwareView, do_commit: bool, do_finalize: bool, path: Optional[str] = None):
|
|
179
|
+
apply_logic = rule["attrs"]["apply_logic"]
|
|
180
|
+
before, after = apply_logic(hw, do_commit=do_commit, do_finalize=do_finalize, path=path)
|
|
181
|
+
return before, after
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def fill_cmd_params(rules: OrderedDict, cmd: Command):
|
|
185
|
+
rule = deploying.match_deploy_rule(rules, (cmd.cmd,), {})
|
|
186
|
+
if rule:
|
|
187
|
+
cmd_params = make_cmd_params(rule)
|
|
188
|
+
cmd.questions = cmd_params.get("questions", None)
|
|
189
|
+
cmd.timeout = cmd_params["timeout"]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def apply_deploy_rulebook(hw: HardwareView, cmd_paths, do_finalize=True, do_commit=True):
|
|
193
|
+
rules = get_rulebook(hw)["deploying"]
|
|
194
|
+
cmds_with_apply = []
|
|
195
|
+
for cmd_path, context in cmd_paths.items():
|
|
196
|
+
rule = deploying.match_deploy_rule(rules, cmd_path, context)
|
|
197
|
+
cmd_params = make_cmd_params(rule)
|
|
198
|
+
before, after = make_apply_commands(rule, hw, do_commit, do_finalize)
|
|
199
|
+
|
|
200
|
+
cmd = Command(cmd_path[-1], **cmd_params)
|
|
201
|
+
# XXX более чистый способ передавать-мета инфу о команде
|
|
202
|
+
cmd.level = len(cmd_path) - 1
|
|
203
|
+
cmds_with_apply.append((cmd, before, after))
|
|
204
|
+
|
|
205
|
+
def _key(item):
|
|
206
|
+
_cmd, before, after = item
|
|
207
|
+
return (tuple(cmd.cmd for cmd in before), tuple(cmd.cmd for cmd in after))
|
|
208
|
+
|
|
209
|
+
cmdlist = CommandList()
|
|
210
|
+
for _k, cmd_before_after in itertools.groupby(cmds_with_apply, key=_key):
|
|
211
|
+
cmd_before_after = list(cmd_before_after)
|
|
212
|
+
_, before, after = cmd_before_after[0]
|
|
213
|
+
for c in before:
|
|
214
|
+
c.level = 0
|
|
215
|
+
fill_cmd_params(rules, c)
|
|
216
|
+
cmdlist.add_cmd(c)
|
|
217
|
+
for cmd, _before, _after in cmd_before_after:
|
|
218
|
+
cmdlist.add_cmd(cmd)
|
|
219
|
+
for c in after:
|
|
220
|
+
c.level = 0
|
|
221
|
+
fill_cmd_params(rules, c)
|
|
222
|
+
cmdlist.add_cmd(c)
|
|
223
|
+
return cmdlist
|