annet 0.0__py3-none-any.whl

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 (137) hide show
  1. annet/__init__.py +61 -0
  2. annet/adapters/__init__.py +0 -0
  3. annet/adapters/netbox/__init__.py +0 -0
  4. annet/adapters/netbox/common/__init__.py +0 -0
  5. annet/adapters/netbox/common/client.py +87 -0
  6. annet/adapters/netbox/common/manufacturer.py +62 -0
  7. annet/adapters/netbox/common/models.py +105 -0
  8. annet/adapters/netbox/common/query.py +23 -0
  9. annet/adapters/netbox/common/status_client.py +25 -0
  10. annet/adapters/netbox/common/storage_opts.py +14 -0
  11. annet/adapters/netbox/provider.py +34 -0
  12. annet/adapters/netbox/v24/__init__.py +0 -0
  13. annet/adapters/netbox/v24/api_models.py +73 -0
  14. annet/adapters/netbox/v24/client.py +59 -0
  15. annet/adapters/netbox/v24/storage.py +196 -0
  16. annet/adapters/netbox/v37/__init__.py +0 -0
  17. annet/adapters/netbox/v37/api_models.py +38 -0
  18. annet/adapters/netbox/v37/client.py +62 -0
  19. annet/adapters/netbox/v37/storage.py +149 -0
  20. annet/annet.py +25 -0
  21. annet/annlib/__init__.py +7 -0
  22. annet/annlib/command.py +49 -0
  23. annet/annlib/diff.py +158 -0
  24. annet/annlib/errors.py +8 -0
  25. annet/annlib/filter_acl.py +196 -0
  26. annet/annlib/jsontools.py +116 -0
  27. annet/annlib/lib.py +495 -0
  28. annet/annlib/netdev/__init__.py +0 -0
  29. annet/annlib/netdev/db.py +62 -0
  30. annet/annlib/netdev/devdb/__init__.py +28 -0
  31. annet/annlib/netdev/devdb/data/devdb.json +137 -0
  32. annet/annlib/netdev/views/__init__.py +0 -0
  33. annet/annlib/netdev/views/dump.py +121 -0
  34. annet/annlib/netdev/views/hardware.py +112 -0
  35. annet/annlib/output.py +246 -0
  36. annet/annlib/patching.py +533 -0
  37. annet/annlib/rbparser/__init__.py +0 -0
  38. annet/annlib/rbparser/acl.py +120 -0
  39. annet/annlib/rbparser/deploying.py +55 -0
  40. annet/annlib/rbparser/ordering.py +52 -0
  41. annet/annlib/rbparser/platform.py +51 -0
  42. annet/annlib/rbparser/syntax.py +115 -0
  43. annet/annlib/rulebook/__init__.py +0 -0
  44. annet/annlib/rulebook/common.py +350 -0
  45. annet/annlib/tabparser.py +648 -0
  46. annet/annlib/types.py +35 -0
  47. annet/api/__init__.py +826 -0
  48. annet/argparse.py +415 -0
  49. annet/cli.py +237 -0
  50. annet/cli_args.py +503 -0
  51. annet/configs/context.yml +18 -0
  52. annet/configs/logging.yaml +39 -0
  53. annet/connectors.py +77 -0
  54. annet/deploy.py +536 -0
  55. annet/diff.py +84 -0
  56. annet/executor.py +551 -0
  57. annet/filtering.py +40 -0
  58. annet/gen.py +865 -0
  59. annet/generators/__init__.py +435 -0
  60. annet/generators/base.py +136 -0
  61. annet/generators/common/__init__.py +0 -0
  62. annet/generators/common/initial.py +33 -0
  63. annet/generators/entire.py +97 -0
  64. annet/generators/exceptions.py +10 -0
  65. annet/generators/jsonfragment.py +125 -0
  66. annet/generators/partial.py +119 -0
  67. annet/generators/perf.py +79 -0
  68. annet/generators/ref.py +15 -0
  69. annet/generators/result.py +127 -0
  70. annet/hardware.py +45 -0
  71. annet/implicit.py +139 -0
  72. annet/lib.py +128 -0
  73. annet/output.py +167 -0
  74. annet/parallel.py +448 -0
  75. annet/patching.py +25 -0
  76. annet/reference.py +148 -0
  77. annet/rulebook/__init__.py +114 -0
  78. annet/rulebook/arista/__init__.py +0 -0
  79. annet/rulebook/arista/iface.py +16 -0
  80. annet/rulebook/aruba/__init__.py +16 -0
  81. annet/rulebook/aruba/ap_env.py +146 -0
  82. annet/rulebook/aruba/misc.py +8 -0
  83. annet/rulebook/cisco/__init__.py +0 -0
  84. annet/rulebook/cisco/iface.py +68 -0
  85. annet/rulebook/cisco/misc.py +57 -0
  86. annet/rulebook/cisco/vlandb.py +90 -0
  87. annet/rulebook/common.py +19 -0
  88. annet/rulebook/deploying.py +87 -0
  89. annet/rulebook/huawei/__init__.py +0 -0
  90. annet/rulebook/huawei/aaa.py +75 -0
  91. annet/rulebook/huawei/bgp.py +97 -0
  92. annet/rulebook/huawei/iface.py +33 -0
  93. annet/rulebook/huawei/misc.py +337 -0
  94. annet/rulebook/huawei/vlandb.py +115 -0
  95. annet/rulebook/juniper/__init__.py +107 -0
  96. annet/rulebook/nexus/__init__.py +0 -0
  97. annet/rulebook/nexus/iface.py +92 -0
  98. annet/rulebook/patching.py +143 -0
  99. annet/rulebook/ribbon/__init__.py +12 -0
  100. annet/rulebook/texts/arista.deploy +20 -0
  101. annet/rulebook/texts/arista.order +125 -0
  102. annet/rulebook/texts/arista.rul +59 -0
  103. annet/rulebook/texts/aruba.deploy +20 -0
  104. annet/rulebook/texts/aruba.order +83 -0
  105. annet/rulebook/texts/aruba.rul +87 -0
  106. annet/rulebook/texts/cisco.deploy +27 -0
  107. annet/rulebook/texts/cisco.order +82 -0
  108. annet/rulebook/texts/cisco.rul +105 -0
  109. annet/rulebook/texts/huawei.deploy +188 -0
  110. annet/rulebook/texts/huawei.order +388 -0
  111. annet/rulebook/texts/huawei.rul +471 -0
  112. annet/rulebook/texts/juniper.rul +120 -0
  113. annet/rulebook/texts/nexus.deploy +24 -0
  114. annet/rulebook/texts/nexus.order +85 -0
  115. annet/rulebook/texts/nexus.rul +83 -0
  116. annet/rulebook/texts/nokia.rul +31 -0
  117. annet/rulebook/texts/pc.order +5 -0
  118. annet/rulebook/texts/pc.rul +9 -0
  119. annet/rulebook/texts/ribbon.deploy +22 -0
  120. annet/rulebook/texts/ribbon.rul +77 -0
  121. annet/rulebook/texts/routeros.order +38 -0
  122. annet/rulebook/texts/routeros.rul +45 -0
  123. annet/storage.py +125 -0
  124. annet/tabparser.py +36 -0
  125. annet/text_term_format.py +95 -0
  126. annet/tracing.py +170 -0
  127. annet/types.py +227 -0
  128. annet-0.0.dist-info/AUTHORS +21 -0
  129. annet-0.0.dist-info/LICENSE +21 -0
  130. annet-0.0.dist-info/METADATA +26 -0
  131. annet-0.0.dist-info/RECORD +137 -0
  132. annet-0.0.dist-info/WHEEL +5 -0
  133. annet-0.0.dist-info/entry_points.txt +5 -0
  134. annet-0.0.dist-info/top_level.txt +2 -0
  135. annet_generators/__init__.py +0 -0
  136. annet_generators/example/__init__.py +12 -0
  137. annet_generators/example/lldp.py +53 -0
@@ -0,0 +1,196 @@
1
+ from logging import getLogger
2
+ from typing import Optional, List, Union
3
+
4
+ from annet.adapters.netbox.common import models
5
+ from annet.adapters.netbox.common.manufacturer import (
6
+ is_supported, get_hw, get_breed,
7
+ )
8
+ from annet.adapters.netbox.common.query import NetboxQuery
9
+ from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
10
+ from annet.storage import Storage
11
+ from . import api_models
12
+ from .client import NetboxV24
13
+
14
+ logger = getLogger(__name__)
15
+
16
+
17
+ def extend_device_ip(
18
+ ip: Optional[api_models.DeviceIp],
19
+ ) -> Optional[models.DeviceIp]:
20
+ if not ip:
21
+ return None
22
+ return models.DeviceIp(
23
+ address=ip.address,
24
+ id=ip.id,
25
+ display=ip.address,
26
+ family=ip.family,
27
+ )
28
+
29
+
30
+ def extend_label(
31
+ label: Optional[api_models.Label],
32
+ ) -> Optional[models.Label]:
33
+ if not label:
34
+ return None
35
+ return models.Label(
36
+ label=label.label,
37
+ value=str(label.value),
38
+ )
39
+
40
+
41
+ def extend_device(
42
+ device: api_models.Device, storage: Storage,
43
+ ) -> models.NetboxDevice:
44
+ manufacturer = device.device_type.manufacturer.name
45
+ model = device.device_type.model
46
+
47
+ return models.NetboxDevice(
48
+ url=device.url,
49
+ id=device.id,
50
+ name=device.name,
51
+ display=device.display_name,
52
+ device_type=device.device_type,
53
+ device_role=device.device_role,
54
+ tenant=device.tenant,
55
+ platform=device.platform,
56
+ serial=device.serial,
57
+ asset_tag=device.asset_tag,
58
+ site=device.site,
59
+ rack=device.rack,
60
+ position=device.position,
61
+ face=extend_label(device.face),
62
+ status=device.status,
63
+ primary_ip=extend_device_ip(device.primary_ip),
64
+ primary_ip4=extend_device_ip(device.primary_ip4),
65
+ primary_ip6=extend_device_ip(device.primary_ip6),
66
+ tags=[models.Entity(0, tag) for tag in device.tags],
67
+ custom_fields=device.custom_fields, # ???
68
+ created=device.created,
69
+ last_updated=device.last_updated,
70
+
71
+ fqdn=device.name,
72
+ hostname=device.name,
73
+ hw=get_hw(manufacturer, model),
74
+ breed=get_breed(manufacturer, model),
75
+ interfaces=[],
76
+ neighbours_ids=[],
77
+ storage=storage,
78
+ )
79
+
80
+
81
+ def extend_interface(interface: api_models.Interface) -> models.Interface:
82
+ return models.Interface(
83
+ id=interface.id,
84
+ name=interface.name,
85
+ device=interface.device,
86
+ enabled=interface.enabled,
87
+ display=interface.name,
88
+ ip_addresses=[],
89
+ )
90
+
91
+
92
+ def extend_ip(ip: api_models.IpAddress) -> models.IpAddress:
93
+ return models.IpAddress(
94
+ id=ip.id,
95
+ assigned_object_id=ip.interface.id,
96
+ display=ip.address,
97
+ family=models.IpFamily(
98
+ value=ip.family,
99
+ label=str(ip.family),
100
+ ),
101
+ address=ip.address,
102
+ status=extend_label(ip.status),
103
+ tags=[models.Entity(0, tag) for tag in ip.tags],
104
+ created=ip.created,
105
+ last_updated=ip.last_updated,
106
+ )
107
+
108
+
109
+ class NetboxStorageV24(Storage):
110
+ def __init__(self, opts: Optional[NetboxStorageOpts] = None):
111
+ self.netbox = NetboxV24(
112
+ url=opts.url,
113
+ token=opts.token,
114
+ )
115
+
116
+ def __enter__(self):
117
+ return self
118
+
119
+ def __exit__(self, _, __, ___):
120
+ pass
121
+
122
+ def resolve_object_ids_by_query(self, query: NetboxQuery):
123
+ return [
124
+ d.id for d in self._load_devices(query)
125
+ ]
126
+
127
+ def resolve_fdnds_by_query(self, query: NetboxQuery):
128
+ return [
129
+ d.name for d in self._load_devices(query)
130
+ ]
131
+
132
+ def make_devices(
133
+ self,
134
+ query: Union[NetboxQuery, list],
135
+ preload_neighbors=False,
136
+ use_mesh=None,
137
+ preload_extra_fields=False,
138
+ **kwargs,
139
+ ) -> List[models.NetboxDevice]:
140
+ if isinstance(query, list):
141
+ query = NetboxQuery.new(query)
142
+ device_ids = {
143
+ device.id: extend_device(device=device, storage=self)
144
+ for device in self._load_devices(query)
145
+ }
146
+ if not device_ids:
147
+ return []
148
+
149
+ interfaces = self._load_interfaces(list(device_ids))
150
+ for interface in interfaces:
151
+ device_ids[interface.device.id].interfaces.append(interface)
152
+ return list(device_ids.values())
153
+
154
+ def _load_devices(self, query: NetboxQuery) -> List[api_models.Device]:
155
+ if not query.globs:
156
+ return []
157
+ return [
158
+ device
159
+ for device in self.netbox.all_devices().results
160
+ if _match_query(query, device)
161
+ if is_supported(device.device_type.manufacturer.name)
162
+ ]
163
+
164
+ def _load_interfaces(self, device_ids: List[int]) -> List[
165
+ models.Interface]:
166
+ interfaces = self.netbox.all_interfaces(device_id=device_ids)
167
+ extended_ifaces = {
168
+ interface.id: extend_interface(interface)
169
+ for interface in interfaces.results
170
+ }
171
+
172
+ ips = self.netbox.all_ip_addresses(interface_id=list(extended_ifaces))
173
+ for ip in ips.results:
174
+ extended_ip = extend_ip(ip)
175
+ interface = extended_ifaces[extended_ip.assigned_object_id]
176
+ interface.ip_addresses.append(extended_ip)
177
+ return list(extended_ifaces.values())
178
+
179
+ def get_device(
180
+ self, obj_id, preload_neighbors=False, use_mesh=None,
181
+ **kwargs,
182
+ ) -> models.NetboxDevice:
183
+ device = self.netbox.get_device(obj_id)
184
+ res = extend_device(device=device, storage=self)
185
+ res.interfaces = self._load_interfaces([device.id])
186
+ return res
187
+
188
+ def flush_perf(self):
189
+ pass
190
+
191
+
192
+ def _match_query(query: NetboxQuery, device_data: api_models.Device) -> bool:
193
+ for subquery in query.globs:
194
+ if subquery.strip() in device_data.name:
195
+ return True
196
+ return False
File without changes
@@ -0,0 +1,38 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+ from typing import List, Optional, Any, Dict
4
+
5
+ from annet.adapters.netbox.common.models import (
6
+ Entity, Label, DeviceType, DeviceIp,
7
+ )
8
+
9
+
10
+ @dataclass
11
+ class Interface(Entity):
12
+ device: Entity
13
+ enabled: bool
14
+ display: str = "" # added in 3.x
15
+
16
+
17
+ @dataclass
18
+ class Device(Entity):
19
+ url: str
20
+ display: str # renamed in 3.x from display_name
21
+ device_type: DeviceType
22
+ device_role: Entity
23
+ tenant: Optional[Entity]
24
+ platform: Optional[Entity]
25
+ serial: str
26
+ asset_tag: Optional[str]
27
+ site: Entity
28
+ rack: Optional[Entity]
29
+ position: Optional[float]
30
+ face: Optional[Label]
31
+ status: Label
32
+ primary_ip: Optional[DeviceIp]
33
+ primary_ip4: Optional[DeviceIp]
34
+ primary_ip6: Optional[DeviceIp]
35
+ tags: List[Entity]
36
+ custom_fields: Dict[str, Any]
37
+ created: datetime
38
+ last_updated: datetime
@@ -0,0 +1,62 @@
1
+ from datetime import datetime
2
+ from typing import List, Optional
3
+
4
+ import dateutil.parser
5
+ from adaptix import Retort, loader
6
+ from dataclass_rest import get
7
+ from dataclass_rest.client_protocol import FactoryProtocol
8
+
9
+ from annet.adapters.netbox.common.client import (
10
+ BaseNetboxClient, collect, PagingResponse,
11
+ )
12
+ from annet.adapters.netbox.common.models import IpAddress
13
+ from .api_models import Device, Interface
14
+
15
+
16
+ class NetboxV37(BaseNetboxClient):
17
+ def _init_response_body_factory(self) -> FactoryProtocol:
18
+ return Retort(recipe=[
19
+ loader(datetime, dateutil.parser.parse)
20
+ ])
21
+
22
+ @get("dcim/interfaces")
23
+ def interfaces(
24
+ self,
25
+ device_id: Optional[List[int]] = None,
26
+ limit: int = 20,
27
+ offset: int = 0,
28
+ ) -> PagingResponse[Interface]:
29
+ pass
30
+
31
+ all_interfaces = collect(interfaces, field="device_id")
32
+
33
+ @get("ipam/ip-addresses")
34
+ def ip_addresses(
35
+ self,
36
+ interface_id: Optional[List[int]] = None,
37
+ limit: int = 20,
38
+ offset: int = 0,
39
+ ) -> PagingResponse[IpAddress]:
40
+ pass
41
+
42
+ all_ip_addresses = collect(ip_addresses, field="interface_id")
43
+
44
+ @get("dcim/devices")
45
+ def devices(
46
+ self,
47
+ name: Optional[List[str]] = None,
48
+ name__ic: Optional[List[str]] = None,
49
+ tag: Optional[List[str]] = None,
50
+ limit: int = 20,
51
+ offset: int = 0,
52
+ ) -> PagingResponse[Device]:
53
+ pass
54
+
55
+ all_devices = collect(devices)
56
+
57
+ @get("dcim/devices/{device_id}")
58
+ def get_device(
59
+ self,
60
+ device_id: int,
61
+ ) -> Device:
62
+ pass
@@ -0,0 +1,149 @@
1
+ from logging import getLogger
2
+ from typing import Optional, List, Union
3
+
4
+ from adaptix import P
5
+ from adaptix.conversion import impl_converter, link
6
+
7
+ from annet.adapters.netbox.common import models
8
+ from annet.adapters.netbox.common.manufacturer import (
9
+ is_supported, get_hw, get_breed,
10
+ )
11
+ from annet.adapters.netbox.common.query import NetboxQuery
12
+ from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
13
+ from annet.annlib.netdev.views.hardware import HardwareView
14
+ from annet.storage import Storage
15
+ from . import api_models
16
+ from .client import NetboxV37
17
+
18
+ logger = getLogger(__name__)
19
+
20
+
21
+ @impl_converter(recipe=[
22
+ link(P[api_models.Device].name, P[models.NetboxDevice].hostname),
23
+ link(P[api_models.Device].name, P[models.NetboxDevice].fqdn),
24
+ ])
25
+ def extend_device_base(
26
+ device: api_models.Device,
27
+ interfaces: List[models.Interface],
28
+ hw: Optional[HardwareView],
29
+ breed: str,
30
+ storage: Storage,
31
+ neighbours_ids: List[int],
32
+ ) -> models.NetboxDevice:
33
+ ...
34
+
35
+
36
+ def extend_device(
37
+ device: api_models.Device, storage: Storage,
38
+ ) -> models.NetboxDevice:
39
+ return extend_device_base(
40
+ device=device,
41
+ interfaces=[],
42
+ breed=get_breed(
43
+ device.device_type.manufacturer.name,
44
+ device.device_type.model,
45
+ ),
46
+ hw=get_hw(
47
+ device.device_type.manufacturer.name,
48
+ device.device_type.model,
49
+ ),
50
+ neighbours_ids=[],
51
+ storage=storage,
52
+ )
53
+
54
+
55
+ @impl_converter
56
+ def extend_interface(
57
+ interface: api_models.Interface, ip_addresses: List[models.IpAddress],
58
+ ) -> models.Interface:
59
+ ...
60
+
61
+
62
+ class NetboxStorageV37(Storage):
63
+ def __init__(self, opts: Optional[NetboxStorageOpts] = None):
64
+ self.netbox = NetboxV37(
65
+ url=opts.url,
66
+ token=opts.token,
67
+ )
68
+
69
+ def __enter__(self):
70
+ return self
71
+
72
+ def __exit__(self, _, __, ___):
73
+ pass
74
+
75
+ def resolve_object_ids_by_query(self, query: NetboxQuery):
76
+ return [
77
+ d.id for d in self._load_devices(query)
78
+ ]
79
+
80
+ def resolve_fdnds_by_query(self, query: NetboxQuery):
81
+ return [
82
+ d.name for d in self._load_devices(query)
83
+ ]
84
+
85
+ def make_devices(
86
+ self,
87
+ query: Union[NetboxQuery, list],
88
+ preload_neighbors=False,
89
+ use_mesh=None,
90
+ preload_extra_fields=False,
91
+ **kwargs,
92
+ ) -> List[models.NetboxDevice]:
93
+ if isinstance(query, list):
94
+ query = NetboxQuery.new(query)
95
+ device_ids = {
96
+ device.id: extend_device(device=device, storage=self)
97
+ for device in self._load_devices(query)
98
+ }
99
+ if not device_ids:
100
+ return []
101
+
102
+ interfaces = self._load_interfaces(list(device_ids))
103
+ for interface in interfaces:
104
+ device_ids[interface.device.id].interfaces.append(interface)
105
+ return list(device_ids.values())
106
+
107
+ def _load_devices(self, query: NetboxQuery) -> List[api_models.Device]:
108
+ if not query.globs:
109
+ return []
110
+ return [
111
+ device
112
+ for device in self.netbox.all_devices(
113
+ name__ic=query.globs,
114
+ ).results
115
+ if _match_query(query, device)
116
+ if is_supported(device.device_type.manufacturer.name)
117
+ ]
118
+
119
+ def _load_interfaces(self, device_ids: List[int]) -> List[
120
+ models.Interface]:
121
+ interfaces = self.netbox.all_interfaces(device_id=device_ids)
122
+ extended_ifaces = {
123
+ interface.id: extend_interface(interface, [])
124
+ for interface in interfaces.results
125
+ }
126
+
127
+ ips = self.netbox.all_ip_addresses(interface_id=list(extended_ifaces))
128
+ for ip in ips.results:
129
+ extended_ifaces[ip.assigned_object_id].ip_addresses.append(ip)
130
+ return list(extended_ifaces.values())
131
+
132
+ def get_device(
133
+ self, obj_id, preload_neighbors=False, use_mesh=None,
134
+ **kwargs,
135
+ ) -> models.NetboxDevice:
136
+ device = self.netbox.get_device(obj_id)
137
+ res = extend_device(device=device, storage=self)
138
+ res.interfaces = self._load_interfaces([device.id])
139
+ return res
140
+
141
+ def flush_perf(self):
142
+ pass
143
+
144
+
145
+ def _match_query(query: NetboxQuery, device_data: api_models.Device) -> bool:
146
+ for subquery in query.globs:
147
+ if subquery.strip() in device_data.name:
148
+ return True
149
+ return False
annet/annet.py ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+
4
+ import annet
5
+ from annet import argparse, cli, generators, hardware, lib, rulebook
6
+
7
+
8
+ # =====
9
+ @lib.catch_ctrl_c
10
+ def main():
11
+ annet.assert_python_version()
12
+ parser = argparse.ArgParser()
13
+ cli.fill_base_args(parser, annet.__name__, "configs/logging.yaml")
14
+ rulebook.rulebook_provider_connector.set(rulebook.DefaultRulebookProvider)
15
+ hardware.hardware_connector.set(hardware.AnnetHardwareProvider)
16
+
17
+ parser.add_commands(parser.find_subcommands(cli.list_subcommands()))
18
+ try:
19
+ return parser.dispatch(pre_call=annet.init, add_help_command=True)
20
+ except (generators.GeneratorError, annet.ExecError):
21
+ return 1
22
+
23
+
24
+ if __name__ == "__main__":
25
+ sys.exit(main())
@@ -0,0 +1,7 @@
1
+ import os
2
+
3
+ import colorama
4
+
5
+ # отключить colorama.init, если стоит env-переменная. Нужно в тестах
6
+ if os.environ.get("ANN_FORCE_COLOR", None) not in [None, "", "0", "no"]:
7
+ colorama.init = lambda *_, **__: None
@@ -0,0 +1,49 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from typing import List, Optional
4
+
5
+
6
+ FIRST_EXCEPTION = 1
7
+ ALL_COMPLETED = 2
8
+
9
+
10
+ @dataclass
11
+ class Question:
12
+ question: str # frame it using / if it is a regular expression
13
+ answer: str
14
+ is_regexp: Optional[bool] = False
15
+
16
+
17
+ @dataclass
18
+ class Command:
19
+ cmd: str
20
+ questions: Optional[List[Question]] = None
21
+ exc_handler: Optional[List[Question]] = None
22
+ timeout: Optional[int] = None
23
+ suppress_nonzero: bool = False
24
+ suppress_eof: bool = False
25
+
26
+ def __str__(self) -> str:
27
+ return self.cmd
28
+
29
+
30
+ @dataclass
31
+ class CommandList:
32
+ cmss: List[Command] = field(default_factory=list)
33
+
34
+ def __post_init__(self):
35
+ if not self.cmss:
36
+ self.cmss = []
37
+
38
+ def __iter__(self):
39
+ return iter(self.cmss)
40
+
41
+ def __len__(self) -> int:
42
+ return len(self.cmss)
43
+
44
+ def add_cmd(self, cmd: Command) -> None:
45
+ assert isinstance(cmd, Command)
46
+ self.cmss.append(cmd)
47
+
48
+ def as_list(self) -> List[Command]: # TODO: delete
49
+ return self.cmss
annet/annlib/diff.py ADDED
@@ -0,0 +1,158 @@
1
+ import functools
2
+ import ipaddress
3
+ from typing import Dict, Generator
4
+
5
+ import colorama
6
+
7
+ from .types import Diff, Op
8
+
9
+ # NOCDEV-1720
10
+
11
+
12
+ diff_ops = {
13
+ "+": Op.ADDED,
14
+ "-": Op.REMOVED,
15
+ " ": Op.AFFECTED,
16
+ ">": Op.MOVED,
17
+ }
18
+
19
+ ops_sign = {
20
+ v: k
21
+ for k, v in diff_ops.items()
22
+ }
23
+
24
+ ops_order = {
25
+ Op.AFFECTED: 0,
26
+ Op.MOVED: 1,
27
+ Op.REMOVED: 2,
28
+ Op.ADDED: 3,
29
+ }
30
+
31
+ ops_color = {
32
+ Op.REMOVED: colorama.Fore.RED,
33
+ Op.ADDED: colorama.Fore.GREEN,
34
+ Op.AFFECTED: colorama.Fore.CYAN,
35
+ Op.MOVED: colorama.Fore.YELLOW,
36
+ }
37
+
38
+
39
+ def is_int(ts):
40
+ try:
41
+ int(ts)
42
+ return True
43
+ except ValueError:
44
+ return False
45
+
46
+
47
+ def is_ip(ts):
48
+ try:
49
+ ipaddress.ip_interface(ts)
50
+ return True
51
+ except ValueError:
52
+ return False
53
+
54
+
55
+ def diff_cmp(diff_l, diff_r):
56
+ """
57
+ Сборник костылей для сравнения двух строк диффа
58
+ """
59
+ (op_l, line_l, _, _) = diff_l
60
+ (op_r, line_r, _, _) = diff_r
61
+
62
+ cmp_line = (line_l > line_r) - (line_l < line_r)
63
+ cmp_op = ops_order[op_l] - ops_order[op_r]
64
+ if cmp_line == 0:
65
+ # При равенстве строк порядок определяется операцией
66
+ return cmp_op
67
+
68
+ if cmp_op == 0:
69
+ # Если для строк операции одинаковы, то считаем их равными
70
+ # По идее TimSort стабилен и порядок просто не должен измениться
71
+ return 0
72
+
73
+ # Для частично совпадающих строк
74
+ lws = line_l.split(" ")
75
+ rws = line_r.split(" ")
76
+ res = 0
77
+ for i, lw in enumerate(lws):
78
+ if len(rws) > i:
79
+ rw = rws[i]
80
+ if is_int(lw) and is_int(rw):
81
+ # В одинаковом положении в строках есть инты, сортируем по ним
82
+ res = int(lw) - int(rw)
83
+ if res == 0:
84
+ # При равных интах - сортируем по операции
85
+ res = cmp_op
86
+ elif is_ip(lw) and is_ip(rw):
87
+ # Аналогично интам обрабатываем ip-адреса
88
+ ip_l = ipaddress.ip_interface(lw)
89
+ ip_r = ipaddress.ip_interface(rw)
90
+ try:
91
+ res = (ip_l > ip_r) - (ip_l < ip_r)
92
+ except TypeError:
93
+ res = 1
94
+ if ip_l.version == 4:
95
+ res = -1
96
+ if res == 0:
97
+ res = cmp_op
98
+ elif i > 0:
99
+ if lw == rw:
100
+ res = cmp_op
101
+ else:
102
+ continue
103
+ break
104
+ if res != 0:
105
+ return res
106
+ return cmp_line
107
+
108
+
109
+ def resort_diff(diff: Diff) -> Diff:
110
+ res = []
111
+ df = sorted(diff, key=functools.cmp_to_key(diff_cmp))
112
+ for line in df:
113
+ ln = line
114
+ if len(line[2]) > 0:
115
+ ln = (line[0], line[1], resort_diff(line[2]), line[3])
116
+ res.append(ln)
117
+ return res
118
+
119
+
120
+ def colorize_line_with_color(line: str, color: int, no_color: bool):
121
+ stripped = line.rstrip("\n")
122
+ add_newlines = len(line) - len(stripped)
123
+ line = stripped
124
+
125
+ if not no_color:
126
+ line = "%s%s%s%s" % (colorama.Style.BRIGHT, color, line, colorama.Style.RESET_ALL)
127
+
128
+ line += "\n" * add_newlines
129
+ return line
130
+
131
+
132
+ def colorize_line(line, no_color=False):
133
+ op = diff_ops[line[0]]
134
+ color = ops_color[op]
135
+ return colorize_line_with_color(line, color, no_color)
136
+
137
+
138
+ def gen_pre_as_diff(
139
+ pre: Dict,
140
+ show_rules: bool,
141
+ indent: str,
142
+ no_color: bool,
143
+ _level: int = 0
144
+ ) -> Generator[str, None, None]:
145
+ ops = [(order, op) for op, order in ops_order.items()]
146
+ ops.sort()
147
+ for (raw_rule, content) in pre.items():
148
+ items = content["items"].items()
149
+ for (_, diff) in items: # pylint: disable=redefined-outer-name
150
+ if show_rules and not raw_rule == "__MULTILINE_BODY__":
151
+ line = "# %s%s\n" % (indent * _level, raw_rule)
152
+ yield colorize_line_with_color(line, colorama.Fore.BLACK, no_color)
153
+ for (op, rows) in [(op, diff[op]) for (_, op) in ops]:
154
+ for item in rows:
155
+ line = "%s%s %s\n" % (ops_sign[op], indent * _level, item["row"])
156
+ yield colorize_line_with_color(line, ops_color[op], no_color)
157
+ if len(item["children"]) != 0:
158
+ yield from gen_pre_as_diff(item["children"], show_rules, indent, no_color, _level + 1)
annet/annlib/errors.py ADDED
@@ -0,0 +1,8 @@
1
+ class ExecError(Exception):
2
+ """Обработчик этого exception должен залоггировать ошибку и выйти с exit_code 1"""
3
+ pass
4
+
5
+
6
+ class DeployCancelled(Exception):
7
+ """Деплой на устройство был отменен"""
8
+ pass