Blame dnf/automatic/emitter.py

Packit 6f3914
# emitter.py
Packit 6f3914
# Emitters for dnf-automatic.
Packit 6f3914
#
Packit 6f3914
# Copyright (C) 2014-2016 Red Hat, Inc.
Packit 6f3914
#
Packit 6f3914
# This copyrighted material is made available to anyone wishing to use,
Packit 6f3914
# modify, copy, or redistribute it subject to the terms and conditions of
Packit 6f3914
# the GNU General Public License v.2, or (at your option) any later version.
Packit 6f3914
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit 6f3914
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit 6f3914
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit 6f3914
# Public License for more details.  You should have received a copy of the
Packit 6f3914
# GNU General Public License along with this program; if not, write to the
Packit 6f3914
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 6f3914
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit 6f3914
# source code or documentation are not subject to the GNU General Public
Packit 6f3914
# License and may only be used or replicated with the express permission of
Packit 6f3914
# Red Hat, Inc.
Packit 6f3914
#
Packit 6f3914
Packit 6f3914
from __future__ import absolute_import
Packit 6f3914
from __future__ import print_function
Packit 6f3914
from __future__ import unicode_literals
Packit 6f3914
from dnf.i18n import _
Packit 6f3914
import logging
Packit 6f3914
import dnf.pycomp
Packit 6f3914
import smtplib
Packit 6f3914
import email.utils
Packit 6f3914
import subprocess
Packit 6f3914
Packit 6f3914
APPLIED = _("The following updates have been applied on '%s':")
Packit 6f3914
AVAILABLE = _("The following updates are available on '%s':")
Packit 6f3914
DOWNLOADED = _("The following updates were downloaded on '%s':")
Packit 6f3914
Packit 6f3914
logger = logging.getLogger('dnf')
Packit 6f3914
Packit 6f3914
Packit 6f3914
class Emitter(object):
Packit 6f3914
    def __init__(self, system_name):
Packit 6f3914
        self._applied = False
Packit 6f3914
        self._available_msg = None
Packit 6f3914
        self._downloaded = False
Packit 6f3914
        self._system_name = system_name
Packit 6f3914
        self._trans_msg = None
Packit 6f3914
Packit 6f3914
    def _prepare_msg(self):
Packit 6f3914
        msg = []
Packit 6f3914
        if self._applied:
Packit 6f3914
            msg.append(APPLIED % self._system_name)
Packit 6f3914
            msg.append(self._available_msg)
Packit 6f3914
        elif self._downloaded:
Packit 6f3914
            msg.append(DOWNLOADED % self._system_name)
Packit 6f3914
            msg.append(self._available_msg)
Packit 6f3914
        elif self._available_msg:
Packit 6f3914
            msg.append(AVAILABLE % self._system_name)
Packit 6f3914
            msg.append(self._available_msg)
Packit 6f3914
        else:
Packit 6f3914
            return None
Packit 6f3914
        return '\n'.join(msg)
Packit 6f3914
Packit 6f3914
    def notify_applied(self):
Packit 6f3914
        assert self._available_msg
Packit 6f3914
        self._applied = True
Packit 6f3914
Packit 6f3914
    def notify_available(self, msg):
Packit 6f3914
        self._available_msg = msg
Packit 6f3914
Packit 6f3914
    def notify_downloaded(self):
Packit 6f3914
        assert self._available_msg
Packit 6f3914
        self._downloaded = True
Packit 6f3914
Packit 6f3914
Packit 6f3914
class EmailEmitter(Emitter):
Packit 6f3914
    def __init__(self, system_name, conf):
Packit 6f3914
        super(EmailEmitter, self).__init__(system_name)
Packit 6f3914
        self._conf = conf
Packit 6f3914
Packit 6f3914
    def _prepare_msg(self):
Packit 6f3914
        if self._applied:
Packit 6f3914
            subj = _("Updates applied on '%s'.") % self._system_name
Packit 6f3914
        elif self._downloaded:
Packit 6f3914
            subj = _("Updates downloaded on '%s'.") % self._system_name
Packit 6f3914
        elif self._available_msg:
Packit 6f3914
            subj = _("Updates available on '%s'.") % self._system_name
Packit 6f3914
        else:
Packit 6f3914
            return None, None
Packit 6f3914
        return subj, super(EmailEmitter, self)._prepare_msg()
Packit 6f3914
Packit 6f3914
    def commit(self):
Packit 6f3914
        subj, body = self._prepare_msg()
Packit 6f3914
        message = dnf.pycomp.email_mime(body)
Packit 6f3914
        message.set_charset('utf-8')
Packit 6f3914
        email_from = self._conf.email_from
Packit 6f3914
        email_to = self._conf.email_to
Packit 6f3914
        message['Date'] = email.utils.formatdate()
Packit 6f3914
        message['From'] = email_from
Packit 6f3914
        message['Subject'] = subj
Packit 6f3914
        message['To'] = ','.join(email_to)
Packit 6f3914
        message['Message-ID'] = email.utils.make_msgid()
Packit 6f3914
Packit 6f3914
        # Send the email
Packit 6f3914
        try:
Packit 6f3914
            smtp = smtplib.SMTP(self._conf.email_host, timeout=300)
Packit 6f3914
            smtp.sendmail(email_from, email_to, message.as_string())
Packit 6f3914
            smtp.close()
Packit 6f3914
        except smtplib.SMTPException as exc:
Packit 6f3914
            msg = _("Failed to send an email via '%s': %s") % (
Packit 6f3914
                self._conf.email_host, exc)
Packit 6f3914
            logger.error(msg)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class CommandEmitterMixIn(object):
Packit 6f3914
    """
Packit 6f3914
    Executes a desired command, and pushes data into its stdin.
Packit 6f3914
    Both data and command can be formatted according to user preference.
Packit 6f3914
    For this reason, this class expects a {str:str} dictionary as _prepare_msg
Packit 6f3914
    return value.
Packit 6f3914
    Meant for mixing with Emitter classes, as it does not define any names used
Packit 6f3914
    for formatting on its own.
Packit 6f3914
    """
Packit 6f3914
    def commit(self):
Packit 6f3914
        command_fmt = self._conf.command_format
Packit 6f3914
        stdin_fmt = self._conf.stdin_format
Packit 6f3914
        msg = self._prepare_msg()
Packit 6f3914
        # all strings passed to shell should be quoted to avoid accidental code
Packit 6f3914
        # execution
Packit 6f3914
        quoted_msg = dict((key, dnf.pycomp.shlex_quote(val))
Packit 6f3914
                          for key, val in msg.items())
Packit 6f3914
        command = command_fmt.format(**quoted_msg)
Packit 6f3914
        stdin_feed = stdin_fmt.format(**msg).encode('utf-8')
Packit 6f3914
Packit 6f3914
        # Execute the command
Packit 6f3914
        subp = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE)
Packit 6f3914
        subp.communicate(stdin_feed)
Packit 6f3914
        subp.stdin.close()
Packit 6f3914
        if subp.wait() != 0:
Packit 6f3914
            msg = _("Failed to execute command '%s': returned %d") \
Packit 6f3914
                  % (command, subp.returncode)
Packit 6f3914
            logger.error(msg)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class CommandEmitter(CommandEmitterMixIn, Emitter):
Packit 6f3914
    def _prepare_msg(self):
Packit 6f3914
        return {'body': super(CommandEmitter, self)._prepare_msg()}
Packit 6f3914
Packit 6f3914
Packit 6f3914
class CommandEmailEmitter(CommandEmitterMixIn, EmailEmitter):
Packit 6f3914
    def _prepare_msg(self):
Packit 6f3914
        subject, body = super(CommandEmailEmitter, self)._prepare_msg()
Packit 6f3914
        return {'subject': subject,
Packit 6f3914
                'body': body,
Packit 6f3914
                'email_from': self._conf.email_from,
Packit 6f3914
                'email_to': ' '.join(self._conf.email_to)}
Packit 6f3914
Packit 6f3914
Packit 6f3914
class StdIoEmitter(Emitter):
Packit 6f3914
    def commit(self):
Packit 6f3914
        msg = self._prepare_msg()
Packit 6f3914
        print(msg)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class MotdEmitter(Emitter):
Packit 6f3914
    def commit(self):
Packit 6f3914
        msg = self._prepare_msg()
Packit 6f3914
        with open('/etc/motd', 'w') as fobj:
Packit 6f3914
            fobj.write(msg)
Packit 6f3914