Source code for openfactory.schemas.infra
"""
Infrastructure Configuration Schemas for OpenFactory
This module defines Pydantic models used to validate and process infrastructure
configuration files in OpenFactory. These configurations typically describe
networking, nodes, volumes, and IP address management (IPAM) settings for
Docker Swarm-based deployments.
Key Components:
---------------
- **Volumes**: Defines named Docker volumes and associated driver options.
- **Nodes**: Represents Docker Swarm managers and workers with IP and labels.
- **Networks**: Includes support for IPAM configuration and multiple network definitions.
- **InfrastructureSchema**: Top-level model combining nodes, volumes, and networks.
- **get_infrastructure_from_config_file**: Utility function to load and validate YAML-based infrastructure config.
Validation Features:
--------------------
- Ensures that manager and worker node IP addresses are unique across the cluster.
- Uses Pydantic aliases for compatibility with external config file formats.
- Provides rich error messages via user notification hooks.
Example YAML Structure:
-----------------------
.. code-block:: yaml
nodes:
managers:
manager-1:
ip: 192.168.1.10
workers:
worker-1:
ip: 192.168.1.11
volumes:
my-volume:
driver_opts:
type: nfs
o: addr=192.168.1.20,nolock,soft,rw
device: ":/data"
networks:
openfactory-network:
name: ofa-net
ipam:
config:
- subnet: 192.168.0.0/16
gateway: 192.168.0.1
Usage:
------
Call `get_infrastructure_from_config_file(path)` to load and validate the infrastructure config.
This module is typically used by OpenFactory's bootstrap and deployment tooling.
"""
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()