Skip to content

Commit

Permalink
Improve docstrings etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
ClausHolbechArista committed Jan 15, 2024
1 parent 29744b9 commit 920b511
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ title: arista.avd.deploy_to_cv
!!! note
Always use the FQCN (Fully Qualified Collection Name) `arista.avd.deploy_to_cv` when using this plugin.

!!! warning "This module is in **preview** mode"
This module is not guaranteed to have a backwards compatible interface.

Deploy various objects to CloudVision

## Synopsis
Expand All @@ -31,23 +34,23 @@ The \`arista.avd.deploy\_to\_cv\` module is an Ansible Action Plugin providing t

| Argument | Type | Required | Default | Value Restrictions | Description |
| -------- | ---- | -------- | ------- | ------------------ | ----------- |
| cv_servers | list | True | None | | List of hostnames or IP addresses for CloudVision instance to deploy to. |
| cv_token | str | True | None | | Service account token. It is strongly recommended to use Vault for this. |
| cv_verify_certs | bool | optional | True | | Verifies CloudVison server certificates. |
| configuration_dir | str | True | None | | Path to directory containing .cfg files with EOS configurations. |
| structured_config_dir | str | True | None | | Path to directory containing files with AVD structured configurations.
| <samp>cv_servers</samp> | list | True | None | | List of hostnames or IP addresses for CloudVision instance to deploy to. |
| <samp>cv_token</samp> | str | True | None | | Service account token. It is strongly recommended to use Vault for this. |
| <samp>cv_verify_certs</samp> | bool | optional | True | | Verifies CloudVison server certificates. |
| <samp>configuration_dir</samp> | str | True | None | | Path to directory containing .cfg files with EOS configurations. |
| <samp>structured_config_dir</samp> | str | True | None | | Path to directory containing files with AVD structured configurations.
If found, the \`serial\_number\` or \`system\_mac\_address\` will be used to identify the Device on CloudVision.
Any tags found in the structured configuration metadata will be applied to the Device and/or Interfaces. |
| structured_config_suffix | str | optional | yml | | File suffix for AVD structured configuration files. |
| device_list | list | True | None | | List of devices to deploy. The names are used to find AVD structured configuration and EOS configuration files. |
| strict_tags | bool | optional | False | | If \`True\` other tags associated with the devices will get removed. Otherwise other tags will be left as\-is. |
| skip_missing_devices | bool | optional | False | | If \`True\` anything that can be deployed will get deployed. Otherwise the Workspace will be abandoned on any issue. |
| configlet_name_template | str | optional | AVD-${hostname} | | Python String Template to use for creating the configlet name for each device configuration. |
| workspace | dict | optional | None | | CloudVision Workspace to create or use for the deployment. |
| name | str | optional | None | | Optional name to use for the created Workspace. By default the name will be \`AVD \<timestamp\>\`. |
| description | str | optional | None | | Optional description to use for the created Workspace. |
| id | str | optional | None | | Optional ID to use for the created Workspace. If there is already a workspace with the same ID, it must be in the \'pending\' state. |
| requested_state | str | optional | built | Valid values:<br>- <code>pending</code><br>- <code>built</code><br>- <code>submitted</code><br>- <code>abandoned</code><br>- <code>deleted</code> | The requested state for the Workspace.
| <samp>structured_config_suffix</samp> | str | optional | yml | | File suffix for AVD structured configuration files. |
| <samp>device_list</samp> | list | True | None | | List of devices to deploy. The names are used to find AVD structured configuration and EOS configuration files. |
| <samp>strict_tags</samp> | bool | optional | False | | If \`True\` other tags associated with the devices will get removed. Otherwise other tags will be left as\-is. |
| <samp>skip_missing_devices</samp> | bool | optional | False | | If \`True\` anything that can be deployed will get deployed. Otherwise the Workspace will be abandoned on any issue. |
| <samp>configlet_name_template</samp> | str | optional | AVD-${hostname} | | Python String Template to use for creating the configlet name for each device configuration. |
| <samp>workspace</samp> | dict | optional | None | | CloudVision Workspace to create or use for the deployment. |
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;name</samp> | str | optional | None | | Optional name to use for the created Workspace. By default the name will be \`AVD \<timestamp\>\`. |
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;description</samp> | str | optional | None | | Optional description to use for the created Workspace. |
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;id</samp> | str | optional | None | | Optional ID to use for the created Workspace. If there is already a workspace with the same ID, it must be in the \'pending\' state. |
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;requested_state</samp> | str | optional | built | Valid values:<br>- <code>pending</code><br>- <code>built</code><br>- <code>submitted</code><br>- <code>abandoned</code><br>- <code>deleted</code> | The requested state for the Workspace.

\- \`\"pending\"\`\: Leave the Workspace in pending state.
\- \`\"built\"\`\: Build the Workspace but do not submit.
Expand All @@ -56,20 +59,20 @@ Any tags found in the structured configuration metadata will be applied to the D
Used for dry\-run where no changes will be committed to CloudVision.
\- \`\"deleted\"\`\: Build, abort and then delete the Workspace.
Used for dry\-run where no changes will be committed to CloudVision and the temporary Workspace will be removed to avoid \"clutter\". |
| force | bool | optional | False | | Force submit the workspace even if some devices are not actively streaming to CloudVision. |
| change_control | dict | optional | None | | CloudVision Change Control to create for the deployment. |
| name | str | optional | None | | Optional name to use for the created Change Control. By default the name generated by CloudVision will be kept. |
| description | str | optional | None | | Optional description to use for the created Change Control. |
| requested_state | str | optional | pending approval | Valid values:<br>- <code>pending approval</code><br>- <code>approved</code><br>- <code>running</code><br>- <code>completed</code> | The requested state for the Change Control.
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;force</samp> | bool | optional | False | | Force submit the workspace even if some devices are not actively streaming to CloudVision. |
| <samp>change_control</samp> | dict | optional | None | | CloudVision Change Control to create for the deployment. |
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;name</samp> | str | optional | None | | Optional name to use for the created Change Control. By default the name generated by CloudVision will be kept. |
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;description</samp> | str | optional | None | | Optional description to use for the created Change Control. |
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;requested_state</samp> | str | optional | pending approval | Valid values:<br>- <code>pending approval</code><br>- <code>approved</code><br>- <code>running</code><br>- <code>completed</code> | The requested state for the Change Control.

\- \`\"pending approval\"\` \(default\)\: Leave the Change Control in \"pending approval\" state.
\- \`\"approved\"\`\: Approve the Change Control but do not start.
\- \`\"running\"\`\: Approve and start the Change Control. Do not wait for the Change Control to be completed or failed.
\- \`\"completed\"\`\: Approve and start the Change Control. Wait for the Change Control to be completed. |
| timeouts | dict | optional | None | | Timeouts for long running operations. May need to be adjusted for large inventories. |
| workspace_build_timeout | float | optional | 300.0 | | Time to wait for Workspace build before failing. |
| change_control_creation_timeout | float | optional | 300.0 | | Time to wait for Change Control creation before failing. |
| return_details | bool | optional | False | | If \`True\` all details will be returned to Ansible and can be registered.
| <samp>timeouts</samp> | dict | optional | None | | Timeouts for long running operations. May need to be adjusted for large inventories. |
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;workspace_build_timeout</samp> | float | optional | 300.0 | | Time to wait for Workspace build before failing. |
| <samp>&nbsp;&nbsp;&nbsp;&nbsp;change_control_creation_timeout</samp> | float | optional | 300.0 | | Time to wait for Change Control creation before failing. |
| <samp>return_details</samp> | bool | optional | False | | If \`True\` all details will be returned to Ansible and can be registered.
For large inventories this can affect performance, so it is disabled by default. |

## Examples
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.

# NOTE: This is supposed to be deprecated as per
# https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html#ansible-metadata-block
# But our doc Jinja2 template renders it as preview which is what we want
ANSIBLE_METADATA = {"metadata_version": "1.0", "status": ["preview"]}

DOCUMENTATION = r"""
---
module: deploy_to_cv
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from datetime import datetime
from typing import TYPE_CHECKING, Literal

from aristaproto import _DateTime

from ..api.arista.changecontrol.v1 import (
ApproveConfig,
ApproveConfigServiceStub,
Expand Down Expand Up @@ -44,7 +46,7 @@ async def get_change_control(
Get Change Control using arista.changecontrol.v1.ChangeControlService.GetOne API
Parameters:
change_control_id: Unique identifier the Change Control.
change_control_id: Unique identifier of the Change Control.
time: Timestamp from which the information is fetched. `now()` if not set.
timeout: Timeout in seconds.
Expand Down Expand Up @@ -75,14 +77,14 @@ async def set_change_control(
Set Change Control details using arista.changecontrol.v1.ChangeControlConfigService.Set API
Parameters:
change_control_id: Unique identifier the Change Control.
change_control_id: Unique identifier of the Change Control.
name: Change Control Name.
description: Change Control description.
TODO: Add CC template
timeout: Timeout in seconds.
Returns:
ChangeControl object matching the change_control_id
ChangeControlConfig object after being set including any server-generated values.
"""
request = ChangeControlConfigSetRequest(
value=ChangeControlConfig(
Expand All @@ -94,15 +96,15 @@ async def set_change_control(

try:
response = await client.set(request, metadata=self._metadata, timeout=timeout)
return response
return response.value

except Exception as e:
raise get_cv_client_exception(e, f"Change Control ID '{change_control_id}'") or e

async def approve_chance_control(
self: CVClient,
change_control_id: str,
timestamp: datetime,
timestamp: _DateTime,
description: str | None = None,
timeout: float = 10.0,
) -> ApproveConfig:
Expand All @@ -111,12 +113,14 @@ async def approve_chance_control(
Parameters:
change_control_id: Unique identifier the Change Control.
time: Timestamp for the change control information to be approved.
timestamp: Timestamp for the change control information to be approved. \
This must be using the aristaproto._DateTime subclass which contains nanosecond information.
description: Description to set on the approval.
timeout: Timeout in seconds.
Returns:
ChangeControl object matching the change_control_id
ApproveConfig object carrying all the values given in the ApproveConfigSetRequest as well
as any server-generated values.
"""
request = ApproveConfigSetRequest(
value=ApproveConfig(
Expand Down Expand Up @@ -145,11 +149,11 @@ async def start_change_control(
Parameters:
change_control_id: Unique identifier the Change Control.
start_description: Description to add for the start request.
description: Description to add for the start request.
timeout: Timeout in seconds.
Returns:
ChangeControl object matching the change_control_id
ChangeControlConfig object including any server-generated values.
"""
request = ChangeControlConfigSetRequest(
value=ChangeControlConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async def get_configlet_containers(
timeout: Timeout in seconds.
Returns:
ConfigletAssignment object.
ConfigletAssignment objects.
"""
request = ConfigletAssignmentStreamRequest(partial_eq_filter=[], time=time)
if container_ids:
Expand Down Expand Up @@ -146,7 +146,7 @@ async def set_configlet_containers(
timeout: Timeout in seconds.
Returns:
ConfigletAssignmentConfig object after being set including any server-generated values.
ConfigletAssignmentKey objects after being set including any server-generated values.
"""

request = ConfigletAssignmentConfigSetSomeRequest(
Expand Down Expand Up @@ -304,7 +304,7 @@ async def set_configlet_from_file(
timeout: Timeout in seconds.
Returns:
ConfigletAssignment object after being set including any server-generated values.
ConfigletConfig object after being set including any server-generated values.
"""
request = ConfigletConfigSetRequest(
value=ConfigletConfig(
Expand Down Expand Up @@ -333,11 +333,11 @@ async def delete_configlets(
Parameters:
workspace_id: Unique identifier of the Workspace for which the information is fetched.
configlet_id: Unique identifier for Configlet.
configlet_ids: List of unique identifiers for Configlets to delete.
timeout: Timeout in seconds.
Returns:
List of ConfigletKey objects after being set including any server-generated values.
List of ConfigletKey objects after being deleted including any server-generated values.
"""
request = ConfigletConfigSetSomeRequest(values=[])
for configlet_id in configlet_ids:
Expand All @@ -360,7 +360,7 @@ async def delete_configlets(
raise get_cv_client_exception(e, f"Workspace ID '{workspace_id}', Configlet IDs '{configlet_ids}'") or e

@staticmethod
def _match_configlet_assignments(a: ConfigletAssignment, b: ConfigletAssignment):
def _match_configlet_assignments(a: ConfigletAssignment, b: ConfigletAssignment) -> bool:
"""
Match up the properties of two configlet assignments. Only matching on the assignment ID field.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ def __init__(self, servers: str | list[str], token: str, port: int = 443, verify
Parameters:
servers: A single FQDN for CVaaS or a list of FQDNs for one CVP cluster.
timeout: Token defined in CloudVision under service-accounts.
token: Token defined in CloudVision under service-accounts.
port: TCP port to use for the connection.
verify_certs: Disables SSL certificate verification if set to False. Not recommended for production.
Use this class as an asynchronous context manager:
```python
with CVClient(servers=["arista.io"], token="...") as cv_client:
...
```
"""
if isinstance(servers, list):
self._servers = servers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@


def get_cv_client_exception(exception: Exception, cv_client_details: str | None = None) -> Exception or None:
"""
Convert GRPCError or TimeoutError instances to an instance of the relevant subclass of CVClientException.
Parameters:
exception: Exception to convert.
Returns:
None if If the exception is unmatched, otherwise an instance of the relevant CVClientException subclass.
"""
if not HAS_GRPCLIB:
raise RuntimeError("Missing Python library 'grpclib'")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ async def get_inventory_devices(
If 'devices' is set to None, all devices will be returned.
Parameters:
devices: List of tuples where each tuple is in the format (<serial number> | None, <system_mac_address> | None, <hostname> | None)
devices: List of tuples where each tuple is in the format (serial number, system_mac_address, hostname)
time: Timestamp from which the information is fetched. `now()` if not set.
timeout: Timeout in seconds.
Returns:
Device objects
Device objects.
"""
request = DeviceStreamRequest(
partial_eq_filter=[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async def get_studio_inputs(
Parameters:
studio_id: Unique identifier for the studio.
workspace_id: Unique identifier of the Workspace for which the information is fetched. Use "" for mainline.
input_path: Data elements leading to specific inputs. Returning all inputs if not given.
default_value: Value to return if no inputs are found.
time: Timestamp from which the information is fetched. `now()` if not set.
timeout: Timeout in seconds.
Expand Down Expand Up @@ -166,6 +166,7 @@ async def get_studio_inputs_with_path(
studio_id: Unique identifier for the studio.
workspace_id: Unique identifier of the Workspace for which the information is fetched. Use "" for mainline.
input_path: Data elements leading to specific inputs.
default_value: Value to return if no inputs are found.
time: Timestamp from which the information is fetched. `now()` if not set.
timeout: Timeout in seconds.
Expand Down Expand Up @@ -266,10 +267,9 @@ async def set_studio_inputs(
workspace_id: Unique identifier of the Workspace for which the information is set.
inputs: Data to set at the given path.
input_path: Data path elements for setting specific inputs. If not given, inputs are set at the root, replacing all existing inputs.
time: Timestamp from which the information is fetched. `now()` if not set.
timeout: Timeout in seconds.
TODO: Refactor to fetch inputs with GetAll so we can stream larger input sets. Issue is to reassemble all this data.
TODO: Refactor to split inputs into multiple messages in case of larger input sets.
Returns:
InputsConfig object after being set including any server-generated values.
Expand All @@ -281,9 +281,7 @@ async def set_studio_inputs(
workspace_id=workspace_id,
path=RepeatedString(values=input_path),
),
# Dumping inputs to JSON using our special JSON Encoder which will also render UserString instances.
# UserString is needed to support the special DeferredFormatString object as a value.
inputs=json.dumps(inputs, cls=self.JsonEncodeWithUserString),
inputs=json.dumps(inputs),
)
)
client = InputsConfigServiceStub(self._channel)
Expand Down
Loading

0 comments on commit 920b511

Please sign in to comment.