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