Source code for dyndns.config

"""Load and validate the configuration."""

from __future__ import annotations

import os
import re
from io import TextIOWrapper
from typing import Any

import yaml

from dyndns.exceptions import ConfigurationError, IpAddressesError, NamesError
from dyndns.ipaddresses import validate as validate_ip
from dyndns.names import Zones, validate_hostname
from dyndns.types import Config


[docs] def load_config(config_file: str | None = None) -> Config: config_files: list[str] = [] if config_file: config_files.append(config_file) if "dyndns_CONFIG_FILE" in os.environ: config_files.append(os.environ["dyndns_CONFIG_FILE"]) config_files.append(os.path.join(os.getcwd(), ".dyndns.yml")) config_files.append("/etc/dyndns.yml") for _config_file in config_files: if os.path.exists(_config_file): config_file = _config_file break if not config_file: raise ConfigurationError("The configuration file could not be found.") stream: TextIOWrapper = open(config_file, "r") config: Config = yaml.safe_load(stream) stream.close() return config
[docs] def validate_secret(secret: Any) -> str: secret = str(secret) if re.match("^[a-zA-Z0-9]+$", secret) and len(secret) >= 8: return secret raise ConfigurationError( "The secret must be at least 8 characters " "long and may not contain any " "non-alpha-numeric characters." )
[docs] def validate_config(config: Config | None = None) -> Config: if not config: try: config = load_config() except IOError: raise ConfigurationError("The configuration file could not be found.") except yaml.error.YAMLError: raise ConfigurationError( "The configuration file is in a invalid YAML format." ) if not config: raise ConfigurationError("The configuration file could not be " "found.") if "secret" not in config: raise ConfigurationError( 'Your configuration must have a "secret" ' 'key, for example: "secret: VDEdxeTKH"' ) config["secret"] = validate_secret(config["secret"]) if "nameserver" not in config: raise ConfigurationError( 'Your configuration must have a "nameserver" ' 'key, for example: "nameserver: 127.0.0.1"' ) try: validate_ip(config["nameserver"]) except IpAddressesError: msg: str = ( 'The "nameserver" entry in your configuration is not a valid ' 'IP address: "{}".'.format(config["nameserver"]) ) raise ConfigurationError(msg) if "dyndns_domain" in config: try: validate_hostname(config["dyndns_domain"]) except NamesError as error: raise ConfigurationError(str(error)) if "zones" not in config: raise ConfigurationError('Your configuration must have a "zones" key.') if not isinstance(config["zones"], (list,)): raise ConfigurationError('Your "zones" key must contain a list of ' "zones.") if not config["zones"]: raise ConfigurationError( "You must have at least one zone configured, " 'for example: "- name: example.com" and ' '"tsig_key: tPyvZA=="' ) for zone in config["zones"]: if "name" not in zone: raise ConfigurationError( "Your zone dictionary must contain a key " '"name"' ) if "tsig_key" not in zone: raise ConfigurationError( "Your zone dictionary must contain a key " '"tsig_key"' ) try: config["zones"] = Zones(config["zones"]) except NamesError as error: raise ConfigurationError(str(error)) return config
[docs] def get_config(config_file: str | None = None) -> Config: return validate_config(load_config(config_file))