catalog.py revision 73
23N/A#!/usr/bin/python
23N/A#
23N/A# CDDL HEADER START
23N/A#
23N/A# The contents of this file are subject to the terms of the
23N/A# Common Development and Distribution License (the "License").
23N/A# You may not use this file except in compliance with the License.
23N/A#
23N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
23N/A# or http://www.opensolaris.org/os/licensing.
23N/A# See the License for the specific language governing permissions
23N/A# and limitations under the License.
23N/A#
23N/A# When distributing Covered Code, include this CDDL HEADER in each
23N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
23N/A# If applicable, add the following below this CDDL HEADER, with the
23N/A# fields enclosed by brackets "[]" replaced with your own identifying
23N/A# information: Portions Copyright [yyyy] [name of copyright owner]
23N/A#
23N/A# CDDL HEADER END
23N/A#
23N/A# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23N/A# Use is subject to license terms.
23N/A
23N/Aimport os
23N/Aimport re
23N/Aimport sha
23N/Aimport shutil
23N/Aimport time
23N/Aimport urllib
23N/A
23N/Aimport pkg.fmri as fmri
36N/Aimport pkg.package as package
23N/A
34N/Aclass Catalog(object):
34N/A """A Catalog is the representation of the package FMRIs available to
34N/A this client or repository. Both purposes utilize the same storage
34N/A format.
24N/A
26N/A The serialized structure of the repository is an unordered list of
26N/A available package versions, followed by an unordered list of
26N/A incorporation relationships between packages. This latter section
26N/A allows the graph to be topologically sorted by the client.
26N/A
50N/A S Last-Modified: [timespec]
50N/A
37N/A XXX A authority mirror-uri ...
37N/A XXX ...
37N/A
26N/A V fmri
26N/A V fmri
26N/A ...
50N/A C fmri
50N/A C fmri
50N/A ...
26N/A I fmri fmri
26N/A I fmri fmri
26N/A ...
59N/A """
37N/A
59N/A # XXX Mirroring records also need to be allowed from client
59N/A # configuration, and not just catalogs.
59N/A #
59N/A # XXX It would be nice to include available tags and package sizes,
59N/A # although this could also be calculated from the set of manifests.
59N/A #
59N/A # XXX self.pkgs should be a dictionary, accessed by fmri string (or
59N/A # package name). Current code is O(N_packages) O(M_versions), should be
59N/A # O(1) O(M_versions), and possibly O(1) O(1). Likely a similar need for
59N/A # critical_pkgs.
59N/A #
59N/A # XXX Initial estimates suggest that the Catalog could be composed of
59N/A # 1e5 - 1e7 lines. Catalogs across these magnitudes will need to be
59N/A # spread out into chunks, and may require a delta-oriented update
59N/A # interface.
23N/A
23N/A def __init__(self):
34N/A self.catalog_root = ""
34N/A
50N/A self.attrs = {}
73N/A self.set_time()
50N/A
45N/A self.authorities = {}
26N/A self.pkgs = []
50N/A self.critical_pkgs = []
50N/A
24N/A self.relns = {}
23N/A
73N/A def set_time(self):
73N/A self.attrs["Last-Modified"] = time.strftime("%Y%m%dT%H%M%SZ")
73N/A
45N/A def add_authority(self, authority, urls):
45N/A self.authorities[authority] = urls
45N/A
45N/A def delete_authority(self, authority):
45N/A del(self.authorities[authority])
34N/A
34N/A def set_catalog_root(self, croot):
34N/A self.catalog_root = croot
34N/A
36N/A def load(self, path):
36N/A self.path = path
36N/A cfile = file(path, "r")
36N/A centries = cfile.readlines()
36N/A
36N/A for entry in centries:
36N/A # each V line is an fmri
36N/A m = re.match("^V (pkg:[^ ]*)", entry)
50N/A if m != None:
50N/A pname = m.group(1)
50N/A self.add_fmri(fmri.PkgFmri(pname, None))
36N/A
50N/A m = re.match("^S ([^:]*): (.*)", entry)
50N/A if m != None:
50N/A self.attrs[m.group(1)] = m.group(2)
50N/A
50N/A m = re.match("^C (pkg:[^ ]*)", entry)
50N/A if m != None:
50N/A pname = m.group(1)
50N/A self.critical_pkgs.append(fmri.PkgFmri(pname,
50N/A None))
36N/A
36N/A def add_fmri(self, pkgfmri):
36N/A name = pkgfmri.get_pkg_stem()
36N/A pfmri = fmri.PkgFmri(name, None)
36N/A
36N/A for pkg in self.pkgs:
39N/A if pkg.fmri.is_same_pkg(pfmri):
36N/A pkg.add_version(pkgfmri)
36N/A return
36N/A
36N/A pkg = package.Package(pfmri)
36N/A pkg.add_version(pkgfmri)
36N/A self.pkgs.append(pkg)
73N/A self.set_time()
36N/A
50N/A def add_pkg(self, pkg, critical = False):
36N/A for opkg in self.pkgs:
73N/A if pkg.fmri.is_same_pkg(opkg.fmri):
37N/A #
37N/A # XXX This package is already in the catalog
37N/A # with some version set. Are we updating the
37N/A # version set or merging the two?
37N/A #
73N/A opkg.add_version(pkg.pversions[0])
73N/A # Skip the append in the else clause
73N/A break
73N/A else:
73N/A self.pkgs.append(pkg)
26N/A
50N/A if critical:
50N/A self.critical_pkgs.append(pkg)
73N/A self.set_time()
34N/A
36N/A def get_matching_pkgs(self, pfmri, constraint):
50N/A """Iterate through the catalogs, looking for an exact fmri
50N/A match. XXX Returns a list of Package objects."""
36N/A
30N/A for pkg in self.pkgs:
59N/A if pkg.fmri.is_similar(pfmri):
30N/A return pkg.matching_versions(pfmri, constraint)
30N/A
50N/A raise KeyError, "FMRI '%s' not found in catalog" % pfmri
50N/A
50N/A def get_regex_matching_fmris(self, regex):
50N/A """Iterate through the catalogs, looking for a regular
50N/A expression match. Returns an unsorted list of PkgFmri
50N/A objects."""
50N/A
50N/A ret = []
50N/A
50N/A for p in self.pkgs:
50N/A for pv in p.pversions:
50N/A fn = "%s@%s" % (p.fmri, pv.version)
50N/A if re.search(regex, fn):
50N/A ret.append(fmri.PkgFmri(fn, None))
50N/A
50N/A if ret == []:
50N/A raise KeyError, "pattern '%s' not found in catalog" \
50N/A % regex
50N/A
50N/A return ret
30N/A
24N/A def __str__(self):
24N/A s = ""
50N/A for a in self.attrs.keys():
50N/A s = s + "S %s: %s\n" % (a, self.attrs[a])
26N/A for p in self.pkgs:
36N/A s = s + p.get_catalog_entry()
50N/A for c in self.critical_pkgs:
50N/A s = s + "C %s\n" % p
24N/A for r in self.relns:
26N/A s = s + "I %s\n" % r
24N/A return s
24N/A
45N/A def display(self):
59N/A """XXX -a to see all versions of all packages. Otherwise
59N/A default is to display latest version of packages."""
59N/A
59N/A # XXX no access to image means that client use of this function
59N/A # limited to unsophisticated output...
59N/A
59N/A if len(self.pkgs) == 0:
59N/A print "pkg: catalog is empty"
59N/A return
59N/A
45N/A for p in self.pkgs:
45N/A print "%-50s" % p.fmri
45N/A for v in p.pversions:
45N/A print " %20s" % v.version
45N/A
34N/A def difference(self, catalog):
34N/A """Return a pair of lists, the first list being those package
34N/A FMRIs present in the current object but not in the presented
34N/A catalog, the second being those present in the presented catalog
34N/A but not in the current catalog."""
34N/A return
45N/A
45N/Aif __name__ == "__main__":
45N/A c = Catalog()
45N/A
45N/A for f in [
59N/A fmri.PkgFmri("pkg:/test@1.0,5.11-1:20000101T120000Z", None),
59N/A fmri.PkgFmri("pkg:/test@1.0,5.11-1:20000101T120010Z", None),
59N/A fmri.PkgFmri("pkg:/test@1.0,5.11-1.1:20000101T120020Z", None),
59N/A fmri.PkgFmri("pkg:/test@1.0,5.11-1.2:20000101T120030Z", None),
59N/A fmri.PkgFmri("pkg:/test@1.0,5.11-2:20000101T120040Z", None),
59N/A fmri.PkgFmri("pkg:/test@1.1,5.11-1:20000101T120040Z", None)
45N/A ]:
45N/A c.add_fmri(f)
45N/A
45N/A print c
45N/A
45N/A tps = [
59N/A "pkg:/test@1.0,5.10-1:20070101T120000Z",
59N/A "pkg:/test@1.0,5.11-1:20061231T120000Z",
45N/A "pkg:/test@1.0,5.11-2",
45N/A "pkg:/test@1.0,5.11-3"
45N/A ]
45N/A
45N/A for tp in tps:
45N/A print "matches for %s:" % tp
45N/A
59N/A for p in c.get_matching_pkgs(fmri.PkgFmri(tp, None), None):
45N/A print " ", p
50N/A
50N/A for p in c.get_regex_matching_fmris("test"):
50N/A print p
50N/A
50N/A try:
50N/A l = c.get_regex_matching_fmris("flob")
50N/A except KeyError:
50N/A print "correctly determined no match for 'flob'"