Blob Blame History Raw
# local.py
# Automatically copy all downloaded packages to a repository on the local
# filesystem and generating repo metadata.
#
# Copyright (C) 2015 Igor Gnatenko
#
# 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 unicode_literals
from dnf.i18n import ucd
from dnfpluginscore import _, logger

import dnf
import dnf.cli
import os
import shutil
import subprocess


class LocalConfParse(object):
    """Parsing config

    Args:
      conf (libdnf.conf.ConfigParser): Config to parse

    """
    def __init__(self, conf):
        self.conf = conf

    def get_value(self, section, key, default=None):
        if self.conf.has_section(section) and self.conf.has_option(section, key):
            return self.conf.get(section, key)
        return default

    def parse_config(self):
        conf = self.conf
        main = {}
        crepo = {}

        if not conf.has_section("main") or not conf.has_section("createrepo") or \
           not conf.has_option("main", "enabled") or not conf.has_option("createrepo", "enabled"):
            raise KeyError("Missing key")
        main["enabled"] = conf.getboolean("main", "enabled")
        crepo["enabled"] = conf.getboolean("createrepo", "enabled")

        if main["enabled"]:
            main["repodir"] = self.get_value("main", "repodir",
                                             default="/var/lib/dnf/plugins/local")
        else:
            raise KeyError("Disabled")

        if crepo["enabled"]:
            crepo["cachedir"] = self.get_value("createrepo", "cachedir")

            if conf.has_option("createrepo", "quiet"):
                crepo["quiet"] = conf.getboolean("createrepo", "quiet")
            else:
                crepo["quiet"] = True

            if conf.has_option("createrepo", "verbose"):
                crepo["verbose"] = conf.getboolean("createrepo", "verbose")
            else:
                crepo["verbose"] = False

        return main, crepo


class Local(dnf.Plugin):

    name = "local"

    def __init__(self, base, cli):
        super(Local, self).__init__(base, cli)
        self.base = base
        self.main = {}
        self.crepo = {}
        self.logger = logger

    def pre_config(self):
        conf = self.read_config(self.base.conf)

        parser = LocalConfParse(conf)
        try:
            self.main, self.crepo = parser.parse_config()
        except KeyError:
            self.main["enabled"] = False
            self.crepo["enabled"] = False
            return

        local_repo = dnf.repo.Repo("_dnf_local", self.base.conf)
        local_repo.baseurl = "file://{}".format(self.main["repodir"])
        local_repo.cost = 500
        local_repo.skip_if_unavailable = True
        self.base.repos.add(local_repo)

    def transaction(self):
        main, crepo = self.main, self.crepo

        if not main["enabled"] or not self.transaction:
            return

        repodir = main["repodir"]
        if not os.path.exists(repodir):
            try:
                os.makedirs(repodir, mode=0o755)
            except OSError as e:
                self.logger.error("local: " + _(
                    "Unable to create a directory '{}' due to '{}'").format(repodir, ucd(e)))
                return
        elif not os.path.isdir(repodir):
            self.logger.error(
                "local: " + _("'{}' is not a directory").format(repodir))
            return

        needs_rebuild = False
        for pkg in self.base.transaction.install_set:
            path = pkg.localPkg()
            if os.path.dirname(path) == repodir:
                continue
            self.logger.debug(
                "local: " + _("Copying '{}' to local repo").format(path))
            try:
                shutil.copy2(path, repodir)
                needs_rebuild = True
            except IOError:
                self.logger.error(
                    "local: " + _("Can't write file '{}'").format(os.path.join(
                        repodir, os.path.basename(path))))

        if not crepo["enabled"] or not needs_rebuild:
            return

        args = ["createrepo_c", "--update", "--unique-md-filenames"]
        if crepo["verbose"]:
            args.append("--verbose")
        elif crepo["quiet"]:
            args.append("--quiet")
        if crepo["cachedir"] is not None:
            args.append("--cachedir")
            args.append(crepo["cachedir"])
        args.append(repodir)
        self.logger.debug("local: " + _("Rebuilding local repo"))
        p = subprocess.Popen(args, stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT)
        for line in p.stdout:
            print(line.decode().rstrip("\n"))