With custom validators, you can implement business logic in Python. Schema-enforcer will automatically
load your plugins from the validator_directory
and run them against your host data.
The validator plugin provides two base classes: ModelValidation and JmesPathModelValidation. The former can be used when you want to implement all logic and the latter can be used as a shortcut for jmespath validation.
Use this class to implement arbitrary validation logic in Python. In order to work correctly, your Python script must meet the following criteria:
-
Exist in the
validator_directory
dir. -
Include a subclass of the BaseValidation class to correctly register with schema-enforcer.
-
Ensure you call
super().__init__()
in your class__init__
if you override. -
Provide a class method in your subclass with the following signature:
def validate(data: dict, strict: bool):
- Data is a dictionary of variables on a per-host basis.
- Strict is set to true when the strict flag is set via the CLI. You can use this to offer strict validation behavior or ignore it if not needed.
The name of your class will be used as the schema-id for mapping purposes. You can override the default schema ID
by providing a class-level id
variable.
Helper functions are provided to add pass/fail results:
def add_validation_error(self, message: str, **kwargs):
"""Add validator error to results.
Args:
message (str): error message
kwargs (optional): additional arguments to add to ValidationResult when required
"""
def add_validation_pass(self, **kwargs):
"""Add validator pass to results.
Args:
kwargs (optional): additional arguments to add to ValidationResult when required
"""
In most cases, you will not need to provide kwargs. However, if you find a use case that requires updating other fields in the ValidationResult, you can send the key/value pairs to update the result directly. This is for advanced users only.
Use this class for basic validation using jmespath expressions to query specific values in your data. In order to work correctly, your Python script must meet the following criteria:
-
Exist in the
validator_directory
dir. -
Include a subclass of the JmesPathModelValidation class to correctly register with schema-enforcer.
-
Provide the following class level variables:
top_level_properties
: Field for mapping of validator to dataid
: Schema ID to use for reporting purposes (optional - defaults to class name)left
: Jmespath expression to query your host dataright
: Value or a compiled jmespath expressionoperator
: Operator to use for comparison between left and right hand side of expressionerror
: Message to report when validation fails
The class provides the following operators for basic use cases:
"gt": int(left) > int(right),
"gte": int(left) >= int(right),
"eq": left == right,
"lt": int(left) < int(right),
"lte": int(left) <= int(right),
"contains": right in left,
If you require additional logic or need to compare other types, use the BaseValidation class and create your own validate method.
from schema_enforcer.schemas.validator import JmesPathModelValidation
class CheckInterface(JmesPathModelValidation): # pylint: disable=too-few-public-methods
top_level_properties = ["interfaces"]
id = "CheckInterface" # pylint: disable=invalid-name
left = "interfaces.*[@.type=='core'][] | length([?@])"
right = 2
operator = "gte"
error = "Less than two core interfaces"
import jmespath
from schema_enforcer.schemas.validator import JmesPathModelValidation
class CheckInterfaceIPv4(JmesPathModelValidation): # pylint: disable=too-few-public-methods
top_level_properties = ["interfaces"]
id = "CheckInterfaceIPv4" # pylint: disable=invalid-name
left = "interfaces.*[@.type=='core'][] | length([?@])"
right = jmespath.compile("interfaces.* | length([[email protected]=='core'][].ipv4)")
operator = "eq"
error = "All core interfaces do not have IPv4 addresses"
Custom validators are run with schema-enforcer validate
and schema-enforcer ansible
commands.
You map validators to keys in your data with top_level_properties
in your subclass or with schema_enforcer_schema_ids
in your data. Schema-enforcer uses the same process to map custom validators and schemas. Refer to the "Mapping Schemas" documentation
for more details.
The CheckInterface validator has a top_level_properties of "interfaces":
class CheckInterface(JmesPathModelValidation): # pylint: disable=too-few-public-methods
top_level_properties = ["interfaces"]
With automapping enabled, this validator will apply to any host with a top-level interfaces
key in the Ansible host_vars data:
---
hostname: "az-phx-pe01"
pair_rtr: "az-phx-pe02"
interfaces:
MgmtEth0/0/CPU0/0:
ipv4: "172.16.1.1"
Loopback0:
ipv4: "192.168.1.1"
ipv6: "2001:db8:1::1"
GigabitEthernet0/0/0/0:
ipv4: "10.1.0.1"
ipv6: "2001:db8::"
peer: "az-phx-pe02"
peer_int: "GigabitEthernet0/0/0/0"
type: "core"
GigabitEthernet0/0/0/1:
ipv4: "10.1.0.37"
ipv6: "2001:db8::12"
peer: "co-den-p01"
peer_int: "GigabitEthernet0/0/0/2"
type: "core"
Alternatively, you can manually map a validator in your Ansible host vars or other data files.
schema_enforcer_automap_default: false
schema_enforcer_schema_ids:
- "CheckInterface"