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.
- {annet-0.14.8/annet.egg-info → annet-0.14.10}/PKG-INFO +1 -1
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/manufacturer.py +1 -12
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/models.py +21 -4
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/query.py +3 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/v24/storage.py +2 -2
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/v37/storage.py +13 -9
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/views/dump.py +8 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/syntax.py +1 -1
- {annet-0.14.8 → annet-0.14.10}/annet/argparse.py +6 -2
- {annet-0.14.8 → annet-0.14.10}/annet/cli.py +113 -23
- {annet-0.14.8 → annet-0.14.10}/annet/cli_args.py +55 -57
- {annet-0.14.8 → annet-0.14.10}/annet/executor.py +1 -5
- {annet-0.14.8 → annet-0.14.10}/annet/gen.py +3 -0
- {annet-0.14.8 → annet-0.14.10}/annet/implicit.py +8 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/aruba/ap_env.py +1 -6
- {annet-0.14.8 → annet-0.14.10}/annet/storage.py +4 -1
- {annet-0.14.8 → annet-0.14.10/annet.egg-info}/PKG-INFO +1 -1
- {annet-0.14.8 → annet-0.14.10}/AUTHORS +0 -0
- {annet-0.14.8 → annet-0.14.10}/LICENSE +0 -0
- {annet-0.14.8 → annet-0.14.10}/MANIFEST.in +0 -0
- {annet-0.14.8 → annet-0.14.10}/README.md +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/client.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/status_client.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/common/storage_opts.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/provider.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/v24/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/adapters/netbox/v37/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annet.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/command.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/diff.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/errors.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/filter_acl.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/jsontools.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/lib.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/db.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/views/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/netdev/views/hardware.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/output.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/patching.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/acl.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/ordering.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/rbparser/platform.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/rulebook/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/rulebook/common.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/tabparser.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/annlib/types.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/api/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/configs/context.yml +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/configs/logging.yaml +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/connectors.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/deploy.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/diff.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/filtering.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/base.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/common/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/common/initial.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/entire.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/exceptions.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/jsonfragment.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/partial.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/perf.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/ref.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/generators/result.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/hardware.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/lib.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/output.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/parallel.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/patching.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/reference.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/arista/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/arista/iface.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/aruba/misc.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/cisco/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/cisco/iface.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/cisco/misc.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/common.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/deploying.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/iface.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/misc.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/juniper/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/nexus/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/nexus/iface.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/patching.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/ribbon/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/arista.order +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/arista.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/aruba.order +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/cisco.deploy +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/cisco.order +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/cisco.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/huawei.deploy +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/huawei.order +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/nexus.order +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/pc.order +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/pc.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/routeros.order +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/tabparser.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/text_term_format.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/tracing.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet/types.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet.egg-info/SOURCES.txt +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet.egg-info/dependency_links.txt +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet.egg-info/entry_points.txt +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet.egg-info/requires.txt +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet.egg-info/top_level.txt +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet_generators/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet_generators/example/__init__.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/annet_generators/example/lldp.py +0 -0
- {annet-0.14.8 → annet-0.14.10}/requirements.txt +0 -0
- {annet-0.14.8 → annet-0.14.10}/setup.cfg +0 -0
- {annet-0.14.8 → annet-0.14.10}/setup.py +0 -0
|
@@ -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
|
-
|
|
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):
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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"~/(
|
|
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
|
-
Для создания более одного уровня команд
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"""
|
|
306
|
+
""" Open the context file using an editor from the EDITOR environment variable.
|
|
215
307
|
|
|
216
|
-
|
|
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()
|