1773 Want tool to obtain contents of packages
authorjohansen <johansen@sun.com>
Mon, 12 May 2008 14:47:31 -0700
changeset 375 fa5286b09191
parent 374 d2875f398282
child 376 6934ec444a99
1773 Want tool to obtain contents of packages
.hgignore
src/Makefile
src/man/Makefile
src/man/pkgrecv.1.txt
src/man/pkgsend.1.txt
src/publish.py
src/pull.py
src/tests/cli-complete.py
src/tests/cli/t_recv.py
src/tests/cli/t_upgrade.py
src/tests/cli/testutils.py
--- a/.hgignore	Mon May 12 13:34:49 2008 -0700
+++ b/.hgignore	Mon May 12 14:47:31 2008 -0700
@@ -13,8 +13,10 @@
 ^src/man/pkg.1$
 ^src/man/pkg.5$
 ^src/man/pkg.depotd.1m$
+^src/man/pkgrecv.1$
 ^src/man/pkgsend.1$
 ^src/pkg$
+^src/pkgrecv$
 ^src/pkg.depotd$
 ^src/pkgdefs/SUNWipkg/prototype$
 ^src/pkgsend$
--- a/src/Makefile	Mon May 12 13:34:49 2008 -0700
+++ b/src/Makefile	Mon May 12 14:47:31 2008 -0700
@@ -23,6 +23,7 @@
 #
 
 # client.py -> /usr/bin/pkg
+# pull.py -> /usr/bin/pkgrecv
 # publish.py -> /usr/bin/pkgsend
 # depot.py -> /usr/lib/pkg.depotd
 #
@@ -67,10 +68,11 @@
 	$(ROOTMAN1M) \
 	$(ROOTMAN5)
 
-PROGS = pkg pkgsend pkg.depotd
+PROGS = pkg pkgrecv pkgsend pkg.depotd
 
 ROOTPROGS = \
 	$(ROOT)/usr/bin/pkg \
+	$(ROOT)/usr/bin/pkgrecv \
 	$(ROOT)/usr/bin/pkgsend \
 	$(ROOT)/usr/lib/pkg.depotd
 
@@ -241,6 +243,7 @@
 PWD:sh = pwd
 link:
 	ln -sf $(PWD)/client.py /usr/bin/pkg
+	ln -sf $(PWD)/pull.py /usr/bin/pkgrecv
 	ln -sf $(PWD)/publish.py /usr/bin/pkgsend
 	ln -sf $(PWD)/depot.py /usr/lib/pkg.depotd
 	ln -sf $(PWD)/modules /usr/lib/python2.4/vendor-packages/pkg
@@ -251,6 +254,7 @@
 
 link-clean:
 	rm -f /usr/bin/pkg
+	rm -f /usr/bin/pkgrecv
 	rm -f /usr/bin/pkgsend
 	rm -f /usr/lib/pkg.depotd
 	rm -f /usr/lib/python2.4/vendor-packages/pkg
@@ -316,6 +320,9 @@
 pkg: client.py
 	cp client.py pkg
 
+pkgrecv: pull.py
+	cp pull.py pkgrecv
+
 pkgsend: publish.py
 	cp publish.py pkgsend
 
--- a/src/man/Makefile	Mon May 12 13:34:49 2008 -0700
+++ b/src/man/Makefile	Mon May 12 14:47:31 2008 -0700
@@ -30,7 +30,7 @@
 ROOTMAN1M = $(ROOTMAN)/cat1m
 ROOTMAN5 = $(ROOTMAN)/cat5
 
-MANPAGES = $(ROOTMAN1)/pkg.1 $(ROOTMAN1)/pkgsend.1 $(ROOTMAN1M)/pkg.depotd.1m $(ROOTMAN5)/pkg.5
+MANPAGES = $(ROOTMAN1)/pkg.1 $(ROOTMAN1)/pkgrecv.1 $(ROOTMAN1)/pkgsend.1 $(ROOTMAN1M)/pkg.depotd.1m $(ROOTMAN5)/pkg.5
 
 all := TARGET = all
 link := TARGET = link
@@ -45,12 +45,14 @@
 link:
 	-mkdir -p /usr/share/man/cat1 /usr/share/man/cat1m /usr/share/man/cat5
 	ln -sf $(PWD)/pkg.1.txt /usr/share/man/cat1/pkg.1
+	ln -sf $(PWD)/pkgrecv.1.txt /usr/share/man/cat1/pkgrecv.1
 	ln -sf $(PWD)/pkgsend.1.txt /usr/share/man/cat1/pkgsend.1
 	ln -sf $(PWD)/pkg.depotd.1m.txt /usr/share/man/cat1m/pkg.depotd.1m
 	ln -sf $(PWD)/pkg.5.txt /usr/share/man/man5/pkg.5
 
 link-clean:
 	rm -f /usr/share/man/cat1/pkg.1
+	rm -f /usr/share/man/cat1/pkgrecv.1
 	rm -f /usr/share/man/cat1/pkgsend.1
 	rm -f /usr/share/man/cat1m/pkg.depotd.1m
 	rm -f /usr/share/man/cat5/pkg.5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/man/pkgrecv.1.txt	Mon May 12 14:47:31 2008 -0700
@@ -0,0 +1,81 @@
+User Commands                                            pkgrecv(1)
+
+
+NAME
+     pkgrecv - image packaging system content retrieval utility
+
+SYNOPSIS
+     /usr/bin/pkgrecv [-d directory] -s server pkg...
+
+     /usr/bin/pkgrecv -s server -n
+
+DESCRIPTION
+     pkgrecv allows the user to download the contents of a package
+     from a server.  The contents are retrieved in a format that can
+     easily be input to pkgsend(1) when used with the 'include'
+     subcommand.
+
+OPTIONS
+     The following options are supported:
+
+     -s repo_url    The URL prefix of the server.
+
+     -d directory   The directory where pkg content should be placed.
+     		    The default location is the current working
+		    directory.
+
+     -n             Instead of downloading packages, list the most recent 
+                    versions of the packages available at the specified
+		    server.
+
+EXAMPLES
+     Example 1:  List newest packages available from server test
+
+     $ pkgrecv -s http://test -n
+     pkg:/[email protected],5.11-0.79:20080221T125720Z
+     pkg:/[email protected],5.11-0.79:20080221T123955Z
+     pkg:/[email protected],5.11-0.79:20080221T125728Z
+     pkg:/[email protected],5.11-0.79:20080221T125730Z
+
+     Example 2: Receive the SUNWlibC, SUNWfreetype, and SUNWlibm
+     packages from example 1
+
+     $ pkgrecv -s http://test [email protected],5.11-0.79:20080221T125720Z
+     [email protected],5.11-0.79:20080221T123955Z
+     [email protected],5.11-0.79:20080221T125728Z
+
+
+EXIT STATUS
+     The following exit values are returned:
+
+     0     Everything worked.
+
+     1     Something bad happened.
+
+     2     Invalid command line options were specified.
+
+FILES
+
+ATTRIBUTES
+     See attributes(5) for descriptions of the  following  attri-
+     butes:
+     ____________________________________________________________
+    |       ATTRIBUTE TYPE        |       ATTRIBUTE VALUE       |
+    |_____________________________|_____________________________|
+    | Availability                |                             |
+    |_____________________________|_____________________________|
+
+SEE ALSO
+     pkgsend(1), attributes(5), pkg(5)
+
+NOTES
+     The image packaging system is an under-development feature.
+     Command names, invocation, formats, and operations are all subject
+     to change.  Development is hosted in the OpenSolaris community
+     at
+
+     http://opensolaris.org/os/project/pkg/
+
+     Other package bundle formats can be created.  Other forms of
+     package publication, via the underlying Python API or via the web
+     API, are also possible.
--- a/src/man/pkgsend.1.txt	Mon May 12 13:34:49 2008 -0700
+++ b/src/man/pkgsend.1.txt	Mon May 12 14:47:31 2008 -0700
@@ -10,7 +10,7 @@
      /usr/bin/pkgsend open [-en] pkg_fmri
      /usr/bin/pkgsend add action arguments
      /usr/bin/pkgsend import bundlefile ...
-     /usr/bin/pkgsend include manifest ...
+     /usr/bin/pkgsend include [-d basedir] manifest ...
      /usr/bin/pkgsend close [-A]
 
      /usr/bin/pkgsend send bundlefile ...
@@ -48,7 +48,7 @@
 	  Add each given bundlefile (such as a SVr4 package) into the
 	  current transaction.
 
-     include manifest ...
+     include [-d basedir] manifest ...
           Add resources associated with the multiple actions present in
 	  each manifest file to the current transaction.  Each line in the
 	  file should be the string representation of an action.  In
@@ -58,6 +58,9 @@
 	  path to the file containing the data should be the second word on
 	  the line.
 
+	  If the user specifies the -d option, basedir is prepended to
+	  the search path when locating files in the manifest. 
+
      close [-A]
           Close current transaction.  With -A, abandon the current
           transaction.
--- a/src/publish.py	Mon May 12 13:34:49 2008 -0700
+++ b/src/publish.py	Mon May 12 14:47:31 2008 -0700
@@ -62,7 +62,7 @@
         pkgsend open [-en] pkg_fmri
         pkgsend add action arguments 
         pkgsend import bundlefile ...
-        pkgsend include manifest ...
+        pkgsend include [-d basedir] manifest ...
         pkgsend close [-A]
 
         pkgsend send bundlefile
@@ -184,6 +184,14 @@
 
 def trans_include(config, fargs):
 
+        basedir = None
+
+        opts, pargs = getopt.getopt(fargs, "d:")
+
+        for opt, arg in opts:
+                if opt == "-d":
+                        basedir = arg
+
         try:
                 trans_id = os.environ["PKG_TRANS_ID"]
         except KeyError:
@@ -192,7 +200,7 @@
                 sys.exit(1)
 
         t = trans.Transaction()
-        for filename in fargs:
+        for filename in pargs:
                 f = file(filename)
                 for line in f:
                         line = line.strip() # 
@@ -208,8 +216,15 @@
                                         print >> sys.stderr, e[0]
                                         sys.exit(1)
 
+                                if basedir:
+                                        fullpath = args[1].lstrip(os.path.sep)
+                                        fullpath = os.path.join(basedir,
+                                            fullpath)
+                                else:
+                                        fullpath = args[1]
+
                                 def opener():
-                                        return open(args[1], "rb")
+                                        return open(fullpath, "rb")
                                 action.data = opener
 
                         else:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pull.py	Mon May 12 14:47:31 2008 -0700
@@ -0,0 +1,309 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+import sys
+import os
+import traceback
+import getopt
+import urllib
+import tempfile
+import gettext
+import shutil
+
+import pkg.fmri
+import pkg.pkgtarfile as ptf
+import pkg.catalog as catalog
+import pkg.actions as actions
+from pkg.misc import versioned_urlopen
+
+def usage(usage_error = None):
+        """ Emit a usage message and optionally prefix it with a more
+            specific error message.  Causes program to exit. """
+
+        pname = os.path.basename(sys.argv[0])
+        if usage_error:
+                print >> sys.stderr, pname + ": " + usage_error
+
+        print >> sys.stderr, _("""\
+Usage:
+        pkgrecv -s server [-d dir] pkgfmri ...
+        pkgrecv -s server -n""")
+
+        sys.exit(2)
+
+
+def error(error):
+        """ Emit an error message prefixed by the command name """
+
+        pname = os.path.basename(sys.argv[0])
+        print >> sys.stderr, pname + ": " + error
+
+def hashes_from_mfst(manifest):
+        """Given a path to a manifest, open the file and read through the
+        actions.  Return a set of all content hashes found in the manifest."""
+
+        hashes = set()
+
+        try:
+                f = file(manifest, "r")
+        except:
+                error(_("Unable to open manifest: %s") % manifest)
+                sys.exit(1)
+
+        for line in f:
+                line = line.lstrip()
+                if not line or line[0] == "#":
+                        continue
+
+                try:
+                        action = actions.fromstr(line)
+                except:
+                        continue
+
+                if hasattr(action, "hash"):
+                        hashes.add(action.hash)
+
+        f.close()
+
+        return hashes
+
+def fetch_files_byhash(server_url, hashes, pkgdir):
+        """Given a list of files named by content hash, download from
+        server_url into pkgdir."""
+
+        req_dict = { }
+
+        for i, k in enumerate(hashes):
+                str = "File-Name-%s" % i
+                req_dict[str] = k
+
+        req_str = urllib.urlencode(req_dict)
+
+        try:
+                f, v = versioned_urlopen(server_url, "filelist", [0],
+                    data = req_str)
+        except:
+                error(_("Unable to download files from: %s") % server_url)
+                sys.exit(1)
+
+        tar_stream = ptf.PkgTarFile.open(mode = "r|", fileobj = f)
+
+        if not os.path.exists(pkgdir):
+                try:
+                        os.makedirs(pkgdir)
+                except:
+                        error(_("Unable to create directory: %s") % pkgdir)
+                        sys.exit(1)
+
+        for info in tar_stream:
+                hashval = info.name
+                try:
+                        tar_stream.extract_to(info, pkgdir, hashval)
+                except:
+                        error(_("Unable to extract file: %s") % info.name)
+                        sys.exit(1)
+
+        tar_stream.close()
+        f.close()
+
+def fetch_manifest(server_url, fmri, basedir):
+        """Fetch the manifest for package-fmri 'fmri' from the server
+        in 'server_url'. Put manifest in a directory named by package stem"""
+
+        # Request manifest from server
+        try:
+                m, v = versioned_urlopen(server_url, "manifest", [0],
+                    fmri.get_url_path())
+        except:
+                error(_("Unable to download manifest %s from %s") %
+                    (fmri.get_url_path(), server_url))
+                sys.exit(1)
+
+        # join pkgname onto basedir.  Manifest goes here
+        opath = os.path.join(basedir, urllib.quote(fmri.pkg_name, ""))
+
+        # Create directories if they don't exist
+        if not os.path.exists(opath):
+                try:
+                        os.makedirs(opath)
+                except:
+                        error(_("Unable to create directory: %s") % opath)
+                        sys.exit(1)
+
+        # Open manifest
+        opath = os.path.join(opath, "manifest")
+        try:
+                ofile = file(opath, "w")
+        except:
+                error(_("Unable to open file: %s") % opath)
+                sys.exit(1)
+
+        # Read from server, write to file
+        try:
+                mfst = m.read()
+        except:
+                error(_("Error occurred while reading from: %s") % server_url)
+                sys.exit(1)
+
+        try:
+                ofile.write(mfst)
+        except:
+                error(_("Error occurred while writing to: %s") % opath)
+                sys.exit(1)
+
+        # Close it up
+        ofile.close()
+        m.close()
+
+        return opath
+
+def list_newest_fmris(cat):
+        """Look through the catalog 'cat' and return the newest version
+        of a fmri found for a given package."""
+
+        fm_hash = { }
+        fm_list = [ ]
+
+        # Order all fmris by package name
+        for f in cat.fmris():
+                if f.pkg_name in fm_hash:
+                        fm_hash[f.pkg_name].append(f)
+                else:
+                        fm_hash[f.pkg_name] = [ f ]
+
+        # sort each fmri list
+        for k in fm_hash.keys():
+                fm_hash[k].sort(reverse = True)
+                l = fm_hash[k]
+                fm_list.append(l[0])
+
+        for e in fm_list:
+                print e
+
+def fetch_catalog(server_url):
+        """Fetch the catalog from the server_url."""
+
+        # open connection for catalog
+        try:
+                c, v = versioned_urlopen(server_url, "catalog", [0])
+        except:
+                error(_("Unable to download catalog from: %s") % server_url)
+                sys.exit(1)
+
+        # make a tempdir for catalog
+        dl_dir = tempfile.mkdtemp()
+
+        # call catalog.recv to pull down catalog
+        try:
+                catalog.recv(c, dl_dir)
+        except: 
+                error(_("Error while reading from: %s") % server_url)
+                sys.exit(1)
+
+        # close connection to server
+        c.close()
+
+        # instantiate catalog object
+        cat = catalog.Catalog(dl_dir)
+        
+        # return (catalog, tmpdir path)
+        return cat, dl_dir
+
+def main_func():
+
+        server = None
+        basedir = None
+        newfmri = False
+
+        # XXX /usr/lib/locale is OpenSolaris-specific.
+        gettext.install("pkgrecv", "/usr/lib/locale")
+
+        try:
+               opts, pargs = getopt.getopt(sys.argv[1:], "s:d:n")
+        except getopt.GetoptError, e:
+                usage(_("Illegal option -- %s") % e.opt) 
+
+        for opt, arg in opts:
+                if opt == "-s":
+                        server = arg
+                if opt == "-d":
+                        basedir = arg
+                if opt == "-n":
+                        newfmri = True
+
+        if not server:
+                usage(_("must specify a server"))
+
+        if not server.startswith("http://"):
+                server = "http://%s" % server
+
+        if newfmri:
+                if pargs or len(pargs) > 0:
+                        usage(_("-n takes no options"))
+
+                cat, dir = fetch_catalog(server)
+                list_newest_fmris(cat)
+                shutil.rmtree(dir)
+                
+        else:
+                if pargs == None or len(pargs) == 0:
+                        usage(_("must specify at least one pkgfmri"))
+
+                if not basedir:
+                        basedir = os.getcwd()
+
+                for pkgfmri in pargs:
+                        if not pkgfmri.startswith("pkg:/"):
+                                pkgfmri = "pkg:/%s" % pkgfmri
+
+                        try:
+                                fmri = pkg.fmri.PkgFmri(pkgfmri)
+                        except AssertionError:
+                                error(_("pkgfmri %s needs a version string") %
+                                    pkgfmri)
+                                return 1
+
+                        mfstpath = fetch_manifest(server, fmri, basedir)
+                        content_hashes = hashes_from_mfst(mfstpath)
+
+                        fetch_files_byhash(server, content_hashes,
+                            os.path.dirname(mfstpath))
+
+        return 0
+
+if __name__ == "__main__":
+        try:
+                ret = main_func()
+        except SystemExit, e:
+                raise e
+        except KeyboardInterrupt:
+                print "Interrupted"
+                sys.exit(1)
+        except:
+                traceback.print_exc()
+                sys.exit(99)
+        sys.exit(ret)
--- a/src/tests/cli-complete.py	Mon May 12 13:34:49 2008 -0700
+++ b/src/tests/cli-complete.py	Mon May 12 14:47:31 2008 -0700
@@ -54,6 +54,7 @@
 	import cli.t_pkg_list
 	import cli.t_commandline
 	import cli.t_upgrade
+        import cli.t_recv
 	import cli.t_rename
 	import cli.t_twodepot
 
@@ -68,6 +69,7 @@
 	    cli.t_commandline.TestCommandLine,
 	    cli.t_upgrade.TestUpgrade,
 	    cli.t_circular_dependencies.TestCircularDependencies,
+            cli.t_recv.TestPkgRecv,
 	    cli.t_rename.TestRename,
 	    cli.t_twodepot.TestTwoDepots ]
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tests/cli/t_recv.py	Mon May 12 14:47:31 2008 -0700
@@ -0,0 +1,138 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+import sys
+import testutils
+if __name__ == "__main__":
+        testutils.setup_environment("../../../proto")
+
+import os
+import shutil
+import tempfile
+import unittest
+
+class TestPkgRecv(testutils.ManyDepotTestCase):
+
+        bronze10 = """
+            open [email protected],5.11-0
+            add dir mode=0755 owner=root group=bin path=/usr
+            add dir mode=0755 owner=root group=bin path=/usr/bin
+            add file /tmp/sh mode=0555 owner=root group=bin path=/usr/bin/sh
+            add link path=/usr/bin/jsh target=./sh
+            add hardlink path=/lib/libc.bronze target=/lib/libc.so.1
+            add file /tmp/bronze1 mode=0444 owner=root group=bin path=/etc/bronze1
+            add file /tmp/bronze2 mode=0444 owner=root group=bin path=/etc/bronze2
+            add file /tmp/bronzeA1 mode=0444 owner=root group=bin path=/A/B/C/D/E/F/bronzeA1
+            add depend fmri=pkg:/[email protected] type=require
+            add license /tmp/copyright2 license=copyright
+            close
+        """
+
+        bronze20 = """
+            open [email protected],5.11-0
+            add dir mode=0755 owner=root group=bin path=/etc
+            add dir mode=0755 owner=root group=bin path=/lib
+            add file /tmp/sh mode=0555 owner=root group=bin path=/usr/bin/sh
+            add file /tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.bronze
+            add link path=/usr/bin/jsh target=./sh
+            add hardlink path=/lib/libc.bronze2.0.hardlink target=/lib/libc.so.1
+            add file /tmp/bronze1 mode=0444 owner=root group=bin path=/etc/bronze1
+            add file /tmp/bronze2 mode=0444 owner=root group=bin path=/etc/amber2
+            add license /tmp/copyright3 license=copyright
+            add file /tmp/bronzeA2 mode=0444 owner=root group=bin path=/A1/B2/C3/D4/E5/F6/bronzeA2
+            add depend fmri=pkg:/[email protected] type=require
+            close 
+        """
+
+        misc_files = [ "/tmp/bronzeA1",  "/tmp/bronzeA2",
+                    "/tmp/bronze1", "/tmp/bronze2",
+                    "/tmp/copyright2", "/tmp/copyright3",
+                    "/tmp/libc.so.1", "/tmp/sh"]
+
+        def setUp(self):
+                """ Start two depots.
+                    depot 1 gets foo and moo, depot 2 gets foo and bar
+                    depot1 is mapped to authority test1 (preferred)
+                    depot2 is mapped to authority test2 """
+
+                testutils.ManyDepotTestCase.setUp(self, 2)
+
+                for p in self.misc_files:
+                        f = open(p, "w")
+                        # write the name of the file into the file, so that
+                        # all files have differing contents
+                        f.write(p)
+                        f.close()
+                        self.debug("wrote %s" % p)
+
+                self.durl1 = self.dcs[1].get_depot_url()
+                self.pkgsend_bulk(self.durl1, self.bronze10)
+                self.pkgsend_bulk(self.durl1, self.bronze20)
+
+                self.durl2 = self.dcs[2].get_depot_url()
+                self.tempdir = tempfile.mkdtemp()
+
+
+        def tearDown(self):
+                testutils.ManyDepotTestCase.tearDown(self)
+                for p in self.misc_files:
+                        os.remove(p)
+                shutil.rmtree(self.tempdir)
+
+        def test_recv_send(self):
+                rc, output = self.pkgrecv(self.durl1,
+                    "-n | grep [email protected]", out = True)
+
+                # Pull the pkg name out of the output from the last cmd
+                outwords = output.split()
+
+                # recv the pkg
+                recvcmd = "-d %s %s" % (self.tempdir, outwords[0])
+                self.pkgrecv(self.durl1, recvcmd)
+
+                # now send it to another depot
+                self.pkgsend(self.durl2, "open [email protected]")
+
+                basedir = os.path.join(self.tempdir, "bronze")
+                manifest = os.path.join(basedir, "manifest")
+
+                cmd = "include -d %s %s" % (basedir, manifest)
+
+                self.pkgsend(self.durl2, cmd)
+                self.pkgsend(self.durl2, "close")
+
+        def test_bad_opts(self):
+                self.pkgrecv("", "-n", exit = 2)
+                self.pkgrecv(self.durl1, "-!", exit = 2)
+                self.pkgrecv(self.durl1, "-p foo", exit = 2)
+                self.pkgrecv(self.durl1, "-d %s [email protected]" % self.tempdir,
+                    exit = 1)
+
+
+if __name__ == "__main__":
+        unittest.main()
+
--- a/src/tests/cli/t_upgrade.py	Mon May 12 13:34:49 2008 -0700
+++ b/src/tests/cli/t_upgrade.py	Mon May 12 14:47:31 2008 -0700
@@ -133,7 +133,7 @@
                         # write the name of the file into the file, so that
                         # all files have differing contents
                         f.write(p)
-                        f.close
+                        f.close()
                         self.debug("wrote %s" % p)
                 
         def tearDown(self):
--- a/src/tests/cli/testutils.py	Mon May 12 13:34:49 2008 -0700
+++ b/src/tests/cli/testutils.py	Mon May 12 14:47:31 2008 -0700
@@ -323,6 +323,33 @@
 
                 return retcode
 
+        def pkgrecv(self, server_url, command, exit = 0, out = False,
+            comment = ""):
+
+                cmdline = "pkgrecv -s %s %s" % (server_url, command)
+                self.debugcmd(cmdline)
+
+                p = subprocess.Popen(cmdline, shell = True,
+                    stdout = subprocess.PIPE,
+                    stderr = subprocess.STDOUT)
+
+                output = p.stdout.read()
+                retcode = p.wait()
+                self.debugresult(retcode, output)
+
+                if retcode == 99:
+                        raise TracebackException(cmdline, output, comment,
+			    debug = self.get_debugbuf())
+                elif retcode != exit:
+                        raise UnexpectedExitCodeException(cmdline,
+                            exit, retcode, output, comment,
+			    debug = self.get_debugbuf())
+
+                if out:
+                        return retcode, output
+
+                return retcode
+
         def pkgsend(self, depot_url, command, exit = 0, comment = ""):
 
                 cmdline = "pkgsend -s %s %s" % (depot_url, command)