--- a/src/client.py Thu Apr 17 19:03:51 2008 -0700
+++ b/src/client.py Fri Apr 18 16:17:09 2008 -0700
@@ -82,10 +82,10 @@
pkg refresh [--full]
Advanced subcommands:
- pkg info [pkg_fmri_pattern ...]
+ pkg info [-lr] [--license] [pkg_fmri_pattern ...]
pkg search [-lr] [-s server] token
pkg verify [-fHqv] [pkg_fmri_pattern ...]
- pkg contents [-Hm] [-o attribute ...] [-s sort_key] [-t action_type ... ]
+ pkg contents [-Hmr] [-o attribute ...] [-s sort_key] [-t action_type ... ]
pkg_fmri_pattern [...]
pkg image-create [-FPUz] [--full|--partial|--user] [--zone]
[-k ssl_key] [-c ssl_cert] -a <prefix>=<url> dir
@@ -650,39 +650,127 @@
return retcode
+def info_license(img, mfst, remote):
+ fmri = mfst.fmri
+
+ for i, license in enumerate(mfst.gen_actions_by_type("license")):
+ if i > 0:
+ print
+
+ if remote:
+ misc.gunzip_from_stream(
+ license.get_remote_opener(img, fmri)(), sys.stdout)
+ else:
+ print license.get_local_opener(img, fmri)().read()[:-1]
+
def info(img, args):
"""Display information about a package or packages.
"""
- # XXX Need remote-info option, to request equivalent information
- # from repository.
+ display_license = False
+ info_local = False
+ info_remote = False
- opts, pargs = getopt.getopt(args, "")
+ opts, pargs = getopt.getopt(args, "lr", ["license"])
+ for opt, arg in opts:
+ if opt == "-l":
+ info_local = True
+ elif opt == "-r":
+ info_remote = True
+ elif opt == "--license":
+ display_license = True
+
+ if not info_local and not info_remote:
+ info_local = True
+ elif info_local and info_remote:
+ usage(_("info: -l and -r may not be combined"))
+
+ if info_remote and not pargs:
+ usage(_("info: must request remote info for specific packages"))
img.load_catalogs(progress.NullProgressTracker())
- err, fmris = installed_fmris_from_args(img, pargs)
- if err != 0:
- return err
- if not fmris:
- return 0
-
+ if info_local:
+ err, fmris = installed_fmris_from_args(img, pargs)
+ if err != 0:
+ return err
+ if not fmris:
+ print _("""\
+pkg: no packages matching the patterns you specified are installed
+on the system. Specify -r to retrieve requested information from the
+repository.""")
+ elif info_remote:
+ fmris = []
+
+ # XXX This loop really needs not to be copied from
+ # Image.make_install_plan()!
+ for p in pargs:
+ try:
+ matches = img.get_matching_fmris(p)
+ except KeyError:
+ print _("""\
+pkg: no package matching '%s' could be found in current catalog
+ suggest relaxing pattern, refreshing and/or examining catalogs""") % p
+ error = 1
+ continue
+
+ pnames = {}
+ pmatch = []
+ npnames = {}
+ npmatch = []
+ for m in matches:
+ if m.preferred_authority():
+ pnames[m.get_pkg_stem()] = 1
+ pmatch.append(m)
+ else:
+ npnames[m.get_pkg_stem()] = 1
+ npmatch.append(m)
+
+ if len(pnames.keys()) > 1:
+ print \
+ _("pkg: '%s' matches multiple packages") % p
+ for k in pnames.keys():
+ print "\t%s" % k
+ error = 1
+ continue
+ elif len(pnames.keys()) < 1 and len(npnames.keys()) > 1:
+ print \
+ _("pkg: '%s' matches multiple packages") % p
+ for k in npnames.keys():
+ print "\t%s" % k
+ error = 1
+ continue
+
+ # matches is a list reverse sorted by version, so take
+ # the first; i.e., the latest.
+ if len(pmatch) > 0:
+ fmris.append(pmatch[0])
+ else:
+ fmris.append(npmatch[0])
+
manifests = ( img.get_manifest(f, filtered = True) for f in fmris )
for i, m in enumerate(manifests):
if i > 0:
print
+ if display_license:
+ info_license(img, m, info_remote)
+ continue
+
authority, name, version = m.fmri.tuple()
authority = fmri.strip_auth_pfx(authority)
summary = m.get("description", "")
if m.fmri.preferred_authority():
authority += _(" (preferred)")
+ if img.is_installed(m.fmri):
+ state = _("Installed")
+ else:
+ state = _("Not installed")
print " Name:", name
print " Summary:", summary
- # XXX Hard wired for now.
- print " State: Installed"
+ print " State:", state
# XXX even more info on the authority would be nice?
print " Authority:", authority
@@ -701,8 +789,6 @@
# XXX need to properly humanize the manifest.size
# XXX add license/copyright info here?
-
-
def display_contents_results(actionlist, attrs, sort_attrs, action_types,
display_headers):
""" Print results of a "list" operation """
@@ -810,8 +896,6 @@
for line in sorted(lines, key = key_extract):
print (fmt % tuple(line)).rstrip()
-
-
def list_contents(img, args):
"""List package contents.
@@ -823,7 +907,7 @@
# XXX Need remote-info option, to request equivalent information
# from repository.
- opts, pargs = getopt.getopt(args, "Ho:s:t:mf")
+ opts, pargs = getopt.getopt(args, "Ho:s:t:mfr")
valid_special_attrs = [ "action.name", "action.key", "action.raw",
"pkg.name", "pkg.fmri", "pkg.shortfmri", "pkg.authority",
@@ -832,6 +916,8 @@
display_headers = True
display_raw = False
display_nofilters = False
+ remote = False
+ local = False
attrs = []
sort_attrs = []
action_types = []
@@ -844,12 +930,19 @@
sort_attrs.append(arg)
elif opt == "-t":
action_types.extend(arg.split(","))
+ elif opt == "-r":
+ remote = True
elif opt == "-m":
display_raw = True
elif opt == "-f":
# Undocumented, for now.
display_nofilters = True
+ if not remote and not local:
+ local = True
+ elif local and remote:
+ usage(_("contents: -l and -r may not be combined"))
+
if display_raw:
display_headers = False
attrs = [ "action.raw" ]
@@ -868,11 +961,60 @@
img.load_catalogs(progress.NullProgressTracker())
- err, fmris = installed_fmris_from_args(img, pargs)
- if err != 0:
- return err
- if not fmris:
- return 0
+ if local:
+ err, fmris = installed_fmris_from_args(img, pargs)
+ if err != 0:
+ return err
+ if not fmris:
+ return 0
+ elif remote:
+ fmris = []
+
+ # XXX This loop really needs not to be copied from
+ # Image.make_install_plan()!
+ for p in pargs:
+ try:
+ matches = img.get_matching_fmris(p)
+ except KeyError:
+ print _("""\
+pkg: no package matching '%s' could be found in current catalog
+ suggest relaxing pattern, refreshing and/or examining catalogs""") % p
+ error = 1
+ continue
+
+ pnames = {}
+ pmatch = []
+ npnames = {}
+ npmatch = []
+ for m in matches:
+ if m.preferred_authority():
+ pnames[m.get_pkg_stem()] = 1
+ pmatch.append(m)
+ else:
+ npnames[m.get_pkg_stem()] = 1
+ npmatch.append(m)
+
+ if len(pnames.keys()) > 1:
+ print \
+ _("pkg: '%s' matches multiple packages") % p
+ for k in pnames.keys():
+ print "\t%s" % k
+ error = 1
+ continue
+ elif len(pnames.keys()) < 1 and len(npnames.keys()) > 1:
+ print \
+ _("pkg: '%s' matches multiple packages") % p
+ for k in npnames.keys():
+ print "\t%s" % k
+ error = 1
+ continue
+
+ # matches is a list reverse sorted by version, so take
+ # the first; i.e., the latest.
+ if len(pmatch) > 0:
+ fmris.append(pmatch[0])
+ else:
+ fmris.append(npmatch[0])
#
# If the user specifies no specific attrs, and no specific
@@ -904,9 +1046,6 @@
display_contents_results(actionlist, attrs, sort_attrs, action_types,
display_headers)
-
-
-
def display_catalog_failures(failures):
total, succeeded = failures.args[1:3]
print _("pkg: %s/%s catalogs successfully updated:") % \
--- a/src/man/pkg.1.txt Thu Apr 17 19:03:51 2008 -0700
+++ b/src/man/pkg.1.txt Fri Apr 18 16:17:09 2008 -0700
@@ -11,8 +11,8 @@
/usr/bin/pkg uninstall [-nrvq] pkg_fmri ...
/usr/bin/pkg verify [-fHvq] [pkg_fmri_pattern ...]
- /usr/bin/pkg info pkg_fmri_pattern [pkg_fmri_pattern ...]
- /usr/bin/pkg contents [-Hm] [-o attribute ...] [-s sort_key]
+ /usr/bin/pkg info [-lr] [--license] [pkg_fmri_pattern ...]
+ /usr/bin/pkg contents [-Hmr] [-o attribute ...] [-s sort_key]
[-t action_type ... ] [pkg_fmri_pattern ...]
/usr/bin/pkg list [-aHsuv] [pkg_fmri_pattern ...]
/usr/bin/pkg search [-lr] [-s server] token
@@ -93,12 +93,21 @@
uninstall any packages which are dependent on the initial
package.
- info [pkg_fmri_pattern ...]
+ info [-lr] [--license] [pkg_fmri_pattern ...]
Display information about packages in a human-readable form.
Multiple FMRI patterns may be specified; with no patterns,
display information on all installed packages in the image.
- contents [-Hm] [-o attribute ...] [-s sort_key] [-t action_type ... ]
+ With -l, use the data available from locally installed packages.
+ This is the default.
+
+ With -r, retrieve the data from the servers. Note that you must
+ specify one or more package patterns in this case.
+
+ With --license, print out the license text(s) for the package.
+ This may be combined with -l or -r.
+
+ contents [-Hmr] [-o attribute ...] [-s sort_key] [-t action_type ... ]
[pkg_fmri_pattern ...]
Display the contents (action attributes) of packages in the
current image. By default, only the path attribute is displayed,
@@ -116,10 +125,13 @@
The -H option causes the headers to be omitted.
+ The -r option retrieves the requested data from the server, for
+ use in cases when the package is not already installed.
+
With no arguments, the output includes all installed packages.
Alternatively, multiple FMRI patterns may be specified, which
- restricts the display to the contents of the matching
- packages.
+ restricts the display to the contents of the matching packages.
+ When using -r, one or more pkg_fmri_patterns must be specified.
Several special "pseudo" attribute names are available for
convenience:
--- a/src/modules/actions/file.py Thu Apr 17 19:03:51 2008 -0700
+++ b/src/modules/actions/file.py Fri Apr 18 16:17:09 2008 -0700
@@ -35,6 +35,7 @@
import sha
from stat import *
import generic
+import pkg.misc as misc
import pkg.portable as portable
try:
import pkg.elf as elf
@@ -107,7 +108,7 @@
stream = self.data()
tfile = file(temp, "wb")
- shasum = generic.gunzip_from_stream(stream, tfile)
+ shasum = misc.gunzip_from_stream(stream, tfile)
tfile.close()
stream.close()
--- a/src/modules/actions/generic.py Thu Apr 17 19:03:51 2008 -0700
+++ b/src/modules/actions/generic.py Fri Apr 18 16:17:09 2008 -0700
@@ -28,8 +28,7 @@
"""module describing a generic packaging object
This module contains the Action class, which represents a generic packaging
-object. It also contains a helper function, gunzip_from_stream(), which actions
-may use to decompress their data payloads."""
+object."""
import os
import sha
@@ -37,73 +36,9 @@
import errno
import pkg.actions
+import pkg.client.retrieve as retrieve
import pkg.portable as portable
-def gunzip_from_stream(gz, outfile):
- """Decompress a gzipped input stream into an output stream.
-
- The argument 'gz' is an input stream of a gzipped file (XXX make it do
- either a gzipped file or raw zlib compressed data), and 'outfile' is is
- an output stream. gunzip_from_stream() decompresses data from 'gz' and
- writes it to 'outfile', and returns the hexadecimal SHA-1 sum of that
- data.
- """
-
- FHCRC = 2
- FEXTRA = 4
- FNAME = 8
- FCOMMENT = 16
-
- # Read the header
- magic = gz.read(2)
- if magic != "\037\213":
- raise IOError, "Not a gzipped file"
- method = ord(gz.read(1))
- if method != 8:
- raise IOError, "Unknown compression method"
- flag = ord(gz.read(1))
- gz.read(6) # Discard modtime, extraflag, os
-
- # Discard an extra field
- if flag & FEXTRA:
- xlen = ord(gz.read(1))
- xlen = xlen + 256 * ord(gz.read(1))
- gz.read(xlen)
-
- # Discard a null-terminated filename
- if flag & FNAME:
- while True:
- s = gz.read(1)
- if not s or s == "\000":
- break
-
- # Discard a null-terminated comment
- if flag & FCOMMENT:
- while True:
- s = gz.read(1)
- if not s or s == "\000":
- break
-
- # Discard a 16-bit CRC
- if flag & FHCRC:
- gz.read(2)
-
- shasum = sha.new()
- dcobj = zlib.decompressobj(-zlib.MAX_WBITS)
-
- while True:
- buf = gz.read(64 * 1024)
- if buf == "":
- ubuf = dcobj.flush()
- shasum.update(ubuf)
- outfile.write(ubuf)
- break
- ubuf = dcobj.decompress(buf)
- shasum.update(ubuf)
- outfile.write(ubuf)
-
- return shasum.hexdigest()
-
class Action(object):
"""Class representing a generic packaging object.
@@ -409,6 +344,18 @@
if e.errno != errno.EPERM:
raise
+ def get_remote_opener(self, img, fmri):
+ """Return an opener for the action's datastream which pulls from
+ the server. The caller may have to decompress the datastream."""
+
+ if not hasattr(self, "hash"):
+ return None
+
+ def opener():
+ return retrieve.get_datastream(img, fmri, self.hash)
+
+ return opener
+
def verify(self, img, **args):
"""returns True if correctly installed in the given image"""
return ["verify method for action type %s unimplemented" % self.name]
--- a/src/modules/actions/license.py Thu Apr 17 19:03:51 2008 -0700
+++ b/src/modules/actions/license.py Fri Apr 18 16:17:09 2008 -0700
@@ -38,6 +38,7 @@
from stat import *
import generic
+import pkg.misc as misc
import pkg.portable as portable
class LicenseAction(generic.Action):
@@ -79,7 +80,7 @@
lfile = file(path, "wb")
# XXX Should throw an exception if shasum doesn't match
# self.hash
- shasum = generic.gunzip_from_stream(stream, lfile)
+ shasum = misc.gunzip_from_stream(stream, lfile)
lfile.close()
stream.close()
@@ -131,3 +132,16 @@
except OSError,e:
if e.errno != errno.ENOENT:
raise
+
+ def get_local_opener(self, img, fmri):
+ """Return an opener for the license text from the local disk."""
+
+ path = os.path.normpath(os.path.join(img.imgdir, "pkg",
+ fmri.get_dir_path(), "license." + self.attrs["license"]))
+
+ def opener():
+ # XXX Do we check to make sure that what's there is what
+ # we think is there (i.e., re-hash)?
+ return file(path, "rb")
+
+ return opener
--- a/src/modules/client/image.py Thu Apr 17 19:03:51 2008 -0700
+++ b/src/modules/client/image.py Fri Apr 18 16:17:09 2008 -0700
@@ -972,8 +972,10 @@
def load_optional_dependencies(self):
for fmri in self.gen_installed_pkgs():
- deps = self.get_manifest(fmri, filtered = True).get_dependencies()
- for required, min_fmri, max_fmri in deps:
+ mfst = self.get_manifest(fmri, filtered = True)
+
+ for dep in mfst.gen_actions_by_type("depend"):
+ required, min_fmri, max_fmri = dep.parse(self)
if required == False:
self.update_optional_dependency(min_fmri)
--- a/src/modules/client/retrieve.py Thu Apr 17 19:03:51 2008 -0700
+++ b/src/modules/client/retrieve.py Fri Apr 18 16:17:09 2008 -0700
@@ -72,14 +72,14 @@
fmri.get_url_path(), ssl_creds = ssl_tuple,
imgtype = img.type)
except urllib2.HTTPError, e:
- raise NameError, "could not retrieve file '%s' from '%s'" % \
- (hash, url_prefix)
+ raise NameError, "could not retrieve manifest '%s' from '%s'" % \
+ (fmri.get_url_path(), url_prefix)
except urllib2.URLError, e:
if len(e.args) == 1 and isinstance(e.args[0], socket.sslerror):
raise RuntimeError, e
raise NameError, "could not retrieve manifest '%s' from '%s'" % \
- (hash, url_prefix)
+ (fmri.get_url_path(), url_prefix)
except:
raise NameError, "could not retrieve manifest '%s' from '%s'" % \
(fmri.get_url_path(), url_prefix)
--- a/src/modules/manifest.py Thu Apr 17 19:03:51 2008 -0700
+++ b/src/modules/manifest.py Fri Apr 18 16:17:09 2008 -0700
@@ -23,6 +23,7 @@
# Use is subject to license terms.
import os
+import errno
import cPickle
from itertools import groupby, chain
@@ -185,13 +186,10 @@
out += "%s -> %s\n" % (src, dest)
return out
- def get_dependencies(self):
- """ generate list of dependencies in this manifest """
- return [
- a.parse(self.img)
- for a in self.actions
- if a.name == "depend"
- ]
+ def gen_actions_by_type(self, type):
+ """Generate actions in the manifest of type "type"."""
+
+ return (a for a in self.actions if a.name == type)
def filter(self, filters):
"""Filter out actions from the manifest based on filters."""
@@ -302,7 +300,11 @@
try:
mfile = file(mfst_path, "w")
except IOError:
- os.makedirs(os.path.dirname(mfst_path))
+ try:
+ os.makedirs(os.path.dirname(mfst_path))
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ raise
mfile = file(mfst_path, "w")
#
--- a/src/modules/misc.py Thu Apr 17 19:03:51 2008 -0700
+++ b/src/modules/misc.py Fri Apr 18 16:17:09 2008 -0700
@@ -30,6 +30,8 @@
import httplib
import platform
import re
+import sha
+import zlib
import pkg.urlhelpers as urlhelpers
import pkg.portable as portable
@@ -152,3 +154,68 @@
return True
return False
+
+def gunzip_from_stream(gz, outfile):
+ """Decompress a gzipped input stream into an output stream.
+
+ The argument 'gz' is an input stream of a gzipped file (XXX make it do
+ either a gzipped file or raw zlib compressed data), and 'outfile' is is
+ an output stream. gunzip_from_stream() decompresses data from 'gz' and
+ writes it to 'outfile', and returns the hexadecimal SHA-1 sum of that
+ data.
+ """
+
+ FHCRC = 2
+ FEXTRA = 4
+ FNAME = 8
+ FCOMMENT = 16
+
+ # Read the header
+ magic = gz.read(2)
+ if magic != "\037\213":
+ raise IOError, "Not a gzipped file"
+ method = ord(gz.read(1))
+ if method != 8:
+ raise IOError, "Unknown compression method"
+ flag = ord(gz.read(1))
+ gz.read(6) # Discard modtime, extraflag, os
+
+ # Discard an extra field
+ if flag & FEXTRA:
+ xlen = ord(gz.read(1))
+ xlen = xlen + 256 * ord(gz.read(1))
+ gz.read(xlen)
+
+ # Discard a null-terminated filename
+ if flag & FNAME:
+ while True:
+ s = gz.read(1)
+ if not s or s == "\000":
+ break
+
+ # Discard a null-terminated comment
+ if flag & FCOMMENT:
+ while True:
+ s = gz.read(1)
+ if not s or s == "\000":
+ break
+
+ # Discard a 16-bit CRC
+ if flag & FHCRC:
+ gz.read(2)
+
+ shasum = sha.new()
+ dcobj = zlib.decompressobj(-zlib.MAX_WBITS)
+
+ while True:
+ buf = gz.read(64 * 1024)
+ if buf == "":
+ ubuf = dcobj.flush()
+ shasum.update(ubuf)
+ outfile.write(ubuf)
+ break
+ ubuf = dcobj.decompress(buf)
+ shasum.update(ubuf)
+ outfile.write(ubuf)
+
+ return shasum.hexdigest()
--- a/src/tests/cli/t_commandline.py Thu Apr 17 19:03:51 2008 -0700
+++ b/src/tests/cli/t_commandline.py Fri Apr 18 16:17:09 2008 -0700
@@ -140,5 +140,42 @@
self.pkg("set-authority -O http://test1:abcde test2", exit=1)
self.pkg("set-authority -O ftp://test2 test2", exit=1)
+ def test_info_local_remote(self):
+ """pkg: check that info behaves for local and remote cases."""
+
+ pkg1 = """
+ open [email protected],5.11-0
+ add dir mode=0755 owner=root group=bin path=/bin
+ close
+ """
+
+ pkg2 = """
+ open [email protected],5.11-0
+ add dir mode=0755 owner=root group=bin path=/bin
+ close
+ """
+
+ durl = self.dc.get_depot_url()
+
+ self.pkgsend_bulk(durl, pkg1)
+ self.pkgsend_bulk(durl, pkg2)
+
+ self.image_create(durl)
+
+ # Install one package and verify
+ self.pkg("install jade")
+ self.pkg("verify -v")
+
+ # Check local info
+ self.pkg("info jade | grep 'State: Installed'")
+ self.pkg("info turquoise | grep 'no packages matching'")
+ self.pkg("info emerald", exit = 1)
+ self.pkg("info emerald 2>&1 | grep 'no matching packages'")
+
+ # Check remote info
+ self.pkg("info -r jade | grep 'State: Installed'")
+ self.pkg("info -r turquoise| grep 'State: Not installed'")
+ self.pkg("info -r emerald | grep 'no package matching'")
+
if __name__ == "__main__":
unittest.main()