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.

Files changed (144) hide show
  1. {annet-0.15.1/annet.egg-info → annet-0.15.3}/PKG-INFO +1 -1
  2. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/storage_opts.py +5 -4
  3. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/provider.py +11 -1
  4. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/v37/storage.py +22 -1
  5. {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/devdb/data/devdb.json +23 -19
  6. {annet-0.15.1 → annet-0.15.3}/annet/annlib/tabparser.py +3 -2
  7. {annet-0.15.1 → annet-0.15.3}/annet/api/__init__.py +5 -5
  8. {annet-0.15.1 → annet-0.15.3}/annet/argparse.py +2 -0
  9. {annet-0.15.1 → annet-0.15.3}/annet/cli.py +6 -6
  10. {annet-0.15.1 → annet-0.15.3}/annet/cli_args.py +8 -1
  11. annet-0.15.3/annet/connectors.py +143 -0
  12. {annet-0.15.1 → annet-0.15.3}/annet/deploy.py +21 -28
  13. {annet-0.15.1 → annet-0.15.3}/annet/filtering.py +1 -0
  14. {annet-0.15.1 → annet-0.15.3}/annet/gen.py +4 -4
  15. {annet-0.15.1 → annet-0.15.3}/annet/hardware.py +1 -0
  16. {annet-0.15.1 → annet-0.15.3}/annet/lib.py +19 -2
  17. {annet-0.15.1 → annet-0.15.3}/annet/output.py +10 -2
  18. {annet-0.15.1 → annet-0.15.3}/annet/storage.py +6 -17
  19. {annet-0.15.1 → annet-0.15.3/annet.egg-info}/PKG-INFO +1 -1
  20. {annet-0.15.1 → annet-0.15.3}/annet.egg-info/entry_points.txt +3 -0
  21. {annet-0.15.1 → annet-0.15.3}/setup.py +3 -0
  22. annet-0.15.1/annet/connectors.py +0 -78
  23. {annet-0.15.1 → annet-0.15.3}/AUTHORS +0 -0
  24. {annet-0.15.1 → annet-0.15.3}/LICENSE +0 -0
  25. {annet-0.15.1 → annet-0.15.3}/MANIFEST.in +0 -0
  26. {annet-0.15.1 → annet-0.15.3}/README.md +0 -0
  27. {annet-0.15.1 → annet-0.15.3}/annet/__init__.py +0 -0
  28. {annet-0.15.1 → annet-0.15.3}/annet/adapters/__init__.py +0 -0
  29. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/__init__.py +0 -0
  30. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/__init__.py +0 -0
  31. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/client.py +0 -0
  32. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/manufacturer.py +0 -0
  33. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/models.py +0 -0
  34. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/query.py +0 -0
  35. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/common/status_client.py +0 -0
  36. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/v24/__init__.py +0 -0
  37. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/v24/storage.py +0 -0
  38. {annet-0.15.1 → annet-0.15.3}/annet/adapters/netbox/v37/__init__.py +0 -0
  39. {annet-0.15.1 → annet-0.15.3}/annet/annet.py +0 -0
  40. {annet-0.15.1 → annet-0.15.3}/annet/annlib/__init__.py +0 -0
  41. {annet-0.15.1 → annet-0.15.3}/annet/annlib/command.py +0 -0
  42. {annet-0.15.1 → annet-0.15.3}/annet/annlib/diff.py +0 -0
  43. {annet-0.15.1 → annet-0.15.3}/annet/annlib/errors.py +0 -0
  44. {annet-0.15.1 → annet-0.15.3}/annet/annlib/filter_acl.py +0 -0
  45. {annet-0.15.1 → annet-0.15.3}/annet/annlib/jsontools.py +0 -0
  46. {annet-0.15.1 → annet-0.15.3}/annet/annlib/lib.py +0 -0
  47. {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/__init__.py +0 -0
  48. {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/db.py +0 -0
  49. {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/devdb/__init__.py +0 -0
  50. {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/views/__init__.py +0 -0
  51. {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/views/dump.py +0 -0
  52. {annet-0.15.1 → annet-0.15.3}/annet/annlib/netdev/views/hardware.py +0 -0
  53. {annet-0.15.1 → annet-0.15.3}/annet/annlib/output.py +0 -0
  54. {annet-0.15.1 → annet-0.15.3}/annet/annlib/patching.py +0 -0
  55. {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/__init__.py +0 -0
  56. {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/acl.py +0 -0
  57. {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/deploying.py +0 -0
  58. {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/ordering.py +0 -0
  59. {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/platform.py +0 -0
  60. {annet-0.15.1 → annet-0.15.3}/annet/annlib/rbparser/syntax.py +0 -0
  61. {annet-0.15.1 → annet-0.15.3}/annet/annlib/rulebook/__init__.py +0 -0
  62. {annet-0.15.1 → annet-0.15.3}/annet/annlib/rulebook/common.py +0 -0
  63. {annet-0.15.1 → annet-0.15.3}/annet/annlib/types.py +0 -0
  64. {annet-0.15.1 → annet-0.15.3}/annet/configs/context.yml +0 -0
  65. {annet-0.15.1 → annet-0.15.3}/annet/configs/logging.yaml +0 -0
  66. {annet-0.15.1 → annet-0.15.3}/annet/diff.py +0 -0
  67. {annet-0.15.1 → annet-0.15.3}/annet/executor.py +0 -0
  68. {annet-0.15.1 → annet-0.15.3}/annet/generators/__init__.py +0 -0
  69. {annet-0.15.1 → annet-0.15.3}/annet/generators/base.py +0 -0
  70. {annet-0.15.1 → annet-0.15.3}/annet/generators/common/__init__.py +0 -0
  71. {annet-0.15.1 → annet-0.15.3}/annet/generators/common/initial.py +0 -0
  72. {annet-0.15.1 → annet-0.15.3}/annet/generators/entire.py +0 -0
  73. {annet-0.15.1 → annet-0.15.3}/annet/generators/exceptions.py +0 -0
  74. {annet-0.15.1 → annet-0.15.3}/annet/generators/jsonfragment.py +0 -0
  75. {annet-0.15.1 → annet-0.15.3}/annet/generators/partial.py +0 -0
  76. {annet-0.15.1 → annet-0.15.3}/annet/generators/perf.py +0 -0
  77. {annet-0.15.1 → annet-0.15.3}/annet/generators/ref.py +0 -0
  78. {annet-0.15.1 → annet-0.15.3}/annet/generators/result.py +0 -0
  79. {annet-0.15.1 → annet-0.15.3}/annet/implicit.py +0 -0
  80. {annet-0.15.1 → annet-0.15.3}/annet/parallel.py +0 -0
  81. {annet-0.15.1 → annet-0.15.3}/annet/patching.py +0 -0
  82. {annet-0.15.1 → annet-0.15.3}/annet/reference.py +0 -0
  83. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/__init__.py +0 -0
  84. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/arista/__init__.py +0 -0
  85. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/arista/iface.py +0 -0
  86. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/aruba/__init__.py +0 -0
  87. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/aruba/ap_env.py +0 -0
  88. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/aruba/misc.py +0 -0
  89. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/cisco/__init__.py +0 -0
  90. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/cisco/iface.py +0 -0
  91. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/cisco/misc.py +0 -0
  92. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/cisco/vlandb.py +0 -0
  93. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/common.py +0 -0
  94. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/deploying.py +0 -0
  95. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/__init__.py +0 -0
  96. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/aaa.py +0 -0
  97. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/bgp.py +0 -0
  98. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/iface.py +0 -0
  99. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/misc.py +0 -0
  100. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/huawei/vlandb.py +0 -0
  101. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/juniper/__init__.py +0 -0
  102. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/nexus/__init__.py +0 -0
  103. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/nexus/iface.py +0 -0
  104. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/patching.py +0 -0
  105. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/ribbon/__init__.py +0 -0
  106. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/arista.deploy +0 -0
  107. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/arista.order +0 -0
  108. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/arista.rul +0 -0
  109. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/aruba.deploy +0 -0
  110. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/aruba.order +0 -0
  111. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/aruba.rul +0 -0
  112. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/cisco.deploy +0 -0
  113. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/cisco.order +0 -0
  114. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/cisco.rul +0 -0
  115. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/huawei.deploy +0 -0
  116. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/huawei.order +0 -0
  117. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/huawei.rul +0 -0
  118. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/juniper.rul +0 -0
  119. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/nexus.deploy +0 -0
  120. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/nexus.order +0 -0
  121. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/nexus.rul +0 -0
  122. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/nokia.rul +0 -0
  123. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/optixtrans.deploy +0 -0
  124. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/optixtrans.order +0 -0
  125. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/optixtrans.rul +0 -0
  126. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/pc.order +0 -0
  127. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/pc.rul +0 -0
  128. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/ribbon.deploy +0 -0
  129. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/ribbon.rul +0 -0
  130. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/routeros.order +0 -0
  131. {annet-0.15.1 → annet-0.15.3}/annet/rulebook/texts/routeros.rul +0 -0
  132. {annet-0.15.1 → annet-0.15.3}/annet/tabparser.py +0 -0
  133. {annet-0.15.1 → annet-0.15.3}/annet/text_term_format.py +0 -0
  134. {annet-0.15.1 → annet-0.15.3}/annet/tracing.py +0 -0
  135. {annet-0.15.1 → annet-0.15.3}/annet/types.py +0 -0
  136. {annet-0.15.1 → annet-0.15.3}/annet.egg-info/SOURCES.txt +0 -0
  137. {annet-0.15.1 → annet-0.15.3}/annet.egg-info/dependency_links.txt +0 -0
  138. {annet-0.15.1 → annet-0.15.3}/annet.egg-info/requires.txt +0 -0
  139. {annet-0.15.1 → annet-0.15.3}/annet.egg-info/top_level.txt +0 -0
  140. {annet-0.15.1 → annet-0.15.3}/annet_generators/__init__.py +0 -0
  141. {annet-0.15.1 → annet-0.15.3}/annet_generators/example/__init__.py +0 -0
  142. {annet-0.15.1 → annet-0.15.3}/annet_generators/example/lldp.py +0 -0
  143. {annet-0.15.1 → annet-0.15.3}/requirements.txt +0 -0
  144. {annet-0.15.1 → annet-0.15.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.15.1
3
+ Version: 0.15.3
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -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
- return cls(
13
- url=os.getenv("NETBOX_URL", "http://localhost"),
14
- token=os.getenv("NETBOX_TOKEN", "").strip(),
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": " 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.C3500": " 35\\d\\d",
17
- "Cisco.Catalyst.C3600": " 36\\d\\d",
18
- "Cisco.Catalyst.C3700": " 37\\d\\d",
19
- "Cisco.Catalyst.C4900": " 49\\d\\d",
20
- "Cisco.Catalyst.C6500": " 65\\d\\d",
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\\d+",
56
- "Huawei.Quidway.S2x": " S2\\d{3}",
57
- "Huawei.Quidway.S2x.S2300": " S23\\d\\d",
58
- "Huawei.Quidway.S2x.S2700": " S27\\d\\d",
59
- "Huawei.Quidway.S5300": " S53\\d\\d",
60
- "Huawei.Quidway.S5700": " S57\\d\\d",
61
- "Huawei.Quidway.S6700": " S67\\d\\d",
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.row
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, connector_opts = get_storage()
222
- storage_opts = connector.opts().parse_params(connector_opts, args)
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.fetcher_connector.get()
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.driver_connector.get()
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.driver_connector.get()
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 driver_connector, fetcher_connector
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, connector_opts = get_storage()
72
- storage_opts = connector.opts().parse_params(connector_opts, args)
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 = fetcher_connector.get()
237
- deploy_driver = driver_connector.get()
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 '~/.annushka/context.yml',
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="deploy/",
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, Type, Any, OrderedDict
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
- return StubFetcher
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
- return StubDeployDriver
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
- class StubFetcher(Fetcher):
73
- def fetch_packages(self, devices: List[Device],
74
- processes: int = 1, max_slots: int = 0):
75
- raise NotImplementedError()
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
- class StubDeployDriver(DeployDriver):
102
- async def bulk_deploy(self, deploy_cmds: dict, args: DeployOptions) -> DeployResult:
103
- NotImplementedError()
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
  # ===
@@ -7,6 +7,7 @@ from annet.connectors import Connector
7
7
  class _FiltererConnector(Connector["Filterer"]):
8
8
  name = "Filterer"
9
9
  ep_name = "filterer"
10
+ ep_by_group_only = "annet.connectors.filterer"
10
11
 
11
12
  def _get_default(self) -> Type["Filterer"]:
12
13
  return NopFilterer
@@ -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 fetcher_connector, scrub_config
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 = fetcher_connector.get()
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 = fetcher_connector.get()
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 = fetcher_connector.get()
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
@@ -15,6 +15,7 @@ except ImportError:
15
15
  class _HardwareConnector(Connector["HarwareProvider"]):
16
16
  name = "Hardware"
17
17
  ep_name = "hardware"
18
+ ep_by_group_only = "annet.connectors.hardware"
18
19
 
19
20
 
20
21
  hardware_connector = _HardwareConnector()
@@ -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
- _TEMPLATE_CONTEXT_PATH: Optional[str] = None
41
- _DEFAULT_CONTEXT_PATH: Optional[str] = None
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: