Source code for openfactory.schemas.infra
""" Pydantic schemas for validating OpenFactory infrastructure configuration files. """
from typing import Dict, Optional
from pydantic import BaseModel, Field, RootModel, model_validator, ValidationError
from ipaddress import IPv4Address
from openfactory.models.user_notifications import user_notify
from openfactory.config import load_yaml
[docs]
class Volume(BaseModel):
""" Volume Schema. """
driver_opts: Optional[Dict[str, str]]
[docs]
class Volumes(RootModel[Dict[str, Optional[Volume]]]):
""" Volumes Schema. """
pass
[docs]
class Node(BaseModel):
""" Docker Swarm Node Schema. """
ip: IPv4Address
labels: Optional[Dict[str, str]] = None
[docs]
class Managers(RootModel[Dict[str, Node]]):
""" Docker Swarm Managers Schema. """
pass
[docs]
class Workers(RootModel[Dict[str, Node]]):
""" Docker Swarm Workers Schema. """
pass
[docs]
class Nodes(BaseModel):
""" Docker Swarm Nodes Schema. """
managers: Optional[Dict[str, Node]] = None
workers: Optional[Dict[str, Node]] = None
[docs]
@model_validator(mode='after')
def check_unique_ips(cls, values: Dict) -> Dict:
"""
Validates that IP addresses are unique across managers and workers.
Args:
values (Dict): Dictionary of values to validate.
Returns:
Dict: Validated values.
Raises:
ValueError: If IP addresses are not unique.
"""
ips = []
# Collect IP addresses from managers
if values.managers: # Access 'managers' directly as an attribute
ips += [node.ip for node in values.managers.values()]
# Collect IP addresses from workers
if values.workers: # Access 'workers' directly as an attribute
ips += [node.ip for node in values.workers.values()]
# Check if all IPs are unique
if len(ips) != len(set(ips)):
raise ValueError("IP addresses must be unique across managers and workers.")
return values
[docs]
class IPAMConfig(BaseModel):
""" IPAM Configuration Schema. """
subnet: str # Subnet in CIDR format
gateway: Optional[str] = None # Optional gateway IP address
ip_range: Optional[str] = Field(None, alias="ip_range") # Optional IP range
aux_addresses: Optional[Dict[str, str]] = Field(None, alias="aux_addresses") # Optional auxiliary addresses
[docs]
class IPAM(BaseModel):
""" IPAM Schema. """
config: list[IPAMConfig]
[docs]
class Network(BaseModel):
""" Network Schema. """
name: Optional[str] = None
ipam: IPAM
[docs]
class Networks(BaseModel):
""" Networks Schema. """
openfactory_network: Network = Field(..., alias="openfactory-network")
docker_ingress_network: Network = Field(..., alias="docker-ingress-network")
[docs]
class InfrastructureSchema(BaseModel):
""" Infrastructure Schema. """
nodes: Nodes
networks: Optional[Dict[str, Network]] = None
volumes: Optional[Volumes] = None
[docs]
def get_infrastructure_from_config_file(infra_yaml_config_file: str) -> Optional[Dict[str, InfrastructureSchema]]:
"""
Load and validate infrastructure configuration from a YAML file.
This function reads a YAML file containing infrastructure configuration,
validates its content using the :class:`InfrastructureSchema` Pydantic model,
and returns the parsed data as a dictionary.
Args:
infra_yaml_config_file (str): Path to the YAML file defining the infrastructure configuration.
Returns:
Optional[Dict[str, InfrastructureSchema]]: A dictionary of validated infrastructure configuration data,
or `None` if validation fails.
Note:
In case of validation errors, user notifications will be triggered and `None` will be returned.
"""
# load yaml description file
cfg = load_yaml(infra_yaml_config_file)
# validate and create devices configuration
try:
infra = InfrastructureSchema(**cfg)
except ValidationError as err:
user_notify.fail(f"Provided YAML configuration file has invalid format\n{err}")
return None
except ValueError as err:
user_notify.fail(f"Provided YAML configuration file has invalid format\n{err}")
return None
return infra.model_dump()