From be4d7e448ecda517f31b2b67abf76d9f11193784 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Thu, 9 Nov 2017 17:36:33 +0100 Subject: [PATCH] Adding properties and evaluating configuration --- pp_lib/deploy_zones_from_pdns.py | 199 ++++++++++++++++++++++++++++++- 1 file changed, 198 insertions(+), 1 deletion(-) diff --git a/pp_lib/deploy_zones_from_pdns.py b/pp_lib/deploy_zones_from_pdns.py index 6dfe1a3..b06f7e9 100644 --- a/pp_lib/deploy_zones_from_pdns.py +++ b/pp_lib/deploy_zones_from_pdns.py @@ -12,7 +12,11 @@ import os import logging import logging.config import textwrap +import re import shlex, subprocess +import copy +import datetime +import socket from subprocess import Popen, TimeoutExpired, PIPE @@ -34,7 +38,7 @@ from .pdns_app import PDNSApiNotFoundError, PDNSApiValidationError from .pdns_zone import PdnsApiZone from .pdns_record import compare_rrsets -__version__ = '0.1.2' +__version__ = '0.2.1' LOG = logging.getLogger(__name__) @@ -50,11 +54,78 @@ class PpDeployZonesApp(PpPDNSApplication): of the BIND named daemon. """ + default_pidfile = '/run/dns-deploy-zones.pid' + + default_named_conf_dir = '/etc' + default_named_zones_cfg_file = 'named.zones.conf' + + zone_masters_local = [ + '217.66.53.87', + ] + + zone_masters_public = [ + '217.66.53.97', + ] + + default_cmd_checkconf = '/usr/sbin/named-checkconf' + default_cmd_reload = '/usr/sbin/rndc reload' + default_cmd_status = '/usr/bin/systemctl status named.service' + default_cmd_start = '/usr/bin/systemctl start named.service' + default_cmd_restart = '/usr/bin/systemctl restart named.service' + + re_ipv4_zone = re.compile(r'^((?:\d+\.)+)in-addr\.arpa\.$') + re_ipv6_zone = re.compile(r'^((?:[\da-f]\.)+)ip6\.arpa\.$') + + re_block_comment = re.compile(r'/\*.*?\*/', re.MULTILINE | re.DOTALL) + re_line_comment = re.compile(r'(?://|#).*$', re.MULTILINE) + + re_split_addresses = re.compile(r'[,;\s]+') + re_integer = re.compile(r'^\s*(\d+)\s*$') + + open_args = {} + if six.PY3: + open_args = { + 'encoding': 'utf-8', + 'errors': 'surrogateescape', + } + # ------------------------------------------------------------------------- def __init__(self, appname=None, base_dir=None, version=__version__): self.zones = [] + self._show_simulate_opt = True + + self.is_internal = False + self.pidfile_name = self.default_pidfile + + # Configuration files and directories + self.named_conf_dir = self.default_named_conf_dir + self._named_zones_cfg_file = self.default_named_zones_cfg_file + + self.zone_masters = copy.copy(self.zone_masters_public) + self.masters_configured = False + + self.tempdir = None + self.temp_zones_cfg_file = None + self.keep_tempdir = False + + self.backup_suffix = ( + '.' + datetime.datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S') + '.bak') + + self.reload_necessary = False + self.restart_necessary = False + + self.cmd_checkconf = self.default_cmd_checkconf + self.cmd_reload = self.default_cmd_reload + self.cmd_status = self.default_cmd_status + self.cmd_start = self.default_cmd_start + self.cmd_restart = self.default_cmd_restart + + self.files2replace = {} + self.moved_files = {} + + description = textwrap.dedent('''\ Generation of the BIND9 configuration file for slave zones. ''') @@ -66,6 +137,132 @@ class PpDeployZonesApp(PpPDNSApplication): self.initialized = True + # ------------------------------------------- + @property + def named_zones_cfg_file(self): + """The file for configuration of all own zones.""" + return os.path.join(self.named_conf_dir, self._named_zones_cfg_file) + + # ------------------------------------------------------------------------- + def init_arg_parser(self): + + super(PpDeployZonesApp, self).init_arg_parser() + + self.arg_parser.add_argument( + '-K', '--keep-tempdir', dest='keep_tempdir', action='store_true', + help=( + "Keeping the temporary directory instead of removing it at the end " + "(e.g. for debugging purposes)"), + ) + + # ------------------------------------------------------------------------- + def perform_arg_parser(self): + """ + Public available method to execute some actions after parsing + the command line parameters. + """ + + super(PpDeployZonesApp, self).perform_arg_parser() + + if self.args.keep_tempdir: + self.keep_tempdir = True + + # ------------------------------------------------------------------------- + def perform_config(self): + + super(PpDeployZonesApp, self).perform_config() + + for section_name in self.cfg.keys(): + + if self.verbose > 3: + LOG.debug("Checking config section {!r} ...".format(section_name)) + + section = self.cfg[section_name] + + if section_name.lower() == 'app': + self._check_path_config(section, section_name, 'pidfile', 'pidfile_name', True) + + if section_name.lower() == 'named': + self.set_named_options(section, section_name) + + if not self.masters_configured: + if self.environment == 'local': + self.zone_masters = copy.copy(self.zone_masters_local) + else: + self.zone_masters = copy.copy(self.zone_masters_public) + + # ------------------------------------------------------------------------- + def set_named_options(self, section, section_name): + + if self.verbose > 2: + LOG.debug("Evaluating config section {n!r}:\n{s}".format( + n=section_name, s=pp(section))) + + # Configuration files and directories + self._check_path_config( + section, section_name, 'config_dir', 'named_conf_dir', True) + self._check_path_config( + section, section_name, 'zones_cfg_file', '_named_zones_cfg_file', False) + + if 'masters' in section: + self._get_masters_from_cfg(section['masters'], section_name) + + for item in ('cmd_checkconf', 'cmd_reload', 'cmd_status', 'cmd_start', 'cmd_restart'): + if item in section and section[item].strip(): + setattr(self, item, section[item].strip()) + + # ------------------------------------------------------------------------- + def _get_masters_from_cfg(self, value, section_name): + + value = value.strip() + if not value: + msg = "No masters given in [{}]/masters.".format(section_name) + LOG.error(msg) + self.config_has_errors = True + return + + masters = [] + + for m in self.re_split_addresses.split(value): + if m: + m = m.strip().lower() + try: + addr_info = socket.getaddrinfo( + m, 53, proto=socket.IPPROTO_TCP, family=socket.AF_INET) + except socket.gaierror as e: + msg = ( + "Invalid hostname or address {!r} found in " + "[{}]/masters: {}").format(m, section_name, e) + LOG.error(msg) + self.config_has_errors = True + m = None + if m: + masters.append(m) + if masters: + if self.verbose > 2: + LOG.debug("Using configured masters: {}".format(pp(masters))) + self.zone_masters = masters + self.masters_configured = True + else: + LOG.warn("No valid masters found in configuration.") + + # ------------------------------------------------------------------------- + def pre_run(self): + """ + Dummy function to run before the main routine. + Could be overwritten by descendant classes. + + """ + + super(PpDeployZonesApp, self).pre_run() + + if self.environment == 'global': + LOG.error( + "Using the global DNS master is not supported, " + "please use 'local' or 'public'") + self.exit(1) + + # ============================================================================= -- 2.39.5