]> Frank Brehm's Git Trees - pixelpark/puppet-tools.git/commitdiff
Extending BaseDPXPuppetApplication by mail capabilities
authorFrank Brehm <frank.brehm@pixelpark.com>
Tue, 7 Feb 2023 14:10:14 +0000 (15:10 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Tue, 7 Feb 2023 14:10:14 +0000 (15:10 +0100)
lib/dpx_puppettools/app/__init__.py

index 207671d36e613ec51035a954a0f4d125a3b6e086..25220cbf751688d35c2fcb1225908b6f940cb4af 100644 (file)
@@ -1,28 +1,42 @@
 # -*- coding: utf-8 -*-
 """
+@summary: A base module for all DPX puppet tools application classes
 @author: Frank Brehm
 @contact: frank.brehm@pixelpark.com
 @copyright: © 2023 by Frank Brehm, Berlin
-@summary: A base module for all DPX puppet tools application classes
 """
 from __future__ import absolute_import
 
 # Standard modules
 import logging
 import shutil
+import copy
+import pipes
+import os
+
+from email.mime.text import MIMEText
+from email import charset
+
+from subprocess import Popen, PIPE
+
+import smtplib
 
 # Third party modules
 from fb_tools.cfg_app import FbConfigApplication
 from fb_tools.errors import FbAppError
-# from fb_tools.multi_config import BaseMultiConfig
+from fb_tools.xlate import format_list
+from fb_tools import MailAddress
 
 # Own modules
 from .. import __version__ as GLOBAL_VERSION
 from .. import DEFAULT_CONFIG_DIR
 from .. import DEFAULT_TERMINAL_WIDTH, DEFAULT_TERMINAL_HEIGHT
+from .. import pp
 
 from ..xlate import XLATOR
 
+from ..argparse_actions import PortOptionAction
+
 from ..config import DpxPuppetConfig
 
 LOG = logging.getLogger(__name__)
@@ -30,7 +44,7 @@ LOG = logging.getLogger(__name__)
 _ = XLATOR.gettext
 ngettext = XLATOR.ngettext
 
-__version__ = '0.2.0'
+__version__ = '0.3.0'
 
 
 # =============================================================================
@@ -56,6 +70,8 @@ class BaseDPXPuppetApplication(FbConfigApplication):
     # show_force_option = False
     show_simulate_option = True
 
+    charset.add_charset('utf-8', charset.SHORTEST, charset.QP)
+
     # -------------------------------------------------------------------------
     def __init__(
         self, appname=None, verbose=0, version=GLOBAL_VERSION, base_dir=None, quiet=False,
@@ -117,5 +133,246 @@ class BaseDPXPuppetApplication(FbConfigApplication):
         else:
             LOG.debug(_("Don't using a logfile."))
 
+        v = getattr(self.args, 'mail_method', None)
+        if v:
+            self.cfg.mail_method = v
+
+        v = getattr(self.args, 'mail_server', None)
+        if v:
+            self.cfg.mail_server = v
+
+        v = getattr(self.args, 'smtp_port', None)
+        if v is not None:
+            if v <= 0 or v > MAX_PORT_NUMBER:
+                msg = _("Got invalid SMTP port number {!r}.").format(v)
+                LOG.error(msg)
+            else:
+                self.cfg.smtp_port = v
+
+        self._perform_cmdline_mail_from()
+        self._perform_cmdline_mail_rcpt()
+        self._perform_cmdline_mail_cc()
+        self._perform_cmdline_reply_to()
+
+    # -------------------------------------------------------------------------
+    def _perform_cmdline_mail_from(self):
+
+        v = getattr(self.args, 'mail_from', None)
+        if not v:
+            return
+
+        if not MailAddress.valid_address(v):
+            msg = _("Got invalid mail from address {!r}.").format(v)
+            LOG.error(msg)
+            self.exit(1)
+
+        self.cfg.mail_from = v
+
+    # -------------------------------------------------------------------------
+    def _perform_cmdline_mail_rcpt(self):
+
+        v = getattr(self.args, 'mail_recipients', None)
+        if v is None:
+            return
+
+        recipients = []
+        bad_rcpts = []
+
+        for addr in v:
+            if MailAddress.valid_address(addr):
+                recipients.append(addr)
+            else:
+                bad_rcpts.append(addr)
+
+        if bad_rcpts:
+            msg = _("Got invalid recipient mail addresses:")
+            msg += " " + format_list(bad_rcpts, do_repr=True)
+            LOG.error(msg)
+            self.exit(1)
+
+        self.cfg.mail_recipients = copy.copy(recipients)
+
+        if not self.cfg.mail_recipients:
+            msg = ("Did not found any valid recipient mail addresses.")
+            LOG.error(msg)
+
+    # -------------------------------------------------------------------------
+    def _perform_cmdline_mail_cc(self):
+
+        v = getattr(self.args, 'mail_cc', None)
+        if v is None:
+            return
+
+        cc = []
+        bad_cc = []
+
+        for addr in v:
+            if MailAddress.valid_address(addr):
+                cc.append(addr)
+            else:
+                bad_cc.append(addr)
+
+        if bad_cc:
+            msg = _("Got invalid cc mail addresses:")
+            msg += " " + format_list(bad_cc, do_repr=True)
+            LOG.error(msg)
+            self.exit(1)
+
+        self.cfg.mail_cc = copy.copy(cc)
+
+    # -------------------------------------------------------------------------
+    def _perform_cmdline_reply_to(self):
+
+        v = getattr(self.args, 'mail_reply_to', None)
+        if not v:
+            return
+
+        if not MailAddress.valid_address(v):
+            msg = _("Got invalid reply mail address {!r}.").format(v)
+            LOG.error(msg)
+            self.exit(1)
+
+        self.cfg.reply_to = v
+
+    # -------------------------------------------------------------------------
+    def init_arg_parser(self):
+        """
+        Public available method to initiate the argument parser.
+        """
+
+        super(BaseDPXPuppetApplication, self).init_arg_parser()
+
+        mail_group = self.arg_parser.add_argument_group(_('Mailing options'))
+
+        mail_from = DpxPuppetConfig.default_mail_from_complete
+        mail_method = DpxPuppetConfig.default_mail_method
+        mail_server = DpxPuppetConfig.default_mail_server
+        smtp_port = DpxPuppetConfig.default_smtp_port
+
+        if self.cfg:
+            mail_from = self.cfg.mail_from
+            mail_method = self.cfg.mail_method
+            mail_server = self.cfg.mail_server
+            smtp_port = self.cfg.smtp_port
+
+        mail_group.add_argument(
+            '--from', '--mail-from',
+            metavar=_("ADDRESS"), dest="mail_from",
+            help=_(
+                "Sender mail address for mails generated by this script. "
+                "Default: {!r}").format(mail_from),
+        )
+
+        mail_group.add_argument(
+            '--recipients', '--mail-recipients',
+            metavar=_("ADDRESS"), nargs='+', dest="mail_recipients",
+            help=_("Mail addresses of all recipients for mails generated by this script.")
+        )
+
+        mail_group.add_argument(
+            '--cc', '--mail-cc',
+            metavar=_("ADDRESS"), nargs='*', dest="mail_cc",
+            help=_("Mail addresses of all CC recipients for mails generated by this script.")
+        )
+
+        mail_group.add_argument(
+            '--reply-to', '--mail-reply-to',
+            metavar=_("ADDRESS"), dest="mail_reply_to",
+            help=_("Reply mail address for mails generated by this script.")
+        )
+
+        methods = DpxPuppetConfig.valid_mail_methods
+        method_list = format_list(methods, do_repr=True)
+        mail_group.add_argument(
+            '--mail-method',
+            metavar=_("METHOD"), choices=methods, dest="mail_method",
+            help=_(
+                "Method for sending the mails generated by this script. "
+                "Valid values: {v}, default: {d!r}.").format(
+                    v=method_list, d=mail_method)
+        )
+
+        mail_group.add_argument(
+            '--mail-server',
+            metavar=_("SERVER"), dest="mail_server",
+            help=_(
+                "Mail server for submitting generated by this script if "
+                "the mail method of this script is 'smtp'. Default: {!r}.").format(mail_server)
+        )
+
+        mail_group.add_argument(
+            '--smtp-port',
+            metavar=_("PORT"), type=int, dest='smtp_port', what="SMTP",
+            action=PortOptionAction,
+            help=_(
+                "The port to use for submitting generated by this script if "
+                "the mail method of this script is 'smtp'. Default: {}.").format(smtp_port)
+        )
+
+    # -------------------------------------------------------------------------
+    def perform_arg_parser(self):
+
+        if self.verbose > 2:
+            LOG.debug(_("Got command line arguments:") + '\n' + pp(self.args))
+
+        super(BaseDPXPuppetApplication, self).perform_arg_parser()
+
+    # -------------------------------------------------------------------------
+    def send_mail(self, subject, body):
+
+        mail = MIMEText(body, 'plain', 'utf-8')
+        mail['Subject'] = subject
+        mail['From'] = self.cfg.mail_from
+        mail['To'] = ', '.join(self.cfg.mail_recipients)
+        mail['Reply-To'] = self.cfg.reply_to
+        mail['X-Mailer'] = self.cfg.xmailer
+        if self.mail_cc:
+            mail['Cc'] = ', '.join(self.mail_cc)
+
+        if self.verbose > 1:
+            LOG.debug(_("Mail to send:") + '\n' + mail.as_string(unixfrom=True))
+
+        if self.mail_method == 'smtp':
+            self._send_mail_smtp(mail)
+        else:
+            self._send_mail_sendmail(mail)
+
+    # -------------------------------------------------------------------------
+    def _send_mail_smtp(self, mail):
+
+        with smtplib.SMTP(self.cfg.mail_server, self.cfg.smtp_port) as smtp:
+            if self.verbose > 2:
+                smtp.set_debuglevel(2)
+            elif self.verbose > 1:
+                smtp.set_debuglevel(1)
+
+            smtp.send_message(mail)
+
+    # -------------------------------------------------------------------------
+    def _send_mail_sendmail(self, mail):
+
+        # Searching for the location of sendmail ...
+        paths = (
+            '/usr/sbin/sendmail',
+            '/usr/lib/sendmail',
+        )
+        sendmail = None
+        for path in paths:
+            if os.path.isfile(path) and os.access(path, os.X_OK):
+                sendmail = path
+                break
+
+        if not sendmail:
+            msg = _("Did not found sendmail executable.")
+            LOG.error(msg)
+            return
+
+        cmd = [sendmail, "-t", "-oi"]
+        cmd_str = ' '.join(map(lambda x: pipes.quote(x), cmd))
+        LOG.debug(_("Executing: {}").format(cmd_str))
+
+        p = Popen(cmd, stdin=PIPE, universal_newlines=True)
+        p.communicate(mail.as_string())
+
 
 # vim: ts=4 et list