Skip to content

PyAVD

PyAVD is a python package providing some of the features from the arista.avd Ansible collection without requiring Ansible. PyAVD leverages the same logic as the Ansible collection, so the generated outputs should be exactly the same based on the same inputs.

PyAVD does not provide any inventory or variable management, so PyAVD cannot replace a full Ansible based solution by itself. PyAVD could serve as an element in larger framework.

Supported features:

  • Validation of inputs based on the eos_designs input schema.
  • Generation of “avd_facts” and “structured config” to be used in other PyAVD functions.
  • Validation of “structured config” based on the eos_cli_config_gen input schema.
  • Generation of device configuration.
  • Generation of device documentation.

Feedback is very welcome. Please use GitHub discussions.

Functions overview

Arista AVD Overview Arista AVD Overview

Known limitations

Warning

Input data and “structured_configs” will be in-place updated by various PyAVD functions. Make sure to deep copy the data first if modifications are not allowed.

Warning

get_device_structured_config(), get_device_config() and get_device_doc() are not thread-safe, so avoid running them for the same device across multiple threads.

Note

  • No support for inline Jinja2 or custom Jinja2 templates.
  • The logic uses the hostname as the unique identifier for each device, so overlapping hostnames will not work.
  • For get_avd_facts(), fabric_name is not used or verified and may differ between devices. All devices in the given inputs will be treated as one fabric.
  • hostname must be set in “structured_config” for each device. hostname will be set correctly when using get_structured_config().

Roadmap

Note

Subject to change. No commitments implied.

  • Add examples
  • Add more tests (current coverage is 85%)
  • Add network state validation similar to eos_validate_state.
  • Add CloudVision tag integrations
  • Make PyAVD the source of AVD logic and use as a dependency for the arista.avd Ansible collection.
  • Explore support for custom Jinja2 templates.

Installation

Install the pyavd Python package:

pip3 install pyavd

Requirements (automatically installed with above command):

jinja2>=3.0
jsonschema>=4.5.1,<4.18
deepmerge>=1.1.0

Optional requirements

For support of get_device_doc(..., add_md_toc=True) install with extra mdtoc:

pip3 install pyavd[mdtoc]

Optional md-toc requirement (automatically installed with above command):

md-toc>=8.1.8

Reference

validate_inputs(inputs)

Validate input variables according to the eos_designs schema as documented on avd.arista.com.

Where supported by the schema, types will be auto type-converted like from “int” to “str”.

Parameters:

Name Type Description Default
inputs dict

Dictionary with inputs for “eos_designs”.

required

Returns:

Type Description
ValidationResult

Validation result object with any validation errors or deprecation warnings.

Source code in python-avd/pyavd/validate_inputs.py
def validate_inputs(inputs: dict) -> ValidationResult:
    """
    Validate input variables according to the `eos_designs` schema as documented on avd.arista.com.

    Where supported by the schema, types will be auto type-converted like from "int" to "str".

    Args:
        inputs: Dictionary with inputs for "eos_designs".

    Returns:
        Validation result object with any validation errors or deprecation warnings.
    """

    # Initialize a global instance of eos_designs_schema_tools
    global eos_designs_schema_tools
    if eos_designs_schema_tools is None:
        eos_designs_schema_tools = AvdSchemaTools(schema_id=EOS_DESIGNS_SCHEMA_ID)

    # Initialize SharedUtils class to fetch default variables below.
    shared_utils = SharedUtils(hostvars=inputs, templar=None)

    # Insert dynamic keys into the input data if not set.
    # These keys are required by the schema, but the default values are set inside shared_utils.
    inputs.setdefault("node_type_keys", shared_utils.node_type_keys)
    inputs.setdefault("connected_endpoints_keys", shared_utils.connected_endpoints_keys)
    inputs.setdefault("network_services_keys", shared_utils.network_services_keys)

    # Inplace conversion of data
    deprecation_warnings = eos_designs_schema_tools.convert_data(inputs)

    # Validate input data
    validation_result = eos_designs_schema_tools.validate_data(inputs)
    validation_result.deprecation_warnings.extend(deprecation_warnings)
    return validation_result

get_avd_facts(all_inputs)

Build avd_facts using the AVD eos_designs_facts logic.

Variables should be converted and validated according to AVD eos_designs schema first using pyavd.validate_inputs.

Note! No support for inline templating or jinja templates for descriptions or ip addressing

Parameters:

Name Type Description Default
all_inputs dict[str, dict]

A dictionary where keys are hostnames and values are dictionaries of input variables per device.

{
    "<hostname1>": dict,
    "<hostname2>": dict,
    ...
}

required

Returns:

Type Description
dict[str, dict]

Nested dictionary with various internal “facts”. The full dict must be given as argument to pyavd.get_device_structured_config:

{
    "avd_switch_facts": dict,
    "avd_overlay_peers": dict,
    "avd_topology_peers" : dict
}

Source code in python-avd/pyavd/get_avd_facts.py
def get_avd_facts(all_inputs: dict[str, dict]) -> dict[str, dict]:
    """
    Build avd_facts using the AVD eos_designs_facts logic.

    Variables should be converted and validated according to AVD `eos_designs` schema first using `pyavd.validate_inputs`.

    Note! No support for inline templating or jinja templates for descriptions or ip addressing

    Args:
        all_inputs: A dictionary where keys are hostnames and values are dictionaries of input variables per device.
            ```python
            {
                "<hostname1>": dict,
                "<hostname2>": dict,
                ...
            }
            ```

    Returns:
        Nested dictionary with various internal "facts". The full dict must be given as argument to `pyavd.get_device_structured_config`:
            ```python
            {
                "avd_switch_facts": dict,
                "avd_overlay_peers": dict,
                "avd_topology_peers" : dict
            }
            ```
    """

    avd_switch_facts_instances = _create_avd_switch_facts_instances(all_inputs)
    avd_switch_facts = _render_avd_switch_facts(avd_switch_facts_instances)
    avd_overlay_peers, avd_topology_peers = _render_peer_facts(avd_switch_facts)

    return {
        "avd_switch_facts": avd_switch_facts,
        "avd_overlay_peers": avd_overlay_peers,
        "avd_topology_peers": avd_topology_peers,
    }

get_device_structured_config(hostname, inputs, avd_facts)

Build and return the AVD structured configuration for one device.

Parameters:

Name Type Description Default
hostname str

Hostname of device.

required
inputs dict

Dictionary with inputs for “eos_designs”. Variables should be converted and validated according to AVD eos_designs schema first using pyavd.validate_inputs.

required
avd_facts dict

Dictionary of avd_facts as returned from pyavd.get_avd_facts.

required

Returns:

Type Description
dict

Device Structured Configuration as a dictionary

Source code in python-avd/pyavd/get_device_structured_config.py
def get_device_structured_config(hostname: str, inputs: dict, avd_facts: dict) -> dict:
    """
    Build and return the AVD structured configuration for one device.

    Args:
        hostname: Hostname of device.
        inputs: Dictionary with inputs for "eos_designs".
            Variables should be converted and validated according to AVD `eos_designs` schema first using `pyavd.validate_inputs`.
        avd_facts: Dictionary of avd_facts as returned from `pyavd.get_avd_facts`.

    Returns:
        Device Structured Configuration as a dictionary
    """

    # Set 'inventory_hostname' on the input hostvars, to keep compatability with Ansible focused code.
    # Also map in avd_facts without touching the hostvars
    mapped_hostvars = ChainMap(
        {
            "inventory_hostname": hostname,
            "switch": avd_facts["avd_switch_facts"][hostname]["switch"],
        },
        avd_facts,
        inputs,
    )

    # We do not validate input variables in this stage (done in "validate_inputs")
    # So we feed the vendored code an empty schema to avoid failures.
    input_schema_tools = AvdSchemaTools(schema={})
    output_schema_tools = AvdSchemaTools(schema_id=EOS_CLI_CONFIG_GEN_SCHEMA_ID)
    result = {}
    structured_config = get_structured_config(
        vars=mapped_hostvars,
        input_schema_tools=input_schema_tools,
        output_schema_tools=output_schema_tools,
        result=result,
        templar=None,
    )
    if result.get("failed"):
        raise AristaAvdError(f"{[str(error) for error in result['errors']]}")

    return structured_config

validate_structured_config(structured_config)

Validate structured config according the eos_cli_config_gen schema as documented on avd.arista.com.

Where supported by the schema, types will be auto type-converted like from “int” to “str”.

Parameters:

Name Type Description Default
structured_config dict

Dictionary with structured configuration.

required

Returns:

Type Description
ValidationResult

Validation result object with any validation errors or deprecation warnings.

Source code in python-avd/pyavd/validate_structured_config.py
def validate_structured_config(structured_config: dict) -> ValidationResult:
    """
    Validate structured config according the `eos_cli_config_gen` schema as documented on avd.arista.com.

    Where supported by the schema, types will be auto type-converted like from "int" to "str".

    Args:
        structured_config: Dictionary with structured configuration.

    Returns:
        Validation result object with any validation errors or deprecation warnings.
    """

    # Initialize a global instance of eos_cli_config_gen_schema_tools
    global eos_cli_config_gen_schema_tools
    if eos_cli_config_gen_schema_tools is None:
        eos_cli_config_gen_schema_tools = AvdSchemaTools(schema_id=EOS_CLI_CONFIG_GEN_SCHEMA_ID)

    # Inplace conversion of data
    deprecation_warnings = eos_cli_config_gen_schema_tools.convert_data(structured_config)

    # Validate input data
    validation_result = eos_cli_config_gen_schema_tools.validate_data(structured_config)
    validation_result.deprecation_warnings.extend(deprecation_warnings)
    return validation_result

get_device_config(structured_config)

Render and return the device configuration using AVD eos_cli_config_gen templates.

Parameters:

Name Type Description Default
structured_config dict

Dictionary with structured configuration. Variables should be converted and validated according to AVD eos_cli_config_gen schema first using pyavd.validate_structured_config.

required

Returns:

Type Description
str

Device configuration in EOS CLI format.

Source code in python-avd/pyavd/get_device_config.py
def get_device_config(structured_config: dict) -> str:
    """
    Render and return the device configuration using AVD eos_cli_config_gen templates.

    Args:
        structured_config: Dictionary with structured configuration.
            Variables should be converted and validated according to AVD `eos_cli_config_gen` schema first using `pyavd.validate_structured_config`.

    Returns:
        Device configuration in EOS CLI format.
    """

    templar = Templar()
    return templar.render_template_from_file(JINJA2_CONFIG_TEMPLATE, structured_config)

get_device_doc(structured_config, add_md_toc=False)

Render and return the device documentation using AVD eos_cli_config_gen templates.

Parameters:

Name Type Description Default
structured_config dict

Dictionary with structured configuration. Variables should be converted and validated according to AVD eos_cli_config_gen schema first using pyavd.validate_structured_config.

required
add_md_toc bool

Add a table of contents for markdown headings.

False

Returns:

Type Description
str

Device documentation in Markdown format.

Source code in python-avd/pyavd/get_device_doc.py
def get_device_doc(structured_config: dict, add_md_toc: bool = False) -> str:
    """
    Render and return the device documentation using AVD eos_cli_config_gen templates.

    Args:
        structured_config: Dictionary with structured configuration.
            Variables should be converted and validated according to AVD `eos_cli_config_gen` schema first using `pyavd.validate_structured_config`.
        add_md_toc: Add a table of contents for markdown headings.

    Returns:
        Device documentation in Markdown format.
    """

    templar = Templar()
    result: str = templar.render_template_from_file(JINJA2_DOCUMENTAITON_TEMPLATE, structured_config)
    if add_md_toc:
        if not HAS_MD_TOC:
            raise ModuleNotFoundError("The python library 'md-toc' is not installed. Install using 'pip3 install pyavd[mdtoc]'")

        return filter_add_md_toc(result, skip_lines=3)

    return result

ValidationResult

Object containing result of data validation

Attributes:

Name Type Description
failed bool

True if data is not valid according to the schema. Otherwise False.

validation_errors list[AvdValidationError]

List of AvdValidationErrors containing schema violations.

deprecation_warnings list[AvdDeprecationWarning]

List of AvdDeprecationWarnings containing warning for deprecated inputs.

Source code in python-avd/pyavd/validation_result.py
class ValidationResult:
    """
    Object containing result of data validation

    Attributes:
        failed: True if data is not valid according to the schema. Otherwise False.
        validation_errors: List of AvdValidationErrors containing schema violations.
        deprecation_warnings: List of AvdDeprecationWarnings containing warning for deprecated inputs.
    """

    failed: bool
    validation_errors: list[AvdValidationError]
    deprecation_warnings: list[AvdDeprecationWarning]

    def __init__(self, failed: bool, validation_errors: list = None, deprecation_warnings: list = None):
        self.failed = failed
        self.validation_errors = validation_errors or []
        self.deprecation_warnings = deprecation_warnings or []