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.
- pyavd/__init__.py +1 -1
- pyavd/_cv/schema/__init__.py +48 -0
- pyavd/_cv/schema/cv_deploy.schema.pickle +0 -0
- pyavd/_cv/workflows/deploy_configs_to_cv.py +88 -2
- pyavd/_cv/workflows/deploy_to_cv.py +30 -32
- pyavd/_cv/workflows/models.py +14 -0
- pyavd/_cv/workflows/utils.py +27 -0
- pyavd/_eos_cli_config_gen/j2templates/compiled_templates/documentation__management_security.py +170 -41
- pyavd/_eos_cli_config_gen/j2templates/compiled_templates/documentation__mpls_and_ldp.py +60 -1
- pyavd/_eos_cli_config_gen/j2templates/compiled_templates/eos__ip_routing_vrfs.py +16 -3
- pyavd/_eos_cli_config_gen/j2templates/compiled_templates/eos__management_security.py +188 -1
- pyavd/_eos_cli_config_gen/j2templates/compiled_templates/eos__mpls.py +60 -1
- pyavd/_eos_cli_config_gen/j2templates/compiled_templates/eos__router_bgp.py +19 -3
- pyavd/_eos_cli_config_gen/schema/__init__.py +1036 -5
- pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.pickle +0 -0
- pyavd/_eos_designs/eos_designs_facts/schema/eos_designs.schema.pickle +0 -0
- pyavd/_eos_designs/eos_designs_facts/schema/protocol.py +4 -4
- pyavd/_eos_designs/eos_designs_facts/vlans.py +1 -1
- pyavd/_eos_designs/schema/__init__.py +1811 -132
- pyavd/_eos_designs/schema/eos_designs.schema.pickle +0 -0
- pyavd/_eos_designs/shared_utils/misc.py +40 -3
- pyavd/_eos_designs/structured_config/base/__init__.py +15 -468
- pyavd/_eos_designs/structured_config/base/aaa_settings.py +193 -0
- pyavd/_eos_designs/structured_config/base/dns_settings.py +52 -0
- pyavd/_eos_designs/structured_config/base/logging.py +83 -0
- pyavd/_eos_designs/structured_config/base/monitor_connectivity.py +69 -0
- pyavd/_eos_designs/structured_config/base/ptp.py +126 -0
- pyavd/_eos_designs/structured_config/base/router_bgp.py +66 -0
- pyavd/_eos_designs/structured_config/connected_endpoints/ethernet_interfaces.py +9 -2
- pyavd/_eos_designs/structured_config/connected_endpoints/port_channel_interfaces.py +30 -8
- pyavd/_eos_designs/structured_config/connected_endpoints/utils.py +22 -1
- pyavd/_eos_designs/structured_config/constants.py +2 -0
- pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/utils.py +2 -2
- pyavd/_eos_designs/structured_config/inband_management/__init__.py +101 -156
- pyavd/_eos_designs/structured_config/network_services/ip_access_lists.py +8 -0
- pyavd/_eos_designs/structured_config/network_services/vlan_interfaces.py +21 -0
- pyavd/_eos_designs/structured_config/underlay/loopback_interfaces.py +30 -6
- pyavd/_eos_designs/structured_config/underlay/router_isis.py +10 -6
- pyavd/_schema/avd_meta_schema.pickle +0 -0
- pyavd/_schema/schemas.json.gz +0 -0
- {pyavd-6.2.0.dev2.dist-info → pyavd-6.2.0.dev3.dist-info}/METADATA +1 -1
- {pyavd-6.2.0.dev2.dist-info → pyavd-6.2.0.dev3.dist-info}/RECORD +45 -38
- {pyavd-6.2.0.dev2.dist-info → pyavd-6.2.0.dev3.dist-info}/WHEEL +0 -0
- {pyavd-6.2.0.dev2.dist-info → pyavd-6.2.0.dev3.dist-info}/licenses/pyavd/LICENSE +0 -0
- {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.
|
|
21
|
+
__version__ = "6.2.0.dev3"
|
|
22
22
|
|
|
23
23
|
__all__ = [
|
|
24
24
|
"get_avd_facts",
|
pyavd/_cv/schema/__init__.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 `
|
|
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
|
-
|
|
76
|
-
|
|
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
|
|
130
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
pyavd/_cv/workflows/models.py
CHANGED
|
@@ -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
|