<!--
  ~ Copyright (c) 2023-2026 Arista Networks, Inc.
  ~ Use of this source code is governed by the Apache License 2.0
  ~ that can be found in the LICENSE file.
  -->

# Custom Structured Configuration

With Custom Structured Configuration the user can override built-in `eos_designs` functionality or add extra knobs in the structured configuration to be picked up by the `eos_cli_config_gen` role.

There are multiple ways of supplying Custom Structured Configuration and they can all be combined:

- `structured_config` in AVD Design data models
- `custom_structured_configuration` - described in this document

## Overview

AVD `eos_designs` role flow is expected to work as follow:

1. from the input variables (inventory, group_vars, host_vars, ...) the `eos_designs` role generates a *structured configuration* object.
   The `structured_config` inputs in AVD Design data models and custom_structured_configuration occurrences are merged at the very end of `eos_designs` processing to generate the final *structured_configuration* used by `eos_cli_config_gen`.
2. The "structured_config" is used as input to the `eos_cli_config_gen` role to generate the intended EOS configuration.

## `structured_config` in `eos_designs` data models

This feature enables the user to supply `structured_config` on various levels in the `eos_designs` data model.

### Connected Endpoints

All relevant `structured_config` sections will be merged.

```yaml
< connected_endpoints_keys.key >:
  < endpoint_1 >:
    adapters:
      - <...>
        # Custom structured config added under ethernet_interfaces.<interface> for the EOS Config schema.
        structured_config: < dictionary >
        port_channel:
          # Custom structured config added under port_channel_interfaces.<interface> for the EOS Config schema.
          structured_config: < dictionary >
```

See [Connected Endpoints](../data-models.md#connected-endpoints-settings)

### Fabric Topology

Only the most specific `structured_config` key will be used

```yaml
< node_type_key >:
  defaults:
    # Custom structured config for the EOS Config schema.
    structured_config: < dictionary >
  nodes:
    < node >:
      # Custom structured config for the EOS Config schema.
      structured_config: < dictionary >
  node_groups:
    < node_group >:
      # Custom structured config for the EOS Config schema.
      structured_config: < dictionary >
      nodes:
        < node >:
          # Custom structured config for the EOS Config schema.
          # Overrides the setting on node_group level.
          structured_config: < dictionary >
```

See [Node type common configuration](../data-models.md#node-type-common-configuration)

### Network Services (a.k.a. "tenants")

All relevant `structured_config` sections will be merged. Note that setting `structured_config` under `svi.nodes` will override the setting on `svi`.

```yaml
tenants:
  vrfs:
    < vrf >:
      # Custom structured config for the EOS Config schema.
      structured_config: < dictionary >
      bgp:
        # Custom structured config added under router_bgp.vrfs.<vrf> for the EOS Config schema.
        structured_config: < dictionary >
      svis:
        < vlan >:
          # Custom structured config added under vlan_interfaces.<interface> for the EOS Config schema.
          structured_config: < dictionary >
          nodes:
            < node >:
              # Custom structured config added under vlan_interfaces.<interface> for the EOS Config schema.
              # Overrides the setting on SVI level.
              structured_config: < dictionary >
```

See [Network Services VRFs configuration](../data-models.md#network-services-vrfs-configuration) and [Network Services SVIs configuration](../data-models.md#network-services-vrf-svis-configuration)

All `structured_config` knobs honor the `list_merge` strategy set in `custom_structured_configuration_list_merge` described in the next section.

## `custom_structured_configuration`

Custom EOS Structured Configuration keys can be set on any group or host_var level using the name
of the corresponding EOS Config key prefixed with content of `custom_structured_configuration_prefix`.
The content of Custom Structured Configuration variables will be combined with the structured config generated by the eos_designs role.
By default Lists are extended (by unique items) or their elements are updated (if primary key matches) and Dictionaries are updated. The combine is done recursively, so it is possible to update a sub-key of a variable set by
`eos_designs` role already.

The List-merge strategy can be changed using `custom_structured_configuration_list_merge`. Since most data models move towards lists and
input data is auto-converted from dicts to lists, it is more likely that `custom_structured_configuration_list_merge: replace` will
overwrite list data unintentionally. So going forward `replace` should be avoided.

### Variables and Options

--8<--
ansible_collections/arista/avd/roles/eos_designs/docs/tables/custom-structured-configuration.md
--8<--

### Examples

#### Example using default prefix

```yaml
custom_structured_configuration_static_routes:
 - prefix: 42.42.42.0/24
   vrf: MGMT
   next_hop: 42.42.42.42
custom_structured_configuration_ethernet_interfaces:
  - name: Ethernet4000
    description: My test
    ip_address: 10.1.2.3/12
    shutdown: false
    type: routed
    mtu: 1500
    peer: MY-own-peer
    peer_interface: Ethernet123
    peer_type: my_precious
```

In this example the contents of the `static_routes` variable in the structured configuration will be extended with the content `[ { "prefix": "42.42.42.0/24", "vrf": "MGMT", "next_hop": "42.42.42.42" } ]` if exact same element is not yet part of the `static_routes` list.
`Ethernet4000` will either be added to the `ethernet_interfaces` dictionary (if an item with `name: Ethernet4000` is not yet defined) or an existing item in the Structured Configuration will be updated.

`custom_structured_configuration_prefix` allows the user to customize the prefix for Custom Structured Configuration variables.
Default value is `custom_structured_configuration_`. Remember to include any delimiter like the last `_` in this case.
It is possible to specify a list of prefixes, which will all be merged one by one. The order of merge will start from beginning of the list, which means that keys defined in the later prefixes will be able to override keys defined in previous ones.

#### Example using multiple prefixes

```yaml
custom_structured_configuration_prefix: [ my_dci_ , my_special_dci_ ]

my_dci_ethernet_interfaces:
  - name: Ethernet4000
    description: My test
    ip_address: 10.1.2.3/12
    shutdown: false
    type: routed
    mtu: 1500
    peer: MY-own-peer
    peer_interface: Ethernet123
    peer_type: my_precious

my_special_dci_ethernet_interfaces:
  - name: Ethernet4000
    ip_address: 10.3.2.1/21
```

In this example `Ethernet4000` will be added to the `ethernet_interfaces` list in the Structured Configuration and the ip_address will be `10.3.2.1/21` since ip_address was overridden on the later `custom_structured_configuration_prefix`

#### Example with `append` list_merge strategy

```yaml
custom_structured_configuration_list_merge: append
custom_structured_configuration_prefix: [ override_ ]

# RADIUS servers from AVD Design inputs
aaa_settings:
  radius:
    servers:
      - host: 10.10.10.1
        key: <encrypted_key_1>
      - host: 10.10.10.2
        key: <encrypted_key_2>

# Additional RADIUS servers via custom_structured_configuration
# Including same host (10.10.10.1) with same config as in AVD Designs
override_radius_server:
  servers:
    - host: 10.10.10.1
      key: <encrypted_key_1>
    - host: 10.10.10.3
      key: <encrypted_key_3>
      timeout: 5
```

In this example the `aaa_settings.radius.servers` variable will be read by `eos_designs` and the following `radius_server` structured configuration will be generated accordingly:

!!! note
    *The following snippet is a representation of the intermediate state inside `eos_designs` processing before the custom structured configuration `override_` prefix is applied.
    * This is what would be rendered by `eos_designs` if no custom structured configuration was applied.

```yaml
radius_server:
  servers:
    - host: 10.10.10.1
      key: <encrypted_key_1>
    - host: 10.10.10.2
      key: <encrypted_key_2>
```

In the final stage of `eos_designs` processing, the `override_radius_server` list will be `appended` to `radius_server` list without checking for element uniqueness, resulting in the following strutcured configuration:

```yaml
radius_server:
  servers:
    - host: 10.10.10.1
      key: <encrypted_key_1>
    - host: 10.10.10.2
      key: <encrypted_key_2>
    - host: 10.10.10.1
      key: <encrypted_key_1>
    - host: 10.10.10.3
      key: <encrypted_key_3>
      timeout: 5
```

And in turn the following configuration will be generated by `eos_cli_config_gen`:

```eos
radius-server host 10.10.10.1 key 7 <encrypted_key_1>
radius-server host 10.10.10.1 key 7 <encrypted_key_1>
radius-server host 10.10.10.2 key 7 <encrypted_key_2>
radius-server host 10.10.10.3 key 7 <encrypted_key_3> timeout 5
```

With the default `append_rp` strategy, the duplicate would have been removed.

### Precedence

Starting from AVD version 6.0.0, EOS Config inputs used directly in `eos_designs` input data are **ignored** and warnings are emitted during validation.
This prevents confusion and ensures a clear separation between the two roles:

- **AVD Design inputs**: High-level data models specific to `eos_designs` (e.g., `dns_settings`, `fabric_name`, etc.)
- **EOS Config inputs**: Low-level structured configuration keys (e.g., `ip_name_server`, `ethernet_interfaces`, etc.)

Previously, mixing these two types of inputs could lead to unexpected behavior where EOS Config inputs would be overwritten
by `eos_designs` generated configuration. This behavior was confusing and could break when upgrading AVD or enabling new `eos_designs` features.

The only way to override `eos_designs` output values is to use `custom_structured_configuration` as described below. This configuration will be merged on top of the `eos_designs` generated variables.

To demonstrate the various behaviors, the following example focus on EOS Config `ip_name_server` key as it is quite straightforward.
It is configured by `dns_settings` for the AVD Design inputs and by `ip_name_server` for the EOS Config inputs.
The assumption is that you are running `eos_designs` and `eos_cli_config_gen` roles one after the other.

#### AVD Design variables only

```yaml
---
# Only AVD Design variables
dns_settings:
  servers:
    - ip_address: 192.168.42.10
    - ip_address: 192.168.42.40
```

generates:

```eos
ip name-server vrf MGMT 192.168.42.10
ip name-server vrf MGMT 192.168.42.40
```

#### AVD Designs variables extended by unique `custom_structured_configuration` when data model has a primary key

```yaml
---
# csc_2_ prefix will override csc_1_ prefix for same keys
custom_structured_configuration_prefix:
  - 'csc_1_'
  - 'csc_2_'

# Both dns_settings from AVD Design Inputs and ip_name_server from
# custom_structured_configuration will make it to the intended config
dns_settings:
  # will be configured in MGMT vrf by default
  servers:
    - ip_address: 192.168.42.10 # Collides with both csc_1_ip_name_server and csc_2_ip_name_server
    - ip_address: 192.168.42.20 # Collides with csc_1_ip_name_server
    - ip_address: 192.168.42.30 # Collides with csc_2_ip_name_server
    - ip_address: 192.168.42.40 # Unique value

csc_1_ip_name_server:
    vrfs:
      - name: MGMT
        servers:
          - ip_address: 192.168.42.10 # Collides with both AVD Design inputs and csc_2_ip_name_server
            priority: 2
          - ip_address: 192.168.42.20 # Collides with AVD Design inputs
            priority: 2
          - ip_address: 192.168.42.50 # Collides with both csc_2_ip_name_server
            priority: 2
          - ip_address: 192.168.42.60 # Unique value
            priority: 2

csc_2_ip_name_server:
    vrfs:
      - name: MGMT
        servers:
          - ip_address: 192.168.42.10 # Collides with both AVD Design inputs and csc_1_ip_name_server
            priority: 3
          - ip_address: 192.168.42.30 # Collides with AVD Design inputs
            priority: 3
          - ip_address: 192.168.42.50 # Collides with both csc_1_local_users
            priority: 3
          - ip_address: 192.168.42.70 # Unique value
            priority: 3
      - name: EOS_CLI # other VRF
        servers:
          - ip_address: 192.168.42.10
            priority: 3
```

will generate as intended config:

```eos
ip name-server vrf EOS_CLI 192.168.42.10 priority 3
ip name-server vrf MGMT 192.168.42.10 priority 3
ip name-server vrf MGMT 192.168.42.20 priority 2
ip name-server vrf MGMT 192.168.42.30 priority 3
ip name-server vrf MGMT 192.168.42.40
ip name-server vrf MGMT 192.168.42.50 priority 3
ip name-server vrf MGMT 192.168.42.60 priority 2
ip name-server vrf MGMT 192.168.42.70 priority 3
```

- Name servers 192.168.42.40, 192.168.42.60 and 192.168.42.70 are configured in one place only Therefore they are reflected in the intended configuration with their original values (original `eos_designs` List is extended by unique values from both `custom_structured_configuration` sources).
- Name servers 192.168.42.20 and 192.168.42.30 are defined in both `eos_designs` and `custom_structured_configuration` (using the prefix `csc_1_` and `csc_2_` respectively). Since the value of the primary key (`ip_address`) matches, the original data from `eos_designs` is updated with data from `custom_structured_configuration`, resulting in the priority for these name servers being set to 2 and 3 respectively.
- Name server `192.168.42.10` is defined in all three data sources (`eos_designs` and `custom_structured_configuration` with prefixes `csc_1_` and `csc_2_`). Since the value of the primary key (`name`) matches in all 3 sources, the original data from `eos_designs` is first updated with data from `custom_structured_configuration` with prefix `csc_1_`. It is then updated again with data from `custom_structured_configuration` with prefix `csc_2_`, resulting in the priority for this name server being set to 3.

#### AVD Designs variables extended by unique `custom_structured_configuration` when data model does not have a primary key

```yaml
---
# RADIUS servers from AVD Design inputs
aaa_settings:
  radius:
    servers:
      - host: 10.10.10.1
        key: <encrypted_key_1>
      - host: 10.10.10.2
        key: <encrypted_key_2>

# Additional RADIUS servers via custom_structured_configuration
# Including same host (10.10.10.1) with TLS configuration
custom_structured_configuration_radius_server:
  servers:
    - host: 10.10.10.1
      tls:
        enabled: true
        ssl_profile: RADIUS_TLS_PROFILE
        port: 2083
    - host: 10.10.10.3
      key: <encrypted_key_3>
      timeout: 5
    - host: 10.10.10.4
      key: <encrypted_key_4>
      retransmit: 3
```

will generate as intended config:

```eos
radius-server host 10.10.10.1 key 7 <encrypted_key_1>
radius-server host 10.10.10.1 ssl-profile RADIUS_TLS_PROFILE port 2083
radius-server host 10.10.10.2 key 7 <encrypted_key_2>
radius-server host 10.10.10.3 key 7 <encrypted_key_3> timeout 5
radius-server host 10.10.10.4 key 7 <encrypted_key_4> retransmit 3
```

Since `radius_server.servers` schema doesn't have a primary key defined, the lists are concatenated due to the default `custom_structured_configuration_list_merge` behavior (`append_rp`).
All servers from both `aaa_settings.radius.servers` (AVD Design input) and `custom_structured_configuration_radius_server.servers` are included in the final configuration.

In this example, host `10.10.10.1` appears twice in the configuration:

- Once without TLS (from `aaa_settings.radius.servers`)
- Once with TLS (from `custom_structured_configuration_radius_server.servers`)

#### EOS Config inputs ignored by eos_designs

!!! warning
    This approach is **not working** and generate warnings. Use `custom_structured_configuration` instead.

```yaml
# Both dns_settings from AVD Design inputs and ip_name_server from EOS Config inputs.
# The ip_name_server WON'T be displayed as they are ignored by eos_designs.
dns_settings:
  servers:
    - ip_address: 192.168.42.10
    - ip_address: 192.168.42.40

# WARNING: This will be ignored when used with eos_designs and a warning will be emitted.
ip_name_server:
    vrfs:
      - name: EOS_CLI
        servers:
          - ip_address: 192.168.42.1
          - ip_address: 192.168.42.10
```

will generate config:

```eos
ip name-server vrf MGMT 192.168.42.10
ip name-server vrf MGMT 192.168.42.40
```

The `ip_name_server` key is ignored and a warning is emitted during validation.
