add mergerepo
authorSeth Vidal <skvidal@fedoraproject.org>
Fri, 17 Oct 2008 19:54:19 +0000 (15:54 -0400)
committerSeth Vidal <skvidal@fedoraproject.org>
Fri, 17 Oct 2008 19:54:19 +0000 (15:54 -0400)
bin/Makefile
bin/mergerepo [new file with mode: 0755]
createrepo/merge.py [new file with mode: 0644]
mergerepo.py [new file with mode: 0755]

index 4497230e6284728d1e0c7947d987d5a0503bd2b7..25437f92e0e8cd0ccc0b0f9abb81cae07a34d711 100644 (file)
@@ -42,6 +42,7 @@ all: $(srcdir)/$(PKGNAME)
 install: all installdirs
        $(INSTALL_BIN) $(srcdir)/$(PKGNAME) $(DESTDIR)$(bindir)/$(PKGNAME)
        $(INSTALL_BIN) $(srcdir)/modifyrepo $(DESTDIR)$(bindir)/modifyrepo
+       $(INSTALL_BIN) $(srcdir)/mergerepo $(DESTDIR)$(bindir)/mergerepo
 
 
 uninstall:
@@ -73,6 +74,7 @@ distfiles:
        $(srcdir)/$(PKGNAME) \
        $(srcdir)/Makefile \
        $(srcdir)/modifyrepo \
+       $(srcdir)/mergerepo \   
        $(top_srcdir)/.disttmp/$$distdir/bin
 
 dailyfiles:
@@ -82,6 +84,7 @@ dailyfiles:
        $(srcdir)/$(PKGNAME) \
        $(srcdir)/Makefile \
        $(srcdir)/modifyrepo \
+       $(srcdir)/mergerepo \   
        $(top_srcdir)/.disttmp/$$distdir/bin
 
 installdirs:
diff --git a/bin/mergerepo b/bin/mergerepo
new file mode 100755 (executable)
index 0000000..df6e2f1
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/share/createrepo/mergerepo.py "$@"
diff --git a/createrepo/merge.py b/createrepo/merge.py
new file mode 100644 (file)
index 0000000..d096c61
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/python -tt
+# 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 2008  Red Hat, Inc - written by seth vidal skvidal at fedoraproject.org
+
+# merge repos from arbitrary repo urls
+
+import os
+import shutil
+import yum
+import yum.Errors
+from yum.misc import unique, getCacheDir
+import yum.update_md
+import rpmUtils.arch
+import operator
+import createrepo
+import tempfile
+
+# take repo paths from cli
+# produce new repo metadata from merging the two together.
+
+#TODO:
+# excludes?
+
+
+class RepoMergeBase():
+    def __init__(self, repolist=[]):
+        self.repolist = repolist
+        self.outputdir = '%s/merged_repo' % os.getcwd()
+        self.exclude_tuples = []
+        self.sort_func = self._sort_func # callback function to magically sort pkgs
+        self.mdconf = createrepo.MetaDataConfig()
+        self.yumbase = yum.YumBase()
+        self.yumbase.conf.cachedir = getCacheDir()
+        self.yumbase.conf.cache = 0
+        # default to all arches
+        self.archlist = unique(rpmUtils.arch.arches.keys() + rpmUtils.arch.arches.values())
+        self.groups = True
+        self.updateinfo = True
+
+    def _sort_func(self, repos):
+        """Default sort func for repomerge. Takes a list of repository objects
+           any package which is not to be included in the merged repo should be
+           delPackage()'d"""
+        # sort the repos by _merge_rank
+        # - lowest number is the highest rank (1st place, 2ndplace, etc)
+        repos.sort(key=operator.attrgetter('_merge_rank'))
+
+        for repo in repos:
+            for pkg in repo.sack:
+                others = self.yumbase.pkgSack.searchNevra(name=pkg.name, arch=pkg.arch)
+                # NOTE the above is definitely going to catch other versions which may 
+                # be an invalid comparison
+                if len(others) > 1:
+                    for thatpkg in others:
+                        if pkg.repoid == thatpkg.repoid: continue
+                        if pkg.repo._merge_rank < thatpkg.repo._merge_rank:
+                            thatpkg.repo.sack.delPackage(thatpkg)
+
+    def merge_repos(self):
+        self.yumbase.repos.disableRepo('*')
+        # add our repos and give them a merge rank in the order they appear in
+        # in the repolist
+        count = 0
+        for r in self.repolist:
+            count +=1
+            rid = 'repo%s' % count
+            n = self.yumbase.add_enable_repo(rid, baseurls=[r])
+            n._merge_rank = count
+
+        #setup our sacks
+        self.yumbase._getSacks(archlist=self.archlist)
+
+        myrepos = self.yumbase.repos.listEnabled()
+
+        self.sort_func(myrepos)
+            
+
+    def write_metadata(self, outputdir=None):
+        mytempdir = tempfile.mkdtemp()
+        if self.groups:
+            comps_fn = mytempdir + '/groups.xml'
+            compsfile = open(comps_fn, 'w')
+            compsfile.write(self.yumbase.comps.xml())
+            compsfile.close()
+            self.mdconf.groupfile=comps_fn
+        
+        if self.updateinfo:
+            ui_fn = mytempdir + '/updateinfo.xml'
+            uifile = open(ui_fn, 'w')
+            umd = yum.update_md.UpdateMetadata()
+            for repo in self.yumbase.repos.listEnabled():
+                try: # attempt to grab the updateinfo.xml.gz from the repodata
+                    umd.add(repo)
+                except yum.Errors.RepoMDError:
+                    continue 
+            umd.xml(fileobj=uifile)
+            uifile.close()
+            self.mdconf.additional_metadata['updateinfo'] = ui_fn
+
+
+        self.mdconf.pkglist = self.yumbase.pkgSack
+        self.mdconf.directory = self.outputdir
+        if outputdir:
+            self.mdconf.directory = outputdir
+        # clean out what was there
+        if os.path.exists(self.mdconf.directory + '/repodata'):
+            shutil.rmtree(self.mdconf.directory + '/repodata')
+
+        if not os.path.exists(self.mdconf.directory):
+            os.makedirs(self.mdconf.directory)
+
+        mdgen = createrepo.MetaDataGenerator(config_obj=self.mdconf)
+        mdgen.doPkgMetadata()
+        mdgen.doRepoMetadata()
+        mdgen.doFinalMove()
diff --git a/mergerepo.py b/mergerepo.py
new file mode 100755 (executable)
index 0000000..9b8aeab
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/python -tt
+# 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 2008  Red Hat, Inc - written by seth vidal skvidal at fedoraproject.org
+
+# merge repos from arbitrary repo urls
+
+import sys
+import createrepo.merge
+from optparse import OptionParser
+
+#TODO:
+# excludes?
+
+def parse_args(args):
+    usage = """
+    mergerepo: take 2 or more repositories and merge their metadata into a new repo
+              
+    mergerepo --repo=url --repo=url --outputdir=/some/path"""
+    
+    parser = OptionParser(version = "mergerepo 0.1", usage=usage)
+    # query options
+    parser.add_option("-r", "--repo", dest='repos', default=[], action="append",
+                      help="repo url")
+    parser.add_option("-a", "--archlist", default=[], action="append",
+                      help="Defaults to all arches - otherwise specify arches")
+    parser.add_option("-d", "--database", default=False, action="store_true")
+    parser.add_option("-o", "--outputdir", default=None, 
+                      help="Location to create the repository")
+    parser.add_option("", "--nogroups", default=False, action="store_true",
+                      help="Do not merge group(comps) metadata")
+    parser.add_option("", "--noupdateinfo", default=False, action="store_true",
+                      help="Do not merge updateinfo metadata")
+    (opts, argsleft) = parser.parse_args()
+
+    if len(opts.repos) < 2:
+        parser.print_usage()
+        sys.exit(1)
+
+    # sort out the comma-separated crap we somehow inherited.    
+    archlist = []
+    for a in opts.archlist:
+        for arch in a.split(','):
+             archlist.append(arch)
+
+    opts.archlist = archlist
+    
+    return opts
+    
+def main(args):
+    opts = parse_args(args)
+    rm = createrepo.merge.RepoMergeBase(opts.repos)
+    if opts.archlist:
+        rm.archlist = opts.archlist
+    if opts.outputdir:
+        rm.outputdir = opts.outputdir
+    if opts.database:
+        rm.mdconf.database = True
+    if opts.nogroups:
+        rm.groups = False
+    if opts.noupdateinfo:
+        rm.updateinfo = False
+
+    rm.merge_repos()
+    rm.write_metadata()
+
+if __name__ == "__main__":
+    main(sys.argv[1:])