--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2023 by Frank Brehm, Berlin
+@summary: A mixin module for the Cobbler class for files dependend methods.
+"""
+from __future__ import absolute_import, print_function
+
+# Standard modules
+import logging
+import hashlib
+import textwrap
+
+# Third party modules
+import paramiko
+from paramiko.ssh_exception import SSHException
+
+from fb_tools.common import pp, to_str, is_sequence, to_bool
+from fb_tools.xlate import format_list
+
+# Own modules
+
+from .. import print_section_start, print_section_end
+
+from ..errors import ExpectedCobblerError
+
+from ..xlate import XLATOR
+
+__version__ = '0.1.0'
+
+LOG = logging.getLogger(__name__)
+
+_ = XLATOR.gettext
+ngettext = XLATOR.ngettext
+
+# =============================================================================
+class CobblerFiles():
+ """
+ A mixin class for extending the Cobbler class for files dependend methods.
+ """
+
+ # -------------------------------------------------------------------------
+ def scp_to(self, local_file, remote_file):
+
+ ssh = None
+
+ try:
+
+ if self.verbose > 2:
+ LOG.debug(_("Initializing {} ...").format('paramiko SSHClient'))
+ ssh = paramiko.SSHClient()
+ if self.verbose > 2:
+ LOG.debug(_("Loading SSH system host keys."))
+ ssh.load_system_host_keys()
+ if self.verbose > 2:
+ LOG.debug(_("Setting SSH missing host key policy to {}.").format('AutoAddPolicy'))
+ ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
+
+ if self.verbose > 1:
+ LOG.debug(_("Connecting to {h!r}, port {p} as {u!r} per SSH ...").format(
+ h=self.host, p=self.ssh_port, u=self.ssh_user))
+
+ if self.simulate:
+ LOG.debug(_(
+ "Simulating SCP of {local!r} to {user}@{host}:{remote} ...").format(
+ local=str(local_file), user=self.ssh_user,
+ host=self.host, remote=str(remote_file)))
+
+ else:
+ ssh.connect(
+ self.host, port=self.ssh_port, timeout=self.ssh_timeout,
+ username=self.ssh_user, key_filename=self.private_ssh_key)
+
+ sftp = ssh.open_sftp()
+
+ LOG.debug(_("SCP of {local!r} to {user}@{host}:{remote} ...").format(
+ local=str(local_file), user=self.ssh_user,
+ host=self.host, remote=str(remote_file)))
+
+ sftp.put(str(local_file), str(remote_file))
+
+ except SSHException as e:
+ msg = _("Could not connect via {w} to {user}@{host}: {e}").format(
+ w='SCP', user=self.ssh_user, host=self.host, e=e)
+ raise ExpectedCobblerError(msg)
+
+ finally:
+ sftp = None
+ if ssh:
+ if self.verbose > 2:
+ LOG.debug(_("Closing SSH connection."))
+ ssh.close()
+
+ # -------------------------------------------------------------------------
+ def check_remote_directory(self, rdir, desc=None):
+
+ if self.verbose > 1:
+ msg = _("Checking existence of remote directory {!r} ...").format(str(rdir))
+ LOG.debug(msg)
+
+ cmd = textwrap.dedent("""\
+ if [ -d {rdir!r} ] ; then
+ exit 0
+ fi
+ exit 7
+ """).format(rdir=str(rdir))
+
+ proc = self.exec_ssh(cmd)
+ if proc.returncode != 0:
+ dsc = _('Remote directory')
+ if desc:
+ dsc = desc
+ msg = _(
+ "{dsc} {rdir!r} on host {host!r} does not exists or is not a directory.").format(
+ dsc=dsc, rdir=str(rdir), host=self.host)
+ raise ExpectedCobblerError(msg)
+
+ # -------------------------------------------------------------------------
+ def ensure_remote_directory(self, rdir, desc=None):
+
+ if self.verbose:
+ msg = _("Ensuring existence of remote directory {!r} ...").format(str(rdir))
+ LOG.debug(msg)
+
+ verb = ''
+ if self.verbose:
+ verb = " --verbose"
+
+ cmd = textwrap.dedent("""\
+ if [ -d {rdir!r} ] ; then
+ exit 0
+ fi
+ if [ -e {rdir!r} ] ; then
+ echo "Path {rdir!r} exists, but is not a directory." >&2
+ exit 7
+ fi
+ mkdir --parents{verb} {rdir!r}
+ """).format(rdir=str(rdir), verb=verb)
+
+ proc = self.exec_ssh(cmd)
+ if proc.returncode == 0:
+ if proc.stdout:
+ LOG.debug(_("Output:") + "\n{}".format(proc.stdout))
+ else:
+ dsc = _('Remote directory')
+ if desc:
+ dsc = desc
+ err = _('No error message')
+ if proc.stderr:
+ err = proc.stderr
+ elif proc.stdout:
+ err = proc.stdout
+ msg = _(
+ "{dsc} {rdir!r} on host {host!r} could not be created: {err}").format(
+ dsc=dsc, rdir=str(rdir), host=self.host, err=err)
+ raise ExpectedCobblerError(msg)
+
+ # -------------------------------------------------------------------------
+ def ensure_remote_file(self, local_file, remote_file, check_parent=True):
+
+ if check_parent:
+ self.check_remote_directory(remote_file.parent)
+
+ msg = _("Checking remote file {rfile!r} based on local {lfile!r} ...").format(
+ rfile=str(remote_file), lfile=str(local_file))
+ LOG.debug(msg)
+
+ if not local_file.exists() or not local_file.is_file():
+ msg = _("Local file {!r} either not exists or is not a regular file.").format(
+ str(local_file))
+ raise ExpectedCobblerError(msg)
+ local_file_content = local_file.read_bytes()
+ digest = hashlib.sha256(local_file_content).hexdigest()
+ if self.verbose > 1:
+ LOG.debug(_('{typ} sum of {ks!r} is: {dig}').format(
+ typ='SHA256', ks=str(local_file), dig=digest))
+
+ cmd = textwrap.dedent("""\
+ if [ -f {rfile!r} ] ; then
+ digest=$(sha256sum {rfile!r} | awk '{{print $1}}')
+ echo "Digest: ${{digest}}"
+ if [ "${{digest}}" != {dig!r} ] ; then
+ echo "SHA256 sum does not match." >&2
+ exit 4
+ fi
+ exit 0
+ else
+ exit 3
+ fi
+ """).format(rfile=str(remote_file), dig=digest)
+
+ proc = self.exec_ssh(cmd)
+ if proc.returncode == 0:
+ LOG.debug(_("Remote file {!r} has the correct content.").format(
+ str(remote_file)))
+ return
+
+ msg = _("File {!r} has to be copied.").format(str(local_file))
+ LOG.warn(msg)
+
+ self.scp_to(local_file, remote_file)
+
+ # -------------------------------------------------------------------------
+ def get_remote_filecontent(self, remote_file):
+
+ LOG.debug(_("Getting content of remote file {!r} ...").format(str(remote_file)))
+
+ cmd = textwrap.dedent("""\
+ if [ -f {rfile!r} ] ; then
+ cat {rfile!r}
+ else
+ echo "Remote file does not exists." >&2
+ exit 7
+ fi
+ """).format(rfile=str(remote_file))
+
+ proc = self.exec_ssh(cmd)
+ if proc.returncode:
+ err = _('No error message')
+ if proc.stderr:
+ err = proc.stderr
+ elif proc.stdout:
+ err = proc.stdout
+ msg = _(
+ "Error getting content of {rfile!r} on host {host!r} - "
+ "returncode was {rc}: {err}").format(
+ rfile=str(remote_file), host=self.host, rc=proc.returncode, err=err)
+ raise ExpectedCobblerError(msg)
+
+ return proc.stdout
+
+ # -------------------------------------------------------------------------
+ def ensure_root_authkeys(self, tmp_auth_keys_file=None):
+
+ bname = 'auth_keys_pp_betrieb'
+ if tmp_auth_keys_file:
+ local_file = tmp_auth_keys_file
+ else:
+ local_file = self.base_dir / 'keys' / bname
+ remote_file = self.cfg.cobbler_ws_docroot / self.cfg.cobbler_ws_rel_filesdir / bname
+
+ self.ensure_remote_file(local_file, remote_file)
+
+ # -------------------------------------------------------------------------
+ def ensure_rsyslog_cfg_files(self):
+
+ files_dir = self.base_dir / 'files'
+ docroot = self.cfg.cobbler_ws_docroot / self.cfg.cobbler_ws_rel_filesdir
+ remote_dir = docroot / self.cfg.system_status
+
+ LOG.info(_("Ensuring currentness of rsyslog config files ..."))
+ print_section_start(
+ 'ensure_rsyslog_cfg_files', 'Ensuring rsyslog config files.', collapsed=True)
+
+ for local_cfg_file in files_dir.glob('*rsyslog.conf*'):
+ remote_cfg_file = remote_dir / local_cfg_file.name
+ LOG.debug(_("Ensuring {loc!r} => {rem!r}.").format(
+ loc=str(local_cfg_file), rem=str(remote_cfg_file)))
+ self.ensure_remote_file(local_cfg_file, remote_cfg_file, check_parent=False)
+
+ print_section_end('ensure_rsyslog_cfg_files')
+
+
+# =============================================================================
+if __name__ == "__main__":
+
+ pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list