annet 0.15.1__tar.gz → 0.15.3__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.15.1/annet.egg-info → annet-0.15.3}/PKG-INFO +1 -1
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/storage_opts.py +5 -4
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/provider.py +11 -1
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/v37/storage.py +22 -1
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/devdb/data/devdb.json +23 -19
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/tabparser.py +3 -2
- {annet-0.15.1 → annet-0.15.3}/annet/api/__init__.py +5 -5
- {annet-0.15.1 → annet-0.15.3}/annet/argparse.py +2 -0
- {annet-0.15.1 → annet-0.15.3}/annet/cli.py +6 -6
- {annet-0.15.1 → annet-0.15.3}/annet/cli_args.py +8 -1
- annet-0.15.3/annet/connectors.py +143 -0
- {annet-0.15.1 → annet-0.15.3}/annet/deploy.py +21 -28
- {annet-0.15.1 → annet-0.15.3}/annet/filtering.py +1 -0
- {annet-0.15.1 → annet-0.15.3}/annet/gen.py +4 -4
- {annet-0.15.1 → annet-0.15.3}/annet/hardware.py +1 -0
- {annet-0.15.1 → annet-0.15.3}/annet/lib.py +19 -2
- {annet-0.15.1 → annet-0.15.3}/annet/output.py +10 -2
- {annet-0.15.1 → annet-0.15.3}/annet/storage.py +6 -17
- {annet-0.15.1 → annet-0.15.3/annet.egg-info}/PKG-INFO +1 -1
- {annet-0.15.1 → annet-0.15.3}/annet.egg-info/entry_points.txt +3 -0
- {annet-0.15.1 → annet-0.15.3}/setup.py +3 -0
- annet-0.15.1/annet/connectors.py +0 -78
- {annet-0.15.1 → annet-0.15.3}/AUTHORS +0 -0
- {annet-0.15.1 → annet-0.15.3}/LICENSE +0 -0
- {annet-0.15.1 → annet-0.15.3}/MANIFEST.in +0 -0
- {annet-0.15.1 → annet-0.15.3}/README.md +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/client.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/manufacturer.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/models.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/query.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/status_client.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/v24/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/v24/storage.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/v37/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annet.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/command.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/diff.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/errors.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/filter_acl.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/jsontools.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/lib.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/db.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/views/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/views/dump.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/views/hardware.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/output.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/patching.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/acl.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/ordering.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/platform.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/syntax.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/rulebook/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/rulebook/common.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/annlib/types.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/configs/context.yml +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/configs/logging.yaml +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/diff.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/executor.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/base.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/common/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/common/initial.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/entire.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/exceptions.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/jsonfragment.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/partial.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/perf.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/ref.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/generators/result.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/implicit.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/parallel.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/patching.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/reference.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/arista/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/arista/iface.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/aruba/ap_env.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/aruba/misc.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/cisco/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/cisco/iface.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/cisco/misc.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/common.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/deploying.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/iface.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/misc.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/juniper/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/nexus/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/nexus/iface.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/patching.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/ribbon/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/arista.order +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/arista.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/aruba.order +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/cisco.deploy +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/cisco.order +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/cisco.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/huawei.deploy +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/huawei.order +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/nexus.order +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/optixtrans.deploy +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/optixtrans.order +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/optixtrans.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/pc.order +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/pc.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/routeros.order +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/tabparser.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/text_term_format.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/tracing.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet/types.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet.egg-info/SOURCES.txt +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet.egg-info/dependency_links.txt +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet.egg-info/requires.txt +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet.egg-info/top_level.txt +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet_generators/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet_generators/example/__init__.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/annet_generators/example/lldp.py +0 -0
- {annet-0.15.1 → annet-0.15.3}/requirements.txt +0 -0
- {annet-0.15.1 → annet-0.15.3}/setup.cfg +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
+
DEFAULT_URL = "http://localhost"
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class NetboxStorageOpts:
|
|
6
8
|
def __init__(self, url: str, token: str):
|
|
@@ -9,7 +11,6 @@ class NetboxStorageOpts:
|
|
|
9
11
|
|
|
10
12
|
@classmethod
|
|
11
13
|
def parse_params(cls, conf_params: dict[str, str] | None, cli_opts: Any):
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
14
|
+
url = os.getenv("NETBOX_URL") or conf_params.get("url") or DEFAULT_URL
|
|
15
|
+
token = os.getenv("NETBOX_TOKEN", "").strip() or conf_params.get("token") or ""
|
|
16
|
+
return cls(url=url, token=token)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
|
|
1
3
|
from dataclass_rest.exceptions import ClientError
|
|
2
4
|
|
|
3
5
|
from annet.storage import StorageProvider, Storage
|
|
6
|
+
from annet.connectors import AdapterWithName, AdapterWithConfig, T
|
|
4
7
|
from .common.status_client import NetboxStatusClient
|
|
5
8
|
from .common.storage_opts import NetboxStorageOpts
|
|
6
9
|
from .common.query import NetboxQuery
|
|
@@ -23,7 +26,14 @@ def storage_factory(opts: NetboxStorageOpts) -> Storage:
|
|
|
23
26
|
raise ValueError(f"Unsupported version: {status.netbox_version}")
|
|
24
27
|
|
|
25
28
|
|
|
26
|
-
class NetboxProvider(StorageProvider):
|
|
29
|
+
class NetboxProvider(StorageProvider, AdapterWithName, AdapterWithConfig):
|
|
30
|
+
def __init__(self, url: Optional[str] = None, token: Optional[str] = None):
|
|
31
|
+
self.url = url
|
|
32
|
+
self.token = token
|
|
33
|
+
|
|
34
|
+
def with_config(self, **kwargs: Dict[str, Any]) -> T:
|
|
35
|
+
return NetboxProvider(**kwargs)
|
|
36
|
+
|
|
27
37
|
def storage(self):
|
|
28
38
|
return storage_factory
|
|
29
39
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from logging import getLogger
|
|
2
|
-
from typing import Optional, List, Union, Dict
|
|
2
|
+
from typing import Any, Optional, List, Union, Dict
|
|
3
3
|
from ipaddress import ip_interface
|
|
4
4
|
from collections import defaultdict
|
|
5
5
|
|
|
@@ -145,6 +145,7 @@ class NetboxStorageV37(Storage):
|
|
|
145
145
|
def _load_devices(self, query: NetboxQuery) -> List[api_models.Device]:
|
|
146
146
|
if not query.globs:
|
|
147
147
|
return []
|
|
148
|
+
query = _hostname_dot_hack(query)
|
|
148
149
|
return [
|
|
149
150
|
device
|
|
150
151
|
for device in self.netbox.dcim_all_devices(
|
|
@@ -223,3 +224,23 @@ def _match_query(query: NetboxQuery, device_data: api_models.Device) -> bool:
|
|
|
223
224
|
if subquery.strip() in device_data.name:
|
|
224
225
|
return True
|
|
225
226
|
return False
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _hostname_dot_hack(netbox_query: NetboxQuery) -> NetboxQuery:
|
|
230
|
+
# there is no proper way to lookup host by its hostname
|
|
231
|
+
# ie find "host" with fqdn "host.example.com"
|
|
232
|
+
# besides using name__ic (ie startswith)
|
|
233
|
+
# since there is no direct analogue for this field in netbox
|
|
234
|
+
# so we need to add a dot to hostnames (top-level fqdn part)
|
|
235
|
+
# so we would not receive devices with a common name prefix
|
|
236
|
+
def add_dot(raw_query: Any) -> Any:
|
|
237
|
+
if isinstance(raw_query, str) and "." not in raw_query:
|
|
238
|
+
raw_query = raw_query + "."
|
|
239
|
+
return raw_query
|
|
240
|
+
|
|
241
|
+
raw_query = netbox_query.query
|
|
242
|
+
if isinstance(raw_query, list):
|
|
243
|
+
for i, name in enumerate(raw_query):
|
|
244
|
+
raw_query[i] = add_dot(name)
|
|
245
|
+
|
|
246
|
+
return NetboxQuery(raw_query)
|
|
@@ -6,18 +6,19 @@
|
|
|
6
6
|
"Cisco.ASR": " ASR",
|
|
7
7
|
"Cisco.ASR.ASR9000": " 9\\d{3}",
|
|
8
8
|
"Cisco.XRV": "XRv",
|
|
9
|
-
"Cisco.Catalyst": "Catalyst",
|
|
10
|
-
"Cisco.Catalyst.C2900": "
|
|
11
|
-
"Cisco.Catalyst.C2900.C2950": "
|
|
12
|
-
"Cisco.Catalyst.C2900.C2960": "
|
|
13
|
-
"Cisco.Catalyst.C2900.C2960.C2960Plus": "
|
|
14
|
-
"Cisco.Catalyst.C2900.C2960.C2960S": "
|
|
15
|
-
"Cisco.Catalyst.C2900.C2960.C2960X": "
|
|
16
|
-
"Cisco.Catalyst.
|
|
17
|
-
"Cisco.Catalyst.
|
|
18
|
-
"Cisco.Catalyst.
|
|
19
|
-
"Cisco.Catalyst.
|
|
20
|
-
"Cisco.Catalyst.
|
|
9
|
+
"Cisco.Catalyst": " (Catalyst |WS-C)",
|
|
10
|
+
"Cisco.Catalyst.C2900": "29\\d\\d",
|
|
11
|
+
"Cisco.Catalyst.C2900.C2950": "2950",
|
|
12
|
+
"Cisco.Catalyst.C2900.C2960": "2960",
|
|
13
|
+
"Cisco.Catalyst.C2900.C2960.C2960Plus": "2960-Plus",
|
|
14
|
+
"Cisco.Catalyst.C2900.C2960.C2960S": "2960S",
|
|
15
|
+
"Cisco.Catalyst.C2900.C2960.C2960X": "2960X",
|
|
16
|
+
"Cisco.Catalyst.C2900.C2960.C2960G": "2960G",
|
|
17
|
+
"Cisco.Catalyst.C3500": "35\\d\\d",
|
|
18
|
+
"Cisco.Catalyst.C3600": "36\\d\\d",
|
|
19
|
+
"Cisco.Catalyst.C3700": "37\\d\\d",
|
|
20
|
+
"Cisco.Catalyst.C4900": "49\\d\\d",
|
|
21
|
+
"Cisco.Catalyst.C6500": "65\\d\\d",
|
|
21
22
|
"Cisco.Nexus": " [Nn]exus",
|
|
22
23
|
"Cisco.Nexus.N3x": " 3\\d\\d\\d",
|
|
23
24
|
"Cisco.Nexus.N3x.N3100": " 31\\d\\d",
|
|
@@ -52,13 +53,13 @@
|
|
|
52
53
|
"Huawei.CE.CE8800.CE8850": " CE8850",
|
|
53
54
|
"Huawei.CE.CE8800.CE8851": " CE8851",
|
|
54
55
|
"Huawei.CE.CE9800": " CE98\\d\\d",
|
|
55
|
-
"Huawei.Quidway": " S
|
|
56
|
-
"Huawei.Quidway.S2x": "
|
|
57
|
-
"Huawei.Quidway.S2x.S2300": "
|
|
58
|
-
"Huawei.Quidway.S2x.S2700": "
|
|
59
|
-
"Huawei.Quidway.S5300": "
|
|
60
|
-
"Huawei.Quidway.S5700": "
|
|
61
|
-
"Huawei.Quidway.S6700": "
|
|
56
|
+
"Huawei.Quidway": " (LS-)?S",
|
|
57
|
+
"Huawei.Quidway.S2x": "2\\d{3}",
|
|
58
|
+
"Huawei.Quidway.S2x.S2300": "23\\d\\d",
|
|
59
|
+
"Huawei.Quidway.S2x.S2700": "27\\d\\d",
|
|
60
|
+
"Huawei.Quidway.S5300": "53\\d\\d",
|
|
61
|
+
"Huawei.Quidway.S5700": "57\\d\\d",
|
|
62
|
+
"Huawei.Quidway.S6700": "67\\d\\d",
|
|
62
63
|
"Huawei.NE": " NE\\d+",
|
|
63
64
|
"Huawei.NE.NE40E": " NE40E",
|
|
64
65
|
"Huawei.NE.NE8000": " NE8000",
|
|
@@ -111,6 +112,9 @@
|
|
|
111
112
|
"PC.Whitebox.Edgecore": "[Ee]dge-?[Cc]ore",
|
|
112
113
|
"PC.Whitebox.Edgecore.AS": "AS",
|
|
113
114
|
"PC.Whitebox.Edgecore.AS9736": "AS9736",
|
|
115
|
+
"PC.Whitebox.NVIDIA": "NVIDIA",
|
|
116
|
+
"PC.Whitebox.NVIDIA.SN": " SN",
|
|
117
|
+
"PC.Whitebox.NVIDIA.SN.SN5600": " SN5600",
|
|
114
118
|
"PC.Moxa": "[Mm]oxa",
|
|
115
119
|
"PC.Moxa.NPort": " [Nn][Pp]ort",
|
|
116
120
|
"PC.Moxa.NPort.6610": "6610",
|
|
@@ -490,10 +490,11 @@ class RosFormatter(CommonFormatter):
|
|
|
490
490
|
rows.append((row, None, row_context))
|
|
491
491
|
|
|
492
492
|
prev_prow = None
|
|
493
|
+
prev_prow_context = {}
|
|
493
494
|
for sub_config, row_group in itertools.groupby(rows, lambda x: x[1]):
|
|
494
495
|
if sub_config is None:
|
|
495
496
|
if prev_prow:
|
|
496
|
-
yield prev_prow
|
|
497
|
+
yield prev_prow, prev_prow_context
|
|
497
498
|
yield BlockBegin, None
|
|
498
499
|
for row, _, row_context in row_group:
|
|
499
500
|
yield row, row_context
|
|
@@ -502,7 +503,7 @@ class RosFormatter(CommonFormatter):
|
|
|
502
503
|
else:
|
|
503
504
|
for row, _, row_context in row_group:
|
|
504
505
|
if context and context.parent and context.parent.row:
|
|
505
|
-
prev_prow = context.parent.
|
|
506
|
+
prev_prow, prev_prow_context = context.parent.current
|
|
506
507
|
prow = f"{context.parent.row} {row}"
|
|
507
508
|
else:
|
|
508
509
|
prow = row
|
|
@@ -218,8 +218,8 @@ def log_host_progress_cb(pool: Parallel, task_result: TaskResult):
|
|
|
218
218
|
stacklevel=2,
|
|
219
219
|
)
|
|
220
220
|
args = cast(cli_args.QueryOptions, pool.args[0])
|
|
221
|
-
connector
|
|
222
|
-
storage_opts = connector.opts().parse_params(
|
|
221
|
+
connector = get_storage()
|
|
222
|
+
storage_opts = connector.opts().parse_params({}, args)
|
|
223
223
|
with connector.storage()(storage_opts) as storage:
|
|
224
224
|
fqdns = storage.resolve_fdnds_by_query(args.query)
|
|
225
225
|
PoolProgressLogger(device_fqdns=fqdns)(pool, task_result)
|
|
@@ -260,7 +260,7 @@ def patch(args: cli_args.ShowPatchOptions, loader: ann_gen.Loader):
|
|
|
260
260
|
""" Сгенерировать патч для устройств """
|
|
261
261
|
global live_configs # pylint: disable=global-statement
|
|
262
262
|
if args.config == "running":
|
|
263
|
-
fetcher = annet.deploy.
|
|
263
|
+
fetcher = annet.deploy.get_fetcher()
|
|
264
264
|
live_configs = fetcher.fetch(loader.devices, processes=args.parallel)
|
|
265
265
|
stdin = args.stdin(filter_acl=args.filter_acl, config=args.config)
|
|
266
266
|
|
|
@@ -434,7 +434,7 @@ class CliDeployerJob(DeployerJob):
|
|
|
434
434
|
self.cmd_lines.extend(["= %s " % device.hostname, ""])
|
|
435
435
|
self.cmd_lines.extend(map(itemgetter(-1), cmds))
|
|
436
436
|
self.cmd_lines.append("")
|
|
437
|
-
deployer_driver = annet.deploy.
|
|
437
|
+
deployer_driver = annet.deploy.get_deployer()
|
|
438
438
|
self.deploy_cmds[device] = deployer_driver.apply_deploy_rulebook(
|
|
439
439
|
device.hw, cmds,
|
|
440
440
|
do_commit=not self.args.dont_commit
|
|
@@ -494,7 +494,7 @@ class PCDeployerJob(DeployerJob):
|
|
|
494
494
|
"generator_types": generator_types,
|
|
495
495
|
}
|
|
496
496
|
self.diffs[device] = upload_files
|
|
497
|
-
deployer_driver = annet.deploy.
|
|
497
|
+
deployer_driver = annet.deploy.get_deployer()
|
|
498
498
|
before, after = deployer_driver.build_configuration_cmdlist(device.hw)
|
|
499
499
|
for cmd in deployer_driver.build_exit_cmdlist(device.hw):
|
|
500
500
|
after.add_cmd(cmd)
|
|
@@ -107,6 +107,8 @@ class Arg:
|
|
|
107
107
|
default = self.kwargs.get("default", None)
|
|
108
108
|
if isinstance(default, ConvertibleDefault) and "type" in self.kwargs:
|
|
109
109
|
default = self.kwargs["default"] = default.convert(self.kwargs["type"])
|
|
110
|
+
elif isinstance(default, Callable):
|
|
111
|
+
default = self.kwargs["default"] = default()
|
|
110
112
|
elif default is False and "action" not in self.kwargs:
|
|
111
113
|
self.kwargs["action"] = "store_true"
|
|
112
114
|
self.default = default
|
|
@@ -15,7 +15,7 @@ import yaml
|
|
|
15
15
|
from contextlog import get_logger
|
|
16
16
|
from valkit.python import valid_logging_level
|
|
17
17
|
|
|
18
|
-
from annet.deploy import
|
|
18
|
+
from annet.deploy import get_fetcher, get_deployer
|
|
19
19
|
from annet import api, cli_args, filtering, generators
|
|
20
20
|
from annet.api import collapse_texts, Deployer
|
|
21
21
|
from annet.argparse import ArgParser, subcommand
|
|
@@ -68,8 +68,8 @@ def get_loader(gen_args: cli_args.GenOptions, args: cli_args.QueryOptionsBase):
|
|
|
68
68
|
exit_stack = ExitStack()
|
|
69
69
|
storages = []
|
|
70
70
|
with exit_stack:
|
|
71
|
-
connector,
|
|
72
|
-
storage_opts = connector.opts().parse_params(
|
|
71
|
+
connector, conf_params = get_storage()
|
|
72
|
+
storage_opts = connector.opts().parse_params(conf_params, args)
|
|
73
73
|
storages.append(exit_stack.enter_context(connector.storage()(storage_opts)))
|
|
74
74
|
yield Loader(*storages, args=gen_args, no_empty_warning=args.query.is_empty())
|
|
75
75
|
|
|
@@ -233,8 +233,8 @@ def deploy(args: cli_args.DeployOptions):
|
|
|
233
233
|
|
|
234
234
|
deployer = Deployer(args)
|
|
235
235
|
filterer = filtering.filterer_connector.get()
|
|
236
|
-
fetcher =
|
|
237
|
-
deploy_driver =
|
|
236
|
+
fetcher = get_fetcher()
|
|
237
|
+
deploy_driver = get_deployer()
|
|
238
238
|
|
|
239
239
|
with get_loader(args, args) as loader:
|
|
240
240
|
return api.deploy(
|
|
@@ -273,7 +273,7 @@ def file_patch(args: cli_args.FilePatchOptions):
|
|
|
273
273
|
def context():
|
|
274
274
|
""" A group of commands for manipulating context.
|
|
275
275
|
|
|
276
|
-
By default, the context file is located in '~/.
|
|
276
|
+
By default, the context file is located in '~/.annet/context.yml',
|
|
277
277
|
but it can be set with the ANN_CONTEXT_CONFIG_PATH environment variable.
|
|
278
278
|
"""
|
|
279
279
|
context_touch()
|
|
@@ -8,6 +8,7 @@ import os
|
|
|
8
8
|
|
|
9
9
|
from valkit.common import valid_string_list
|
|
10
10
|
|
|
11
|
+
import annet.lib
|
|
11
12
|
from annet.argparse import Arg, ArgGroup, DefaultFromEnv
|
|
12
13
|
from annet.hardware import hardware_connector
|
|
13
14
|
from annet.storage import Query, get_storage
|
|
@@ -69,6 +70,11 @@ opt_expand_path = Arg(
|
|
|
69
70
|
help="Use full paths of Entire-generators and no just names when writing them to the file system"
|
|
70
71
|
)
|
|
71
72
|
|
|
73
|
+
opt_dest_force_create_dir = Arg(
|
|
74
|
+
"--dest-force-create-dir", default=False,
|
|
75
|
+
help="Output generated data to dir, even for blackboxes"
|
|
76
|
+
)
|
|
77
|
+
|
|
72
78
|
opt_old = Arg(
|
|
73
79
|
"old",
|
|
74
80
|
help="A path to a file (or a directory with a batch of files) that contains the old config"
|
|
@@ -228,7 +234,7 @@ opt_log_json = Arg(
|
|
|
228
234
|
)
|
|
229
235
|
|
|
230
236
|
opt_log_dest = Arg(
|
|
231
|
-
"--log-dest", default=
|
|
237
|
+
"--log-dest", default=annet.lib.get_default_log_dest,
|
|
232
238
|
help="Log to a specified file, directory, or '-' (stdout)"
|
|
233
239
|
)
|
|
234
240
|
|
|
@@ -425,6 +431,7 @@ class FileOutOptions(ArgGroup):
|
|
|
425
431
|
expand_path = opt_expand_path
|
|
426
432
|
no_label = opt_no_label
|
|
427
433
|
no_color = opt_no_color
|
|
434
|
+
dest_force_create_dir = opt_dest_force_create_dir
|
|
428
435
|
|
|
429
436
|
def __init__(self, *args, **kwargs):
|
|
430
437
|
super().__init__(*args, **kwargs)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from importlib.metadata import entry_points
|
|
5
|
+
from typing import Generic, Optional, Type, TypeVar, List, Dict, Any, Tuple
|
|
6
|
+
import warnings
|
|
7
|
+
from annet.lib import get_context
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Connector(ABC, Generic[T]):
|
|
13
|
+
name: str
|
|
14
|
+
# legacy
|
|
15
|
+
ep_name: str
|
|
16
|
+
ep_group: str = "annet.connectors"
|
|
17
|
+
# right way just to use ep groups
|
|
18
|
+
ep_by_group_only: str = ""
|
|
19
|
+
_classes: Optional[List[Type[T]]] = None
|
|
20
|
+
|
|
21
|
+
def _get_default(self) -> Type[T]:
|
|
22
|
+
raise RuntimeError(f"{self.name} is not set")
|
|
23
|
+
|
|
24
|
+
@cached_property
|
|
25
|
+
def _entry_point(self) -> List[Type[T]]:
|
|
26
|
+
ep = load_entry_point(self.ep_group, self.ep_name)
|
|
27
|
+
if self.ep_by_group_only:
|
|
28
|
+
ep.extend(load_entry_point_new(self.ep_by_group_only))
|
|
29
|
+
return ep
|
|
30
|
+
|
|
31
|
+
def get(self, *args, **kwargs) -> T:
|
|
32
|
+
"""
|
|
33
|
+
Returns connector. If more than one is registered returns random and throw warning
|
|
34
|
+
"""
|
|
35
|
+
if self._classes is None:
|
|
36
|
+
self._classes = self._entry_point or [self._get_default()]
|
|
37
|
+
if not self._classes:
|
|
38
|
+
raise Exception(f"Not found registered class for group={self.ep_group}")
|
|
39
|
+
if len(self._classes) > 1:
|
|
40
|
+
warnings.warn(f"Multiple classes are registered with the group={self.ep_group} but "
|
|
41
|
+
f"{[cls for cls in self._classes]}", UserWarning)
|
|
42
|
+
res = self._classes[0]
|
|
43
|
+
return res(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
def get_all(self, *args, **kwargs) -> List[T]:
|
|
46
|
+
if self._classes is None:
|
|
47
|
+
self._classes = self._entry_point or [self._get_default()]
|
|
48
|
+
|
|
49
|
+
return [cls(*args, **kwargs) for cls in self._classes]
|
|
50
|
+
|
|
51
|
+
def set(self, cls: Type[T]):
|
|
52
|
+
if self._classes is not None:
|
|
53
|
+
raise RuntimeError(f"Cannot reinitialize value of {self.name}")
|
|
54
|
+
self._classes = [cls]
|
|
55
|
+
|
|
56
|
+
def set_all(self, classes: List[Type[T]]):
|
|
57
|
+
if self._classes is not None:
|
|
58
|
+
raise RuntimeError(f"Cannot reinitialize value of {self.name}")
|
|
59
|
+
self._classes = list(classes)
|
|
60
|
+
|
|
61
|
+
def is_default(self) -> bool:
|
|
62
|
+
return self._classes is self._entry_point is None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class CachedConnector(Connector[T], ABC):
|
|
66
|
+
_cache: Optional[T] = None
|
|
67
|
+
|
|
68
|
+
def get(self, *args, **kwargs) -> T:
|
|
69
|
+
assert not (args or kwargs), "Arguments forwarding is not allowed for cached connectors"
|
|
70
|
+
if self._cache is None:
|
|
71
|
+
self._cache = super().get()
|
|
72
|
+
return self._cache
|
|
73
|
+
|
|
74
|
+
def set(self, cls: Type[T]):
|
|
75
|
+
super().set(cls)
|
|
76
|
+
self._cache = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def load_entry_point(group: str, name: str):
|
|
80
|
+
if sys.version_info < (3, 10):
|
|
81
|
+
ep = [item for item in entry_points().get(group, []) if item.name == name]
|
|
82
|
+
else:
|
|
83
|
+
ep = entry_points(group=group, name=name) # pylint: disable=unexpected-keyword-arg
|
|
84
|
+
if not ep:
|
|
85
|
+
return []
|
|
86
|
+
return [item.load() for item in ep]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def load_entry_point_new(group: str) -> List:
|
|
90
|
+
if sys.version_info < (3, 10):
|
|
91
|
+
ep = [item for item in entry_points().get(group, [])]
|
|
92
|
+
else:
|
|
93
|
+
ep = entry_points(group=group) # pylint: disable=unexpected-keyword-arg
|
|
94
|
+
if not ep:
|
|
95
|
+
return []
|
|
96
|
+
return [item.load() for item in ep]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AdapterWithConfig(ABC, Generic[T]):
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def with_config(self, **kwargs: Dict[str, Any]) -> T:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AdapterWithName(ABC):
|
|
106
|
+
@abstractmethod
|
|
107
|
+
def name(self) -> str:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_connector_from_config(config_key: str, connectors: List[Connector]) -> Tuple[Connector, Dict[str, Any]]:
|
|
112
|
+
seen: list[str] = []
|
|
113
|
+
if not connectors:
|
|
114
|
+
raise Exception("empty connectors")
|
|
115
|
+
connector = connectors[0] # default
|
|
116
|
+
connector_params: Dict[str, Any] = {}
|
|
117
|
+
if context_storage := get_context().get(config_key):
|
|
118
|
+
adapter_name = context_storage.get("adapter", None)
|
|
119
|
+
connector_params = context_storage.get("params", {})
|
|
120
|
+
if adapter_name:
|
|
121
|
+
for con in connectors:
|
|
122
|
+
con_name = connector.__class__.__name__
|
|
123
|
+
if isinstance(con, AdapterWithName):
|
|
124
|
+
con_name = con.name()
|
|
125
|
+
seen.append(con_name)
|
|
126
|
+
if adapter_name == con_name:
|
|
127
|
+
connector = con
|
|
128
|
+
break
|
|
129
|
+
else:
|
|
130
|
+
raise Exception("unknown %s %s: seen %s" % (config_key, adapter_name, seen))
|
|
131
|
+
else:
|
|
132
|
+
connector = connectors[0]
|
|
133
|
+
if len(connectors) > 1:
|
|
134
|
+
warnings.warn(f"Please specify adapter for '{config_key}'. Found more than one classes {connectors}", UserWarning)
|
|
135
|
+
else:
|
|
136
|
+
connector = connectors[0]
|
|
137
|
+
if len(connectors) > 1:
|
|
138
|
+
warnings.warn(f"Please specify '{config_key}'. Found more than one classes {connectors}", UserWarning)
|
|
139
|
+
if isinstance(connector, AdapterWithConfig):
|
|
140
|
+
connector = connector.with_config(**connector_params)
|
|
141
|
+
# return connector_params only for storage
|
|
142
|
+
# TODO: switch storage interface to AdapterWithConfig
|
|
143
|
+
return connector, connector_params
|
|
@@ -6,7 +6,7 @@ import itertools
|
|
|
6
6
|
import re
|
|
7
7
|
from collections import namedtuple
|
|
8
8
|
from contextlib import contextmanager
|
|
9
|
-
from typing import Dict, List, Optional,
|
|
9
|
+
from typing import Dict, List, Optional, Any, OrderedDict, Tuple, Type
|
|
10
10
|
|
|
11
11
|
from contextlog import get_logger
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ from annet.annlib.command import Command, Question, CommandList
|
|
|
15
15
|
from annet.annlib.netdev.views.hardware import HardwareView
|
|
16
16
|
from annet.annlib.rbparser.deploying import MakeMessageMatcher, Answer
|
|
17
17
|
from annet.cli_args import DeployOptions
|
|
18
|
-
from annet.connectors import Connector
|
|
18
|
+
from annet.connectors import Connector, get_connector_from_config
|
|
19
19
|
from annet.output import TextArgs
|
|
20
20
|
from annet.rulebook import get_rulebook, deploying
|
|
21
21
|
from annet.storage import Device
|
|
@@ -39,17 +39,23 @@ class DeployResult(_DeployResultBase): # noqa: E302
|
|
|
39
39
|
class _FetcherConnector(Connector["Fetcher"]):
|
|
40
40
|
name = "Fetcher"
|
|
41
41
|
ep_name = "deploy_fetcher"
|
|
42
|
+
ep_by_group_only = "annet.connectors.fetcher"
|
|
42
43
|
|
|
43
44
|
def _get_default(self) -> Type["Fetcher"]:
|
|
44
|
-
|
|
45
|
+
# if entry points are broken, try to use direct import
|
|
46
|
+
import annet.adapters.fetchers.stub.fetcher as stub_fetcher
|
|
47
|
+
return stub_fetcher.StubFetcher
|
|
45
48
|
|
|
46
49
|
|
|
47
50
|
class _DriverConnector(Connector["DeployDriver"]):
|
|
48
51
|
name = "DeployDriver"
|
|
49
52
|
ep_name = "deploy_driver"
|
|
53
|
+
ep_by_group_only = "annet.connectors.deployer"
|
|
50
54
|
|
|
51
55
|
def _get_default(self) -> Type["DeployDriver"]:
|
|
52
|
-
|
|
56
|
+
# if entry points are broken, try to use direct import
|
|
57
|
+
import annet.adapters.deployers.stub.deployer as stub_deployer
|
|
58
|
+
return stub_deployer.StubDeployDriver
|
|
53
59
|
|
|
54
60
|
|
|
55
61
|
fetcher_connector = _FetcherConnector()
|
|
@@ -59,7 +65,7 @@ driver_connector = _DriverConnector()
|
|
|
59
65
|
class Fetcher(abc.ABC):
|
|
60
66
|
@abc.abstractmethod
|
|
61
67
|
def fetch_packages(self, devices: List[Device],
|
|
62
|
-
processes: int = 1, max_slots: int = 0):
|
|
68
|
+
processes: int = 1, max_slots: int = 0) -> Tuple[Dict[Device, str], Dict[Device, Any]]:
|
|
63
69
|
pass
|
|
64
70
|
|
|
65
71
|
@abc.abstractmethod
|
|
@@ -69,15 +75,10 @@ class Fetcher(abc.ABC):
|
|
|
69
75
|
pass
|
|
70
76
|
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def fetch(self, devices: List[Device],
|
|
78
|
-
files_to_download: Dict[str, List[str]] = None,
|
|
79
|
-
processes: int = 1, max_slots: int = 0):
|
|
80
|
-
raise NotImplementedError()
|
|
78
|
+
def get_fetcher() -> Fetcher:
|
|
79
|
+
connectors = fetcher_connector.get_all()
|
|
80
|
+
fetcher, _ = get_connector_from_config("fetcher", connectors)
|
|
81
|
+
return fetcher
|
|
81
82
|
|
|
82
83
|
|
|
83
84
|
class DeployDriver(abc.ABC):
|
|
@@ -86,11 +87,11 @@ class DeployDriver(abc.ABC):
|
|
|
86
87
|
pass
|
|
87
88
|
|
|
88
89
|
@abc.abstractmethod
|
|
89
|
-
def apply_deploy_rulebook(self, hw, cmd_paths, do_finalize=True, do_commit=True):
|
|
90
|
+
def apply_deploy_rulebook(self, hw: HardwareView, cmd_paths, do_finalize=True, do_commit=True):
|
|
90
91
|
pass
|
|
91
92
|
|
|
92
93
|
@abc.abstractmethod
|
|
93
|
-
def build_configuration_cmdlist(self, hw, do_finalize=True, do_commit=True):
|
|
94
|
+
def build_configuration_cmdlist(self, hw: HardwareView, do_finalize=True, do_commit=True):
|
|
94
95
|
pass
|
|
95
96
|
|
|
96
97
|
@abc.abstractmethod
|
|
@@ -98,18 +99,10 @@ class DeployDriver(abc.ABC):
|
|
|
98
99
|
pass
|
|
99
100
|
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def apply_deploy_rulebook(self, hw, cmd_paths, do_finalize=True, do_commit=True):
|
|
106
|
-
NotImplementedError()
|
|
107
|
-
|
|
108
|
-
def build_configuration_cmdlist(self, hw, do_finalize=True, do_commit=True):
|
|
109
|
-
NotImplementedError()
|
|
110
|
-
|
|
111
|
-
def build_exit_cmdlist(self, hw):
|
|
112
|
-
raise NotImplementedError()
|
|
102
|
+
def get_deployer() -> DeployDriver:
|
|
103
|
+
connectors = fetcher_connector.get_all()
|
|
104
|
+
deployer, _ = get_connector_from_config("deployer", connectors)
|
|
105
|
+
return deployer
|
|
113
106
|
|
|
114
107
|
|
|
115
108
|
# ===
|
|
@@ -30,7 +30,7 @@ from annet.annlib import jsontools
|
|
|
30
30
|
from annet.annlib.rbparser import platform
|
|
31
31
|
from annet.annlib.rbparser.acl import compile_acl_text
|
|
32
32
|
from annet.cli_args import DeployOptions, GenOptions, ShowGenOptions
|
|
33
|
-
from annet.deploy import
|
|
33
|
+
from annet.deploy import scrub_config, get_fetcher
|
|
34
34
|
from annet.filtering import Filterer
|
|
35
35
|
from annet.generators import (
|
|
36
36
|
BaseGenerator,
|
|
@@ -407,7 +407,7 @@ def old_new(
|
|
|
407
407
|
if do_files_download and config == "running":
|
|
408
408
|
files_to_download = _get_files_to_download(devices, gens)
|
|
409
409
|
devices_with_files = [device for device in devices if device in files_to_download]
|
|
410
|
-
fetcher =
|
|
410
|
+
fetcher = get_fetcher()
|
|
411
411
|
fetched_packages, failed_packages = fetcher.fetch_packages(devices_with_files)
|
|
412
412
|
|
|
413
413
|
ctx = OldNewDeviceContext(
|
|
@@ -808,7 +808,7 @@ def _old_resolve_running(config: str, devices: List[Device]) -> Tuple[Dict[Devic
|
|
|
808
808
|
global live_configs # pylint: disable=global-statement
|
|
809
809
|
if live_configs is None:
|
|
810
810
|
# предварительно прочесть все конфиги прямо по ssh
|
|
811
|
-
fetcher =
|
|
811
|
+
fetcher = get_fetcher()
|
|
812
812
|
running, failed_running = fetcher.fetch(devices)
|
|
813
813
|
else:
|
|
814
814
|
running, failed_running = live_configs # pylint: disable=unpacking-non-sequence
|
|
@@ -826,7 +826,7 @@ def _old_resolve_files(config: str,
|
|
|
826
826
|
files_to_download = _get_files_to_download(devices, gens)
|
|
827
827
|
devices_with_files = [device for device in devices if device in files_to_download]
|
|
828
828
|
if devices_with_files:
|
|
829
|
-
fetcher =
|
|
829
|
+
fetcher = get_fetcher()
|
|
830
830
|
downloaded_files, failed_files = fetcher.fetch(devices_with_files,
|
|
831
831
|
files_to_download=files_to_download)
|
|
832
832
|
return downloaded_files, failed_files
|
|
@@ -37,8 +37,20 @@ from annet.annlib.lib import ( # pylint: disable=unused-import
|
|
|
37
37
|
from contextlog import get_logger
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
_HOMEDIR_PATH: Optional[str] = None # defaults to ~/.annet
|
|
41
|
+
_TEMPLATE_CONTEXT_PATH: Optional[str] = None # defaults to annet/configs/context.yml
|
|
42
|
+
_DEFAULT_CONTEXT_PATH: Optional[str] = None # defaults to ~/.annet/context.yml
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_homedir_path() -> str:
|
|
46
|
+
if _HOMEDIR_PATH is None:
|
|
47
|
+
set_homedir_path("~/.annet/")
|
|
48
|
+
return _HOMEDIR_PATH
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def set_homedir_path(path: str) -> None:
|
|
52
|
+
global _HOMEDIR_PATH # pylint: disable=global-statement
|
|
53
|
+
_HOMEDIR_PATH = path
|
|
42
54
|
|
|
43
55
|
|
|
44
56
|
def get_template_context_path() -> str:
|
|
@@ -63,6 +75,11 @@ def set_default_context_path(path: str) -> None:
|
|
|
63
75
|
_DEFAULT_CONTEXT_PATH = path
|
|
64
76
|
|
|
65
77
|
|
|
78
|
+
def get_default_log_dest() -> str:
|
|
79
|
+
homedir = get_homedir_path()
|
|
80
|
+
return os.path.join(homedir, "deploy/")
|
|
81
|
+
|
|
82
|
+
|
|
66
83
|
@lru_cache(maxsize=1)
|
|
67
84
|
def _get_template_context():
|
|
68
85
|
with open(get_template_context_path()) as f:
|