catalystwan 0.41.6.dev2__py3-none-any.whl → 0.41.6.dev4__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.
- catalystwan/endpoints/configuration/network_hierarchy.py +9 -1
- catalystwan/models/configuration/network_hierarchy/node.py +20 -1
- catalystwan/tests/templates/conftest.py +21 -0
- catalystwan/tests/templates/payload/cisco-vpn-definition.json +1 -0
- catalystwan/tests/templates/payload/ospf-multiple-route-policy-variables.json +1 -0
- catalystwan/tests/templates/payload/ospfv3-multiple-route-policy-variables.json +1 -0
- catalystwan/tests/templates/test_find_template_values.py +73 -1
- catalystwan/utils/feature_template/find_template_values.py +100 -6
- {catalystwan-0.41.6.dev2.dist-info → catalystwan-0.41.6.dev4.dist-info}/METADATA +1 -1
- {catalystwan-0.41.6.dev2.dist-info → catalystwan-0.41.6.dev4.dist-info}/RECORD +12 -8
- {catalystwan-0.41.6.dev2.dist-info → catalystwan-0.41.6.dev4.dist-info}/LICENSE +0 -0
- {catalystwan-0.41.6.dev2.dist-info → catalystwan-0.41.6.dev4.dist-info}/WHEEL +0 -0
|
@@ -6,7 +6,7 @@ from uuid import UUID
|
|
|
6
6
|
from catalystwan.endpoints import APIEndpoints, delete, get, post, put, versions
|
|
7
7
|
from catalystwan.models.configuration.feature_profile.parcel import Parcel, ParcelCreationResponse
|
|
8
8
|
from catalystwan.models.configuration.network_hierarchy.cflowd import CflowdParcel
|
|
9
|
-
from catalystwan.models.configuration.network_hierarchy.node import NodeInfo
|
|
9
|
+
from catalystwan.models.configuration.network_hierarchy.node import NodeCreationRequest, NodeCreationResponse, NodeInfo
|
|
10
10
|
from catalystwan.models.configuration.network_hierarchy.security_logging import SecurityLoggingParcel
|
|
11
11
|
from catalystwan.typed_list import DataSequence
|
|
12
12
|
|
|
@@ -16,6 +16,14 @@ class NetworkHierarchy(APIEndpoints):
|
|
|
16
16
|
@versions(">=20.10")
|
|
17
17
|
def list_nodes(self) -> DataSequence[NodeInfo]: ...
|
|
18
18
|
|
|
19
|
+
@post("/v1/network-hierarchy")
|
|
20
|
+
@versions(">=20.10")
|
|
21
|
+
def create_node(self, payload: NodeCreationRequest) -> NodeCreationResponse: ...
|
|
22
|
+
|
|
23
|
+
@delete("/v1/network-hierarchy/{node_id}")
|
|
24
|
+
@versions(">=20.10")
|
|
25
|
+
def delete_node(self, node_id: UUID) -> None: ...
|
|
26
|
+
|
|
19
27
|
@post("/v1/network-hierarchy/{node_id}/network-settings/cflowd")
|
|
20
28
|
@versions(">20.12")
|
|
21
29
|
def create_cflowd(self, node_id: UUID, payload: CflowdParcel) -> ParcelCreationResponse: ...
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Copyright 2024 Cisco Systems, Inc. and its affiliates
|
|
2
|
-
from typing import Literal, Optional
|
|
2
|
+
from typing import List, Literal, Optional
|
|
3
3
|
from uuid import UUID
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
@@ -86,3 +86,22 @@ class NodeInfo(BaseModel):
|
|
|
86
86
|
id: str
|
|
87
87
|
name: str
|
|
88
88
|
uuid: Optional[UUID] = None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class NodeCreationRequest(BaseModel):
|
|
92
|
+
name: str
|
|
93
|
+
description: Optional[str] = Field(default=None)
|
|
94
|
+
data: Node
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class NodeCreatedInfo(BaseModel):
|
|
98
|
+
name: str
|
|
99
|
+
uuid: UUID
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class NodeCreationResponse(BaseModel):
|
|
103
|
+
created: List[NodeCreatedInfo]
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def created_uuid(self) -> UUID:
|
|
107
|
+
return self.created[0].uuid
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture(scope="module")
|
|
10
|
+
def template_payload_dir() -> Path:
|
|
11
|
+
return Path(Path(__file__).resolve().parent / "payload")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture(scope="module")
|
|
15
|
+
def template_definition_loader(template_payload_dir) -> Callable[[str], dict]:
|
|
16
|
+
|
|
17
|
+
def loader(path: str) -> dict:
|
|
18
|
+
filepath = template_payload_dir / path
|
|
19
|
+
return json.load(open(filepath))
|
|
20
|
+
|
|
21
|
+
return loader
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"vpn-id":{"vipObjectType":"object","vipType":"constant","vipValue":30},"name":{"vipObjectType":"object","vipType":"variableName","vipValue":"","vipVariableName":"vpn_name"},"ecmp-hash-key":{"layer4":{"vipObjectType":"object","vipType":"ignore","vipValue":"false","vipVariableName":"vpn_layer4"}},"nat64-global":{"prefix":{"stateful":{}}},"nat64":{"v4":{"pool":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["name"]}}},"tenant-vpn-id":{},"org-name":{},"omp-admin-distance-ipv4":{"vipObjectType":"object","vipType":"ignore","vipVariableName":"vpn_omp-admin-distance-ipv4"},"omp-admin-distance-ipv6":{"vipObjectType":"object","vipType":"ignore","vipVariableName":"vpn_omp-admin-distance-ipv6"},"nat":{"natpool":{"vipType":"constant","vipValue":[{"name":{"vipObjectType":"object","vipType":"constant","vipValue":1,"vipVariableName":"vpn_nat_pool_name"},"prefix-length":{"vipObjectType":"object","vipType":"constant","vipValue":8,"vipVariableName":"vpn_nat_prefix_length"},"range-start":{"vipObjectType":"object","vipType":"constant","vipValue":"192.168.2.1","vipVariableName":"vpn_nat_range_start"},"range-end":{"vipObjectType":"object","vipType":"constant","vipValue":"192.168.2.150","vipVariableName":"vpn_nat_range_end"},"overload":{"vipObjectType":"object","vipType":"ignore","vipValue":"true","vipVariableName":"vpn_nat_pool_overload"},"direction":{"vipObjectType":"object","vipType":"constant","vipValue":"outside","vipVariableName":"vpn_nat_direction"},"tracker-id":{"vipObjectType":"object","vipType":"ignore","vipVariableName":"vpn_nat_tracker_id"},"priority-order":["name","prefix-length","range-start","range-end","overload","direction","tracker-id"]},{"name":{"vipObjectType":"object","vipType":"variableName","vipValue":"","vipVariableName":"vpn_nat_pool_name"},"prefix-length":{"vipObjectType":"object","vipType":"constant","vipValue":24,"vipVariableName":"vpn_nat_prefix_length"},"range-start":{"vipObjectType":"object","vipType":"constant","vipValue":"192.168.3.1","vipVariableName":"vpn_nat_range_start"},"range-end":{"vipObjectType":"object","vipType":"constant","vipValue":"192.168.3.150","vipVariableName":"vpn_nat_range_end"},"overload":{"vipObjectType":"object","vipType":"ignore","vipValue":"true","vipVariableName":"vpn_nat_pool_overload"},"direction":{"vipObjectType":"object","vipType":"constant","vipValue":"outside","vipVariableName":"vpn_nat_direction"},"tracker-id":{"vipObjectType":"object","vipType":"ignore","vipVariableName":"vpn_nat_tracker_id"},"priority-order":["name","prefix-length","range-start","range-end","overload","direction","tracker-id"]}],"vipObjectType":"tree","vipPrimaryKey":["name"]},"port-forward":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["source-port","translate-port","source-ip","translate-ip","proto"]},"static":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["source-ip","translate-ip"]},"subnet-static":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["source-ip-subnet","translate-ip-subnet"]}},"route-import-from":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["protocol","source-vpn"]},"route-import":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["protocol"]},"route-export":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["protocol"]},"host":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["hostname"]},"service":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["svc-type"]},"ip":{"gre-route":{},"ipsec-route":{},"service-route":{}},"ipv6":{},"omp":{"advertise":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["protocol"]},"ipv6-advertise":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["protocol"]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"ospf":{"router-id":{"vipObjectType":"object","vipType":"ignore","vipVariableName":"ospf_router_id"},"auto-cost":{"reference-bandwidth":{"vipObjectType":"object","vipType":"ignore","vipValue":100,"vipVariableName":"ospf_reference_bandwidth"}},"compatible":{"rfc1583":{"vipObjectType":"object","vipType":"ignore","vipValue":"true","vipVariableName":"ospf_rfc1583"}},"distance":{"external":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospf_distance_external"},"inter-area":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospf_distance_inter_area"},"intra-area":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospf_distance_intra_area"}},"timers":{"spf":{"delay":{"vipObjectType":"object","vipType":"ignore","vipValue":200,"vipVariableName":"ospf_delay"},"initial-hold":{"vipObjectType":"object","vipType":"ignore","vipValue":1000,"vipVariableName":"ospf_initial_hold"},"max-hold":{"vipObjectType":"object","vipType":"ignore","vipValue":10000,"vipVariableName":"ospf_max_hold"}}},"route-policy":{"vipType":"constant","vipValue":[{"direction":{"vipObjectType":"object","vipType":"constant","vipValue":"in"},"pol-name":{"vipObjectType":"object","vipType":"variableName","vipValue":"","vipVariableName":"ospf_pol_name"},"priority-order":["direction","pol-name"]}],"vipObjectType":"tree","vipPrimaryKey":["direction"]},"redistribute":{"vipType":"constant","vipValue":[{"protocol":{"vipObjectType":"object","vipType":"constant","vipValue":"static","vipVariableName":"ospf_redistribute_protocol"},"route-policy":{"vipObjectType":"object","vipType":"variableName","vipValue":"","vipVariableName":"ospf_redistribute_route_policy"},"priority-order":["protocol","route-policy"]}],"vipObjectType":"tree","vipPrimaryKey":["protocol"]},"max-metric":{"router-lsa":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["ad-type"]}},"area":{"vipType":"constant","vipValue":[{"a-num":{"descriptionI18nKey":"server.vmanage.template.general.ciscoOspf.areaNumber","detailsI18nKey":"server.vmanage.template.general.ciscoOspf.setOSPFAreaNumber","originalDefaultOption":"constant","dataPath":[],"vipObjectType":"object","vipType":"constant","vipValue":999,"vipVariableName":"ospf_area_a_num"},"stub":{"no-summary":{"vipType":"ignore","vipObjectType":"node-only"}},"nssa":{"no-summary":{"vipType":"ignore","vipObjectType":"node-only"}},"priority-order":["a-num"]}],"vipObjectType":"tree","vipPrimaryKey":["a-num"]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"ospfv3":{"address-family":{"ipv4":{"router-id":{"vipObjectType":"object","vipType":"ignore","vipVariableName":"ospfv3_router_id"},"auto-cost":{"reference-bandwidth":{"vipObjectType":"object","vipType":"ignore","vipValue":100,"vipVariableName":"ospfv3_reference_bandwidth"}},"distance-ipv4":{"distance":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospfv3_distance_distance"},"ospf":{"external":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospfv3_distance_external"},"inter-area":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospfv3_distance_inter_area"},"intra-area":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospfv3_distance_intra_area"}}},"table-map":{"name":{"vipObjectType":"object","vipType":"variableName","vipValue":"","vipVariableName":"ospfv3_distance_name"}},"compatible":{"rfc1583":{"vipObjectType":"object","vipType":"ignore","vipValue":"true","vipVariableName":"ospfv3_rfc1583"}},"timers":{"throttle":{"spf":{"delay":{"vipObjectType":"object","vipType":"ignore","vipValue":200,"vipVariableName":"ospfv3_delay"},"initial-hold":{"vipObjectType":"object","vipType":"ignore","vipValue":1000,"vipVariableName":"ospfv3_initial_hold"},"max-hold":{"vipObjectType":"object","vipType":"ignore","vipValue":10000,"vipVariableName":"ospfv3_max_hold"}}}},"redistribute":{"vipType":"constant","vipValue":[{"protocol":{"vipObjectType":"object","vipType":"constant","vipValue":"static","vipVariableName":"ospf_redistribute_protocol"},"route-policy":{"vipObjectType":"object","vipType":"ignore","vipVariableName":"ospf_redistribute_route_policy"},"priority-order":["protocol","route-policy"]},{"protocol":{"vipObjectType":"object","vipType":"constant","vipValue":"nat-route","vipVariableName":"ospf_redistribute_protocol"},"route-policy":{"vipObjectType":"object","vipType":"variableName","vipValue":"","vipVariableName":"ospf_redistribute_route_policy"},"dia":{"vipObjectType":"object","vipType":"constant","vipValue":"true","vipVariableName":"ospf_redistribute_dia"},"priority-order":["protocol","route-policy","dia"]},{"protocol":{"vipObjectType":"object","vipType":"constant","vipValue":"eigrp","vipVariableName":"ospf_redistribute_protocol"},"route-policy":{"vipObjectType":"object","vipType":"variableName","vipValue":"","vipVariableName":"ospf_redistribute_route_policy"},"priority-order":["protocol","route-policy"]}],"vipObjectType":"tree","vipPrimaryKey":["protocol"]},"max-metric":{"router-lsa":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["ad-type"]}}},"ipv6":{"router-id":{"vipObjectType":"object","vipType":"ignore","vipVariableName":"ospfv3_router_id_ipv6"},"auto-cost":{"reference-bandwidth":{"vipObjectType":"object","vipType":"ignore","vipValue":100,"vipVariableName":"ospfv3_reference_bandwidth_ipv6"}},"distance-ipv4":{"distance":{},"ospf":{"external":{},"inter-area":{},"intra-area":{}}},"table-map":{"name":{"vipObjectType":"object","vipType":"ignore","vipVariableName":"ospfv3_distance_name_ipv6"}},"compatible":{"rfc1583":{"vipObjectType":"object","vipType":"ignore","vipValue":"true","vipVariableName":"ospfv3_rfc1583_ipv6"}},"distance-ipv6":{"ospf":{"external":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospfv3_distance_external_ipv6"},"inter-area":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospfv3_distance_inter_area_ipv6"},"intra-area":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospfv3_distance_intra_area_ipv6"}},"distance":{"vipObjectType":"object","vipType":"ignore","vipValue":110,"vipVariableName":"ospfv3_distance_distance_ipv6"}},"timers":{"throttle":{"spf":{"delay":{"vipObjectType":"object","vipType":"ignore","vipValue":200,"vipVariableName":"ospfv3_delay_ipv6"},"initial-hold":{"vipObjectType":"object","vipType":"ignore","vipValue":1000,"vipVariableName":"ospfv3_initial_hold_ipv6"},"max-hold":{"vipObjectType":"object","vipType":"ignore","vipValue":10000,"vipVariableName":"ospfv3_max_hold_ipv6"}}}},"max-metric":{"router-lsa":{"vipType":"ignore","vipValue":[],"vipObjectType":"tree","vipPrimaryKey":["ad-type"]}}}}}}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# Copyright 2024 Cisco Systems, Inc. and its affiliates
|
|
2
2
|
|
|
3
|
+
from functools import reduce
|
|
4
|
+
|
|
3
5
|
from catalystwan.api.templates.device_variable import DeviceVariable
|
|
4
6
|
from catalystwan.utils.feature_template.find_template_values import find_template_values
|
|
5
7
|
|
|
6
8
|
|
|
9
|
+
def dict_get(dictionary, keys):
|
|
10
|
+
return reduce(lambda a, x: a[x], keys, dictionary)
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
def test_find_template_values():
|
|
8
14
|
input_values = {
|
|
9
15
|
"vpn-id": {"vipObjectType": "object", "vipType": "constant", "vipValue": 0},
|
|
@@ -238,6 +244,72 @@ def test_nested_variable_values():
|
|
|
238
244
|
result = find_template_values(input_values)
|
|
239
245
|
|
|
240
246
|
# Assert
|
|
241
|
-
variable = result
|
|
247
|
+
variable = dict_get(result, ("tunnel-interface", "color", "value"))
|
|
242
248
|
assert isinstance(variable, DeviceVariable)
|
|
243
249
|
assert variable.name == "test_value_variable"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def test_ospf_multiple_route_policy_variables(template_definition_loader):
|
|
253
|
+
# Arrange
|
|
254
|
+
data = template_definition_loader("ospf-multiple-route-policy-variables.json")
|
|
255
|
+
|
|
256
|
+
# Act
|
|
257
|
+
values = find_template_values(data)
|
|
258
|
+
|
|
259
|
+
# Assert
|
|
260
|
+
var_policy = dict_get(values, ("ospf", "route-policy", 0, "pol-name"))
|
|
261
|
+
assert isinstance(var_policy, DeviceVariable)
|
|
262
|
+
assert var_policy._template_path == ("ospf", "route-policy", "in", "pol-name")
|
|
263
|
+
|
|
264
|
+
var_redistribute = dict_get(values, ("ospf", "redistribute", "route-policy"))
|
|
265
|
+
assert isinstance(var_redistribute, DeviceVariable)
|
|
266
|
+
assert var_redistribute._template_path == ("ospf", "redistribute", "static", "route-policy")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def test_ospfv3_multiple_route_policy_variables(template_definition_loader):
|
|
270
|
+
# Arrange
|
|
271
|
+
data = template_definition_loader("ospfv3-multiple-route-policy-variables.json")
|
|
272
|
+
|
|
273
|
+
# Act
|
|
274
|
+
values = find_template_values(data)
|
|
275
|
+
|
|
276
|
+
# Assert
|
|
277
|
+
var_policy = dict_get(values, ("ospfv3", "address-family", "ipv4", "table-map", "name"))
|
|
278
|
+
assert isinstance(var_policy, DeviceVariable)
|
|
279
|
+
assert var_policy._template_path == ("ospfv3", "address-family", "ipv4", "table-map", "name")
|
|
280
|
+
|
|
281
|
+
var_redistribute_nat = dict_get(values, ("ospfv3", "address-family", "ipv4", "redistribute", 1, "route-policy"))
|
|
282
|
+
assert isinstance(var_redistribute_nat, DeviceVariable)
|
|
283
|
+
assert var_redistribute_nat._template_path == (
|
|
284
|
+
"ospfv3",
|
|
285
|
+
"address-family",
|
|
286
|
+
"ipv4",
|
|
287
|
+
"redistribute",
|
|
288
|
+
"nat-route",
|
|
289
|
+
"route-policy",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
var_redistribute_eigrp = dict_get(values, ("ospfv3", "address-family", "ipv4", "redistribute", 2, "route-policy"))
|
|
293
|
+
assert isinstance(var_redistribute_eigrp, DeviceVariable)
|
|
294
|
+
assert var_redistribute_eigrp._template_path == (
|
|
295
|
+
"ospfv3",
|
|
296
|
+
"address-family",
|
|
297
|
+
"ipv4",
|
|
298
|
+
"redistribute",
|
|
299
|
+
"eigrp",
|
|
300
|
+
"route-policy",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def test_cisco_vpn(template_definition_loader):
|
|
305
|
+
# Arrange
|
|
306
|
+
data = template_definition_loader("cisco-vpn-definition.json")
|
|
307
|
+
|
|
308
|
+
# Act
|
|
309
|
+
values = find_template_values(data)
|
|
310
|
+
|
|
311
|
+
# Assert
|
|
312
|
+
var_natpool = dict_get(values, ("nat", "natpool", 1, "name"))
|
|
313
|
+
assert isinstance(var_natpool, DeviceVariable)
|
|
314
|
+
assert var_natpool._object_type == "variableName"
|
|
315
|
+
assert var_natpool._template_path == ("nat", "natpool", "vpn_nat_pool_name", "name")
|
|
@@ -7,6 +7,20 @@ from catalystwan.api.templates.device_variable import DeviceVariable
|
|
|
7
7
|
logger = logging.getLogger(__name__)
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
class _TreeValueList(list):
|
|
11
|
+
"""List of tree values with mapping-style access for a single item."""
|
|
12
|
+
|
|
13
|
+
def __getitem__(self, key):
|
|
14
|
+
if isinstance(key, str):
|
|
15
|
+
if len(self) != 1:
|
|
16
|
+
raise KeyError(key)
|
|
17
|
+
item = super().__getitem__(0)
|
|
18
|
+
if not isinstance(item, dict):
|
|
19
|
+
raise KeyError(key)
|
|
20
|
+
return item[key]
|
|
21
|
+
return super().__getitem__(key)
|
|
22
|
+
|
|
23
|
+
|
|
10
24
|
def find_template_values(
|
|
11
25
|
template_definition: dict,
|
|
12
26
|
templated_values: Optional[dict] = None,
|
|
@@ -14,6 +28,7 @@ def find_template_values(
|
|
|
14
28
|
target_key_default_values: List[str] = ["ignore", "notIgnore"],
|
|
15
29
|
target_key_for_template_value: str = "vipValue",
|
|
16
30
|
path: Optional[List[str]] = None,
|
|
31
|
+
_template_path_prefix: Optional[List[str]] = None,
|
|
17
32
|
) -> Dict[str, Union[str, list, dict]]:
|
|
18
33
|
"""Based on provided template definition generates a dictionary with template fields and values
|
|
19
34
|
|
|
@@ -32,6 +47,8 @@ def find_template_values(
|
|
|
32
47
|
"""
|
|
33
48
|
if path is None:
|
|
34
49
|
path = []
|
|
50
|
+
if _template_path_prefix is None:
|
|
51
|
+
_template_path_prefix = []
|
|
35
52
|
if templated_values is None:
|
|
36
53
|
templated_values = {}
|
|
37
54
|
# if value object is reached, try to extract the value
|
|
@@ -57,7 +74,7 @@ def find_template_values(
|
|
|
57
74
|
if var_name := template_definition.get("vipVariableName"):
|
|
58
75
|
new_value = DeviceVariable(name=var_name)
|
|
59
76
|
new_value._object_type = value
|
|
60
|
-
new_value._template_path = tuple(path)
|
|
77
|
+
new_value._template_path = tuple(_template_path_prefix + path)
|
|
61
78
|
current_nesting = get_nested_dict(templated_values, path[:-1])
|
|
62
79
|
current_nesting[field_key] = new_value
|
|
63
80
|
return templated_values
|
|
@@ -74,23 +91,100 @@ def find_template_values(
|
|
|
74
91
|
current_nesting = get_nested_dict(templated_values, path[:-1])
|
|
75
92
|
current_nesting[field_key] = template_value
|
|
76
93
|
elif isinstance(template_value, dict):
|
|
77
|
-
find_template_values(
|
|
94
|
+
find_template_values(
|
|
95
|
+
value,
|
|
96
|
+
templated_values,
|
|
97
|
+
target_key,
|
|
98
|
+
target_key_default_values,
|
|
99
|
+
target_key_for_template_value,
|
|
100
|
+
path,
|
|
101
|
+
_template_path_prefix,
|
|
102
|
+
)
|
|
78
103
|
elif isinstance(template_value, list):
|
|
79
104
|
current_nesting = get_nested_dict(templated_values, path[:-1])
|
|
80
|
-
current_nesting[field_key] =
|
|
105
|
+
current_nesting[field_key] = _TreeValueList()
|
|
81
106
|
for item in template_value:
|
|
82
|
-
|
|
107
|
+
item_path_prefix = _template_path_prefix + get_list_item_template_path(
|
|
108
|
+
template_definition,
|
|
109
|
+
item,
|
|
110
|
+
path,
|
|
111
|
+
target_key,
|
|
112
|
+
target_key_for_template_value,
|
|
113
|
+
)
|
|
114
|
+
item_value = find_template_values(
|
|
115
|
+
item,
|
|
116
|
+
{},
|
|
117
|
+
target_key,
|
|
118
|
+
target_key_default_values,
|
|
119
|
+
target_key_for_template_value,
|
|
120
|
+
path=[],
|
|
121
|
+
_template_path_prefix=item_path_prefix,
|
|
122
|
+
)
|
|
83
123
|
if item_value:
|
|
84
124
|
current_nesting[field_key].append(item_value)
|
|
85
125
|
return templated_values
|
|
86
126
|
|
|
87
127
|
# iterate the dict to extract values and assign them to their fields
|
|
88
|
-
for key
|
|
128
|
+
for key in ordered_item_keys(template_definition):
|
|
129
|
+
value = template_definition[key]
|
|
89
130
|
if isinstance(value, dict) and value not in target_key_default_values:
|
|
90
|
-
find_template_values(
|
|
131
|
+
find_template_values(
|
|
132
|
+
value,
|
|
133
|
+
templated_values,
|
|
134
|
+
target_key,
|
|
135
|
+
target_key_default_values,
|
|
136
|
+
target_key_for_template_value,
|
|
137
|
+
path + [key],
|
|
138
|
+
_template_path_prefix,
|
|
139
|
+
)
|
|
91
140
|
return templated_values
|
|
92
141
|
|
|
93
142
|
|
|
143
|
+
def ordered_item_keys(item: dict) -> List[str]:
|
|
144
|
+
"""Return payload keys in schema order, followed by any unlisted keys."""
|
|
145
|
+
priority_order = item.get("priority-order", [])
|
|
146
|
+
if not isinstance(priority_order, list):
|
|
147
|
+
priority_order = []
|
|
148
|
+
return [key for key in priority_order if key in item] + [key for key in item if key not in priority_order]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_list_item_template_path(
|
|
152
|
+
tree_definition: dict,
|
|
153
|
+
item: Any,
|
|
154
|
+
path: List[str],
|
|
155
|
+
target_key: str,
|
|
156
|
+
target_key_for_template_value: str,
|
|
157
|
+
) -> List[str]:
|
|
158
|
+
"""Build a stable path for a tree-list item from its concrete primary keys."""
|
|
159
|
+
item_path = list(path)
|
|
160
|
+
if not isinstance(item, dict):
|
|
161
|
+
return item_path
|
|
162
|
+
|
|
163
|
+
primary_keys = tree_definition.get("vipPrimaryKey", [])
|
|
164
|
+
if not isinstance(primary_keys, list):
|
|
165
|
+
return item_path
|
|
166
|
+
|
|
167
|
+
for key in ordered_item_keys(item):
|
|
168
|
+
if key not in primary_keys:
|
|
169
|
+
continue
|
|
170
|
+
field = item.get(key)
|
|
171
|
+
if not isinstance(field, dict):
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
field_type = field.get(target_key)
|
|
175
|
+
if field_type == "constant":
|
|
176
|
+
value = field.get(target_key_for_template_value)
|
|
177
|
+
elif field_type in ("variableName", "variable"):
|
|
178
|
+
value = field.get("vipVariableName")
|
|
179
|
+
else:
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
if value is not None and not isinstance(value, (dict, list)):
|
|
183
|
+
item_path.append(str(value))
|
|
184
|
+
|
|
185
|
+
return item_path
|
|
186
|
+
|
|
187
|
+
|
|
94
188
|
def get_nested_dict(d: dict, path: List[str], populate: bool = True):
|
|
95
189
|
current_dict = d
|
|
96
190
|
for path_key in path:
|
|
@@ -97,7 +97,7 @@ catalystwan/endpoints/configuration/feature_profile/sdwan/system.py,sha256=b8QFq
|
|
|
97
97
|
catalystwan/endpoints/configuration/feature_profile/sdwan/topology.py,sha256=h1pZ3WXhAnOOFRSj8XfaMb4hC1DZPhfx3tBoXeIzGSc,3676
|
|
98
98
|
catalystwan/endpoints/configuration/feature_profile/sdwan/transport.py,sha256=EFAsy9AvnGrh6I70jesrxbLChQGUfG_cAzlJPnfl780,9711
|
|
99
99
|
catalystwan/endpoints/configuration/feature_profile/sdwan/uc_voice.py,sha256=0yn4peVqLxljGvYGC5G6BpXUseP4lOC4WPxZ1jxJyoo,3398
|
|
100
|
-
catalystwan/endpoints/configuration/network_hierarchy.py,sha256=
|
|
100
|
+
catalystwan/endpoints/configuration/network_hierarchy.py,sha256=EAHYXgMntIcpSOtfW4bn1NRCmSTU3Daoe1AXHG9_dAM,2559
|
|
101
101
|
catalystwan/endpoints/configuration/packages.py,sha256=hcKee_1YYWLlwzSQ7fAoHIGvdGAlxDBVIhEnt0R27_o,1309
|
|
102
102
|
catalystwan/endpoints/configuration/policy/abstractions.py,sha256=d9W8YCNclZAHAEwRW3L113dKzPdHvUDYH042tM4bDGI,1417
|
|
103
103
|
catalystwan/endpoints/configuration/policy/definition/access_control_list.py,sha256=XJ6CfF_6QNjTy782yv_0mF-1GFca-KIlTb9ZyVO8RHI,1969
|
|
@@ -372,7 +372,7 @@ catalystwan/models/configuration/feature_profile/sdwan/uc_voice/voice_global.py,
|
|
|
372
372
|
catalystwan/models/configuration/feature_profile/sdwan/uc_voice/voice_tenant.py,sha256=7av6UzCBbztqFNbfhRIbJGDJg87n3QrTzZL9KT7Pems,1365
|
|
373
373
|
catalystwan/models/configuration/network_hierarchy/__init__.py,sha256=mOJHS_J4vCt6A6Zj1SD8iUQmK8f97zF1zBAP4U_iwgo,529
|
|
374
374
|
catalystwan/models/configuration/network_hierarchy/cflowd.py,sha256=Id3xvzKfFXhADJH7wmqxaqRSFk0SoTxa2EmmQYbn7t8,6394
|
|
375
|
-
catalystwan/models/configuration/network_hierarchy/node.py,sha256=
|
|
375
|
+
catalystwan/models/configuration/network_hierarchy/node.py,sha256=fQGaQ_spVY8y5q-vggU70IRXc3OzcNR-OG2sQ48hz3M,3148
|
|
376
376
|
catalystwan/models/configuration/network_hierarchy/security_logging.py,sha256=7Eeh8eYcLfQPISVBr0p5H3vvrTUVRxy8VgXXiCTGlmQ,3320
|
|
377
377
|
catalystwan/models/configuration/packages.py,sha256=udhaMRv4zatrxUKCdpnXacsCdgjM61M4NAvlTAjjma8,2037
|
|
378
378
|
catalystwan/models/configuration/policy_group.py,sha256=8A_TsNmjSRVx1BAlYUgQQWkpp_SizDL_ZTS8zUTtHgs,2235
|
|
@@ -473,6 +473,7 @@ catalystwan/tests/models/configuration/feature_profile/sdwan/policy_object/secur
|
|
|
473
473
|
catalystwan/tests/models/configuration/feature_profile/sdwan/topology/test_custom_control.py,sha256=9HMDeELC0KrNBW71l3QHyp3KLdYw0VsZVM6_1mu0oJg,1450
|
|
474
474
|
catalystwan/tests/models/test_digital_interface.py,sha256=tYz6xAjy-IqVhYjY5_C_dGLZnFwlm0s9WJEUjhzl8uw,2302
|
|
475
475
|
catalystwan/tests/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
476
|
+
catalystwan/tests/templates/conftest.py,sha256=-149nn4Zve0Kv1Qz-WXzYVqEqzWB4L-EaJ6KJ39tfHc,523
|
|
476
477
|
catalystwan/tests/templates/definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
477
478
|
catalystwan/tests/templates/definitions/aaa_basic.json,sha256=tc8_o5wmnmOciW4s-jU8Cpbf7VlK3qSoT_pnYla9Vi8,2909
|
|
478
479
|
catalystwan/tests/templates/definitions/aaa_complex.json,sha256=3oBmuQ64F36BQoN0nsVhp7sKX6X6pwt3glbr1XcG71A,19230
|
|
@@ -528,6 +529,9 @@ catalystwan/tests/templates/models/security_vsmart.py,sha256=xljsJU-nixZ4OF9mGoB
|
|
|
528
529
|
catalystwan/tests/templates/models/system_vsmart.py,sha256=I4bp_v98VXpOnMwq7jEplNJIb57b15EchCKTe-Y_ksA,1323
|
|
529
530
|
catalystwan/tests/templates/models/vpn_vsmart.py,sha256=jD5ikbxRZBL-I-BwH2DAeEsOCv1iQz4LitEqjHfDxOY,921
|
|
530
531
|
catalystwan/tests/templates/models/vpn_vsmart_interface.py,sha256=dygGcNY7V_0QicgeGrLi50HIHrI0N6JbWgyT3IzQ_Yo,1466
|
|
532
|
+
catalystwan/tests/templates/payload/cisco-vpn-definition.json,sha256=x9T4kHODdkInM3GHxOGDprRi8gOK9gg9jw8EkS6-lp0,3857
|
|
533
|
+
catalystwan/tests/templates/payload/ospf-multiple-route-policy-variables.json,sha256=XXNIH5zt3_QXNBRavEzVRaxTiQJZhXvYAu9PWyqNiQM,2506
|
|
534
|
+
catalystwan/tests/templates/payload/ospfv3-multiple-route-policy-variables.json,sha256=6_XFmhM3eMdKuC5Sq5h6g7fskgMKL8ox5YQ5ONM-8hQ,4229
|
|
531
535
|
catalystwan/tests/templates/schemas/aaa.json,sha256=agK-RfxHQfc2aiQl8pPN20YfqnSaJWDhZ76JYzH_NbQ,37703
|
|
532
536
|
catalystwan/tests/templates/schemas/basic/alias.json,sha256=NIMCKgaVxCFCMgJrpWOnS7ArnDMca8v9lLYKJYK9VEY,605
|
|
533
537
|
catalystwan/tests/templates/schemas/basic/basic.json,sha256=SPUn9exAckayvi4Gm-mukzjEJXp6xaGbTzu4ekwO1tA,574
|
|
@@ -554,7 +558,7 @@ catalystwan/tests/templates/schemas/vpn-vsmart-interface.json,sha256=4JkUmBhHeJV
|
|
|
554
558
|
catalystwan/tests/templates/schemas/vpn-vsmart.json,sha256=7HAG6WYoVxG_ZNrQHj3rVyHxcS8ZVe4twhjIsdXR-m8,14994
|
|
555
559
|
catalystwan/tests/templates/test_chose_model.py,sha256=JAxRaBDkg-9zAjDGD_TylmgefsNzcRO1HrGQQ3Iw9PE,1086
|
|
556
560
|
catalystwan/tests/templates/test_deserialize_model.py,sha256=uIDA7fQyESJaNbLdYKrFo_8X_flbQRame1dPCFlzsI0,2625
|
|
557
|
-
catalystwan/tests/templates/test_find_template_values.py,sha256=
|
|
561
|
+
catalystwan/tests/templates/test_find_template_values.py,sha256=ZC-WABbUw8St4jmsqU5azHOadncCg4NgXEKRTDgJOO4,10787
|
|
558
562
|
catalystwan/tests/templates/test_find_template_values_2.py,sha256=vERVA4rLv4xOq-BFe65jethDHh9FTg7X5goAxTv0zic,6818
|
|
559
563
|
catalystwan/tests/templates/test_generate_payload.py,sha256=9fuYPtb2gXjTE4RiiHM8EDCwlsFwLnSCNhE_Gz_aIFc,4598
|
|
560
564
|
catalystwan/tests/templates/test_serialize_model.py,sha256=lunkXGyMgUuvzfS2Vd-9WooHcOoUkLom6CUCOjkkXUY,1755
|
|
@@ -602,7 +606,7 @@ catalystwan/utils/dashboard.py,sha256=5DBDdnFuNEebo9R8UpXDETrYJsfZp-vv9OCr9ahU6V
|
|
|
602
606
|
catalystwan/utils/device_model.py,sha256=IPEQD7yYyhCH91Uw9P-dF34TcIEimYEnImYVaaQOjRY,15996
|
|
603
607
|
catalystwan/utils/dict.py,sha256=1IC4pcPFQ7yS39-ZWSFwyCvQKYzfZoxsz3YtJ-S6l7w,2110
|
|
604
608
|
catalystwan/utils/feature_template/choose_model.py,sha256=JoQ7l_TF2PTH8bciOSVAFavevo35n2F4DbusIIvapM8,953
|
|
605
|
-
catalystwan/utils/feature_template/find_template_values.py,sha256=
|
|
609
|
+
catalystwan/utils/feature_template/find_template_values.py,sha256=kxppXw0kO4XQHr9VTx3Se8plm7m9Ymr6LJNnw9yPPRg,8346
|
|
606
610
|
catalystwan/utils/model.py,sha256=AbuERvgU_3w16rFaKOA1QM3F-xL7xW13YTB-hFWRIEo,1692
|
|
607
611
|
catalystwan/utils/operation_status.py,sha256=SeT34c_x8F98mwl9x0-klvGTMKbrKSTY_w0zaKyuds8,584
|
|
608
612
|
catalystwan/utils/persona.py,sha256=fZEMM2wgvvSuNaGwsLk-qWJUuZzkhYm1C3YUnRGOqRw,147
|
|
@@ -618,7 +622,7 @@ catalystwan/version.py,sha256=X5yIMPIrWPbdoF9eJfwxfjcvWr7JMfWQF98hQhlGB5I,3032
|
|
|
618
622
|
catalystwan/vmanage_auth.py,sha256=HpJpnZwynlAKczEtwWVQFpKSe_n9_3wmXjs93g8JZh4,11393
|
|
619
623
|
catalystwan/workflows/backup_restore_device_templates.py,sha256=BoR2KlRbpCI6RxIWG7j7tucaLpw5w3ESN20dLhsrZFY,19796
|
|
620
624
|
catalystwan/workflows/tenant_migration.py,sha256=4KLbqUwH4gEegE92PCGfRd226eZd_crCUpaYUWoGXDI,9790
|
|
621
|
-
catalystwan-0.41.6.
|
|
622
|
-
catalystwan-0.41.6.
|
|
623
|
-
catalystwan-0.41.6.
|
|
624
|
-
catalystwan-0.41.6.
|
|
625
|
+
catalystwan-0.41.6.dev4.dist-info/LICENSE,sha256=97ROi91Vxrj_Jio2v8FI9E8-y6x2uYdQRaFlrEbVjkc,11375
|
|
626
|
+
catalystwan-0.41.6.dev4.dist-info/METADATA,sha256=SAS4xqPWU9i8UP_djiFucK_J22hB9tkQGMDLdKq2wxY,21271
|
|
627
|
+
catalystwan-0.41.6.dev4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
628
|
+
catalystwan-0.41.6.dev4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|