Blame dnf/automatic/main.py

Packit Service 21c75c
# __init__.py
Packit Service 21c75c
# dnf.automatic CLI
Packit Service 21c75c
#
Packit Service 21c75c
# Copyright (C) 2014-2016 Red Hat, Inc.
Packit Service 21c75c
#
Packit Service 21c75c
# This copyrighted material is made available to anyone wishing to use,
Packit Service 21c75c
# modify, copy, or redistribute it subject to the terms and conditions of
Packit Service 21c75c
# the GNU General Public License v.2, or (at your option) any later version.
Packit Service 21c75c
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit Service 21c75c
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit Service 21c75c
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit Service 21c75c
# Public License for more details.  You should have received a copy of the
Packit Service 21c75c
# GNU General Public License along with this program; if not, write to the
Packit Service 21c75c
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit Service 21c75c
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit Service 21c75c
# source code or documentation are not subject to the GNU General Public
Packit Service 21c75c
# License and may only be used or replicated with the express permission of
Packit Service 21c75c
# Red Hat, Inc.
Packit Service 21c75c
#
Packit Service 21c75c
Packit Service 21c75c
from __future__ import absolute_import
Packit Service 21c75c
from __future__ import print_function
Packit Service 21c75c
from __future__ import unicode_literals
Packit Service 21c75c
Packit Service 21c75c
import argparse
Packit Service 21c75c
import logging
Packit Service 21c75c
import random
Packit Service 21c75c
import socket
Packit Service 21c75c
import time
Packit Service 21c75c
Packit Service 21c75c
from dnf.i18n import _, ucd, P_
Packit Service 21c75c
import dnf
Packit Service 21c75c
import dnf.automatic.emitter
Packit Service 21c75c
import dnf.cli
Packit Service 21c75c
import dnf.cli.cli
Packit Service 21c75c
import dnf.cli.output
Packit Service 21c75c
import dnf.conf
Packit Service 21c75c
import dnf.const
Packit Service 21c75c
import dnf.exceptions
Packit Service 21c75c
import dnf.util
Packit Service 21c75c
import dnf.logging
Packit Service 21c75c
import dnf.pycomp
Packit Service 21c75c
import libdnf.conf
Packit Service 21c75c
Packit Service 21c75c
logger = logging.getLogger('dnf')
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def build_emitters(conf):
Packit Service 21c75c
    emitters = dnf.util.MultiCallList([])
Packit Service 21c75c
    system_name = conf.emitters.system_name
Packit Service 21c75c
    emit_via = conf.emitters.emit_via
Packit Service 21c75c
    if emit_via:
Packit Service 21c75c
        for name in emit_via:
Packit Service 21c75c
            if name == 'email':
Packit Service 21c75c
                emitter = dnf.automatic.emitter.EmailEmitter(system_name, conf.email)
Packit Service 21c75c
                emitters.append(emitter)
Packit Service 21c75c
            elif name == 'stdio':
Packit Service 21c75c
                emitter = dnf.automatic.emitter.StdIoEmitter(system_name)
Packit Service 21c75c
                emitters.append(emitter)
Packit Service 21c75c
            elif name == 'motd':
Packit Service 21c75c
                emitter = dnf.automatic.emitter.MotdEmitter(system_name)
Packit Service 21c75c
                emitters.append(emitter)
Packit Service 21c75c
            elif name == 'command':
Packit Service 21c75c
                emitter = dnf.automatic.emitter.CommandEmitter(system_name, conf.command)
Packit Service 21c75c
                emitters.append(emitter)
Packit Service 21c75c
            elif name == 'command_email':
Packit Service 21c75c
                emitter = dnf.automatic.emitter.CommandEmailEmitter(system_name, conf.command_email)
Packit Service 21c75c
                emitters.append(emitter)
Packit Service 21c75c
            else:
Packit Service 21c75c
                raise dnf.exceptions.ConfigError("Unknown emitter option: %s" % name)
Packit Service 21c75c
    return emitters
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def parse_arguments(args):
Packit Service 21c75c
    parser = argparse.ArgumentParser()
Packit Service 21c75c
    parser.add_argument('conf_path', nargs='?', default=dnf.const.CONF_AUTOMATIC_FILENAME)
Packit Service 21c75c
    parser.add_argument('--timer', action='store_true')
Packit Service 21c75c
    parser.add_argument('--installupdates', dest='installupdates', action='store_true')
Packit Service 21c75c
    parser.add_argument('--downloadupdates', dest='downloadupdates', action='store_true')
Packit Service 21c75c
    parser.add_argument('--no-installupdates', dest='installupdates', action='store_false')
Packit Service 21c75c
    parser.add_argument('--no-downloadupdates', dest='downloadupdates', action='store_false')
Packit Service 21c75c
    parser.set_defaults(installupdates=None)
Packit Service 21c75c
    parser.set_defaults(downloadupdates=None)
Packit Service 21c75c
Packit Service 21c75c
    return parser.parse_args(args), parser
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class AutomaticConfig(object):
Packit Service 21c75c
    def __init__(self, filename=None, downloadupdates=None,
Packit Service 21c75c
                 installupdates=None):
Packit Service 21c75c
        if not filename:
Packit Service 21c75c
            filename = dnf.const.CONF_AUTOMATIC_FILENAME
Packit Service 21c75c
        self.commands = CommandsConfig()
Packit Service 21c75c
        self.email = EmailConfig()
Packit Service 21c75c
        self.emitters = EmittersConfig()
Packit Service 21c75c
        self.command = CommandConfig()
Packit Service 21c75c
        self.command_email = CommandEmailConfig()
Packit Service 21c75c
        self._parser = None
Packit Service 21c75c
        self._load(filename)
Packit Service 21c75c
Packit Service 21c75c
        if downloadupdates:
Packit Service 21c75c
            self.commands.download_updates = True
Packit Service 21c75c
        elif downloadupdates is False:
Packit Service 21c75c
            self.commands.download_updates = False
Packit Service 21c75c
        if installupdates:
Packit Service 21c75c
            self.commands.apply_updates = True
Packit Service 21c75c
        elif installupdates is False:
Packit Service 21c75c
            self.commands.apply_updates = False
Packit Service 21c75c
Packit Service 21c75c
        self.commands.imply()
Packit Service 21c75c
        self.filename = filename
Packit Service 21c75c
Packit Service 21c75c
    def _load(self, filename):
Packit Service 21c75c
        parser = libdnf.conf.ConfigParser()
Packit Service 21c75c
        try:
Packit Service 21c75c
            parser.read(filename)
Packit Service 21c75c
        except RuntimeError as e:
Packit Service 21c75c
            raise dnf.exceptions.ConfigError('Parsing file "%s" failed: %s' % (filename, e))
Packit Service 21c75c
        except IOError as e:
Packit Service 21c75c
            logger.warning(e)
Packit Service 21c75c
Packit Service 21c75c
        self.commands.populate(parser, 'commands', filename,
Packit Service 21c75c
                               libdnf.conf.Option.Priority_AUTOMATICCONFIG)
Packit Service 21c75c
        self.email.populate(parser, 'email', filename, libdnf.conf.Option.Priority_AUTOMATICCONFIG)
Packit Service 21c75c
        self.emitters.populate(parser, 'emitters', filename,
Packit Service 21c75c
                               libdnf.conf.Option.Priority_AUTOMATICCONFIG)
Packit Service 21c75c
        self.command.populate(parser, 'command', filename,
Packit Service 21c75c
                              libdnf.conf.Option.Priority_AUTOMATICCONFIG)
Packit Service 21c75c
        self.command_email.populate(parser, 'command_email', filename,
Packit Service 21c75c
                                    libdnf.conf.Option.Priority_AUTOMATICCONFIG)
Packit Service 21c75c
        self._parser = parser
Packit Service 21c75c
Packit Service 21c75c
    def update_baseconf(self, baseconf):
Packit Service 21c75c
        baseconf._populate(self._parser, 'base', self.filename, dnf.conf.PRIO_AUTOMATICCONFIG)
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class Config(object):
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        self._options = {}
Packit Service 21c75c
Packit Service 21c75c
    def add_option(self, name, optionobj):
Packit Service 21c75c
        self._options[name] = optionobj
Packit Service 21c75c
Packit Service 21c75c
        def prop_get(obj):
Packit Service 21c75c
            return obj._options[name].getValue()
Packit Service 21c75c
Packit Service 21c75c
        def prop_set(obj, val):
Packit Service 21c75c
            obj._options[name].set(libdnf.conf.Option.Priority_RUNTIME, val)
Packit Service 21c75c
Packit Service 21c75c
        setattr(type(self), name, property(prop_get, prop_set))
Packit Service 21c75c
Packit Service 21c75c
    def populate(self, parser, section, filename, priority):
Packit Service 21c75c
        """Set option values from an INI file section."""
Packit Service 21c75c
        if parser.hasSection(section):
Packit Service 21c75c
            for name in parser.options(section):
Packit Service 21c75c
                value = parser.getValue(section, name)
Packit Service 21c75c
                if not value or value == 'None':
Packit Service 21c75c
                    value = ''
Packit Service 21c75c
                opt = self._options.get(name, None)
Packit Service 21c75c
                if opt:
Packit Service 21c75c
                    try:
Packit Service 21c75c
                        opt.set(priority, value)
Packit Service 21c75c
                    except RuntimeError as e:
Packit Service 21c75c
                        logger.debug(_('Unknown configuration value: %s=%s in %s; %s'),
Packit Service 21c75c
                                     ucd(name), ucd(value), ucd(filename), str(e))
Packit Service 21c75c
                else:
Packit Service 21c75c
                    logger.debug(
Packit Service 21c75c
                        _('Unknown configuration option: %s = %s in %s'),
Packit Service 21c75c
                        ucd(name), ucd(value), ucd(filename))
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class CommandsConfig(Config):
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        super(CommandsConfig, self).__init__()
Packit Service 21c75c
        self.add_option('apply_updates', libdnf.conf.OptionBool(False))
Packit Service 21c75c
        self.add_option('base_config_file', libdnf.conf.OptionString('/etc/dnf/dnf.conf'))
Packit Service 21c75c
        self.add_option('download_updates', libdnf.conf.OptionBool(False))
Packit Service 21c75c
        self.add_option('upgrade_type', libdnf.conf.OptionEnumString('default',
Packit Service 21c75c
                        libdnf.conf.VectorString(['default', 'security'])))
Packit Service 21c75c
        self.add_option('random_sleep', libdnf.conf.OptionNumberInt32(300))
Packit Service 21c75c
        self.add_option('network_online_timeout', libdnf.conf.OptionNumberInt32(60))
Packit Service 21c75c
Packit Service 21c75c
    def imply(self):
Packit Service 21c75c
        if self.apply_updates:
Packit Service 21c75c
            self.download_updates = True
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class EmailConfig(Config):
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        super(EmailConfig, self).__init__()
Packit Service 21c75c
        self.add_option('email_to',
Packit Service 21c75c
                        libdnf.conf.OptionStringList(libdnf.conf.VectorString(["root"])))
Packit Service 21c75c
        self.add_option('email_from', libdnf.conf.OptionString("root"))
Packit Service 21c75c
        self.add_option('email_host', libdnf.conf.OptionString("localhost"))
Packit Service 21c75c
        self.add_option('email_port', libdnf.conf.OptionNumberInt32(25))
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class CommandConfig(Config):
Packit Service 21c75c
    _default_command_format = "cat"
Packit Service 21c75c
    _default_stdin_format = "{body}"
Packit Service 21c75c
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        super(CommandConfig, self).__init__()
Packit Service 21c75c
        self.add_option('command_format',
Packit Service 21c75c
                        libdnf.conf.OptionString(self._default_command_format))
Packit Service 21c75c
        self.add_option('stdin_format',
Packit Service 21c75c
                        libdnf.conf.OptionString(self._default_stdin_format))
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class CommandEmailConfig(CommandConfig):
Packit Service 21c75c
    _default_command_format = "mail -Ssendwait -s {subject} -r {email_from} {email_to}"
Packit Service 21c75c
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        super(CommandEmailConfig, self).__init__()
Packit Service 21c75c
        self.add_option('email_to',
Packit Service 21c75c
                        libdnf.conf.OptionStringList(libdnf.conf.VectorString(["root"])))
Packit Service 21c75c
        self.add_option('email_from', libdnf.conf.OptionString("root"))
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class EmittersConfig(Config):
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        super(EmittersConfig, self).__init__()
Packit Service 21c75c
        self.add_option('emit_via', libdnf.conf.OptionStringList(
Packit Service 21c75c
            libdnf.conf.VectorString(['email', 'stdio'])))
Packit Service 21c75c
        self.add_option('output_width', libdnf.conf.OptionNumberInt32(80))
Packit Service 21c75c
        self.add_option('system_name', libdnf.conf.OptionString(socket.gethostname()))
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def gpgsigcheck(base, pkgs):
Packit Service 21c75c
    ok = True
Packit Service 21c75c
    for po in pkgs:
Packit Service 21c75c
        result, errmsg = base.package_signature_check(po)
Packit Service 21c75c
        if result != 0:
Packit Service 21c75c
            ok = False
Packit Service 21c75c
            logger.critical(errmsg)
Packit Service 21c75c
    if not ok:
Packit Service 21c75c
        raise dnf.exceptions.Error(_("GPG check FAILED"))
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def wait_for_network(repos, timeout):
Packit Service 21c75c
    '''
Packit Service 21c75c
    Wait up to <timeout> seconds for network connection to be available.
Packit Service 21c75c
    if <timeout> is 0 the network availability detection will be skipped.
Packit Service 21c75c
    Returns True if any remote repository is accessible or remote repositories are not enabled.
Packit Service 21c75c
    Returns False if none of remote repositories is accessible.
Packit Service 21c75c
    '''
Packit Service 21c75c
    if timeout <= 0:
Packit Service 21c75c
        return True
Packit Service 21c75c
Packit Service 21c75c
    remote_schemes = {
Packit Service 21c75c
        'http': 80,
Packit Service 21c75c
        'https': 443,
Packit Service 21c75c
        'ftp': 21,
Packit Service 21c75c
    }
Packit Service 21c75c
Packit Service 21c75c
    def remote_address(url_list):
Packit Service 21c75c
        for url in url_list:
Packit Service 21c75c
            parsed_url = dnf.pycomp.urlparse.urlparse(url)
Packit Service 21c75c
            if parsed_url.hostname and parsed_url.scheme in remote_schemes:
Packit Service 21c75c
                yield (parsed_url.hostname,
Packit Service 21c75c
                       parsed_url.port or remote_schemes[parsed_url.scheme])
Packit Service 21c75c
Packit Service 21c75c
    # collect possible remote repositories urls
Packit Service 21c75c
    addresses = set()
Packit Service 21c75c
    for repo in repos.iter_enabled():
Packit Service 21c75c
        addresses.update(remote_address(repo.baseurl))
Packit Service 21c75c
        addresses.update(remote_address([repo.mirrorlist]))
Packit Service 21c75c
        addresses.update(remote_address([repo.metalink]))
Packit Service 21c75c
Packit Service 21c75c
    if not addresses:
Packit Service 21c75c
        # there is no remote repository enabled so network connection should not be needed
Packit Service 21c75c
        return True
Packit Service 21c75c
Packit Service 21c75c
    logger.debug(_('Waiting for internet connection...'))
Packit Service 21c75c
    time_0 = time.time()
Packit Service 21c75c
    while time.time() - time_0 < timeout:
Packit Service 21c75c
        for host, port in addresses:
Packit Service 21c75c
            try:
Packit Service 21c75c
                s = socket.create_connection((host, port), 1)
Packit Service 21c75c
                s.close()
Packit Service 21c75c
                return True
Packit Service 21c75c
            except socket.error:
Packit Service 21c75c
                pass
Packit Service 21c75c
        time.sleep(1)
Packit Service 21c75c
    return False
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def main(args):
Packit Service 21c75c
    (opts, parser) = parse_arguments(args)
Packit Service 21c75c
Packit Service 21c75c
    try:
Packit Service 21c75c
        conf = AutomaticConfig(opts.conf_path, opts.downloadupdates,
Packit Service 21c75c
                               opts.installupdates)
Packit Service 21c75c
        with dnf.Base() as base:
Packit Service 21c75c
            cli = dnf.cli.Cli(base)
Packit Service 21c75c
            cli._read_conf_file()
Packit Service 21c75c
            # Although dnf-automatic does not use demands, the versionlock
Packit Service 21c75c
            # plugin uses this demand do decide whether it's rules should
Packit Service 21c75c
            # be applied.
Packit Service 21c75c
            # https://bugzilla.redhat.com/show_bug.cgi?id=1746562
Packit Service 21c75c
            cli.demands.resolving = True
Packit Service 21c75c
            conf.update_baseconf(base.conf)
Packit Service 21c75c
            base.init_plugins(cli=cli)
Packit Service 21c75c
            logger.debug(_('Started dnf-automatic.'))
Packit Service 21c75c
Packit Service 21c75c
            if opts.timer:
Packit Service 21c75c
                sleeper = random.randint(0, conf.commands.random_sleep)
Packit Service 21c75c
                logger.debug(P_('Sleep for {} second', 'Sleep for {} seconds', sleeper).format(sleeper))
Packit Service 21c75c
                time.sleep(sleeper)
Packit Service 21c75c
Packit Service 21c75c
            base.pre_configure_plugins()
Packit Service 21c75c
            base.read_all_repos()
Packit Service 21c75c
Packit Service 21c75c
            if not wait_for_network(base.repos, conf.commands.network_online_timeout):
Packit Service 21c75c
                logger.warning(_('System is off-line.'))
Packit Service 21c75c
Packit Service 21c75c
            base.configure_plugins()
Packit Service 21c75c
            base.fill_sack()
Packit Service 21c75c
            upgrade(base, conf.commands.upgrade_type)
Packit Service 21c75c
            base.resolve()
Packit Service 21c75c
            output = dnf.cli.output.Output(base, base.conf)
Packit Service 21c75c
            trans = base.transaction
Packit Service 21c75c
            if not trans:
Packit Service 21c75c
                return 0
Packit Service 21c75c
Packit Service 21c75c
            lst = output.list_transaction(trans, total_width=80)
Packit Service 21c75c
            emitters = build_emitters(conf)
Packit Service 21c75c
            emitters.notify_available(lst)
Packit Service 21c75c
            if not conf.commands.download_updates:
Packit Service 21c75c
                emitters.commit()
Packit Service 21c75c
                return 0
Packit Service 21c75c
Packit Service 21c75c
            base.download_packages(trans.install_set)
Packit Service 21c75c
            emitters.notify_downloaded()
Packit Service 21c75c
            if not conf.commands.apply_updates:
Packit Service 21c75c
                emitters.commit()
Packit Service 21c75c
                return 0
Packit Service 21c75c
Packit Service 21c75c
            gpgsigcheck(base, trans.install_set)
Packit Service 21c75c
            base.do_transaction()
Packit Service 21c75c
            emitters.notify_applied()
Packit Service 21c75c
            emitters.commit()
Packit Service 21c75c
    except dnf.exceptions.Error as exc:
Packit Service 21c75c
        logger.error(_('Error: %s'), ucd(exc))
Packit Service 21c75c
        return 1
Packit Service 21c75c
    return 0
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def upgrade(base, upgrade_type):
Packit Service 21c75c
    if upgrade_type == 'security':
Packit Service 21c75c
        base._update_security_filters.append(base.sack.query().upgrades().filterm(
Packit Service 21c75c
            advisory_type='security'))
Packit Service 21c75c
        base.upgrade_all()
Packit Service 21c75c
    elif upgrade_type == 'default':
Packit Service 21c75c
        base.upgrade_all()
Packit Service 21c75c
    else:
Packit Service 21c75c
        raise dnf.exceptions.Error(
Packit Service 21c75c
            'Unsupported upgrade_type "{}", only "default" and "security" supported'.format(
Packit Service 21c75c
                upgrade_type))