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.
- annet/__init__.py +61 -0
- annet/adapters/__init__.py +0 -0
- annet/adapters/netbox/__init__.py +0 -0
- annet/adapters/netbox/common/__init__.py +0 -0
- annet/adapters/netbox/common/client.py +87 -0
- annet/adapters/netbox/common/manufacturer.py +62 -0
- annet/adapters/netbox/common/models.py +105 -0
- annet/adapters/netbox/common/query.py +23 -0
- annet/adapters/netbox/common/status_client.py +25 -0
- annet/adapters/netbox/common/storage_opts.py +14 -0
- annet/adapters/netbox/provider.py +34 -0
- annet/adapters/netbox/v24/__init__.py +0 -0
- annet/adapters/netbox/v24/api_models.py +73 -0
- annet/adapters/netbox/v24/client.py +59 -0
- annet/adapters/netbox/v24/storage.py +196 -0
- annet/adapters/netbox/v37/__init__.py +0 -0
- annet/adapters/netbox/v37/api_models.py +38 -0
- annet/adapters/netbox/v37/client.py +62 -0
- annet/adapters/netbox/v37/storage.py +149 -0
- annet/annet.py +25 -0
- annet/annlib/__init__.py +7 -0
- annet/annlib/command.py +49 -0
- annet/annlib/diff.py +158 -0
- annet/annlib/errors.py +8 -0
- annet/annlib/filter_acl.py +196 -0
- annet/annlib/jsontools.py +116 -0
- annet/annlib/lib.py +495 -0
- annet/annlib/netdev/__init__.py +0 -0
- annet/annlib/netdev/db.py +62 -0
- annet/annlib/netdev/devdb/__init__.py +28 -0
- annet/annlib/netdev/devdb/data/devdb.json +137 -0
- annet/annlib/netdev/views/__init__.py +0 -0
- annet/annlib/netdev/views/dump.py +121 -0
- annet/annlib/netdev/views/hardware.py +112 -0
- annet/annlib/output.py +246 -0
- annet/annlib/patching.py +533 -0
- annet/annlib/rbparser/__init__.py +0 -0
- annet/annlib/rbparser/acl.py +120 -0
- annet/annlib/rbparser/deploying.py +55 -0
- annet/annlib/rbparser/ordering.py +52 -0
- annet/annlib/rbparser/platform.py +51 -0
- annet/annlib/rbparser/syntax.py +115 -0
- annet/annlib/rulebook/__init__.py +0 -0
- annet/annlib/rulebook/common.py +350 -0
- annet/annlib/tabparser.py +648 -0
- annet/annlib/types.py +35 -0
- annet/api/__init__.py +826 -0
- annet/argparse.py +415 -0
- annet/cli.py +237 -0
- annet/cli_args.py +503 -0
- annet/configs/context.yml +18 -0
- annet/configs/logging.yaml +39 -0
- annet/connectors.py +77 -0
- annet/deploy.py +536 -0
- annet/diff.py +84 -0
- annet/executor.py +551 -0
- annet/filtering.py +40 -0
- annet/gen.py +865 -0
- annet/generators/__init__.py +435 -0
- annet/generators/base.py +136 -0
- annet/generators/common/__init__.py +0 -0
- annet/generators/common/initial.py +33 -0
- annet/generators/entire.py +97 -0
- annet/generators/exceptions.py +10 -0
- annet/generators/jsonfragment.py +125 -0
- annet/generators/partial.py +119 -0
- annet/generators/perf.py +79 -0
- annet/generators/ref.py +15 -0
- annet/generators/result.py +127 -0
- annet/hardware.py +45 -0
- annet/implicit.py +139 -0
- annet/lib.py +128 -0
- annet/output.py +167 -0
- annet/parallel.py +448 -0
- annet/patching.py +25 -0
- annet/reference.py +148 -0
- annet/rulebook/__init__.py +114 -0
- annet/rulebook/arista/__init__.py +0 -0
- annet/rulebook/arista/iface.py +16 -0
- annet/rulebook/aruba/__init__.py +16 -0
- annet/rulebook/aruba/ap_env.py +146 -0
- annet/rulebook/aruba/misc.py +8 -0
- annet/rulebook/cisco/__init__.py +0 -0
- annet/rulebook/cisco/iface.py +68 -0
- annet/rulebook/cisco/misc.py +57 -0
- annet/rulebook/cisco/vlandb.py +90 -0
- annet/rulebook/common.py +19 -0
- annet/rulebook/deploying.py +87 -0
- annet/rulebook/huawei/__init__.py +0 -0
- annet/rulebook/huawei/aaa.py +75 -0
- annet/rulebook/huawei/bgp.py +97 -0
- annet/rulebook/huawei/iface.py +33 -0
- annet/rulebook/huawei/misc.py +337 -0
- annet/rulebook/huawei/vlandb.py +115 -0
- annet/rulebook/juniper/__init__.py +107 -0
- annet/rulebook/nexus/__init__.py +0 -0
- annet/rulebook/nexus/iface.py +92 -0
- annet/rulebook/patching.py +143 -0
- annet/rulebook/ribbon/__init__.py +12 -0
- annet/rulebook/texts/arista.deploy +20 -0
- annet/rulebook/texts/arista.order +125 -0
- annet/rulebook/texts/arista.rul +59 -0
- annet/rulebook/texts/aruba.deploy +20 -0
- annet/rulebook/texts/aruba.order +83 -0
- annet/rulebook/texts/aruba.rul +87 -0
- annet/rulebook/texts/cisco.deploy +27 -0
- annet/rulebook/texts/cisco.order +82 -0
- annet/rulebook/texts/cisco.rul +105 -0
- annet/rulebook/texts/huawei.deploy +188 -0
- annet/rulebook/texts/huawei.order +388 -0
- annet/rulebook/texts/huawei.rul +471 -0
- annet/rulebook/texts/juniper.rul +120 -0
- annet/rulebook/texts/nexus.deploy +24 -0
- annet/rulebook/texts/nexus.order +85 -0
- annet/rulebook/texts/nexus.rul +83 -0
- annet/rulebook/texts/nokia.rul +31 -0
- annet/rulebook/texts/pc.order +5 -0
- annet/rulebook/texts/pc.rul +9 -0
- annet/rulebook/texts/ribbon.deploy +22 -0
- annet/rulebook/texts/ribbon.rul +77 -0
- annet/rulebook/texts/routeros.order +38 -0
- annet/rulebook/texts/routeros.rul +45 -0
- annet/storage.py +125 -0
- annet/tabparser.py +36 -0
- annet/text_term_format.py +95 -0
- annet/tracing.py +170 -0
- annet/types.py +227 -0
- annet-0.0.dist-info/AUTHORS +21 -0
- annet-0.0.dist-info/LICENSE +21 -0
- annet-0.0.dist-info/METADATA +26 -0
- annet-0.0.dist-info/RECORD +137 -0
- annet-0.0.dist-info/WHEEL +5 -0
- annet-0.0.dist-info/entry_points.txt +5 -0
- annet-0.0.dist-info/top_level.txt +2 -0
- annet_generators/__init__.py +0 -0
- annet_generators/example/__init__.py +12 -0
- 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())
|
annet/annlib/__init__.py
ADDED
annet/annlib/command.py
ADDED
|
@@ -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)
|