#!/usr/bin/python # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Copyright Red Hat Inc. 2007, 2008 # # Author: James Antill # Examples: # # yum --tmprepo=http://example.com/foo/bar.repo ... from yum.plugins import TYPE_INTERACTIVE import logging # for commands import urlgrabber.grabber import tempfile import os import shutil import time requires_api_version = '2.5' plugin_type = (TYPE_INTERACTIVE,) def make_validate(log, pkgs_gpgcheck, repo_gpgcheck): def tvalidate(repo): if pkgs_gpgcheck or repo_gpgcheck: # Don't ever allow them to set gpgcheck='false' if pkgs_gpgcheck and not repo.gpgcheck: log.warn("Repo %s tried to set gpgcheck=false" % repo) return False if (repo_gpgcheck and hasattr(repo, 'repo_gpgcheck') and not repo.repo_gpgcheck): log.warn("Repo %s tried to set repo_gpgcheck=false" % repo) return False # Don't allow them to set gpgkey=anything for key in repo.gpgkey: if not key.startswith('file:/'): log.warn("Repo %s tried to set gpgkey to %s" % (repo, repo.gpgkey)) return False return True return tvalidate dnames = [] class AutoCleanupDir: """ Given a directory ... let it exist until "the end", and then clean it up. """ def __init__(self, dname): self.dname = dname # Want it to live until python shutdown dnames.append(self) # Can't use __del__ as python doesn't dtrt. on exit def cleanup(self): shutil.rmtree(self.dname, ignore_errors=True) def close_hook(conduit): global dnames for dname in dnames: dname.cleanup() dnames = [] def add_dir_repo(base, trepo, cleanup): # Let people do it via. local directories ... we don't want this for # HTTP because people _should_ just generate .repo files, but for # local CDs of pkgs etc. we'll be nice. trepo_path = trepo[len("file:"):] trepo_data = tempfile.mkdtemp() if cleanup: AutoCleanupDir(trepo_data) else: os.chmod(trepo_data, 0755) trepo_name = os.path.basename(os.path.dirname(trepo_path)) tmp_fname = "%s/tmp-%s.repo" % (trepo_data, trepo_name) repoid = "T-%4.4s-%x" % (trepo_name, int(time.time())) tpc = 'true' if not lpgpgcheck: tpc = 'false' trc = 'true' if not lrgpgcheck: trc = 'false' open(tmp_fname, "wb").write("""\ [%(repoid)s] name=Tmp. repo. for %(path)s baseurl=file:%(dname)s enabled=1 gpgcheck=%(pkgs_gpgcheck)s repo_gpgcheck=%(repo_gpgcheck)s metadata_expire=0 # Make cost smaller, as we know it's "local" ... if this isn't good just create # your own .repo file. ... then you won't need to createrepo each run either. cost=500 """ % {'path' : trepo_path, 'repoid' : repoid, 'dname' : trepo_data, 'pkgs_gpgcheck' : tpc, 'repo_gpgcheck' : trc, }) if cleanup: print "Creating tmp. repodata for:", trepo_path else: print "Creating saved repodata for:", trepo_path print " * Result is saved here :", tmp_fname os.spawnlp(os.P_WAIT, "createrepo", "createrepo", "--database", "--baseurl", trepo, "--outputdir", trepo_data, trepo_path) AutoCleanupDir("%s/%s" % (base.conf.cachedir, repoid)) return tmp_fname def add_repomd_repo(base, repomd): # Let people do it via. poitning at the repomd.xml file ... smeg it trepo_data = tempfile.mkdtemp() AutoCleanupDir(trepo_data) trepo_name = os.path.basename(os.path.dirname(os.path.dirname(repomd))) tmp_fname = "%s/tmp-%s.repo" % (trepo_data, trepo_name) repoid = "T-%4.4s-%x" % (trepo_name, int(time.time())) pgpgcheck, rgpgcheck = rpgpgcheck, rrgpgcheck if repomd.startswith("file:"): pgpgcheck, rgpgcheck = lpgpgcheck, lrgpgcheck tpc = 'true' if not pgpgcheck: tpc = 'false' trc = 'true' if not rgpgcheck: trc = 'false' open(tmp_fname, "wb").write("""\ [%(repoid)s] name=Tmp. repo. for %(path)s baseurl=%(dname)s enabled=1 gpgcheck=%(pkgs_gpgcheck)s repo_gpgcheck=%(repo_gpgcheck)s metadata_expire=0 """ % {'path' : repomd, 'repoid' : repoid, 'dname' : repomd[:-len("repodata/repomd.xml")], 'pkgs_gpgcheck' : tpc, 'repo_gpgcheck' : trc, }) print "Creating tmp. repo for:", repomd AutoCleanupDir("%s/%s" % (base.conf.cachedir, repoid)) return tmp_fname # Note that mirrorlist also includes metalink, due to the anaconda hack. def add_mirrorlist_repo(base, mirrorlist): # Let people do it via. poitning at the repomd.xml file ... smeg it trepo_data = tempfile.mkdtemp() AutoCleanupDir(trepo_data) trepo_name = os.path.basename(mirrorlist) tmp_fname = "%s/tmp-%s.repo" % (trepo_data, trepo_name) repoid = "T-%4.4s-%x" % (trepo_name, int(time.time())) tpc = 'true' if not rpgpgcheck: tpc = 'false' trc = 'true' if not rrgpgcheck: trc = 'false' open(tmp_fname, "wb").write("""\ [%(repoid)s] name=Tmp. repo. for %(path)s mirrorlist=%(dname)s enabled=1 gpgcheck=true metadata_expire=0 """ % {'path' : mirrorlist, 'repoid' : repoid, 'dname' : mirrorlist, 'pkgs_gpgcheck' : tpc, 'repo_gpgcheck' : trc, }) print "Creating tmp. repo for:", mirrorlist AutoCleanupDir("%s/%s" % (base.conf.cachedir, repoid)) return tmp_fname def add_repos(base, log, tmp_repos, tvalidate, tlocvalidate, cleanup_dir_temp, nogpgcheck): """ Add temporary repos to yum. """ # Don't use self._splitArg()? ... or require URLs without commas? for trepo in tmp_repos: if trepo.startswith("~/"): trepo = "%s%s" % (os.environ['HOME'], trepo[1:]) if trepo.startswith("/"): trepo = "file:%s" % trepo validate = tvalidate if trepo.startswith("file:"): validate = tlocvalidate if trepo.startswith("file:") and trepo.endswith("/"): if not os.path.isdir(trepo[len("file:"):]): log.warn("Failed to find directory " + trepo[len("file:"):]) continue fname = add_dir_repo(base, trepo, cleanup_dir_temp) elif trepo.endswith("repodata/repomd.xml"): fname = add_repomd_repo(base, trepo) elif trepo.endswith(".repo"): grab = urlgrabber.grabber.URLGrabber() # Need to keep alive until fname is used gc_keep = tempfile.NamedTemporaryFile() fname = gc_keep.name try: fname = grab.urlgrab(trepo, fname) except urlgrabber.grabber.URLGrabError, e: log.warn("Failed to retrieve " + trepo) continue else: fname = add_mirrorlist_repo(base, trepo) base.getReposFromConfigFile(fname, validate=validate) if nogpgcheck: for repo in base.repos.listEnabled(): repo.gpgcheck = False repo.repo_gpgcheck = False # Just do it all again... base.setupProgressCallbacks() rpgpgcheck = True # Remote rrgpgcheck = False # Remote lpgpgcheck = True lrgpgcheck = False def_tmp_repos_cleanup = False def config_hook(conduit): ''' Yum Plugin Config Hook: Add the --tmprepo option. ''' global rpgpgcheck global rrgpgcheck global lpgpgcheck global lrgpgcheck global def_tmp_repos_cleanup parser = conduit.getOptParser() if not parser: return if hasattr(parser, 'plugin_option_group'): parser = parser.plugin_option_group parser.add_option("--tmprepo", action='append', type='string', dest='tmp_repos', default=[], help="enable one or more repositories from URLs", metavar='[url]') parser.add_option("--tmprepo-keep-created", action='store_true', dest='tmp_repos_cleanup', default=False, help="keep created direcotry based tmp. repos.") # We don't default to repository checks for repo files, because no one does # that signing. rpgpgcheck = conduit.confBool('main', 'pkgs_gpgcheck', default=True) rrgpgcheck = conduit.confBool('main', 'repo_gpgcheck', default=False) lpgpgcheck = conduit.confBool('main', 'pkgs_local_gpgcheck', default=rpgpgcheck) lrgpgcheck = conduit.confBool('main', 'repo_local_gpgcheck', default=False) def_tmp_repos_cleanup = conduit.confBool('main', 'cleanup', default=False) _tmprepo_done = False def prereposetup_hook(conduit): ''' Process the tmp repos from --tmprepos. ''' # Stupid group commands doing the explicit setup stuff... global _tmprepo_done if _tmprepo_done: return _tmprepo_done = True opts, args = conduit.getCmdLine() if not opts.tmp_repos: return if hasattr(conduit, 'registerPackageName'): conduit.registerPackageName("yum-plugin-tmprepo") log = logging.getLogger("yum.verbose.main") add_repos(conduit._base, log, opts.tmp_repos, make_validate(log, rpgpgcheck, rrgpgcheck), make_validate(log, lpgpgcheck, lrgpgcheck), not (opts.tmp_repos_cleanup or def_tmp_repos_cleanup), opts.nogpgcheck)