pyavd 6.2.0.dev2__py3-none-any.whl → 6.2.0.dev3__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.
Files changed (45) hide show
  1. pyavd/__init__.py +1 -1
  2. pyavd/_cv/schema/__init__.py +48 -0
  3. pyavd/_cv/schema/cv_deploy.schema.pickle +0 -0
  4. pyavd/_cv/workflows/deploy_configs_to_cv.py +88 -2
  5. pyavd/_cv/workflows/deploy_to_cv.py +30 -32
  6. pyavd/_cv/workflows/models.py +14 -0
  7. pyavd/_cv/workflows/utils.py +27 -0
  8. pyavd/_eos_cli_config_gen/j2templates/compiled_templates/documentation__management_security.py +170 -41
  9. pyavd/_eos_cli_config_gen/j2templates/compiled_templates/documentation__mpls_and_ldp.py +60 -1
  10. pyavd/_eos_cli_config_gen/j2templates/compiled_templates/eos__ip_routing_vrfs.py +16 -3
  11. pyavd/_eos_cli_config_gen/j2templates/compiled_templates/eos__management_security.py +188 -1
  12. pyavd/_eos_cli_config_gen/j2templates/compiled_templates/eos__mpls.py +60 -1
  13. pyavd/_eos_cli_config_gen/j2templates/compiled_templates/eos__router_bgp.py +19 -3
  14. pyavd/_eos_cli_config_gen/schema/__init__.py +1036 -5
  15. pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.pickle +0 -0
  16. pyavd/_eos_designs/eos_designs_facts/schema/eos_designs.schema.pickle +0 -0
  17. pyavd/_eos_designs/eos_designs_facts/schema/protocol.py +4 -4
  18. pyavd/_eos_designs/eos_designs_facts/vlans.py +1 -1
  19. pyavd/_eos_designs/schema/__init__.py +1811 -132
  20. pyavd/_eos_designs/schema/eos_designs.schema.pickle +0 -0
  21. pyavd/_eos_designs/shared_utils/misc.py +40 -3
  22. pyavd/_eos_designs/structured_config/base/__init__.py +15 -468
  23. pyavd/_eos_designs/structured_config/base/aaa_settings.py +193 -0
  24. pyavd/_eos_designs/structured_config/base/dns_settings.py +52 -0
  25. pyavd/_eos_designs/structured_config/base/logging.py +83 -0
  26. pyavd/_eos_designs/structured_config/base/monitor_connectivity.py +69 -0
  27. pyavd/_eos_designs/structured_config/base/ptp.py +126 -0
  28. pyavd/_eos_designs/structured_config/base/router_bgp.py +66 -0
  29. pyavd/_eos_designs/structured_config/connected_endpoints/ethernet_interfaces.py +9 -2
  30. pyavd/_eos_designs/structured_config/connected_endpoints/port_channel_interfaces.py +30 -8
  31. pyavd/_eos_designs/structured_config/connected_endpoints/utils.py +22 -1
  32. pyavd/_eos_designs/structured_config/constants.py +2 -0
  33. pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/utils.py +2 -2
  34. pyavd/_eos_designs/structured_config/inband_management/__init__.py +101 -156
  35. pyavd/_eos_designs/structured_config/network_services/ip_access_lists.py +8 -0
  36. pyavd/_eos_designs/structured_config/network_services/vlan_interfaces.py +21 -0
  37. pyavd/_eos_designs/structured_config/underlay/loopback_interfaces.py +30 -6
  38. pyavd/_eos_designs/structured_config/underlay/router_isis.py +10 -6
  39. pyavd/_schema/avd_meta_schema.pickle +0 -0
  40. pyavd/_schema/schemas.json.gz +0 -0
  41. {pyavd-6.2.0.dev2.dist-info → pyavd-6.2.0.dev3.dist-info}/METADATA +1 -1
  42. {pyavd-6.2.0.dev2.dist-info → pyavd-6.2.0.dev3.dist-info}/RECORD +45 -38
  43. {pyavd-6.2.0.dev2.dist-info → pyavd-6.2.0.dev3.dist-info}/WHEEL +0 -0
  44. {pyavd-6.2.0.dev2.dist-info → pyavd-6.2.0.dev3.dist-info}/licenses/pyavd/LICENSE +0 -0
  45. {pyavd-6.2.0.dev2.dist-info → pyavd-6.2.0.dev3.dist-info}/top_level.txt +0 -0
pyavd/__init__.py CHANGED
@@ -18,7 +18,7 @@ PYAVD_PRERELEASE = "" # Set this to aN or bN for alpha and beta releases of pya
18
18
  __author__ = "Arista Networks"
19
19
  __copyright__ = "Copyright 2023-2026 Arista Networks"
20
20
  __license__ = "Apache 2.0"
21
- __version__ = "6.2.0.dev2"
21
+ __version__ = "6.2.0.dev3"
22
22
 
23
23
  __all__ = [
24
24
  "get_avd_facts",
@@ -105,6 +105,7 @@ class CvDeploy(AvdModel):
105
105
  "system_mac_address": {"type": str},
106
106
  "cv_tags": {"type": EosCliConfigGen.Metadata.CvTags},
107
107
  "cv_pathfinder": {"type": EosCliConfigGen.Metadata.CvPathfinder},
108
+ "cv_use_static_config_manifest": {"type": bool},
108
109
  }
109
110
  _allow_other_keys: ClassVar[bool] = True
110
111
  is_deployed: bool | None
@@ -119,6 +120,18 @@ class CvDeploy(AvdModel):
119
120
  cv_tags: EosCliConfigGen.Metadata.CvTags
120
121
  cv_pathfinder: EosCliConfigGen.Metadata.CvPathfinder
121
122
  """Metadata used for CV Pathfinder visualization on CloudVision."""
123
+ cv_use_static_config_manifest: bool | None
124
+ """
125
+ PREVIEW: This option is marked as "preview", meaning the data models or generated configuration can
126
+ change at any time.
127
+
128
+ The device configuration is expected to be deployed via the
129
+ `static_config_manifest` / `cv_static_config_manifest` hierarchy instead of the
130
+ flat "AVD
131
+ Configurations" layout in the Static Configlet Studio.
132
+ The device will still be verified and
133
+ onboarded in the Inventory & Topology Studio.
134
+ """
122
135
 
123
136
  if TYPE_CHECKING:
124
137
 
@@ -130,6 +143,7 @@ class CvDeploy(AvdModel):
130
143
  system_mac_address: str | None | UndefinedType = Undefined,
131
144
  cv_tags: EosCliConfigGen.Metadata.CvTags | UndefinedType = Undefined,
132
145
  cv_pathfinder: EosCliConfigGen.Metadata.CvPathfinder | UndefinedType = Undefined,
146
+ cv_use_static_config_manifest: bool | None | UndefinedType = Undefined,
133
147
  ) -> None:
134
148
  """
135
149
  Metadata.
@@ -145,6 +159,16 @@ class CvDeploy(AvdModel):
145
159
  system_mac_address: system_mac_address
146
160
  cv_tags: cv_tags
147
161
  cv_pathfinder: Metadata used for CV Pathfinder visualization on CloudVision.
162
+ cv_use_static_config_manifest:
163
+ PREVIEW: This option is marked as "preview", meaning the data models or generated configuration can
164
+ change at any time.
165
+
166
+ The device configuration is expected to be deployed via the
167
+ `static_config_manifest` / `cv_static_config_manifest` hierarchy instead of the
168
+ flat "AVD
169
+ Configurations" layout in the Static Configlet Studio.
170
+ The device will still be verified and
171
+ onboarded in the Inventory & Topology Studio.
148
172
 
149
173
  """
150
174
 
@@ -155,6 +179,7 @@ class CvDeploy(AvdModel):
155
179
  "cv_device_tags": {"type": CvDeviceTags},
156
180
  "cv_interface_tags": {"type": CvInterfaceTags},
157
181
  "cv_pathfinder_metadata": {"type": EosCliConfigGen.Metadata.CvPathfinder},
182
+ "cv_use_static_config_manifest": {"type": bool},
158
183
  "metadata": {"type": Metadata},
159
184
  }
160
185
  _allow_other_keys: ClassVar[bool] = True
@@ -208,6 +233,18 @@ class CvDeploy(AvdModel):
208
233
  """
209
234
  cv_pathfinder_metadata: EosCliConfigGen.Metadata.CvPathfinder
210
235
  """Metadata used for CV Pathfinder visualization on CloudVision."""
236
+ cv_use_static_config_manifest: bool | None
237
+ """
238
+ PREVIEW: This option is marked as "preview", meaning the data models or generated configuration can
239
+ change at any time.
240
+
241
+ The device configuration is expected to be deployed via the
242
+ `static_config_manifest` / `cv_static_config_manifest` hierarchy instead of the
243
+ flat "AVD
244
+ Configurations" layout in the Static Configlet Studio.
245
+ The device will still be verified and
246
+ onboarded in the Inventory & Topology Studio.
247
+ """
211
248
  metadata: Metadata
212
249
  """
213
250
  Metadata from the `eos_designs` role, loaded automatically from structured configs.
@@ -230,6 +267,7 @@ class CvDeploy(AvdModel):
230
267
  cv_device_tags: CvDeviceTags | UndefinedType = Undefined,
231
268
  cv_interface_tags: CvInterfaceTags | UndefinedType = Undefined,
232
269
  cv_pathfinder_metadata: EosCliConfigGen.Metadata.CvPathfinder | UndefinedType = Undefined,
270
+ cv_use_static_config_manifest: bool | None | UndefinedType = Undefined,
233
271
  metadata: Metadata | UndefinedType = Undefined,
234
272
  ) -> None:
235
273
  """
@@ -275,6 +313,16 @@ class CvDeploy(AvdModel):
275
313
  Subclass of AvdList
276
314
  with `CvInterfaceTagsItem` items.
277
315
  cv_pathfinder_metadata: Metadata used for CV Pathfinder visualization on CloudVision.
316
+ cv_use_static_config_manifest:
317
+ PREVIEW: This option is marked as "preview", meaning the data models or generated configuration can
318
+ change at any time.
319
+
320
+ The device configuration is expected to be deployed via the
321
+ `static_config_manifest` / `cv_static_config_manifest` hierarchy instead of the
322
+ flat "AVD
323
+ Configurations" layout in the Static Configlet Studio.
324
+ The device will still be verified and
325
+ onboarded in the Inventory & Topology Studio.
278
326
  metadata:
279
327
  Metadata from the `eos_designs` role, loaded automatically from structured configs.
280
328
  For standalone
Binary file
@@ -3,13 +3,14 @@
3
3
  # that can be found in the LICENSE file.
4
4
  from __future__ import annotations
5
5
 
6
+ from asyncio import gather
6
7
  from logging import getLogger
7
- from typing import TYPE_CHECKING
8
+ from typing import TYPE_CHECKING, cast
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from pyavd._cv.client import CVClient
11
12
 
12
- from .models import CVEosConfig, DeployToCvResult
13
+ from .models import CVDeviceDeployment, CVEosConfig, DeployToCvResult
13
14
 
14
15
  LOGGER = getLogger(__name__)
15
16
 
@@ -169,3 +170,88 @@ async def deploy_configlet_containers_to_cv(configs: list[CVEosConfig], workspac
169
170
  container_id=CONFIGLET_CONTAINER_ID,
170
171
  child_assignment_ids=list(update_device_container_ids.union(existing_device_containers_by_id)),
171
172
  )
173
+
174
+
175
+ async def delete_configs_from_cv(device_deployments: list[CVDeviceDeployment], result: DeployToCvResult, cv_client: CVClient) -> None:
176
+ """
177
+ Delete leftovers device configurations from a previous `deploy_configs_to_cv` run.
178
+
179
+ For devices with `use_static_config_manifest=True`, the device configuration is expected to be deployed
180
+ via the static config manifest instead of the flat "AVD Configurations" layout. This function removes
181
+ the corresponding container (avd-<serial>) and configlet for these devices if they exist.
182
+
183
+ If all children are removed, the root container itself is deleted and unregistered from the Studio
184
+ """
185
+ workspace_id = result.workspace.id
186
+
187
+ # Build a stable list of target IDs from manifest-opted devices.
188
+ target_ids = [
189
+ f"{CONFIGLET_ID_PREFIX}{device_deployment.device.serial_number}"
190
+ for device_deployment in device_deployments
191
+ if device_deployment.use_static_config_manifest and device_deployment.device.serial_number
192
+ ]
193
+ if not target_ids:
194
+ return
195
+ target_ids_set = set(target_ids)
196
+
197
+ # Find what actually exists on CV for our targets and the root container.
198
+ existing_configlets, existing_containers = await gather(
199
+ cv_client.get_configlets(workspace_id=workspace_id, configlet_ids=target_ids),
200
+ cv_client.get_configlet_containers(workspace_id=workspace_id, container_ids=[*target_ids, CONFIGLET_CONTAINER_ID]),
201
+ )
202
+
203
+ configlets_by_id = {cast("str", configlet.key.configlet_id): configlet for configlet in existing_configlets}
204
+ containers_by_id = {cast("str", container.key.configlet_assignment_id): container for container in existing_containers}
205
+ root_cv_container = containers_by_id.pop(CONFIGLET_CONTAINER_ID, None)
206
+
207
+ # Delete leftover per-device containers and configlets in parallel.
208
+ delete_coroutines = [cv_client.delete_configlet_container(workspace_id=workspace_id, assignment_id=container_id) for container_id in containers_by_id]
209
+ if configlets_by_id:
210
+ delete_coroutines.append(cv_client.delete_configlets(workspace_id=workspace_id, configlet_ids=list(configlets_by_id)))
211
+
212
+ if delete_coroutines:
213
+ LOGGER.info(
214
+ "delete_configs_from_cv: Removing %s device containers and %s configlets.",
215
+ len(containers_by_id),
216
+ len(configlets_by_id),
217
+ )
218
+ await gather(*delete_coroutines)
219
+
220
+ result.removed_configs.extend(cast("str", configlet.display_name) for configlet in configlets_by_id.values())
221
+
222
+ # Reconcile the root "AVD Configurations" container if it exists.
223
+ if root_cv_container is None:
224
+ return
225
+
226
+ existing_child_ids = root_cv_container.child_assignment_ids.values
227
+ remaining_child_ids = [child_id for child_id in existing_child_ids if child_id not in target_ids_set]
228
+
229
+ if remaining_child_ids:
230
+ # Root still has non-target children. Update its child list only if we actually removed something.
231
+ if remaining_child_ids != list(existing_child_ids):
232
+ LOGGER.info("delete_configs_from_cv: Updating root container children (%s remaining).", len(remaining_child_ids))
233
+ await cv_client.set_configlet_container(
234
+ workspace_id=workspace_id,
235
+ container_id=CONFIGLET_CONTAINER_ID,
236
+ child_assignment_ids=remaining_child_ids,
237
+ )
238
+ return
239
+
240
+ # No remaining children, delete the root container and unregister from studio roots.
241
+ LOGGER.info("delete_configs_from_cv: All device containers removed. Cleaning up root container.")
242
+ await cv_client.delete_configlet_container(workspace_id=workspace_id, assignment_id=CONFIGLET_CONTAINER_ID)
243
+
244
+ root_containers: list = await cv_client.get_studio_inputs_with_path(
245
+ studio_id=STATIC_CONFIGLET_STUDIO_ID,
246
+ workspace_id=workspace_id,
247
+ input_path=["configletAssignmentRoots"],
248
+ default_value=[],
249
+ )
250
+ if CONFIGLET_CONTAINER_ID in root_containers:
251
+ root_containers.remove(CONFIGLET_CONTAINER_ID)
252
+ await cv_client.set_studio_inputs(
253
+ studio_id=STATIC_CONFIGLET_STUDIO_ID,
254
+ workspace_id=workspace_id,
255
+ input_path=["configletAssignmentRoots"],
256
+ inputs=root_containers,
257
+ )
@@ -10,7 +10,7 @@ from pyavd._cv.client import CVClient
10
10
  from pyavd._cv.client.exceptions import CVClientException
11
11
 
12
12
  from .create_workspace_on_cv import create_workspace_on_cv
13
- from .deploy_configs_to_cv import deploy_configs_to_cv
13
+ from .deploy_configs_to_cv import delete_configs_from_cv, deploy_configs_to_cv
14
14
  from .deploy_cv_pathfinder_metadata_to_cv import deploy_cv_pathfinder_metadata_to_cv
15
15
  from .deploy_static_config_studio_manifest_to_cv import deploy_static_config_studio_manifest_to_cv
16
16
  from .deploy_studio_inputs_to_cv import deploy_studio_inputs_to_cv
@@ -20,15 +20,13 @@ from .finalize_workspace_on_cv import finalize_workspace_on_cv
20
20
  from .models import (
21
21
  CloudVision,
22
22
  CVChangeControl,
23
- CVDeviceTag,
24
- CVEosConfig,
25
- CVInterfaceTag,
26
- CVPathfinderMetadata,
23
+ CVDeviceDeployment,
27
24
  CVStudioInputs,
28
25
  CVTimeOuts,
29
26
  CVWorkspace,
30
27
  DeployToCvResult,
31
28
  )
29
+ from .utils import extract_from_device_deployments
32
30
  from .verify_devices_on_cv import verify_devices_on_cv
33
31
  from .verify_inputs import verify_device_inputs
34
32
 
@@ -42,12 +40,9 @@ async def deploy_to_cv(
42
40
  cloudvision: CloudVision,
43
41
  workspace: CVWorkspace | None = None,
44
42
  change_control: CVChangeControl | None = None,
45
- configs: list[CVEosConfig] | None = None,
43
+ device_deployments: list[CVDeviceDeployment] | None = None,
46
44
  static_config_manifest: AvdManifest | None = None,
47
- device_tags: list[CVDeviceTag] | None = None,
48
- interface_tags: list[CVInterfaceTag] | None = None,
49
45
  studio_inputs: list[CVStudioInputs] | None = None,
50
- cv_pathfinder_metadata: list[CVPathfinderMetadata] | None = None,
51
46
  skip_missing_devices: bool = False,
52
47
  strict_system_mac_address: bool = False,
53
48
  strict_tags: bool = True,
@@ -56,7 +51,7 @@ async def deploy_to_cv(
56
51
  """
57
52
  Deploy various objects to CloudVision.
58
53
 
59
- For any device referred under `configs`, `device_tags` and `interface_tags` the device:
54
+ For any device referred under `device_deployments`:
60
55
  - The device must be present in the CloudVision Inventory and onboarded to the "Inventory & Topology Studio".
61
56
  - TODO: See if we can onboard ZTP devices and/or preprovision.
62
57
  - The hostname will we updated in the I&T Studio.
@@ -72,9 +67,8 @@ async def deploy_to_cv(
72
67
  change_control: CloudVision Change Control to create for the deployment. \
73
68
  It is not supported to reuse an existing Change Control, so the `id` field should not be set in the given CVChangeControl object. \
74
69
  The `id` and `state` properties will be inplace updated in the given CVChangeControl object.
75
- configs: Configs to be deployed using the "Static Configlet Studio".
76
- device_tags: Device Tags to be deployed and assigned.
77
- interface_tags: Interface Tags to be deployed and assigned.
70
+ device_deployments: Per-device deployment objects containing configs, tags, and metadata to be deployed.
71
+ static_config_manifest: Static Configuration Studio manifest to deploy.
78
72
  studio_inputs: Studio Inputs to be deployed. \
79
73
  It is not supported to update overlapping input paths for the same studio in the same deployment.
80
74
  cv_pathfinder_metadata: Special metadata for CV Pathfinder solution. Metadata will be combined and deployed to the hidden metadata studio.
@@ -126,16 +120,24 @@ async def deploy_to_cv(
126
120
  """
127
121
  LOGGER.info("deploy_to_cv:")
128
122
  result = DeployToCvResult(workspace=workspace or CVWorkspace(), change_control=change_control)
129
- if device_tags is None:
130
- device_tags = []
131
- if interface_tags is None:
132
- interface_tags = []
133
- if configs is None:
134
- configs = []
123
+ if device_deployments is None:
124
+ device_deployments = []
135
125
  if studio_inputs is None:
136
126
  studio_inputs = []
137
- if cv_pathfinder_metadata is None:
138
- cv_pathfinder_metadata = []
127
+
128
+ # Extract sub-lists from device deployments.
129
+ # TODO: Refactor sub-workflows to accept list[CVDeviceDeployment] directly and extract what they need internally.
130
+ devices = [device_deployment.device for device_deployment in device_deployments]
131
+ configs, device_tags, interface_tags, cv_pathfinder_metadata = extract_from_device_deployments(device_deployments)
132
+
133
+ # Warn if devices are opted into the manifest but no manifest is provided.
134
+ if static_config_manifest is None and any(device_deployment.use_static_config_manifest for device_deployment in device_deployments):
135
+ manifest_device_count = sum(1 for device_deployment in device_deployments if device_deployment.use_static_config_manifest)
136
+ result.warnings.append(
137
+ f"{manifest_device_count} device(s) have 'cv_use_static_config_manifest' set to 'true' but no static config manifest was provided. "
138
+ "These devices will not have their configuration deployed to CloudVision."
139
+ )
140
+
139
141
  try:
140
142
  async with CVClient(
141
143
  servers=cloudvision.servers,
@@ -151,17 +153,6 @@ async def deploy_to_cv(
151
153
  # Create workspace
152
154
  await create_workspace_on_cv(workspace=result.workspace, cv_client=cv_client)
153
155
 
154
- # Form deduplicated list of targeted CVDevices
155
- devices = list(
156
- {
157
- id(device): device
158
- for device in (
159
- [tag.device for tag in device_tags if tag.device is not None]
160
- + [tag.device for tag in interface_tags if tag.device is not None]
161
- + [config.device for config in configs if config.device is not None]
162
- )
163
- }.values()
164
- )
165
156
  # Check structured config of the targeted devices for overlapping `serial_number`s or `system_mac_address`es.
166
157
  verify_device_inputs(devices, result.warnings, strict_system_mac_address=strict_system_mac_address)
167
158
 
@@ -230,6 +221,13 @@ async def deploy_to_cv(
230
221
  cv_client=cv_client,
231
222
  )
232
223
 
224
+ # Delete any leftover device configs for devices managed by the static config manifest
225
+ await delete_configs_from_cv(
226
+ device_deployments=device_deployments,
227
+ result=result,
228
+ cv_client=cv_client,
229
+ )
230
+
233
231
  except CVClientException as e:
234
232
  result.errors.append(e)
235
233
  result.failed = True
@@ -270,6 +270,20 @@ class CVEosConfig:
270
270
  """By default "AVD_<hostname>"""
271
271
 
272
272
 
273
+ @dataclass
274
+ class CVDeviceDeployment:
275
+ """All deployment objects for a single device."""
276
+
277
+ device: CVDevice
278
+ use_static_config_manifest: bool = False
279
+ """When `True`, the device configuration is expected to be deployed via the static config manifest hierarchy
280
+ instead of the flat "AVD Configurations" layout."""
281
+ eos_config: CVEosConfig | None = None
282
+ device_tags: list[CVDeviceTag] = field(default_factory=list)
283
+ interface_tags: list[CVInterfaceTag] = field(default_factory=list)
284
+ cv_pathfinder_metadata: CVPathfinderMetadata | None = None
285
+
286
+
273
287
  @dataclass
274
288
  class CVTimeOuts:
275
289
  """Timeouts in seconds."""
@@ -0,0 +1,27 @@
1
+ # Copyright (c) 2023-2026 Arista Networks, Inc.
2
+ # Use of this source code is governed by the Apache License 2.0
3
+ # that can be found in the LICENSE file.
4
+ from __future__ import annotations
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from .models import CVDeviceDeployment, CVDeviceTag, CVEosConfig, CVInterfaceTag, CVPathfinderMetadata
10
+
11
+
12
+ def extract_from_device_deployments(
13
+ device_deployments: list[CVDeviceDeployment],
14
+ ) -> tuple[list[CVEosConfig], list[CVDeviceTag], list[CVInterfaceTag], list[CVPathfinderMetadata]]:
15
+ """Extract configs, device tags, interface tags and pathfinder metadata from a list of CVDeviceDeployment objects."""
16
+ configs: list[CVEosConfig] = []
17
+ device_tags: list[CVDeviceTag] = []
18
+ interface_tags: list[CVInterfaceTag] = []
19
+ cv_pathfinder_metadata: list[CVPathfinderMetadata] = []
20
+ for device_deployment in device_deployments:
21
+ if device_deployment.eos_config is not None:
22
+ configs.append(device_deployment.eos_config)
23
+ device_tags.extend(device_deployment.device_tags)
24
+ interface_tags.extend(device_deployment.interface_tags)
25
+ if device_deployment.cv_pathfinder_metadata is not None:
26
+ cv_pathfinder_metadata.append(device_deployment.cv_pathfinder_metadata)
27
+ return configs, device_tags, interface_tags, cv_pathfinder_metadata