From 8edb9ad7c9fba65e249389b5d7c2f2b7cd7ddddc Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Fri, 22 Sep 2023 10:47:33 +0200 Subject: [PATCH] Separating files dependend methods from cr_vmware_tpl.cobbler to cr_vmware_tpl.cobbler.files. --- lib/cr_vmware_tpl/cobbler/files.py | 273 +++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 lib/cr_vmware_tpl/cobbler/files.py diff --git a/lib/cr_vmware_tpl/cobbler/files.py b/lib/cr_vmware_tpl/cobbler/files.py new file mode 100644 index 0000000..1fac870 --- /dev/null +++ b/lib/cr_vmware_tpl/cobbler/files.py @@ -0,0 +1,273 @@ +#!/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 -- 2.39.5