annet 0.14.8__tar.gz → 0.14.10__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 (140) hide show
  1. {annet-0.14.8/annet.egg-info → annet-0.14.10}/PKG-INFO +1 -1
  2. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/manufacturer.py +1 -12
  3. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/models.py +21 -4
  4. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/query.py +3 -0
  5. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/v24/storage.py +2 -2
  6. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/v37/storage.py +13 -9
  7. {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/views/dump.py +8 -0
  8. {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/syntax.py +1 -1
  9. {annet-0.14.8 → annet-0.14.10}/annet/argparse.py +6 -2
  10. {annet-0.14.8 → annet-0.14.10}/annet/cli.py +113 -23
  11. {annet-0.14.8 → annet-0.14.10}/annet/cli_args.py +55 -57
  12. {annet-0.14.8 → annet-0.14.10}/annet/executor.py +1 -5
  13. {annet-0.14.8 → annet-0.14.10}/annet/gen.py +3 -0
  14. {annet-0.14.8 → annet-0.14.10}/annet/implicit.py +8 -0
  15. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/aruba/ap_env.py +1 -6
  16. {annet-0.14.8 → annet-0.14.10}/annet/storage.py +4 -1
  17. {annet-0.14.8 → annet-0.14.10/annet.egg-info}/PKG-INFO +1 -1
  18. {annet-0.14.8 → annet-0.14.10}/AUTHORS +0 -0
  19. {annet-0.14.8 → annet-0.14.10}/LICENSE +0 -0
  20. {annet-0.14.8 → annet-0.14.10}/MANIFEST.in +0 -0
  21. {annet-0.14.8 → annet-0.14.10}/README.md +0 -0
  22. {annet-0.14.8 → annet-0.14.10}/annet/__init__.py +0 -0
  23. {annet-0.14.8 → annet-0.14.10}/annet/adapters/__init__.py +0 -0
  24. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/__init__.py +0 -0
  25. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/__init__.py +0 -0
  26. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/client.py +0 -0
  27. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/status_client.py +0 -0
  28. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/storage_opts.py +0 -0
  29. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/provider.py +0 -0
  30. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/v24/__init__.py +0 -0
  31. {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/v37/__init__.py +0 -0
  32. {annet-0.14.8 → annet-0.14.10}/annet/annet.py +0 -0
  33. {annet-0.14.8 → annet-0.14.10}/annet/annlib/__init__.py +0 -0
  34. {annet-0.14.8 → annet-0.14.10}/annet/annlib/command.py +0 -0
  35. {annet-0.14.8 → annet-0.14.10}/annet/annlib/diff.py +0 -0
  36. {annet-0.14.8 → annet-0.14.10}/annet/annlib/errors.py +0 -0
  37. {annet-0.14.8 → annet-0.14.10}/annet/annlib/filter_acl.py +0 -0
  38. {annet-0.14.8 → annet-0.14.10}/annet/annlib/jsontools.py +0 -0
  39. {annet-0.14.8 → annet-0.14.10}/annet/annlib/lib.py +0 -0
  40. {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/__init__.py +0 -0
  41. {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/db.py +0 -0
  42. {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/devdb/__init__.py +0 -0
  43. {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
  44. {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/views/__init__.py +0 -0
  45. {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/views/hardware.py +0 -0
  46. {annet-0.14.8 → annet-0.14.10}/annet/annlib/output.py +0 -0
  47. {annet-0.14.8 → annet-0.14.10}/annet/annlib/patching.py +0 -0
  48. {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/__init__.py +0 -0
  49. {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/acl.py +0 -0
  50. {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/deploying.py +0 -0
  51. {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/ordering.py +0 -0
  52. {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/platform.py +0 -0
  53. {annet-0.14.8 → annet-0.14.10}/annet/annlib/rulebook/__init__.py +0 -0
  54. {annet-0.14.8 → annet-0.14.10}/annet/annlib/rulebook/common.py +0 -0
  55. {annet-0.14.8 → annet-0.14.10}/annet/annlib/tabparser.py +0 -0
  56. {annet-0.14.8 → annet-0.14.10}/annet/annlib/types.py +0 -0
  57. {annet-0.14.8 → annet-0.14.10}/annet/api/__init__.py +0 -0
  58. {annet-0.14.8 → annet-0.14.10}/annet/configs/context.yml +0 -0
  59. {annet-0.14.8 → annet-0.14.10}/annet/configs/logging.yaml +0 -0
  60. {annet-0.14.8 → annet-0.14.10}/annet/connectors.py +0 -0
  61. {annet-0.14.8 → annet-0.14.10}/annet/deploy.py +0 -0
  62. {annet-0.14.8 → annet-0.14.10}/annet/diff.py +0 -0
  63. {annet-0.14.8 → annet-0.14.10}/annet/filtering.py +0 -0
  64. {annet-0.14.8 → annet-0.14.10}/annet/generators/__init__.py +0 -0
  65. {annet-0.14.8 → annet-0.14.10}/annet/generators/base.py +0 -0
  66. {annet-0.14.8 → annet-0.14.10}/annet/generators/common/__init__.py +0 -0
  67. {annet-0.14.8 → annet-0.14.10}/annet/generators/common/initial.py +0 -0
  68. {annet-0.14.8 → annet-0.14.10}/annet/generators/entire.py +0 -0
  69. {annet-0.14.8 → annet-0.14.10}/annet/generators/exceptions.py +0 -0
  70. {annet-0.14.8 → annet-0.14.10}/annet/generators/jsonfragment.py +0 -0
  71. {annet-0.14.8 → annet-0.14.10}/annet/generators/partial.py +0 -0
  72. {annet-0.14.8 → annet-0.14.10}/annet/generators/perf.py +0 -0
  73. {annet-0.14.8 → annet-0.14.10}/annet/generators/ref.py +0 -0
  74. {annet-0.14.8 → annet-0.14.10}/annet/generators/result.py +0 -0
  75. {annet-0.14.8 → annet-0.14.10}/annet/hardware.py +0 -0
  76. {annet-0.14.8 → annet-0.14.10}/annet/lib.py +0 -0
  77. {annet-0.14.8 → annet-0.14.10}/annet/output.py +0 -0
  78. {annet-0.14.8 → annet-0.14.10}/annet/parallel.py +0 -0
  79. {annet-0.14.8 → annet-0.14.10}/annet/patching.py +0 -0
  80. {annet-0.14.8 → annet-0.14.10}/annet/reference.py +0 -0
  81. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/__init__.py +0 -0
  82. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/arista/__init__.py +0 -0
  83. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/arista/iface.py +0 -0
  84. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/aruba/__init__.py +0 -0
  85. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/aruba/misc.py +0 -0
  86. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/cisco/__init__.py +0 -0
  87. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/cisco/iface.py +0 -0
  88. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/cisco/misc.py +0 -0
  89. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/cisco/vlandb.py +0 -0
  90. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/common.py +0 -0
  91. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/deploying.py +0 -0
  92. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/__init__.py +0 -0
  93. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/aaa.py +0 -0
  94. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/bgp.py +0 -0
  95. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/iface.py +0 -0
  96. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/misc.py +0 -0
  97. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/vlandb.py +0 -0
  98. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/juniper/__init__.py +0 -0
  99. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/nexus/__init__.py +0 -0
  100. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/nexus/iface.py +0 -0
  101. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/patching.py +0 -0
  102. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/ribbon/__init__.py +0 -0
  103. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/arista.deploy +0 -0
  104. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/arista.order +0 -0
  105. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/arista.rul +0 -0
  106. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/aruba.deploy +0 -0
  107. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/aruba.order +0 -0
  108. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/aruba.rul +0 -0
  109. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/cisco.deploy +0 -0
  110. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/cisco.order +0 -0
  111. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/cisco.rul +0 -0
  112. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/huawei.deploy +0 -0
  113. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/huawei.order +0 -0
  114. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/huawei.rul +0 -0
  115. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/juniper.rul +0 -0
  116. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/nexus.deploy +0 -0
  117. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/nexus.order +0 -0
  118. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/nexus.rul +0 -0
  119. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/nokia.rul +0 -0
  120. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/pc.order +0 -0
  121. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/pc.rul +0 -0
  122. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/ribbon.deploy +0 -0
  123. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/ribbon.rul +0 -0
  124. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/routeros.order +0 -0
  125. {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/routeros.rul +0 -0
  126. {annet-0.14.8 → annet-0.14.10}/annet/tabparser.py +0 -0
  127. {annet-0.14.8 → annet-0.14.10}/annet/text_term_format.py +0 -0
  128. {annet-0.14.8 → annet-0.14.10}/annet/tracing.py +0 -0
  129. {annet-0.14.8 → annet-0.14.10}/annet/types.py +0 -0
  130. {annet-0.14.8 → annet-0.14.10}/annet.egg-info/SOURCES.txt +0 -0
  131. {annet-0.14.8 → annet-0.14.10}/annet.egg-info/dependency_links.txt +0 -0
  132. {annet-0.14.8 → annet-0.14.10}/annet.egg-info/entry_points.txt +0 -0
  133. {annet-0.14.8 → annet-0.14.10}/annet.egg-info/requires.txt +0 -0
  134. {annet-0.14.8 → annet-0.14.10}/annet.egg-info/top_level.txt +0 -0
  135. {annet-0.14.8 → annet-0.14.10}/annet_generators/__init__.py +0 -0
  136. {annet-0.14.8 → annet-0.14.10}/annet_generators/example/__init__.py +0 -0
  137. {annet-0.14.8 → annet-0.14.10}/annet_generators/example/lldp.py +0 -0
  138. {annet-0.14.8 → annet-0.14.10}/requirements.txt +0 -0
  139. {annet-0.14.8 → annet-0.14.10}/setup.cfg +0 -0
  140. {annet-0.14.8 → annet-0.14.10}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.14.8
3
+ Version: 0.14.10
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -25,8 +25,6 @@ def get_hw(manufacturer: str, model: str, platform_name: str):
25
25
  model = model.replace("MSN", "SN", 1)
26
26
  vendor = manufacturer + " " + model
27
27
  hw = HardwareView(_VENDORS.get(vendor.lower(), vendor), platform_name)
28
- if not hw:
29
- raise ValueError(f"unsupported manufacturer {manufacturer}")
30
28
  return hw
31
29
 
32
30
 
@@ -47,13 +45,4 @@ def get_breed(manufacturer: str, model: str):
47
45
  return "adva8"
48
46
  elif manufacturer == "Arista":
49
47
  return "eos4"
50
- raise ValueError(f"unsupported manufacturer {manufacturer}")
51
-
52
-
53
- def is_supported(manufacturer: str) -> bool:
54
- if manufacturer not in (
55
- "Huawei", "Mellanox", "Juniper", "Cisco", "Adva", "Arista",
56
- ):
57
- logger.warning("Unsupported manufacturer `%s`", manufacturer)
58
- return False
59
- return True
48
+ return ""
@@ -2,15 +2,20 @@ from dataclasses import dataclass, field
2
2
  from datetime import datetime
3
3
  from typing import List, Optional, Any, Dict
4
4
 
5
+ from annet.annlib.netdev.views.dump import DumpableView
5
6
  from annet.annlib.netdev.views.hardware import HardwareView
6
7
  from annet.storage import Storage
7
8
 
8
9
 
9
10
  @dataclass
10
- class Entity:
11
+ class Entity(DumpableView):
11
12
  id: int
12
13
  name: str
13
14
 
15
+ @property
16
+ def _dump__list_key(self):
17
+ return self.name
18
+
14
19
 
15
20
  @dataclass
16
21
  class Label:
@@ -32,15 +37,19 @@ class DeviceType:
32
37
 
33
38
 
34
39
  @dataclass
35
- class DeviceIp:
40
+ class DeviceIp(DumpableView):
36
41
  id: int
37
42
  display: str
38
43
  address: str
39
44
  family: int
40
45
 
46
+ @property
47
+ def _dump__list_key(self):
48
+ return self.address
49
+
41
50
 
42
51
  @dataclass
43
- class Prefix:
52
+ class Prefix(DumpableView):
44
53
  id: int
45
54
  prefix: str
46
55
  site: Entity | None
@@ -55,9 +64,13 @@ class Prefix:
55
64
  last_updated: datetime
56
65
  description: str | None = ""
57
66
 
67
+ @property
68
+ def _dump__list_key(self):
69
+ return self.prefix
70
+
58
71
 
59
72
  @dataclass
60
- class IpAddress:
73
+ class IpAddress(DumpableView):
61
74
  id: int
62
75
  assigned_object_id: int
63
76
  display: str
@@ -70,6 +83,10 @@ class IpAddress:
70
83
  prefix: Optional[Prefix] = None
71
84
  vrf: Optional[Entity] = None
72
85
 
86
+ @property
87
+ def _dump__list_key(self):
88
+ return self.address
89
+
73
90
 
74
91
  @dataclass
75
92
  class InterfaceConnectedEndpoint(Entity):
@@ -21,3 +21,6 @@ class NetboxQuery(Query):
21
21
  def globs(self):
22
22
  # We process every query host as a glob
23
23
  return self.query
24
+
25
+ def is_empty(self) -> bool:
26
+ return len(self.query) == 0
@@ -6,10 +6,11 @@ from annetbox.v24.client_sync import NetboxV24
6
6
 
7
7
  from annet.adapters.netbox.common import models
8
8
  from annet.adapters.netbox.common.manufacturer import (
9
- is_supported, get_hw, get_breed,
9
+ get_hw, get_breed,
10
10
  )
11
11
  from annet.adapters.netbox.common.query import NetboxQuery
12
12
  from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
13
+ from annet.annlib.netdev.views.hardware import HardwareView
13
14
  from annet.storage import Storage
14
15
 
15
16
  logger = getLogger(__name__)
@@ -162,7 +163,6 @@ class NetboxStorageV24(Storage):
162
163
  device
163
164
  for device in self.netbox.dcim_all_devices().results
164
165
  if _match_query(query, device)
165
- if is_supported(device.device_type.manufacturer.name)
166
166
  ]
167
167
 
168
168
  def _load_interfaces(self, device_ids: List[int]) -> List[
@@ -10,7 +10,7 @@ from annetbox.v37.client_sync import NetboxV37
10
10
 
11
11
  from annet.adapters.netbox.common import models
12
12
  from annet.adapters.netbox.common.manufacturer import (
13
- is_supported, get_hw, get_breed,
13
+ get_hw, get_breed,
14
14
  )
15
15
  from annet.adapters.netbox.common.query import NetboxQuery
16
16
  from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
@@ -43,20 +43,25 @@ def extend_device(
43
43
  storage: Storage,
44
44
  ) -> models.NetboxDevice:
45
45
  platform_name: str = ""
46
+ breed: str = ""
47
+ hw = HardwareView("", "")
46
48
  if device.platform:
47
49
  platform_name = device.platform.name
48
- res = extend_device_base(
49
- device=device,
50
- interfaces=interfaces,
51
- breed=get_breed(
50
+ if device.device_type and device.device_type.manufacturer:
51
+ breed = get_breed(
52
52
  device.device_type.manufacturer.name,
53
53
  device.device_type.model,
54
- ),
55
- hw=get_hw(
54
+ )
55
+ hw = get_hw(
56
56
  device.device_type.manufacturer.name,
57
57
  device.device_type.model,
58
58
  platform_name,
59
- ),
59
+ )
60
+ res = extend_device_base(
61
+ device=device,
62
+ interfaces=interfaces,
63
+ breed=breed,
64
+ hw=hw,
60
65
  storage=storage,
61
66
  )
62
67
  res.neighbours = neighbours
@@ -146,7 +151,6 @@ class NetboxStorageV37(Storage):
146
151
  name__ic=query.globs,
147
152
  ).results
148
153
  if _match_query(query, device)
149
- if is_supported(device.device_type.manufacturer.name)
150
154
  ]
151
155
 
152
156
  def _extend_interfaces(self, interfaces: List[models.Interface]) -> List[models.Interface]:
@@ -73,6 +73,14 @@ class DumpableView:
73
73
 
74
74
  if isinstance(value, DumpableView):
75
75
  ret += value.dump(prefix, seen=seen) # pylint: disable=no-member
76
+ elif isinstance(value, dict):
77
+ for k, v in value.items():
78
+ ret.extend(self.__dump_value(f"{prefix}[{repr(k)}]", v, seen))
79
+ elif isinstance(value, (list, tuple)):
80
+ for i, v in enumerate(value):
81
+ name = getattr(v, "_dump__list_key", None)
82
+ name = repr(name) if name is not None else str(i)
83
+ ret.extend(self.__dump_value(f"{prefix}[{name}]", v, seen))
76
84
  else:
77
85
  fmt = repr(value)
78
86
  vtype = type(value)
@@ -31,7 +31,7 @@ def compile_row_regexp(row, flags=0):
31
31
  row = row[:-3]
32
32
  elif "~/" in row:
33
33
  # ~/{regex}/ -> {regex}, () не нужны поскольку уже (?:) - non-captured
34
- row = re.sub(r"~/([^/]+)/", r"\1", row)
34
+ row = re.sub(r"~/(((?!~/).)+)/", r"\1", row)
35
35
  else:
36
36
  row += r"(?:\s|$)"
37
37
  row = re.sub(r"\s+", r"\\s+", row)
@@ -370,7 +370,7 @@ class ArgParser(argparse.ArgumentParser):
370
370
  yield from _get_meta(func).opts
371
371
 
372
372
 
373
- def subcommand(*arg_list: Union[Arg, Type[ArgGroup]], parent: Callable = None):
373
+ def subcommand(*arg_list: Union[Arg, Type[ArgGroup]], parent: Callable = None, is_group: bool = False):
374
374
  """
375
375
  декоратор, задающий cli-аргументы подпрограммы
376
376
 
@@ -382,7 +382,7 @@ def subcommand(*arg_list: Union[Arg, Type[ArgGroup]], parent: Callable = None):
382
382
  Связь аргументов происходит только по порядковому номеру, каждый аргумент subcommand становится аргументом функции.
383
383
  Функция вызывается только с позиционными аргументами, всегда с одним и тем же количеством аргументов.
384
384
 
385
- Для создания более одного уровня команд используeтся агрумент parent
385
+ Для создания более одного уровня команд используется аргумент parent
386
386
 
387
387
  Пример: 'ann some thing' - вызовет some_thing()
388
388
 
@@ -397,6 +397,10 @@ def subcommand(*arg_list: Union[Arg, Type[ArgGroup]], parent: Callable = None):
397
397
  """
398
398
  def _amend_func(func):
399
399
  meta = _get_meta(func)
400
+ if is_group:
401
+ @functools.wraps(func)
402
+ def func():
403
+ meta.parser.print_help()
400
404
  cmd_name = func.__name__
401
405
  if parent:
402
406
  parentprefix = parent.__name__ + "_"
@@ -1,25 +1,29 @@
1
1
  import argparse
2
+ import itertools
3
+ import json
2
4
  import operator
3
5
  import os
4
6
  import platform
5
7
  import subprocess
6
8
  import shutil
9
+ import sys
7
10
  from contextlib import ExitStack, contextmanager
8
11
  from typing import Tuple, Iterable
9
12
 
13
+ import tabulate
10
14
  import yaml
11
15
  from contextlog import get_logger
12
16
  from valkit.python import valid_logging_level
13
17
 
14
18
  from annet.deploy import driver_connector, fetcher_connector
15
- from annet import api, cli_args, filtering
19
+ from annet import api, cli_args, filtering, generators
16
20
  from annet.api import collapse_texts, Deployer
17
21
  from annet.argparse import ArgParser, subcommand
18
22
  from annet.diff import gen_sort_diff
19
23
  from annet.gen import Loader, old_raw
20
24
  from annet.lib import get_context_path, repair_context_file
21
25
  from annet.output import output_driver_connector, OutputDriver
22
- from annet.storage import get_storage
26
+ from annet.storage import get_storage, Device
23
27
 
24
28
 
25
29
  def fill_base_args(parser: ArgParser, pkg_name: str, logging_config: str):
@@ -60,19 +64,25 @@ def _gen_current_items(
60
64
 
61
65
 
62
66
  @contextmanager
63
- def get_loader(gen_args: cli_args.GenOptions, args: cli_args.QueryOptions):
67
+ def get_loader(gen_args: cli_args.GenOptions, args: cli_args.QueryOptionsBase):
64
68
  exit_stack = ExitStack()
65
69
  storages = []
66
70
  with exit_stack:
67
71
  connector, connector_opts = get_storage()
68
72
  storage_opts = connector.opts().parse_params(connector_opts, args)
69
73
  storages.append(exit_stack.enter_context(connector.storage()(storage_opts)))
70
- yield Loader(*storages, args=gen_args)
74
+ yield Loader(*storages, args=gen_args, no_empty_warning=args.query.is_empty())
71
75
 
72
76
 
73
- @subcommand(cli_args.QueryOptions, cli_args.opt_config, cli_args.FileOutOptions)
77
+ @subcommand(is_group=True)
78
+ def show():
79
+ """ A group of commands for showing parameters/configurations/data from deivces and data sources """
80
+ pass
81
+
82
+
83
+ @subcommand(cli_args.QueryOptions, cli_args.opt_config, cli_args.FileOutOptions, parent=show)
74
84
  def show_current(args: cli_args.QueryOptions, config, arg_out: cli_args.FileOutOptions) -> None:
75
- """ Показать текущий конфиг устройств """
85
+ """ Show current devices' configuration """
76
86
  gen_args = cli_args.GenOptions(args, no_acl=True)
77
87
  output_driver = output_driver_connector.get()
78
88
  with get_loader(gen_args, args) as loader:
@@ -89,9 +99,90 @@ def show_current(args: cli_args.QueryOptions, config, arg_out: cli_args.FileOutO
89
99
  output_driver.write_output(arg_out, items, len(loader.devices))
90
100
 
91
101
 
102
+ @subcommand(cli_args.QueryOptions, cli_args.FileOutOptions, parent=show)
103
+ def show_device_dump(args: cli_args.QueryOptions, arg_out: cli_args.FileOutOptions):
104
+ """ Show a dump of network devices' structure """
105
+ def _show_device_dump_items(devices):
106
+ for device in devices:
107
+ get_logger(host=device.hostname) # add hostname into context
108
+ if hasattr(device, "dump"):
109
+ yield (
110
+ device.hostname,
111
+ "\n".join(device.dump("device")),
112
+ False,
113
+ )
114
+ else:
115
+ get_logger().warning("method `dump` not implemented for %s", type(device))
116
+ arg_gens = cli_args.GenOptions(arg_out, args)
117
+ with get_loader(arg_gens, args) as loader:
118
+ if not loader.devices:
119
+ get_logger().error("No devices found for %s", args.query)
120
+ output_driver_connector.get().write_output(
121
+ arg_out,
122
+ _show_device_dump_items(loader.devices),
123
+ len(loader.device_ids),
124
+ )
125
+
126
+
127
+ @subcommand(cli_args.ShowGeneratorsOptions, parent=show)
128
+ def show_generators(args: cli_args.ShowGeneratorsOptions):
129
+ """ List applicable generators (for a device if query is set) """
130
+ arg_gens = cli_args.GenOptions(args)
131
+ with get_loader(arg_gens, args) as loader:
132
+ device: Device | None = None
133
+ devices = loader.devices
134
+ if len(devices) == 1:
135
+ device = devices[0]
136
+ elif len(devices) > 1:
137
+ get_logger().error("cannot show generators for more than one device at once")
138
+ sys.exit(1)
139
+ elif len(devices) == 0 and not args.query.is_empty():
140
+ # the error message will be logged in get_loader()
141
+ sys.exit(1)
142
+
143
+ if not device:
144
+ found_generators = loader.iter_all_gens()
145
+ else:
146
+ found_generators = []
147
+ gens = loader.resolve_gens(loader.devices)
148
+ for g in gens.partial[device]:
149
+ acl_func = g.acl_safe if args.acl_safe else g.acl
150
+ if g.supports_device(device) and acl_func(device):
151
+ found_generators.append(g)
152
+ for g in gens.entire[device]:
153
+ if g.supports_device(device) and g.path(device):
154
+ found_generators.append(g)
155
+ for g in gens.json_fragment[device]:
156
+ if g.supports_device(device) and g.path(device) and g.acl(device):
157
+ found_generators.append(g)
158
+
159
+ output_data = []
160
+ for g in found_generators:
161
+ output_data.append({
162
+ "name": g.__class__.__name__,
163
+ "type": g.TYPE,
164
+ "tags": g.TAGS,
165
+ "module": g.__class__.__module__,
166
+ "description": generators.get_description(g.__class__),
167
+ })
168
+
169
+ if args.format == "json":
170
+ print(json.dumps(output_data))
171
+
172
+ elif args.format == "text":
173
+ keyfunc = operator.itemgetter("type")
174
+ for gen_type, gens in itertools.groupby(sorted(output_data, key=keyfunc, reverse=True), keyfunc):
175
+ print(tabulate.tabulate(
176
+ [(g["name"], ", ".join(g["tags"]), g["module"], g["description"]) for g in gens],
177
+ [f"{gen_type}-Class", "Tags", "Module", "Description"],
178
+ tablefmt="orgtbl",
179
+ ))
180
+ print()
181
+
182
+
92
183
  @subcommand(cli_args.ShowGenOptions)
93
184
  def gen(args: cli_args.ShowGenOptions):
94
- """ Сгенерировать конфиг для устройств """
185
+ """ Generate configuration for devices """
95
186
  with get_loader(args, args) as loader:
96
187
  (success, fail) = api.gen(args, loader)
97
188
 
@@ -110,7 +201,7 @@ def gen(args: cli_args.ShowGenOptions):
110
201
 
111
202
  @subcommand(cli_args.ShowDiffOptions)
112
203
  def diff(args: cli_args.ShowDiffOptions):
113
- """ Сгенерировать конфиг для устройств и показать дифф по рулбуку с текущим """
204
+ """ Generate configuration for devices and show a diff with current configuration using the rulebook """
114
205
  with get_loader(args, args) as loader:
115
206
  filterer = filtering.filterer_connector.get()
116
207
  device_ids = loader.device_ids
@@ -123,7 +214,7 @@ def diff(args: cli_args.ShowDiffOptions):
123
214
 
124
215
  @subcommand(cli_args.ShowPatchOptions)
125
216
  def patch(args: cli_args.ShowPatchOptions):
126
- """ Сгенерировать конфиг для устройств и сформировать патч """
217
+ """ Generate configuration patch for devices """
127
218
  with get_loader(args, args) as loader:
128
219
  (success, fail) = api.patch(args, loader)
129
220
 
@@ -138,7 +229,7 @@ def patch(args: cli_args.ShowPatchOptions):
138
229
 
139
230
  @subcommand(cli_args.DeployOptions)
140
231
  def deploy(args: cli_args.DeployOptions):
141
- """ Сгенерировать конфиг для устройств и задеплоить его """
232
+ """ Generate and deploy configuration for devices """
142
233
 
143
234
  deployer = Deployer(args)
144
235
  filterer = filtering.filterer_connector.get()
@@ -155,7 +246,7 @@ def deploy(args: cli_args.DeployOptions):
155
246
 
156
247
  @subcommand(cli_args.FileDiffOptions)
157
248
  def file_diff(args: cli_args.FileDiffOptions):
158
- """ Показать дифф по рулбуку между файлами или каталогами """
249
+ """ Generate a diff between files or directories using the rulebook """
159
250
  (success, fail) = api.file_diff(args)
160
251
  out = []
161
252
  output_driver = output_driver_connector.get()
@@ -168,7 +259,7 @@ def file_diff(args: cli_args.FileDiffOptions):
168
259
 
169
260
  @subcommand(cli_args.FilePatchOptions)
170
261
  def file_patch(args: cli_args.FilePatchOptions):
171
- """ Сформировать патч для файлов или каталогов """
262
+ """ Generate configuration patch for files or directories """
172
263
  (success, fail) = api.file_patch(args)
173
264
  out = []
174
265
  output_driver = output_driver_connector.get()
@@ -178,26 +269,27 @@ def file_patch(args: cli_args.FilePatchOptions):
178
269
  output_driver.write_output(args, out, len(out))
179
270
 
180
271
 
181
- @subcommand()
272
+ @subcommand(is_group=True)
182
273
  def context():
183
- """ Операции для управления файлом контекста.
274
+ """ A group of commands for manipulating context.
184
275
 
185
- По-умолчанию находится в '~/.annushka/context.yml', либо по пути в переменной окружения ANN_CONTEXT_CONFIG_PATH.
276
+ By default, the context file is located in '~/.annushka/context.yml',
277
+ but it can be set with the ANN_CONTEXT_CONFIG_PATH environment variable.
186
278
  """
187
279
  context_touch()
188
280
 
189
281
 
190
282
  @subcommand(parent=context)
191
283
  def context_touch():
192
- """ Вывести путь к файлу контекста и, при отсутствии, создать его и наполнить данными (команда по-умолчанию) """
284
+ """ Show the context file path, and if the file is not present, create it with the default configuration """
193
285
  print(get_context_path(touch=True))
194
286
 
195
287
 
196
288
  @subcommand(cli_args.SelectContext, parent=context)
197
289
  def context_set_context(args: cli_args.SelectContext):
198
- """ Задать текущий активный контекст по имени в конфигурации
290
+ """ Set the current active context.
199
291
 
200
- Выбранный контекст будет использоваться по-умолчанию при незаданной переменной окружения ANN_SELECTED_CONTEXT
292
+ The selected context is used by default unless the environment variable ANN_SELECTED_CONTEXT is set
201
293
  """
202
294
  with open(path := get_context_path(touch=True)) as f:
203
295
  data = yaml.safe_load(f)
@@ -211,12 +303,10 @@ def context_set_context(args: cli_args.SelectContext):
211
303
 
212
304
  @subcommand(parent=context)
213
305
  def context_edit():
214
- """ Открыть файл конфигурации контекста в редакторе из переменной окружения EDITOR
306
+ """ Open the context file using an editor from the EDITOR environment variable.
215
307
 
216
- Если переменная окружения EDITOR не задана,
217
- для Windows пытаемся открыть файл средствами ОС, для остальных случаев пытаемся открыть в vi
308
+ If the EDITOR variable is not set, default variables are: "notepad.exe" for Windows and "vi" otherwise
218
309
  """
219
- editor = ""
220
310
  if e := os.getenv("EDITOR"):
221
311
  editor = e
222
312
  elif platform.system() == "Windows":
@@ -232,5 +322,5 @@ def context_edit():
232
322
 
233
323
  @subcommand(parent=context)
234
324
  def context_repair():
235
- """ Попытаться исправить расхождения в формате файла контекста после изменении версии """
325
+ """ Try to fix the context file's structure if it was generated for the older versions of annet """
236
326
  repair_context_file()