diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/relaxed-structured-config-validation.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/relaxed-structured-config-validation.cfg new file mode 100644 index 00000000000..85e22f7b913 --- /dev/null +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/relaxed-structured-config-validation.cfg @@ -0,0 +1,25 @@ +! +no enable password +no aaa root +! +vlan internal order ascending range 1006 1199 +! +transceiver qsfp default-mode 4x10G +! +service routing protocols model multi-agent +! +hostname relaxed-structured-config-validation +! +vrf instance MGMT +! +management api http-commands + protocol https + no shutdown + ! + vrf MGMT + no shutdown +! +aaa accounting exec console start-stop group node_group +no ip routing vrf MGMT +! +end diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/relaxed-structured-config-validation.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/relaxed-structured-config-validation.yml new file mode 100644 index 00000000000..2e1da556d87 --- /dev/null +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/relaxed-structured-config-validation.yml @@ -0,0 +1,28 @@ +hostname: relaxed-structured-config-validation +is_deployed: true +service_routing_protocols_model: multi-agent +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +transceiver_qsfp_default_mode_4x10: true +vrfs: +- name: MGMT + ip_routing: false +management_api_http: + enable_vrfs: + - name: MGMT + enable_https: true +ip_igmp_snooping: + globally_enabled: true +aaa_accounting: + exec: + console: + group: node_group + type: start-stop diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/relaxed-structured-config-validation.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/relaxed-structured-config-validation.yml new file mode 100644 index 00000000000..0ed087dadef --- /dev/null +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/relaxed-structured-config-validation.yml @@ -0,0 +1,19 @@ +--- +# The required key 'type' is added via custom_structured_configuration. +custom_structured_configuration_aaa_accounting: + exec: + console: + type: start-stop + +type: l2leaf + +l2leaf: + nodes: + - name: relaxed-structured-config-validation + structured_config: + aaa_accounting: + exec: + console: + # Adding group here but not including the required key 'type'. + # This will not raise a validation error because of relax mode in the schema. + group: node_group diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/hosts.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/hosts.yml index 62a5b1e8ca0..19c15c67bbb 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/hosts.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/hosts.yml @@ -13,6 +13,7 @@ all: children: CLEAN_UNIT_TESTS: # Single Devices testing one specific case and only using hostvars hosts: + relaxed-structured-config-validation: always-configure-ip-routing: bgp-options: connected_endpoints: diff --git a/ansible_collections/arista/avd/roles/eos_cli_config_gen/docs/tables/aaa-accounting.md b/ansible_collections/arista/avd/roles/eos_cli_config_gen/docs/tables/aaa-accounting.md index 01a2c528802..a98e3cc9320 100644 --- a/ansible_collections/arista/avd/roles/eos_cli_config_gen/docs/tables/aaa-accounting.md +++ b/ansible_collections/arista/avd/roles/eos_cli_config_gen/docs/tables/aaa-accounting.md @@ -10,7 +10,7 @@ | [aaa_accounting](## "aaa_accounting") | Dictionary | | | | | | [  exec](## "aaa_accounting.exec") | Dictionary | | | | | | [    console](## "aaa_accounting.exec.console") | Dictionary | | | | | - | [      type](## "aaa_accounting.exec.console.type") | String | | | Valid Values:
- none
- start-stop
- stop-only | | + | [      type](## "aaa_accounting.exec.console.type") | String | Required | | Valid Values:
- none
- start-stop
- stop-only | | | [      group](## "aaa_accounting.exec.console.group") | String | | | | Group Name. | | [      logging](## "aaa_accounting.exec.console.logging") | Boolean | | | | | | [    default](## "aaa_accounting.exec.default") | Dictionary | | | | | @@ -43,7 +43,7 @@ aaa_accounting: exec: console: - type: + type: # Group Name. group: diff --git a/python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py b/python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py index 05f524ba330..dbccb61c640 100644 --- a/python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py +++ b/python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py @@ -28,7 +28,7 @@ class Console(AvdModel): """Subclass of AvdModel.""" _fields: ClassVar[dict] = {"type": {"type": str}, "group": {"type": str}, "logging": {"type": bool}, "_custom_data": {"type": dict}} - type: Literal["none", "start-stop", "stop-only"] | None + type: Literal["none", "start-stop", "stop-only"] group: str | None """Group Name.""" logging: bool | None @@ -39,7 +39,7 @@ class Console(AvdModel): def __init__( self, *, - type: Literal["none", "start-stop", "stop-only"] | None | UndefinedType = Undefined, + type: Literal["none", "start-stop", "stop-only"] | UndefinedType = Undefined, group: str | None | UndefinedType = Undefined, logging: bool | None | UndefinedType = Undefined, _custom_data: dict[str, Any] | UndefinedType = Undefined, diff --git a/python-avd/pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.yml b/python-avd/pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.yml index 092b4cf020e..9fddab8f82e 100644 --- a/python-avd/pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.yml +++ b/python-avd/pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.yml @@ -24,6 +24,7 @@ keys: - none - start-stop - stop-only + required: true group: description: Group Name. type: str diff --git a/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/aaa_accounting.schema.yml b/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/aaa_accounting.schema.yml index dcbc097c9a2..437b370effa 100644 --- a/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/aaa_accounting.schema.yml +++ b/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/aaa_accounting.schema.yml @@ -18,6 +18,7 @@ keys: type: type: str valid_values: ["none", "start-stop", "stop-only"] + required: true group: description: Group Name. type: str diff --git a/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/access_lists.schema.yml b/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/access_lists.schema.yml index 8891d3ea3fc..1ac86f3346a 100644 --- a/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/access_lists.schema.yml +++ b/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/access_lists.schema.yml @@ -16,7 +16,8 @@ keys: name: type: str description: Access-list Name. - convert_types: [ int ] + convert_types: + - int counters_per_entry: type: bool permit_response_traffic: diff --git a/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml b/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml index b241f60331a..9b77a0373a5 100644 --- a/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml @@ -204,6 +204,7 @@ keys: default: false structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: @@ -224,6 +225,7 @@ keys: default: false structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: @@ -244,6 +246,7 @@ keys: default: true structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: @@ -264,6 +267,7 @@ keys: default: true structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: @@ -284,6 +288,7 @@ keys: default: true structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: @@ -304,6 +309,7 @@ keys: default: true structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: @@ -324,6 +330,7 @@ keys: default: true structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: @@ -389,6 +396,7 @@ keys: default: 1 structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: @@ -446,6 +454,7 @@ keys: default: 1 structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: @@ -3282,6 +3291,7 @@ keys: description: Only use entropy from the hardware source. structured_config: type: dict + relaxed_validation: true documentation_options: hide_keys: true description: Custom structured config for eos_cli_config_gen. @@ -5655,6 +5665,7 @@ $defs: the final EOS configuration. structured_config: type: dict + relaxed_validation: true description: Custom structured config added under port_channel_interfaces.[name=] for eos_cli_config_gen. documentation_options: @@ -5682,6 +5693,7 @@ $defs: EOS configuration. structured_config: type: dict + relaxed_validation: true description: Custom structured config added under ethernet_interfaces.[name=] for eos_cli_config_gen. documentation_options: @@ -6822,6 +6834,7 @@ $defs: `fabric_flow_tracking.l3_interfaces` setting. structured_config: type: dict + relaxed_validation: true description: Custom structured config added under ethernet_interfaces.[name=] for eos_cli_config_gen. documentation_options: @@ -7154,6 +7167,7 @@ $defs: ' structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.vrfs.[name=] for eos_cli_config_gen. documentation_options: @@ -7262,6 +7276,7 @@ $defs: EOS configuration. structured_config: type: dict + relaxed_validation: true description: Custom structured config for eos_cli_config_gen. documentation_options: hide_keys: true @@ -7442,6 +7457,7 @@ $defs: keys: structured_config: type: dict + relaxed_validation: true description: 'Custom structured config added under router_bgp.vlans.[id=] for eos_cli_config_gen. @@ -7730,6 +7746,7 @@ $defs: hide_keys: true description: Custom structured config for eos_cli_config_gen. type: dict + relaxed_validation: true $ref: eos_cli_config_gen# uplink_type: documentation_options: @@ -7938,6 +7955,7 @@ $defs: table: node-type-uplink-configuration hide_keys: true type: dict + relaxed_validation: true description: 'Custom structured config applied to "uplink_interfaces", and "uplink_switch_interfaces". @@ -7962,6 +7980,7 @@ $defs: table: node-type-l2-mlag-configuration hide_keys: true type: dict + relaxed_validation: true description: 'Custom structured config applied to MLAG peer link port-channel id. @@ -7979,6 +7998,7 @@ $defs: table: node-type-l2-mlag-configuration hide_keys: true type: dict + relaxed_validation: true description: 'Custom structured config applied to MLAG Peer Link (control link) SVI interface id. @@ -7996,6 +8016,7 @@ $defs: table: node-type-l2-mlag-configuration hide_keys: true type: dict + relaxed_validation: true description: 'Custom structured config applied to MLAG underlay L3 peering SVI interface id. @@ -9508,6 +9529,7 @@ $defs: setting. structured_config: type: dict + relaxed_validation: true documentation_options: hide_keys: true description: Custom structured config for the Ethernet interface. @@ -9722,6 +9744,7 @@ $defs: - ebgp: Enforce plain IPv4 BGP peering' structured_config: type: dict + relaxed_validation: true documentation_options: hide_keys: true description: 'Custom structured config for interfaces. @@ -10064,6 +10087,7 @@ $defs: keys: structured_config: type: dict + relaxed_validation: true description: 'Structured configuration and EOS CLI commands rendered on router_bgp.vlans.[id=]. @@ -10087,6 +10111,7 @@ $defs: ' structured_config: type: dict + relaxed_validation: true description: 'Custom structured config added under vlan_interfaces.[name=] for eos_cli_config_gen. diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/bgp_peer_groups.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/bgp_peer_groups.schema.yml index 0c71f1b866a..0be78d985ed 100644 --- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/bgp_peer_groups.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/bgp_peer_groups.schema.yml @@ -29,6 +29,7 @@ keys: default: False structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -48,6 +49,7 @@ keys: default: False structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -67,6 +69,7 @@ keys: default: True structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -86,6 +89,7 @@ keys: default: True structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -105,6 +109,7 @@ keys: default: True structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -124,6 +129,7 @@ keys: default: True structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -143,6 +149,7 @@ keys: default: True structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -197,6 +204,7 @@ keys: default: 1 structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -244,6 +252,7 @@ keys: default: 1 structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.peer_groups.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_adapter_config.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_adapter_config.schema.yml index cfc5883f5bf..42733f30bd8 100644 --- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_adapter_config.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_adapter_config.schema.yml @@ -483,6 +483,7 @@ $defs: description: EOS CLI rendered directly on the port-channel interface in the final EOS configuration. structured_config: type: dict + relaxed_validation: true description: Custom structured config added under port_channel_interfaces.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -506,6 +507,7 @@ $defs: description: EOS CLI rendered directly on the ethernet interface in the final EOS configuration. structured_config: type: dict + relaxed_validation: true description: Custom structured config added under ethernet_interfaces.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_network_services.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_network_services.schema.yml index 8bf38f18c9a..c09e726b3a0 100644 --- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_network_services.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_network_services.schema.yml @@ -787,6 +787,7 @@ $defs: Configures flow-tracking on the interface. Overrides `fabric_flow_tracking.l3_interfaces` setting. structured_config: type: dict + relaxed_validation: true description: Custom structured config added under ethernet_interfaces.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -1064,6 +1065,7 @@ $defs: EOS CLI rendered directly on the Router BGP, VRF definition in the final EOS configuration. structured_config: type: dict + relaxed_validation: true description: Custom structured config added under router_bgp.vrfs.[name=] for eos_cli_config_gen. documentation_options: hide_keys: true @@ -1158,6 +1160,7 @@ $defs: description: EOS CLI rendered directly on the root level of the final EOS configuration. structured_config: type: dict + relaxed_validation: true description: Custom structured config for eos_cli_config_gen. documentation_options: hide_keys: true @@ -1304,6 +1307,7 @@ $defs: keys: structured_config: type: dict + relaxed_validation: true description: | Custom structured config added under router_bgp.vlans.[id=] for eos_cli_config_gen. This configuration will not be applied to vlan aware bundles. diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_node_type.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_node_type.schema.yml index af03a895337..c887e4abf79 100644 --- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_node_type.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_node_type.schema.yml @@ -164,6 +164,7 @@ $defs: hide_keys: true description: Custom structured config for eos_cli_config_gen. type: dict + relaxed_validation: true $ref: "eos_cli_config_gen#" uplink_type: documentation_options: @@ -340,6 +341,7 @@ $defs: hide_keys: true # TODO: Add deprecation warning when introducing new keys for specific configuration. type: dict + relaxed_validation: true description: | Custom structured config applied to "uplink_interfaces", and "uplink_switch_interfaces". When uplink_type == "p2p", custom structured config added under ethernet_interfaces.[name=] for eos_cli_config_gen overrides the settings on the ethernet interface level. @@ -354,6 +356,7 @@ $defs: table: node-type-l2-mlag-configuration hide_keys: true type: dict + relaxed_validation: true description: | Custom structured config applied to MLAG peer link port-channel id. Added under port_channel_interfaces.[name=] for eos_cli_config_gen. @@ -365,6 +368,7 @@ $defs: table: node-type-l2-mlag-configuration hide_keys: true type: dict + relaxed_validation: true description: | Custom structured config applied to MLAG Peer Link (control link) SVI interface id. Added under vlan_interfaces.[name=] for eos_cli_config_gen. @@ -376,6 +380,7 @@ $defs: table: node-type-l2-mlag-configuration hide_keys: true type: dict + relaxed_validation: true description: | Custom structured config applied to MLAG underlay L3 peering SVI interface id. Added under vlan_interfaces.[name=] for eos_cli_config_gen. diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_node_type_l3_interfaces.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_node_type_l3_interfaces.schema.yml index 68a3e99a8b0..c8ec13cb031 100644 --- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_node_type_l3_interfaces.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_node_type_l3_interfaces.schema.yml @@ -179,6 +179,7 @@ $defs: Configures flow-tracking on the interface. Overrides `fabric_flow_tracking.l3_interfaces` setting. structured_config: type: dict + relaxed_validation: true documentation_options: hide_keys: true description: |- diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_p2p_links.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_p2p_links.schema.yml index 88e455074c7..62fc8a436ef 100644 --- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_p2p_links.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_p2p_links.schema.yml @@ -200,6 +200,7 @@ $defs: - ebgp: Enforce plain IPv4 BGP peering structured_config: type: dict + relaxed_validation: true documentation_options: hide_keys: true description: |- diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_svi_settings.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_svi_settings.schema.yml index 579a1b2f1a1..99439dd61ad 100644 --- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_svi_settings.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_svi_settings.schema.yml @@ -272,6 +272,7 @@ $defs: keys: structured_config: type: dict + relaxed_validation: true description: | Structured configuration and EOS CLI commands rendered on router_bgp.vlans.[id=]. This configuration will not be applied to vlan aware bundles. @@ -288,6 +289,7 @@ $defs: EOS CLI rendered directly on the VLAN interface in the final EOS configuration. structured_config: type: dict + relaxed_validation: true description: | Custom structured config added under vlan_interfaces.[name=] for eos_cli_config_gen. documentation_options: diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/platform_settings.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/platform_settings.schema.yml index c493a8a90b7..d5f454146d0 100644 --- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/platform_settings.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/platform_settings.schema.yml @@ -134,6 +134,7 @@ keys: description: Only use entropy from the hardware source. structured_config: type: dict + relaxed_validation: true documentation_options: hide_keys: true description: Custom structured config for eos_cli_config_gen. diff --git a/python-avd/pyavd/_schema/avd_meta_schema.json b/python-avd/pyavd/_schema/avd_meta_schema.json index bc97a16186d..0a19301094b 100644 --- a/python-avd/pyavd/_schema/avd_meta_schema.json +++ b/python-avd/pyavd/_schema/avd_meta_schema.json @@ -337,6 +337,11 @@ "required": { "$ref": "#/$defs/required" }, + "relaxed_validation": { + "type": "boolean", + "default": false, + "description": "Disable required key validation." + }, "deprecation": { "$ref": "#/$defs/deprecation" }, @@ -460,4 +465,4 @@ }, "title": "Arista AVD Schema", "$ref": "#/$defs/avd_schema_var" -} \ No newline at end of file +} diff --git a/python-avd/pyavd/_schema/avdvalidator.py b/python-avd/pyavd/_schema/avdvalidator.py index 5845cc2f38b..558cdd3a529 100644 --- a/python-avd/pyavd/_schema/avdvalidator.py +++ b/python-avd/pyavd/_schema/avdvalidator.py @@ -31,6 +31,8 @@ def __init__(self, schema: dict) -> None: "unique_keys": self.unique_keys_validator, "$ref": self.ref_validator, } + self._relaxed_validation: bool = False + """Used to ignore missing required keys.""" def validate(self, instance: Any, schema: dict | None = None, path: list[str | int] | None = None) -> Generator: if schema is None: @@ -50,6 +52,7 @@ def validate(self, instance: Any, schema: dict | None = None, path: list[str | i yield from validator(schema_value, instance, schema, path) def type_validator(self, schema_type: str, instance: Any, _schema: dict, path: list[str | int]) -> Generator: + """Validates the type of `instance` equal to `schema_type`.""" if not self.is_type(instance, schema_type): yield AvdValidationError( f"Invalid type '{type(instance).__name__}'. Expected a '{schema_type}'.", @@ -57,6 +60,7 @@ def type_validator(self, schema_type: str, instance: Any, _schema: dict, path: l ) def unique_keys_validator(self, unique_keys: list[str], instance: list, _schema: dict, path: list[str | int]) -> Generator: + """Validates that the keys defined in the `unique_keys` list are unique in the `instance`.""" if not instance: return @@ -116,16 +120,21 @@ def keys_validator(self, keys: dict, instance: dict, schema: dict, path: list[st # Validation of "allow_other_keys" if not schema.get("allow_other_keys", False): # Check that instance only contains the schema keys + invalid_keys = ", ".join([key for key in instance if key not in all_keys and not key.startswith("_")]) if invalid_keys: yield AvdValidationError(f"Unexpected key(s) '{invalid_keys}' found in dict.", path=path) + old_relaxed_validation = self._relaxed_validation + if (relaxed_validation := schema.get("relaxed_validation")) is not None: + self._relaxed_validation = relaxed_validation + # Run over child keys and check for required and update child schema with dynamic valid values before # descending into validation of child schema. for key, childschema in all_keys.items(): if instance.get(key) is None: # Validation of "required" on child keys - if childschema.get("required"): + if childschema.get("required") and not self._relaxed_validation: yield AvdValidationError(f"Required key '{key}' is not set in dict.", path=path) # Skip further validation since there is nothing to validate. @@ -147,6 +156,9 @@ def keys_validator(self, keys: dict, instance: dict, schema: dict, path: list[st # Perform validation of the modified childschema. yield from self.validate(instance[key], modified_childschema, path=[*path, key]) + # Restore value + self._relaxed_validation = old_relaxed_validation + def dynamic_keys_validator(self, _dynamic_keys: dict, instance: dict, schema: dict, path: list[str | int]) -> Generator: """This function triggers the regular "keys" validator in case only dynamic_keys is set.""" if "keys" not in schema: diff --git a/python-avd/schema_tools/metaschema/meta_schema_model.py b/python-avd/schema_tools/metaschema/meta_schema_model.py index 70b7d78a5b0..de347e62bbe 100644 --- a/python-avd/schema_tools/metaschema/meta_schema_model.py +++ b/python-avd/schema_tools/metaschema/meta_schema_model.py @@ -1,8 +1,27 @@ # Copyright (c) 2023-2024 Arista Networks, Inc. # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. +""" +This module provides Pydantic models (classes) representing the meta-schema of the AVD Schema. + +Each variable in the schema is called a field, and for each type of field we have a corresponding Pydantic model: +- AvdSchemaInt +- AvdSchemaBool +- AvdSchemaStr +- AvdSchemaList +- AvdSchemaDict + +The alias "AvdSchemaField" is a union of all the models above, and can be used as an easy type hint for any field type. + +All the type-specific Pydantic models inherit the common base class "AvdSchemaBaseModel", and have local overrides +as needed. For example, only "AvdSchemaList" and "AvdSchemaDict" need to parse child fields. + +The overall schema is covered by the class "AristaAvdSchema" which inherits from "AvdSchemaDict" since the root of the schema is a dict. +""" + from __future__ import annotations +import logging from abc import ABC from enum import Enum from functools import cached_property @@ -21,24 +40,7 @@ from schema_tools.generate_classes.src_generators import SrcData -""" -This module provides Pydantic models (classes) representing the meta-schema of the AVD Schema. - -Each variable in the schema is called a field, and for each type of field we have a corresponding Pydantic model: -- AvdSchemaInt -- AvdSchemaBool -- AvdSchemaStr -- AvdSchemaList -- AvdSchemaDict - -The alias "AvdSchemaField" is a union of all the models above, and can be used as an easy type hint for any field type. - -All the type-specific Pydantic models inherit the common base class "AvdSchemaBaseModel", and have local overrides -as needed. For example, only "AvdSchemaList" and "AvdSchemaDict" need to parse child fields. - -The overall schema is covered by the class "AristaAvdSchema" which inherits from "AvdSchemaDict" since the root of the schema is a dict. -""" - +LOGGER = logging.getLogger(__name__) KEY_PATTERN = r"^[a-z][a-z0-9_]*$" """Common pattern to match legal key strings""" @@ -514,6 +516,8 @@ class DocumentationOptions(AvdSchemaBaseModel.DocumentationOptions): `schema` is the schema for each key. This is a recursive schema, so the value must conform to AVD Schema. Note that this is building the schema from values in the _data_ being validated! """ + relaxed_validation: bool | None = False + """Disable validation of `required` keys for any children.""" allow_other_keys: bool | None = False """Allow keys in the dictionary which are not defined in the schema.""" documentation_options: DocumentationOptions | None = None diff --git a/python-avd/schema_tools/metaschema/resolvemodel.py b/python-avd/schema_tools/metaschema/resolvemodel.py index 9b4cd5455cc..7764f45c5bb 100644 --- a/python-avd/schema_tools/metaschema/resolvemodel.py +++ b/python-avd/schema_tools/metaschema/resolvemodel.py @@ -38,7 +38,7 @@ def merge_schema_from_ref(schema: dict, resolve_schema: Literal["eos_designs", " raise ValueError(msg) pure_ref_schema = ( - {"type", "$ref", "description", "documentation_options", "deprecation"}.issuperset(schema.keys()) + {"type", "$ref", "description", "documentation_options", "deprecation", "relaxed_validation"}.issuperset(schema.keys()) and resolve_schema not in [None, "all"] and not schema["$ref"].startswith(f"{resolve_schema}#") )