Home | History | Annotate | Download | only in publish
      1   838      Bart #!/usr/bin/python2.4
      2   838      Bart #
      3   838      Bart # CDDL HEADER START
      4   838      Bart #
      5   838      Bart # The contents of this file are subject to the terms of the
      6   838      Bart # Common Development and Distribution License (the "License").
      7   838      Bart # You may not use this file except in compliance with the License.
      8   838      Bart #
      9   838      Bart # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10   838      Bart # or http://www.opensolaris.org/os/licensing.
     11   838      Bart # See the License for the specific language governing permissions
     12   838      Bart # and limitations under the License.
     13   838      Bart #
     14   838      Bart # When distributing Covered Code, include this CDDL HEADER in each
     15   838      Bart # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16   838      Bart # If applicable, add the following below this CDDL HEADER, with the
     17   838      Bart # fields enclosed by brackets "[]" replaced with your own identifying
     18   838      Bart # information: Portions Copyright [yyyy] [name of copyright owner]
     19   838      Bart #
     20   838      Bart # CDDL HEADER END
     21   838      Bart #
     22   838      Bart # Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     23   838      Bart # Use is subject to license terms.
     24   838      Bart #
     25   838      Bart 
     26   838      Bart import sys
     27   838      Bart import os
     28   838      Bart import traceback
     29   838      Bart import getopt
     30   838      Bart import urllib
     31   838      Bart import tempfile
     32   838      Bart import gettext
     33   838      Bart import shutil
     34   838      Bart 
     35   838      Bart import pkg.fmri
     36   838      Bart import pkg.pkgtarfile as ptf
     37   838      Bart import pkg.actions as actions
     38   838      Bart import pkg.manifest as manifest
     39  1352       srw import pkg.server.catalog as catalog
     40   838      Bart import pkg.version as version
     41   838      Bart 
     42   838      Bart from pkg.misc import versioned_urlopen, gunzip_from_stream, msg, PipeError
     43   838      Bart from pkg.client import global_settings
     44   838      Bart 
     45   838      Bart def pname():
     46   838      Bart         return os.path.basename(sys.argv[0])
     47   838      Bart 
     48   838      Bart def usage(usage_error = None):
     49   838      Bart 
     50   838      Bart         if usage_error:
     51   838      Bart                 error(usage_error)
     52   838      Bart 
     53   838      Bart         print >> sys.stderr, _("""\
     54   838      Bart Usage:
     55   838      Bart         %s -r [-d dir] [-n] -v varname,url -v varname,url [-v varname,url ...] variant_type  pkgname [pkgname ...]
     56   838      Bart 
     57  1431       srw         example:
     58   838      Bart 
     59   838      Bart         %s -r -d /tmp/merge -n -v sparc,http://server1 -v i386,http://server2 arch entire
     60   838      Bart         """ % (pname(), pname()))
     61   838      Bart 
     62   838      Bart         sys.exit(2)
     63   838      Bart 
     64   838      Bart def error(error):
     65   838      Bart         """ Emit an error message prefixed by the command name """
     66   838      Bart 
     67   838      Bart         print >> sys.stderr, pname() + ": " + error
     68   838      Bart 
     69   838      Bart def fetch_files_byhash(server_url, hashes, pkgdir):
     70   838      Bart         """Given a list of files named by content hash, download from
     71   838      Bart         server_url into pkgdir."""
     72   838      Bart 
     73   838      Bart         req_dict = { }
     74   838      Bart 
     75   838      Bart         for i, k in enumerate(hashes):
     76   838      Bart                 str = "File-Name-%s" % i
     77   838      Bart                 req_dict[str] = k
     78   838      Bart 
     79   838      Bart         req_str = urllib.urlencode(req_dict)
     80   838      Bart 
     81   838      Bart         try:
     82   838      Bart                 f, v = versioned_urlopen(server_url, "filelist", [0],
     83   838      Bart                     data = req_str)
     84   838      Bart         except:
     85   838      Bart                 error(_("Unable to download files from: %s") % server_url)
     86   838      Bart                 sys.exit(1)
     87   838      Bart 
     88   838      Bart         tar_stream = ptf.PkgTarFile.open(mode = "r|", fileobj = f)
     89   838      Bart 
     90   838      Bart         if not os.path.exists(pkgdir):
     91   838      Bart                 try:
     92   838      Bart                         os.makedirs(pkgdir)
     93   838      Bart                 except:
     94   838      Bart                         error(_("Unable to create directory: %s") % pkgdir)
     95   838      Bart                         sys.exit(1)
     96   838      Bart 
     97   838      Bart         for info in tar_stream:
     98   838      Bart                 gzfobj = None
     99   838      Bart                 try:
    100   838      Bart                         # Uncompress as we retrieve the files
    101   838      Bart                         gzfobj = tar_stream.extractfile(info)
    102   838      Bart                         fpath = os.path.join(pkgdir, info.name)
    103   838      Bart                         outfile = open(fpath, "wb")
    104   838      Bart                         gunzip_from_stream(gzfobj, outfile)
    105   838      Bart                         outfile.close()
    106   838      Bart                         gzfobj.close()
    107   838      Bart                 except:
    108   838      Bart                         error(_("Unable to extract file: %s") % info.name)
    109   838      Bart                         sys.exit(1)
    110   838      Bart 
    111   838      Bart         tar_stream.close()
    112   838      Bart         f.close()
    113   838      Bart 
    114   838      Bart manifest_cache={}
    115   838      Bart null_manifest = manifest.Manifest()
    116   838      Bart 
    117   838      Bart def get_manifest(server_url, fmri):
    118   838      Bart         if not fmri: # no matching fmri
    119   838      Bart                 return null_manifest
    120   838      Bart 
    121   838      Bart         key = "%s->%s" % (server_url, fmri)
    122   838      Bart         if key not in manifest_cache:
    123   838      Bart                 manifest_cache[key] = fetch_manifest(server_url, fmri)
    124   838      Bart         return manifest_cache[key]
    125   838      Bart 
    126   838      Bart def fetch_manifest(server_url, fmri):
    127   838      Bart         """Fetch the manifest for package-fmri 'fmri' from the server
    128   838      Bart         in 'server_url'... return as Manifest object."""
    129   838      Bart         # Request manifest from server
    130   838      Bart 
    131   838      Bart         try:
    132   838      Bart                 m, v = versioned_urlopen(server_url, "manifest", [0],
    133   838      Bart                     fmri.get_url_path())
    134   838      Bart         except:
    135   838      Bart                 error(_("Unable to download manifest %s from %s") %
    136   838      Bart                     (fmri.get_url_path(), server_url))
    137   838      Bart                 sys.exit(1)
    138   838      Bart 
    139   838      Bart         # Read from server, write to file
    140   838      Bart         try:
    141   838      Bart                 mfst_str = m.read()
    142   838      Bart         except:
    143   838      Bart                 error(_("Error occurred while reading from: %s") % server_url)
    144   838      Bart                 sys.exit(1)
    145   838      Bart 
    146   838      Bart         m = manifest.Manifest()
    147   838      Bart         m.set_content(mfst_str)
    148   838      Bart 
    149   838      Bart         return m
    150   838      Bart 
    151   838      Bart catalog_cache = {}
    152   838      Bart 
    153   838      Bart def get_catalog(server_url):
    154   838      Bart         if server_url not in catalog_cache:
    155   838      Bart                 catalog_cache[server_url] = fetch_catalog(server_url)
    156   838      Bart         return catalog_cache[server_url][0]
    157   838      Bart 
    158   838      Bart def cleanup_catalogs():
    159   838      Bart         global catalog_cache
    160   838      Bart         for c, d in catalog_cache.values():
    161   838      Bart                 shutil.rmtree(d)
    162   838      Bart         catalog_cache = {}
    163   838      Bart 
    164   838      Bart def fetch_catalog(server_url):
    165   838      Bart         """Fetch the catalog from the server_url."""
    166   838      Bart 
    167   838      Bart         # open connection for catalog
    168   838      Bart         try:
    169   838      Bart                 c, v = versioned_urlopen(server_url, "catalog", [0])
    170   838      Bart         except:
    171   838      Bart                 error(_("Unable to download catalog from: %s") % server_url)
    172   838      Bart                 sys.exit(1)
    173   838      Bart 
    174   838      Bart         # make a tempdir for catalog
    175   838      Bart         dl_dir = tempfile.mkdtemp()
    176   838      Bart 
    177   838      Bart         # call catalog.recv to pull down catalog
    178   838      Bart         try:
    179  1352       srw                 catalog.ServerCatalog.recv(c, dl_dir)
    180  1431       srw         except:
    181   838      Bart                 error(_("Error while reading from: %s") % server_url)
    182   838      Bart                 sys.exit(1)
    183   838      Bart 
    184   838      Bart         # close connection to server
    185   838      Bart         c.close()
    186   838      Bart 
    187   838      Bart         # instantiate catalog object
    188  1352       srw         cat = catalog.ServerCatalog(dl_dir, read_only=True)
    189  1431       srw 
    190   838      Bart         # return (catalog, tmpdir path)
    191   838      Bart         return cat, dl_dir
    192   838      Bart 
    193   838      Bart catalog_dict = {}
    194   848      Bart def load_catalog(server_url):
    195   848      Bart         c = get_catalog(server_url)
    196   848      Bart         d = {}
    197   848      Bart         for f in c.fmris():
    198   848      Bart                 if f.pkg_name in d:
    199   848      Bart                         d[f.pkg_name].append(f)
    200   848      Bart                 else:
    201   848      Bart                         d[f.pkg_name] = [f]
    202   848      Bart                 for k in d.keys():
    203   848      Bart                         d[k].sort(reverse = True)
    204  1431       srw         catalog_dict[server_url] = d
    205   838      Bart 
    206   838      Bart def expand_fmri(server_url, fmri_string, constraint=version.CONSTRAINT_AUTO):
    207   838      Bart         """ from specified server, find matching fmri using CONSTRAINT_AUTO
    208   838      Bart         cache for performance.  Returns None if no matching fmri is found """
    209   838      Bart         if server_url not in catalog_dict:
    210   848      Bart                 load_catalog(server_url)
    211   838      Bart 
    212  1431       srw         fmri = pkg.fmri.PkgFmri(fmri_string, "5.11")
    213   838      Bart 
    214   838      Bart         for f in catalog_dict[server_url].get(fmri.pkg_name, []):
    215   838      Bart                 if not fmri.version or f.version.is_successor(fmri.version, constraint):
    216   838      Bart                         return f
    217   838      Bart         return None
    218   848      Bart 
    219   848      Bart def get_all_pkg_names(server_url):
    220   848      Bart         """ return all the pkg_names in this catalog """
    221   848      Bart         if server_url not in catalog_dict:
    222   848      Bart                 load_catalog(server_url)
    223   848      Bart         return catalog_dict[server_url].keys()
    224   838      Bart 
    225   838      Bart def get_dependencies(server_url, fmri_list):
    226   838      Bart         s = set()
    227   838      Bart         for f in fmri_list:
    228   838      Bart                 fmri = expand_fmri(server_url, f)
    229   838      Bart                 _get_dependencies(s, server_url, fmri)
    230   838      Bart         return s
    231   838      Bart 
    232   838      Bart def _get_dependencies(s, server_url, fmri):
    233   838      Bart         """ recursive incorp expansion"""
    234   838      Bart         s.add(fmri)
    235   838      Bart         for a in get_manifest(server_url, fmri).gen_actions_by_type("depend"):
    236   838      Bart                 if a.attrs["type"] == "incorporate":
    237   838      Bart                         new_fmri = expand_fmri(server_url, a.attrs["fmri"])
    238   838      Bart                         if new_fmri and new_fmri not in s:
    239   838      Bart                                 _get_dependencies(s, server_url, new_fmri)
    240   838      Bart         return s
    241   838      Bart 
    242  1431       srw 
    243   838      Bart def main_func():
    244   838      Bart 
    245   838      Bart         basedir = None
    246   838      Bart         newfmri = False
    247   838      Bart 
    248   838      Bart         # XXX /usr/lib/locale is OpenSolaris-specific.
    249   838      Bart         gettext.install("pkgmerge", "/usr/lib/locale")
    250   838      Bart 
    251   838      Bart         global_settings.client_name = "pkgmerge"
    252   838      Bart 
    253   838      Bart         try:
    254   838      Bart                opts, pargs = getopt.getopt(sys.argv[1:], "d:nrv:")
    255   838      Bart         except getopt.GetoptError, e:
    256  1431       srw                 usage(_("Illegal option -- %s") % e.opt)
    257   838      Bart 
    258   838      Bart         varlist = []
    259   838      Bart         recursive = False
    260   838      Bart         get_files = True
    261   838      Bart 
    262   838      Bart         for opt, arg in opts:
    263   838      Bart                 if opt == "-d":
    264   838      Bart                         basedir = arg
    265   838      Bart                 if opt == "-v":
    266   838      Bart                         varlist.append(arg)
    267   838      Bart                 if opt == "-r":
    268   838      Bart                         recursive = True
    269   838      Bart                 if opt == "-n":
    270   838      Bart                         get_files = False
    271  1431       srw 
    272  1431       srw 
    273   838      Bart         if len(varlist) < 2:
    274   838      Bart                 usage(_("at least two -v arguments needed to merge"))
    275  1431       srw 
    276   838      Bart         if not basedir:
    277   838      Bart                 basedir = os.getcwd()
    278   838      Bart 
    279   848      Bart         server_list = [
    280  1431       srw             v.split(",", 1)[1]
    281  1431       srw             for v in varlist
    282  1431       srw         ]
    283  1431       srw 
    284   848      Bart         if len(pargs) == 1:
    285   848      Bart                 recursive = False
    286   848      Bart                 overall_set = set()
    287   848      Bart                 for s in server_list:
    288   848      Bart                         for name in get_all_pkg_names(s):
    289   848      Bart                                 overall_set.add(name)
    290   848      Bart                 fmri_arguments = list(overall_set)
    291   848      Bart 
    292   848      Bart         else:
    293   848      Bart                 fmri_arguments = pargs[1:]
    294  1431       srw 
    295  1431       srw         if not pargs:
    296  1431       srw                 usage(_("you must specify a variant"))
    297   838      Bart 
    298   838      Bart         variant = "variant.%s" % pargs[0]
    299   838      Bart 
    300   838      Bart         variant_list = [
    301   838      Bart                 v.split(",", 1)[0]
    302   838      Bart                 for v in varlist
    303   838      Bart                 ]
    304   838      Bart 
    305   838      Bart         fmri_expansions = []
    306   838      Bart 
    307   838      Bart         if recursive:
    308   838      Bart                 overall_set = set()
    309   838      Bart                 for s in server_list:
    310   838      Bart                         deps = get_dependencies(s, fmri_arguments)
    311   838      Bart                         for d in deps:
    312   838      Bart                                 if d:
    313   838      Bart                                         q = str(d).rsplit(":", 1)[0]
    314   838      Bart                                         overall_set.add(q)
    315   838      Bart                 fmri_arguments = list(overall_set)
    316  1431       srw 
    317   838      Bart         fmri_arguments.sort()
    318   838      Bart         print "Processing %d packages" % len(fmri_arguments)
    319   838      Bart 
    320   838      Bart         for fmri in fmri_arguments:
    321   838      Bart                 try:
    322   838      Bart                         fmri_list = [
    323   838      Bart                                 expand_fmri(s, fmri)
    324   838      Bart                                 for s in server_list
    325   838      Bart                                 ]
    326   838      Bart                         if len(set([
    327   838      Bart                                    str(f).rsplit(":", 1)[0]
    328   838      Bart                                    for f in fmri_list
    329   838      Bart                                    if f
    330   838      Bart                                    ])) != 1:
    331   838      Bart                                 error("fmris at different versions: %s" % fmri_list)
    332   838      Bart                                 continue
    333   838      Bart 
    334   856  johansen                 except pkg.fmri.IllegalFmri:
    335   838      Bart                         error(_("pkgfmri error"))
    336   838      Bart                         return 1
    337   838      Bart 
    338   838      Bart                 for f in fmri_list:
    339   838      Bart                         if f:
    340   838      Bart                                 basename = f.get_name()
    341   838      Bart                                 break
    342   838      Bart                 else:
    343   838      Bart                         error("No package of name %s in specified catalogs %s; ignoring." %\
    344   838      Bart                                       (fmri, server_list))
    345   838      Bart                         continue
    346  1431       srw 
    347   838      Bart                 merge_fmris(server_list, fmri_list, variant_list, variant, basedir, basename, get_files)
    348   838      Bart         cleanup_catalogs()
    349   838      Bart 
    350   838      Bart         return 0
    351   838      Bart 
    352  1431       srw def merge_fmris(server_list, fmri_list, variant_list, variant, basedir,
    353  1431       srw     basename, get_files):
    354   838      Bart 
    355   838      Bart         manifest_list = [
    356   838      Bart                 get_manifest(s, f)
    357   838      Bart                 for s, f in zip(server_list, fmri_list)
    358   838      Bart                 ]
    359   838      Bart 
    360  1431       srw         # remove variant tags and package variant metadata
    361   838      Bart         # from manifests since we're reassigning...
    362   838      Bart         # this allows merging pre-tagged packages
    363   838      Bart         for m in manifest_list:
    364   838      Bart                 for i, a in enumerate(m.actions[:]):
    365   838      Bart                         if variant in a.attrs:
    366   838      Bart                                 del a.attrs[variant]
    367   838      Bart                         if a.name == "set" and a.attrs["name"] == variant:
    368   838      Bart                                 del m.actions[i]
    369   838      Bart 
    370   838      Bart         action_lists = manifest.Manifest.comm(*tuple(manifest_list))
    371   838      Bart 
    372  1431       srw         # set fmri actions require special merge logic.
    373  1431       srw         set_fmris = []
    374  1431       srw         for l in action_lists:
    375  1431       srw                 for i, a in enumerate(l):
    376  1431       srw                         if not (a.name == "set" and
    377  1431       srw                             a.attrs["name"] == "pkg.fmri"):
    378  1431       srw                                 continue
    379  1431       srw 
    380  1431       srw                         set_fmris.append(a)
    381  1431       srw                         del l[i]
    382  1431       srw 
    383  1431       srw         # If set fmris are present, then only the most recent one
    384  1431       srw         # and add it back to the last action list.
    385  1431       srw         if set_fmris:
    386  1431       srw                 def order(a, b):
    387  1431       srw                         f1 = pkg.fmri.PkgFmri(a.attrs["value"], "5.11")
    388  1431       srw                         f2 = pkg.fmri.PkgFmri(b.attrs["value"], "5.11")
    389  1431       srw                         return cmp(f1, f2)
    390  1431       srw                 set_fmris.sort(cmp=order)
    391  1431       srw                 action_lists[-1].insert(0, set_fmris[-1])
    392  1431       srw 
    393   838      Bart         for a_list, v in zip(action_lists[0:-1], variant_list):
    394   838      Bart                 for a in a_list:
    395   838      Bart                         a.attrs[variant] = v
    396   838      Bart 
    397   838      Bart         # combine actions into single list
    398   838      Bart         allactions = reduce(lambda a,b:a + b, action_lists)
    399  1431       srw 
    400   838      Bart         # figure out which variants are actually there for this pkg
    401   838      Bart         actual_variant_list = [
    402   838      Bart                 v
    403   838      Bart                 for m, v in zip(manifest_list, variant_list)
    404   838      Bart                 if m != null_manifest
    405   838      Bart                 ]
    406   838      Bart         print "Merging %s for %s" % (basename, actual_variant_list)
    407   838      Bart 
    408   838      Bart         # add set action to document which variants are supported
    409   838      Bart         allactions.append(actions.fromstr("set name=%s %s" % (variant,
    410   838      Bart             " ".join(["value=%s" % a
    411   838      Bart                       for a in actual_variant_list
    412   838      Bart                       ]))))
    413   838      Bart 
    414   838      Bart         allactions.sort()
    415  1431       srw 
    416   838      Bart         m = manifest.Manifest()
    417  1018      Bart         m.actions = allactions
    418  1431       srw 
    419  1018      Bart         # urlquote to avoid problems w/ fmris w/ '/' character in name
    420  1018      Bart         basedir = os.path.join(basedir, urllib.quote(basename, ""))
    421   838      Bart         if not os.path.exists(basedir):
    422   838      Bart                 os.makedirs(basedir)
    423  1431       srw 
    424   838      Bart         m_file = file(os.path.join(basedir, "manifest"), "w")
    425   838      Bart         m_file.write(m.tostr_unsorted())
    426   838      Bart         m_file.close()
    427   838      Bart 
    428   838      Bart         for f in fmri_list:
    429   838      Bart                 if f:
    430  1018      Bart                         fmri = str(f).rsplit(":", 1)[0]
    431   838      Bart                         break
    432   838      Bart         f_file = file(os.path.join(basedir, "fmri"), "w")
    433   838      Bart         f_file.write(fmri)
    434   838      Bart         f_file.close()
    435   838      Bart 
    436  1431       srw 
    437   838      Bart         if get_files:
    438   848      Bart                 # generate list of hashes for each server; last is commom
    439   848      Bart                 already_seen = {}
    440   848      Bart                 def repeated(a, d):
    441   848      Bart                         if a in d:
    442   848      Bart                                 return True
    443   848      Bart                         d[a] = 1
    444   848      Bart                         return False
    445   848      Bart 
    446   838      Bart                 hash_sets = [
    447   838      Bart                         set(
    448   838      Bart                                 [
    449   838      Bart                                  a.hash
    450   838      Bart                                  for a in action_list
    451   848      Bart                                  if hasattr(a, "hash") and not \
    452   848      Bart                                  repeated(a.hash, already_seen)
    453   838      Bart                                 ]
    454   838      Bart                                 )
    455   838      Bart                         for action_list in action_lists
    456   838      Bart                         ]
    457   848      Bart                 # remove duplicate files (save time)
    458  1431       srw 
    459   838      Bart                 for server, hash_set in zip(server_list + [server_list[0]], hash_sets):
    460   838      Bart                         if len(hash_set) > 0:
    461   838      Bart                                 fetch_files_byhash(server, hash_set, basedir)
    462   838      Bart 
    463   838      Bart         return 0
    464   838      Bart 
    465   838      Bart 
    466   838      Bart if __name__ == "__main__":
    467   838      Bart         try:
    468   838      Bart                 ret = main_func()
    469   838      Bart         except SystemExit, e:
    470   838      Bart                 raise e
    471   838      Bart         except (PipeError, KeyboardInterrupt):
    472   838      Bart                 # We don't want to display any messages here to prevent
    473   838      Bart                 # possible further broken pipe (EPIPE) errors.
    474   838      Bart                 sys.exit(1)
    475   838      Bart         except:
    476   838      Bart                 traceback.print_exc()
    477   838      Bart                 sys.exit(99)
    478   838      Bart         sys.exit(ret)
    479   838      Bart 
    480