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.
@@ -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.get("tunnel-interface", {}).get("color", {}).get("value")
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(value, templated_values, path=path)
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
- item_value = find_template_values(item, {})
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, value in template_definition.items():
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(value, templated_values, path=path + [key])
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: catalystwan
3
- Version: 0.41.6.dev2
3
+ Version: 0.41.6.dev4
4
4
  Summary: Cisco Catalyst WAN SDK for Python
5
5
  Author: sbasan
6
6
  Author-email: sbasan@cisco.com
@@ -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=WgJA6CvU4m6ptKOe5nsCSBIGh3Sdq_WA2WQHo9kVsZE,2243
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=Ex1OMLl00uU3y4fSSD-xl-t1AJYotYhFRr0KQ826pK8,2792
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=x1JJ0y5R4Qu-QRZQcoX52Vf-lnltWPQxODM29I5FIgM,8321
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=fE5AtB-swZx8Fc2Lwbh60T_fBrzqbx9HNf9da4zWxP0,5273
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.dev2.dist-info/LICENSE,sha256=97ROi91Vxrj_Jio2v8FI9E8-y6x2uYdQRaFlrEbVjkc,11375
622
- catalystwan-0.41.6.dev2.dist-info/METADATA,sha256=NtN6NtB2ee8-1IibahOiDjd0u5xHGkGRCLS0A3YqSgw,21271
623
- catalystwan-0.41.6.dev2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
624
- catalystwan-0.41.6.dev2.dist-info/RECORD,,
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,,