5871 publisher apis desired
authorShawn Walker <Shawn.Walker@Sun.COM>
Mon, 09 Mar 2009 16:09:13 -0500
changeset 926 6ee411c9026a
parent 925 089ed400edf4
child 927 2beb452e6245
5871 publisher apis desired 3682 pkg publisher should show mirrors in default listing 5071 provide direct way to change publisher to not use certificate 5373 pkg commands shouldn't traceback when client can't read a key/cert file 6843 problems with empty string for ssl_cert, ssl_key 6897 "authority" should be "publisher" 7046 client intent is not sent if same api object used for multiple operations / reasons
doc/TODO
doc/catalog.txt
doc/client_api_versions.txt
doc/depot.txt
doc/history.txt
doc/image.txt
doc/rfes.txt
src/client.py
src/man/pkg.1.txt
src/man/pkg.5.txt
src/modules/actions/depend.py
src/modules/catalog.py
src/modules/client/api.py
src/modules/client/api_errors.py
src/modules/client/filelist.py
src/modules/client/history.py
src/modules/client/image.py
src/modules/client/imageconfig.py
src/modules/client/imageplan.py
src/modules/client/progress.py
src/modules/client/publisher.py
src/modules/client/retrieve.py
src/modules/fmri.py
src/modules/manifest.py
src/modules/misc.py
src/modules/server/api.py
src/modules/server/catalog.py
src/modules/server/config.py
src/modules/server/depot.py
src/modules/server/transaction.py
src/modules/updatelog.py
src/pkgdefs/SUNWipkg/prototype
src/tests/api/t_fmri.py
src/tests/api/t_imageconfig.py
src/tests/baseline.txt
src/tests/cli/t_api.py
src/tests/cli/t_api_info.py
src/tests/cli/t_pkg_api_install.py
src/tests/cli/t_pkg_authority.py
src/tests/cli/t_pkg_history.py
src/tests/cli/t_pkg_image_create.py
src/tests/cli/t_pkg_install.py
src/tests/cli/t_pkg_intent.py
src/tests/cli/t_pkg_list.py
src/tests/cli/t_pkg_property.py
src/tests/cli/t_pkg_publisher.py
src/tests/cli/t_pkg_refresh.py
src/tests/cli/t_pkg_search.py
src/tests/cli/t_pkgrecv.py
src/tests/perf/actionbench.py
src/tests/perf/fmribench.py
--- a/doc/TODO	Mon Mar 09 13:16:54 2009 +0000
+++ b/doc/TODO	Mon Mar 09 16:09:13 2009 -0500
@@ -7,10 +7,10 @@
 
 1.  Algorithms/Logic
 
-	- Preferred authority fallback
+	- Preferred publisher fallback
 		- a discussion of catalogs, comparisons between, and
 		  operations on
-		- per-package authority association
+		- per-package publisher association
 
 	- Catalog update mechanism
 		- event oriented
@@ -35,7 +35,7 @@
 
 	- Compatibility options
 		- understand SysV as fixed version:timestamp packages
-		  from a legacy authority?
+		  from a legacy publisher?
 
 2.  Formats/interfaces
 
--- a/doc/catalog.txt	Mon Mar 09 13:16:54 2009 +0000
+++ b/doc/catalog.txt	Mon Mar 09 16:09:13 2009 -0500
@@ -52,13 +52,13 @@
 
 If some instance of [email protected] is installed, we would have a directory
 
-/var/pkg/db/pkg/[authority?]/which/2.16
-				authority
+/var/pkg/db/pkg/[publisher?]/which/2.16
+				publisher
 				manifest
 				state
 
 XXX Can we have two packages with the same name, but from different
-authorities?
+publishers?
 
 An open question is whether we store one or more reverse indices in the
 inventory.  We could have indices into basenames, pathnames, and
@@ -85,7 +85,7 @@
 
 In a directory-based approach, we might have
 
-/var/pkg/catalog/repository_authority/category/package/versions
+/var/pkg/catalog/repository_publisher/category/package/versions
 
 with example
 
--- a/doc/client_api_versions.txt	Mon Mar 09 13:16:54 2009 +0000
+++ b/doc/client_api_versions.txt	Mon Mar 09 16:09:13 2009 -0500
@@ -1,3 +1,118 @@
+Version 11:
+Incompatible with clients using versions 1-10:
+Changes:
+   This version changes all parameter names and property names from 'authority'
+   to 'publisher'.  For example, parameters named 'auths' were changed to
+   'pubs'; parameters named 'authent' were changed to 'pubent' and so forth.
+
+   In addition, the following new api functions were added to ImageInterface:
+        def add_publisher(self, pub, refresh_allowed=True):
+                """Add the provided publisher object to the image
+                configuration."""
+
+        def get_preferred_publisher(self):
+                """Returns the preferred publisher object for the image."""
+
+        def get_publisher(self, prefix=None, alias=None, duplicate=False):
+                """Retrieves a publisher object matching the provided prefix
+                (name) or alias.
+
+                'duplicate' is an optional boolean value indicating whether
+                a copy of the publisher object should be returned instead
+                of the original.
+                """
+
+        def get_publishers(self, duplicate=False):
+                """Returns a list of the publisher objects for the current
+                image.
+
+                'duplicate' is an optional boolean value indicating whether
+                copies of the publisher objects should be returned instead
+                of the originals.
+                """
+
+        def get_publisher_last_update_time(self, prefix=None, alias=None):
+                """Returns a datetime object representing the last time the
+                catalog for a publisher was modified or None."""
+
+        def has_publisher(self, prefix=None, alias=None):
+                """Retrieves a publisher object matching the provided prefix
+                (name) or alias."""
+
+        def remove_publisher(self, prefix=None, alias=None):
+                """Removes a publisher object matching the provided prefix
+                (name) or alias."""
+
+        def set_preferred_publisher(self, prefix=None, alias=None):
+                """Sets the preferred publisher for the image."""
+
+        def update_publisher(self, pub, refresh_allowed=True):
+                """Replaces an existing publisher object with the provided one
+                using the _source_object_id identifier set during copy."""
+
+        def update_publisher(self, pub, refresh_allowed=True):
+                """Replaces an existing publisher object with the provided one
+                using the _source_object_id identifier set during copy.
+
+                'refresh_allowed' is an optional boolean value indicating
+                whether a refresh of publisher metadata (such as its catalog)
+                should be performed if transport information is changed for a
+                repository, mirror, or origin.  If False, no attempt will be
+                made to retrieve publisher metadata."""
+
+
+        def log_operation_end(self, error=None, result=None):
+                """Marks the end of an operation to be recorded in image
+                history.
+
+                'result' should be a pkg.client.history constant value
+                representing the outcome of an operation.  If not provided,
+                and 'error' is provided, the final result of the operation will
+                be based on the class of 'error' and 'error' will be recorded
+                for the current operation.  If 'result' and 'error' is not
+                provided, success is assumed."""
+
+        def log_operation_error(self, error):
+                """Adds an error to the list of errors to be recorded in image
+                history for the current opreation."""
+
+        def log_operation_start(self, name):
+                """Marks the start of an operation to be recorded in image
+                history."""
+ 
+        def parse_p5i(self, fileobj=None, location=None):
+                """Reads the pkg(5) publisher json formatted data at 'location'
+                or from the provided file-like object 'fileobj' and returns a
+                list of tuples of the format (publisher object, pkg_names).
+                pkg_names is a list of strings representing package names or
+                FMRIs.  If any pkg_names not specific to a publisher were
+                provided, the last tuple returned will be of the format (None,
+                pkg_names).
+
+                'fileobj' is an optional file-like object that must support a
+                'read' method for retrieving data.
+
+                'location' is an optional string value that should either start
+                with a leading slash and be pathname of a file or a URI string.
+
+                'fileobj' or 'location' must be provided."""
+
+        def write_p5i(self, fileobj, pkg_names=None, pubs=None):
+                """Writes the publisher, repository, and provided package names
+                to the provided file-like object 'fileobj' in json p5i format.
+
+                'fileobj' is only required to have a 'write' method that accepts
+                data to be written as a parameter.
+
+                'pkg_names' is a dict of lists, tuples, or sets indexed by
+                publisher prefix that contain package names, FMRI strings, or
+                package info objects.  A prefix of "" can be used for packages
+                that are not specific to a publisher.
+
+                'pubs' is an optional list of publisher prefixes or Publisher
+                objects.  If not provided, the information for all publishers
+                (excluding those disabled) will be output."""
+
 Version 10:
 Incompatible with clients using versions 1-9:
 Changes:
@@ -155,7 +270,7 @@
 
         def refresh(self, full_refresh, auths=None):
                 """Refreshes the catalogs. full_refresh controls whether to do
-                a full retrieval of the catalog from the authority or only
+                a full retrieval of the catalog from the publisher or only
                 update the existing catalog. auths is a list of authorities to
                 refresh. Passing an empty list or using the default value means
                 all known authorities will be refreshed. While it currently
--- a/doc/depot.txt	Mon Mar 09 13:16:54 2009 +0000
+++ b/doc/depot.txt	Mon Mar 09 16:09:13 2009 -0500
@@ -114,14 +114,14 @@
 
     The above example can be broken down into four basic components:
 
-        authority_origin_url    - http://pkg.opensolaris.org/release/
+        publisher_origin_url    - http://pkg.opensolaris.org/release/
         operation_name          - manifest
         protocol_version        - 0
         operation_arguments     - SUNWvim%407.1.284%2C5.11-0.101%3A20081119T230659Z
 
     Each of these components can be described as follows:
 
-        authority_origin_url    - A URL that can be used to access a depot
+        publisher_origin_url    - A URL that can be used to access a depot
                                   server's repository.
 
         operation_name          - The name of the operation that the client is
@@ -268,7 +268,7 @@
 
             Expects:
                 A URL-encoded pkg(5) FMRI, excluding the 'pkg:/' scheme prefix
-                and authority information, and including the full version
+                and publisher information, and including the full version
                 information.
 
             Returns:
@@ -278,7 +278,7 @@
             Sample Output:
                 Name: entire
                 Summary: entire incorporation
-                Authority: Unknown
+                Publisher: Unknown
                 Version: 0.5.11
                 Build Release: 5.11
                 Branch: 0.101
@@ -299,7 +299,7 @@
 
             Expects:
                 A URL-encoded pkg(5) FMRI excluding the 'pkg:/' scheme prefix
-                and authority information and including the full version
+                and publisher information and including the full version
                 information.
 
             Returns:
--- a/doc/history.txt	Mon Mar 09 13:16:54 2009 +0000
+++ b/doc/history.txt	Mon Mar 09 16:09:13 2009 -0500
@@ -14,18 +14,19 @@
     The following operations are currently recorded by the clients that are
     part of pkg(5):
 
-        add-mirror
-        delete-authority
-        delete-mirror
+        add-publisher
         image-create
         image-set-attributes
         image-update
         install
         purge-history
         rebuild-index
-        set-authority
-        set-preferred-authority
+        refresh-publisher
+        remove-publisher
+        set-publisher
+        set-preferred-publisher
         uninstall
+        update-publisher
 
     These operations were chosen because they have the potential to alter the
     behavior of pkg(5) operations or because they modify an image.
--- a/doc/image.txt	Mon Mar 09 13:16:54 2009 +0000
+++ b/doc/image.txt	Mon Mar 09 16:09:13 2009 -0500
@@ -35,7 +35,7 @@
 2.2.1.  Configuration inheritance
 
     Some aspects of configuration can be shared between all images.  For
-    instance, a user image may specify authorities beyond those encoded
+    instance, a user image may specify publishers beyond those encoded
     into the system defaults.  So, a user image must have authoritative
     configuration, but be able to draw on the parent image's state for
     default settings.
@@ -81,14 +81,14 @@
 
 2.2.2.  Configuration components
 
-    List of authorities.  For each authority, we have a prefix, an
+    List of publishers.  For each publisher, we have a prefix, an
     origin URL, a list of mirror URLs, and annotations.
 
-    authority_[escaped_name]/  Property group of type "com.sun.pkg,authority"
-      /prefix                  pkg: authority
+    publisher_[escaped_name]/  Property group of type "com.sun.pkg,publisher"
+      /prefix                  pkg: publisher
       /origin                  http:, https:, or ftp: URL
       /mirrors                 list of URLs
-      /disabled                boolean indicating whether authority should be used
+      /disabled                boolean indicating whether publisher should be used
 
     Image properties.  The image has a collection of simple properties,
     like machine type and policies that control the behavior of the pkg(5)
@@ -101,7 +101,7 @@
       /flush-content-cache-on-success
                                 should downloaded compressed files be removed
                                 after a successful install
-      /preferred-authority      preferred authority for unknown package lookups
+      /preferred-publisher      preferred publisher for unknown package lookups
       /title                    title of image for use in GUIs and other UIs
       /description              longer description of the content of the image
       /[various]
--- a/doc/rfes.txt	Mon Mar 09 13:16:54 2009 +0000
+++ b/doc/rfes.txt	Mon Mar 09 16:09:13 2009 -0500
@@ -38,7 +38,7 @@
 11.  [pelegri]  Use of package-level metadata to provide additional
      information, such as links to training/learning resources,
      declarations of related packages, endorsements by certifying
-     authorities.
+     publishers.
 
 12.  [sch]  Support use of :timestamp field for "newer" and "older"
      queries.
--- a/src/client.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/client.py	Mon Mar 09 16:09:13 2009 -0500
@@ -42,52 +42,46 @@
 # PKG_IMAGE_TYPE [entire, partial, user] - type of image
 #       XXX or is this in the Image configuration?
 
+import calendar
+import datetime
+import errno
 import getopt
 import gettext
+import itertools
 import locale
-import itertools
 import os
 import socket
 import sys
+import time
 import traceback
 import urllib2
 import urlparse
-import datetime
-import time
-import calendar
-import errno
 
-import OpenSSL.crypto
-
-from pkg.client import global_settings
-import pkg.client.bootenv as bootenv
-import pkg.client.image as image
-import pkg.client.filelist as filelist
-import pkg.client.progress as progress
-import pkg.client.history as history
+import pkg
 import pkg.client.api as api
 import pkg.client.api_errors as api_errors
+import pkg.client.bootenv as bootenv
+import pkg.client.filelist as filelist
+import pkg.client.history as history
+import pkg.client.image as image
+import pkg.client.imagetypes as imgtypes
+import pkg.client.progress as progress
+import pkg.client.publisher as publisher
 import pkg.search_errors as search_errors
 import pkg.fmri as fmri
 import pkg.misc as misc
-from pkg.misc import msg, emsg, PipeError
-import pkg.version
-import pkg.Uuid25
-import pkg
-from pkg.client.debugvalues import DebugValues
 
-from pkg.client.history import RESULT_FAILED_UNKNOWN
-from pkg.client.history import RESULT_CANCELED
-from pkg.client.history import RESULT_FAILED_TRANSPORT
-from pkg.client.history import RESULT_SUCCEEDED
-from pkg.client.history import RESULT_FAILED_SEARCH
-from pkg.client.history import RESULT_FAILED_STORAGE
-from pkg.client.retrieve import ManifestRetrievalError
-from pkg.client.retrieve import DatastreamRetrievalError
-from pkg.client.retrieve import CatalogRetrievalError
+from pkg.client import global_settings
+from pkg.client.debugvalues import DebugValues
+from pkg.client.history import (RESULT_CANCELED, RESULT_FAILED_BAD_REQUEST,
+    RESULT_FAILED_CONFIGURATION, RESULT_FAILED_SEARCH, RESULT_FAILED_STORAGE,
+    RESULT_FAILED_TRANSPORT, RESULT_FAILED_UNKNOWN, RESULT_SUCCEEDED)
 from pkg.client.filelist import FileListRetrievalError
+from pkg.client.retrieve import (CatalogRetrievalError,
+    DatastreamRetrievalError, ManifestRetrievalError)
+from pkg.misc import msg, emsg, PipeError
 
-CLIENT_API_VERSION = 10
+CLIENT_API_VERSION = 11
 PKG_CLIENT_NAME = "pkg"
 
 def error(text):
@@ -105,6 +99,14 @@
         # program name on all platforms.
         emsg(ws + "pkg: " + text_nows)
 
+def cmd_error(cmd, text):
+        error("%s: %s" % (cmd, text))
+
+def cmd_usage(cmd, usage_error=None):
+        if usage_error:
+                return usage("%s: %s" % (cmd, usage_error))
+        return usage()
+
 def usage(usage_error = None):
         """Emit a usage message and optionally prefix it with a more
             specific error message.  Causes program to exit. """
@@ -121,7 +123,7 @@
         pkg uninstall [-nrvq] [--no-index] package...
         pkg list [-aHsuvf] [package...]
         pkg image-update [-fnvq] [--be-name name] [--no-refresh] [--no-index]
-        pkg refresh [--full] [authority ...]
+        pkg refresh [--full] [publisher ...]
         pkg version
         pkg help
 
@@ -140,12 +142,12 @@
         pkg unset-property propname ...
         pkg property [-H] [propname ...]
 
-        pkg set-authority [-Ped] [-k ssl_key] [-c ssl_cert] [--reset-uuid]
+        pkg set-publisher [-Ped] [-k ssl_key] [-c ssl_cert] [--reset-uuid]
             [-O origin_url] [-m mirror_to_add | --add-mirror=mirror_to_add]
             [-M mirror_to_remove | --remove-mirror=mirror_to_remove]
-            [--enable] [--disable] [--no-refresh] authority
-        pkg unset-authority authority ...
-        pkg authority [-HPa] [authority ...]
+            [--enable] [--disable] [--no-refresh] publisher
+        pkg unset-publisher publisher ...
+        pkg publisher [-HPa] [publisher ...]
         pkg history [-Hl]
         pkg purge-history
         pkg rebuild-index
@@ -250,11 +252,11 @@
                                                     ("FMRI", "STATE", "UFIX"))
                                         elif summary:
                                                 msg(fmt_str % \
-                                                    ("NAME (AUTHORITY)",
+                                                    ("NAME (PUBLISHER)",
                                                     "SUMMARY"))
                                         else:
                                                 msg(fmt_str % \
-                                                    ("NAME (AUTHORITY)",
+                                                    ("NAME (PUBLISHER)",
                                                     "VERSION", "STATE", "UFIX"))
                                 found = True
                         ufix = "%c%c%c%c" % \
@@ -263,23 +265,24 @@
                             state["incorporated"] and "i" or "-",
                             state["excludes"] and "x" or "-")
 
-                        if pfmri.preferred_authority():
-                                auth = ""
+                        if pfmri.preferred_publisher():
+                                pub = ""
                         else:
-                                auth = " (" + pfmri.get_authority() + ")"
+                                pub = " (" + pfmri.get_publisher() + ")"
 
                         if verbose:
-                                pf = pfmri.get_fmri(img.get_default_authority())
+                                pf = pfmri.get_fmri(
+                                    img.get_preferred_publisher())
                                 msg("%-64s %-10s %s" % (pf, state["state"],
                                     ufix))
                         elif summary:
-                                pf = pfmri.get_name() + auth
+                                pf = pfmri.get_name() + pub
 
                                 m = img.get_manifest(pfmri)
                                 msg(fmt_str % (pf, m.get("description", "")))
 
                         else:
-                                pf = pfmri.get_name() + auth
+                                pf = pfmri.get_name() + pub
                                 msg(fmt_str % (pf, pfmri.get_version(),
                                     state["state"], ufix))
 
@@ -471,7 +474,7 @@
         """Attempt to take all installed packages specified to latest
         version."""
 
-        # XXX Authority-catalog issues.
+        # XXX Publisher-catalog issues.
         # XXX Are filters appropriate for an image update?
         # XXX Leaf package refinements.
 
@@ -629,16 +632,16 @@
         """Given an api_inst object, print depot status information."""
 
         status_fmt = "%-10s %-35s %10s %10s"
-        print status_fmt % ("Authority", "URL", "Success", "Failure")
+        print status_fmt % ("Publisher", "URI", "Success", "Failure")
 
         for ds in api_inst.img.gen_depot_status():
-               print status_fmt % (ds.auth, ds.url, ds.good_tx, ds.errors)
+                print status_fmt % (ds.prefix, ds.url, ds.good_tx, ds.errors)
 
 def install(img_dir, args):
         """Attempt to take package specified to INSTALLED state.  The operands
         are interpreted as glob patterns."""
 
-        # XXX Authority-catalog issues.
+        # XXX Publisher-catalog issues.
 
         opts, pargs = getopt.getopt(args, "nvb:f:q", ["no-refresh", "no-index"])
 
@@ -702,7 +705,7 @@
                 else:
                         raise RuntimeError("Catalog refresh failed during"
                             " install.")
-        except api_errors.InvalidCertException:
+        except api_errors.CertificateError:
                 return 1
         except (api_errors.PlanCreationException,
             api_errors.NetworkUnavailableException,
@@ -896,10 +899,6 @@
 def search(img, args):
         """Search through the reverse index databases for the given token."""
 
-        # Verify validity of certificates before attempting network operations
-        if not img.check_cert_validity():
-                return 1
-
         opts, pargs = getopt.getopt(args, "lrs:I")
 
         local = remote = case_sensitive = False
@@ -910,12 +909,12 @@
                 elif opt == "-r":
                         remote = True
                 elif opt == "-s":
-                        if not misc.valid_auth_url(arg):
+                        if not misc.valid_pub_url(arg):
                                 orig_arg = arg
                                 arg = "http://" + arg
-                                if not misc.valid_auth_url(arg):
+                                if not misc.valid_pub_url(arg):
                                         error(_("%s is not a valid "
-                                            "server URL.") % orig_arg)
+                                            "server URI.") % orig_arg)
                                         return 1
                         remote = True
                         servers.append({"origin": arg})
@@ -943,6 +942,17 @@
                         return 1
 
         if remote:
+                # Verify validity of certificates before attempting network
+                # operations.
+                try:
+                        img.check_cert_validity()
+                except api_errors.PermissionsException, e:
+                        error("\n" + str(e))
+                        return 1
+                except api_errors.CertificateError, e:
+                        error(_("search: %s") % e)
+                        return 1
+
                 searches.append(img.remote_search(pargs, servers))
 
         # By default assume we don't find anything.
@@ -968,17 +978,17 @@
 
         except RuntimeError, failed:
                 emsg("Some servers failed to respond:")
-                for auth, err in failed.args[0]:
+                for pub, err in failed.args[0]:
                         if isinstance(err, urllib2.HTTPError):
                                 emsg("    %s: %s (%d)" % \
-                                    (auth["origin"], err.msg, err.code))
+                                    (pub["origin"], err.msg, err.code))
                         elif isinstance(err, urllib2.URLError):
                                 if isinstance(err.args[0], socket.timeout):
                                         emsg("    %s: %s" % \
-                                            (auth["origin"], "timeout"))
+                                            (pub["origin"], "timeout"))
                                 else:
                                         emsg("    %s: %s" % \
-                                            (auth["origin"], err.args[0][1]))
+                                            (pub["origin"], err.args[0][1]))
 
                 retcode = 4
 
@@ -1032,6 +1042,9 @@
                 illegals = ret[api.ImageInterface.INFO_ILLEGALS]
                 multi_match = ret[api.ImageInterface.INFO_MULTI_MATCH]
 
+        except api_errors.PermissionsException, e:
+                error(e)
+                return 1
         except api_errors.NoPackagesInstalledException:
                 error(_("no packages installed"))
                 return 1
@@ -1070,8 +1083,8 @@
 
                 msg(_("         State:"), state)
 
-                # XXX even more info on the authority would be nice?
-                msg(_("     Authority:"), pi.authority)
+                # XXX even more info on the publisher would be nice?
+                msg(_("     Publisher:"), pi.publisher)
                 msg(_("       Version:"), pi.version)
                 msg(_(" Build Release:"), pi.build_release)
                 msg(_("        Branch:"), pi.branch)
@@ -1171,8 +1184,8 @@
                         elif attr == "pkg.shortfmri":
                                 a = manifest.fmri.get_short_fmri()
                                 just = JUST_LEFT
-                        elif attr == "pkg.authority":
-                                a = manifest.fmri.get_authority()
+                        elif attr == "pkg.publisher":
+                                a = manifest.fmri.get_publisher()
                                 just = JUST_LEFT
                         else:
                                 a = ""
@@ -1239,12 +1252,11 @@
         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",
+            "pkg.name", "pkg.fmri", "pkg.shortfmri", "pkg.publisher",
             "pkg.size" ]
 
         display_headers = True
         display_raw = False
-        display_nofilters = False
         remote = False
         local = False
         attrs = []
@@ -1263,9 +1275,6 @@
                         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
@@ -1321,9 +1330,11 @@
         elif remote:
                 # Verify validity of certificates before attempting network
                 # operations
-                if not img.check_cert_validity():
-                        img.history.operation_result = \
-                            history.RESULT_FAILED_TRANSPORT
+                try:
+                        img.check_cert_validity()
+                except (api_errors.CertificateError,
+                    api_errors.PermissionsException), e:
+                        img.history.log_operation_end(error=e)
                         return 1
 
                 fmris = []
@@ -1345,7 +1356,7 @@
                         npnames = {}
                         npmatch = []
                         for m, state in matches:
-                                if m.preferred_authority():
+                                if m.preferred_publisher():
                                         pnames[m.get_pkg_stem()] = 1
                                         pmatch.append(m)
                                 else:
@@ -1392,7 +1403,6 @@
                 else:
                         sort_attrs = attrs[:1]
 
-        filt = not display_nofilters
         manifests = ( img.get_manifest(f) for f in fmris )
 
         actionlist = [ (m, a)
@@ -1429,7 +1439,7 @@
         succeeded = cre.succeeded
         msg(_("pkg: %s/%s catalogs successfully updated:") % (succeeded, total))
 
-        for auth, err in cre.failed:
+        for pub, err in cre.failed:
                 if isinstance(err, urllib2.HTTPError):
                         emsg("   %s: %s - %s" % \
                             (err.filename, err.code, err.msg))
@@ -1437,22 +1447,22 @@
                         if err.args[0][0] == 8:
                                 emsg("    %s: %s" % \
                                     (urlparse.urlsplit(
-                                        auth["origin"])[1].split(":")[0],
+                                        pub["origin"])[1].split(":")[0],
                                     err.args[0][1]))
                         else:
                                 if isinstance(err.args[0], socket.timeout):
                                         emsg("    %s: %s" % \
-                                            (auth["origin"], "timeout"))
+                                            (pub["origin"], "timeout"))
                                 else:
                                         emsg("    %s: %s" % \
-                                            (auth["origin"], err.args[0][1]))
+                                            (pub["origin"], err.args[0][1]))
                 elif isinstance(err, CatalogRetrievalError) and \
                     isinstance(err.exc, EnvironmentError) and \
                     err.exc.errno == errno.EACCES:
-                        if err.auth:
+                        if err.prefix:
                                 emsg("   ", _("Could not update catalog "
                                      "for '%s' due to insufficient "
-                                     "permissions.") % err.auth)
+                                     "permissions.") % err.prefix)
                         else:
                                 emsg("   ", _("Could not update a catalog "
                                      "due to insufficient permissions."))
@@ -1489,11 +1499,9 @@
 
         try:
                 api_inst.refresh(full_refresh, pargs)
-        except api_errors.UnrecognizedAuthorityException, e:
-                tmp = _("%s is not a recognized or enabled authority to " \
-                    "refresh. \n'pkg authority' will show a" \
-                    " list of authorities.")
-                error(tmp % e.auth)
+        except api_errors.PublisherError, e:
+                error(e)
+                error(_("'pkg publisher' will show a list of publishers."))
                 return 1
         except (api_errors.PermissionsException,
             api_errors.NetworkUnavailableException), e:
@@ -1509,10 +1517,10 @@
         else:
                 return 0
 
-def authority_set(img, args):
-        """pkg set-authority [-Ped] [-k ssl_key] [-c ssl_cert] [--reset-uuid]
+def publisher_set(img_dir, args):
+        """pkg set-publisher [-Ped] [-k ssl_key] [-c ssl_cert] [--reset-uuid]
             [-O origin_url] [-m mirror to add] [-M mirror to remove]
-            [--enable] [--disable] [--no-refresh] authority"""
+            [--enable] [--disable] [--no-refresh] publisher"""
 
         preferred = False
         ssl_key = None
@@ -1551,64 +1559,124 @@
                         disable = True
 
         if len(pargs) == 0:
-                usage(
-                    _("set-authority: requires an authority name"))
+                cmd_usage("set-publisher", _("requires a publisher name"))
         elif len(pargs) > 1:
-                usage(
-                    _("set-authority: only one authority name may be specified"))
+                cmd_usage("set-publisher", _("only one publisher name may be "
+                        "specified"))
 
-        auth = pargs[0]
+        name = pargs[0]
 
-        if ssl_key:
-                ssl_key = os.path.abspath(ssl_key)
-                if not os.path.exists(ssl_key):
-                        error(_("set-authority: SSL key file '%s' does not " \
-                            "exist") % ssl_key)
-                        return 1
+        if preferred and disable:
+                cmd_usage("set-publisher", _("the -p and -d options may not be "
+                    "combined"))
 
-        if ssl_cert:
-                ssl_cert = os.path.abspath(ssl_cert)
-                if not os.path.exists(ssl_cert):
-                        error(_("set-authority: SSL key cert '%s' does not " \
-                            "exist") % ssl_cert)
-                        return 1
-
-
-        if not img.has_authority(auth) and origin_url == None:
-                error(_("set-authority: authority does not exist. Use " \
-                    "-O to define origin URL for new authority"))
+        progresstracker = get_tracker(True)
+        try:
+                api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
+                    progresstracker, None, PKG_CLIENT_NAME)
+        except api_errors.ImageNotFoundException, e:
+                cmd_error("set-publisher",
+                    _("'%s' is not an install image") % e.user_dir)
                 return 1
 
-        elif not img.has_authority(auth) and not misc.valid_auth_prefix(auth):
-                error(_("set-authority: authority name has invalid characters"))
+        new_pub = False
+        try:
+                pub = api_inst.get_publisher(prefix=name, alias=name,
+                    duplicate=True)
+                if reset_uuid:
+                        pub.reset_client_uuid()
+                repo = pub.selected_repository
+        except api_errors.PermissionsException, e:
+                cmd_error("set-publisher", e)
                 return 1
+        except api_errors.UnknownPublisher:
+                if not origin_url:
+                        cmd_error("set-publisher", _("publisher does not "
+                            "exist. Use -O to define origin URI for new "
+                            "publisher."))
+                        return 1
+                # No pre-existing, so create a new one.
+                repo = publisher.Repository()
+                pub = publisher.Publisher(name, repositories=[repo])
+                new_pub = True
 
-        if origin_url and not misc.valid_auth_url(origin_url):
-                error(_("set-authority: authority URL is invalid"))
-                return 1
+        if disable is not None:
+                # Set disabled property only if provided.
+                pub.disabled = disable
+
+        if origin_url:
+                try:
+                        if not repo.origins:
+                                # New publisher case.
+                                repo.add_origin(origin_url)
+                                origin = repo.origins[0]
+                        else:
+                                origin = repo.origins[0]
+                                origin.uri = origin_url
 
-        if disable and auth == img.get_default_authority():
-                error(_("set-authority: cannot disable the preferred authority"))
-                return 1
+                        # XXX once image configuration supports storing this
+                        # information at the uri level, ssl info should be set
+                        # here.
+                except api_errors.PublisherError, e:
+                        cmd_error("set-publisher", e)
+                        return 1
+
+        if add_mirror:
+                try:
+                        # XXX once image configuration supports storing this
+                        # information at the uri level, ssl info should be set
+                        # here.
+                        repo.add_mirror(add_mirror)
+                except (api_errors.PublisherError,
+                    api_errors.CertificateError), e:
+                        cmd_error("set-publisher", e)
+                        return 1
 
-        uuid = None
-        if reset_uuid:
-                uuid = pkg.Uuid25.uuid1()
+        if remove_mirror:
+                try:
+                        repo.remove_mirror(remove_mirror)
+                except api_errors.PublisherError, e:
+                        cmd_error("set-publisher", e)
+                        return 1
+
+        # None is checked for here so that a client can unset a ssl_cert or
+        # ssl_key by using -k "" or -c "".
+        if ssl_cert is not None or ssl_key is not None:
+                # Assume the user wanted to update the ssl_cert or ssl_key
+                # information for *all* of the currently selected
+                # repository's origins and mirrors.
+                try:
+                        for uri in repo.origins:
+                                if ssl_cert is not None:
+                                        uri.ssl_cert = ssl_cert
+                                if ssl_key is not None:
+                                        uri.ssl_key = ssl_key
+                        for uri in repo.mirrors:
+                                if ssl_cert is not None:
+                                        uri.ssl_cert = ssl_cert
+                                if ssl_key is not None:
+                                        uri.ssl_key = ssl_key
+                except (api_errors.PublisherError,
+                    api_errors.CertificateError), e:
+                        cmd_error("set-publisher", e)
+                        return 1
 
         try:
-                img.set_authority(auth, origin_url=origin_url,
-                    ssl_key=ssl_key, ssl_cert=ssl_cert,
-                    refresh_allowed=refresh_catalogs, uuid=uuid,
-                    disabled=disable)
+                if new_pub:
+                        api_inst.add_publisher(pub,
+                            refresh_allowed=refresh_catalogs)
+                else:
+                        api_inst.update_publisher(pub,
+                            refresh_allowed=refresh_catalogs)
         except api_errors.CatalogRefreshException, e:
                 text = "Could not refresh the catalog for %s"
-                error(_(text) % auth)
+                error(_(text) % pub)
                 return 1
         except api_errors.InvalidDepotResponseException, e:
-                error(_("The URL '%s' does not appear to point to a "
-                    "valid pkg server.\nPlease check the server's "
-                    "address and client's network configuration."
-                    "\nAdditional details:\n\n%s" % (origin_url, e)))
+                error(_("The origin URIs for '%s' do not appear to point to a "
+                    "valid pkg server.\nPlease check the server's address and "
+                    "client's network configuration."
+                    "\nAdditional details:\n\n%s" % (pub.prefix, e)))
                 return 1
         except api_errors.PermissionsException, e:
                 # Prepend a newline because otherwise the exception will
@@ -1617,75 +1685,39 @@
                 return 1
 
         if preferred:
-                if img.get_authority(auth)["disabled"]:
-                        error(_("set-authority: the preferred authority must be enabled"))
-                        return 1
-                img.set_preferred_authority(auth)
-
-        if add_mirror:
-
-                if not misc.valid_auth_url(add_mirror):
-                        error(_("set-authority: added mirror's URL is invalid"))
-                        return 1
-
-                if img.has_mirror(auth, add_mirror):
-                        error(_("set-authority: mirror already exists"))
-                        return 1
-
-                img.add_mirror(auth, add_mirror)
-
-        if remove_mirror:
-
-                if not misc.valid_auth_url(remove_mirror):
-                        error(_("set-authority: removed mirror has bad URL"))
-                        return 1
-
-                if not img.has_mirror(auth, remove_mirror):
-                        error(_("set-authority: mirror does not exist"))
-                        return 1
-
-
-                img.del_mirror(auth, remove_mirror)
-
+                api_inst.set_preferred_publisher(prefix=pub.prefix)
 
         return 0
 
-def authority_unset(img, args):
-        """pkg unset-authority authority ..."""
-
-        # is this an existing authority in our image?
-        # if so, delete it
-        # if not, error
-        preferred_auth = img.get_default_authority()
+def publisher_unset(img_dir, args):
+        """pkg unset-publisher publisher ..."""
 
         if len(args) == 0:
                 usage()
 
-        for a in args:
-                if not img.has_authority(a):
-                        error(_("unset-authority: no such authority: %s") \
-                            % a)
-                        return 1
+        progresstracker = get_tracker(True)
+        try:
+                api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
+                    progresstracker, None, PKG_CLIENT_NAME)
+        except api_errors.ImageNotFoundException, e:
+                error(_("'%s' is not an install image") % e.user_dir)
+                return 1
 
-                if a == preferred_auth:
-                        error(_("unset-authority: removal of preferred " \
-                            "authority not allowed."))
-                        return 1
+        for name in args:
                 try:
-                        img.delete_authority(a)
-                except api_errors.PermissionsException, e:
+                        api_inst.remove_publisher(prefix=name, alias=name)
+                except (api_errors.PermissionsException,
+                    api_errors.PublisherError), e:
                         # Prepend a newline because otherwise the exception
                         # will be printed on the same line as the spinner.
                         error("\n" + str(e))
                         return 1
-
         return 0
 
-def authority_list(img, args):
-        """pkg authorities"""
+def publisher_list(img_dir, args):
+        """pkg publishers"""
         omit_headers = False
         preferred_only = False
-        preferred_authority = img.get_default_authority()
         inc_disabled = False
 
         opts, pargs = getopt.getopt(args, "HPa")
@@ -1697,82 +1729,151 @@
                 if opt == "-a":
                         inc_disabled = True
 
-        if len(pargs) == 0:
-                if not omit_headers:
-                        msg("%-35s %s" % ("AUTHORITY", "URL"))
-
-                if preferred_only:
-                        auths = [img.get_authority(preferred_authority)]
-                else:
-                        auths = img.gen_authorities(inc_disabled=inc_disabled)
+        progresstracker = get_tracker(True)
 
-                for a in auths:
-                        # summary list
-                        pfx, url, ssl_key, ssl_cert, dt, mir = \
-                            img.split_authority(a)
-
-                        if not preferred_only and pfx == preferred_authority:
-                                pfx += " (preferred)"
-                        if a["disabled"]:
-                                pfx += " (disabled)"
-                        msg("%-35s %s" % (pfx, url))
-        else:
-                img.load_catalogs(get_tracker())
+        try:
+                api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
+                    progresstracker, None, PKG_CLIENT_NAME)
+        except api_errors.ImageNotFoundException, e:
+                error(_("'%s' is not an install image") % e.user_dir)
+                return 1
 
-                for a in pargs:
-                        if not img.has_authority(a):
-                                error(_("authority: no such authority: %s") \
-                                    % a)
-                                return 1
-
-                        # detailed print
-                        auth = img.get_authority(a)
-                        pfx, url, ssl_key, ssl_cert, dt, mir = \
-                            img.split_authority(auth)
+        cert_cache = {}
+        def get_cert_info(ssl_cert):
+                if not ssl_cert:
+                        return None
+                if ssl_cert not in cert_cache:
+                        c = cert_cache[ssl_cert] = {}
+                        errors = c["errors"] = []
+                        times = c["info"] = {
+                            "effective": "",
+                            "expiration": "",
+                        }
 
-                        if dt:
-                                dt = dt.ctime()
-
-                        if ssl_cert:
-                                try:
-                                        cert = img.build_cert(ssl_cert)
-                                except (IOError, OpenSSL.crypto.Error):
-                                        error(_("SSL certificate for %s" \
-                                            "is invalid or non-existent.") % \
-                                            pfx)
-                                        error(_("Please check file at %s") %\
-                                            ssl_cert)
-                                        continue
-
+                        try:
+                                cert = misc.validate_ssl_cert(ssl_cert)
+                        except (EnvironmentError,
+                            api_errors.CertificateError,
+                            api_errors.PermissionsException), e:
+                                # If the cert information can't be retrieved,
+                                # add the errors to a list and continue on.
+                                errors.append(e)
+                                c["valid"] = False
+                        else:
                                 nb = cert.get_notBefore()
                                 t = time.strptime(nb, "%Y%m%d%H%M%SZ")
                                 nb = datetime.datetime.utcfromtimestamp(
                                     calendar.timegm(t))
+                                times["effective"] = nb.ctime()
 
                                 na = cert.get_notAfter()
                                 t = time.strptime(na, "%Y%m%d%H%M%SZ")
                                 na = datetime.datetime.utcfromtimestamp(
                                     calendar.timegm(t))
-                        else:
-                                cert = None
+                                times["expiration"] = na.ctime()
+                                c["valid"] = True
+
+                return cert_cache[ssl_cert]
+
+        retcode = 0
+        if len(pargs) == 0:
+                fmt = "%-24s %-12s %-8s %-8s %s"
+                if not omit_headers:
+                        msg(fmt % (_("PUBLISHER"), "", _("TYPE"), _("STATUS"),
+                            _("URI")))
+
+                pref_pub = api_inst.get_preferred_publisher()
+                if preferred_only:
+                        pubs = [pref_pub]
+                else:
+                        pubs = [
+                            p for p in api_inst.get_publishers()
+                            if inc_disabled or not p.disabled
+                        ]
+
+                for p in pubs:
+                        pfx = p.prefix
+                        pstatus = ""
+                        if not preferred_only and p == pref_pub:
+                                pstatus = _("(preferred)")
+                        if p.disabled:
+                                pstatus = _("(disabled)")
+
+                        # Only show the selected repository's information in
+                        # summary view.
+                        r = p.selected_repository
+                        for uri in r.origins:
+                                # XXX get the real origin status
+                                msg(fmt % (pfx, pstatus, _("origin"), "online",
+                                    uri))
+                        for uri in r.mirrors:
+                                # XXX get the real mirror status
+                                msg(fmt % (pfx, pstatus, _("mirror"), "online",
+                                    uri))
+        else:
+                def display_ssl_info(uri):
+                        retcode = 0
+                        c = get_cert_info(uri.ssl_cert)
+                        msg(_("              SSL Key:"), uri.ssl_key)
+                        msg(_("             SSL Cert:"), uri.ssl_cert)
+
+                        if not c:
+                                return retcode
+
+                        if c["errors"]:
+                                retcode = 1
+
+                        for e in c["errors"]:
+                                emsg("\n" + str(e) + "\n")
+
+                        if c["valid"]:
+                                msg(_(" Cert. Effective Date:"),
+                                    c["info"]["effective"])
+                                msg(_("Cert. Expiration Date:"),
+                                    c["info"]["expiration"])
+                        return retcode
+
+                def display_repository(r):
+                        retcode = 0
+                        for uri in r.origins:
+                                msg(_("           Origin URI:"), uri)
+                                rval = display_ssl_info(uri)
+                                if rval == 1:
+                                        retcode = 3
+
+                        for uri in r.mirrors:
+                                msg(_("           Mirror URI:"), uri)
+                                rval = display_ssl_info(uri)
+                                if rval == 1:
+                                        retcode = 3
+                        return retcode
+
+                for name in pargs:
+                        # detailed print
+                        pub = api_inst.get_publisher(prefix=name, alias=name)
+                        dt = api_inst.get_publisher_last_update_time(pub.prefix)
+                        if dt:
+                                dt = dt.ctime()
 
                         msg("")
-                        msg("           Authority:", pfx)
-                        msg("          Origin URL:", url)
-                        msg("             SSL Key:", ssl_key)
-                        msg("            SSL Cert:", ssl_cert)
-                        if cert:
-                                msg(" Cert Effective Date:", nb.ctime())
-                                msg("Cert Expiration Date:", na.ctime())
-                        msg("                UUID:", auth["uuid"])
-                        msg("     Catalog Updated:", dt)
-                        msg("             Mirrors:", mir)
-                        e = "Yes"
-                        if auth["disabled"]:
-                                e = "No"
-                        msg("             Enabled:", e)
+                        msg(_("            Publisher:"), pub.prefix)
+                        msg(_("                Alias:"), pub.alias)
 
-        return 0
+                        for r in pub.repositories:
+                                rval = display_repository(r)
+                                if rval != 0:
+                                        # There was an error in displaying some
+                                        # of the information about a repository.
+                                        # However, continue on.
+                                        retcode = rval
+
+                        msg(_("          Client UUID:"), pub.client_uuid)
+                        msg(_("      Catalog Updated:"), dt)
+                        if pub.disabled:
+                                msg(_("              Enabled:"), _("No"))
+                        else:
+                                msg(_("              Enabled:"), _("Yes"))
+        return retcode
 
 def property_set(img, args):
         """pkg set-property propname propvalue"""
@@ -1784,9 +1885,9 @@
         except ValueError:
                 usage(_("set-property: requires a property name and value"))
 
-        if propname == "preferred-authority":
-                error(_("set-property: set-authority must be used to change "
-                        "the preferred authority"))
+        if propname == "preferred-publisher":
+                error(_("set-property: set-publisher must be used to change "
+                        "the preferred publisher"))
                 return 1
 
         try:
@@ -1812,9 +1913,9 @@
                 usage(_("unset-property: requires at least one property name"))
 
         for p in pargs:
-                if p == "preferred-authority":
-                        error(_("unset-property: set-authority must be used to "
-                            "change the preferred authority"))
+                if p == "preferred-publisher":
+                        error(_("unset-property: set-publisher must be used to "
+                            "change the preferred publisher"))
                         return 1
 
                 try:
@@ -1859,7 +1960,7 @@
 
 def image_create(img, args):
         """Create an image of the requested kind, at the given path.  Load
-        catalog for initial authority for convenience.
+        catalog for initial publisher for convenience.
 
         At present, it is legitimate for a user image to specify that it will be
         deployed in a zone.  An easy example would be a program with an optional
@@ -1870,25 +1971,25 @@
         is_zone = False
         ssl_key = None
         ssl_cert = None
-        auth_name = None
-        auth_url = None
+        pub_name = None
+        pub_url = None
         refresh_catalogs = True
         force = False
         variants = {}
 
         opts, pargs = getopt.getopt(args, "fFPUza:k:c:",
-            ["force", "full", "partial", "user", "zone", "authority=",
+            ["force", "full", "partial", "user", "zone", "publisher=",
                 "no-refresh", "variant="])
 
         for opt, arg in opts:
                 if opt == "-f" or opt == "--force":
                         force = True
                 if opt == "-F" or opt == "--full":
-                        imgtype = image.IMG_ENTIRE
+                        imgtype = imgtypes.IMG_ENTIRE
                 if opt == "-P" or opt == "--partial":
-                        imgtype = image.IMG_PARTIAL
+                        imgtype = imgtypes.IMG_PARTIAL
                 if opt == "-U" or opt == "--user":
-                        imgtype = image.IMG_USER
+                        imgtype = imgtypes.IMG_USER
                 if opt == "-z" or opt == "--zone":
                         is_zone = True
                         imgtype = image.IMG_ENTIRE
@@ -1898,11 +1999,11 @@
                         ssl_key = arg
                 if opt == "-c":
                         ssl_cert = arg
-                if opt == "-a" or opt == "--authority":
+                if opt == "-a" or opt == "--publisher":
                         try:
-                                auth_name, auth_url = arg.split("=", 1)
+                                pub_name, pub_url = arg.split("=", 1)
                         except ValueError:
-                                usage(_("image-create requires authority "
+                                usage(_("image-create requires publisher "
                                     "argument to be of the form "
                                     "'<prefix>=<url>'."))
                 if opt == "--variant":
@@ -1922,36 +2023,36 @@
         if ssl_key:
                 ssl_key = os.path.abspath(ssl_key)
                 if not os.path.exists(ssl_key):
-                        msg(_("pkg: set-authority: SSL key file '%s' does " \
+                        msg(_("pkg: set-publisher: SSL key file '%s' does " \
                             "not exist") % ssl_key)
                         return 1
 
         if ssl_cert:
                 ssl_cert = os.path.abspath(ssl_cert)
                 if not os.path.exists(ssl_cert):
-                        msg(_("pkg: set-authority: SSL key cert '%s' does " \
+                        msg(_("pkg: set-publisher: SSL key cert '%s' does " \
                             "not exist") % ssl_cert)
                         return 1
 
-        if not auth_name and not auth_url:
-                usage(_("image-create requires an authority argument"))
+        if not pub_name and not pub_url:
+                usage(_("image-create requires a publisher argument"))
 
-        if not auth_name or not auth_url:
-                usage(_("image-create requires authority argument to be of "
+        if not pub_name or not pub_url:
+                usage(_("image-create requires publisher argument to be of "
                     "the form '<prefix>=<url>'."))
 
-        if auth_name.startswith(fmri.PREF_AUTH_PFX):
+        if pub_name.startswith(fmri.PREF_PUB_PFX):
                 error(_("image-create requires that a prefix not match: %s"
-                        % fmri.PREF_AUTH_PFX))
+                        % fmri.PREF_PUB_PFX))
                 return 1
 
-        if not misc.valid_auth_prefix(auth_name):
-                error(_("image-create: authority prefix has invalid " \
+        if not misc.valid_pub_prefix(pub_name):
+                error(_("image-create: publisher prefix has invalid " \
                     "characters"))
                 return 1
 
-        if not misc.valid_auth_url(auth_url):
-                error(_("image-create: authority URL is invalid"))
+        if not misc.valid_pub_url(pub_url):
+                error(_("image-create: publisher URI is invalid"))
                 return 1
 
         image_dir = pargs[0]
@@ -1970,18 +2071,21 @@
                 return 1
 
         try:
-                img.set_attrs(imgtype, image_dir, is_zone, auth_name, auth_url,
+                img.set_attrs(imgtype, image_dir, is_zone, pub_name, pub_url,
                     ssl_key=ssl_key, ssl_cert=ssl_cert, variants=variants,
                     refresh_allowed=refresh_catalogs)
         except OSError, e:
                 error(_("cannot create image at %s: %s") % \
                     (image_dir, e.args[1]))
                 return 1
+        except api_errors.PermissionsException, e:
+                cmd_error("image-create", e)
+                return 1
         except api_errors.InvalidDepotResponseException, e:
-                error(_("The URL '%s' does not appear to point to a "
+                error(_("The URI '%s' does not appear to point to a "
                     "valid pkg server.\nPlease check the server's "
                     "address and client's network configuration."
-                    "\nAdditional details:\n\n%s" % (auth_url, e)))
+                    "\nAdditional details:\n\n%s" % (pub_url, e)))
                 return 1
         except api_errors.CatalogRefreshException, cre:
                 if display_catalog_failures(cre) == 0:
@@ -2005,6 +2109,9 @@
         try:
                 img.history.operation_name = "rebuild-index"
                 img.rebuild_search_index(get_tracker(quiet))
+        except api_errors.PermissionsException, e:
+                cmd_error("rebuild-index", e)
+                return 1
         except api_errors.CorruptedIndexException:
                 img.history.operation_result = RESULT_FAILED_SEARCH
                 error(INCONSISTENT_INDEX_ERROR_MESSAGE)
@@ -2051,6 +2158,9 @@
                 try:
                         he = history.History(root_dir=img.history.root_dir,
                             filename=entry)
+                except api_errors.PermissionsException, e:
+                        cmd_error("history", e)
+                        return 1
                 except history.HistoryLoadException, e:
                         if e.parse_failure:
                                 # Ignore corrupt entries.
@@ -2134,10 +2244,11 @@
         for opt, arg in opts:
                 if opt == "-D" or opt == "--debug":
                         try:
-                                key, value = arg.split('=', 1)
-                        except:
-                                usage(_("%s takes argument of form name=value, not %s")
-                                    % (opt, arg))
+                                key, value = arg.split("=", 1)
+                        except (AttributeError, ValueError):
+                                usage(_("%(opt)s takes argument of form "
+                                    "name=value, not %(arg)s") % { "opt":  opt,
+                                    "arg": arg })
                         DebugValues.set_value(key, value)
                 elif opt == "-R":
                         mydir = arg
@@ -2218,7 +2329,11 @@
                         error(_("No image found."))
                 return 1
 
-        img.load_config()
+        try:
+                img.load_config()
+        except api_errors.ApiException, e:
+                error(_("client configuration error: %s") % e)
+                return 1
 
         try:
                 if subcommand == "refresh":
@@ -2245,12 +2360,12 @@
                         return fix_image(img, pargs)
                 elif subcommand == "verify":
                         return verify_image(img, pargs)
-                elif subcommand == "set-authority":
-                        return authority_set(img, pargs)
-                elif subcommand == "unset-authority":
-                        return authority_unset(img, pargs)
-                elif subcommand == "authority":
-                        return authority_list(img, pargs)
+                elif subcommand == "set-publisher":
+                        return publisher_set(mydir, pargs)
+                elif subcommand == "unset-publisher":
+                        return publisher_unset(mydir, pargs)
+                elif subcommand == "publisher":
+                        return publisher_list(mydir, pargs)
                 elif subcommand == "set-property":
                         return property_set(img, pargs)
                 elif subcommand == "unset-property":
@@ -2290,6 +2405,16 @@
                 # We don't want to display any messages here to prevent
                 # possible further broken pipe (EPIPE) errors.
                 __ret = 1
+        except api_errors.CertificateError, __e:
+                if __img:
+                        __img.history.abort(RESULT_FAILED_CONFIGURATION)
+                error(__e)
+                __ret = 1
+        except api_errors.PublisherError, __e:
+                if __img:
+                        __img.history.abort(RESULT_FAILED_BAD_REQUEST)
+                error(__e)
+                __ret = 1
         except misc.TransportException, __e:
                 if __img:
                         __img.history.abort(RESULT_FAILED_TRANSPORT)
--- a/src/man/pkg.1.txt	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/man/pkg.1.txt	Mon Mar 09 16:09:13 2009 -0500
@@ -18,11 +18,11 @@
      /usr/bin/pkg list [-aHsuvf] [pkg_fmri_pattern ...]
      /usr/bin/pkg search [-lrI] [-s server] token
 
-     /usr/bin/pkg refresh [--full] [authority ...]
+     /usr/bin/pkg refresh [--full] [publisher ...]
 
      /usr/bin/pkg image-create [-fFPUz] [--force] [--full|--partial|--user]
          [--zone] [-k ssl_key] [-c ssl_cert] [--no-refresh]
-         -a authority=origin_url dir
+         -a publisher=origin_url dir
      /usr/bin/pkg image-update [-fnvq] [--be-name name] [--no-refresh]
          [--no-index]
 
@@ -30,12 +30,12 @@
      /usr/bin/pkg unset-property propname ...
      /usr/bin/pkg property [-H] [propname ...]
 
-     /usr/bin/pkg set-authority [-Ped] [-k ssl_key] [-c ssl_cert]
+     /usr/bin/pkg set-publisher [-Ped] [-k ssl_key] [-c ssl_cert]
          [-O origin_url] [-m mirror_to_add | --add-mirror=mirror_to_add]
          [-M mirror_to_remove | --remove-mirror=mirror_to_remove]
-         [--enable] [--disable] [--no-refresh] [--reset-uuid] authority
-     /usr/bin/pkg unset-authority authority ...
-     /usr/bin/pkg authority [-HPa] [authority ...]
+         [--enable] [--disable] [--no-refresh] [--reset-uuid] publisher
+     /usr/bin/pkg unset-publisher publisher ...
+     /usr/bin/pkg publisher [-HPa] [publisher ...]
 
      /usr/bin/pkg history [-Hl]
      /usr/bin/pkg purge-history
@@ -49,9 +49,9 @@
      pkg is the retrieval client for the image packaging system.  With a
      valid configuration, pkg can be invoked to create locations for
      packages to be installed, called 'images', and install packages
-     into those images.  Packages are published by authorities, who may
+     into those images.  Packages are published by publishers, who may
      make their packages available at one or more repositories.  pkg,
-     then, retrieves packages from an authority's repository and
+     then, retrieves packages from a publisher's repository and
      installs them into an image.
 
      pkg can also uninstall packages, refresh the package catalogs,
@@ -80,24 +80,24 @@
      The following subcommands are supported:
 
      image-create [-fFPUz] [--force] [--full|--partial|--user] [--zone]
-       [-k ssl_key] [-c ssl_cert] [--no-refresh] -a authority=origin_url dir
+       [-k ssl_key] [-c ssl_cert] [--no-refresh] -a publisher=origin_url dir
           Create, at location given by dir, an image suitable for package
           operations.  The default image type is user, as given by the -U
           (--user) option.  The image type may be set to a full image (-F
           or --full) or to a partial image (-P or --partial) linked to the
           full image enclosing the given dir path.
 
-          A preferred authority must be set using the -a option.  An
-          attempt to retrieve the catalog associated with this authority
+          A preferred publisher must be set using the -a option.  An
+          attempt to retrieve the catalog associated with this publisher
           will be made following the initial creation operations.
 
-          For authorities using client SSL authentication, a client key and
+          For publishers using client SSL authentication, a client key and
           client certificate may be registered via the -c and -k options.
 
           If the image is to be run within nonglobal zone context, then
           the -z (--zone) option can be used to set an appropriate filter.
 
-          With --no-refresh, do not attempt to contact the image's authority
+          With --no-refresh, do not attempt to contact the image's publisher
           to retrieve its catalog.
 
           With -f (--force), force the creation of an image over an existing
@@ -115,7 +115,7 @@
           With the -v option, issue verbose progress messages during the
           requested operation.  With the -q option, be completely silent.
 
-          With  --no-refresh, do not attempt to contact the image's authorities
+          With  --no-refresh, do not attempt to contact the image's publishers
           to retrieve their catalogs.  With --no-index do not update the
           search indices after the operation has completed successfully.
 
@@ -123,9 +123,9 @@
           argument given.  This option is only valid if a new boot environment
           is created during image update. See also beadm(1m).
 
-     refresh [--full] [authority ...]
-          Retrieve updates to the catalogs for each authority specified.
-          When given no arguments, retrieves updates for each authority
+     refresh [--full] [publisher ...]
+          Retrieve updates to the catalogs for each publisher specified.
+          When given no arguments, retrieves updates for each publisher
           registered within the image. With --full, retrieve the full
           catalogs.
 
@@ -142,7 +142,7 @@
           uninstall any packages which are dependent on the initial
           package.
 
-          With --no-refresh, do not attempt to contact the image's authorities
+          With --no-refresh, do not attempt to contact the image's publishers
           to retrieve their catalogs. With --no-index do not update the
           search indices after the operation has completed successfully.
 
@@ -209,7 +209,7 @@
                                 the package containing the action, such
                                 as pkg://opensolaris.org/[email protected]
 
-          pkg.authority         Corresponds to the authority of the
+          pkg.publisher         Corresponds to the publisher of the
                                 the package containing the action, such
                                 as "opensolaris.org"
 
@@ -221,7 +221,7 @@
           By default, and with -l, search the image's installed packages.
 
           With -r, search the repositories corresponding to the image's
-          authorities.
+          publishers.
 
           With -s, search the identified server, which is expected to be
           a pkg(5) repository, at the given URL.  This may be specified
@@ -256,13 +256,13 @@
 
      set-property propname propvalue
           Update an existing image property or add a new image property;
-          except for preferred-authority, which can only be changed using
-          set-authority.
+          except for preferred-publisher, which can only be changed using
+          set-publisher.
 
      unset-property propname ...
           Remove an existing image property or properties; except for
-          preferred-authority, which can only be changed using
-          set-authority.
+          preferred-publisher, which can only be changed using
+          set-publisher.
 
      property [-H] [propname ...]
           Display image property information.  With no argument, display the
@@ -270,46 +270,46 @@
           property names is requested, display the names and values for those
           properties.  With -H, omit the headers from the listing.
 
-     set-authority [-Ped] [-k ssl_key] [-c ssl_cert] [-O origin_url]
+     set-publisher [-Ped] [-k ssl_key] [-c ssl_cert] [-O origin_url]
        [-m mirror_to_add | --add-mirror=mirror_to_add]
        [-M mirror_to_remove | --remove-mirror=mirror_to_remove]
-       [--enable] [--disable] [--no-refresh] [--reset-uuid] authority
-          Update an existing authority or add an additional package
-          authority.  With -P, set the specified authority as the
-          preferred authority.  With -c and -k, specify client SSL
+       [--enable] [--disable] [--no-refresh] [--reset-uuid] publisher
+          Update an existing publisher or add an additional package
+          publisher.  With -P, set the specified publisher as the
+          preferred publisher.  With -c and -k, specify client SSL
           certificate and key respectively.  The -O option sets the URL
-          prefix for the origin packaging repository for the authority.
+          prefix for the origin packaging repository for the publisher.
 
-          With --no-refresh, do not attempt to contact the authority
+          With --no-refresh, do not attempt to contact the publisher
           specified to retrieve its catalog.
 
           With --reset-uuid, choose a new unique identifier that identifies
-          this image to its authority.
+          this image to its publisher.
 
           With -m (--add-mirror), add the URL as a mirror for the given
-          authority.
+          publisher.
 
           With -M (--remove-mirror), remove the URL from the list of mirrors
-          for the given authority.
+          for the given publisher.
 
-          With -e (--enable), enable the authority; with -d (--disable), disable
-          the authority.  A disabled authority is not used when populating the
+          With -e (--enable), enable the publisher; with -d (--disable), disable
+          the publisher.  A disabled publisher is not used when populating the
           package list or in certain package operations (install, uninstall, and
-          image-update).  However, the properties for a disabled authority can
-          still be set and viewed.  If only one authority exists, it cannot be
+          image-update).  However, the properties for a disabled publisher can
+          still be set and viewed.  If only one publisher exists, it cannot be
           disabled.
 
-     unset-authority authority ...
-          Remove the configuration associated with the given authority
-          or authorities.
+     unset-publisher publisher ...
+          Remove the configuration associated with the given publisher
+          or publishers.
 
-     authority [-HPa] [authority ...]
-          Display authority information.  With no arguments, display the
-          list of enabled authorities, and their origin URLs.  If
-          specific authorities are requested, display the configuration
-          values, including mirrors, associated with those authorities.  With
+     publisher [-HPa] [publisher ...]
+          Display publisher information.  With no arguments, display the
+          list of enabled publishers, and their origin URLs.  If
+          specific publishers are requested, display the configuration
+          values, including mirrors, associated with those publishers.  With
           -H, omit the headers from the listing.  With -P, display only the
-          preferred authority. With -a, display all authorities (including those
+          preferred publisher. With -a, display all publishers (including those
           that are disabled).
 
      history [-Hl]
@@ -336,7 +336,7 @@
           Display a usage message.
 
 EXAMPLES
-     Example 1:  Create a new, full image, with authority example.com,
+     Example 1:  Create a new, full image, with publisher example.com,
      stored at /aux0/example_root
 
      $ pkg image-create -F -a example.com=http://pkg.example.com:10000 \
@@ -401,7 +401,7 @@
      type, starting at $META as above.
 
      $META/catalog              Directory of catalogs from defined
-                                authorities.
+                                publishers.
 
 ATTRIBUTES
      See attributes(5) for descriptions of the  following  attri-
--- a/src/man/pkg.5.txt	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/man/pkg.5.txt	Mon Mar 09 16:09:13 2009 -0500
@@ -17,12 +17,12 @@
 
      Each package is represented by a fault management resource identifier
      (FMRI) with the scheme 'pkg:'.  The full FMRI for a package consists
-     of the scheme, an authority, the package name, and a version string in
+     of the scheme, a publisher, the package name, and a version string in
      the following format:
 
          pkg://opensolaris.org/library/[email protected],5.11-0.75:20071001T163427Z
 
-     'opensolaris.org' is the authority.  'library/libc' is the package
+     'opensolaris.org' is the publisher.  'library/libc' is the package
      name.  Although the namespace is hierarchical and arbitrarily deep,
      there is no enforced containment -- the name is essentially arbitrary.
 
@@ -53,7 +53,7 @@
 
      Many parts of the system, when appropriate, will contract FMRIs when
      displaying them to reduce the volume of information provided.
-     Typically, the scheme, authority, build version, and timestamp will be
+     Typically, the scheme, publisher, build version, and timestamp will be
      elided, and sometimes the versioning information altogether.
 
   Actions
@@ -350,7 +350,7 @@
   Client-Server Operation
      TBD
 
-  Authorities and Mirroring
+  Publishers and Mirroring
      TBD
 
   Images and Substrates
@@ -371,7 +371,7 @@
 
         send-uuid
             When true, a unique identifier (UUID) that identifies the image
-            to the authority is sent on all requests.  Default value: False 
+            to the publisher is sent on all requests.  Default value: False 
 
 ATTRIBUTES
      See attributes(5) for descriptions of the  following  attri-
--- a/src/modules/actions/depend.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/actions/depend.py	Mon Mar 09 16:09:13 2009 -0500
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 
@@ -51,9 +51,8 @@
         transfer - dependency on minimum version of other package that donated
         components to this package at earlier version.  Other package need not
         be installed, but if it is, it must be at the specified version.  Effect
-        is the same as optional, but the semantics are different.  OpenSolaris 
-        doesn't use these for bundled packages, as incorporations are 
-        preferred.
+        is the same as optional, but the semantics are different.  OpenSolaris
+        doesn't use these for bundled packages, as incorporations are preferred.
 
         incorporate - optional freeze at specified version
 
@@ -73,7 +72,6 @@
                 except ValueError:
                         print "Warning: failed to clean FMRI: %s" % \
                             self.attrs["fmri"]
-                        pass
 
         def clean_fmri(self):
                 """ Clean up an invalid depend fmri into one which
@@ -120,9 +118,9 @@
                 dots = verdots.split(".")
 
                 # Do the correction
-                cleanvers = ".".join([str(int(x)) for x in dots])
+                cleanvers = ".".join([str(int(s)) for s in dots])
 
-                # 
+                #
                 # Next, find the branch if it exists, the first '-'
                 # following the version.
                 #
@@ -147,7 +145,7 @@
                         cleanfmri = fmri_string[:verbegin] + cleanvers + \
                             fmri_string[verend:branchbegin] + cleanbranch + \
                             fmri_string[branchend:]
-                            
+
                 # XXX enable if you need to debug
                 #if cleanfmri != fmri_string:
                 #       print "corrected invalid fmri: %s -> %s" % \
@@ -158,54 +156,55 @@
                 """ returns fmri of incorporation pkg or None if not
                 an incorporation"""
 
-                type = self.attrs["type"]
-                if type != "incorporate":
+                ctype = self.attrs["type"]
+                if ctype != "incorporate":
                         return None
 
                 pkgfmri = self.attrs["fmri"]
                 f = fmri.PkgFmri(pkgfmri, image.attrs["Build-Release"])
-                image.fmri_set_default_authority(f)
-        
+                image.fmri_set_default_publisher(f)
+
                 return f
 
         def parse(self, image, source_name):
                 """decode depend action into fmri & constraint"""
-                type = self.attrs["type"]
+                ctype = self.attrs["type"]
                 fmristr = self.attrs["fmri"]
                 f = fmri.PkgFmri(fmristr, image.attrs["Build-Release"])
                 min_ver = f.version
 
                 if min_ver == None:
-                        min_ver = pkg.version.Version("0", image.attrs["Build-Release"])
+                        min_ver = pkg.version.Version("0",
+                            image.attrs["Build-Release"])
 
                 name = f.get_name()
                 max_ver = None
 
-                if type == "require":
+                if ctype == "require":
                         presence = constraint.Constraint.ALWAYS
-                elif type == "exclude":
+                elif ctype == "exclude":
                         presence = constraint.Constraint.NEVER
-                elif type == "incorporate":
+                elif ctype == "incorporate":
                         presence = constraint.Constraint.MAYBE
                         max_ver = min_ver
-                elif type == "optional":
+                elif ctype == "optional":
                         if image.cfg_cache.get_policy(REQUIRE_OPTIONAL):
                                 presence = constraint.Constraint.ALWAYS
                         else:
-                                presence = constraint.Constraint.MAYBE                        
-                if type == "transfer":
+                                presence = constraint.Constraint.MAYBE
+                elif ctype == "transfer":
                         presence = constraint.Constraint.MAYBE
 
-                return f, constraint.Constraint(name, min_ver, max_ver, 
+                return f, constraint.Constraint(name, min_ver, max_ver,
                     presence, source_name)
 
         def verify(self, image, **args):
                 # XXX Exclude and range between min and max not yet handled
 
-                type = self.attrs["type"]
+                ctype = self.attrs["type"]
 
-                if type not in self.known_types:
-                        return ["Unknown type (%s) in depend action" % type]
+                if ctype not in self.known_types:
+                        return ["Unknown type (%s) in depend action" % ctype]
 
                 pkgfmri = self.attrs["fmri"]
                 f = fmri.PkgFmri(pkgfmri, image.attrs["Build-Release"])
@@ -216,7 +215,7 @@
 
                 if cons.max_ver:
                         max_fmri = min_fmri.copy()
-                        max_fmri.version = cons.max_ver 
+                        max_fmri.version = cons.max_ver
                 else:
                         max_fmri = None
 
@@ -228,22 +227,22 @@
                             min_fmri.version.is_successor(vi,
                             pkg.version.CONSTRAINT_NONE):
                                 return ["%s dependency %s is downrev (%s)" %
-                                    (type, min_fmri, installed_version)]
+                                    (ctype, min_fmri, installed_version)]
                         if max_fmri and vi > max_fmri.version and \
-                            not vi.is_successor(max_fmri.version, 
+                            not vi.is_successor(max_fmri.version,
                             pkg.version.CONSTRAINT_AUTO):
                                 return ["%s dependency %s is uprev (%s)" %
-                                    (type, max_fmri, installed_version)]
+                                    (ctype, max_fmri, installed_version)]
                 elif required:
                         return ["Required dependency %s is not installed" % f]
 
                 return []
 
         def generate_indices(self):
-                type = self.attrs["type"]
-                fmri = self.attrs["fmri"]
+                ctype = self.attrs["type"]
+                pfmri = self.attrs["fmri"]
 
-                if type not in self.known_types:
+                if ctype not in self.known_types:
                         return {}
 
                 #
@@ -254,45 +253,11 @@
                 # manually.
                 #
                 # XXX This code will need to change once we start using fmris
-                # with authorities.
+                # with publishers.
                 #
-                if fmri.startswith("pkg:/"):
-                        fmri = fmri[5:]
+                if pfmri.startswith("pkg:/"):
+                        pfmri = pfmri[5:]
                 # Note that this creates a directory hierarchy!
-                fmri = urllib.quote(fmri, "@").replace("@", "/")
-
-                return {
-                    "depend": fmri
-                }
+                pfmri = urllib.quote(pfmri, "@").replace("@", "/")
 
-if __name__ == "__main__":
-        x = DependencyAction(fmri="pkg:/SUNWpool@1,2-3", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/[email protected]", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/[email protected]", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/[email protected]", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/[email protected],3.4-0.097", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/SUNWpool@0", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/SUNWpool@0-0", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/SUNWpool@0", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/SUNWpool@0-0", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/SUNWpool@", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/SUNWpool@-", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/SUNWpool@-:", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/SUNWpool@1-2:", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/[email protected]", type="require")
-        print x
-        x = DependencyAction(fmri="pkg:/SUNWpool@1,2-", type="require")
-        print x
+                return { "depend": pfmri }
--- a/src/modules/catalog.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/catalog.py	Mon Mar 09 16:09:13 2009 -0500
@@ -82,7 +82,7 @@
 
         S Last-Modified: [timespec]
 
-        XXX A authority mirror-uri ...
+        XXX A publisher mirror-uri ...
         XXX ...
 
         V fmri
@@ -119,21 +119,21 @@
         # spread out into chunks, and may require a delta-oriented update
         # interface.
 
-        def __init__(self, cat_root, authority = None, pkg_root = None,
+        def __init__(self, cat_root, publisher = None, pkg_root = None,
             read_only = False, rebuild = True):
                 """Create a catalog.  If the path supplied does not exist,
                 this will create the required directory structure.
                 Otherwise, if the directories are already in place, the
                 existing catalog is opened.  If pkg_root is specified
                 and no catalog is found at cat_root, the catalog will be
-                rebuilt.  authority names the authority that
+                rebuilt.  publisher names the publisher that
                 is represented by this catalog."""
 
                 self.catalog_root = cat_root
                 self.catalog_file = os.path.normpath(os.path.join(
                     self.catalog_root, "catalog"))
                 self.attrs = {}
-                self.auth = authority
+                self.pub = publisher
                 self.renamed = None
                 self.pkg_root = pkg_root
                 self.read_only = read_only
@@ -268,20 +268,20 @@
                 return ts
 
         @staticmethod
-        def cache_fmri(d, pfmri, auth):
+        def cache_fmri(d, pfmri, pub):
                 """Store the fmri in a data structure 'd' for fast lookup.
 
                 'd' is a dict that maps each package name to another dictionary,
                 itself mapping each version string to a tuple of the fmri object
-                and a list of authorities from which the package version is
+                and a list of publishers from which the package version is
                 available, as well as a special key, "versions", which maps to a
                 list of version objects, kept in sorted order.
 
                     pkg_name1: {
                         "versions": [ <version1>, <version2>, <version3>, ... ],
-                        "version1": ( <fmri1>, [ "auth1", "auth2", ... ],
-                        "version2": ( <fmri2>, [ "auth1", "auth2", ... ],
-                        "version3": ( <fmri3>, [ "auth1", "auth2", ... ],
+                        "version1": ( <fmri1>, [ "pub1", "pub2", ... ],
+                        "version2": ( <fmri2>, [ "pub1", "pub2", ... ],
+                        "version3": ( <fmri3>, [ "pub1", "pub2", ... ],
                         ...
                     },
                     pkg_name2: {
@@ -292,7 +292,7 @@
                 (where names in quotes are strings, names in angle brackets are
                 objects, and the rest of the syntax is Pythonic.
 
-                The fmri is expected not to have an embedded authority.  If it
+                The fmri is expected not to have an embedded publisher.  If it
                 does, it will be ignored."""
 
                 pversion = str(pfmri.version)
@@ -301,17 +301,17 @@
                         # structure.
                         d[pfmri.pkg_name] = {
                             "versions": [ pfmri.version ],
-                            pversion: (pfmri, [ auth ])
+                            pversion: (pfmri, [ pub ])
                         }
                 elif pversion not in d[pfmri.pkg_name]:
-                        d[pfmri.pkg_name][pversion] = (pfmri, [ auth ])
+                        d[pfmri.pkg_name][pversion] = (pfmri, [ pub ])
                         bisect.insort(
                             d[pfmri.pkg_name]["versions"], pfmri.version)
                 else:
-                        d[pfmri.pkg_name][pversion][1].append(auth)
+                        d[pfmri.pkg_name][pversion][1].append(pub)
 
         @staticmethod
-        def read_catalog(catalog, path, auth=None):
+        def read_catalog(catalog, path, pub=None):
                 """Read the catalog file in "path" and combine it with the
                 existing data in "catalog"."""
 
@@ -322,7 +322,7 @@
                                 continue
 
                         f = fmri.PkgFmri(line[6:].replace(" ", "@"))
-                        Catalog.cache_fmri(catalog, f, auth)
+                        Catalog.cache_fmri(catalog, f, pub)
 
                 catf.close()
 
@@ -485,7 +485,7 @@
                                 if pkg == "pkg":
                                         yield fmri.PkgFmri("%s@%s" %
                                             (cat_name, cat_version),
-                                            authority = self.auth)
+                                            publisher = self.pub)
                         except ValueError:
                                 # Handle old two-column catalog file, mostly in
                                 # use on server.  If *this* doesn't work, we
@@ -495,10 +495,10 @@
                                 except ValueError:
                                         raise RuntimeError, \
                                             "corrupt catalog entry for " \
-                                            "authority '%s': %s" % \
-                                            (self.auth, entry)
+                                            "publisher '%s': %s" % \
+                                            (self.pub, entry)
                                 yield fmri.PkgFmri(cat_fmri,
-                                    authority = self.auth)
+                                    publisher = self.pub)
 
                 pfile.close()
 
@@ -596,7 +596,7 @@
                 return self.attrs.get("origin", None)
 
         @classmethod
-        def recv(cls, filep, path, auth=None, content_size=-1):
+        def recv(cls, filep, path, pub=None, content_size=-1):
                 """A static method that takes a file-like object and
                 a path.  This is the other half of catalog.send().  It
                 reads a stream as an incoming catalog and lays it down
@@ -669,9 +669,9 @@
                         os.remove(catpath)
                         raise bad_fmri
 
-                # Write the authority's origin into our attributes
-                if auth:
-                        origstr = "S origin: %s\n" % auth["origin"]
+                # Write the publisher's origin into our attributes
+                if pub:
+                        origstr = "S origin: %s\n" % pub["origin"]
                         attrf.write(origstr)
 
                 attrf.close()
@@ -1030,11 +1030,11 @@
                             fmri.PkgFmri(pattern, "5.11").tuple()
 
         def by_pattern(p):
-                cat_auth, cat_name, cat_version = p.tuple()
+                cat_pub, cat_name, cat_version = p.tuple()
                 for pattern in patterns:
-                        pat_auth, pat_name, pat_version = tuples[pattern]
-                        if (fmri.is_same_authority(pat_auth, cat_auth) or not \
-                            pat_auth) and matcher(cat_name, pat_name):
+                        pat_pub, pat_name, pat_version = tuples[pattern]
+                        if (fmri.is_same_publisher(pat_pub, cat_pub) or not \
+                            pat_pub) and matcher(cat_name, pat_name):
                                 if not pat_version or \
                                     p.version.is_successor(
                                     pat_version, constraint) or \
@@ -1045,8 +1045,8 @@
                                                 else:
                                                         counthash[pattern] = 1
 
-                                        if pat_auth:
-                                                p.set_authority(pat_auth)
+                                        if pat_pub:
+                                                p.set_publisher(pat_pub)
                                         return p
 
         def by_version(p):
--- a/src/modules/client/api.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/client/api.py	Mon Mar 09 16:09:13 2009 -0500
@@ -22,19 +22,25 @@
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 
-import pkg.search_errors as search_errors
+import copy
+import errno
+import os
 import pkg.client.bootenv as bootenv
 import pkg.client.image as image
 import pkg.client.api_errors as api_errors
 import pkg.client.history as history
+import pkg.client.publisher as publisher
+import pkg.fmri as fmri
 import pkg.misc as misc
-import pkg.fmri as fmri
+import pkg.search_errors as search_errors
 from pkg.client.imageplan import EXECUTED_OK
 from pkg.client import global_settings
-
+import simplejson as json
 import threading
+import urllib2
 
-CURRENT_API_VERSION = 10
+CURRENT_API_VERSION = 11
+CURRENT_P5I_VERSION = 1
 
 class ImageInterface(object):
         """This class presents an interface to images that clients may use.
@@ -69,7 +75,7 @@
                 canceled changes. It can raise VersionException and
                 ImageNotFoundException."""
 
-                compatible_versions = set([10])
+                compatible_versions = set([11])
 
                 if version_id not in compatible_versions:
                         raise api_errors.VersionException(CURRENT_API_VERSION,
@@ -101,7 +107,7 @@
         @staticmethod
         def check_be_name(be_name):
                 return bootenv.BootEnv.check_be_name(be_name)
-                
+
         def plan_install(self, pkg_list, filters, refresh_catalogs=True,
             noexecute=False, verbose=False, update_index=True):
                 """Contructs a plan to install the packages provided in
@@ -115,11 +121,10 @@
                 two things. The first is a boolean which tells the client
                 whether there is anything to do. The third is either None, or an
                 exception which indicates partial success. It can raise
-                InvalidCertException, PlanCreationException,
-                NetworkUnavailableException, PermissionsException and
-                InventoryException. The noexecute argument is included for
-                compatibility with operational history. The hope is it can be
-                removed in the future."""
+                PlanCreationException, NetworkUnavailableException,
+                PermissionsException and InventoryException. The noexecute
+                argument is included for compatibility with operational
+                history. The hope is it can be removed in the future."""
 
                 self.__activity_lock.acquire()
                 try:
@@ -128,11 +133,16 @@
                                 raise api_errors.PlanExistsException(
                                     self.plan_type)
                         try:
-                                self.img.history.operation_name = "install"
+                                self.log_operation_start("install")
                                 # Verify validity of certificates before
-                                # attempting network operations
-                                if not self.img.check_cert_validity():
-                                        raise api_errors.InvalidCertException()
+                                # attempting network operations.
+                                try:
+                                        self.img.check_cert_validity()
+                                except api_errors.ExpiringCertificate, e:
+                                        misc.emsg(e)
+                                except api_errors.CertificateError, e:
+                                        self.log_operation_end(error=e)
+                                        raise
 
                                 self.img.load_catalogs(self.progresstracker)
 
@@ -169,32 +179,22 @@
                                     self.img.imageplan)
                                 if self.img.imageplan.nothingtodo() or \
                                     noexecute:
-                                        self.img.history.operation_result = \
-                                            history.RESULT_NOTHING_TO_DO
+                                        self.log_operation_end(
+                                            result=history.RESULT_NOTHING_TO_DO)
                                 self.img.imageplan.update_index = update_index
                                 res = not self.img.imageplan.nothingtodo()
-                        except api_errors.CanceledException:
-                                self.__reset_unlock()
-                                self.img.history.operation_result = \
-                                    history.RESULT_CANCELED
-                                raise
                         except api_errors.PlanCreationException, e:
                                 self.__reset_unlock()
                                 self.__set_history_PlanCreationException(e)
                                 raise
-                        except fmri.IllegalFmri:
+                        except (api_errors.CanceledException, fmri.IllegalFmri,
+                            Exception), e:
                                 self.__reset_unlock()
-                                self.img.history.operation_result = \
-                                    history.RESULT_FAILED_BAD_REQUEST
-                                raise
-                        except Exception:
-                                self.__reset_unlock()
-                                self.img.history.operation_result = \
-                                    history.RESULT_FAILED_UNKNOWN
+                                self.log_operation_end(error=e)
                                 raise
                 finally:
                         self.__activity_lock.release()
-                
+
                 return res, exception_caught
 
 
@@ -217,7 +217,7 @@
                                 raise api_errors.PlanExistsException(
                                     self.plan_type)
                         try:
-                                self.img.history.operation_name = "uninstall"
+                                self.log_operation_start("uninstall")
                                 self.img.load_catalogs(self.progresstracker)
 
                                 self.img.make_uninstall_plan(pkg_list,
@@ -269,7 +269,7 @@
                         self.__activity_lock.release()
 
                 return res
-                
+
         def plan_update_all(self, actual_cmd, refresh_catalogs=True,
             noexecute=False, force=False, verbose=False, update_index=True,
             be_name=None):
@@ -298,7 +298,7 @@
                                 raise api_errors.PlanExistsException(
                                     self.plan_type)
                         try:
-                                self.img.history.operation_name = "image-update"
+                                self.log_operation_start("image-update")
                                 exception_caught = None
                                 if not self.check_be_name(be_name):
                                         raise api_errors.InvalidBENameException(
@@ -306,9 +306,14 @@
                                 self.be_name = be_name
 
                                 # Verify validity of certificates before
-                                # attempting network operations
-                                if not self.img.check_cert_validity():
-                                        raise api_errors.InvalidCertException()
+                                # attempting network operations.
+                                try:
+                                        self.img.check_cert_validity()
+                                except api_errors.ExpiringCertificate, e:
+                                        misc.emsg(e)
+                                except api_errors.CertificateError, e:
+                                        self.log_operation_end(error=e)
+                                        raise
 
                                 self.img.load_catalogs(self.progresstracker)
 
@@ -345,7 +350,8 @@
                                                     actual_cmd,
                                                     self.__check_cancelation,
                                                     noexecute,
-                                                    refresh_catalogs, self.progresstracker):
+                                                    refresh_catalogs,
+                                                    self.progresstracker):
                                                         self.img.history.operation_result = \
                                                             history.RESULT_FAILED_CONSTRAINED
                                                         raise api_errors.IpkgOutOfDateException()
@@ -354,9 +360,9 @@
                                                 # so we proceed
                                                 pass
 
-                                pkg_list = [ 
-                                        ipkg.get_pkg_stem()
-                                        for ipkg in self.img.gen_installed_pkgs() 
+                                pkg_list = [
+                                    ipkg.get_pkg_stem()
+                                    for ipkg in self.img.gen_installed_pkgs()
                                 ]
 
                                 self.img.make_install_plan(pkg_list,
@@ -408,14 +414,14 @@
                                 raise
                 finally:
                         self.__activity_lock.release()
-                
+
                 return res, opensolaris_image, exception_caught
 
         def describe(self):
                 """Returns None if no plan is ready yet, otherwise returns
                 a PlanDescription"""
                 return self.plan_desc
-                
+
         def prepare(self):
                 """Takes care of things which must be done before the plan
                 can be executed. This includes downloading the packages to
@@ -478,7 +484,7 @@
 
                         if self.executed:
                                 raise api_errors.AlreadyExecutedException()
-                        
+
                         assert self.plan_type == self.__INSTALL or \
                             self.plan_type == self.__UNINSTALL or \
                             self.plan_type == self.__IMAGE_UPDATE
@@ -547,39 +553,45 @@
                         self.executed = True
                 finally:
                         self.__activity_lock.release()
-                        
-                
-        def refresh(self, full_refresh, auths=None):
-                """Refreshes the catalogs. full_refresh controls whether to do
-                a full retrieval of the catalog from the authority or only
-                update the existing catalog. auths is a list of authorities to
-                refresh. Passing an empty list or using the default value means
-                all known authorities will be refreshed. While it currently
-                returns an image object, this is an expedient for allowing
-                existing code to work while the rest of the API is put into
-                place."""
-                
+
+
+        def refresh(self, full_refresh, pubs=None):
+                """Refreshes the metadata for a publisher (e.g. catalog).
+
+                'full_refresh' is a boolean value indicating whether a full
+                retrieval of the catalog or only an update to the existing
+                catalog should be performed.
+
+                'pubs' is a list of prefixes of publishers to refresh.  Passing
+                an empty list or using the default value means all known
+                publishers will be refreshed.
+
+                Currently returns an image object, allowing existing code to
+                work while the rest of the API is put into place."""
+
+                self.log_operation_start("refresh-publisher")
                 self.__activity_lock.acquire()
                 self.__set_can_be_canceled(False)
                 try:
                         # Verify validity of certificates before attempting
-                        # network operations
-                        if not self.img.check_cert_validity():
-                                raise api_errors.InvalidCertException()
+                        # network operations.
+                        try:
+                                self.img.check_cert_validity()
+                        except api_errors.ExpiringCertificate, e:
+                                misc.emsg(e)
+
+                        pubs_to_refresh = []
 
-                        auths_to_refresh = []
-                        
-                        if not auths:
-                                auths = []
-                        for auth in auths:
-                                try:
-                                        a = self.img.get_authority(auth)
-                                except KeyError:
-                                        raise api_errors.UnrecognizedAuthorityException(auth)
-                                if a["disabled"]:
-                                        raise api_errors.UnrecognizedAuthorityException(auth)
-                                auths_to_refresh.append(a)
-
+                        if not pubs:
+                                # Omit disabled publishers.
+                                pubs = [p for p in self.img.gen_publishers()]
+                        for pub in pubs:
+                                p = pub
+                                if not isinstance(p, publisher.Publisher):
+                                        p = self.img.get_publisher(prefix=pub)
+                                if p.disabled:
+                                        raise api_errors.DisabledPublisher(pub)
+                                pubs_to_refresh.append(p)
 
                         # Ensure Image directory structure is valid.
                         self.img.mkdirs()
@@ -589,12 +601,13 @@
                         self.img.load_catalogs(self.progresstracker)
 
                         self.img.retrieve_catalogs(full_refresh,
-                            auths_to_refresh)
+                            pubs_to_refresh)
 
                         return self.img
-                        
+
                 finally:
                         self.__activity_lock.release()
+                        self.log_operation_end()
 
         def __licenses(self, mfst, local):
                 """Private function. Returns the license info from the
@@ -616,27 +629,25 @@
                 return license_lst
 
         def info(self, fmri_strings, local, info_needed):
-                """Gathers information about fmris. fmri_strings is a list
-                of fmri_names for which information is desired. local
-                determines whether to retrieve the information locally.
-                get_licenses determines whether to retrieve the text of
-                the licenses. It returns a dictionary of lists. The keys
-                for the dictionary are the constants specified in the class
-                definition. The values are lists of PackageInfo objects or
-                strings."""
+                """Gathers information about fmris.  fmri_strings is a list
+                of fmri_names for which information is desired.  local
+                determines whether to retrieve the information locally.  It
+                returns a dictionary of lists.  The keys for the dictionary are
+                the constants specified in the class definition.  The values are
+                lists of PackageInfo objects or strings."""
 
                 bad_opts = info_needed - PackageInfo.ALL_OPTIONS
                 if bad_opts:
                         raise api_errors.UnrecognizedOptionsToInfo(bad_opts)
-                
-                self.img.history.operation_name = "info"
+
+                self.log_operation_start("info")
                 self.img.load_catalogs(self.progresstracker)
 
                 fmris = []
                 notfound = []
                 multiple_matches = []
                 illegals = []
-                
+
                 if local:
                         fmris, notfound, illegals = \
                             self.img.installed_fmris_from_args(fmri_strings)
@@ -646,12 +657,15 @@
                                 raise api_errors.NoPackagesInstalledException()
                 else:
                         # Verify validity of certificates before attempting
-                        # network operations
-                        if not self.img.check_cert_validity():
-                                self.img.history.operation_result = \
-                                    history.RESULT_FAILED_TRANSPORT
-                                raise api_errors.InvalidCertException()
-                        
+                        # network operations.
+                        try:
+                                self.img.check_cert_validity()
+                        except api_errors.ExpiringCertificate, e:
+                                misc.emsg(e)
+                        except api_errors.CertificateError, e:
+                                self.log_operation_end(error=e)
+                                raise
+
                         # XXX This loop really needs not to be copied from
                         # Image.make_install_plan()!
                         for p in fmri_strings:
@@ -673,7 +687,7 @@
                                 npnames = {}
                                 npmatch = []
                                 for m, state in matches:
-                                        if m.preferred_authority():
+                                        if m.preferred_publisher():
                                                 pnames[m.get_pkg_stem()] = 1
                                                 pmatch.append(m)
                                         else:
@@ -702,20 +716,20 @@
                 pis = []
 
                 for f in fmris:
-                        authority = name = version = release = None
+                        pub = name = version = release = None
                         build_release = branch = packaging_date = None
                         if PackageInfo.IDENTITY in info_needed:
-                                authority, name, version = f.tuple()
-                                authority = fmri.strip_auth_pfx(authority)
-                                release=version.release
-                                build_release=version.build_release
-                                branch=version.branch
-                                packaging_date=version.get_timestamp().ctime()
-                        pref_auth = None
+                                pub, name, version = f.tuple()
+                                pub = fmri.strip_pub_pfx(pub)
+                                release = version.release
+                                build_release = version.build_release
+                                branch = version.branch
+                                packaging_date = version.get_timestamp().ctime()
+                        pref_pub = None
                         if PackageInfo.PREF_AUTHORITY in info_needed:
-                                pref_auth = False
-                                if f.preferred_authority():
-                                        pref_auth = True
+                                pref_pub = False
+                                if f.preferred_publisher():
+                                        pref_pub = True
                         state = None
                         if PackageInfo.STATE in info_needed:
                                 if self.img.is_installed(f):
@@ -772,7 +786,7 @@
 
                         pis.append(PackageInfo(pkg_stem=name, summary=summary,
                             category_info_list=cat_info, state=state,
-                            authority=authority, preferred_authority=pref_auth,
+                            publisher=pub, preferred_publisher=pref_pub,
                             version=release, build_release=build_release,
                             branch=branch, packaging_date=packaging_date,
                             size=size, pfmri=str(f), licenses=licenses,
@@ -793,7 +807,7 @@
                     self.INFO_MULTI_MATCH: multiple_matches,
                     self.INFO_ILLEGALS: illegals
                 }
-                        
+
         def can_be_canceled(self):
                 """Returns true if the API is in a cancelable state."""
                 return self.__can_be_canceled
@@ -834,7 +848,7 @@
                 code to use to determine whether the current action has been
                 canceled."""
                 return self.__canceling
-                
+
         def cancel(self):
                 """Used for asynchronous cancelation. It returns the API
                 to the state it was in prior to the current method being
@@ -860,14 +874,403 @@
         def __set_history_PlanCreationException(self, e):
                 if e.unfound_fmris or e.multiple_matches or \
                     e.missing_matches or e.illegal:
-                        self.img.history.operation_result = \
-                            history.RESULT_FAILED_BAD_REQUEST
+                        self.log_operation_end(error=e,
+                            result=history.RESULT_FAILED_BAD_REQUEST)
                 elif e.constraint_violations:
-                        self.img.history.operation_result = \
-                            history.RESULT_FAILED_CONSTRAINED
+                        self.log_operation_end(error=e,
+                            result=history.RESULT_FAILED_CONSTRAINED)
+                else:
+                        self.log_operation_end(error=e)
+
+        def add_publisher(self, pub, refresh_allowed=True):
+                """Add the provided publisher object to the image
+                configuration."""
+                self.img.add_publisher(pub, refresh_allowed=refresh_allowed)
+
+        def get_preferred_publisher(self):
+                """Returns the preferred publisher object for the image."""
+                return self.get_publisher(
+                    prefix=self.img.get_preferred_publisher())
+
+        def get_publisher(self, prefix=None, alias=None, duplicate=False):
+                """Retrieves a publisher object matching the provided prefix
+                (name) or alias.
+
+                'duplicate' is an optional boolean value indicating whether
+                a copy of the publisher object should be returned instead
+                of the original.
+                """
+                pub = self.img.get_publisher(prefix=prefix, alias=alias)
+                if duplicate:
+                        # Never return the original so that changes to the
+                        # retrieved object are not reflected until
+                        # update_publisher is called.
+                        return copy.copy(pub)
+                return pub
+
+        def get_publishers(self, duplicate=False):
+                """Returns a list of the publisher objects for the current
+                image.
+
+                'duplicate' is an optional boolean value indicating whether
+                copies of the publisher objects should be returned instead
+                of the originals.
+                """
+                if duplicate:
+                        # Return a copy so that changes to the retrieved objects
+                        # are not reflected until update_publisher is called.
+                        pubs = [
+                            copy.copy(p)
+                            for p in self.img.get_publishers().values()
+                        ]
                 else:
-                        self.img.history.operation_result = \
-                            history.RESULT_FAILED_UNKNOWN
+                        pubs = self.img.get_publishers().values()
+                return misc.get_sorted_publishers(pubs,
+                    preferred=self.img.get_preferred_publisher())
+
+        def get_publisher_last_update_time(self, prefix=None, alias=None):
+                """Returns a datetime object representing the last time the
+                catalog for a publisher was modified or None."""
+                if alias:
+                        prefix = self.get_publisher(alias=alias).prefix
+                dt = None
+                self.__activity_lock.acquire()
+                try:
+                        self.__set_can_be_canceled(True)
+                        try:
+                                dt = self.img.get_publisher_last_update_time(
+                                    prefix)
+                        except api_errors.CanceledException:
+                                self.__reset_unlock()
+                                raise
+                        except Exception:
+                                self.__reset_unlock()
+                                raise
+                finally:
+                        self.__activity_lock.release()
+                return dt
+
+        def has_publisher(self, prefix=None, alias=None):
+                """Retrieves a publisher object matching the provided prefix
+                (name) or alias."""
+                return self.img.has_publisher(prefix=prefix, alias=alias)
+
+        def remove_publisher(self, prefix=None, alias=None):
+                """Removes a publisher object matching the provided prefix
+                (name) or alias."""
+                self.img.remove_publisher(prefix=prefix, alias=alias)
+
+        def set_preferred_publisher(self, prefix=None, alias=None):
+                """Sets the preferred publisher for the image."""
+                self.img.set_preferred_publisher(prefix=prefix, alias=alias)
+
+        def update_publisher(self, pub, refresh_allowed=True):
+                """Replaces an existing publisher object with the provided one
+                using the _source_object_id identifier set during copy.
+
+                'refresh_allowed' is an optional boolean value indicating
+                whether a refresh of publisher metadata (such as its catalog)
+                should be performed if transport information is changed for a
+                repository, mirror, or origin.  If False, no attempt will be
+                made to retrieve publisher metadata."""
+
+                self.log_operation_start("update-publisher")
+
+                if pub.disabled and \
+                    pub.prefix == self.img.get_preferred_publisher():
+                        raise api_errors.SetPreferredPublisherDisabled(
+                            pub.prefix)
+
+                refresh_catalog = False
+                purge_catalog = False
+
+                def need_refresh(oldo, newo):
+                        if oldo.disabled and not newo.disabled:
+                                # The publisher has been re-enabled, so
+                                # retrieve the catalog.
+                                return True
+
+                        if len(newo.repositories) != len(oldo.repositories):
+                                # If there are an unequal number of repositories
+                                # then some have been added or removed.
+                                return True
+
+                        matched = 0
+                        for oldr in oldo.repositories:
+                                for newr in newo.repositories:
+                                        if newr._source_object_id == id(oldr):
+                                                matched += 1
+                                                if oldr.origins != newr.origins:
+                                                        return True
+
+                        if matched != len(newo.repositories):
+                                # If not all of the repositories match up, then
+                                # one has been removed or added, or an origin
+                                # URI has changed.
+                                return True
+                        return False
+
+                updated = False
+                publishers = self.img.get_publishers()
+                for key, old in publishers.iteritems():
+                        if pub._source_object_id == id(old):
+                                if need_refresh(old, pub):
+                                        refresh_catalog = True
+                                elif pub.disabled:
+                                        purge_catalog = True
+                                del publishers[key]
+                                publishers[pub.prefix] = pub
+                                updated = True
+
+                if not updated:
+                        # If a matching publisher couldn't be found and
+                        # replaced, something is wrong (client api usage
+                        # error).
+                        e = api_errors.UnknownPublisher(pub)
+                        self.log_operation_end(e)
+                        raise e
+
+                if refresh_allowed and not refresh_catalog:
+                        # If the publisher's catalog is missing, retrieve it.
+                        refresh_catalog = not self.img.has_catalog(pub.prefix)
+
+                if refresh_catalog or purge_catalog:
+                        try:
+                                self.img.destroy_catalog(pub)
+                                self.img.destroy_catalog_cache()
+                        except EnvironmentError, e:
+                                if e.errno == errno.EACCES:
+                                        raise api_errors.PermissionsException(
+                                            e.filename)
+                                raise
+
+                        if purge_catalog:
+                                self.img.cache_catalogs()
+                        elif refresh_allowed:
+                                self.refresh(True, pubs=[pub])
+
+                # Successful refresh or purge has happened, so save
+                # final configuration.
+                self.img.save_config()
+                self.log_operation_end()
+                return
+
+        def log_operation_end(self, error=None, result=None):
+                """Marks the end of an operation to be recorded in image
+                history.
+
+                'result' should be a pkg.client.history constant value
+                representing the outcome of an operation.  If not provided,
+                and 'error' is provided, the final result of the operation will
+                be based on the class of 'error' and 'error' will be recorded
+                for the current operation.  If 'result' and 'error' is not
+                provided, success is assumed."""
+                self.img.history.log_operation_end(error=error, result=result)
+
+        def log_operation_error(self, error):
+                """Adds an error to the list of errors to be recorded in image
+                history for the current opreation."""
+                self.img.history.log_operation_error(error)
+
+        def log_operation_start(self, name):
+                """Marks the start of an operation to be recorded in image
+                history."""
+                self.img.history.log_operation_start(name)
+
+        def parse_p5i(self, fileobj=None, location=None):
+                """Reads the pkg(5) publisher json formatted data at 'location'
+                or from the provided file-like object 'fileobj' and returns a
+                list of tuples of the format (publisher object, pkg_names).
+                pkg_names is a list of strings representing package names or
+                FMRIs.  If any pkg_names not specific to a publisher were
+                provided, the last tuple returned will be of the format (None,
+                pkg_names).
+
+                'fileobj' is an optional file-like object that must support a
+                'read' method for retrieving data.
+
+                'location' is an optional string value that should either start
+                with a leading slash and be pathname of a file or a URI string.
+                If it is a URI string, supported protocol schemes are 'file',
+                'ftp', 'http', and 'https'.
+
+                'fileobj' or 'location' must be provided."""
+
+                if location is None and fileobj is None:
+                        raise api_errors.InvalidResourceLocation(location)
+
+                if location:
+                        if location.startswith(os.path.sep):
+                                location = os.path.abspath(location)
+                                location = "file://" + location
+
+                        try:
+                                fileobj = urllib2.urlopen(location)
+                        except (EnvironmentError, ValueError,
+                            urllib2.HTTPError), e:
+                                raise api_errors.RetrievalError(e,
+                                    location=location)
+
+                try:
+                        dump_struct = json.load(fileobj)
+                except (EnvironmentError, urllib2.HTTPError), e:
+                        raise api_errors.RetrievalError(e)
+                except ValueError, e:
+                        # Not a valid json file.
+                        raise api_errors.InvalidP5IFile(e)
+
+                try:
+                        ver = int(dump_struct["version"])
+                except KeyError:
+                        raise api_errors.InvalidP5IFile(_("missing version"))
+                except ValueError:
+                        raise api_errors.InvalidP5IFile(_("invalid version"))
+
+                if ver > CURRENT_P5I_VERSION:
+                        raise api_errors.UnsupportedP5IFile()
+
+                result = []
+                try:
+                        plist = dump_struct.get("publishers", [])
+
+                        for p in plist:
+                                alias = p.get("alias", None)
+                                prefix = p.get("name", None)
+
+                                if not prefix:
+                                        prefix = "Unknown"
+
+                                pub = publisher.Publisher(prefix, alias=alias)
+                                pkglist = p.get("packages", [])
+                                result.append((pub, pkglist))
+
+                                for r in p.get("repositories", []):
+                                        rargs = {}
+                                        for prop in ("collection_type",
+                                            "description", "name",
+                                            "refresh_seconds",
+                                            "registration_uri"):
+                                                val = r.get(prop, None)
+                                                if val is None or val == "None":
+                                                        continue
+                                                rargs[prop] = val
+
+                                        for prop in ("legal_uris", "mirrors",
+                                            "origins", "related_uris"):
+                                                val = r.get(prop, [])
+                                                if not isinstance(val, list):
+                                                        continue
+                                                rargs[prop] = val
+
+                                        if rargs.get("origins", None):
+                                                repo = publisher.Repository(
+                                                    **rargs)
+                                                pub.add_repository(repo)
+
+                        pkglist = dump_struct.get("packages", [])
+                        if pkglist:
+                                result.append((None, pkglist))
+                except (api_errors.PublisherError, TypeError, ValueError), e:
+                        raise api_errors.InvalidP5IFile(str(e))
+                return result
+
+        def write_p5i(self, fileobj, pkg_names=None, pubs=None):
+                """Writes the publisher, repository, and provided package names
+                to the provided file-like object 'fileobj' in json p5i format.
+
+                'fileobj' is only required to have a 'write' method that accepts
+                data to be written as a parameter.
+
+                'pkg_names' is a dict of lists, tuples, or sets indexed by
+                publisher prefix that contain package names, FMRI strings, or
+                package info objects.  A prefix of "" can be used for packages
+                that are not specific to a publisher.
+
+                'pubs' is an optional list of publisher prefixes or Publisher
+                objects.  If not provided, the information for all publishers
+                (excluding those disabled) will be output."""
+
+                dump_struct = {
+                    "packages": [],
+                    "publishers": [],
+                    "version": CURRENT_P5I_VERSION,
+                }
+
+                if not pubs:
+                        plist = [
+                            p for p in self.get_publishers()
+                            if not p.disabled
+                        ]
+                else:
+                        plist = []
+                        for p in pubs:
+                                if not isinstance(p, publisher.Publisher):
+                                        plist.append(self.img.get_publisher(
+                                            prefix=p, alias=p))
+                                else:
+                                        plist.append(p)
+
+                if pkg_names is None:
+                        pkg_names = {}
+
+                def copy_pkg_names(source, dest):
+                        for entry in source:
+                                # Publisher information is intentionally
+                                # omitted as association with this specific
+                                # publisher is implied by location in the
+                                # output.
+                                if isinstance(entry, PackageInfo):
+                                        dest.append(entry.fmri.get_fmri(
+                                            anarchy=True))
+                                elif isinstance(entry, fmri.PkgFmri):
+                                        dest.append(entry.get_fmri(
+                                            anarchy=True))
+                                else:
+                                        dest.append(str(entry))
+
+                dpubs = dump_struct["publishers"]
+                for p in plist:
+                        dpub = {
+                            "alias": p.alias,
+                            "name": p.prefix,
+                            "packages": [],
+                            "repositories": []
+                        }
+                        dpubs.append(dpub)
+
+                        try:
+                                copy_pkg_names(pkg_names[p.prefix],
+                                    dpub["packages"])
+                        except KeyError:
+                                pass
+
+                        drepos = dpub["repositories"]
+                        for r in p.repositories:
+                                reg_uri = ""
+                                if r.registration_uri:
+                                        reg_uri = r.registration_uri.uri
+
+                                drepos.append({
+                                    "collection_type": r.collection_type,
+                                    "description": r.description,
+                                    "legal_uris": [u.uri for u in r.legal_uris],
+                                    "mirrors": [u.uri for u in r.mirrors],
+                                    "name": r.name,
+                                    "origins": [u.uri for u in r.origins],
+                                    "refresh_seconds": r.refresh_seconds,
+                                    "registration_uri": reg_uri,
+                                    "related_uris": [
+                                        u.uri for u in r.related_uris
+                                    ],
+                                })
+
+                try:
+                        copy_pkg_names(pkg_names[""], dump_struct["packages"])
+                except KeyError:
+                        pass
+
+                return json.dump(dump_struct, fileobj, ensure_ascii=False,
+                    allow_nan=False, indent=2, sort_keys=True)
 
 
 class PlanDescription(object):
@@ -907,7 +1310,7 @@
                         return "%s (%s)" % (self.category, self.scheme)
                 else:
                         return "%s" % self.category
-        
+
 class PackageInfo(object):
         """A class capturing the information about packages that a client
         could need. The fmri is guaranteed to be set. All other values may
@@ -918,16 +1321,17 @@
         NOT_INSTALLED = 2
 
 
+
         __NUM_PROPS = 12
         IDENTITY, SUMMARY, CATEGORIES, STATE, PREF_AUTHORITY, SIZE, LICENSES, \
             LINKS, HARDLINKS, FILES, DIRS, DEPENDENCIES = range(__NUM_PROPS)
         ALL_OPTIONS = frozenset(range(__NUM_PROPS))
         ACTION_OPTIONS = frozenset([LINKS, HARDLINKS, FILES, DIRS,
             DEPENDENCIES])
-        
+
         def __init__(self, pfmri, pkg_stem=None, summary=None,
-            category_info_list=None, state=None, authority=None,
-            preferred_authority=None, version=None, build_release=None,
+            category_info_list=None, state=None, publisher=None,
+            preferred_publisher=None, version=None, build_release=None,
             branch=None, packaging_date=None, size=None, licenses=None,
             links=None, hardlinks=None, files=None, dirs=None,
             dependencies=None):
@@ -937,8 +1341,8 @@
                         category_info_list = []
                 self.category_info_list = category_info_list
                 self.state = state
-                self.authority = authority
-                self.preferred_authority = preferred_authority
+                self.publisher = publisher
+                self.preferred_publisher = preferred_publisher
                 self.version = version
                 self.build_release = build_release
                 self.branch = branch
@@ -959,9 +1363,10 @@
         def build_from_fmri(f):
                 if not f:
                         return f
-                authority, name, version = f.tuple()
-                authority = fmri.strip_auth_pfx(authority)
-                return PackageInfo(pkg_stem=name, authority=authority,
+                pub, name, version = f.tuple()
+                pub = fmri.strip_pub_pfx(pub)
+                return PackageInfo(pkg_stem=name, publisher=pub,
                     version=version.release,
                     build_release=version.build_release, branch=version.branch,
-                    packaging_date=version.get_timestamp().ctime(), pfmri=str(f))
+                    packaging_date=version.get_timestamp().ctime(),
+                    pfmri=str(f))
--- a/src/modules/client/api_errors.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/client/api_errors.py	Mon Mar 09 16:09:13 2009 -0500
@@ -19,10 +19,17 @@
 #
 # CDDL HEADER END
 #
+
+#
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
-from pkg.misc import EmptyI
+import urlparse
+
+# EmptyI for argument defaults; can't import from misc due to circular
+# dependency.
+EmptyI = tuple()
 
 class ApiException(Exception):
         pass
@@ -49,9 +56,6 @@
                 self.expected_version = expected_version
                 self.received_version = received_version
 
-class InvalidCertException(ApiException):
-        pass
-
 class PlanExistsException(ApiException):
         def __init__(self, plan_type):
                 ApiException.__init__(self)
@@ -83,11 +87,6 @@
 class PlanMissingException(ApiException):
         pass
 
-class UnrecognizedAuthorityException(ApiException):
-        def __init__(self, auth):
-                ApiException.__init__(self)
-                self.auth = auth
-
 class NoPackagesInstalledException(ApiException):
         pass
 
@@ -98,13 +97,14 @@
 
         def __str__(self):
                 if self.path:
-                        return _("Could not operate on %s\nbecause of insufficient "
-                                "permissions. Please try the command again using pfexec\n"
-                                "or otherwise increase your permissions.") % self.path
+                        return _("Could not operate on %s\nbecause of "
+                            "insufficient permissions. Please try the command "
+                            "again using pfexec\nor otherwise increase your "
+                            "privileges.") % self.path
                 else:
                         return _("""
 Could not complete the operation because of insufficient permissions. Please
-try the command again using pfexec or otherwise increase your permissions.
+try the command again using pfexec or otherwise increase your privileges.
 """)
 
 class FileInUseException(PermissionsException):
@@ -119,8 +119,8 @@
 
 class PlanCreationException(ApiException):
         def __init__(self, unfound_fmris=EmptyI, multiple_matches=EmptyI,
-            missing_matches=EmptyI, illegal=EmptyI, constraint_violations=EmptyI, 
-            badarch=EmptyI):
+            missing_matches=EmptyI, illegal=EmptyI,
+            constraint_violations=EmptyI, badarch=EmptyI):
                 ApiException.__init__(self)
                 self.unfound_fmris         = unfound_fmris
                 self.multiple_matches      = multiple_matches
@@ -135,8 +135,8 @@
                         s = _("""\
 pkg: The following pattern(s) did not match any packages in the current
 catalog. Try relaxing the pattern, refreshing and/or examining the catalogs""")
-                        res += [ s ]
-                        res += [ "\t%s" % p for p in self.unfound_fmris ]
+                        res += [s]
+                        res += ["\t%s" % p for p in self.unfound_fmris]
 
                 if self.multiple_matches:
                         s = _("pkg: '%s' matches multiple packages")
@@ -152,9 +152,10 @@
                 res += [ s % p for p in self.illegal ]
 
                 if self.constraint_violations:
-                        s = _("pkg: the following package(s) violated constraints:")
-                        res += [ s ] 
-                        res += [ "\t%s" % p for p in self.constraint_violations ]
+                        s = _("pkg: the following package(s) violated "
+                            "constraints:")
+                        res += [s] 
+                        res += ["\t%s" % p for p in self.constraint_violations]
 
                 if self.badarch:
                         s = _("'%s' supports the following architectures: %s")
@@ -189,7 +190,7 @@
                 if self.notfound:
                         outstr += _("No matching package could be found for "
                             "the following FMRIs in any of the catalogs for "
-                            "the current authorities:\n")
+                            "the current publishers:\n")
 
                         for x in self.notfound:
                                 outstr += "%s\n" % x
@@ -235,6 +236,7 @@
         """Raised when the depot doesn't have versions of operations
         that the client needs to operate successfully."""
         def __init__(self, url, data):
+                ApiException.__init__(self)
                 self.url = url
                 self.data = data
 
@@ -251,6 +253,76 @@
         def __init__(self):
                 ApiException.__init__(self)
 
+
+class DataError(ApiException):
+        """Base exception class used for all data related errors."""
+
+        def __init__(self, *args, **kwargs):
+                ApiException.__init__(self, *args)
+                if args:
+                        self.data = args[0]
+                else:
+                        self.data = None
+                self.args = kwargs
+
+
+class InvalidP5IFile(DataError):
+        """Used to indicate that the specified location does not contain a
+        valid p5i-formatted file."""
+
+        def __str__(self):
+                if self.data:
+                        return _("The specified file is in an unrecognized "
+                            "format or does not contain valid publisher "
+                            "information: %s") % self.data
+                return _("The specified file is in an unrecognized format or "
+                    "does not contain valid publisher information.")
+
+
+class UnsupportedP5IFile(DataError):
+        """Used to indicate that an attempt to read an unsupported version
+        of pkg(5) info file was attempted."""
+
+        def __str__(self):
+                return _("Unsupported pkg(5) publisher information data "
+                    "format.")
+
+
+class TransportError(ApiException):
+        """Base exception class for all transfer exceptions."""
+
+        def __init__(self, *args, **kwargs):
+                ApiException.__init__(self, *args)
+                if args:
+                        self.data = args[0]
+                else:
+                        self.data = None
+                self.args = kwargs
+
+        def __str__(self):
+                return str(self.data)
+
+
+class RetrievalError(TransportError):
+        """Used to indicate that a a requested resource could not be
+        retrieved."""
+
+        def __str__(self):
+                location = self.args.get("location", None)
+                if location:
+                        return _("Error encountered while retrieving data from "
+                            "'%s':\n%s") % (location, self.data)
+                return _("Error encountered while retrieving data from: %s") % \
+                    self.data
+
+
+class InvalidResourceLocation(TransportError):
+        """Used to indicate that an invalid transport location was provided."""
+
+        def __str__(self):
+                return _("'%s' is not a valid location.") % self.data
+
+
 class InvalidBENameException(BEException):
         def __init__(self, be_name):
                 BEException.__init__(self)
@@ -308,7 +380,8 @@
                 return _("""\
 Naming a boot environment when operating on a non-live image is
 not allowed.""")
-                         
+
+
 class UnrecognizedOptionsToInfo(ApiException):
         def __init__(self, opts):
                 ApiException.__init__(self)
@@ -319,3 +392,352 @@
                 for o in self._opts:
                         s += _(" '") + str(o) + _("'")
                 return s
+
+
+class PublisherError(ApiException):
+        """Base exception class for all publisher exceptions."""
+
+        def __init__(self, *args, **kwargs):
+                ApiException.__init__(self, *args)
+                if args:
+                        self.data = args[0]
+                else:
+                        self.data = None
+                self.args = kwargs
+
+        def __str__(self):
+                return str(self.data)
+
+
+class BadPublisherPrefix(PublisherError):
+        """Used to indicate that a publisher name is not valid."""
+
+        def __str__(self):
+                return _("'%s' is not a valid publisher name.") % self.data
+
+
+class BadRepositoryAttributeValue(PublisherError):
+        """Used to indicate that the specified repository attribute value is
+        invalid."""
+
+        def __str__(self):
+                return _("'%(value)s' is not a valid value for repository "
+                    "attribute '%(attribute)s'.") % {
+                    "value": self.args["value"], "attribute": self.data }
+
+
+class BadRepositoryCollectionType(PublisherError):
+        """Used to indicate that the specified repository collection type is
+        invalid."""
+
+        def __init__(self, *args, **kwargs):
+                PublisherError.__init__(self, *args, **kwargs)
+
+        def __str__(self):
+                return _("'%s' is not a valid repository collection type.") % \
+                    self.data
+
+
+class BadRepositoryURI(PublisherError):
+        """Used to indicate that a repository URI is not syntactically valid."""
+
+        def __str__(self):
+                return _("'%s' is not a valid URI.") % self.data
+
+
+class BadRepositoryURIPriority(PublisherError):
+        """Used to indicate that the priority specified for a repository URI is
+        not valid."""
+
+        def __str__(self):
+                return _("'%s' is not a valid URI priority; integer value "
+                    "expected.") % self.data
+
+
+class BadRepositoryURISortPolicy(PublisherError):
+        """Used to indicate that the specified repository URI sort policy is
+        invalid."""
+
+        def __init__(self, *args, **kwargs):
+                PublisherError.__init__(self, *args, **kwargs)
+
+        def __str__(self):
+                return _("'%s' is not a valid repository URI sort policy.") % \
+                    self.data
+
+
+class DisabledPublisher(PublisherError):
+        """Used to indicate that an attempt to use a disabled publisher occurred
+        during an operation."""
+
+        def __str__(self):
+                return _("Publisher '%s' is disabled and cannot be used for "
+                    "packaging operations.") % self.data
+
+
+class DuplicatePublisher(PublisherError):
+        """Used to indicate that a publisher with the same name or alias already
+        exists for an image."""
+
+        def __str__(self):
+                return _("A publisher with the same name or alias as '%s' "
+                    "already exists.") % self.data
+
+
+class DuplicateRepository(PublisherError):
+        """Used to indicate that a repository with the same origin uris
+        already exists for a publisher."""
+
+        def __str__(self):
+                return _("A repository with the same name or origin URIs "
+                   "already exists for publisher '%s'.") % self.data
+
+
+class DuplicateRepositoryMirror(PublisherError):
+        """Used to indicate that a repository URI is already in use by another
+        repository mirror."""
+
+        def __str__(self):
+                return _("Mirror '%s' already exists for the specified "
+                    "repository.") % self.data
+
+
+class DuplicateRepositoryOrigin(PublisherError):
+        """Used to indicate that a repository URI is already in use by another
+        repository origin."""
+
+        def __str__(self):
+                return _("Origin '%s' already exists for the specified "
+                    "repository.") % self.data
+
+
+class RemovePreferredPublisher(PublisherError):
+        """Used to indicate an attempt to remove the preferred publisher was
+        made."""
+
+        def __str__(self):
+                return _("The preferred publisher cannot be removed.")
+
+
+class SelectedRepositoryRemoval(PublisherError):
+        """Used to indicate that an attempt to remove the selected repository
+        for a publisher was made."""
+
+        def __str__(self):
+                return _("Cannot remove the selected repository for a "
+                    "publisher.")
+
+
+class SetPreferredPublisherDisabled(PublisherError):
+        """Used to indicate an attempt to set a disabled publisher as the
+        preferred publisher was made."""
+
+        def __str__(self):
+                return _("Publisher '%(pub)s' is disabled and cannot be set as "
+                    "the preferred publisher.") % self.data
+
+
+class UnknownLegalURI(PublisherError):
+        """Used to indicate that no matching legal URI could be found using the
+        provided criteria."""
+
+        def __str__(self):
+                return _("Unknown legal URI '%s'.") % self.data
+
+
+class UnknownPublisher(PublisherError):
+        """Used to indicate that no matching publisher could be found using the
+        provided criteria."""
+
+        def __str__(self):
+                return _("Unknown publisher '%s'.") % self.data
+
+
+class UnknownRelatedURI(PublisherError):
+        """Used to indicate that no matching related URI could be found using
+        the provided criteria."""
+
+        def __str__(self):
+                return _("Unknown related URI '%s'.") % self.data
+
+
+class UnknownRepository(PublisherError):
+        """Used to indicate that no matching repository could be found using the
+        provided criteria."""
+
+        def __str__(self):
+                return _("Unknown repository '%s'.") % self.data
+
+
+class UnknownRepositoryMirror(PublisherError):
+        """Used to indicate that a repository URI could not be found in the
+        list of repository mirrors."""
+
+        def __str__(self):
+                return _("Unknown repository mirror '%s'.") % self.data
+
+
+class UnknownRepositoryOrigin(PublisherError):
+        """Used to indicate that a repository URI could not be found in the
+        list of repository origins."""
+
+        def __str__(self):
+                return _("Unknown repository origin '%s'") % self.data
+
+
+class UnsupportedRepositoryURI(PublisherError):
+        """Used to indicate that the specified repository URI uses an
+        unsupported scheme."""
+
+        def __str__(self):
+                if self.data:
+                        scheme = urlparse.urlsplit(self.data,
+                            allow_fragments=0)[0]
+                        return _("The URI '%(uri)s' contains an unsupported "
+                            "scheme '%(scheme)s'.") % { "uri": self.data,
+                            "scheme": scheme }
+                return _("The specified URI contains an unsupported scheme.")
+
+
+class UnsupportedRepositoryURIAttribute(PublisherError):
+        """Used to indicate that the specified repository URI attribute is not
+        supported for the URI's scheme."""
+
+        def __str__(self):
+                return _("'%(attr)s' is not supported for '%(scheme)s'.") % {
+                    "attr": self.data, "scheme": self.args["scheme"] }
+
+
+class CertificateError(ApiException):
+        """Base exception class for all certificate exceptions."""
+
+        def __init__(self, *args, **kwargs):
+                ApiException.__init__(self, *args)
+                if args:
+                        self.data = args[0]
+                else:
+                        self.data = None
+                self.args = kwargs
+
+        def __str__(self):
+                return str(self.data)
+
+
+class ExpiredCertificate(CertificateError):
+        """Used to indicate that a certificate has expired."""
+
+        def __str__(self):
+                publisher = self.args.get("publisher", None)
+                uri = self.args.get("uri", None)
+                if publisher:
+                        if uri:
+                                return _("Certificate '%(cert)s' for publisher "
+                                    "'%(pub)s' needed to access '%(uri)s', "
+                                    "has expired.  Please install a valid "
+                                    "certificate.") % { "cert": self.data,
+                                    "uri": uri }
+                        return _("Certificate '%(cert)s' for publisher "
+                            "'%(pub)s', has expired.  Please install a valid "
+                            "certificate.") % { "cert": self.data,
+                            "pub": publisher }
+                if uri:
+                        return _("Certificate '%(cert)s', needed to access "
+                            "'%(uri)s', has expired.  Please install a valid "
+                            "certificate.") % { "cert": self.data, "uri": uri }
+                return _("Certificate '%s' has expired.  Please install a "
+                    "valid certificate.") % self.data
+
+
+class ExpiringCertificate(CertificateError):
+        """Used to indicate that a certificate has expired."""
+
+        def __str__(self):
+                publisher = self.args.get("publisher", None)
+                uri = self.args.get("uri", None)
+                days = self.args.get("days", 0)
+                if publisher:
+                        if uri:
+                                return _("Certificate '%(cert)s' for publisher "
+                                    "'%(pub)s', needed to access '%(uri)s', "
+                                    "will expire in '%(days)s' days.") % {
+                                    "cert": self.data, "pub": publisher,
+                                    "uri": uri, "days": days }
+                        return _("Certificate '%(cert)s' for publisher "
+                            "'%(pub)s' will expire in '%(days)s' days.") % {
+                            "cert": self.data, "pub": publisher, "days": days }
+                if uri:
+                        return _("Certificate '%(cert)s', needed to access "
+                            "'%(uri)s', will expire in '%(days)s' days.") % {
+                            "cert": self.data, "uri": uri, "days": days }
+                return _("Certificate '%(cert)s' will expire in "
+                    "'%(days)s' days.") % { "cert": self.data, "days": days }
+
+
+class InvalidCertificate(CertificateError):
+        """Used to indicate that a certificate is invalid."""
+
+        def __str__(self):
+                publisher = self.args.get("publisher", None)
+                uri = self.args.get("uri", None)
+                if publisher:
+                        if uri:
+                                return _("Certificate '%(cert)s' for publisher "
+                                    "'%(pub)s', needed to access '%(uri)s', is "
+                                    "invalid.") % { "cert": self.data,
+                                    "pub": publisher, "uri": uri }
+                        return _("Certificate '%(cert)s' for publisher "
+                            "'%(pub)s' is invalid.") % { "cert": self.data,
+                            "pub": publisher }
+                if uri:
+                        return _("Certificate '%(cert)s' needed to access "
+                            "'%(uri)s' is invalid.") % { "cert": self.data,
+                            "uri": uri }
+                return _("Invalid certificate '%s'.") % self.data
+
+
+class NoSuchCertificate(CertificateError):
+        """Used to indicate that a certificate could not be found."""
+
+        def __str__(self):
+                publisher = self.args.get("publisher", None)
+                uri = self.args.get("uri", None)
+                if publisher:
+                        if uri:
+                                return _("Unable to locate certificate "
+                                    "'%(cert)s' for publisher '%(pub)s' needed "
+                                    "to access '%(uri)s'.") % {
+                                    "cert": self.data, "pub": publisher,
+                                    "uri": uri }
+                        return _("Unable to locate certificate '%(cert)s' for "
+                            "publisher '%(pub)s'.") % { "cert": self.data,
+                            "pub": publisher }
+                if uri:
+                        return _("Unable to locate certificate '%(cert)s' "
+                            "needed to access '%(uri)s'.") % {
+                            "cert": self.data, "uri": uri }
+                return _("Unable to locate certificate '%s'.") % self.data
+
+
+class NotYetValidCertificate(CertificateError):
+        """Used to indicate that a certificate is not yet valid (future
+        effective date)."""
+
+        def __str__(self):
+                publisher = self.args.get("publisher", None)
+                uri = self.args.get("uri", None)
+                if publisher:
+                        if uri:
+                                return _("Certificate '%(cert)s' for publisher "
+                                    "'%(pub)s', needed to access '%(uri)s', "
+                                    "has a future effective date.") % {
+                                    "cert": self.data, "pub": publisher,
+                                    "uri": uri }
+                        return _("Certificate '%(cert)s' for publisher "
+                            "'%(pub)s' has a future effective date.") % {
+                            "cert": self.data, "pub": publisher }
+                if uri:
+                        return _("Certificate '%(cert)s' needed to access "
+                            "'%(uri)s' has a future effective date.") % {
+                            "cert": self.data, "uri": uri }
+                return _("Certificate '%s' has a future effective date.") % \
+                    self.data
--- a/src/modules/client/filelist.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/client/filelist.py	Mon Mar 09 16:09:13 2009 -0500
@@ -36,7 +36,6 @@
 
 import pkg.pkgtarfile as ptf
 import pkg.portable as portable
-import pkg.fmri
 import pkg.client.api_errors as api_errors
 import pkg.misc as misc
 from pkg.client import global_settings
@@ -100,13 +99,13 @@
                 self.effective_nfiles = 0
 
                 if fmri:
-                        auth, pkg_name, version = self.fmri.tuple()
+                        pub = self.fmri.get_publisher()
 
-                        self.authority = pkg.fmri.strip_auth_pfx(auth)
-                        self.ssl_tuple = self.image.get_ssl_credentials(auth)
-                        self.uuid = self.image.get_uuid(self.authority)
+                        self.publisher = pub
+                        self.ssl_tuple = self.image.get_ssl_credentials(pub)
+                        self.uuid = self.image.get_uuid(self.publisher)
                 else:
-                        self.authority = None
+                        self.publisher = None
                         self.ssl_tuple = None
                         self.uuid = None
 
@@ -225,7 +224,7 @@
                 """A wrapper around _get_files.  This handles exceptions
                 that might occur and deals with timeouts."""
 
-                num_mirrors = self.image.num_mirrors(self.authority)
+                num_mirrors = self.image.num_mirrors(self.publisher)
                 max_timeout = global_settings.PKG_TIMEOUT_MAX
                 if num_mirrors > 0:
                         retry_count = max_timeout * (num_mirrors + 1)
@@ -491,7 +490,7 @@
                 elif self.ds:
                         self.url = self.ds.url
                 else:
-                        self.ds = self.image.select_mirror(self.authority,
+                        self.ds = self.image.select_mirror(self.publisher,
                             chosen_set)
                         self.url = self.ds.url
                         chosen_set.add(self.ds)
@@ -529,7 +528,7 @@
                                     "computed: %s" % (action.hash, fhash))
                         return
 
-                newhash, cdata = misc.get_data_digest(filepath)
+                newhash = misc.get_data_digest(filepath)[0]
                 if chash != newhash:
                         os.remove(filepath)
                         raise InvalidContentException(path,
--- a/src/modules/client/history.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/client/history.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import errno
 import os
@@ -31,35 +33,49 @@
 import xml.parsers.expat as expat
 
 import pkg
+import pkg.client.api_errors as api_errors
+import pkg.fmri as fmri
 import pkg.misc as misc
 import pkg.portable as portable
 
 # Constants for the (outcome, reason) combination for operation result.
-# Indicates that the operation succeeded.
-RESULT_SUCCEEDED = ["Succeeded"]
 # Indicates that the user canceled the operation.
 RESULT_CANCELED = ["Canceled"]
 # Indicates that the operation had no work to perform or didn't need to make
 # any changes to the image.
 RESULT_NOTHING_TO_DO = ["Nothing to do"]
-# Indicates that the operation failed for an unknown reason.
-RESULT_FAILED_UNKNOWN = ["Failed", "Unknown"]
+# Indicates that the operation succeeded.
+RESULT_SUCCEEDED = ["Succeeded"]
+# Indicates that the user or client provided bad information which resulted in
+# operation failure.
+RESULT_FAILED_BAD_REQUEST = ["Failed", "Bad Request"]
+# Indicates that the operation failed due to a configuration error (such as an
+# invalid SSL Certificate, etc.).
+RESULT_FAILED_CONFIGURATION = ["Failed", "Configuration"]
 # Indicates that the operation failed due to package constraints or because of
 # a restriction enforced by the client (e.g. SUNWipkg out of date).
 RESULT_FAILED_CONSTRAINED = ["Failed", "Constrained"]
-# Indicates that the user or client provided bad information which resulted in
-# operation failure.
-RESULT_FAILED_BAD_REQUEST = ["Failed", "Bad Request"]
 # Indicates that a search operation failed.
 RESULT_FAILED_SEARCH = ["Failed", "Search"]
 # Indicates that there was a problem writing a file or a permissions error.
 RESULT_FAILED_STORAGE = ["Failed", "Storage"]
 # Indicates that a transport error caused the operation to fail.
 RESULT_FAILED_TRANSPORT = ["Failed", "Transport"]
+# Indicates that the operation failed for an unknown reason.
+RESULT_FAILED_UNKNOWN = ["Failed", "Unknown"]
 
 # Operations that are discarded, not saved, when recorded by history.
 DISCARDED_OPERATIONS = ["contents", "info", "list"]
 
+# Cross-reference table for errors and results.  Entries should be ordered
+# most-specific to least-specific.
+error_results = {
+    api_errors.CertificateError: RESULT_FAILED_CONFIGURATION,
+    api_errors.PublisherError: RESULT_FAILED_BAD_REQUEST,
+    api_errors.CanceledException: RESULT_CANCELED,
+    fmri.IllegalFmri: RESULT_FAILED_BAD_REQUEST,
+}
+
 class _HistoryException(Exception):
         """Private base exception class for all History exceptions."""
         def __init__(self, *args):
@@ -579,3 +595,45 @@
                         # caused the client to abort() also caused the storage
                         # of the history information to fail.
                         return
+
+        def log_operation_start(self, name):
+                """Marks the start of an operation to be recorded in image
+                history."""
+                self.operation_name = name
+
+        def log_operation_end(self, error=None, result=None):
+                """Marks the end of an operation to be recorded in image
+                history.
+
+                'result' should be a pkg.client.history constant value
+                representing the outcome of an operation.  If not provided,
+                and 'error' is provided, the final result of the operation will
+                be based on the class of 'error' and 'error' will be recorded
+                for the current operation.  If 'result' and 'error' is not
+                provided, success is assumed."""
+
+                if error and not result:
+                        try:
+                                # Attempt get an exact error match first.
+                                result = error_results[error.__class__]
+                        except (AttributeError, KeyError):
+                                # Failing an exact match, determine if this
+                                # error is a subclass of an existing one.
+                                for entry, val in error_results.iteritems():
+                                        if isinstance(error, entry):
+                                                result = val
+                                                break
+                        if not result:
+                                # If a result could still not be determined,
+                                # assume unknown failure case.
+                                result = RESULT_FAILED_UNKNOWN
+                elif not result:
+                        # Assume success if no error and no result.
+                        result = RESULT_SUCCEEDED
+                self.operation_result = result
+
+        def log_operation_error(self, error):
+                """Adds an error to the list of errors to be recorded in image
+                history for the current opreation."""
+                if self.operation_name:
+                        self.operation_errors.append(error)
--- a/src/modules/client/image.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/client/image.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,12 +20,13 @@
 # CDDL HEADER END
 #
 
+#
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import cPickle
-import calendar
-import datetime
+import copy
 import errno
 import httplib
 import os
@@ -37,12 +38,6 @@
 import urllib
 import urllib2
 
-import OpenSSL.crypto as osc
-
-from pkg.misc import msg, emsg
-
-# import uuid           # XXX interesting 2.5 module
-
 import pkg.Uuid25
 import pkg.catalog             as catalog
 import pkg.client.api_errors   as api_errors
@@ -56,26 +51,26 @@
 import pkg.client.progress     as progress
 import pkg.client.query_engine as query_e
 import pkg.client.retrieve     as retrieve
+import pkg.client.publisher    as publisher
 import pkg.client.variant      as variant
 import pkg.fmri
 import pkg.manifest            as manifest
 import pkg.misc                as misc
 import pkg.portable            as portable
 import pkg.search_errors       as search_errors
-import pkg.updatelog           as updatelog
 import pkg.version
 
-from pkg.misc import versioned_urlopen
-from pkg.misc import EmptyI, EmptyDict
-from pkg.misc import TransportException
-from pkg.misc import TransferTimedOutException
-from pkg.misc import TransportFailures
-from pkg.misc import CLIENT_DEFAULT_MEM_USE_KB
-from pkg.misc import CfgCacheError
 from pkg.actions import MalformedActionError
 from pkg.client import global_settings
 from pkg.client.api_errors import InvalidDepotResponseException
-from pkg.client.imagetypes import *
+from pkg.client.imagetypes import IMG_USER, IMG_ENTIRE
+from pkg.misc import CLIENT_DEFAULT_MEM_USE_KB
+from pkg.misc import CfgCacheError
+from pkg.misc import EmptyI, EmptyDict
+from pkg.misc import msg, emsg
+from pkg.misc import versioned_urlopen
+from pkg.misc import TransportException
+from pkg.misc import TransportFailures
 
 img_user_prefix = ".org.opensolaris,pkg"
 img_root_prefix = "var/pkg"
@@ -83,9 +78,6 @@
 PKG_STATE_INSTALLED = "installed"
 PKG_STATE_KNOWN = "known"
 
-# Minimum number of days to issue warning before a certificate expires
-MIN_WARN_DAYS = datetime.timedelta(days=30)
-
 class Image(object):
         """An Image object is a directory tree containing the laid-down contents
         of a self-consistent graph of Packages.
@@ -199,7 +191,7 @@
 
                 # right now we don't explicitly set dir/file modes everywhere;
                 # set umask to proper value to prevent problems w/ overly
-                # locked down umask.  
+                # locked down umask.
                 os.umask(0022)
 
         def _check_subdirs(self, sub_d, prefix):
@@ -222,7 +214,7 @@
                              self._check_subdirs(d, img_root_prefix):
                         rv = IMG_ENTIRE
                 return rv
-                
+
         def find_root(self, d, exact_match=False):
                 # Ascend from the given directory d to find first
                 # encountered image. If exact_match is true, if the
@@ -288,18 +280,22 @@
                 # make sure we define architecture variant; upgrade config
                 # file if possible.
                 if "variant.arch" not in self.cfg_cache.variants:
-                        self.cfg_cache.variants["variant.arch"] = platform.processor()
+                        self.cfg_cache.variants["variant.arch"] = \
+                            platform.processor()
                         try:
                                 self.save_config()
                         except api_errors.PermissionsException:
                                 pass
                 # make sure we define zone variant; upgrade config if possible
                 if "variant.opensolaris.zone" not in self.cfg_cache.variants:
-                        zone = self.cfg_cache.filters.get("opensolaris.zone", "")
+                        zone = self.cfg_cache.filters.get("opensolaris.zone",
+                            "")
                         if zone == "nonglobal":
-                                self.cfg_cache.variants["variant.opensolaris.zone"] = "nonglobal"
+                                self.cfg_cache.variants[
+                                    "variant.opensolaris.zone"] = "nonglobal"
                         else:
-                                self.cfg_cache.variants["variant.opensolaris.zone"] = "global"
+                                self.cfg_cache.variants[
+                                    "variant.opensolaris.zone"] = "global"
                         try:
                                 self.save_config()
                         except api_errors.PermissionsException:
@@ -335,12 +331,14 @@
                 self.dl_cache_incoming = os.path.normpath(os.path.join(
                     self.dl_cache_dir, "incoming-%d" % os.getpid()))
 
-        def set_attrs(self, type, root, is_zone, auth_name, auth_url,
-            ssl_key=None, ssl_cert=None, variants=EmptyDict, refresh_allowed=True):
-
-                self.__set_dirs(imgtype=type, root=root)
-
-                if not os.path.exists(os.path.join(self.imgdir, imageconfig.CFG_FILE)):
+        def set_attrs(self, imgtype, root, is_zone, prefix, pub_url,
+            ssl_key=None, ssl_cert=None, variants=EmptyDict,
+            refresh_allowed=True):
+
+                self.__set_dirs(imgtype=imgtype, root=root)
+
+                if not os.path.exists(os.path.join(self.imgdir,
+                    imageconfig.CFG_FILE)):
                         self.history.operation_name = "image-create"
                 else:
                         self.history.operation_name = "image-set-attributes"
@@ -351,29 +349,25 @@
 
                 if is_zone:
                         self.cfg_cache.filters["opensolaris.zone"] = "nonglobal"
-                        self.cfg_cache.variants["variant.opensolaris.zone"] = "nonglobal"
+                        self.cfg_cache.variants[
+                            "variant.opensolaris.zone"] = "nonglobal"
                 else:
-                        self.cfg_cache.variants["variant.opensolaris.zone"] = "global"
-
-                newauth = {}
-
-                newauth["prefix"] = auth_name
-                newauth["origin"] = misc.url_affix_trailing_slash(auth_url)
-                newauth["disabled"] = False
-                newauth["mirrors"] = []
-                newauth["ssl_key"] = ssl_key
-                newauth["ssl_cert"] = ssl_cert
-                newauth["uuid"] = pkg.Uuid25.uuid1()
+                        self.cfg_cache.variants[
+                            "variant.opensolaris.zone"] = "global"
+
+                repo = publisher.Repository()
+                repo.add_origin(pub_url, ssl_cert=ssl_cert, ssl_key=ssl_key)
+                newpub = publisher.Publisher(prefix, repositories=[repo])
 
                 # Refresh catalog for new server, if allowed.
                 if refresh_allowed:
                         self.retrieve_catalogs(full_refresh=True,
-                            auths=[newauth])
+                            pubs=[newpub])
 
                 # If we reached this point, the refresh succeeded.  Add the
-                # authority to the cfg_cache and save the config.
-                self.cfg_cache.authorities[auth_name] = newauth
-                self.cfg_cache.preferred_authority = auth_name
+                # publisher to the cfg_cache and save the config.
+                self.cfg_cache.publishers[prefix] = newpub
+                self.cfg_cache.preferred_publisher = prefix 
 
                 self.cfg_cache.variants["variant.arch"] = \
                     variants.get("variant.arch", platform.processor())
@@ -386,7 +380,8 @@
                 return self.root == "/"
 
         def is_zone(self):
-                return self.cfg_cache.variants["variant.opensolaris.zone"] == "nonglobal"
+                return self.cfg_cache.variants[
+                    "variant.opensolaris.zone"] == "nonglobal"
 
         def get_arch(self):
                 return self.cfg_cache.variants["variant.arch"]
@@ -394,67 +389,67 @@
         def get_root(self):
                 return self.root
 
-        def gen_authorities(self, inc_disabled = False):
+        def gen_publishers(self, inc_disabled=False):
                 if not self.cfg_cache:
                         raise CfgCacheError, "empty ImageConfig"
-                if not self.cfg_cache.authorities:
-                        raise CfgCacheError, "no defined authorities"
-                for a in self.cfg_cache.authorities:
-                        auth = self.cfg_cache.authorities[a]
-                        if inc_disabled or not auth["disabled"]:
-                                yield self.cfg_cache.authorities[a]
-
-        def get_url_by_authority(self, authority = None):
-                """Return the URL prefix associated with the given authority.
+                if not self.cfg_cache.publishers:
+                        raise CfgCacheError, "no defined publishers"
+                for p in self.cfg_cache.publishers:
+                        pub = self.cfg_cache.publishers[p]
+                        if inc_disabled or not pub.disabled:
+                                yield self.cfg_cache.publishers[p]
+
+        def get_url_by_publisher(self, prefix=None):
+                """Return the URL prefix associated with the given prefix.
                 For the undefined case, represented by None, return the
-                preferred authority."""
+                preferred publisher."""
 
                 # XXX This function is a possible location to insert one or more
                 # policies regarding use of mirror responses, etc.
 
-                if authority == None:
-                        authority = self.cfg_cache.preferred_authority
+                if prefix is None:
+                        prefix = self.cfg_cache.preferred_publisher
 
                 try:
-                        o = self.cfg_cache.authorities[authority]["origin"]
+                        o = self.cfg_cache.publishers[prefix]["origin"]
                 except KeyError:
-                        # If the authority that we're trying to get no longer
-                        # exists, fall back to preferred authority.
-                        authority = self.cfg_cache.preferred_authority
-                        o = self.cfg_cache.authorities[authority]["origin"]
+                        # If the publisher that we're trying to get no longer
+                        # exists, fall back to preferred publisher.
+                        prefix = self.cfg_cache.preferred_publisher
+                        o = self.cfg_cache.publishers[prefix]["origin"]
 
                 return o.rstrip("/")
 
         def gen_depot_status(self):
-                """Walk all authorities and return all depot status
-                objects for both mirrors and primary authorities."""
-
-                auths = self.cfg_cache.authorities
-                # return depot status objects in authority order
-                for auth in auths.keys():
-                        # first yield authority origin
-                        yield self.cfg_cache.authority_status[auth]
+                """Walk all publishers and return all depot status
+                objects for both mirrors and primary publishers."""
+
+                pubs = self.cfg_cache.publishers
+                # return depot status objects in publisher order
+                for pub in pubs.keys():
+                        # first yield publisher origin
+                        yield self.cfg_cache.publisher_status[pub]
                         # then return mirrors
-                        for ds in self.cfg_cache.mirror_status[auth]:
+                        for ds in self.cfg_cache.mirror_status[pub]:
                                 yield ds
 
-        def num_mirrors(self, auth):
+        def num_mirrors(self, pub):
                 """Return the number of mirrors configured for the
-                given authority."""
-
-                if auth == None:
-                        auth = self.cfg_cache.preferred_authority
+                given publisher."""
+
+                if pub == None:
+                        pub = self.cfg_cache.preferred_publisher
 
                 try:
-                        num = len(self.cfg_cache.mirror_status[auth])
+                        num = len(self.cfg_cache.mirror_status[pub])
                 except KeyError:
-                        # Auth isn't in the list of mirrors, return 0
+                        # pub isn't in the list of mirrors, return 0
                         num = 0
 
                 return num
 
-        def select_mirror(self, auth = None, chosen_set = None):
-                """For the given authority, look through the status of
+        def select_mirror(self, pub = None, chosen_set = None):
+                """For the given publisher, look through the status of
                 the mirrors.  Pick the best one.  This method returns
                 a DepotStatus object or None.  The chosen_set argument
                 contains a set object that lists the mirrors that were
@@ -462,19 +457,19 @@
                 by depot status statistics and ensures we don't
                 always pick the same depot."""
 
-                if auth == None:
-                        auth = self.cfg_cache.preferred_authority
+                if pub == None:
+                        pub = self.cfg_cache.preferred_publisher
                 try:
-                        slst = self.cfg_cache.mirror_status[auth]
+                        slst = self.cfg_cache.mirror_status[pub]
                 except KeyError:
-                        # If the authority that we're trying to get no longer
-                        # exists, fall back to preferred authority.
-                        auth = self.cfg_cache.preferred_authority
-                        slst = self.cfg_cache.mirror_status[auth]
+                        # If the publisher that we're trying to get no longer
+                        # exists, fall back to preferred publisher.
+                        pub = self.cfg_cache.preferred_publisher
+                        slst = self.cfg_cache.mirror_status[pub]
 
                 if len(slst) == 0:
-                        if auth in self.cfg_cache.authority_status:
-                                return self.cfg_cache.authority_status[auth]
+                        if pub in self.cfg_cache.publisher_status:
+                                return self.cfg_cache.publisher_status[pub]
                         else:
                                 return None
 
@@ -493,11 +488,11 @@
                 slst.sort(cmp = cmp_depotstatus)
 
                 # All mirrors in the chosen_set have already been
-                # selected.  Try the authority origin instead.
+                # selected.  Try the publisher origin instead.
                 # Empty chosen_set, next time we start over.
                 if chosen_set and len(chosen_set) == len(slst):
                         chosen_set.clear()
-                        return self.cfg_cache.authority_status[auth]
+                        return self.cfg_cache.publisher_status[pub]
 
                 if chosen_set and slst[0] in chosen_set:
                         for ds in slst:
@@ -506,270 +501,179 @@
 
                 return slst[0]
 
-        def get_ssl_credentials(self, authority=None, origin=None,
-            authent=None):
-                """Return a tuple containing (ssl_key, ssl_cert) for the
-                specified authority prefix.  If the authority isn't specified,
-                attempt to determine the authority by the given origin.  If
-                neither is specified, use the preferred authority.  Authent
-                is a dictionary argument that contains the authority
-                information.
-                """
-
-                # If authent supplied, don't bother with any other
-                # fancy processing.
-                if authent:
-                        return authent["ssl_key"], authent["ssl_cert"]
-
-                if authority is None:
+        def get_ssl_credentials(self, prefix=None, origin=None,
+            pubent=None):
+                """Deprecated; this function will be removed in a future
+                release.  This information should be retrieved directly from a
+                repository origin or mirror object.
+
+                Return a tuple containing (ssl_key, ssl_cert) for the
+                specified publisher prefix.  If the publisher isn't specified,
+                attempt to determine the publisher by the given origin.  If
+                neither is specified, use the preferred publisher.  pubent
+                is a dictionary argument that contains the publisher
+                information."""
+
+                if not pubent and prefix is None:
                         if origin is None:
-                                authority = self.cfg_cache.preferred_authority
+                                prefix = self.cfg_cache.preferred_publisher
                         else:
-                                auths = self.cfg_cache.authorities
-                                for pfx, auth in auths.iteritems():
-                                        if auth["origin"] == origin:
-                                                authority = pfx
+                                pubs = self.cfg_cache.publishers
+                                for pfx, pub in pubs.iteritems():
+                                        repo = pub.selected_repository
+                                        if repo.has_origin(origin):
+                                                prefix = pfx
                                                 break
                                 else:
                                         return None
-                try:
-                        authent = self.cfg_cache.authorities[authority]
-                except KeyError:
-                        authority = self.cfg_cache.preferred_authority
-                        authent = self.cfg_cache.authorities[authority]
-
-                return (authent["ssl_key"], authent["ssl_cert"])
-
-        @staticmethod
-        def build_cert(path):
-                """Take the file given in path, open it, and use it to create
-                an X509 certificate object."""
-
-                cf = file(path, "rb")
-                certdata = cf.read()
-                cf.close()
-                cert = osc.load_certificate(osc.FILETYPE_PEM, certdata)
-
-                return cert
+
+                # One of these should be defined at this point unless the
+                # caller didn't provide anything.
+                assert prefix or origin or pubent
+
+                if not pubent:
+                        try:
+                                pubent = self.cfg_cache.publishers[prefix]
+                        except KeyError:
+                                prefix = self.cfg_cache.preferred_publisher
+                                pubent = self.cfg_cache.publishers[prefix]
+
+                repo = pubent.selected_repository
+                origin = repo.origins[0]
+                return (origin.ssl_key, origin.ssl_cert)
 
         def check_cert_validity(self):
-                """Look through the authorities defined for the image.  Print
+                """Look through the publishers defined for the image.  Print
                 a message and exit with an error if one of the certificates
                 has expired.  If certificates are getting close to expiration,
                 print a warning instead."""
 
-                for a in self.gen_authorities():
-                        pfx, url, ssl_key, ssl_cert, dt, mir = \
-                            self.split_authority(a)
-
-                        if not ssl_cert:
-                                continue
-
-                        try:
-                                cert = self.build_cert(ssl_cert)
-                        except IOError, e:
-                                if e.errno == errno.ENOENT:
-                                        emsg(_("Certificate for authority %s" \
-                                            " not found") % pfx)
-                                        emsg(_("File was supposed to exist at" \
-                                           "  path %s") % ssl_cert)
-                                        return False
-                                else:
-                                        raise
-                        # OpenSSL.crypto.Error
-                        except osc.Error, e:
-                                emsg(_("Certificate for authority %(pfx)s at" \
-                                    " %(ssl_cert)s has an invalid format.") % \
-                                    vars())
-                                return False
-
-                        if cert.has_expired():
-                                emsg(_("Certificate for authority %s" \
-                                    " has expired") % pfx)
-                                emsg(_("Please install a valid certificate"))
-                                return False
-
-                        now = datetime.datetime.utcnow()
-                        nb = cert.get_notBefore()
-                        t = time.strptime(nb, "%Y%m%d%H%M%SZ")
-                        nbdt = datetime.datetime.utcfromtimestamp(
-                            calendar.timegm(t))
-
-                        # PyOpenSSL's has_expired() doesn't validate the notBefore
-                        # time on the certificate.  Don't ask me why.
-
-                        if nbdt > now:
-                                emsg(_("Certificate for authority %s is" \
-                                    " invalid") % pfx)
-                                emsg(_("Certificate effective date is in" \
-                                    " the future"))
-                                return False
-
-                        na = cert.get_notAfter()
-                        t = time.strptime(na, "%Y%m%d%H%M%SZ")
-                        nadt = datetime.datetime.utcfromtimestamp(
-                            calendar.timegm(t))
-
-                        diff = nadt - now
-
-                        if diff <= MIN_WARN_DAYS:
-                                emsg(_("Certificate for authority %s will" \
-                                    " expire in %d days" % (pfx, diff.days)))
-
+                for p in self.gen_publishers():
+                        for r in p.repositories:
+                                for uri in r.origins:
+                                        if uri.ssl_cert:
+                                                misc.validate_ssl_cert(
+                                                    uri.ssl_cert,
+                                                    prefix=p.prefix, uri=uri)
                 return True
 
-        def get_uuid(self, authority):
-                """Return the UUID for the specified authority prefix.  If the
-                policy for sending the UUID is set to false, return None.
-                """
+        def get_uuid(self, prefix):
+                """Deprecated; this function will be removed in a future
+                release.  This information should be retrieved directly from a
+                publisher object.
+
+                Return the UUID for the specified publisher prefix.  If the
+                policy for sending the UUID is set to false, return None."""
+
                 if not self.cfg_cache.get_policy(imageconfig.SEND_UUID):
                         return None
 
                 try:
-                        return self.cfg_cache.authorities[authority]["uuid"]
+                        return self.cfg_cache.publishers[prefix].client_uuid
                 except KeyError:
                         return None
-                        
-        def get_default_authority(self):
-                return self.cfg_cache.preferred_authority
-
-        def has_authority(self, auth_name):
-                return auth_name in self.cfg_cache.authorities
-
-        def delete_authority(self, auth_name):
-                self.history.operation_name = "delete-authority"
-                if not self.has_authority(auth_name):
-                        error = "no such authority '%s'" % auth_name
-                        self.history.operation_errors.append(error)
-                        self.history.operation_result = \
-                            history.RESULT_FAILED_UNKNOWN
-                        raise KeyError, error
-                self.cfg_cache.delete_authority(auth_name)
+
+        def has_publisher(self, prefix=None, alias=None):
+                for pub in self.gen_publishers():
+                        if prefix == pub.prefix or (alias and
+                            alias == pub.alias):
+                                return True
+                return False
+
+        def remove_publisher(self, prefix=None, alias=None):
+                self.history.log_operation_start("remove-publisher")
+                try:
+                        pub = self.get_publisher(prefix=prefix,
+                            alias=alias)
+                except api_errors.ApiException, e:
+                        self.history.log_operation_end(e)
+                        raise e
+
+                if pub.prefix == self.cfg_cache.preferred_publisher:
+                        e = api_errors.RemovePreferredPublisher()
+                        self.history.log_operation_end(error=e)
+                        raise e
+
+                self.cfg_cache.remove_publisher(prefix)
                 self.save_config()
-                self.destroy_catalog(auth_name)
+                self.destroy_catalog(prefix)
                 self.cache_catalogs()
-                self.history.operation_result = history.RESULT_SUCCEEDED
-
-        def get_authority(self, auth_name):
-                if not self.has_authority(auth_name):
-                        raise KeyError, "no such authority '%s'" % auth_name
-
-                return self.cfg_cache.authorities[auth_name]
-
-        def split_authority(self, auth):
-                prefix = auth["prefix"]
+                self.history.log_operation_end()
+
+        def get_publishers(self):
+                return self.cfg_cache.publishers
+
+        def get_publisher(self, prefix=None, alias=None, origin=None):
+                publishers = [p for p in self.get_publishers().values()]
+                for pub in publishers:
+                        if prefix and prefix == pub.prefix:
+                                return pub
+                        elif alias and alias == pub.alias:
+                                return pub
+                        elif origin and \
+                            pub.selected_repository.has_origin(origin):
+                                return pub
+                raise api_errors.UnknownPublisher(max(prefix, alias, origin))
+
+        def get_publisher_last_update_time(self, prefix, cached=True):
+                """Returns a datetime object (or 'None') representing the last
+                time the catalog for a publisher was updated.
+                
+                If the catalog has already been loaded, this reflects the
+                in-memory state of the catalog.
+
+                If the catalog has not already been loaded or 'cached' is False,
+                then the catalog will be temporarily loaded and the most recent
+                information returned."""
+
+                if not cached:
+                        try:
+                                cat = self.catalogs[prefix]
+                        except KeyError:
+                                pass
+                        else:
+                                update_dt = cat.last_modified()
+                                if update_dt:
+                                        update_dt = catalog.ts_to_datetime(update_dt)
+                                return update_dt
+
+                # Temporarily retrieve the catalog object, but don't
+                # cache it as that would interfere with load_catalogs.
+                try:
+                        croot = "%s/catalog/%s" % (self.imgdir, prefix)
+                        cat = catalog.Catalog(croot, publisher=prefix)
+                except (EnvironmentError, catalog.CatalogException):
+                        cat = None
+
                 update_dt = None
-
-                try:
-                        cat = self.catalogs[prefix]
-                except KeyError:
-                        cat = None
-
                 if cat:
                         update_dt = cat.last_modified()
                         if update_dt:
                                 update_dt = catalog.ts_to_datetime(update_dt)
-
-                return (prefix, auth["origin"], auth["ssl_key"],
-                    auth["ssl_cert"], update_dt, auth["mirrors"])
-
-        def set_preferred_authority(self, auth_name):
-                self.history.operation_name = "set-preferred-authority"
-                if not self.has_authority(auth_name):
-                        error = "no such authority '%s'" % auth_name
-                        self.history.operation_errors.append(error)
-                        self.history.operation_result = \
-                            history.RESULT_FAILED_UNKNOWN
-                        raise KeyError, error
-                if self.get_authority(auth_name)["disabled"]:
-                        error = "authority '%s' is disabled" % auth_name
-                        self.history.operation_errors.append(error)
-                        self.history.operation_result = \
-                            history.RESULT_FAILED_BAD_REQUEST
-                        raise KeyError, error
-                self.cfg_cache.preferred_authority = auth_name
+                return update_dt
+
+        def get_preferred_publisher(self):
+                """Returns the prefix of the preferred publisher."""
+                return self.cfg_cache.preferred_publisher
+
+        def set_preferred_publisher(self, prefix=None, alias=None):
+                self.history.log_operation_start("set-preferred-publisher")
+                try:
+                        pub = self.get_publisher(prefix=prefix, alias=alias)
+                except api_errors.UnknownPublisher, e:
+                        self.history.log_operation_end(error=e)
+                        raise e
+
+                if pub.disabled:
+                        e = api_errors.SetPreferredPublisherDisabled(pub)
+                        self.history.log_operation_end(error=e)
+                        raise e
+                self.cfg_cache.preferred_publisher = pub.prefix
                 self.save_config()
-                self.history.operation_result = history.RESULT_SUCCEEDED
-
-        def set_authority(self, auth_name, origin_url = None, ssl_key = None,
-            ssl_cert = None, refresh_allowed = True, uuid = None,
-            disabled = None):
-                self.history.operation_name = "set-authority"
-                auths = self.cfg_cache.authorities
-
-                refresh_catalog = False
-                purge_catalog = False
-
-                if auth_name in auths:
-                        # Copy old authority information to new entry.
-                        oldauth = auths[auth_name]
-                        newauth = oldauth.copy()
-
-                        # Update any fields that have changed.
-                        if origin_url:
-                                newauth["origin"] = \
-                                    misc.url_affix_trailing_slash(origin_url)
-                                refresh_catalog = True
-                        if ssl_key:
-                                newauth["ssl_key"] = ssl_key
-                        if ssl_cert:
-                                newauth["ssl_cert"] = ssl_cert
-                        if uuid:
-                                newauth["uuid"] = uuid
-                        if disabled != None:
-                                # don't make the preferred authority disabled
-                                # the caller is responsible for checking this
-                                assert(not disabled or \
-                                    auth_name != self.get_default_authority())
-                                newauth["disabled"] = disabled
-                                if disabled:
-                                        purge_catalog = True
-                                else:
-                                        refresh_catalog = True
-                             
-                else:
-                        newauth = {}
-                        newauth["prefix"] = auth_name
-                        newauth["origin"] = \
-                            misc.url_affix_trailing_slash(origin_url)
-                        newauth["mirrors"] = []
-                        newauth["ssl_key"] = ssl_key
-                        newauth["ssl_cert"] = ssl_cert
-                        if not uuid:
-                                uuid = pkg.Uuid25.uuid1()
-                        newauth["uuid"] = uuid
-                        if disabled is None:
-                                disabled = False
-                        newauth["disabled"] = disabled
-                        if not newauth["disabled"]:
-                                refresh_catalog = True
-
-                if refresh_catalog or purge_catalog:
-                        try:
-                                self.destroy_catalog(auth_name)
-                                self.destroy_catalog_cache()
-                        except EnvironmentError, e:
-                                if e.errno == errno.EACCES:
-                                        raise api_errors.PermissionsException(
-                                            e.filename)
-                                raise
-
-                        if purge_catalog:
-                                self.cache_catalogs()
-                        elif refresh_allowed:
-                                self.retrieve_catalogs(full_refresh=True,
-                                    auths=[newauth])
-
-                # If the code got here, it successfully refreshed
-                # the authority, and passed any sanity checks.  Save
-                # the configuration.
-                auths[auth_name] = newauth
-                self.save_config()
-
-                self.history.operation_result = history.RESULT_SUCCEEDED
+                self.history.log_operation_end()
 
         def set_property(self, prop_name, prop_value):
-                assert prop_name != "preferred-authority"
+                assert prop_name != "preferred-publisher"
                 self.cfg_cache.properties[prop_name] = prop_value
                 self.save_config()
 
@@ -780,7 +684,7 @@
                 return prop_name in self.cfg_cache.properties
 
         def delete_property(self, prop_name):
-                assert prop_name != "preferred-authority"
+                assert prop_name != "preferred-publisher"
                 del self.cfg_cache.properties[prop_name]
                 self.save_config()
 
@@ -788,37 +692,40 @@
                 for p in self.cfg_cache.properties:
                         yield p
 
-        def add_mirror(self, auth_name, mirror):
-                """Add the mirror URL contained in mirror to
-                auth_name's list of mirrors."""
-                self.history.operation_name = "add-mirror"
-                auths = self.cfg_cache.authorities
-                auths[auth_name]["mirrors"].append(mirror)
+        def add_publisher(self, pub, refresh_allowed=True):
+                """Adds the provided publisher object to the image
+                configuration."""
+                self.history.log_operation_start("add-publisher")
+                for p in self.cfg_cache.publishers.values():
+                        if pub == p or (pub.alias and pub.alias == p.alias):
+                                error = api_errors.DuplicatePublisher(pub)
+                                self.history.log_operation_end(error=error)
+                                raise error
+
+                try:
+                        self.destroy_catalog(pub.prefix)
+                        self.destroy_catalog_cache()
+                except EnvironmentError, e:
+                        if e.errno == errno.EACCES:
+                                raise api_errors.PermissionsException(
+                                    e.filename)
+                        raise
+
+                if refresh_allowed:
+                        self.retrieve_catalogs(full_refresh=True, pubs=[pub])
+
+                # Only after success should the new publisher be added to the
+                # configuration.
+                self.cfg_cache.publishers[pub.prefix] = pub
                 self.save_config()
-                self.history.operation_result = history.RESULT_SUCCEEDED
-
-        def has_mirror(self, auth_name, url):
-                """Returns true if url is in auth_name's list of mirrors."""
-
-                return url in self.cfg_cache.authorities[auth_name]["mirrors"]
-
-        def del_mirror(self, auth_name, mirror):
-                """Remove the mirror URL contained in mirror from
-                auth_name's list of mirrors."""
-
-                self.history.operation_name = "delete-mirror"
-                auths = self.cfg_cache.authorities
-
-                if mirror in self.cfg_cache.authorities[auth_name]["mirrors"]:
-                        auths[auth_name]["mirrors"].remove(mirror)
-                        self.save_config()
-                self.history.operation_result = history.RESULT_SUCCEEDED
+                self.history.log_operation_end()
 
         def verify(self, fmri, progresstracker, **args):
                 """generator that returns any errors in installed pkgs
                 as tuple of action, list of errors"""
 
-                for act in self.get_manifest(fmri).gen_actions(self.list_excludes()):
+                for act in self.get_manifest(fmri).gen_actions(
+                    self.list_excludes()):
                         errors = act.verify(self, pkg_fmri=fmri, **args)
                         progresstracker.verify_add_progress(fmri)
                         actname = act.distinguished_name()
@@ -880,8 +787,8 @@
                                         raise failures
                         except MalformedActionError, e:
                                 retry_count -= 1
-                                auth = fmri.get_authority()
-                                url = self.cfg_cache.authorities[auth]["origin"]
+                                pub = fmri.get_publisher()
+                                url = self.cfg_cache.publishers[pub]["origin"]
                                 te = misc.TransferContentException(url=url,
                                     reason=str(e))
                                 failures.append(te)
@@ -891,7 +798,7 @@
 
                 return m
 
-        def __get_touched_manifest(self, fmri):
+        def __get_touched_manifest(self, fmri, intent):
                 """Returns whether intent information has been provided for the
                 given fmri."""
 
@@ -912,9 +819,14 @@
                         # fmri for the current operation.
                         return False
 
+                if intent not in self.__touched_manifests[op][f]:
+                        # No intent information has been provided for this
+                        # fmri for the current operation and reason.
+                        return False
+
                 return True
 
-        def __set_touched_manifest(self, fmri):
+        def __set_touched_manifest(self, fmri, intent):
                 """Records that intent information has been provided for the
                 given fmri's manifest."""
 
@@ -933,36 +845,39 @@
                 if f not in self.__touched_manifests[op]:
                         # No intent information has yet been provided for this
                         # fmri for the current operation.
-                        self.__touched_manifests[op][f] = None
+                        self.__touched_manifests[op][f] = { intent: None }
+                else:
+                        # No intent information has yet been provided for this
+                        # fmri for the current operation and reason.
+                        self.__touched_manifests[op][f][intent] = None
 
         def __touch_manifest(self, fmri):
                 """Perform steps necessary to 'touch' a manifest to provide
                 intent information.  Ignores most exceptions as this operation
                 is only for informational purposes."""
 
-                if not self.__get_touched_manifest(fmri):
+                # What is the client currently processing?
+                target, intent = self.state.get_target()
+
+                # Ignore dry-runs of operations or operations which do not have
+                # a set target.
+                if not target or intent == imagestate.INTENT_EVALUATE:
+                        return
+
+                if not self.__get_touched_manifest(fmri, intent):
                         # If the manifest for this fmri hasn't been "seen"
                         # before, determine if intent information needs to be
                         # provided.
 
-                        # What is the client currently processing?
-                        target, intent = self.state.get_target()
-
-                        if target and intent != imagestate.INTENT_EVALUATE:
-                                # If the client is currently performing an
-                                # image-modifying operation, not just an
-                                # an evaluation, then perform further checks.
-
-                                # Ignore the authority for comparison.
-                                na_target = target.get_fmri(anarchy=True)
-                                na_fmri = target.get_fmri(anarchy=True)
-
-                                if na_target == na_fmri:
-                                        # If the client is currently processing
-                                        # the given fmri (for an install, etc.)
-                                        # then intent information is needed.
-                                        retrieve.touch_manifest(self, fmri)
-                                        self.__set_touched_manifest(fmri)
+                        # Ignore the publisher for comparison.
+                        np_target = target.get_fmri(anarchy=True)
+                        np_fmri = fmri.get_fmri(anarchy=True)
+                        if np_target == np_fmri:
+                                # If the client is currently processing
+                                # the given fmri (for an install, etc.)
+                                # then intent information is needed.
+                                retrieve.touch_manifest(self, fmri)
+                                self.__set_touched_manifest(fmri, intent)
 
         def __fetch_manifest(self, fmri, excludes=EmptyI):
                 """Perform steps necessary to get manifest from remote host
@@ -985,14 +900,14 @@
                 mcontent = retrieve.get_manifest(self, fmri)
                 m.set_content(mcontent)
 
-                # Write the originating authority into the manifest.
+                # Write the originating publisher into the manifest.
                 # Manifests prior to this change won't contain this information.
                 # In that case, the client attempts to re-download the manifest
                 # from the depot.
-                if not fmri.has_authority():
-                        m["authority"] = self.get_default_authority()
+                if not fmri.has_publisher():
+                        m["publisher"] = self.get_preferred_publisher()
                 else:
-                        m["authority"] = fmri.get_authority()
+                        m["publisher"] = fmri.get_publisher()
 
                 try:
                         m.store(mpath)
@@ -1000,28 +915,42 @@
                         if e.errno not in (errno.EROFS, errno.EACCES):
                                 raise
 
-                self.__set_touched_manifest(fmri)
-                
-                # if we were passed actual excludes, reset 
+                # What is the client currently processing?
+                targets = self.state.get_targets()
+
+                intent = None
+                for entry in targets:
+                        target, reason = entry
+
+                        # Ignore the publisher for comparison.
+                        np_target = target.get_fmri(anarchy=True)
+                        np_fmri = fmri.get_fmri(anarchy=True)
+                        if np_target == np_fmri:
+                                intent = reason
+
+                # If no intent could be found, assume INTENT_INFO.
+                self.__set_touched_manifest(fmri, max(intent,
+                    imagestate.INTENT_INFO))
+
+                # if we were passed actual excludes, reset in-memory
                 # in-memory content to reflect that.
                 if excludes:
                         m.set_content(mcontent, excludes)
                 return m
 
-        def _valid_manifest(self, fmri, manifest):
-                """Check authority attached to manifest.  Make sure
-                it matches authority specified in FMRI."""
-
-                authority = fmri.get_authority()
-                if not authority:
-                        authority = self.get_default_authority()
-
-                if not "authority" in manifest:
+        def _valid_manifest(self, fmri, m):
+                """Check publisher attached to manifest.  Make sure
+                it matches publisher specified in FMRI."""
+
+                pub = fmri.get_publisher()
+                if not pub:
+                        pub = self.get_preferred_publisher()
+
+                try:
+                        if m["publisher"] != pub:
+                                return False
+                except KeyError:
                         return False
-
-                if manifest["authority"] != authority:
-                        return False
-
                 return True
 
         def get_manifest_path(self, fmri):
@@ -1047,10 +976,10 @@
 
                 try:
                         # If the manifest didn't already exist, or isn't from
-                        # the correct authority, or no authority is attached
+                        # the correct publisher, or no publisher is attached
                         # to the manifest, attempt to download a new one.
                         if not m or not self._valid_manifest(fmri, m):
-                                m = self.__fetch_manifest_with_retries(fmri, 
+                                m = self.__fetch_manifest_with_retries(fmri,
                                     excludes)
                 except (retrieve.ManifestRetrievalError,
                     retrieve.DatastreamRetrievalError):
@@ -1089,9 +1018,9 @@
                 self.__touch_manifest(fmri)
                 return m
 
-        def installed_file_authority(self, filepath):
+        def installed_file_publisher(self, filepath):
                 """Find the pkg's installed file named by filepath.
-                Return the authority that installed this package."""
+                Return the publisher that installed this package."""
 
                 read_only = False
 
@@ -1106,47 +1035,48 @@
                         f = file(filepath, "r")
 
                 flines = f.readlines()
-                newauth = None
+                newpub = None
 
                 try:
-                        version, auth = flines
+                        version, pub = flines
                         version = version.strip()
-                        auth = auth.strip()
+                        pub = pub.strip()
                 except ValueError:
                         # If we get a ValueError, we've encoutered an
                         # installed file of a previous format.  If we want
                         # upgrade to work in this situation, it's necessary
                         # to assume that the package was installed from
-                        # the preferred authority.  Here, we set up
-                        # the authority to record that.
+                        # the preferred publisher.  Here, we set up
+                        # the publisher to record that.
                         if flines:
-                                auth = flines[0]
-                                auth = auth.strip()
-                                newauth = "%s_%s" % (pkg.fmri.PREF_AUTH_PFX,
-                                    auth)
+                                pub = flines[0]
+                                pub = pub.strip()
+                                newpub = "%s_%s" % (pkg.fmri.PREF_PUB_PFX,
+                                    pub)
                         else:
-                                newauth = "%s_%s" % (pkg.fmri.PREF_AUTH_PFX,
-                                    self.get_default_authority())
+                                newpub = "%s_%s" % (pkg.fmri.PREF_PUB_PFX,
+                                    self.get_preferred_publisher())
 
                         # Exception handler is only part of this code that
-                        # sets newauth
-                        auth = newauth
-
-                if newauth and not read_only:
+                        # sets newpub
+                        pub = newpub
+
+                if newpub and not read_only:
                         # This is where we actually update the installed
-                        # file with the new authority.
+                        # file with the new publisher.
                         f.seek(0)
-                        f.writelines(["VERSION_1\n", newauth, "\n"])
+                        f.writelines(["VERSION_1\n", newpub, "\n"])
 
                 f.close()
 
-                assert auth
-
-                return auth
+                assert pub
+
+                return pub
 
         def _install_file(self, fmri):
                 """Returns the path to the "installed" file for a given fmri."""
-                return "%s/pkg/%s/installed" % (self.imgdir, fmri.get_dir_path())
+                return "%s/pkg/%s/installed" % (self.imgdir,
+                    fmri.get_dir_path())
 
         def install_file_present(self, fmri):
                 """Returns true if the package named by the fmri is installed
@@ -1177,7 +1107,7 @@
                                 raise
                         f = file(self._install_file(fmri), "w")
 
-                f.writelines(["VERSION_1\n", fmri.get_authority_str(), "\n"])
+                f.writelines(["VERSION_1\n", fmri.get_publisher_str(), "\n"])
                 f.close()
 
                 fi = file("%s/state/installed/%s" % (self.imgdir,
@@ -1223,7 +1153,7 @@
                         if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
                                 return
                         elif e.errno == errno.EACCES:
-                                # The directory may exist and be non-empty 
+                                # The directory may exist and be non-empty
                                 # even though we got EACCES.  Try
                                 # to determine its emptiness another way.
                                 try:
@@ -1231,10 +1161,11 @@
                                             len(os.listdir(statedir)) > 0:
                                                 return
                                 except EnvironmentError:
-                                        # ignore this error, pass on the original
-                                        # access error
+                                        # ignore this error, pass on the
+                                        # original access error
                                         pass
-                                raise api_errors.PermissionsException(e.filename)
+                                raise api_errors.PermissionsException(
+                                    e.filename)
                         elif e.errno != errno.ENOENT:
                                 raise
 
@@ -1249,7 +1180,8 @@
                         os.makedirs(tmpdir)
                 except OSError, e:
                         if e.errno == errno.EACCES:
-                                raise api_errors.PermissionsException(e.filename)
+                                raise api_errors.PermissionsException(
+                                    e.filename)
                         if e.errno != errno.EEXIST or \
                             not os.path.isdir(tmpdir):
                                 raise
@@ -1267,8 +1199,8 @@
                                 continue
 
                         fmristr = urllib.unquote("%s@%s" % (pd, vd))
-                        auth = self.installed_file_authority(path)
-                        f = pkg.fmri.PkgFmri(fmristr, authority = auth)
+                        pub = self.installed_file_publisher(path)
+                        f = pkg.fmri.PkgFmri(fmristr, publisher = pub)
                         fi = file(os.path.join(tmpdir, f.get_link_path()), "w")
                         fi.close()
 
@@ -1295,45 +1227,45 @@
                 return self.pkg_states.get(pfmri.get_fmri(anarchy = True)[5:],
                     (PKG_STATE_KNOWN, None))[0]
 
-        def get_pkg_auth_by_fmri(self, pfmri):
-                """Return the authority from which 'pfmri' was installed."""
+        def get_pkg_pub_by_fmri(self, pfmri):
+                """Return the publisher from which 'pfmri' was installed."""
 
                 f = self.pkg_states.get(pfmri.get_fmri(anarchy = True)[5:],
                     (PKG_STATE_KNOWN, None))[1]
                 if f:
                         # Return the non-preferred-prefixed name
-                        return f.get_authority()
+                        return f.get_publisher()
                 return None
 
-        def fmri_set_default_authority(self, fmri):
+        def fmri_set_default_publisher(self, fmri):
                 """If the FMRI supplied as an argument does not have
-                an authority, set it to the image's preferred authority."""
-
-                if fmri.has_authority():
+                a publisher, set it to the image's preferred publisher."""
+
+                if fmri.has_publisher():
                         return
 
-                fmri.set_authority(self.get_default_authority(), True)
+                fmri.set_publisher(self.get_preferred_publisher(), True)
 
         def get_catalog(self, fmri, exception = False):
-                """Given a FMRI, look at the authority and return the
+                """Given a FMRI, look at the publisher and return the
                 correct catalog for this image."""
 
-                # If FMRI has no authority, or is default authority,
-                # then return the catalog for the preferred authority
-                if not fmri.has_authority() or fmri.preferred_authority():
-                        cat = self.catalogs[self.get_default_authority()]
+                # If FMRI has no publisher, or is default publisher,
+                # then return the catalog for the preferred publisher
+                if not fmri.has_publisher() or fmri.preferred_publisher():
+                        cat = self.catalogs[self.get_preferred_publisher()]
                 else:
                         try:
-                                cat = self.catalogs[fmri.get_authority()]
+                                cat = self.catalogs[fmri.get_publisher()]
                         except KeyError:
-                                # If the authority that installed this package
-                                # has vanished, pick the default authority
+                                # If the publisher that installed this package
+                                # has vanished, pick the default publisher
                                 # instead.
                                 if exception:
                                         raise
                                 else:
                                         cat = self.catalogs[\
-                                            self.get_default_authority()]
+                                            self.get_preferred_publisher()]
 
                 return cat
 
@@ -1343,10 +1275,10 @@
 
                 v = self.get_version_installed(fmri)
 
-                if v and not fmri.has_authority():
-                        fmri.set_authority(v.get_authority_str())
-                elif not fmri.has_authority():
-                        fmri.set_authority(self.get_default_authority(), True)
+                if v and not fmri.has_publisher():
+                        fmri.set_publisher(v.get_publisher_str())
+                elif not fmri.has_publisher():
+                        fmri.set_publisher(self.get_preferred_publisher(), True)
 
                 if v and self.fmri_is_successor(v, fmri):
                         return True
@@ -1378,7 +1310,7 @@
 
                 v = self.get_version_installed(fmri)
 
-                assert fmri.has_authority()
+                assert fmri.has_publisher()
 
                 if v:
                         return v
@@ -1397,8 +1329,8 @@
                 """Check that the exact version given in the FMRI is installed
                 in the current image."""
 
-                # All FMRIs passed to is_installed shall have an authority
-                assert fmri.has_authority()
+                # All FMRIs passed to is_installed shall have a publisher
+                assert fmri.has_publisher()
 
                 v = self.get_version_installed(fmri)
                 if not v:
@@ -1407,11 +1339,12 @@
                 return v == fmri
 
         def list_excludes(self, new_variants=None):
-                """Generate a list of callables that each return True if an action
-                is to be included in the image using the currently defined 
-                variants for the image, or an updated set if new_variants are
-                specified.  The callables take a single action argument.
-                Variants, facets and filters will be handled in this fashion."""
+                """Generate a list of callables that each return True if an
+                action is to be included in the image using the currently
+                defined variants for the image, or an updated set if
+                new_variants are specified.  The callables take a single action
+                argument.  Variants, facets and filters will be handled in
+                this fashion."""
                 # XXX simple for now; facets and filters need impl.
                 if new_variants:
                         new_vars = self.cfg_cache.variants.copy()
@@ -1429,7 +1362,7 @@
                         progtrack.evaluate_progress(fmri)
                         mfst = self.get_manifest(fmri)
 
-                        for dep in mfst.gen_actions_by_type("depend", 
+                        for dep in mfst.gen_actions_by_type("depend",
                             self.list_excludes()):
                                 if dep.attrs["type"] != "require":
                                         continue
@@ -1456,17 +1389,17 @@
                                 dependents.extend(self.__req_dependents[f])
                 return dependents
 
-        def __do_get_versions(self, auth):
+        def __do_get_versions(self, pub):
                 """An internal method that is a wrapper around get_catalog.
                 This handles retryable exceptions and timeouts."""
 
                 retry_count = global_settings.PKG_TIMEOUT_MAX
                 failures = TransportFailures()
                 versdict = None
-        
+
                 while not versdict:
                         try:
-                                versdict = retrieve.get_versions(self, auth)
+                                versdict = retrieve.get_versions(self, pub)
                         except TransportException, e:
                                 retry_count -= 1
                                 failures.append(e)
@@ -1476,64 +1409,65 @@
 
                 return versdict
 
-        def valid_authority_test(self, auth):
-                """Test that the authority supplied in auth actually
+        def valid_publisher_test(self, pub):
+                """Test that the publisher supplied in pub actually
                 points to a valid packaging server."""
 
                 try:
-                        vd = self.__do_get_versions(auth)
+                        vd = self.__do_get_versions(pub)
                 except (retrieve.VersionRetrievalError,
                     TransportFailures), e:
                         # Failure when contacting server.  Report
                         # this as an error.
-                        raise InvalidDepotResponseException(auth["origin"],
+                        raise InvalidDepotResponseException(pub["origin"],
                             "Transport errors encountered when trying to "
                             "contact depot server.  Reported the following "
                             "errors:\n%s" % e)
 
                 if not self._valid_versions_test(vd):
-                        raise InvalidDepotResponseException(auth["origin"],
+                        raise InvalidDepotResponseException(pub["origin"],
                             "Invalid or unparseable version information.")
 
                 return True
 
         def captive_portal_test(self):
                 """A captive portal forces a HTTP client on a network
-                to see a special web page, usually for authentication
+                to see a special web page, usually for pubentication
                 purposes.  (http://en.wikipedia.org/wiki/Captive_portal)."""
 
                 vd = None
 
-                for auth in self.gen_authorities():
+                for pub in self.gen_publishers():
                         try:
-                                vd = self.__do_get_versions(auth)
+                                vd = self.__do_get_versions(pub)
                         except (retrieve.VersionRetrievalError,
                             TransportFailures):
                                 # Encountered a transport error while
-                                # trying to contact this authority.
-                                # Pick another authority instead.
+                                # trying to contact this publisher.
+                                # Pick another publisher instead.
                                 continue
 
                         if self._valid_versions_test(vd):
                                 return
                         else:
-                                raise InvalidDepotResponseException(auth["origin"],
-                                    "This server is not a valid package depot.") 
+                                raise InvalidDepotResponseException(
+                                    pub["origin"], _("This server is not a "
+                                    "valid package depot."))
                 if not vd:
-                        # We got all the way through the list of authorites but
+                        # We got all the way through the list of puborites but
                         # encountered transport errors in every case.  This is
                         # likely a network configuration problem.  Report our
                         # inability to contact a server.
                         raise InvalidDepotResponseException(None,
-                            "Unable to contact any configured authorities. "
+                            "Unable to contact any configured publishers. "
                             "This is likely a network configuration problem.")
 
         def _valid_versions_test(self, versdict):
                 """Check that the versions information contained in
                 versdict contains valid version specifications.
 
-                In order to test for this condition, pick an authority
-                from the list of active authorities.  Check to see if
+                In order to test for this condition, pick a publisher
+                from the list of active publishers.  Check to see if
                 we can connect to it.  If so, test to see if it supports
                 the versions/0 operations.  If versions/0 is not found,
                 we get an unparseable response, or the response does
@@ -1565,7 +1499,7 @@
                 # Some other error encountered. Fail
                 return False
 
-        def _do_get_catalog(self, auth, hdr, ts):
+        def _do_get_catalog(self, pub, hdr, ts):
                 """An internal method that is a wrapper around get_catalog.
                 This handles retryable exceptions and timeouts."""
 
@@ -1575,7 +1509,7 @@
 
                 while not success:
                         try:
-                                success = retrieve.get_catalog(self, auth,
+                                success = retrieve.get_catalog(self, pub,
                                     hdr, ts)
                         except TransportException, e:
                                 retry_count -= 1
@@ -1585,76 +1519,76 @@
                                         raise failures
 
         def retrieve_catalogs(self, full_refresh = False,
-            auths = None, progtrack = None):
+            pubs = None, progtrack = None):
                 failed = []
                 total = 0
                 succeeded = 0
                 cat = None
                 ts = 0
 
-                # XXX The authority validation checks depend upon the
-                # assumption that callers who pass a list of authorties
-                # have newly created the authorities in question and want
+                # XXX The publisher validation checks depend upon the
+                # assumption that callers who pass a list of puborties
+                # have newly created the publishers in question and want
                 # to specifically refresh their catalogs and verify that they
                 # are indeed reachable.
-                if not auths:
-                        # If no auths were passed into this routine, we're
+                if not pubs:
+                        # If no pubs were passed into this routine, we're
                         # performing a refresh of all catalogs.  In that case,
                         # it's best to simply check that we're
                         # not connected to a captive portal.
                         self.captive_portal_test()
-                        authlist = list(self.gen_authorities())
+                        publist = list(self.gen_publishers())
                 else:
                         # The code that's calling us has instantiated
-                        # new authorities.  Verify that each auth is
+                        # new publishers.  Verify that each pub is
                         # valid and reachable.  This is a more strict
                         # check than the captive_portal_test()
-                        authlist = [
-                            auth
-                            for auth in auths
-                            if self.valid_authority_test(auth)
+                        publist = [
+                            pub
+                            for pub in pubs
+                            if self.valid_publisher_test(pub)
                         ]
 
                 if progtrack:
-                        progtrack.refresh_start(len(authlist))
-
-                for auth in authlist:
-                        if auth["disabled"]:
+                        progtrack.refresh_start(len(publist))
+
+                for pub in publist:
+                        if pub.disabled:
                                 continue
-                                
+
                         total += 1
                         if progtrack:
-                                progtrack.refresh_progress(auth["prefix"])
-
-                        full_refresh_this_auth = False
-
-                        if auth["prefix"] in self.catalogs:
-                                cat = self.catalogs[auth["prefix"]]
+                                progtrack.refresh_progress(pub.prefix)
+
+                        full_refresh_this_pub = False
+
+                        if pub.prefix in self.catalogs:
+                                cat = self.catalogs[pub.prefix]
                                 ts = cat.last_modified()
 
                                 # Although we may have a catalog with a
                                 # timestamp, the user may have changed the
-                                # origin URL for the authority.  If this has
+                                # origin URL for the publisher.  If this has
                                 # occurred, we need to perform a full refresh.
-                                if cat.origin() != auth["origin"]:
-                                        full_refresh_this_auth = True
+                                if cat.origin() != pub["origin"]:
+                                        full_refresh_this_pub = True
 
                         if ts and not full_refresh and \
-                            not full_refresh_this_auth:
+                            not full_refresh_this_pub:
                                 hdr = {'If-Modified-Since': ts}
                         else:
                                 hdr = {}
 
                         try:
-                                self._do_get_catalog(auth, hdr, ts)
+                                self._do_get_catalog(pub, hdr, ts)
                         except retrieve.CatalogRetrievalError, e:
-                                failed.append((auth, e))
+                                failed.append((pub, e))
                         except TransportFailures, e:
-                                failed.append((auth, e))
+                                failed.append((pub, e))
                         else:
                                 succeeded += 1
 
-                self.cache_catalogs(auths)
+                self.cache_catalogs(pubs)
                 self.update_installed_pkgs()
 
                 if progtrack:
@@ -1664,33 +1598,33 @@
                         raise api_errors.CatalogRefreshException(failed, total,
                             succeeded)
 
-        CATALOG_CACHE_VERSION = 1
-
-        def cache_catalogs(self, auths=None):
+        CATALOG_CACHE_VERSION = 2
+
+        def cache_catalogs(self, pubs=None):
                 """Read in all the catalogs and cache the data."""
                 cache = {}
-                authlist = []
+                publist = []
 
                 try:
-                        authlist = list(self.gen_authorities())
+                        publist = list(self.gen_publishers())
                 except CfgCacheError:
-                        # No authorities defined.  If the caller hasn't
-                        # supplied authorities to cache, raise the error
-                        if not auths:
+                        # No publishers defined.  If the caller hasn't
+                        # supplied publishers to cache, raise the error
+                        if not pubs:
                                 raise
 
-                if auths:
-                        # If caller passed authorities, include this in
-                        # the list of authorities to cache.
-                        authlist.extend(auths)
-
-                for auth in authlist:
-                        croot = "%s/catalog/%s" % (self.imgdir, auth["prefix"])
+                if pubs:
+                        # If caller passed publishers, include this in
+                        # the list of publishers to cache.
+                        publist.extend(pubs)
+
+                for pub in publist:
+                        croot = "%s/catalog/%s" % (self.imgdir, pub.prefix)
                         # XXX Should I be removing pkg_names.pkl now that we're
                         # not using it anymore?
                         try:
                                 catalog.Catalog.read_catalog(cache,
-                                    croot, auth = auth["prefix"])
+                                    croot, pub = pub.prefix)
                         except EnvironmentError, e:
                                 # If a catalog file is just missing, ignore it.
                                 # If there's a worse error, make sure the user
@@ -1744,35 +1678,36 @@
                         try:
                                 version, self._catalog = \
                                     cPickle.load(file(cache_file, "rb"))
-                        except (cPickle.PickleError, EnvironmentError, EOFError):
+                        except (cPickle.PickleError, EnvironmentError,
+                            EOFError):
                                 self._catalog = {}
                                 self._catalog_cache_mod_time = None
                                 raise RuntimeError
 
                         self._catalog_cache_mod_time = mod_time
-                        
+
                         # If we don't recognize the version, complain.
                         if version != self.CATALOG_CACHE_VERSION:
                                 raise RuntimeError
 
         def load_catalogs(self, progresstracker):
-                for auth in self.gen_authorities():
-                        croot = "%s/catalog/%s" % (self.imgdir, auth["prefix"])
-                        progresstracker.catalog_start(auth["prefix"])
-                        if auth["prefix"] == self.cfg_cache.preferred_authority:
-                                authpfx = "%s_%s" % (pkg.fmri.PREF_AUTH_PFX,
-                                    auth["prefix"])
+                for pub in self.gen_publishers():
+                        croot = "%s/catalog/%s" % (self.imgdir, pub.prefix)
+                        progresstracker.catalog_start(pub.prefix)
+                        if pub.prefix == self.cfg_cache.preferred_publisher:
+                                pubpfx = "%s_%s" % (pkg.fmri.PREF_PUB_PFX,
+                                    pub.prefix)
                                 c = catalog.Catalog(croot,
-                                    authority=authpfx)
+                                    publisher=pubpfx)
                         else:
                                 c = catalog.Catalog(croot,
-                                    authority = auth["prefix"])
-                        self.catalogs[auth["prefix"]] = c
+                                    publisher=pub.prefix)
+                        self.catalogs[pub.prefix] = c
                         progresstracker.catalog_done()
 
                 # Try to load the catalog cache file.  If that fails, load the
                 # data from the canonical text copies of the catalogs from each
-                # authority.  Try to save it, to spare the time in the future.
+                # publisher.  Try to save it, to spare the time in the future.
                 # XXX Given that this is a read operation, should we be writing?
                 try:
                         self.load_catalog_cache()
@@ -1785,12 +1720,12 @@
                 for state, f in self.pkg_states.values():
                         if state != PKG_STATE_INSTALLED:
                                 continue
-                        auth, name, vers = f.tuple()
+                        pub, name, vers = f.tuple()
 
                         if name not in self._catalog or \
                             vers not in self._catalog[name]["versions"]:
                                 catalog.Catalog.cache_fmri(self._catalog, f,
-                                    f.get_authority())
+                                    f.get_publisher())
 
         def destroy_catalog_cache(self):
                 pickle_file = os.path.join(self.imgdir, "catalog/catalog.pkl")
@@ -1800,10 +1735,14 @@
                         if e.errno != errno.ENOENT:
                                 raise
 
-        def destroy_catalog(self, auth_name):
+        def has_catalog(self, prefix):
+                return os.path.exists(os.path.join(self.imgdir, "catalog",
+                    prefix, "catalog"))
+
+        def destroy_catalog(self, prefix):
                 try:
                         shutil.rmtree("%s/catalog/%s" %
-                            (self.imgdir, auth_name))
+                            (self.imgdir, prefix))
                 except OSError, e:
                         if e.errno not in (errno.ENOENT, errno.ESRCH):
                                 raise
@@ -1820,7 +1759,7 @@
                 if cfmri.is_same_pkg(pfmri):
                         return True
 
-                # Get the catalog for the correct authority
+                # Get the catalog for the correct publisher
                 cat = self.get_catalog(cfmri)
                 return cat.rename_is_same_pkg(cfmri, pfmri)
 
@@ -1829,12 +1768,12 @@
                 """Since the catalog keeps track of renames, it's no longer
                 sufficient to rely on the FMRI class to determine whether a
                 package is a successor.  This routine takes two FMRIs, and
-                if they have the same authority, checks if they've been
+                if they have the same publisher, checks if they've been
                 renamed.  If a rename has occurred, this runs the is_successor
                 routine from the catalog.  Otherwise, this runs the standard
                 fmri.is_successor() code."""
 
-                # Get the catalog for the correct authority
+                # Get the catalog for the correct publisher
                 cat = self.get_catalog(cfmri)
 
                 # If the catalog has a rename record that names fmri as a
@@ -1885,7 +1824,7 @@
                 """Build up the package state dictionary.
 
                 This dictionary maps the full fmri string to a tuple of the
-                state, the prefix of the authority from which it's installed,
+                state, the prefix of the publisher from which it's installed,
                 and the fmri object.
 
                 Note that this dictionary only maps installed packages.  Use
@@ -1910,8 +1849,8 @@
                                 fmristr = urllib.unquote(pl)
                                 tmpf = pkg.fmri.PkgFmri(fmristr)
                                 path = self._install_file(tmpf)
-                                auth = self.installed_file_authority(path)
-                                f = pkg.fmri.PkgFmri(fmristr, authority = auth)
+                                pub = self.installed_file_publisher(path)
+                                f = pkg.fmri.PkgFmri(fmristr, publisher = pub)
 
                                 self.pkg_states[fmristr] = \
                                     (PKG_STATE_INSTALLED, f)
@@ -1928,8 +1867,8 @@
                                         continue
 
                                 fmristr = urllib.unquote("%s@%s" % (pd, vd))
-                                auth = self.installed_file_authority(path)
-                                f = pkg.fmri.PkgFmri(fmristr, authority = auth)
+                                pub = self.installed_file_publisher(path)
+                                f = pkg.fmri.PkgFmri(fmristr, publisher = pub)
 
                                 self.pkg_states[fmristr] = \
                                     (PKG_STATE_INSTALLED, f)
@@ -1942,7 +1881,8 @@
                 return pkg.fmri.PkgFmri(myfmri, self.attrs["Build-Release"])
 
         def strtomatchingfmri(self, myfmri):
-                return pkg.fmri.MatchingPkgFmri(myfmri, self.attrs["Build-Release"])
+                return pkg.fmri.MatchingPkgFmri(myfmri,
+                    self.attrs["Build-Release"])
 
         def load_constraints(self, progtrack):
                 """Load constraints for all install pkgs"""
@@ -1951,16 +1891,17 @@
                         # skip loading if already done
                         if self.constraints.start_loading(fmri):
                                 mfst = self.get_manifest(fmri)
-                                for dep in mfst.gen_actions_by_type("depend", 
+                                for dep in mfst.gen_actions_by_type("depend",
                                     self.list_excludes()):
                                         progtrack.evaluate_progress()
-                                        f, constraint = dep.parse(self, fmri.get_name())
-                                        self.constraints.update_constraints(constraint)
+                                        f, con = dep.parse(self,
+                                            fmri.get_name())
+                                        self.constraints.update_constraints(con)
                                 self.constraints.finish_loading(fmri)
 
         def get_installed_unbound_inc_list(self):
-                """Returns list of packages containing incorporation dependencies
-                on which no other pkgs depend."""
+                """Returns list of packages containing incorporation
+                dependencies on which no other pkgs depend."""
 
                 inc_tuples = []
                 dependents = set()
@@ -1968,19 +1909,20 @@
                 for fmri in self.gen_installed_pkgs():
                         fmri_name = fmri.get_pkg_stem()
                         mfst = self.get_manifest(fmri)
-                        for dep in mfst.gen_actions_by_type("depend", self.list_excludes()):
+                        for dep in mfst.gen_actions_by_type("depend",
+                            self.list_excludes()):
                                 con_fmri = dep.get_constrained_fmri(self)
                                 if con_fmri:
                                         con_name = con_fmri.get_pkg_stem()
                                         dependents.add(con_name)
                                         inc_tuples.append((fmri_name, con_name))
-                # remove those incorporations which are depended on by other 
+                # remove those incorporations which are depended on by other
                 # incorporations.
                 deletions = 0
                 for i, a in enumerate(inc_tuples[:]):
                         if a[0] in dependents:
                                 del inc_tuples[i - deletions]
-                                
+
                 return list(set([ a[0] for a in inc_tuples ]))
 
         def get_user_by_name(self, name):
@@ -2017,17 +1959,17 @@
                 """Applies a matcher to a name across a list of patterns.
                 Returns all tuples of patterns which match the name.  Each tuple
                 contains the index into the original list, the pattern itself,
-                the package version, the authority, and the raw authority
+                the package version, the publisher, and the raw publisher
                 string."""
                 return [
                     (i, pat, pat.tuple()[2],
-                        pat.get_authority(), pat.get_authority_str())
+                        pat.get_publisher(), pat.get_publisher_str())
                     for i, pat in enumerate(patterns)
                     if matcher(name, pat.tuple()[1])
                 ]
 
-        def __inventory(self, patterns = None, all_known = False, matcher = None,
-            constraint = pkg.version.CONSTRAINT_AUTO):
+        def __inventory(self, patterns=None, all_known=False, matcher=None,
+            constraint=pkg.version.CONSTRAINT_AUTO):
                 """Private method providing the back-end for inventory()."""
 
                 if not matcher:
@@ -2048,8 +1990,8 @@
                                         if "*" in pat or "?" in pat:
                                                 matcher = pkg.fmri.glob_match
                                                 patterns[i] = \
-                                                    pkg.fmri.MatchingPkgFmri(pat,
-                                                        "5.11")
+                                                    pkg.fmri.MatchingPkgFmri(
+                                                        pat, "5.11")
                                         else:
                                                 patterns[i] = \
                                                     pkg.fmri.PkgFmri(pat,
@@ -2060,13 +2002,13 @@
                 if illegals:
                         raise api_errors.InventoryException(illegal=illegals)
 
-                pauth = self.cfg_cache.preferred_authority
+                ppub = self.cfg_cache.preferred_publisher
 
                 # matchingpats is the set of all the patterns which matched a
                 # package in the catalog.  This allows us to return partial
                 # failure if some patterns match and some don't.
                 # XXX It would be nice to keep track of why some patterns failed
-                # to match -- based on name, version, or authority.
+                # to match -- based on name, version, or publisher.
                 matchingpats = set()
 
                 # XXX Perhaps we shouldn't sort here, but in the caller, to save
@@ -2107,42 +2049,42 @@
                                         continue
 
                                 # Like the version skipping above, do the same
-                                # for authorities.
-                                authlist = set(self._catalog[name][str(ver)][1])
+                                # for publishers.
+                                publist = set(self._catalog[name][str(ver)][1])
                                 nomatch = []
                                 for i, match in enumerate(vmatches):
                                         if match[3] and \
-                                            match[3] not in authlist:
+                                            match[3] not in publist:
                                                 nomatch.append(i)
 
-                                amatches = [
+                                pmatches = [
                                     vmatches[i]
                                     for i, match in enumerate(vmatches)
                                     if i not in nomatch
                                 ]
 
-                                if vmatches and not amatches:
+                                if vmatches and not pmatches:
                                         continue
 
                                 # If no patterns were specified or any still-
-                                # matching pattern specified no authority, we
-                                # use the entire authlist for this version.
-                                # Otherwise, we use the intersection of authlist
-                                # and the auths in the patterns.
-                                aset = set(i[3] for i in amatches)
+                                # matching pattern specified no publisher, we
+                                # use the entire publist for this version.
+                                # Otherwise, we use the intersection of publist
+                                # and the pubs in the patterns.
+                                aset = set(i[3] for i in pmatches)
                                 if aset and None not in aset:
-                                        authlist = set(
+                                        publist = set(
                                             m[3:5]
-                                            for m in amatches
-                                            if m[3] in authlist
+                                            for m in pmatches
+                                            if m[3] in publist
                                         )
                                 else:
-                                        authlist = zip(authlist, authlist)
+                                        publist = zip(publist, publist)
 
                                 pfmri = self._catalog[name][str(ver)][0]
 
                                 inst_state = self.get_pkg_state_by_fmri(pfmri)
-                                inst_auth = self.get_pkg_auth_by_fmri(pfmri)
+                                inst_pub = self.get_pkg_pub_by_fmri(pfmri)
                                 state = {
                                     "upgradable": ver != newest,
                                     "frozen": False,
@@ -2151,19 +2093,19 @@
                                 }
 
                                 # We yield copies of the fmri objects in the
-                                # catalog because we add the authorities in, and
+                                # catalog because we add the publishers in, and
                                 # don't want to mess up the canonical catalog.
-                                # If a pattern had specified an authority as
+                                # If a pattern had specified a publisher as
                                 # preferred, be sure to emit an fmri that way,
                                 # too.
                                 yielded = False
                                 if all_known:
-                                        for auth, rauth in authlist:
+                                        for pub, rpub in publist:
                                                 nfmri = pfmri.copy()
-                                                nfmri.set_authority(rauth,
-                                                    auth == pauth)
+                                                nfmri.set_publisher(rpub,
+                                                    pub == ppub)
                                                 st = state.copy()
-                                                if auth == inst_auth:
+                                                if pub == inst_pub:
                                                         st["state"] = \
                                                             PKG_STATE_INSTALLED
                                                 else:
@@ -2173,19 +2115,21 @@
                                                 yielded = True
                                 elif inst_state == PKG_STATE_INSTALLED:
                                         nfmri = pfmri.copy()
-                                        nfmri.set_authority(inst_auth,
-                                            inst_auth == pauth)
+                                        nfmri.set_publisher(inst_pub,
+                                            inst_pub == ppub)
                                         state["state"] = inst_state
                                         yield nfmri, state
                                         yielded = True
 
                                 if yielded:
-                                        matchingpats |= set(i[:2] for i in amatches)
+                                        matchingpats |= set(
+                                            i[:2] for i in pmatches)
 
                 nonmatchingpats = [
                     opatterns[i]
                     for i, f in set(enumerate(patterns)) - matchingpats
                 ]
+
                 if nonmatchingpats:
                         raise api_errors.InventoryException(
                             notfound=nonmatchingpats)
@@ -2213,14 +2157,14 @@
                 # "preferred" and "first_only" are private arguments that are
                 # currently only used in evaluate_fmri(), but could be made more
                 # generally useful.  "preferred" ensures that all potential
-                # matches from the preferred authority are generated before
-                # those from non-preferred authorities.  In the current
+                # matches from the preferred publisher are generated before
+                # those from non-preferred publishers.  In the current
                 # implementation, this consumes more memory.  "first_only"
                 # signals us to return only the first match, which allows us to
                 # save all the memory that "preferred" currently eats up.
                 preferred = kwargs.pop("preferred", False)
                 first_only = kwargs.pop("first_only", False)
-                pauth = self.cfg_cache.preferred_authority
+                ppub = self.cfg_cache.preferred_publisher
 
                 if not preferred:
                         for f in self.__inventory(*args, **kwargs):
@@ -2229,7 +2173,7 @@
                         nplist = []
                         firstnp = None
                         for f in self.__inventory(*args, **kwargs):
-                                if f[0].get_authority() == pauth:
+                                if f[0].get_publisher() == ppub:
                                         yield f
                                         if first_only:
                                                 return
@@ -2289,28 +2233,40 @@
                 failed = []
 
                 if not servers:
-                        servers = self.gen_authorities()
-
-                for auth in servers:
-                        ssl_tuple = self.get_ssl_credentials(
-                            authority = auth.get("prefix", None),
-                            origin = auth["origin"])
-                        try:
-                                uuid = self.get_uuid(auth["prefix"])
-                        except KeyError:
-                                uuid = None
+                        servers = self.gen_publishers()
+
+                for pub in servers:
+                        if not isinstance(pub, publisher.Publisher):
+                                origin = pub["origin"]
+                                try:
+                                        pub = self.get_publisher(
+                                            origin=origin)
+                                except api_errors.UnknownPublisher:
+                                        pass
+                                else:
+                                        repo = pub.selected_repository
+                                        origin = repo.get_origin(origin)
+                        else:
+                                origin = pub.selected_repository.origins[0]
+
+                        uuid = None
+                        ssl_tuple = (None, None)
+                        if isinstance(origin, publisher.RepositoryURI):
+                                ssl_tuple = (origin.ssl_key, origin.ssl_cert)
+                                uuid = self.get_uuid(pub.prefix)
+                                origin = origin.uri
 
                         try:
-                                res, v = versioned_urlopen(auth["origin"],
-                                    "search", [0], urllib.quote(args[0], ""),
+                                res, v = versioned_urlopen(origin, "search",
+                                    [0], urllib.quote(args[0], ""),
                                     ssl_creds=ssl_tuple, imgtype=self.type,
                                     uuid=uuid)
                         except urllib2.HTTPError, e:
                                 if e.code != httplib.NOT_FOUND:
-                                        failed.append((auth, e))
+                                        failed.append((pub, e))
                                 continue
                         except urllib2.URLError, e:
-                                failed.append((auth, e))
+                                failed.append((pub, e))
                                 continue
 
                         try:
@@ -2322,7 +2278,7 @@
                                         else:
                                                 yield fields[:4]
                         except socket.timeout, e:
-                                failed.append((auth, e))
+                                failed.append((pub, e))
                                 continue
 
                 if failed:
@@ -2360,9 +2316,9 @@
                         shutil.rmtree(self.dl_cache_dir, True)
 
         def salvagedir(self, path):
-                """Called when directory contains something and it's not supposed
-                to because it's being deleted. XXX Need to work out a better error
-                passback mechanism. Path is rooted in /...."""
+                """Called when directory contains something and it's not
+                supposed to because it's being deleted. XXX Need to work out a
+                better error passback mechanism. Path is rooted in /...."""
 
                 salvagedir = os.path.normpath(
                     os.path.join(self.imgdir, "lost+found",
@@ -2371,13 +2327,15 @@
                 parent = os.path.dirname(salvagedir)
                 if not os.path.exists(parent):
                         os.makedirs(parent)
-                shutil.move(os.path.normpath(os.path.join(self.root, path)), salvagedir)
+                shutil.move(os.path.normpath(os.path.join(self.root, path)),
+                    salvagedir)
                 # XXX need a better way to do this.
                 emsg("\nWarning - directory %s not empty - contents preserved "
                         "in %s" % (path, salvagedir))
 
         def temporary_file(self):
-                """ create a temp file under image directory for various purposes"""
+                """create a temp file under image directory for various
+                purposes"""
                 tempdir = os.path.normpath(os.path.join(self.imgdir, "tmp"))
                 if not os.path.exists(tempdir):
                         os.makedirs(tempdir)
@@ -2385,7 +2343,8 @@
                 os.close(fd)
                 return name
 
-        def expanddirs(self, dirs):
+        @staticmethod
+        def expanddirs(dirs):
                 """given a set of directories, return expanded set that includes
                 all components"""
                 out = set()
@@ -2403,16 +2362,16 @@
                 to assemble an appropriate image plan.  This is a helper
                 routine for some common operations in the client.
 
-                This method checks all authorities for a package match;
-                however, it defaults to choosing the preferred authority
+                This method checks all publishers for a package match;
+                however, it defaults to choosing the preferred publisher
                 when an ambiguous package name is specified.  If the user
-                wishes to install a package from a non-preferred authority,
-                the full FMRI that contains an authority should be used
+                wishes to install a package from a non-preferred publisher,
+                the full FMRI that contains a publisher should be used
                 to name the package."""
 
                 if filters is None:
                         filters = []
-                
+
                 error = 0
                 ip = imageplan.ImagePlan(self, progtrack, check_cancelation,
                     filters=filters, noexecute=noexecute)
@@ -2429,7 +2388,7 @@
                 # done first
 
                 inc_list = self.get_installed_unbound_inc_list()
-                
+
                 head = []
                 tail = []
 
@@ -2449,19 +2408,22 @@
                 for p in pkg_list:
                         progtrack.evaluate_progress()
                         try:
-                                conp = pkg.fmri.PkgFmri(p, self.attrs["Build-Release"])
+                                conp = pkg.fmri.PkgFmri(p,
+                                    self.attrs["Build-Release"])
                         except pkg.fmri.IllegalFmri:
                                 illegal_fmris.append(p)
                                 error = 1
                                 continue
                         try:
-                                conp = self.constraints.apply_constraints_to_fmri(conp)
+                                conp = \
+                                    self.constraints.apply_constraints_to_fmri(
+                                    conp)
                         except constraint.ConstraintException, e:
                                 error = 1
-                                constraint_violations.extend(str(e).split("\n"))                               
+                                constraint_violations.extend(str(e).split("\n"))
                                 continue
                         try:
-                                matches = list(self.inventory([ conp ],
+                                matches = list(self.inventory([conp],
                                     all_known = True))
                         except api_errors.InventoryException, e:
                                 assert(not (e.notfound and e.illegal))
@@ -2478,7 +2440,7 @@
                         npnames = {}
                         npmatch = []
                         for m, state in matches:
-                                if m.preferred_authority():
+                                if m.preferred_publisher():
                                         pnames[m.get_pkg_stem()] = 1
                                         pmatch.append(m)
                                 else:
@@ -2503,7 +2465,7 @@
 
                 if error != 0:
                         raise api_errors.PlanCreationException(unfound_fmris,
-                            multiple_matches, [], illegal_fmris, 
+                            multiple_matches, [], illegal_fmris,
                             constraint_violations=constraint_violations)
 
                 if verbose:
@@ -2599,7 +2561,7 @@
                 #
                 # This routine makes the distinction between the "target image",
                 # which will be altered, and the "running image", which is
-                # to say whatever image appears to contain the version of the 
+                # to say whatever image appears to contain the version of the
                 # pkg command we're running.
                 #
 
@@ -2621,7 +2583,7 @@
                         progtrack = progress.QuietProgressTracker()
 
                 img = self
-                
+
                 if not img.is_liveroot():
                         newimg = Image()
                         cmdpath = os.path.join(os.getcwd(), actual_cmd)
--- a/src/modules/client/imageconfig.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/client/imageconfig.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,30 +20,30 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 
 import ConfigParser
-import re
 import errno
 import os.path
 import pkg.fmri as fmri
-import pkg.misc as misc
 import pkg.client.api_errors as api_errors
+import pkg.client.publisher as publisher
 import pkg.client.variant as variant
-from pkg.misc import msg
+from pkg.misc import emsg
 import pkg.portable as portable
+import re
 
 class DepotStatus(object):
         """An object that encapsulates status about a depot server.
         This includes things like observed performance, availability,
         successful and unsuccessful transaction rates, etc."""
 
-        def __init__(self, authority, url):
-                """Authority is the authority prefix for this depot.
-                Url is the URL that names the server or mirror itself."""
+        def __init__(self, prefix, url):
+                """prefix is the publisher prefix for this depot.  url is the
+                URL that names the server or mirror itself."""
 
-                self.auth = authority
+                self.prefix = prefix
                 self.url = url.rstrip("/")
                 self.available = True
 
@@ -91,30 +91,30 @@
 
 class ImageConfig(object):
         """An ImageConfig object is a collection of configuration information:
-        URLs, authorities, properties, etc. that allow an Image to operate."""
+        URLs, publishers, properties, etc. that allow an Image to operate."""
 
         # XXX The SSL ssl_key attribute asserts that there is one
-        # ssl_key per authority.  This may be insufficiently general:  we
+        # ssl_key per publisher.  This may be insufficiently general:  we
         # may need one ssl_key per mirror.
 
         # XXX Use of ConfigParser is convenient and at most speculative--and
         # definitely not interface.
 
         def __init__(self):
-                self.authorities = {}
-                self.authority_status = {}
+                self.publishers = {}
+                self.publisher_status = {}
                 self.mirror_status = {}
                 self.properties = dict((
                     (p, str(v)) 
                     for p, v in default_policies.iteritems()
                 ))
-                self.preferred_authority = None
+                self.preferred_publisher = None
                 self.filters = {}
                 self.variants = variant.Variants()
                 self.children = []
 
         def __str__(self):
-                return "%s\n%s" % (self.authorities, self.properties)
+                return "%s\n%s" % (self.publishers, self.properties)
 
         def get_policy(self, policy):
                 """Return a boolean value for the named policy.  Returns
@@ -127,25 +127,27 @@
                         return policystr.lower() in ("true", "yes")
                 return default_policies[policy]
                 
-        def read(self, dir):
-                """Read the config files for the image from the given directory."""
+        def read(self, path):
+                """Read the config files for the image from the given directory.
+                """
 
                 cp = ConfigParser.SafeConfigParser()
 
-                ccfile = os.path.join(dir, CFG_FILE)
+                ccfile = os.path.join(path, CFG_FILE)
                 r = cp.read(ccfile)
                 if len(r) == 0:
-                        raise RuntimeError("Couldn't read configuration from %s" % ccfile)
+                        raise RuntimeError("Couldn't read configuration from "
+                            "%s" % ccfile)
 
                 assert r[0] == ccfile
 
                 for s in cp.sections():
                         if re.match("authority_.*", s):
-                                k, a = self.read_authority(cp, s)
+                                k, a = self.read_publisher(cp, s)
                                 ms = []
 
-                                self.authorities[k] = a
-                                self.authority_status[k] = DepotStatus(k,
+                                self.publishers[k] = a
+                                self.publisher_status[k] = DepotStatus(k,
                                     a["origin"])
 
                                 for mirror in a["mirrors"]:
@@ -153,8 +155,8 @@
 
                                 self.mirror_status[k] = ms
                                 
-                                if self.preferred_authority == None:
-                                        self.preferred_authority = k
+                                if self.preferred_publisher == None:
+                                        self.preferred_publisher = k
 
                 # read in the policy section to provide backward
                 # compatibility for older images
@@ -175,36 +177,56 @@
                         for o in cp.options("variant"):
                                 self.variants[o] = cp.get("variant", o)
 
-                if "preferred-authority" in self.properties:
-                        self.preferred_authority = self.properties["preferred-authority"]
+                try:
+                        self.preferred_publisher = \
+                            self.properties["preferred-publisher"]
+                except KeyError:
+                        try:
+                                # Compatibility with older clients.
+                                self.properties["preferred-publisher"] = \
+                                    self.properties["preferred-authority"]
+                                self.preferred_publisher = \
+                                    self.properties["preferred-publisher"]
+                                del self.properties["preferred-authority"]
+                        except KeyError:
+                                pass
 
-                # read disabled authority file
+                # read disabled publisher file
                 # XXX when compatility with the old code is no longer needed,
                 # this can be removed
                 cp = ConfigParser.SafeConfigParser()
-                dafile = os.path.join(dir, DA_FILE)
+                dafile = os.path.join(path, DA_FILE)
                 if os.path.exists(dafile):
                         r = cp.read(dafile)
                         if len(r) == 0:
-                                raise RuntimeError("Couldn't read configuration from %s" % dafile)
+                                raise RuntimeError("Couldn't read "
+                                    "configuration from %s" % dafile)
                         for s in cp.sections():
                                 if re.match("authority_.*", s):
-                                        k, a = self.read_authority(cp, s)
-                                        self.authorities[k] = a
+                                        k, a = self.read_publisher(cp, s)
+                                        self.publishers[k] = a
                                         # status objects are not created for
-                                        # disabled authorities
+                                        # disabled publishers
 
-        def write(self, dir):
+        def write(self, path):
                 """Write the configuration to the given directory"""
                 cp = ConfigParser.SafeConfigParser()
                 # XXX the use of the disabled_auth file can be removed when
                 # compatibility with the older code is no longer needed
                 da = ConfigParser.SafeConfigParser()
-                self.properties["preferred-authority"] = self.preferred_authority
+
+                try:
+                        del self.properties["preferred-publisher"]
+                except KeyError:
+                        pass
+
+                self.properties["preferred-authority"] = \
+                    self.preferred_publisher
 
                 cp.add_section("property")
                 for p in self.properties:
-                        cp.set("property", p, self.properties[p].encode('utf-8'))
+                        cp.set("property", p,
+                            self.properties[p].encode("utf-8"))
 
                 cp.add_section("filter")
                 for f in self.filters:
@@ -214,27 +236,56 @@
                 for f in self.variants:
                         cp.set("variant", f, str(self.variants[f]))
 
-                for a in self.authorities:
-                        auth = self.authorities[a]
-                        section = "authority_%s" % auth["prefix"]
+                for prefix in self.publishers:
+                        pub = self.publishers[prefix]
+                        section = "authority_%s" % pub.prefix
 
                         c = cp
-                        if auth["disabled"]:
+                        if pub.disabled:
                                 c = da
 
                         c.add_section(section)
-                        c.set(section, "prefix", auth["prefix"])
-                        c.set(section, "origin", auth["origin"])
-                        c.set(section, "disabled", str(auth["disabled"]))
-                        c.set(section, "mirrors", str(auth["mirrors"]))
-                        c.set(section, "ssl_key", str(auth["ssl_key"]))
-                        c.set(section, "ssl_cert", str(auth["ssl_cert"]))
-                        c.set(section, "uuid", str(auth["uuid"]))
+                        c.set(section, "alias", str(pub.alias))
+                        c.set(section, "prefix", str(pub.prefix))
+                        c.set(section, "disabled", str(pub.disabled))
+
+                        repo = pub.selected_repository
+                        c.set(section, "origin", repo.origins[0].uri)
+                        c.set(section, "mirrors",
+                            str([u.uri for u in repo.mirrors]))
+
+                        # XXX this should be per origin or mirror
+                        c.set(section, "ssl_key", str(pub["ssl_key"]))
+                        c.set(section, "ssl_cert", str(pub["ssl_cert"]))
+
+                        # XXX this should really be client_uuid, but is being
+                        # left with this name for compatibility with older
+                        # clients.
+                        c.set(section, "uuid", str(pub.client_uuid))
+
+                        # Write selected repository data.
+                        # XXX this is temporary until a switch to a more
+                        # expressive configuration format is made.
+                        repo = pub.selected_repository
+                        repo_data = {
+                            "collection_type": repo.collection_type,
+                            "description": repo.description,
+                            "legal_uris": [u.uri for u in repo.legal_uris],
+                            "name": repo.name,
+                            "refresh_seconds": repo.refresh_seconds,
+                            "registered": repo.registered,
+                            "registration_uri": repo.registration_uri,
+                            "related_uris": [u.uri for u in repo.related_uris],
+                            "sort_policy": repo.sort_policy,
+                        }
+
+                        for key, val in repo_data.iteritems():
+                                c.set(section, "repo.%s" % key, str(val))
 
                 # XXX Child images
 
                 for afile, acp in [(CFG_FILE, cp), (DA_FILE, da)]:
-                        thefile = os.path.join(dir, afile)
+                        thefile = os.path.join(path, afile)
                         if len(acp.sections()) == 0:
                                 if os.path.exists(thefile):
                                         portable.remove(thefile)
@@ -248,18 +299,18 @@
                                 raise
                         acp.write(f)
 
-        def delete_authority(self, auth):
-                del self.authorities[auth]
+        def remove_publisher(self, prefix):
+                del self.publishers[prefix]
 
         @staticmethod
-        def read_list(str):
+        def read_list(list_str):
                 """Take a list in string representation and convert it back
                 to a Python list."""
                 
                 # Strip brackets and any whitespace
-                str = str.strip("][ ")
+                list_str = list_str.strip("][ ")
                 # Strip comma and any whitespeace
-                lst = str.split(", ")
+                lst = list_str.split(", ")
                 # Strip empty whitespace, single, and double quotation marks
                 lst = [ s.strip("' \"") for s in lst ]
                 # Eliminate any empty strings
@@ -267,49 +318,129 @@
 
                 return lst
 
-        def read_authority(self, cp, s):
-                # authority block has prefix, origin, and mirrors
-                a = {}
-                k = cp.get(s, "prefix")
+        def read_publisher(self, cp, s):
+                # publisher block has alias, prefix, origin, and mirrors
+                try:
+                        alias = cp.get(s, "alias")
+                except ConfigParser.NoOptionError:
+                        alias = None
 
-                if k.startswith(fmri.PREF_AUTH_PFX):
+                prefix = cp.get(s, "prefix")
+
+                if prefix.startswith(fmri.PREF_PUB_PFX):
                         raise RuntimeError(
-                            "Invalid Authority name: %s" % k)
+                            "Invalid Publisher name: %s" % prefix)
 
-                a["prefix"] = k
-                a["origin"] = cp.get(s, "origin")
+                origin = cp.get(s, "origin")
                 try:
                         d = cp.get(s, "disabled")
                 except ConfigParser.NoOptionError:
                         d = 'False'
-                a["disabled"] = d.lower() in ("true", "yes")
+                disabled = d.lower() in ("true", "yes")
 
                 mir_str = cp.get(s, "mirrors")
                 if mir_str == "None":
-                        a["mirrors"] = []
+                        mirrors = []
                 else:
-                        a["mirrors"] = self.read_list(mir_str)
+                        mirrors = self.read_list(mir_str)
 
                 try:
-                        a["ssl_key"] = cp.get(s, "ssl_key")
-                        if a["ssl_key"] == "None":
-                                a["ssl_key"] = None
+                        ssl_key = cp.get(s, "ssl_key")
+                        if ssl_key == "None":
+                                ssl_key = None
                 except ConfigParser.NoOptionError:
-                        a["ssl_key"] = None
+                        ssl_key = None
+
+                try:
+                        ssl_cert = cp.get(s, "ssl_cert")
+                        if ssl_cert == "None":
+                                ssl_cert = None
+                except ConfigParser.NoOptionError:
+                        ssl_cert = None
 
                 try:
-                        a["ssl_cert"] = cp.get(s, "ssl_cert")
-                        if a["ssl_cert"] == "None":
-                                a["ssl_cert"] = None
+                        # XXX this should really be client_uuid, but is being
+                        # left with this name for compatibility with older
+                        # clients.
+                        client_uuid = cp.get(s, "uuid")
+                        if client_uuid == "None":
+                                client_uuid = None
                 except ConfigParser.NoOptionError:
-                        a["ssl_cert"] = None
+                        client_uuid = None
+
+                # Load selected repository data.
+                # XXX this is temporary until a switch to a more expressive
+                # configuration format is made.
+                repo_data = {
+                    "collection_type": None,
+                    "description": None,
+                    "legal_uris": None,
+                    "name": None,
+                    "refresh_seconds": None,
+                    "registered": None,
+                    "registration_uri": None,
+                    "related_uris": None,
+                    "sort_policy": None,
+                }
+
+                for key in repo_data:
+                        try:
+                                val = cp.get(s, "repo.%s" % key)
+                                if key.endswith("_uris"):
+                                        val = self.read_list(val)
+                                        if val == "None":
+                                                val = []
+                                else:
+                                        if val == "None":
+                                                val = None
+                                repo_data[key] = val
+                        except ConfigParser.NoOptionError:
+                                if key.endswith("_uris"):
+                                        repo_data[key] = []
+                                else:
+                                        repo_data[key] = None
 
-                try:
-                        a["uuid"] = cp.get(s, "uuid")
-                except ConfigParser.NoOptionError:
-                        a["uuid"] = "None"
+                # Normalize/sanitize repository data.
+                val = repo_data["registered"]
+                if val is not None and val.lower() in ("true", "yes", "1"):
+                        repo_data["registered"] = True
+                else:
+                        repo_data["registered"] = False
+
+                for attr in ("collection_type", "sort_policy"):
+                        if not repo_data[attr]:
+                                # Assume default value for attr.
+                                del repo_data[attr]
+
+                # Guard against invalid configuration for ssl information. If
+                # this isn't done, the user won't be able to load the client
+                # to fix the problem.
+                if not origin.startswith("https"):
+                        ssl_key = None
+                        ssl_cert = None
 
-                a["origin"] = \
-                    misc.url_affix_trailing_slash(a["origin"])
+                if ssl_key:
+                        ssl_key = os.path.abspath(ssl_key)
+                        if not os.path.exists(ssl_key):
+                                # XXX need client messaging framework
+                                emsg(api_errors.NoSuchCertificate(ssl_key,
+                                    uri=origin, publisher=prefix))
+                                ssl_key = None
 
-                return k, a
+                if ssl_cert:
+                        ssl_cert = os.path.abspath(ssl_cert)
+                        if not os.path.exists(ssl_cert):
+                                # XXX need client messaging framework
+                                emsg(api_errors.NoSuchCertificate(ssl_cert,
+                                    uri=origin, publisher=prefix))
+                                ssl_cert = None
+
+                r = publisher.Repository(**repo_data)
+                r.add_origin(origin, ssl_cert=ssl_cert, ssl_key=ssl_key)
+                for m in mirrors:
+                        r.add_mirror(m, ssl_cert=ssl_cert, ssl_key=ssl_key)
+                pub = publisher.Publisher(prefix, alias=alias,
+                    client_uuid=client_uuid, disabled=disabled,
+                    repositories=[r])
+
+                return prefix, pub
--- a/src/modules/client/imageplan.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/client/imageplan.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,18 +20,18 @@
 # CDDL HEADER END
 #
 
+#
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import os
 import errno
-import pkg.fmri as fmri
 import pkg.client.api_errors as api_errors
 import pkg.client.imagestate as imagestate
 import pkg.client.pkgplan as pkgplan
 import pkg.client.indexer as indexer
 import pkg.search_errors as se
-from pkg.client.imageconfig import REQUIRE_OPTIONAL
 import pkg.client.actuator as actuator
 
 from pkg.client.filter import compile_filter
@@ -71,7 +71,8 @@
         plan to identify when this operation is safe or unsafe."""
 
         def __init__(self, image, progtrack, check_cancelation,
-            recursive_removal=False, filters=None, variants=None, noexecute=False):
+            recursive_removal=False, filters=None, variants=None,
+            noexecute=False):
                 if filters is None:
                         filters = []
                 self.image = image
@@ -121,7 +122,7 @@
                 s = ""
                 for pp in self.pkg_plans:
                         s = s + "%s\n" % pp
-                
+
                 s = s + "Actuators:\n%s" % self.actuators
                 return s
 
@@ -141,21 +142,22 @@
                         msg("%s -> %s" % (pp.origin_fmri, pp.destination_fmri))
                 msg("Actuators:\n%s" % self.actuators)
 
-        def is_proposed_fmri(self, fmri):
+        def is_proposed_fmri(self, pfmri):
                 for pf in self.target_fmris:
-                        if self.image.fmri_is_same_pkg(fmri, pf):
-                                return not self.image.fmri_is_successor(fmri, pf)
+                        if self.image.fmri_is_same_pkg(pfmri, pf):
+                                return not self.image.fmri_is_successor(pfmri,
+                                    pf)
                 return False
 
-        def is_proposed_rem_fmri(self, fmri):
+        def is_proposed_rem_fmri(self, pfmri):
                 for pf in self.target_rem_fmris:
-                        if self.image.fmri_is_same_pkg(fmri, pf):
+                        if self.image.fmri_is_same_pkg(pfmri, pf):
                                 return True
                 return False
 
-        def propose_fmri(self, fmri):
+        def propose_fmri(self, pfmri):
                 # is a version of fmri.stem in the inventory?
-                if self.image.has_version_installed(fmri):
+                if self.image.has_version_installed(pfmri):
                         return
 
                 #   is there a freeze or incorporation statement?
@@ -166,51 +168,50 @@
                 # update so that we meet any optional dependencies
                 #
 
-                fmri = self.image.constraints.apply_constraints_to_fmri(fmri)
-                self.image.fmri_set_default_authority(fmri)
-                
+                pfmri = self.image.constraints.apply_constraints_to_fmri(pfmri)
+                self.image.fmri_set_default_publisher(pfmri)
+
                 # Add fmri to target list only if it (or a successor) isn't
                 # there already.
                 for i, p in enumerate(self.target_fmris):
-                        if self.image.fmri_is_successor(fmri, p):
-                                self.target_fmris[i] = fmri
+                        if self.image.fmri_is_successor(pfmri, p):
+                                self.target_fmris[i] = pfmri
                                 break
-                        if self.image.fmri_is_successor(p, fmri):
+                        if self.image.fmri_is_successor(p, pfmri):
                                 break
                 else:
-                        self.target_fmris.append(fmri)
-
+                        self.target_fmris.append(pfmri)
                 return
 
-        def get_proposed_version(self, fmri):
+        def get_proposed_version(self, pfmri):
                 """ Return version of fmri already proposed, or None
                 if not proposed yet."""
                 for p in self.target_fmris:
-                        if fmri.get_name() == p.get_name():
+                        if pfmri.get_name() == p.get_name():
                                 return p
                 else:
                         return None
 
-        def older_version_proposed(self, fmri):
-                # returns true if older version of this fmri has been
-                # proposed already
+        def older_version_proposed(self, pfmri):
+                # returns true if older version of this pfmri has been proposed
+                # already
                 for p in self.target_fmris:
-                        if self.image.fmri_is_successor(fmri, p):
+                        if self.image.fmri_is_successor(pfmri, p):
                                 return True
                 return False
 
         # XXX Need to make sure that the same package isn't being added and
         # removed in the same imageplan.
-        def propose_fmri_removal(self, fmri):
-                if not self.image.has_version_installed(fmri):
+        def propose_fmri_removal(self, pfmri):
+                if not self.image.has_version_installed(pfmri):
                         return
 
                 for i, p in enumerate(self.target_rem_fmris):
-                        if self.image.fmri_is_successor(fmri, p):
-                                self.target_rem_fmris[i] = fmri
+                        if self.image.fmri_is_successor(pfmri, p):
+                                self.target_rem_fmris[i] = pfmri
                                 break
                 else:
-                        self.target_rem_fmris.append(fmri)
+                        self.target_rem_fmris.append(pfmri)
 
         def gen_new_installed_pkgs(self):
                 """ generates all the fmris in the new set of installed pkgs"""
@@ -220,21 +221,22 @@
                 for p in self.pkg_plans:
                         p.update_pkg_set(fmri_set)
 
-                for fmri in fmri_set:
-                        yield fmri
+                for pfmri in fmri_set:
+                        yield pfmri
 
         def gen_new_installed_actions(self):
                 """generates actions in new installed image"""
-                for fmri in self.gen_new_installed_pkgs():
-                        m = self.image.get_manifest(fmri)
+                for pfmri in self.gen_new_installed_pkgs():
+                        m = self.image.get_manifest(pfmri)
                         for act in m.gen_actions(self.new_excludes):
                                 yield act
 
-        def gen_new_installed_actions_bytype(self, type):
+        def gen_new_installed_actions_bytype(self, atype):
                 """generates actions in new installed image"""
-                for fmri in self.gen_new_installed_pkgs():
-                        m = self.image.get_manifest(fmri)
-                        for act in m.gen_actions_by_type(type, self.new_excludes):
+                for pfmri in self.gen_new_installed_pkgs():
+                        m = self.image.get_manifest(pfmri)
+                        for act in m.gen_actions_by_type(atype,
+                            self.new_excludes):
                                 yield act
 
         def get_directories(self):
@@ -267,7 +269,7 @@
                                         d[t] = [act]
                 self.__link_actions = d
                 return self.__link_actions
-                
+
         def evaluate_fmri(self, pfmri):
                 self.progtrack.evaluate_progress(pfmri)
                 self.image.state.set_target(pfmri, self.__intent)
@@ -275,7 +277,7 @@
                 if self.check_cancelation():
                         raise api_errors.CanceledException()
 
-                self.image.fmri_set_default_authority(pfmri)
+                self.image.fmri_set_default_publisher(pfmri)
 
                 m = self.image.get_manifest(pfmri)
 
@@ -288,40 +290,41 @@
 
                 # build list of (action, fmri, constraint) of dependencies
                 a_list = [
-                    (a,) + a.parse(self.image, pfmri.get_name())                        
+                    (a,) + a.parse(self.image, pfmri.get_name())
                     for a in m.gen_actions_by_type("depend", self.new_excludes)
                 ]
 
                 # Update constraints first to avoid problems w/ depth first
-                # traversal of dependencies; we may violate an existing constraint
-                # here.
+                # traversal of dependencies; we may violate an existing
+                # constraint here.
                 if self.image.constraints.start_loading(pfmri):
                         for a, f, constraint in a_list:
-                                self.image.constraints.update_constraints(constraint)
+                                self.image.constraints.update_constraints(
+                                    constraint)
                         self.image.constraints.finish_loading(pfmri)
 
                 # now check what work is required
                 for a, f, constraint in a_list:
- 
+
                         # discover if we have an installed or proposed
-                        # version of this pkg already; proposed fmris 
+                        # version of this pkg already; proposed fmris
                         # will always be newer
                         ref_fmri = self.get_proposed_version(f)
                         if not ref_fmri:
                                 ref_fmri = self.image.get_version_installed(f)
 
-                        # check if new constraint requires us to make any changes
-                        # to already proposed pkgs or existing ones.
+                        # check if new constraint requires us to make any
+                        # changes to already proposed pkgs or existing ones.
                         if not constraint.check_for_work(ref_fmri):
                                 continue
-                        # Apply any active optional/incorporation constraints 
+                        # Apply any active optional/incorporation constraints
                         # from other packages
 
                         cf = self.image.constraints.apply_constraints_to_fmri(f)
-                         
+
                         # This will be the newest version of the specified
                         # dependency package, coming from the preferred
-                        # authority, if it's available there.
+                        # publisher, if it's available there.
                         cf = self.image.inventory([ cf ],
                             all_known = True, preferred = True,
                             first_only = True).next()[0]
@@ -353,7 +356,7 @@
                         self.target_update_count += 1
                 else:
                         self.target_insall_count += 1
-                        
+
                 self.pkg_plans.append(pp)
 
         def evaluate_fmri_removal(self, pfmri):
@@ -406,9 +409,6 @@
 
         def evaluate(self):
                 assert self.state == UNEVALUATED
-                
-                evaluate_npkgs = len(self.target_fmris) + \
-                    len(self.target_rem_fmris)
 
                 outstring = ""
 
@@ -445,19 +445,22 @@
 
                 self.state = EVALUATED_PKGS
 
-                self.removal_actions = [ (p, src, dest)
-                                         for p in self.pkg_plans
-                                         for src, dest in p.gen_removal_actions()
+                self.removal_actions = [
+                    (p, src, dest)
+                    for p in self.pkg_plans
+                    for src, dest in p.gen_removal_actions()
                 ]
 
-                self.update_actions = [ (p, src, dest)
-                                        for p in self.pkg_plans
-                                        for src, dest in p.gen_update_actions()
+                self.update_actions = [
+                    (p, src, dest)
+                    for p in self.pkg_plans
+                    for src, dest in p.gen_update_actions()
                 ]
 
-                self.install_actions = [ (p, src, dest)
-                                         for p in self.pkg_plans
-                                         for src, dest in p.gen_install_actions()
+                self.install_actions = [
+                    (p, src, dest)
+                    for p in self.pkg_plans
+                    for src, dest in p.gen_install_actions()
                 ]
 
                 self.progtrack.evaluate_progress()
@@ -481,7 +484,8 @@
                         if a[1].name == "file":
                                 attrs = a[1].attrs
                                 fname = attrs.get("original_name",
-                                    "%s:%s" % (a[0].origin_fmri.get_name(), attrs["path"]))
+                                    "%s:%s" % (a[0].origin_fmri.get_name(),
+                                    attrs["path"]))
                                 named_removals[fname] = \
                                     (i - deletions,
                                     id(self.removal_actions[i-deletions][1]))
@@ -491,20 +495,22 @@
                 self.progtrack.evaluate_progress()
 
                 for a in self.install_actions:
-                        # In order to handle editable files that move their path or
-                        # change pkgs, for all new files with original_name attribute,
-                        # make sure file isn't being removed by checking removal list.
-                        # if it is, tag removal to save file, and install to recover
-                        # cached version... caching is needed if directories
-                        # are removed or don't exist yet.
-                        if a[2].name == "file" and "original_name" in a[2].attrs and \
-                            a[2].attrs["original_name"] in named_removals:
+                        # In order to handle editable files that move their path
+                        # or change pkgs, for all new files with original_name
+                        # attribute, make sure file isn't being removed by
+                        # checking removal list.  If it is, tag removal to save
+                        # file, and install to recover cached version... caching
+                        # is needed if directories are removed or don't exist
+                        # yet.
+                        if (a[2].name == "file" and "original_name" in
+                            a[2].attrs and a[2].attrs["original_name"] in
+                            named_removals):
                                 cache_name = a[2].attrs["original_name"]
                                 index = named_removals[cache_name][0]
-                                assert(id(self.removal_actions[index][1]) == 
+                                assert(id(self.removal_actions[index][1]) ==
                                        named_removals[cache_name][1])
-                                self.removal_actions[index][1].attrs["save_file"] = \
-                                    cache_name
+                                self.removal_actions[index][1].attrs[
+                                    "save_file"] = cache_name
                                 a[2].attrs["save_file"] = cache_name
 
                         self.actuators.scan_install(a[2].attrs)
@@ -514,13 +520,16 @@
                 l_actions = self.get_link_actions()
                 l_refresh = []
                 for a in self.update_actions:
-                        # for any files being updated that are the target of
+                        # For any files being updated that are the target of
                         # _any_ hardlink actions, append the hardlink actions
-                        # to the update list so that they are not broken...
-                        if a[2].name == "file": 
+                        # to the update list so that they are not broken.
+                        if a[2].name == "file":
                                 path = a[2].attrs["path"]
                                 if path in l_actions:
-                                        l_refresh.extend([(a[0], l, l) for l in l_actions[path]])
+                                        l_refresh.extend([
+                                            (a[0], l, l)
+                                            for l in l_actions[path]
+                                        ])
 
                         # scan both old and new actions
                         # repairs may result in update action w/o orig action
@@ -568,7 +577,7 @@
                 preexecute, execute and postexecute
                 execute actions need to be sorted across packages
                 """
-                
+
                 assert self.state == EVALUATED_OK
 
                 if self.nothingtodo():
@@ -641,34 +650,39 @@
                 # The following constraints are key in understanding imageplan
                 # execution:
                 #
-                # 1) All non-directory actions (files, users, hardlinks, symbolic
-                # links, etc.) must appear in only a single installed package. 
+                # 1) All non-directory actions (files, users, hardlinks,
+                # symbolic links, etc.) must appear in only a single installed
+                # package.
                 #
                 # 2) All installed packages must be consistent in their view of
-                # action types; if /usr/openwin is a directory in one package, it
-                # must be a directory in all packages, never a symbolic link.  This
-                # includes implicitly defined directories.
-                # 
-                # A key goal in IPS is to be able to undergo an arbtrary transformation
-                # in package contents in a single step.  Packages must be able to exchange
-                # files, convert directories to symbolic links, etc.; so long as the start
-                # and end states meet the above two constraints IPS must be able to transition
+                # action types; if /usr/openwin is a directory in one package,
+                # it must be a directory in all packages, never a symbolic link;
+                # this includes implicitly defined directories.
+                #
+                # A key goal in IPS is to be able to undergo an arbtrary
+                # transformation in package contents in a single step.  Packages
+                # must be able to exchange files, convert directories to
+                # symbolic links, etc.; so long as the start and end states meet
+                # the above two constraints IPS must be able to transition
                 # between the states directly.  This leads to the following:
-                # 
-                # 1) All actions must be ordered across packages; packages cannot be updated 
-                #    one at a time.
+                #
+                # 1) All actions must be ordered across packages; packages
+                # cannot be updated one at a time.
                 #
-                #    This is readily apparent when one considers two packages exchanging 
-                #    files in their new versions; in each case the package now owning the
-                #    file must be installed last, but it is not possible for each package to
-                #    to be installed before the other.  Clearly, all the removals must be done 
-                #    first, followed by the installs and updates.
+                #    This is readily apparent when one considers two packages
+                #    exchanging files in their new versions; in each case the
+                #    package now owning the file must be installed last, but it
+                #    is not possible for each package to be installed before the
+                #    other.  Clearly, all the removals must be done first,
+                #    followed by the installs and updates.
                 #
-                # 2) Installs of new actions must preceed updates of existing ones.
-                #    
-                #    In order to accomodate changes of file ownership of existing files
-                #    to a newly created user, it is necessary for the installation of that
-                #    user to preceed the update of files to reflect their new ownership.
+                # 2) Installs of new actions must preceed updates of existing
+                # ones.
+                #
+                #    In order to accomodate changes of file ownership of
+                #    existing files to a newly created user, it is necessary
+                #    for the installation of that user to preceed the update of
+                #    files to reflect their new ownership.
                 #
 
                 if self.nothingtodo():
@@ -681,7 +695,7 @@
 
                 try:
                         try:
-                
+
                                 # execute removals
 
                                 self.progtrack.actions_set_goal("Removal Phase",
@@ -731,7 +745,7 @@
                         self.actuators.exec_post_actuators(self.image)
 
                 self.state = EXECUTED_OK
-                
+
                 # reduce memory consumption
 
                 del self.removal_actions
@@ -743,7 +757,7 @@
                 del self.__directories
 
                 del self.actuators
-                
+
                 # Perform the incremental update to the search indexes
                 # for all changed packages
                 if self.update_index:
--- a/src/modules/client/progress.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/client/progress.py	Mon Mar 09 16:09:13 2009 -0500
@@ -29,7 +29,7 @@
 import sys
 import os
 import time
-from pkg.misc import msg, PipeError
+from pkg.misc import PipeError
 import pkg.portable as portable
 
 IND_DELAY = 0.05
@@ -59,9 +59,9 @@
         def reset(self):
                 self.cat_cur_catalog = None
 
-                self.refresh_auth_cnt = 0
-                self.refresh_cur_auth_cnt = 0
-                self.refresh_cur_auth = None
+                self.refresh_pub_cnt = 0
+                self.refresh_cur_pub_cnt = 0
+                self.refresh_cur_pub = None
 
                 self.ver_cur_fmri = None
 
@@ -98,14 +98,14 @@
         def catalog_done(self):
                 self.cat_output_done()
 
-        def refresh_start(self, auth_cnt):
-                self.refresh_auth_cnt = auth_cnt
-                self.refresh_cur_auth_cnt = 0
+        def refresh_start(self, pub_cnt):
+                self.refresh_pub_cnt = pub_cnt
+                self.refresh_cur_pub_cnt = 0
                 self.refresh_output_start()
 
-        def refresh_progress(self, auth):
-                self.refresh_cur_auth = auth
-                self.refresh_cur_auth_cnt += 1
+        def refresh_progress(self, pub):
+                self.refresh_cur_pub = pub
+                self.refresh_cur_pub_cnt += 1
                 self.refresh_output_progress()
 
         def refresh_done(self):
@@ -170,7 +170,8 @@
                 assert self.dl_cur_nbytes == self.dl_goal_nbytes
 
         def download_get_progress(self):
-                return (self.dl_cur_npkgs, self.dl_cur_nfiles, self.dl_cur_nbytes)
+                return (self.dl_cur_npkgs, self.dl_cur_nfiles,
+                    self.dl_cur_nbytes)
 
         def actions_set_goal(self, phase, nactions):
                 self.act_phase = phase
@@ -208,10 +209,12 @@
         # called directly.  Subclasses should implement all of these methods.
         #
         def cat_output_start(self):
-                raise NotImplementedError("cat_output_start() not implemented in superclass")
+                raise NotImplementedError("cat_output_start() not implemented "
+                    "in superclass")
 
         def cat_output_done(self):
-                raise NotImplementedError("cat_output_done() not implemented in superclass")
+                raise NotImplementedError("cat_output_done() not implemented "
+                    "in superclass")
 
         def refresh_output_start(self):
                 return
@@ -223,37 +226,48 @@
                 return
 
         def eval_output_start(self):
-                raise NotImplementedError("eval_output_start() not implemented in superclass")
+                raise NotImplementedError("eval_output_start() not implemented "
+                    "in superclass")
 
         def eval_output_progress(self):
-                raise NotImplementedError("eval_output_progress() not implemented in superclass")
+                raise NotImplementedError("eval_output_progress() not "
+                    "implemented in superclass")
 
         def eval_output_done(self):
-                raise NotImplementedError("eval_output_done() not implemented in superclass")
+                raise NotImplementedError("eval_output_done() not implemented "
+                    "in superclass")
 
         def ver_output(self):
-                raise NotImplementedError("ver_output() not implemented in superclass")
+                raise NotImplementedError("ver_output() not implemented in "
+                    "superclass")
 
         def ver_output_error(self, actname, errors):
-                raise NotImplementedError("ver_output_error() not implemented in superclass")
+                raise NotImplementedError("ver_output_error() not implemented "
+                    "in superclass")
 
         def dl_output(self):
-                raise NotImplementedError("dl_output() not implemented in superclass")
+                raise NotImplementedError("dl_output() not implemented in "
+                    "superclass")
 
         def dl_output_done(self):
-                raise NotImplementedError("dl_output_done() not implemented in superclass")
+                raise NotImplementedError("dl_output_done() not implemented "
+                    "in superclass")
 
-        def act_output(self):
-                raise NotImplementedError("act_output() not implemented in superclass")
+        def act_output(self, force=False):
+                raise NotImplementedError("act_output() not implemented in "
+                    "superclass")
 
         def act_output_done(self):
-                raise NotImplementedError("act_output_done() not implemented in superclass")
+                raise NotImplementedError("act_output_done() not implemented "
+                    "in superclass")
 
-        def ind_output(self):
-                raise NotImplementedError("ind_output() not implemented in superclass")
+        def ind_output(self, force=False):
+                raise NotImplementedError("ind_output() not implemented in "
+                    "superclass")
 
         def ind_output_done(self):
-                raise NotImplementedError("ind_output_done() not implemented in superclass")
+                raise NotImplementedError("ind_output_done() not implemented "
+                    "in superclass")
 
 
 class ProgressTrackerException(Exception):
@@ -274,31 +288,44 @@
         def __init__(self):
                 ProgressTracker.__init__(self)
 
-        def cat_output_start(self): return
+        def cat_output_start(self):
+                return
 
-        def cat_output_done(self): return
+        def cat_output_done(self):
+                return
 
-        def eval_output_start(self): return
+        def eval_output_start(self):
+                return
 
-        def eval_output_progress(self): return
+        def eval_output_progress(self):
+                return
 
-        def eval_output_done(self): return
+        def eval_output_done(self):
+                return
 
-        def ver_output(self): return
+        def ver_output(self):
+                return
 
-        def ver_output_error(self, actname, errors): return
+        def ver_output_error(self, actname, errors):
+                return
 
-        def dl_output(self): return
+        def dl_output(self):
+                return
 
-        def dl_output_done(self): return
+        def dl_output_done(self):
+                return
 
-        def act_output(self): return
+        def act_output(self, force=False):
+                return
 
-        def act_output_done(self): return
+        def act_output_done(self):
+                return
 
-        def ind_output(self): return
+        def ind_output(self, force=False):
+                return
 
-        def ind_output_done(self): return
+        def ind_output_done(self):
+                return
 
 
 class NullProgressTracker(QuietProgressTracker):
@@ -321,19 +348,26 @@
                 ProgressTracker.__init__(self)
                 self.dl_last_printed_pkg = None
 
-        def cat_output_start(self): return
+        def cat_output_start(self):
+                return
 
-        def cat_output_done(self): return
+        def cat_output_done(self):
+                return
 
-        def eval_output_start(self): return
+        def eval_output_start(self):
+                return
 
-        def eval_output_progress(self): return
+        def eval_output_progress(self):
+                return
 
-        def eval_output_done(self): return
+        def eval_output_done(self):
+                return
 
-        def ver_output(self): return
+        def ver_output(self):
+                return
 
-        def ver_output_error(self, actname, errors): return
+        def ver_output_error(self, actname, errors):
+                return
 
         def dl_output(self):
                 try:
@@ -358,7 +392,7 @@
                                 raise PipeError, e
                         raise
 
-        def act_output(self):
+        def act_output(self, force=False):
                 if self.act_phase != self.act_phase_last:
                         try:
                                 print "%s ... " % self.act_phase,
@@ -378,7 +412,7 @@
                                 raise PipeError, e
                         raise
 
-        def ind_output(self):
+        def ind_output(self, force=False):
                 if self.ind_phase != self.ind_phase_last:
                         try:
                                 print "%s ... " % self.ind_phase,
@@ -473,8 +507,8 @@
                         print " " * self.cat_curstrlen,
                         print self.cr,
                         s = "Refreshing Catalog %d/%d %s" % \
-                            (self.refresh_cur_auth_cnt, self.refresh_auth_cnt,
-                            self.refresh_cur_auth)
+                            (self.refresh_cur_pub_cnt, self.refresh_pub_cnt,
+                            self.refresh_cur_pub)
                         self.cat_curstrlen = len(s)
                         print "%s" % s,
                         sys.stdout.flush()
@@ -515,7 +549,8 @@
                         self.spinner = 0
                 try:
                         print self.cr,
-                        s = "Creating Plan %c" % self.spinner_chars[self.spinner]
+                        s = "Creating Plan %c" % self.spinner_chars[
+                            self.spinner]
                         self.cat_curstrlen = len(s)
                         print "%s" % s,
                         sys.stdout.flush()
@@ -616,7 +651,7 @@
                                 raise PipeError, e
                         raise
 
-        def act_output(self, force = False):
+        def act_output(self, force=False):
                 if force or (time.time() - self.last_print_time) >= 0.05:
                         self.last_print_time = time.time()
                 else:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/client/publisher.py	Mon Mar 09 16:09:13 2009 -0500
@@ -0,0 +1,891 @@
+#!/usr/bin/python2.4
+#
+# 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 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+#
+# NOTE: Any changes to this file are considered a change in client api
+# interfaces and must be fully documented in doc/client_api_versions.txt
+# if they are visible changes to the public interfaces provided.
+#
+# This also means that changes to the interfaces here must be reflected in
+# the client version number and compatible_versions specifier found in
+# modules/client/api.py:__init__.
+#
+import copy
+import os
+import pkg.client.api_errors as api_errors
+import pkg.misc
+import pkg.Uuid25
+import urlparse
+
+# The "core" type indicates that a repository contains all of the dependencies
+# declared by packages in the repository.  It is primarily used for operating
+# system repositories.
+REPO_CTYPE_CORE = "core"
+
+# The "supplemental" type indicates that a repository contains packages that
+# rely on or are intended to be used with packages located in another
+# repository.
+REPO_CTYPE_SUPPLEMENTAL = "supplemental"
+
+# Mapping of constant values to names (in the event these ever get changed to
+# numeric values or it is decided they need "prettier" or different labels).
+REPO_COLLECTION_TYPES = {
+    REPO_CTYPE_CORE: "core",
+    REPO_CTYPE_SUPPLEMENTAL: "supplemental",
+}
+
+# Supported Protocol Schemes
+SUPPORTED_SCHEMES = set(("http", "https"))
+
+# SSL Protocol Schemes
+SSL_SCHEMES = set(("https",))
+
+# Supported RepositoryURI sorting policies.
+URI_SORT_PRIORITY = "priority"
+
+# Sort policy mapping.
+URI_SORT_POLICIES = {
+    URI_SORT_PRIORITY: lambda obj: (obj.priority, obj.uri),
+}
+
+class RepositoryURI(object):
+        """Class representing a repository URI and any transport-related
+        information."""
+
+        # These properties are declared here so that they show up in the pydoc
+        # documentation as private, and for clarity in the property declarations
+        # found near the end of the class definition.
+        __priority = None
+        __ssl_cert = None
+        __ssl_key = None
+        __trailing_slash = None
+        __uri = None
+
+        # Used to store the id of the original object this one was copied
+        # from during __copy__.
+        _source_object_id = None
+
+        def __init__(self, uri, priority=None, ssl_cert=None, ssl_key=None,
+            trailing_slash=True):
+                # Must set first.
+                self.__trailing_slash = trailing_slash
+
+                # Note that the properties set here are intentionally lacking
+                # the '__' prefix which means assignment will occur using the
+                # get/set methods declared for the property near the end of
+                # the class definition.
+                self.priority = priority
+                self.uri = uri
+                self.ssl_cert = ssl_cert
+                self.ssl_key = ssl_key
+
+        def __copy__(self):
+                uri = RepositoryURI(self.__uri, ssl_cert=self.__ssl_cert,
+                    ssl_key=self.__ssl_key)
+                uri._source_object_id = id(self)
+                return uri
+
+        def __eq__(self, other):
+                if isinstance(other, RepositoryURI):
+                        return self.uri == other.uri
+                if isinstance(other, str):
+                        return self.uri == other
+                return False
+
+        def __ne__(self, other):
+                if isinstance(other, RepositoryURI):
+                        return self.uri != other.uri
+                if isinstance(other, str):
+                        return self.uri != other
+                return True
+
+        def __set_priority(self, value):
+                if value is not None:
+                        try:
+                                value = int(value)
+                        except (TypeError, ValueError):
+                                raise api_errors.BadRepositoryURIPriority(value)
+                self.__priority = value
+
+        def __set_ssl_cert(self, filename):
+                if self.scheme not in SSL_SCHEMES and filename:
+                        raise api_errors.UnsupportedRepositoryURIAttribute(
+                            "ssl_cert", scheme=self.scheme)
+                if filename:
+                        filename = os.path.abspath(filename)
+                        if not os.path.exists(filename):
+                                raise api_errors.NoSuchCertificate(filename,
+                                    uri=self.uri)
+                if filename == "":
+                        filename = None
+                # XXX attempt certificate verification here?
+                self.__ssl_cert = filename
+
+        def __set_ssl_key(self, filename):
+                if self.scheme not in SSL_SCHEMES and filename:
+                        raise api_errors.UnsupportedRepositoryURIAttribute(
+                            "ssl_key", scheme=self.scheme)
+                if filename:
+                        filename = os.path.abspath(filename)
+                        if not os.path.exists(filename):
+                                raise api_errors.NoSuchCertificate(filename,
+                                    uri=self.uri)
+                if filename == "":
+                        filename = None
+                # XXX attempt key verification here?
+                self.__ssl_key = filename
+
+        def __set_uri(self, uri):
+                if uri is None:
+                        raise api_errors.BadRepositoryURI(uri)
+
+                # Decompose URI to verify attributes.
+                scheme, netloc, path, params, query = \
+                    urlparse.urlsplit(uri, allow_fragments=0)
+
+                # The set of currently supported protocol schemes.
+                if scheme.lower() not in SUPPORTED_SCHEMES:
+                        raise api_errors.UnsupportedRepositoryURI(uri)
+
+                # XXX valid_pub_url's check isn't quite right and could prevent
+                # usage of IDNs (international domain names).
+                if not netloc or not pkg.misc.valid_pub_url(uri):
+                        raise api_errors.BadRepositoryURI(uri)
+
+                # Normalize URI scheme.
+                uri = uri.replace(scheme, scheme.lower(), 1)
+
+                if self.__trailing_slash:
+                        uri = pkg.misc.url_affix_trailing_slash(uri)
+
+                if scheme.lower() not in SSL_SCHEMES:
+                        self.__ssl_cert = None
+                        self.__ssl_key = None
+
+                self.__uri = uri
+
+        def __str__(self):
+                return self.__uri
+
+        ssl_cert = property(lambda self: self.__ssl_cert, __set_ssl_cert, None,
+            "The absolute pathname of a PEM-encoded SSL certificate file.")
+
+        ssl_key = property(lambda self: self.__ssl_key, __set_ssl_key, None,
+            "The absolute pathname of a PEM-encoded SSL key file.")
+
+        uri = property(lambda self: self.__uri, __set_uri, None,
+            "The URI used to access a repository.")
+
+        priority = property(lambda self: self.__priority, __set_priority, None,
+           "An integer value representing the importance of this repository "
+           "URI relative to others.")
+
+        @property
+        def scheme(self):
+                """The URI scheme."""
+                if not self.__uri:
+                        return ""
+                return urlparse.urlsplit(self.__uri, allow_fragments=0)[0]
+
+
+class Repository(object):
+        """Class representing a repository object.
+
+        A repository object represents a location where clients can publish
+        and retrieve package content and/or metadata.  It has the following
+        characteristics:
+
+                - may have one or more origins (URIs) for publication and
+                  retrieval of package metadata and content.
+
+                - may have zero or more mirrors (URIs) for retrieval of package
+                  content."""
+
+        # These properties are declared here so that they show up in the pydoc
+        # documentation as private, and for clarity in the property declarations
+        # found near the end of the class definition.
+        __collection_type = None
+        __legal_uris = []
+        __mirrors = []
+        __origins = []
+        __refresh_seconds = None
+        __registration_uri = None
+        __related_uris = []
+        __sort_policy = URI_SORT_PRIORITY
+
+        # Used to store the id of the original object this one was copied
+        # from during __copy__.
+        _source_object_id = None
+
+        name = None
+        description = None
+        registered = False
+
+        def __init__(self, collection_type=REPO_CTYPE_CORE, description=None,
+            legal_uris=None, mirrors=None, name=None, origins=None,
+            refresh_seconds=None, registered=False, registration_uri=None,
+            related_uris=None, sort_policy=URI_SORT_PRIORITY):
+                """Initializes a repository object.
+
+                'collection_type' is an optional constant value indicating the
+                type of packages in the repository.
+
+                'description' is an optional string value containing a
+                descriptive paragraph for the repository.
+
+                'legal_uris' should be a list of RepositoryURI objects or URI
+                strings indicating where licensing, legal, and terms of service
+                information for the repository can be found.
+
+                'mirrors' is an optional list of RepositoryURI objects or URI
+                strings indicating where package content can be retrieved.
+
+                'name' is an optional, short, descriptive name for the
+                repository.
+
+                'origins' should be a list of RepositoryURI objects or URI
+                strings indicating where package metadata can be retrieved.
+
+                'refresh_seconds' is an optional integer value indicating the
+                number of seconds clients should wait before refreshing cached
+                repository catalog or repository metadata information.
+
+                'registered' is an optional boolean value indicating whether
+                a client has registered with the repository's publisher.
+
+                'registration_uri' is an optional RepositoryURI object or a URI
+                string indicating a location clients can use to register or
+                obtain credentials needed to access the repository.
+
+                'related_uris' is an optional list of RepositoryURI objects or a
+                list of URI strings indicating the location of related
+                repositories that a client may be interested in.
+
+                'sort_policy' is an optional constant value indicating how
+                legal_uris, mirrors, origins, and related_uris should be
+                sorted."""
+
+                # Note that the properties set here are intentionally lacking
+                # the '__' prefix which means assignment will occur using the
+                # get/set methods declared for the property near the end of
+                # the class definition.
+
+                # Must be set first so that it will apply to attributes set
+                # afterwards.
+                self.sort_policy = sort_policy
+
+                self.collection_type = collection_type
+                self.description = description
+                self.legal_uris = legal_uris
+                self.mirrors = mirrors
+                self.name = name
+                self.origins = origins
+                self.refresh_seconds = refresh_seconds
+                self.registered = registered
+                self.registration_uri = registration_uri
+                self.related_uris = related_uris
+
+        def __add_uri(self, attr, uri, dup_check=None, priority=None,
+            ssl_cert=None, ssl_key=None, trailing_slash=True):
+                if not isinstance(uri, RepositoryURI):
+                        uri = RepositoryURI(uri, priority=priority,
+                            ssl_cert=ssl_cert, ssl_key=ssl_key,
+                            trailing_slash=trailing_slash)
+
+                if dup_check:
+                        dup_check(uri)
+
+                ulist = getattr(self, attr)
+                ulist.append(uri)
+                ulist.sort(key=URI_SORT_POLICIES[self.__sort_policy])
+
+        def __copy__(self):
+                cluris = [copy.copy(u) for u in self.legal_uris]
+                cmirrors = [copy.copy(u) for u in self.mirrors]
+                cruris = [copy.copy(u) for u in self.related_uris]
+                corigins = [copy.copy(u) for u in self.origins]
+                repo = Repository(collection_type=self.collection_type,
+                    description=self.description,
+                    legal_uris=cluris,
+                    mirrors=cmirrors, name=self.name,
+                    origins=corigins,
+                    refresh_seconds=self.refresh_seconds,
+                    registered=self.registered,
+                    registration_uri=copy.copy(self.registration_uri),
+                    related_uris=cruris)
+                repo._source_object_id = id(self)
+                return repo
+
+        def __replace_uris(self, attr, value, trailing_slash=True):
+                if value is None:
+                        value = []
+                if not isinstance(value, list):
+                        raise api_errors.BadRepositoryAttributeValue(attr,
+                            value=value)
+                uris = []
+                for u in value:
+                        if not isinstance(u, RepositoryURI):
+                                u = RepositoryURI(u,
+                                    trailing_slash=trailing_slash)
+                        elif trailing_slash:
+                                u.uri = pkg.misc.url_affix_trailing_slash(u.uri)
+                        uris.append(u)
+                uris.sort(key=URI_SORT_POLICIES[self.__sort_policy])
+                return uris
+
+        def __set_collection_type(self, value):
+                if value not in REPO_COLLECTION_TYPES:
+                        raise api_errors.BadRepositoryCollectionType(value)
+                self.__collection_type = value
+
+        def __set_legal_uris(self, value):
+                self.__legal_uris = self.__replace_uris("legal_uris", value,
+                    trailing_slash=False)
+
+        def __set_mirrors(self, value):
+                self.__mirrors = self.__replace_uris("mirrors", value)
+
+        def __set_origins(self, value):
+                self.__origins = self.__replace_uris("origins", value)
+
+        def __set_registration_uri(self, value):
+                if value and not isinstance(value, RepositoryURI):
+                        value = RepositoryURI(value, trailing_slash=False)
+                self.__registration_uri = value
+
+        def __set_related_uris(self, value):
+                self.__related_uris = self.__replace_uris("related_uris",
+                    value, trailing_slash=False)
+
+        def __set_refresh_seconds(self, value):
+                if value is not None:
+                        try:
+                                value = int(value)
+                        except (TypeError, ValueError):
+                                raise api_errors.BadRepositoryAttributeValue(
+                                    "refresh_seconds", value)
+                self.__refresh_seconds = value
+
+        def __set_sort_policy(self, value):
+                if value not in URI_SORT_POLICIES:
+                        raise api_errors.BadRepositoryURISortPolicy(value)
+                self.__sort_policy = value
+
+        def add_legal_uri(self, uri, priority=None, ssl_cert=None,
+            ssl_key=None):
+                """Adds the specified legal URI to the repository.
+
+                'uri' can be a RepositoryURI object or a URI string.  If
+                it is a RepositoryURI object, all other parameters will be
+                ignored."""
+
+                self.__add_uri("legal_uris", uri, priority=priority,
+                    ssl_cert=ssl_cert, ssl_key=ssl_key, trailing_slash=False)
+
+        def add_mirror(self, mirror, priority=None, ssl_cert=None,
+            ssl_key=None):
+                """Adds the specified mirror to the repository.
+
+                'mirror' can be a RepositoryURI object or a URI string.  If
+                it is a RepositoryURI object, all other parameters will be
+                ignored."""
+
+                def dup_check(mirror):
+                        if self.has_mirror(mirror):
+                                raise api_errors.DuplicateRepositoryMirror(
+                                    mirror)
+
+                self.__add_uri("mirrors", mirror, dup_check=dup_check,
+                    priority=priority, ssl_cert=ssl_cert, ssl_key=ssl_key)
+
+        def add_related_uri(self, uri, priority=None, ssl_cert=None,
+            ssl_key=None):
+                """Adds the specified related URI to the repository.
+
+                'uri' can be a RepositoryURI object or a URI string.  If
+                it is a RepositoryURI object, all other parameters will be
+                ignored."""
+
+                self.__add_uri("related_uris", uri, priority=priority,
+                    ssl_cert=ssl_cert, ssl_key=ssl_key, trailing_slash=False)
+
+        def add_origin(self, origin, priority=None, ssl_cert=None,
+            ssl_key=None):
+                """Adds the specified origin to the repository.
+
+                'origin' can be a RepositoryURI object or a URI string.  If
+                it is a RepositoryURI object, all other parameters will be
+                ignored."""
+
+                def dup_check(origin):
+                        if self.has_origin(origin):
+                                raise api_errors.DuplicateRepositoryOrigin(
+                                    origin)
+
+                self.__add_uri("origins", origin, dup_check=dup_check,
+                    priority=priority, ssl_cert=ssl_cert, ssl_key=ssl_key)
+
+        def get_mirror(self, mirror):
+                """Returns a RepositoryURI object representing the mirror
+                that matches 'mirror'.
+
+                'mirror' can be a RepositoryURI object or a URI string."""
+
+                if not isinstance(mirror, RepositoryURI):
+                        mirror = pkg.misc.url_affix_trailing_slash(mirror)
+                for m in self.mirrors:
+                        if mirror == m.uri:
+                                return m
+                raise api_errors.UnknownRepositoryMirror(mirror)
+
+        def get_origin(self, origin):
+                """Returns a RepositoryURI object representing the origin
+                that matches 'origin'.
+
+                'origin' can be a RepositoryURI object or a URI string."""
+
+                if not isinstance(origin, RepositoryURI):
+                        origin = pkg.misc.url_affix_trailing_slash(origin)
+                for o in self.origins:
+                        if origin == o.uri:
+                                return o
+                raise api_errors.UnknownRepositoryOrigin(origin)
+
+        def has_mirror(self, mirror):
+                """Returns a boolean value indicating whether a matching
+                'mirror' exists for the repository.
+
+                'mirror' can be a RepositoryURI object or a URI string."""
+
+                if not isinstance(mirror, RepositoryURI):
+                        mirror = pkg.misc.url_affix_trailing_slash(mirror)
+                return mirror in self.mirrors
+
+        def has_origin(self, origin):
+                """Returns a boolean value indicating whether a matching
+                'origin' exists for the repository.
+
+                'origin' can be a RepositoryURI object or a URI string."""
+
+                if not isinstance(origin, RepositoryURI):
+                        origin = pkg.misc.url_affix_trailing_slash(origin)
+                return origin in self.origins
+
+        def remove_legal_uri(self, uri):
+                """Removes the legal URI matching 'uri' from the repository.
+
+                'uri' can be a RepositoryURI object or a URI string."""
+
+                for i, m in enumerate(self.legal_uris):
+                        if uri == m.uri:
+                                # Immediate return as the index into the array
+                                # changes with each removal.
+                                del self.legal_uris[i]
+                                return
+                raise api_errors.UnknownLegalURI(uri)
+
+        def remove_mirror(self, mirror):
+                """Removes the mirror matching 'mirror' from the repository.
+
+                'mirror' can be a RepositoryURI object or a URI string."""
+
+                if not isinstance(mirror, RepositoryURI):
+                        mirror = pkg.misc.url_affix_trailing_slash(mirror)
+                for i, m in enumerate(self.mirrors):
+                        if mirror == m.uri:
+                                # Immediate return as the index into the array
+                                # changes with each removal.
+                                del self.mirrors[i]
+                                return
+                raise api_errors.UnknownRepositoryMirror(mirror)
+
+        def remove_origin(self, origin):
+                """Removes the origin matching 'origin' from the repository.
+
+                'origin' can be a RepositoryURI object or a URI string."""
+
+                if not isinstance(origin, RepositoryURI):
+                        origin = pkg.misc.url_affix_trailing_slash(origin)
+                for i, o in enumerate(self.origins):
+                        if origin == o.uri:
+                                # Immediate return as the index into the array
+                                # changes with each removal.
+                                del self.origins[i]
+                                return
+                raise api_errors.UnknownRepositoryOrigin(origin)
+
+        def remove_related_uri(self, uri):
+                """Removes the related URI matching 'uri' from the repository.
+
+                'uri' can be a RepositoryURI object or a URI string."""
+
+                for i, m in enumerate(self.related_uris):
+                        if uri == m.uri:
+                                # Immediate return as the index into the array
+                                # changes with each removal.
+                                del self.related_uris[i]
+                                return
+                raise api_errors.UnknownRelatedURI(uri)
+
+        def update_mirror(self, mirror, priority=None, ssl_cert=None,
+            ssl_key=None):
+                """Updates an existing mirror object matching 'mirror'.
+
+                'mirror' can be a RepositoryURI object or a URI string."""
+
+                if not isinstance(mirror, RepositoryURI):
+                        mirror = RepositoryURI(mirror, priority=priority,
+                            ssl_cert=ssl_cert, ssl_key=ssl_key)
+
+                target = self.get_mirror(mirror)
+                target.priority = mirror.priority
+                target.ssl_cert = mirror.ssl_cert
+                target.ssl_key = mirror.ssl_key
+                self.mirrors.sort(key=URI_SORT_POLICIES[self.__sort_policy])
+
+        def update_origin(self, origin, priority=None, ssl_cert=None,
+            ssl_key=None):
+                """Updates an existing origin object matching 'origin'.
+
+                'origin' can be a RepositoryURI object or a URI string."""
+
+                if not isinstance(origin, RepositoryURI):
+                        origin = RepositoryURI(origin, priority=priority,
+                            ssl_cert=ssl_cert, ssl_key=ssl_key)
+
+                target = self.get_origin(origin)
+                target.priority = origin.priority
+                target.ssl_cert = origin.ssl_cert
+                target.ssl_key = origin.ssl_key
+                self.origins.sort(key=URI_SORT_POLICIES[self.__sort_policy])
+
+        def reset_mirrors(self):
+                """Discards the current list of repository mirrors."""
+
+                self.mirrors = []
+
+        def reset_origins(self):
+                """Discards the current list of repository origins."""
+
+                self.origins = []
+
+        collection_type = property(lambda self: self.__collection_type,
+            __set_collection_type, None,
+            """A constant value indicating the type of packages in the
+            repository.  The following collection types are recognized:
+
+                    REPO_CTYPE_CORE
+                        The "core" type indicates that the repository contains
+                        all of the dependencies declared by packages in the
+                        repository.  It is primarily used for operating system
+                        repositories.
+
+                    REPO_CTYPE_SUPPLEMENTAL
+                        The "supplemental" type indicates that the repository
+                        contains packages that rely on or are intended to be
+                        used with packages located in another repository.""")
+
+        legal_uris = property(lambda self: self.__legal_uris,
+            __set_legal_uris, None,
+            """A list of RepositoryURI objects indicating where licensing,
+            legal, and terms of service information for the repository can be
+            found.""")
+
+        mirrors = property(lambda self: self.__mirrors, __set_mirrors, None,
+            """A list of RepositoryURI objects indicating where package content
+            can be retrieved.  If any value in the list provided is a URI
+            string, it will be replaced with a RepositoryURI object.""")
+
+        origins = property(lambda self: self.__origins, __set_origins, None,
+            """A list of RepositoryURI objects indicating where package content
+            can be retrieved.  If any value in the list provided is a URI
+            string, it will be replaced with a RepositoryURI object.""")
+
+        registration_uri = property(lambda self: self.__registration_uri,
+            __set_registration_uri, None,
+            """A RepositoryURI object indicating a location clients can use to
+            register or obtain credentials needed to access the repository.  If
+            the value provided is a URI string, it will be replaced with a
+            RepositoryURI object.""")
+
+        related_uris = property(lambda self: self.__related_uris,
+            __set_related_uris, None,
+            """A list of RepositoryURI objects indicating the location of
+            related repositories that a client may be interested in.  If any
+            value in the list provided is a URI string, it will be replaced with
+            a RepositoryURI object.""")
+
+        refresh_seconds = property(lambda self: self.__refresh_seconds,
+            __set_refresh_seconds, None,
+            """An integer value indicating the number of seconds clients should
+            wait before refreshing cached repository metadata information.  A
+            value of None indicates that refreshes should be performed at the
+            client's discretion.""")
+
+        sort_policy = property(lambda self: self.__sort_policy,
+            __set_sort_policy, None,
+            """A constant value indicating how legal_uris, mirrors, origins, and
+            related_uris should be sorted.  The following policies are
+            recognized:
+
+                    URI_SORT_PRIORITY
+                        The "priority" policy indicate that URIs should be
+                        sorted according to the value of their priority
+                        attribute.""")
+
+
+class Publisher(object):
+        """Class representing a publisher object and a set of interfaces to set
+        and retrieve its information.
+
+        A publisher is a forward or reverse domain name identifying a source
+        (e.g. "publisher") of packages."""
+
+        # These properties are declared here so that they show up in the pydoc
+        # documentation as private, and for clarity in the property declarations
+        # found near the end of the class definition.
+        __alias = None
+        __client_uuid = None
+        __disabled = False
+        __prefix = None
+        __selected_repository = None
+        __repositories = []
+
+        # Used to store the id of the original object this one was copied
+        # from during __copy__.
+        _source_object_id = None
+
+        def __init__(self, prefix, alias=None, client_uuid=None, disabled=False,
+            repositories=None, selected_repository=None):
+                """Initialize a new publisher object."""
+
+                if client_uuid is None:
+                        self.reset_client_uuid()
+                else:
+                        self.__client_uuid = client_uuid
+
+                self.__repositories = []
+
+                # Note that the properties set here are intentionally lacking
+                # the '__' prefix which means assignment will occur using the
+                # get/set methods declared for the property near the end of
+                # the class definition.
+                self.alias = alias
+                self.disabled = disabled
+                self.prefix = prefix
+
+                if repositories:
+                        for r in repositories:
+                                self.add_repository(r)
+
+                if selected_repository:
+                        self.selected_repository = selected_repository
+
+        def __cmp__(self, other):
+                if other is None:
+                        return 1
+                if isinstance(other, Publisher):
+                        return cmp(self.prefix, other.prefix)
+                return cmp(self.prefix, other)
+
+        @staticmethod
+        def __contains__(key):
+                """Supports deprecated compatibility interface."""
+
+                return key in ("client_uuid", "disabled", "mirrors", "origin",
+                    "prefix", "ssl_cert", "ssl_key")
+
+        def __copy__(self):
+                selected = None
+                repositories = []
+                for r in self.__repositories:
+                        repo = copy.copy(r)
+                        if r == self.selected_repository:
+                                selected = repo
+                        repositories.append(repo)
+                pub = Publisher(self.__prefix, client_uuid=self.__client_uuid,
+                    disabled=self.__disabled, repositories=repositories,
+                    selected_repository=selected)
+                pub._source_object_id = id(self)
+                return pub
+
+        def __eq__(self, other):
+                if isinstance(other, Publisher):
+                        return self.prefix == other.prefix
+                if isinstance(other, str):
+                        return self.prefix == other
+                return False
+
+        def __getitem__(self, key):
+                """Deprecated compatibility interface allowing publisher
+                attributes to be read as pub["attribute"]."""
+
+                if key == "client_uuid":
+                        return self.__client_uuid
+                if key == "disabled":
+                        return self.__disabled
+                if key == "prefix":
+                        return self.__prefix
+
+                repo = self.selected_repository
+                if key == "mirrors":
+                        return [str(m) for m in repo.mirrors]
+                if key == "origin":
+                        if not repo.origins[0]:
+                                return None
+                        return repo.origins[0].uri
+                if key == "ssl_cert":
+                        if not repo.origins[0]:
+                                return None
+                        return repo.origins[0].ssl_cert
+                if key == "ssl_key":
+                        if not repo.origins[0]:
+                                return None
+                        return repo.origins[0].ssl_key
+
+        def __ne__(self, other):
+                if isinstance(other, Publisher):
+                        return self.prefix != other.prefix
+                if isinstance(other, str):
+                        return self.prefix != other
+                return True
+
+        def __set_alias(self, value):
+                self.__alias = value
+
+        def __set_disabled(self, disabled):
+                if disabled:
+                        self.__disabled = True
+                else:
+                        self.__disabled = False
+
+        def __set_prefix(self, prefix):
+                if not pkg.misc.valid_pub_prefix(prefix):
+                        raise api_errors.BadPublisherPrefix(prefix)
+                self.__prefix = prefix
+
+        def __set_selected_repository(self, value):
+                if not isinstance(value, Repository):
+                        raise api_errors.UnknownRepository(value)
+                self.__selected_repository = value
+
+        def __set_client_uuid(self, value):
+                self.__client_uuid = value
+
+        def __str__(self):
+                return self.prefix
+
+        def add_repository(self, repository):
+                """Adds the provided repository object to the publisher and
+                sets it as the selected one if no repositories exist."""
+
+                for r in self.__repositories:
+                        if repository.name == r.name:
+                                raise api_errors.DuplicateRepository(
+                                    self.prefix)
+                        for o in repository.origins:
+                                if o.uri in r.origins:
+                                        raise api_errors.DuplicateRepository(
+                                            self.prefix)
+
+                self.__repositories.append(repository)
+                if len(self.__repositories) == 1:
+                        self.selected_repository = repository
+
+        def get_repository(self, name=None, origin=None):
+                """Returns the repository object matching the name or that has
+                a matching origin URI."""
+
+                assert not (name and origin)
+                for r in self.__repositories:
+                        if (name and r.name == name) or (origin and
+                            r.has_origin(origin)):
+                                return r
+                raise api_errors.UnknownRepository(max(name, origin))
+
+        def get_ssl_creds(self):
+                """Deprecated"""
+
+                origin = self.selected_repository.origins[0]
+                return (origin.ssl_key, origin.ssl_cert)
+
+        def remove_repository(self, name=None, origin=None):
+                """Removes the repository object matching the name or that has
+                a matching origin URI from the publisher."""
+
+                assert not (name and origin)
+                for i, r in enumerate(self.__repositories):
+                        if (name and r.name == name) or (origin and
+                            r.has_origin(origin)):
+                                if r != self.selected_repository:
+                                        # Immediate return as the index into the
+                                        # array changes with each removal.
+                                        del self.__repositories[i]
+                                        return
+                                raise api_errors.SelectedRepositoryRemoval(r)
+
+        def reset_client_uuid(self):
+                """Replaces the current client_uuid with a new UUID."""
+
+                self.__client_uuid = pkg.Uuid25.uuid1()
+
+        def set_origin(self, origin):
+                """Deprecated"""
+
+                r = self.selected_repository
+                r.reset_origins()
+                r.add_origin(origin)
+
+        def set_selected_repository(self, name=None, origin=None):
+                """Sets the selected repository for the publisher to the
+                repository object matching the name or that has a matching
+                origin URI."""
+
+                self.__selected_repository = self.get_repository(name=name,
+                    origin=origin)
+
+        alias = property(lambda self: self.__alias, __set_alias,
+            doc="An alternative name for a publisher.")
+
+        client_uuid = property(lambda self: self.__client_uuid,
+            __set_client_uuid,
+            doc="A Universally Unique Identifier (UUID) used to identify a "
+            "client image to a publisher.")
+
+        disabled = property(lambda self: self.__disabled, __set_disabled,
+            doc="A boolean value indicating whether the publisher should be "
+            "used for packaging operations.")
+
+        prefix = property(lambda self: self.__prefix, __set_prefix,
+            doc="The name of the publisher.")
+
+        repositories = property(lambda self: self.__repositories,
+            doc="A list of repository objects that belong to the publisher.")
+
+        selected_repository = property(lambda self: self.__selected_repository,
+            __set_selected_repository,
+            doc="A reference to the selected repository object.")
--- a/src/modules/client/retrieve.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/client/retrieve.py	Mon Mar 09 16:09:13 2009 -0500
@@ -41,22 +41,22 @@
 
 class CatalogRetrievalError(Exception):
         """Used when catalog retrieval fails"""
-        def __init__(self, data, exc=None, auth=None):
+        def __init__(self, data, exc=None, prefix=None):
                 Exception.__init__(self)
                 self.data = data
                 self.exc = exc
-                self.auth = auth
+                self.prefix = prefix
 
         def __str__(self):
                 return str(self.data)
 
 class VersionRetrievalError(Exception):
         """Used when catalog retrieval fails"""
-        def __init__(self, data, exc=None, auth=None):
+        def __init__(self, data, exc=None, prefix=None):
                 Exception.__init__(self)
                 self.data = data
                 self.exc = exc
-                self.auth = auth
+                self.prefix = prefix
 
         def __str__(self):
                 return str(self.data)
@@ -82,18 +82,18 @@
 # client/retrieve.py - collected methods for retrieval of pkg components
 # from repositories
 
-def get_catalog(img, auth, hdr, ts):
+def get_catalog(img, pub, hdr, ts):
         """Get a catalog from a remote host.  Img is the image object
-        that we're updating.  Auth is the authority from which the
+        that we're updating.  pub is the publisher from which the
         catalog will be retrieved.  Additional headers are contained
         in hdr.  Ts is the timestamp if we're performing an incremental
         catalog operation."""
 
-        prefix = auth["prefix"]
-        ssl_tuple = img.get_ssl_credentials(authent=auth)
+        prefix = pub["prefix"]
+        ssl_tuple = img.get_ssl_credentials(pubent=pub)
 
         try:
-                c, v = versioned_urlopen(auth["origin"],
+                c, v = versioned_urlopen(pub["origin"],
                     "catalog", [0], ssl_creds=ssl_tuple,
                     headers=hdr, imgtype=img.type,
                     uuid=img.get_uuid(prefix))
@@ -138,7 +138,7 @@
         croot = "%s/catalog/%s" % (img.imgdir, prefix)
 
         try:
-                updatelog.recv(c, croot, ts, auth)
+                updatelog.recv(c, croot, ts, pub)
         except (ValueError, httplib.IncompleteRead):
                 raise TransferContentException(prefix,
                     "Incomplete Read from remote host")
@@ -178,7 +178,7 @@
         target_pkg = None
         initial_pkg = None
         needed_by_pkg = None
-        current_auth = fmri.get_authority()
+        current_pub = fmri.get_publisher()
 
         targets = img.state.get_targets()
         if targets:
@@ -186,7 +186,7 @@
                 # manifest for this fmri and what its current target is.
                 target, reason = targets[-1]
 
-                # Compare the FMRIs with no authority information embedded.
+                # Compare the FMRIs with no publisher information embedded.
                 na_current = fmri.get_fmri(anarchy=True)
                 na_target = target.get_fmri(anarchy=True)
 
@@ -196,10 +196,10 @@
                         # target.  If they do not match, then the target fmri is
                         # being retrieved for information purposes only (e.g.
                         # dependency calculation, etc.).
-                        target_auth = target.get_authority()
-                        if target_auth == current_auth:
+                        target_pub = target.get_publisher()
+                        if target_pub == current_pub:
                                 # Prevent providing information across
-                                # authorities.
+                                # publishers.
                                 target_pkg = na_target[len("pkg:/"):]
                         else:
                                 target_pkg = "unknown"
@@ -208,10 +208,10 @@
                         # caused the current and needed_by fmris to be
                         # retrieved.
                         initial = targets[0][0]
-                        initial_auth = initial.get_authority()
-                        if initial_auth == current_auth:
+                        initial_pub = initial.get_publisher()
+                        if initial_pub == current_pub:
                                 # Prevent providing information across
-                                # authorities.
+                                # publishers.
                                 initial_pkg = initial.get_fmri(
                                     anarchy=True)[len("pkg:/"):]
 
@@ -236,17 +236,17 @@
                                 # current one in the target list.
                                 needed_by = targets[-2][0]
 
-                                needed_by_auth = needed_by.get_authority()
-                                if needed_by_auth == current_auth:
-                                        # To prevent dependency information being shared
-                                        # across authority boundaries, authorities must
-                                        # match.
+                                needed_by_pub = needed_by.get_publisher()
+                                if needed_by_pub == current_pub:
+                                        # To prevent dependency information
+                                        # being shared across publisher
+                                        # boundaries, publishers must match.
                                         needed_by_pkg = needed_by.get_fmri(
                                             anarchy=True)[len("pkg:/"):]
                                 else:
-                                        # If they didn't match, indicate that the
-                                        # package is needed by another, but not which
-                                        # one.
+                                        # If they didn't match, indicate that
+                                        # the package is needed by another, but
+                                        # not which one.
                                         needed_by_pkg = "unknown"
         else:
                 # An operation is being performed that has not provided any
@@ -266,10 +266,10 @@
                         # We didn't get a match back, drive on.
                         pass
                 else:
-                        prior_auth = prior.get_authority()
-                        if prior_auth != current_auth:
+                        prior_pub = prior.get_publisher()
+                        if prior_pub != current_pub:
                                 # Prevent providing information across
-                                # authorities by indicating that a prior
+                                # publishers by indicating that a prior
                                 # version was installed, but not which one.
                                 prior_version = "unknown"
 
@@ -292,11 +292,11 @@
         """Retrieve a file handle based on a package fmri and a file hash.
         """
 
-        authority = fmri.get_authority_str()
-        authority = pkg.fmri.strip_auth_pfx(authority)
-        url_prefix = img.get_url_by_authority(authority)
-        ssl_tuple = img.get_ssl_credentials(authority)
-        uuid = img.get_uuid(authority)
+        publisher = fmri.get_publisher_str()
+        publisher = pkg.fmri.strip_pub_pfx(publisher)
+        url_prefix = img.get_url_by_publisher(publisher)
+        ssl_tuple = img.get_ssl_credentials(publisher)
+        uuid = img.get_uuid(publisher)
 
         try:
                 f = versioned_urlopen(url_prefix, "file", [0], fhash,
@@ -323,11 +323,11 @@
         for the related manifest and send intent information.
         """
 
-        authority = fmri.get_authority_str()
-        authority = pkg.fmri.strip_auth_pfx(authority)
-        url_prefix = img.get_url_by_authority(authority)
-        ssl_tuple = img.get_ssl_credentials(authority)
-        uuid = img.get_uuid(authority)
+        publisher = fmri.get_publisher_str()
+        publisher = pkg.fmri.strip_pub_pfx(publisher)
+        url_prefix = img.get_url_by_publisher(publisher)
+        ssl_tuple = img.get_ssl_credentials(publisher)
+        uuid = img.get_uuid(publisher)
 
         # Tell the server why this resource is being requested.
         headers = {
@@ -343,9 +343,9 @@
         the caller.
         """
 
-        authority = fmri.tuple()[0]
-        authority = pkg.fmri.strip_auth_pfx(authority)
-        url_prefix = img.get_url_by_authority(authority)
+        publisher = fmri.tuple()[0]
+        publisher = pkg.fmri.strip_pub_pfx(publisher)
+        url_prefix = img.get_url_by_publisher(publisher)
 
         try:
                 m = __get_manifest(img, fmri, "GET")
@@ -416,9 +416,8 @@
         """Perform a HEAD operation on the manifest for the given fmri.
         """
 
-        authority = fmri.get_authority_str()
-        authority = pkg.fmri.strip_auth_pfx(authority)
-        url_prefix = img.get_url_by_authority(authority)
+        publisher = fmri.get_publisher_str()
+        publisher = pkg.fmri.strip_pub_pfx(publisher)
 
         try:
                 __get_manifest(img, fmri, "HEAD")
@@ -429,17 +428,17 @@
                 # operation that returns no information.
                 pass
 
-def get_versions(img, auth):
+def get_versions(img, pub):
         """Get version information from a remote host.
 
         Img is the image object that the retrieve is using.
-        Auth is the authority that will be queried for version information."""
+        pub is the publisher that will be queried for version information."""
 
-        prefix = auth["prefix"]
-        ssl_tuple = img.get_ssl_credentials(authent=auth)
+        prefix = pub["prefix"]
+        ssl_tuple = img.get_ssl_credentials(pubent=pub)
 
         try:
-                s, v = versioned_urlopen(auth["origin"],
+                s, v = versioned_urlopen(pub["origin"],
                     "versions", [0], ssl_creds=ssl_tuple,
                     imgtype=img.type, uuid=img.get_uuid(prefix))
         except urllib2.HTTPError, e:
@@ -501,5 +500,5 @@
                     for s in (l.strip() for l in verlines)
                 )
         except ValueError:
-                raise InvalidDepotResponseException(auth["origin"],
+                raise InvalidDepotResponseException(pub["origin"],
                     "Unable to parse server response")
--- a/src/modules/fmri.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/fmri.py	Mon Mar 09 16:09:13 2009 -0500
@@ -19,7 +19,9 @@
 #
 # CDDL HEADER END
 #
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 
@@ -29,20 +31,21 @@
 
 from version import Version, IllegalVersion
 
-# In order to keep track of what authority is presently the preferred authority,
-# a prefix is included ahead of the name of the authority.  If this prefix is
-# present, the authority is considered to be the current preferred authority for
+# In order to keep track of what publisher is presently the preferred publisher,
+# a prefix is included ahead of the name of the publisher.  If this prefix is
+# present, the publisher is considered to be the current preferred publisher for
 # the image.  This is where we define the prefix, since it's used primarily in
-# the FMRI.  PREF_AUTH_PFX => preferred authority prefix.
-PREF_AUTH_PFX = "_PRE"
+# the FMRI.  PREF_PUB_PFX => preferred publisher prefix.
+PREF_PUB_PFX = "_PRE"
 
 #
-# For is_same_authority(), we need a version of this constant with the
+# For is_same_publisher(), we need a version of this constant with the
 # trailing _ attached.
 #
-PREF_AUTH_PFX_ = PREF_AUTH_PFX + "_"
+PREF_PUB_PFX_ = PREF_PUB_PFX + "_"
 
-g_valid_pkg_name = re.compile("^[A-Za-z0-9][A-Za-z0-9_\-\.\+]*(/[A-Za-z0-9][A-Za-z0-9_\-\.\+]*)*$")
+g_valid_pkg_name = re.compile("^[A-Za-z0-9][A-Za-z0-9_\-\.\+]*(/[A-Za-z0-9]"
+    "[A-Za-z0-9_\-\.\+]*)*$")
 
 class IllegalFmri(Exception):
 
@@ -72,8 +75,8 @@
         msg_prefix = "Illegal matching pattern"
 
 class PkgFmri(object):
-        """The authority is the anchor of a package namespace.  Clients can
-        choose to take packages from multiple authorities, and specify a default
+        """The publisher is the anchor of a package namespace.  Clients can
+        choose to take packages from multiple publishers, and specify a default
         search path.  In general, package names may also be prefixed by a domain
         name, reverse domain name, or a stock symbol to avoid conflict.  The
         unprefixed namespace is expected to be managed by architectural review.
@@ -86,37 +89,37 @@
         # Stored in a class variable so that subclasses can override
         valid_pkg_name = g_valid_pkg_name
 
-        def __init__(self, fmri, build_release = None, authority = None):
+        def __init__(self, fmri, build_release = None, publisher = None):
                 """XXX pkg:/?pkg_name@version not presently supported."""
                 fmri = fmri.rstrip()
 
                 veridx, nameidx = PkgFmri.gen_fmri_indexes(fmri)
 
                 if veridx != None:
-			try:
-				self.version = Version(fmri[veridx + 1:],
-				    build_release)
-			except IllegalVersion, iv:
-				raise IllegalFmri(fmri, IllegalFmri.BAD_VERSION,
+                        try:
+                                self.version = Version(fmri[veridx + 1:],
+                                    build_release)
+                        except IllegalVersion, iv:
+                                raise IllegalFmri(fmri, IllegalFmri.BAD_VERSION,
                                     nested_exc=iv)
                 else:
                         self.version = veridx = None
 
-                self.authority = authority
+                self.publisher = publisher
                 if fmri.startswith("pkg://"):
-                        self.authority = fmri[6:nameidx - 1]
+                        self.publisher = fmri[6:nameidx - 1]
 
                 if veridx != None:
                         self.pkg_name = fmri[nameidx:veridx]
                 else:
                         self.pkg_name = fmri[nameidx:]
 
-		if not self.pkg_name:
-			raise IllegalFmri(fmri, IllegalFmri.SYNTAX_ERROR,
+                if not self.pkg_name:
+                        raise IllegalFmri(fmri, IllegalFmri.SYNTAX_ERROR,
                             detail="Missing package name")
                      
                 if not self.valid_pkg_name.match(self.pkg_name):
-			raise IllegalFmri(fmri, IllegalFmri.BAD_PACKAGENAME,
+                        raise IllegalFmri(fmri, IllegalFmri.BAD_PACKAGENAME,
                             detail=self.pkg_name) 
 
         def copy(self):
@@ -133,12 +136,12 @@
 
                 if fmri.startswith("pkg://"):
                         nameidx = fmri.find("/", 6)
-			if nameidx == -1:
-				raise IllegalFmri(fmri,
+                        if nameidx == -1:
+                                raise IllegalFmri(fmri,
                                     IllegalFmri.SYNTAX_ERROR,
-                                    detail="Missing '/' after authority name")
-			# Name starts after / which terminates authority
-			nameidx += 1
+                                    detail="Missing '/' after publisher name")
+                        # Name starts after / which terminates publisher
+                        nameidx += 1
                 elif fmri.startswith("pkg:/"):
                         nameidx = 5
                 else:
@@ -146,34 +149,34 @@
 
                 return (veridx, nameidx)
 
-        def get_authority(self):
-                """Return the name of the authority that is contained
+        def get_publisher(self):
+                """Return the name of the publisher that is contained
                 within this FMRI.  This strips off extraneous data
-                that may be attached to the authority.  The output
-                is suitable as a key into the authority["prefix"] table."""
+                that may be attached to the publisher.  The output
+                is suitable as a key into the publisher["prefix"] table."""
 
-                # Strip off preferred authority prefix, if it exists.
-                if self.authority and self.authority.startswith(PREF_AUTH_PFX):
-                        r = self.authority.rsplit('_', 1)
+                # Strip off preferred publisher prefix, if it exists.
+                if self.publisher and self.publisher.startswith(PREF_PUB_PFX):
+                        r = self.publisher.rsplit('_', 1)
                         a = r[len(r) - 1]
                         return a
 
-                # Otherwise just return the authority
-                return self.authority
+                # Otherwise just return the publisher
+                return self.publisher
 
-        def set_authority(self, authority, preferred = False):
-                """Set the FMRI's authority.  If this is a preferred
-                authority, set preferred to True."""
+        def set_publisher(self, publisher, preferred = False):
+                """Set the FMRI's publisher.  If this is a preferred
+                publisher, set preferred to True."""
 
-                if preferred and not authority.startswith(PREF_AUTH_PFX):
-                        self.authority = "%s_%s" % (PREF_AUTH_PFX, authority)
+                if preferred and not publisher.startswith(PREF_PUB_PFX):
+                        self.publisher = "%s_%s" % (PREF_PUB_PFX, publisher)
                 else:
-                        self.authority = authority
+                        self.publisher = publisher
 
-        def has_authority(self):
-                """Returns true if the FMRI has an authority."""
+        def has_publisher(self):
+                """Returns true if the FMRI has a publisher."""
 
-                if self.authority:
+                if self.publisher:
                         return True
 
                 return False
@@ -184,23 +187,23 @@
                         return True
                 return False
 
-        def preferred_authority(self):
-                """Returns true if this FMRI's authority is the preferred
-                authority."""
+        def preferred_publisher(self):
+                """Returns true if this FMRI's publisher is the preferred
+                publisher."""
 
-                if not self.authority or \
-                    self.authority.startswith(PREF_AUTH_PFX):
+                if not self.publisher or \
+                    self.publisher.startswith(PREF_PUB_PFX):
                         return True
 
                 return False
 
-        def get_authority_str(self):
+        def get_publisher_str(self):
                 """Return the bare string that specifies everything about
-                the authority.  This should only be used by code that
-                must write out (or restore) the complete authority
+                the publisher.  This should only be used by code that
+                must write out (or restore) the complete publisher
                 information to disk."""
 
-                return self.authority
+                return self.publisher
 
         def get_name(self):
                 return self.pkg_name
@@ -217,42 +220,41 @@
         def get_version(self):
                 return self.version.get_short_version()
 
-        def get_pkg_stem(self, default_authority = None, anarchy = False,
-            include_pkg = True):
+        def get_pkg_stem(self, anarchy=False, include_pkg=True):
                 """Return a string representation of the FMRI without a specific
-                version.  Anarchy returns a stem without any authority."""
+                version.  Anarchy returns a stem without any publisher."""
                 pkg_str = ""
-                if not self.authority or \
-                    self.authority.startswith(PREF_AUTH_PFX) or anarchy:
+                if not self.publisher or \
+                    self.publisher.startswith(PREF_PUB_PFX) or anarchy:
                         if include_pkg:
                                 pkg_str = "pkg:/"
                         return "%s%s" % (pkg_str, self.pkg_name)
                 if include_pkg:
                         pkg_str = "pkg://"
-                return "%s%s/%s" % (pkg_str, self.authority, self.pkg_name)
+                return "%s%s/%s" % (pkg_str, self.publisher, self.pkg_name)
 
-        def get_short_fmri(self, default_authority = None):
+        def get_short_fmri(self, default_publisher = None):
                 """Return a string representation of the FMRI without a specific
                 version."""
-                authority = self.authority
-                if not authority:
-                        authority = default_authority
+                publisher = self.publisher
+                if not publisher:
+                        publisher = default_publisher
 
-                if not authority or authority.startswith(PREF_AUTH_PFX):
+                if not publisher or publisher.startswith(PREF_PUB_PFX):
                         return "pkg:/%s@%s" % (self.pkg_name,
                             self.version.get_short_version())
 
-                return "pkg://%s/%s@%s" % (authority, self.pkg_name,
+                return "pkg://%s/%s@%s" % (publisher, self.pkg_name,
                     self.version.get_short_version())
 
-        def get_fmri(self, default_authority = None, anarchy = False):
+        def get_fmri(self, default_publisher = None, anarchy = False):
                 """Return a string representation of the FMRI.
-                Anarchy returns a string without any authority."""
-                authority = self.authority
-                if authority == None:
-                        authority = default_authority
+                Anarchy returns a string without any publisher."""
+                publisher = self.publisher
+                if publisher == None:
+                        publisher = default_publisher
 
-                if not authority or authority.startswith(PREF_AUTH_PFX) \
+                if not publisher or publisher.startswith(PREF_PUB_PFX) \
                     or anarchy:
                         if self.version == None:
                                 return "pkg:/%s" % self.pkg_name
@@ -260,9 +262,9 @@
                         return "pkg:/%s@%s" % (self.pkg_name, self.version)
 
                 if self.version == None:
-                        return "pkg://%s/%s" % (authority, self.pkg_name)
+                        return "pkg://%s/%s" % (publisher, self.pkg_name)
 
-                return "pkg://%s/%s@%s" % (authority, self.pkg_name,
+                return "pkg://%s/%s@%s" % (publisher, self.pkg_name,
                                 self.version)
 
         def __str__(self):
@@ -271,17 +273,17 @@
 
         def __repr__(self):
                 """Return as specific an FMRI representation as possible."""
-                if not self.authority:
+                if not self.publisher:
                         if not self.version:
                                 fmristr = "pkg:/%s" % self.pkg_name
                         else:
                                 fmristr = "pkg:/%s@%s" % (self.pkg_name,
                                     self.version)
                 elif not self.version:
-                        fmristr = "pkg://%s/%s" % (self.authority,
+                        fmristr = "pkg://%s/%s" % (self.publisher,
                             self.pkg_name)
                 else:
-                        fmristr = "pkg://%s/%s@%s" % (self.authority,
+                        fmristr = "pkg://%s/%s@%s" % (self.publisher,
                             self.pkg_name, self.version)
 
                 return "<pkg.fmri.PkgFmri '%s' at %#x>" % (fmristr, id(self))
@@ -350,7 +352,7 @@
                 return self.pkg_name == other.pkg_name
 
         def tuple(self):
-                return self.get_authority_str(), self.pkg_name, self.version
+                return self.get_publisher_str(), self.pkg_name, self.version
 
         def is_name_match(self, fmristr):
                 """True if the regular expression given in fmristr matches the
@@ -388,6 +390,7 @@
                         raise IllegalMatchingFmri(e.fmri, e.reason,
                             detail=e.detail, nested_exc=e.nested_exc)
 
+
 def fmri_match(pkg_name, pattern):
         """Returns true if 'pattern' is a proper subset of 'pkg_name'."""
         return ("/" + pkg_name).endswith("/" + pattern)
@@ -396,7 +399,8 @@
         return fnmatch.fnmatchcase(pkg_name, pattern)
 
 def regex_match(pkg_name, pattern):
-        """Returns true if 'pattern' is a regular expression matching 'pkg_name'."""
+        """Returns true if 'pattern' is a regular expression matching
+        'pkg_name'."""
         return re.search(pattern, pkg_name)
 
 def exact_name_match(pkg_name, pattern):
@@ -417,18 +421,18 @@
 
         return pkg_name
 
-def strip_auth_pfx(auth):
-        """Strip the PREF_AUTH_PFX off of an authority."""
-        if auth.startswith(PREF_AUTH_PFX_):
-                outstr = auth[len(PREF_AUTH_PFX_):]
+def strip_pub_pfx(pub):
+        """Strip the PREF_PUB_PFX off of a publisher."""
+        if pub.startswith(PREF_PUB_PFX_):
+                outstr = pub[len(PREF_PUB_PFX_):]
         else:
-                outstr = auth
+                outstr = pub
 
         return outstr
         
 
-def is_same_authority(auth1, auth2):
-        """Compare two authorities.  Return true if they are the same, false
+def is_same_publisher(pub1, pub2):
+        """Compare two publishers.  Return true if they are the same, false
            otherwise. """
         #
         # This code is performance sensitive.  Ensure that you benchmark
@@ -436,19 +440,19 @@
         #
 
         # Fastest path for most common case.
-        if auth1 == auth2:
+        if pub1 == pub2:
                 return True
 
-        if auth1 == None:
-                auth1 = ""
-        if auth2 == None:
-                auth2 = ""
+        if pub1 == None:
+                pub1 = ""
+        if pub2 == None:
+                pub2 = ""
 
         # String concatenation and string equality are both pretty fast.
-        if ((PREF_AUTH_PFX_ + auth1) == auth2) or \
-            (auth1 == (PREF_AUTH_PFX_ + auth2)):
+        if ((PREF_PUB_PFX_ + pub1) == pub2) or \
+            (pub1 == (PREF_PUB_PFX_ + pub2)):
                 return True
-        if auth1.startswith(PREF_AUTH_PFX_) and auth2.startswith(PREF_AUTH_PFX_):
+        if pub1.startswith(PREF_PUB_PFX_) and pub2.startswith(PREF_PUB_PFX_):
                 return True
         return False
 
--- a/src/modules/manifest.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/manifest.py	Mon Mar 09 16:09:13 2009 -0500
@@ -19,8 +19,11 @@
 #
 # CDDL HEADER END
 #
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import os
 import errno
@@ -28,7 +31,6 @@
 from pkg.misc import EmptyI
 
 import pkg.actions as actions
-import pkg.client.filter as filter
 from pkg.actions.attribute import AttributeAction
 
 # The type member is used for the ordering of actions.
@@ -119,7 +121,8 @@
                 return r
 
 
-        def difference(self, origin, origin_exclude=EmptyI, self_exclude=EmptyI):
+        def difference(self, origin, origin_exclude=EmptyI,
+            self_exclude=EmptyI):
                 """Return three lists of action pairs representing origin and
                 destination actions.  The first list contains the pairs
                 representing additions, the second list contains the pairs
@@ -171,16 +174,16 @@
                 """Like the unix utility comm, except that this function
                 takes an arbitrary number of manifests and compares them,
                 returning a tuple consisting of each manifest's actions
-                that are not the same for all manifests, followed by a 
+                that are not the same for all manifests, followed by a
                 list of actions that are the same in each manifest."""
 
                 # construct list of dictionaries of actions in each
                 # manifest, indexed by unique keys
-                m_dicts = [ 
+                m_dicts = [
                     dict(
-                    ((a.name, a.attrs.get(a.key_attr, id(a))), a) for a in m.actions 
-                    )
-                    for m in compare_m                   
+                    ((a.name, a.attrs.get(a.key_attr, id(a))), a)
+                    for a in m.actions)
+                    for m in compare_m
                 ]
                 # construct list of key sets in each dict
                 #
@@ -190,7 +193,7 @@
                 ]
 
                 common_keys = reduce(lambda a, b: a & b, m_sets)
-                
+
                 # determine which common_keys have common actions
                 for k in common_keys.copy():
                         for i in range(len(m_dicts) - 1):
@@ -200,7 +203,7 @@
                                         break
                 return tuple(
                     [
-                        [ m_dicts[i][k] for k in m_sets[i] - common_keys ]
+                        [m_dicts[i][k] for k in m_sets[i] - common_keys]
                         for i in range(len(m_dicts))
                     ]
                     +
@@ -210,7 +213,7 @@
                 )
 
 
-        def combined_difference(self, origin, ov=[], sv=[]):
+        def combined_difference(self, origin, ov=EmptyI, sv=EmptyI):
                 """Where difference() returns three lists, combined_difference()
                 returns a single list of the concatenation of the three."""
                 return list(chain(*self.difference(origin, ov, sv)))
@@ -242,23 +245,23 @@
                         else:
                                 yield a
 
-        def gen_actions_by_type(self, type, excludes=EmptyI):
+        def gen_actions_by_type(self, atype, excludes=EmptyI):
                 """Generate actions in the manifest of type "type"
                 through ordered callable list"""
-                for a in self.actions_bytype.get(type, []):
+                for a in self.actions_bytype.get(atype, []):
                         for c in excludes:
                                 if not c(a):
                                         break
                         else:
                                 yield a
-                       
-        def gen_key_attribute_value_by_type(self, type, excludes=EmptyI):
+
+        def gen_key_attribute_value_by_type(self, atype, excludes=EmptyI):
                 """Generate the value of the key atrribute for each action
                 of type "type" in the manifest."""
 
                 return (
                     a.attrs.get(a.key_attr)
-                    for a in self.gen_actions_by_type(type, excludes)
+                    for a in self.gen_actions_by_type(atype, excludes)
                 )
 
         def duplicates(self, excludes=EmptyI):
@@ -271,9 +274,9 @@
                         return a.name, a.attrs.get(a.key_attr, id(a))
 
                 alldups = []
-                actions = [ a for a in self.gen_actions(excludes)]
+                acts = [a for a in self.gen_actions(excludes)]
 
-                for k, g in groupby(sorted(actions, key=fun), fun):
+                for k, g in groupby(sorted(acts, key=fun), fun):
                         glist = list(g)
                         dups = set()
                         for i in range(len(glist) - 1):
@@ -288,15 +291,15 @@
                 self.img = img
                 self.fmri = fmri
 
-        def set_content(self, str, excludes=EmptyI):
-                """str is the text representation of the manifest"""
+        def set_content(self, content, excludes=EmptyI):
+                """content is the text representation of the manifest"""
                 self.size = 0
                 self.actions = []
                 self.actions_bytype = {}
-                self.variants = {}   
-                self.facets = {}     
-                self.attributes = {} 
- 
+                self.variants = {}
+                self.facets = {}
+                self.attributes = {}
+
                 # So we could build up here the type/key_attr dictionaries like
                 # sdict and odict in difference() above, and have that be our
                 # main datastore, rather than the simple list we have now.  If
@@ -304,7 +307,7 @@
                 # can't be in a manifest twice.  (The problem of having the same
                 # action more than once in packages that can be installed
                 # together has to be solved somewhere else, though.)
-                for l in str.splitlines():
+                for l in content.splitlines():
                         l = l.lstrip()
                         if not l or l[0] == "#":
                                 continue
@@ -316,6 +319,11 @@
                                 e.fmri = self.fmri
                                 raise
 
+                        if action.name == "set" and \
+                            action.attrs["name"] == "authority":
+                                # Translate old action to new.
+                                action.attrs["name"] = "publisher"
+
                         if action.attrs.has_key("path"):
                                 np = action.attrs["path"].lstrip(os.path.sep)
                                 action.attrs["path"] = np
@@ -405,7 +413,8 @@
                                         elif v not in action_dict[tok_type]:
                                                 action_dict[tok_type][v] = [t]
                                         else:
-                                                action_dict[tok_type][v].append(t)
+                                                action_dict[tok_type][v].append(
+                                                    t)
                                         assert action_dict[tok_type][v]
                 return action_dict
 
@@ -433,10 +442,10 @@
         def get_variants(self, name):
                 if name not in self.attributes:
                         return None
-                vars = self.attributes[name]
-                if not isinstance(vars, str):
-                        return vars
-                return [vars]
+                variants = self.attributes[name]
+                if not isinstance(variants, str):
+                        return variants
+                return [variants]
 
         def get(self, key, default):
                 try:
--- a/src/modules/misc.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/misc.py	Mon Mar 09 16:09:13 2009 -0500
@@ -25,10 +25,14 @@
 
 import calendar
 import cStringIO
+import datetime
 import errno
 import httplib
 import locale
+import OpenSSL.crypto as osc
+import operator
 import os
+import pkg.client.api_errors as api_errors
 import pkg.portable as portable
 import pkg.urlhelpers as urlhelpers
 import platform
@@ -47,6 +51,9 @@
 from pkg.client import global_settings
 from pkg import VERSION
 
+# Minimum number of days to issue warning before a certificate expires
+MIN_WARN_DAYS = datetime.timedelta(days=30)
+
 def time_to_timestamp(t):
         """convert seconds since epoch to %Y%m%dT%H%M%SZ format"""
         # XXX optimize?
@@ -168,8 +175,8 @@
 _invalid_host_chars = re.compile(".*[^a-zA-Z0-9\-\.]+")
 _valid_proto = ["http", "https"]
 
-def valid_auth_prefix(prefix):
-        """Verify that the authority prefix only contains valid characters."""
+def valid_pub_prefix(prefix):
+        """Verify that the publisher prefix only contains valid characters."""
 
         # This is a workaround for the the hostname_re being slow when
         # it comes to finding invalid characters in the prefix string.
@@ -183,8 +190,8 @@
 
         return False
 
-def valid_auth_url(url):
-        """Verify that the authority URL contains only valid characters."""
+def valid_pub_url(url):
+        """Verify that the publisher URL contains only valid characters."""
 
         # First split the URL and check if the scheme is one we support
         o = urlparse.urlsplit(url)
@@ -464,9 +471,9 @@
                 # not provide the desired ordering. It uses the same
                 # ordering on package names as fmri.__cmp__ but it
                 # reverse sorts on version, so that 98 comes before 97.
-                # Also, authorities are taken into account so that
-                # preferred authorities come before others. Finally,
-                # authorties are presented in alphabetical order.
+                # Also, publishers are taken into account so that
+                # preferred publishers come before others. Finally,
+                # publishers are presented in alphabetical order.
                 def __fmri_cmp((f1, s1), (f2, s2)):
                         t = cmp(f1.pkg_name, f2.pkg_name)
                         if t != 0:
@@ -474,12 +481,12 @@
                         t = cmp(f2, f1)
                         if t != 0:
                                 return t
-                        if f1.preferred_authority():
+                        if f1.preferred_publisher():
                                 return -1
-                        if f2.preferred_authority():
+                        if f2.preferred_publisher():
                                 return 1
-                        return cmp(f1.get_authority(),
-                            f2.get_authority())
+                        return cmp(f1.get_publisher(),
+                            f2.get_publisher())
                 
                 res.sort(cmp=__fmri_cmp)
         return res
@@ -751,5 +758,78 @@
         def __oops(self):
                 raise TypeError, "Item assignment to ImmutableDict"
 
+def get_sorted_publishers(pubs, preferred=None):
+        spubs = []
+        for p in sorted(pubs, key=operator.attrgetter("prefix")):
+                if preferred and preferred == p.prefix:
+                        spubs.insert(0, p)
+                else:
+                        spubs.append(p)
+        return spubs
+
+def build_cert(path, uri=None, pub=None):
+        """Take the file given in path, open it, and use it to create
+        an X509 certificate object.
+
+        'uri' is an optional value indicating the uri associated with or that
+        requires the certificate for access.
+
+        'pub' is an optional string value containing the name (prefix) of a
+        related publisher."""
+
+        try:
+                cf = file(path, "rb")
+                certdata = cf.read()
+                cf.close()
+        except EnvironmentError, e:
+                if e.errno == errno.ENOENT:
+                        raise api_errors.NoSuchCertificate(path, uri=uri,
+                            publisher=pub)
+                if e.errno == errno.EACCES:
+                        raise api_errors.PermissionsException(e.filename)
+                raise
+
+        try:
+                return osc.load_certificate(osc.FILETYPE_PEM, certdata)
+        except osc.Error, e:
+                # OpenSSL.crypto.Error
+                raise api_errors.InvalidCertificate(path, uri=uri,
+                    publisher=pub)
+
+def validate_ssl_cert(ssl_cert, prefix=None, uri=None):
+        """Validates the indicated certificate and returns a pyOpenSSL object
+        representing it if it is valid."""
+        cert = build_cert(ssl_cert, uri=uri, pub=prefix)
+
+        if cert.has_expired():
+                raise api_errors.ExpiredCertificate(ssl_cert, uri=uri,
+                    publisher=prefix)
+
+        now = datetime.datetime.utcnow()
+        nb = cert.get_notBefore()
+        t = time.strptime(nb, "%Y%m%d%H%M%SZ")
+        nbdt = datetime.datetime.utcfromtimestamp(
+            calendar.timegm(t))
+
+        # PyOpenSSL's has_expired() doesn't validate the notBefore
+        # time on the certificate.  Don't ask me why.
+
+        if nbdt > now:
+                raise api_errors.NotYetValidCertificate(ssl_cert, uri=uri,
+                    publisher=prefix)
+
+        na = cert.get_notAfter()
+        t = time.strptime(na, "%Y%m%d%H%M%SZ")
+        nadt = datetime.datetime.utcfromtimestamp(
+            calendar.timegm(t))
+
+        diff = nadt - now
+
+        if diff <= MIN_WARN_DAYS:
+                raise api_errors.ExpiringCertificate(ssl_cert, uri=uri,
+                    publisher=prefix, days=diff.days)
+
+        return cert
+
 EmptyDict = ImmutableDict()
 
--- a/src/modules/server/api.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/server/api.py	Mon Mar 09 16:09:13 2009 -0500
@@ -286,7 +286,7 @@
                                 description     A descriptive paragraph for the
                                                 feed.
 
-                                authority       A fully-qualified domain name or
+                                publisher       A fully-qualified domain name or
                                                 email address that is used to
                                                 generate a unique identifier for
                                                 each entry in the feed.
--- a/src/modules/server/catalog.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/server/catalog.py	Mon Mar 09 16:09:13 2009 -0500
@@ -46,7 +46,7 @@
 class ServerCatalog(catalog.Catalog):
         """The catalog information which is only needed by the server."""
 
-        def __init__(self, cat_root, authority=None, pkg_root=None,
+        def __init__(self, cat_root, publisher=None, pkg_root=None,
             read_only=False, index_root=None, repo_root=None,
             rebuild=False, verbose=False, fork_allowed=False):
 
@@ -85,7 +85,7 @@
 
                 self.refresh_again = False
 
-                catalog.Catalog.__init__(self, cat_root, authority, pkg_root,
+                catalog.Catalog.__init__(self, cat_root, publisher, pkg_root,
                     read_only, rebuild)
 
                 searchdb_file = os.path.join(self.repo_root, "search")
@@ -304,7 +304,7 @@
                 return self._search_available or self._check_search()
 
         @staticmethod
-        def read_catalog(cat, path, auth=None):
+        def read_catalog(cat, path, pub=None):
                 """Read the catalog file in "path" and combine it with the
                 existing data in "catalog"."""
 
@@ -315,7 +315,7 @@
                                 continue
 
                         f = fmri.PkgFmri(line[7:])
-                        ServerCatalog.cache_fmri(cat, f, auth)
+                        ServerCatalog.cache_fmri(cat, f, pub)
 
                 catf.close()
 
--- a/src/modules/server/config.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/server/config.py	Mon Mar 09 16:09:13 2009 -0500
@@ -47,21 +47,21 @@
 
 # depot Server Configuration
 class SvrConfig(object):
-        """Server configuration and state object.  The authority is the default
-        authority under which packages will be stored.  Repository locations are
+        """Server configuration and state object.  The publisher is the default
+        publisher under which packages will be stored.  Repository locations are
         the primary derived configuration.  State is the current set of
         transactions and packages stored by the repository.
 
         If 'auto_create' is True, a new repository will be created at the
         location specified by 'repo_root' if one does not already exist."""
 
-        def __init__(self, repo_root, content_root, authority,
+        def __init__(self, repo_root, content_root, publisher,
             auto_create=False, fork_allowed=False):
                 self.set_repo_root(repo_root)
                 self.set_content_root(content_root)
 
                 self.auto_create = auto_create
-                self.authority = authority
+                self.publisher = publisher
                 self.fork_allowed = fork_allowed
                 self.read_only = False
                 self.mirror = False
--- a/src/modules/server/depot.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/server/depot.py	Mon Mar 09 16:09:13 2009 -0500
@@ -661,11 +661,11 @@
 
                 m.set_content(file(mpath).read())
 
-                authority, name, ver = f.tuple()
-                if authority:
-                        authority = fmri.strip_auth_pfx(authority)
+                publisher, name, ver = f.tuple()
+                if publisher:
+                        publisher = fmri.strip_pub_pfx(publisher)
                 else:
-                        authority = "Unknown"
+                        publisher = "Unknown"
                 summary = m.get("description", "")
 
                 lsummary = cStringIO.StringIO()
@@ -684,7 +684,7 @@
                 return """\
           Name: %s
        Summary: %s
-     Authority: %s
+     Publisher: %s
        Version: %s
  Build Release: %s
         Branch: %s
@@ -694,6 +694,6 @@
 
 License:
 %s
-""" % (name, summary, authority, ver.release, ver.build_release,
+""" % (name, summary, publisher, ver.release, ver.build_release,
     ver.branch, ver.get_timestamp().ctime(), misc.bytes_to_str(m.size),
     f, lsummary.read())
--- a/src/modules/server/transaction.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/server/transaction.py	Mon Mar 09 16:09:13 2009 -0500
@@ -205,7 +205,7 @@
                         return m.group(1), urllib.unquote(m.group(2))
 
                 trans_id = self.get_basename()
-                timestamp, pkg_fmri = split_trans_id(trans_id)
+                pkg_fmri = split_trans_id(trans_id)[1]
 
                 # set package state to SUBMITTED
                 pkg_state = "SUBMITTED"
@@ -363,7 +363,7 @@
 
                 cfg = self.cfg
 
-                authority, pkg_name, version = self.fmri.tuple()
+                pkg_name = self.fmri.pkg_name
                 pkgdir = os.path.join(cfg.pkg_root, urllib.quote(pkg_name, ""))
 
                 # If the directory isn't there, create it.
--- a/src/modules/updatelog.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/modules/updatelog.py	Mon Mar 09 16:09:13 2009 -0500
@@ -232,7 +232,7 @@
                 return True
 
         @staticmethod
-        def recv(c, path, ts, auth):
+        def recv(c, path, ts, pub):
                 """Take a connection object and a catalog path.  This method
                 receives a catalog from the server.  If it is an incremental
                 update, it is processed by the updatelog.  If it is a full
@@ -247,7 +247,7 @@
                 if update_type == 'incremental':
                         UpdateLog._recv_updates(c, path, ts, cl_size)
                 else:
-                        catalog.recv(c, path, auth, cl_size)
+                        catalog.recv(c, path, pub, cl_size)
 
 
         @staticmethod
--- a/src/pkgdefs/SUNWipkg/prototype	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/pkgdefs/SUNWipkg/prototype	Mon Mar 09 16:09:13 2009 -0500
@@ -103,6 +103,8 @@
 f none usr/lib/python2.4/vendor-packages/pkg/client/pkgplan.pyc 444 root bin
 f none usr/lib/python2.4/vendor-packages/pkg/client/progress.py 444 root bin
 f none usr/lib/python2.4/vendor-packages/pkg/client/progress.pyc 444 root bin
+f none usr/lib/python2.4/vendor-packages/pkg/client/publisher.py 444 root bin
+f none usr/lib/python2.4/vendor-packages/pkg/client/publisher.pyc 444 root bin
 f none usr/lib/python2.4/vendor-packages/pkg/client/query_engine.py 444 root bin
 f none usr/lib/python2.4/vendor-packages/pkg/client/query_engine.pyc 444 root bin
 f none usr/lib/python2.4/vendor-packages/pkg/client/retrieve.py 444 root bin
--- a/src/tests/api/t_fmri.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/api/t_fmri.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import unittest
 import pkg.fmri as fmri
@@ -63,7 +65,7 @@
                 self.n8 = fmri.PkgFmri(
                     "pkg://origin/[email protected],5.11-0.72:20070922T153047Z")
                 self.n9 = fmri.PkgFmri("sunos/[email protected],5.11-0",
-                    authority = "opensolaris.org")
+                    publisher = "opensolaris.org")
                 self.n10 = fmri.PkgFmri(
                     "pkg://origin2/[email protected],5.11-0.72:20070922T153047Z")
                 # same as n10
@@ -108,15 +110,15 @@
         def testfmrisimilar3(self):
                 self.assert_(not self.n1.is_similar(self.n6))
 
-        def testfmrihasauthority(self):
-                self.assert_(self.n1.has_authority() == True)
-                self.assert_(self.n2.has_authority() == False)
-                self.assert_(self.n3.has_authority() == False)
-                self.assert_(self.n4.has_authority() == False)
-                self.assert_(self.n5.has_authority() == False)
-                self.assert_(self.n6.has_authority() == False)
-                self.assert_(self.n7.has_authority() == True)
-                self.assert_(self.n8.has_authority() == True)
+        def testfmrihaspublisher(self):
+                self.assert_(self.n1.has_publisher() == True)
+                self.assert_(self.n2.has_publisher() == False)
+                self.assert_(self.n3.has_publisher() == False)
+                self.assert_(self.n4.has_publisher() == False)
+                self.assert_(self.n5.has_publisher() == False)
+                self.assert_(self.n6.has_publisher() == False)
+                self.assert_(self.n7.has_publisher() == True)
+                self.assert_(self.n8.has_publisher() == True)
 
         def testfmrihasversion(self):
                 self.assert_(self.n1.has_version() == False)
--- a/src/tests/api/t_imageconfig.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/api/t_imageconfig.py	Mon Mar 09 16:09:13 2009 -0500
@@ -49,11 +49,21 @@
 name = an image
                 
 [authority_sfbay.sun.com]
+alias: zruty
 prefix: sfbay.sun.com
-origin: http://zruty.sfbay:10001
+origin: http://zruty.sfbay:10001/
 mirrors:
 ssl_key:
 ssl_cert:
+repo.collection_type: supplemental
+repo.description: Lots of development packages here.
+repo.legal_uris: ['http://zruty.sfbay:10001/legal.html', 'http://zruty.sfbay:10001/tos.html']
+repo.name: zruty development repository
+repo.refresh_seconds: 86400
+repo.registered: True
+repo.registration_uri: http://zruty.sfbay:10001/reg.html
+repo.related_uris:
+sort_policy: priority
 """)
                 f.close()
                 self.ic = imageconfig.ImageConfig()
@@ -64,10 +74,31 @@
                 except:
                         pass
 
-        def test_read(self):
+        def test_0_read(self):
+                """Verify that read works and that values are read properly."""
                 self.ic.read(self.sample_dir)
 
-        def test_unicode(self):
+                pub = self.ic.publishers["sfbay.sun.com"]
+                self.assertEqual(pub.alias, "zruty")
+                repo = pub.selected_repository
+                origin = repo.origins[0]
+                self.assertEqual(origin.uri, "http://zruty.sfbay:10001/")
+                self.assertEqual(origin.ssl_key, None)
+                self.assertEqual(origin.ssl_cert, None)
+                self.assertEqual(repo.collection_type, "supplemental")
+                self.assertEqual(repo.description,
+                    "Lots of development packages here.")
+                self.assertEqual([u.uri for u in repo.legal_uris],
+                    ["http://zruty.sfbay:10001/legal.html",
+                    "http://zruty.sfbay:10001/tos.html"])
+                self.assertEqual(repo.name, "zruty development repository")
+                self.assertEqual(repo.refresh_seconds, 86400)
+                self.assertEqual(repo.registered, True)
+                self.assertEqual(repo.registration_uri, "http://zruty.sfbay:10001/reg.html")
+                self.assertEqual(repo.related_uris, [])
+                self.assertEqual(repo.sort_policy, "priority")
+
+        def test_1_unicode(self):
                 self.ic.read(self.sample_dir)
                 ustr = u'abc\u3041def'
                 self.ic.properties['name'] = ustr
@@ -79,7 +110,7 @@
                 shutil.rmtree(newdir)
                 self.assert_(ustr == ustr2)
 
-        def test_missing_conffile(self):
+        def test_2_missing_conffile(self):
                 #
                 #  See what happens if the conf file is missing.
                 #
--- a/src/tests/baseline.txt	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/baseline.txt	Mon Mar 09 16:09:13 2009 -0500
@@ -73,8 +73,8 @@
 api.t_fmri.py TestFMRI.testfmricmp1|pass
 api.t_fmri.py TestFMRI.testfmricmp2|pass
 api.t_fmri.py TestFMRI.testfmricmp3|pass
-api.t_fmri.py TestFMRI.testfmrihasauthority|pass
 api.t_fmri.py TestFMRI.testfmrihash|pass
+api.t_fmri.py TestFMRI.testfmrihaspublisher|pass
 api.t_fmri.py TestFMRI.testfmrihasversion|pass
 api.t_fmri.py TestFMRI.testfmriissamepkg|pass
 api.t_fmri.py TestFMRI.testfmrisimilar1|pass
@@ -102,9 +102,9 @@
 api.t_history.py TestHistory.test_7_aborted_operations|pass
 api.t_history.py TestHistory.test_8_bug_3540|pass
 api.t_history.py TestHistory.test_9_bug_5153|pass
-api.t_imageconfig.py TestImageConfig.test_missing_conffile|pass
-api.t_imageconfig.py TestImageConfig.test_read|pass
-api.t_imageconfig.py TestImageConfig.test_unicode|pass
+api.t_imageconfig.py TestImageConfig.test_0_read|pass
+api.t_imageconfig.py TestImageConfig.test_1_unicode|pass
+api.t_imageconfig.py TestImageConfig.test_2_missing_conffile|pass
 api.t_manifest.py TestManifest.test_diffs1|pass
 api.t_manifest.py TestManifest.test_diffs10|pass
 api.t_manifest.py TestManifest.test_diffs2|pass
@@ -200,6 +200,7 @@
 api.t_version.py TestVersion.testversionsuccessor9|pass
 cli.t_actuators.py TestPkgActuators.test_actuators|pass
 cli.t_api.py TestPkgApi.test_bad_orderings|pass
+cli.t_api.py TestPkgApi.test_publisher_apis|pass
 cli.t_api.py TestPkgApi.test_reset|pass
 cli.t_api_info.py TestApiInfo.test_info_local_remote|pass
 cli.t_fix.py TestFix.test_fix1|pass
@@ -222,15 +223,6 @@
 cli.t_pkg_api_install.py TestPkgApiInstall.test_install_matching|error
 cli.t_pkg_api_install.py TestPkgApiInstall.test_nonrecursive_dependent_uninstall|pass
 cli.t_pkg_api_install.py TestPkgApiInstall.test_recursive_uninstall|pass
-cli.t_pkg_authority.py TestPkgAuthorityBasics.test_authority_add_remove|pass
-cli.t_pkg_authority.py TestPkgAuthorityBasics.test_authority_bad_opts|pass
-cli.t_pkg_authority.py TestPkgAuthorityBasics.test_authority_uuid|pass
-cli.t_pkg_authority.py TestPkgAuthorityBasics.test_authority_validation|pass
-cli.t_pkg_authority.py TestPkgAuthorityBasics.test_mirror|pass
-cli.t_pkg_authority.py TestPkgAuthorityBasics.test_mirror_longopt|pass
-cli.t_pkg_authority.py TestPkgAuthorityBasics.test_missing_perms|pass
-cli.t_pkg_authority.py TestPkgAuthorityBasics.test_pkg_authority_bogus_opts|pass
-cli.t_pkg_authority.py TestPkgAuthorityMany.test_enable_disable|pass
 cli.t_pkg_contents.py TestPkgContentsBasics.test_contents_1|pass
 cli.t_pkg_contents.py TestPkgContentsBasics.test_contents_2|pass
 cli.t_pkg_contents.py TestPkgContentsBasics.test_contents_3|pass
@@ -260,8 +252,8 @@
 cli.t_pkg_image_create.py TestImageCreateNoDepot.test_763a|pass
 cli.t_pkg_image_create.py TestImageCreateNoDepot.test_763c|pass
 cli.t_pkg_image_create.py TestImageCreateNoDepot.test_765|pass
-cli.t_pkg_image_create.py TestImageCreateNoDepot.test_bad_authority_options|pass
 cli.t_pkg_image_create.py TestImageCreateNoDepot.test_bad_image_create|pass
+cli.t_pkg_image_create.py TestImageCreateNoDepot.test_bad_publisher_options|pass
 cli.t_pkg_image_create.py TestPkgImageCreateBasics.test_3588|pass
 cli.t_pkg_image_create.py TestPkgImageCreateBasics.test_3588_1|pass
 cli.t_pkg_image_create.py TestPkgImageCreateBasics.test_3588_2|pass
@@ -271,8 +263,8 @@
 cli.t_pkg_image_create.py TestPkgImageCreateNoDepot.test_763a|pass
 cli.t_pkg_image_create.py TestPkgImageCreateNoDepot.test_763c|pass
 cli.t_pkg_image_create.py TestPkgImageCreateNoDepot.test_765|pass
-cli.t_pkg_image_create.py TestPkgImageCreateNoDepot.test_bad_authority_options|pass
 cli.t_pkg_image_create.py TestPkgImageCreateNoDepot.test_bad_image_create|pass
+cli.t_pkg_image_create.py TestPkgImageCreateNoDepot.test_bad_publisher_options|pass
 cli.t_pkg_image_update.py TestImageUpdate.test_image_update_bad_opts|pass
 cli.t_pkg_info.py TestPkgInfoBasics.test_bug_2274|pass
 cli.t_pkg_info.py TestPkgInfoBasics.test_info_empty_image|pass
@@ -338,14 +330,14 @@
 cli.t_pkg_install.py TestTwoDepots.test_basics_1|pass
 cli.t_pkg_install.py TestTwoDepots.test_basics_2|pass
 cli.t_pkg_install.py TestTwoDepots.test_basics_3|pass
-cli.t_pkg_install.py TestTwoDepots.test_uninstall_from_wrong_authority|pass
+cli.t_pkg_install.py TestTwoDepots.test_uninstall_from_wrong_publisher|pass
 cli.t_pkg_install.py TestTwoDepots.test_upgrade_non_preferred_to_preferred|pass
 cli.t_pkg_install.py TestTwoDepots.test_upgrade_non_preferred_to_preferred_incorporated|pass
 cli.t_pkg_install.py TestTwoDepots.test_upgrade_preferred_to_non_preferred|pass
 cli.t_pkg_install.py TestTwoDepots.test_upgrade_preferred_to_non_preferred_incorporated|pass
-cli.t_pkg_install.py TestTwoDepots.test_yyy_install_after_authority_removal|pass
-cli.t_pkg_install.py TestTwoDepots.test_zzz_uninstall_after_preferred_authority_change|pass
-cli.t_pkg_install.py TestTwoDepots.test_zzz_uninstall_after_preferred_authority_removal|pass
+cli.t_pkg_install.py TestTwoDepots.test_yyy_install_after_publisher_removal|pass
+cli.t_pkg_install.py TestTwoDepots.test_zzz_uninstall_after_preferred_publisher_change|pass
+cli.t_pkg_install.py TestTwoDepots.test_zzz_uninstall_after_preferred_publisher_removal|pass
 cli.t_pkg_intent.py TestPkgIntent.test_0_info|pass
 cli.t_pkg_intent.py TestPkgIntent.test_1_install_uninstall|pass
 cli.t_pkg_intent.py TestPkgIntent.test_2_upgrade|pass
@@ -367,12 +359,22 @@
 cli.t_pkg_property.py TestPkgInfoBasics.test_bug_4372|pass
 cli.t_pkg_property.py TestPkgInfoBasics.test_missing_permssions|pass
 cli.t_pkg_property.py TestPkgInfoBasics.test_pkg_properties|pass
+cli.t_pkg_publisher.py TestPkgPublisherBasics.test_mirror|pass
+cli.t_pkg_publisher.py TestPkgPublisherBasics.test_mirror_longopt|pass
+cli.t_pkg_publisher.py TestPkgPublisherBasics.test_missing_perms|pass
+cli.t_pkg_publisher.py TestPkgPublisherBasics.test_pkg_publisher_bogus_opts|pass
+cli.t_pkg_publisher.py TestPkgPublisherBasics.test_publisher_add_remove|pass
+cli.t_pkg_publisher.py TestPkgPublisherBasics.test_publisher_bad_opts|pass
+cli.t_pkg_publisher.py TestPkgPublisherBasics.test_publisher_uuid|pass
+cli.t_pkg_publisher.py TestPkgPublisherBasics.test_publisher_validation|pass
+cli.t_pkg_publisher.py TestPkgPublisherMany.test_enable_disable|pass
 cli.t_pkg_rebuild_index.py TestPkgRebuildIndex.test_rebuild_index_bad_opts|pass
 cli.t_pkg_rebuild_index.py TestPkgRebuildIndex.test_rebuild_index_bad_perms|pass
 cli.t_pkg_refresh.py TestPkgRefreshMulti.test_general_refresh|pass
+cli.t_pkg_refresh.py TestPkgRefreshMulti.test_refresh_certificate_problems|pass
 cli.t_pkg_refresh.py TestPkgRefreshMulti.test_refresh_cli_options|pass
-cli.t_pkg_refresh.py TestPkgRefreshMulti.test_set_authority_induces_delayed_full_refresh|pass
-cli.t_pkg_refresh.py TestPkgRefreshMulti.test_set_authority_induces_full_refresh|pass
+cli.t_pkg_refresh.py TestPkgRefreshMulti.test_set_publisher_induces_delayed_full_refresh|pass
+cli.t_pkg_refresh.py TestPkgRefreshMulti.test_set_publisher_induces_full_refresh|pass
 cli.t_pkg_refresh.py TestPkgRefreshMulti.test_specific_refresh|pass
 cli.t_pkg_search.py TestPkgSearchBasics.test_bug_2849|pass
 cli.t_pkg_search.py TestPkgSearchBasics.test_bug_2863|pass
--- a/src/tests/cli/t_api.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_api.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,23 +20,27 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
 	testutils.setup_environment("../../../proto")
 
+import cStringIO
+import pkg.fmri as fmri
 import os
-import time
-import unittest
-import sys
-from stat import *
 import pkg.client.api as api
 import pkg.client.api_errors as api_errors
 import pkg.client.progress as progress
+import sys
+import tempfile
+import time
+import unittest
 
-API_VERSION = 10
+API_VERSION = 11
 PKG_CLIENT_NAME = "pkg"
 
 class TestPkgApi(testutils.SingleDepotTestCase):
@@ -53,6 +57,45 @@
             add file /tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1
             close """
         
+        bar10 = """
+            open [email protected],5.11-0
+            close """
+
+        p5i_bobcat = """{
+  "packages": [
+    "pkg:/[email protected],5.11-0", 
+    "baz"
+  ], 
+  "publishers": [
+    {
+      "alias": "cat", 
+      "name": "bobcat", 
+      "packages": [
+        "pkg:/[email protected],5.11-0"
+      ], 
+      "repositories": [
+        {
+          "collection_type": "core", 
+          "description": "xkcd.net/325", 
+          "legal_uris": [
+            "http://xkcd.com/license.html"
+          ], 
+          "mirrors": [], 
+          "name": "source", 
+          "origins": [
+            "http://localhost:12001/"
+          ], 
+          "refresh_seconds": 43200, 
+          "registration_uri": "", 
+          "related_uris": []
+        }
+      ]
+    }
+  ], 
+  "version": 1
+}"""
+
+
         misc_files = [ "/tmp/libc.so.1", "/tmp/cat", "/tmp/baz" ]
 
         def setUp(self):
@@ -219,3 +262,174 @@
                 self.assert_(api_obj.describe() is None)
 
                 self.pkg("verify")
+
+
+        def test_publisher_apis(self):
+                """Verify that the publisher api methods work as expected.
+                
+                Note that not all methods are tested here as this would be
+                redundant since other tests for the client will use those
+                methods indirectly."""
+
+                durl = self.dc.get_depot_url()
+                plist = self.pkgsend_bulk(durl, self.foo10 + self.bar10)
+                self.image_create(durl, prefix="bobcat")
+
+                progresstracker = progress.NullProgressTracker()
+                api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
+                    progresstracker, lambda x: False, PKG_CLIENT_NAME)
+
+                # Verify that existence tests succeed.
+                self.assertTrue(api_obj.has_publisher("bobcat"))
+
+                # Verify preferred publisher prefix is returned correctly.
+                self.assertEqual(api_obj.get_preferred_publisher(), "bobcat")
+
+                # Verify that get_publisher returned the correct publisher object.
+                pub = api_obj.get_publisher(prefix="bobcat")
+                self.assertEqual(pub.prefix, "bobcat")
+
+                # Verify that not specifying matching criteria for get_publisher
+                # raises a UnknownPublisher exception.
+                self.assertRaises(api_errors.UnknownPublisher,
+                    api_obj.get_publisher, "zuul")
+                self.assertRaises(api_errors.UnknownPublisher,
+                    api_obj.get_publisher)
+
+                # Verify that publisher objects returned from get_publishers
+                # match those returned by get_publisher.
+                pubs = api_obj.get_publishers()
+                self.assertEqual(pub.prefix, pubs[0].prefix)
+                self.assertEqual(id(pub), id(pubs[0]))
+
+                # Verify that duplicate actually creates duplicates.
+                cpub = api_obj.get_publisher(prefix="bobcat", duplicate=True)
+                self.assertNotEqual(id(pub), id(cpub))
+
+                # Now modify publisher information and update.
+                cpub.alias = "cat"
+                repo = cpub.selected_repository
+                repo.name = "source"
+                repo.description = "xkcd.net/325"
+                repo.legal_uris = ["http://xkcd.com/license.html"]
+                repo.refresh_seconds = 43200
+                repo.registered = False
+                api_obj.update_publisher(cpub)
+
+                # Verify that the update happened.
+                pub = api_obj.get_publisher(prefix="bobcat")
+                self.assertEqual(pub.alias, "cat")
+                repo = pub.selected_repository
+                self.assertEqual(repo.name, "source")
+                self.assertEqual(repo.description, "xkcd.net/325")
+                self.assertEqual(repo.legal_uris[0],
+                    "http://xkcd.com/license.html")
+                self.assertEqual(repo.refresh_seconds, 43200)
+                self.assertEqual(repo.registered, False)
+
+                cpub = None
+
+                cpubs = api_obj.get_publishers(duplicate=True)
+                self.assertNotEqual(id(pub), id(cpubs[0]))
+                cpubs = None
+
+                # Verify that publisher_last_update_time returns a value.
+                self.assertTrue(
+                    api_obj.get_publisher_last_update_time("bobcat"))
+
+                # Verify that p5i export and parse works as expected.
+
+                # Ensure that PackageInfo, PkgFmri, and strings are all
+                # supported properly.
+
+                # Strip timestamp information so that comparison with
+                # pre-generated test data will succeed.
+                ffoo = fmri.PkgFmri(plist[0])
+                sfoo = str(ffoo).replace(":%s" % ffoo.version.timestr, "")
+                ffoo = fmri.PkgFmri(sfoo)
+
+                fbar = fmri.PkgFmri(plist[1])
+                sbar = str(fbar).replace(":%s" % fbar.version.timestr, "")
+                fbar = fmri.PkgFmri(sbar)
+
+                # Build a simple list of packages.
+                pnames = {
+                    "bobcat": (api.PackageInfo(ffoo),),
+                    "": [fbar, "baz"],
+                }
+
+                # Dump the p5i data.
+                fobj = cStringIO.StringIO()
+                api_obj.write_p5i(fileobj=fobj, pkg_names=pnames, pubs=[pub])
+
+                # Verify that output matches expected output.
+                fobj.seek(0)
+                output = fobj.read()
+                self.assertEqual(output, self.p5i_bobcat)
+
+                def validate_results(results):
+                        # First result should be 'bobcat' publisher and its
+                        # pkg_names.
+                        pub, pkg_names = results[0]
+
+                        self.assertEqual(pub.prefix, "bobcat")
+                        self.assertEqual(pub.alias, "cat")
+                        repo = pub.selected_repository
+                        self.assertEqual(repo.name, "source")
+                        self.assertEqual(repo.description, "xkcd.net/325")
+                        self.assertEqual(repo.legal_uris[0],
+                            "http://xkcd.com/license.html")
+                        self.assertEqual(repo.refresh_seconds, 43200)
+                        self.assertEqual(pkg_names, [sfoo])
+
+                        # Last result should be no publisher and a list of
+                        # pkg_names.
+                        pub, pkg_names = results[1]
+                        self.assertEqual(pub, None)
+                        self.assertEqual(pkg_names, [sbar, "baz"])
+
+                # Verify that parse returns the expected object and information
+                # when provided a fileobj.
+                fobj.seek(0)
+                validate_results(api_obj.parse_p5i(fileobj=fobj))
+
+                # Verify that an add of the parsed object works (the name has to
+                # be changed to prevent a duplicate error here).
+                fobj.seek(0)
+                results = api_obj.parse_p5i(fileobj=fobj)
+                pub, pkg_names = results[0]
+
+                pub.prefix = "p5icat"
+                pub.alias = "copycat"
+                api_obj.add_publisher(pub, refresh_allowed=False)
+
+                # Now verify that we can retrieve the added publisher.
+                api_obj.get_publisher(prefix=pub.prefix)
+                api_obj.get_publisher(alias=pub.alias)
+
+                # Verify that parse returns the expected object and information
+                # when provided a file path.
+                fobj.seek(0)
+                (fd1, path1) = tempfile.mkstemp(dir=self.get_test_prefix())
+                os.write(fd1, fobj.read())
+                validate_results(api_obj.parse_p5i(location=path1))
+
+                # Verify that parse returns the expected object and information
+                # when provided a file URI.
+                validate_results(api_obj.parse_p5i(location="file://" + path1))
+                fobj.close()
+                fobj = None
+
+                # Verify that appropriate exceptions are raised for invalid
+                # p5i information.
+                self.assertRaises(api_errors.RetrievalError,
+                    api_obj.parse_p5i, location="file://foo")
+
+                self.assertRaises(api_errors.RetrievalError,
+                    api_obj.parse_p5i, location="/tmp/foo")
+
+                self.assertRaises(api_errors.InvalidP5IFile,
+                    api_obj.parse_p5i, location="/tmp/libc.so.1")
+
+                self.assertRaises(api_errors.InvalidP5IFile,
+                    api_obj.parse_p5i, location="file:///tmp/libc.so.1")
--- a/src/tests/cli/t_api_info.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_api_info.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -38,7 +40,7 @@
 import pkg.client.api_errors as api_errors
 import pkg.client.progress as progress
 
-API_VERSION = 10
+API_VERSION = 11
 PKG_CLIENT_NAME = "pkg"
 
 class TestApiInfo(testutils.SingleDepotTestCase):
@@ -177,8 +179,8 @@
 
                 self.assert_(res.pkg_stem is not None)
                 self.assert_(res.summary is not None)
-                self.assert_(res.authority is not None)
-                self.assert_(res.preferred_authority is not None)
+                self.assert_(res.publisher is not None)
+                self.assert_(res.preferred_publisher is not None)
                 self.assert_(res.version is not None)
                 self.assert_(res.build_release is not None)
                 self.assert_(res.branch is not None)
@@ -220,8 +222,8 @@
                 self.assert_(res.summary is None)
                 self.assert_(res.category_info_list == [])
                 self.assert_(res.state is None)
-                self.assert_(res.authority is None)
-                self.assert_(res.preferred_authority is None)
+                self.assert_(res.publisher is None)
+                self.assert_(res.preferred_publisher is None)
                 self.assert_(res.version is None)
                 self.assert_(res.build_release is None)
                 self.assert_(res.branch is None)
@@ -275,8 +277,8 @@
                 self.assert_(res.summary is None)
                 self.assert_(res.category_info_list == [])
                 self.assert_(res.state is None)
-                self.assert_(res.authority is None)
-                self.assert_(res.preferred_authority is None)
+                self.assert_(res.publisher is None)
+                self.assert_(res.preferred_publisher is None)
                 self.assert_(res.version is None)
                 self.assert_(res.build_release is None)
                 self.assert_(res.branch is None)
--- a/src/tests/cli/t_pkg_api_install.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkg_api_install.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -36,7 +38,7 @@
 import pkg.client.api_errors as api_errors
 import pkg.client.progress as progress
 
-API_VERSION = 10
+API_VERSION = 11
 PKG_CLIENT_NAME = "pkg"
 
 class TestPkgApiInstall(testutils.SingleDepotTestCase):
--- a/src/tests/cli/t_pkg_authority.py	Mon Mar 09 13:16:54 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,281 +0,0 @@
-#!/usr/bin/python2.4
-#
-# 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 testutils
-if __name__ == "__main__":
-        testutils.setup_environment("../../../proto")
-
-import unittest
-import os
-import tempfile
-
-class TestPkgAuthorityBasics(testutils.SingleDepotTestCase):
-        # Only start/stop the depot once (instead of for every test)
-        persistent_depot = True
-
-        def test_pkg_authority_bogus_opts(self):
-                """ pkg bogus option checks """
-
-                durl = self.dc.get_depot_url()
-                self.image_create(durl)
-
-                self.pkg("set-authority -@ test3", exit=2)
-                self.pkg("authority -@ test5", exit=2)
-                self.pkg("set-authority -k", exit=2)
-                self.pkg("set-authority -c", exit=2)
-                self.pkg("set-authority -O", exit=2)
-                self.pkg("unset-authority", exit=2)
-
-        def test_authority_add_remove(self):
-                """pkg: add and remove an authority"""
-                durl = self.dc.get_depot_url()
-                self.image_create(durl)
-
-                self.pkg("set-authority -O http://%s1 test1" % self.bogus_url,
-                    exit=1)
-                self.pkg("set-authority --no-refresh -O http://%s1 test1" %
-                    self.bogus_url)
-                self.pkg("authority | grep test")
-                self.pkg("set-authority -P -O http://%s2 test2" %
-                    self.bogus_url, exit=1)
-                self.pkg("set-authority -P --no-refresh -O http://%s2 test2" %
-                    self.bogus_url)
-                self.pkg("authority | grep test2")
-                self.pkg("unset-authority test1")
-                self.pkg("authority | grep test1", exit=1)
-                self.pkg("unset-authority test2", exit=1)
-
-        def test_authority_uuid(self):
-                """pkg: set the uuid for an authority"""
-                durl = self.dc.get_depot_url()
-                self.image_create(durl)
-                self.pkg("set-authority -O http://%s1 --no-refresh --reset-uuid test1" %
-                    self.bogus_url)
-                self.pkg("set-authority --no-refresh --reset-uuid test1")
-
-        def test_authority_bad_opts(self):
-                """pkg: more insidious option abuse for set-authority"""
-                durl = self.dc.get_depot_url()
-                self.image_create(durl)
-
-                key_fh, key_path = tempfile.mkstemp()
-                cert_fh, cert_path = tempfile.mkstemp()
-
-                self.pkg(
-                    "set-authority -O http://%s1 test1 -O http://%s2 test2" %
-                    (self.bogus_url, self.bogus_url), exit=2)
-
-                self.pkg("set-authority -O http://%s1 test1" % self.bogus_url,
-                    exit=1)
-                self.pkg("set-authority -O http://%s2 test2" % self.bogus_url,
-                    exit=1)
-                self.pkg("set-authority --no-refresh -O http://%s1 test1" %
-                    self.bogus_url)
-                self.pkg("set-authority --no-refresh -O http://%s2 test2" %
-                    self.bogus_url)
-
-                self.pkg("set-authority -k %s test1" % key_path)
-                os.close(key_fh)
-                os.unlink(key_path)
-                self.pkg("set-authority -k %s test2" % key_path, exit=1)
-
-                self.pkg("set-authority -c %s test1" % cert_path)
-                os.close(cert_fh)
-                os.unlink(cert_path)
-                self.pkg("set-authority -c %s test2" % cert_path, exit=1)
-
-                self.pkg("authority test1")
-                self.pkg("authority test3", exit=1)
-                self.pkg("authority -H | grep URL", exit=1)
-
-        def test_authority_validation(self):
-                """Verify that we catch poorly formed auth prefixes and URL"""
-                durl = self.dc.get_depot_url()
-                self.image_create(durl)
-
-                self.pkg("set-authority -O http://%s1 test1" % self.bogus_url,
-                    exit=1)
-                self.pkg("set-authority --no-refresh -O http://%s1 test1" %
-                    self.bogus_url)
-
-                self.pkg(("set-authority -O http://%s2 " % self.bogus_url) +
-                    "$%^8", exit=1)
-                self.pkg(("set-authority -O http://%s2 " % self.bogus_url) +
-                    "8^$%", exit=1)
-                self.pkg("set-authority -O http://*^5$% test2", exit=1)
-                self.pkg("set-authority -O http://%s1:abcde test2" %
-                    self.bogus_url, exit=1)
-                self.pkg("set-authority -O ftp://%s2 test2" % self.bogus_url,
-                    exit=1)
-
-        def test_mirror(self):
-                """Test set-mirror and unset-mirror."""
-                durl = self.dc.get_depot_url()
-                pfx = "mtest"
-                self.image_create(durl, prefix = pfx)
-
-                self.pkg("set-authority -m http://%s1 mtest" % self.bogus_url)
-                self.pkg("set-authority -m http://%s2 mtest" %
-                    self.bogus_url)
-                self.pkg("set-authority -m http://%s5" % self.bogus_url, exit=2)
-                self.pkg("set-authority -m mtest", exit=2)
-                self.pkg("set-authority -m http://%s1 mtest" % self.bogus_url,
-                    exit=1)
-                self.pkg("set-authority -m http://%s5 test" % self.bogus_url,
-                    exit=1)
-                self.pkg("set-authority -m %s7 mtest" % self.bogus_url, exit=1)
-
-                self.pkg("set-authority -M http://%s1 mtest" % self.bogus_url)
-                self.pkg("set-authority -M http://%s2 mtest" %
-                    self.bogus_url)
-                self.pkg("set-authority -M mtest http://%s2 http://%s4" %
-                    (self.bogus_url, self.bogus_url), exit=2)
-                self.pkg("set-authority -M http://%s5" % self.bogus_url, exit=2)
-                self.pkg("set-authority -M mtest", exit=2)
-                self.pkg("set-authority -M http://%s5 test" % self.bogus_url,
-                    exit=1)
-                self.pkg("set-authority -M http://%s6 mtest" % self.bogus_url,
-                    exit=1)
-                self.pkg("set-authority -M %s7 mtest" % self.bogus_url, exit=1)
-
-        def test_missing_perms(self):
-                """Bug 2393"""
-                durl = self.dc.get_depot_url()
-                pfx = "mtest"
-                self.image_create(durl, prefix=pfx)
-
-                self.pkg("set-authority --no-refresh -O http://%s1 test1" %
-                    self.bogus_url, su_wrap=True, exit=1)
-                self.pkg("set-authority --no-refresh -O http://%s1 foo" %
-                    self.bogus_url)
-                self.pkg("authority | grep foo")
-                self.pkg("set-authority -P --no-refresh -O http://%s2 test2" %
-                    self.bogus_url, su_wrap=True, exit=1)
-                self.pkg("unset-authority foo", su_wrap=True, exit=1)
-                self.pkg("unset-authority foo")
-
-                self.pkg("set-authority -m http://%s1 mtest" % self.bogus_url, \
-                    su_wrap=True, exit=1)
-                self.pkg("set-authority -m http://%s2 mtest" %
-                    self.bogus_url)
-
-                self.pkg("set-authority -M http://%s2 mtest" %
-                    self.bogus_url, su_wrap=True, exit=1)
-                self.pkg("set-authority -M http://%s2 mtest" %
-                    self.bogus_url)
-
-        def test_mirror_longopt(self):
-                """Test set-mirror and unset-mirror."""
-                durl = self.dc.get_depot_url()
-                pfx = "mtest"
-                self.image_create(durl, prefix = pfx)
-
-                self.pkg("set-authority --add-mirror=http://%s1 mtest" %
-                    self.bogus_url)
-                self.pkg("set-authority --add-mirror=http://%s2 mtest" %
-                    self.bogus_url)
-                self.pkg("set-authority --add-mirror=http://%s5" %
-                    self.bogus_url, exit=2)
-                self.pkg("set-authority --add-mirror=mtest", exit=2)
-                self.pkg("set-authority --add-mirror=http://%s1 mtest" %
-                    self.bogus_url, exit=1)
-                self.pkg("set-authority --add-mirror=http://%s5 test" %
-                    self.bogus_url, exit=1)
-                self.pkg("set-authority --add-mirror=%s7 mtest" %
-                    self.bogus_url, exit=1)
-
-                self.pkg("set-authority --remove-mirror=http://%s1 mtest" %
-                    self.bogus_url)
-                self.pkg("set-authority --remove-mirror=http://%s2 mtest" %
-                    self.bogus_url)
-                self.pkg("set-authority --remove-mirror=mtest http://%s2 http://%s4" %
-                    (self.bogus_url, self.bogus_url), exit=2)
-                self.pkg("set-authority --remove-mirror=http://%s5" %
-                    self.bogus_url, exit=2)
-                self.pkg("set-authority --remove-mirror=mtest", exit=2)
-                self.pkg("set-authority --remove-mirror=http://%s5 test" %
-                    self.bogus_url, exit=1)
-                self.pkg("set-authority --remove-mirror=http://%s6 mtest" %
-                    self.bogus_url, exit=1)
-                self.pkg("set-authority --remove-mirror=%s7 mtest" %
-                    self.bogus_url, exit=1)
-
-class TestPkgAuthorityMany(testutils.ManyDepotTestCase):
-        # Only start/stop the depot once (instead of for every test)
-        persistent_depot = True
-
-        foo1 = """
-            open foo@1,5.11-0
-            close """
-
-        bar1 = """
-            open bar@1,5.11-0
-            close """
-
-        def setUp(self):
-                testutils.ManyDepotTestCase.setUp(self, 2)
-
-                durl1 = self.dcs[1].get_depot_url()
-                self.pkgsend_bulk(durl1, self.foo1)
-
-                durl2 = self.dcs[2].get_depot_url()
-                self.pkgsend_bulk(durl2, self.bar1)
-
-                self.image_create(durl1, prefix = "test1")
-                self.pkg("set-authority -O " + durl2 + " test2")
-
-        def tearDown(self):
-                testutils.ManyDepotTestCase.tearDown(self)
-
-        def test_enable_disable(self):
-                """Test enable and disable."""
-
-                self.pkg("authority | grep test1")
-                self.pkg("authority | grep test2")
-
-                self.pkg("set-authority -d test2")
-                self.pkg("authority | grep test2", exit=1)
-                self.pkg("list -a bar", exit=1)
-                self.pkg("authority -a | grep test2")
-                self.pkg("set-authority -P test2", exit=1)
-                self.pkg("authority test2")
-                self.pkg("set-authority -e test2")
-                self.pkg("authority | grep test2")
-                self.pkg("list -a bar")
-
-                self.pkg("set-authority --disable test2")
-                self.pkg("authority | grep test2", exit=1)
-                self.pkg("list -a bar", exit=1)
-                self.pkg("authority -a | grep test2")
-                self.pkg("set-authority --enable test2")
-                self.pkg("authority | grep test2")
-                self.pkg("list -a bar")
-
-                # should fail because test is the preferred authority
-                self.pkg("set-authority -d test1", exit=1)
-                self.pkg("set-authority --disable test1", exit=1)
-
-if __name__ == "__main__":
-        unittest.main()
--- a/src/tests/cli/t_pkg_history.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkg_history.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -90,11 +92,11 @@
                     "install foo",
                     "uninstall foo",
                     "image-update",
-                    "set-authority -O " + durl2 + " test2",
-                    "set-authority -P test1",
-                    "set-authority -m " + durl2 + " test1",
-                    "set-authority -M " + durl2 + " test1",
-                    "unset-authority test2",
+                    "set-publisher -O " + durl2 + " test2",
+                    "set-publisher -P test1",
+                    "set-publisher -m " + durl2 + " test1",
+                    "set-publisher -M " + durl2 + " test1",
+                    "unset-publisher test2",
                     "rebuild-index"
                 ]
 
@@ -102,11 +104,10 @@
                     "install",
                     "uninstall",
                     "image-update",
-                    "set-authority",
-                    "set-preferred-authority",
-                    "add-mirror",
-                    "delete-mirror",
-                    "delete-authority",
+                    "add-publisher",
+                    "update-publisher",
+                    "set-preferred-publisher",
+                    "remove-publisher",
                     "rebuild-index"
                 ]
 
--- a/src/tests/cli/t_pkg_image_create.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkg_image_create.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -51,7 +53,7 @@
 
 
         def test_766(self):
-                """Bug 766: image-create without authority prefix specified."""
+                """Bug 766: image-create without publisher prefix specified."""
 
                 durl = self.dc.get_depot_url()
 
@@ -108,7 +110,7 @@
 		    self.image_create, durl)
 
         def test_765(self):
-                """Bug 765: malformed authority URL."""
+                """Bug 765: malformed publisher URL."""
 
                 durl = "bar=baz"
                 self.assertRaises(testutils.UnexpectedExitCodeException, \
@@ -122,13 +124,13 @@
 
         def test_763c(self):
                 """Bug 763, traceback 3: -a given to image-create, but no
-                authority specified."""
+                publisher specified."""
 
                 self.assertRaises(testutils.UnexpectedExitCodeException, \
                     self.pkg, "image-create -a foo")
 
-        def test_bad_authority_options(self):
-                """More tests that abuse the authority prefix and URL."""
+        def test_bad_publisher_options(self):
+                """More tests that abuse the publisher prefix and URL."""
 
                 self.assertRaises(testutils.UnexpectedExitCodeException, \
                     self.pkg, "image-create -a $%^8" + ("=http://%s1" %
@@ -165,7 +167,7 @@
 		    self.image_create, durl)
 
         def test_765(self):
-                """Bug 765: malformed authority URL."""
+                """Bug 765: malformed publisher URL."""
 
                 durl = "bar=baz"
                 self.assertRaises(testutils.UnexpectedExitCodeException, \
@@ -179,13 +181,13 @@
 
         def test_763c(self):
                 """Bug 763, traceback 3: -a given to image-create, but no
-                authority specified."""
+                publisher specified."""
 
                 self.assertRaises(testutils.UnexpectedExitCodeException, \
                     self.pkg, "image-create -a foo")
 
-        def test_bad_authority_options(self):
-                """More tests that abuse the authority prefix and URL."""
+        def test_bad_publisher_options(self):
+                """More tests that abuse the publisher prefix and URL."""
 
                 self.assertRaises(testutils.UnexpectedExitCodeException, \
                     self.pkg, "image-create -a $%^8" + ("=http://%s1" %
--- a/src/tests/cli/t_pkg_install.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkg_install.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -1733,8 +1735,8 @@
         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 """
+                    depot1 is mapped to publisher test1 (preferred)
+                    depot2 is mapped to publisher test2 """
 
                 testutils.ManyDepotTestCase.setUp(self, 2)
 
@@ -1756,11 +1758,11 @@
                 self.pkgsend_bulk(durl2, self.upgrade_p11)
                 self.pkgsend_bulk(durl2, self.upgrade_np10)
 
-                # Create image and hence primary authority
+                # Create image and hence primary publisher
                 self.image_create(durl1, prefix="test1")
 
-                # Create second authority using depot #2
-                self.pkg("set-authority -O " + durl2 + " test2")
+                # Create second publisher using depot #2
+                self.pkg("set-publisher -O " + durl2 + " test2")
 
         def tearDown(self):
                 testutils.ManyDepotTestCase.tearDown(self)
@@ -1791,40 +1793,40 @@
                 self.pkg("uninstall foo")
 
         def test_basics_2(self):
-                """ Test install from an explicit preferred authority """
+                """ Test install from an explicit preferred publisher """
                 self.pkg("install pkg://test1/foo")
                 self.pkg("list foo")
                 self.pkg("list pkg://test1/foo")
                 self.pkg("uninstall foo")
 
         def test_basics_3(self):
-                """ Test install from an explicit non-preferred authority """
+                """ Test install from an explicit non-preferred publisher """
                 self.pkg("install pkg://test2/foo")
                 self.pkg("list foo")
                 self.pkg("list pkg://test2/foo")
                 self.pkg("uninstall foo")
 
         def test_upgrade_preferred_to_non_preferred(self):
-                """Install a package from the preferred authority, and then
+                """Install a package from the preferred publisher, and then
                 upgrade it, implicitly switching to a non-preferred
-                authority."""
+                publisher."""
                 self.pkg("list -a upgrade-p")
                 self.pkg("install [email protected]")
                 self.pkg("install [email protected]")
                 self.pkg("uninstall upgrade-p")
 
         def test_upgrade_non_preferred_to_preferred(self):
-                """Install a package from a non-preferred authority, and then
-                upgrade it, implicitly switching to the preferred authority."""
+                """Install a package from a non-preferred publisher, and then
+                upgrade it, implicitly switching to the preferred publisher."""
                 self.pkg("list -a upgrade-np")
                 self.pkg("install [email protected]")
                 self.pkg("install [email protected]")
                 self.pkg("uninstall upgrade-np")
 
         def test_upgrade_preferred_to_non_preferred_incorporated(self):
-                """Install a package from the preferred authority, and then
+                """Install a package from the preferred publisher, and then
                 upgrade it, implicitly switching to a non-preferred
-                authority, when the package is constrained by an
+                publisher, when the package is constrained by an
                 incorporation."""
                 self.pkg("list -a upgrade-p incorp-p")
                 self.pkg("install [email protected]")
@@ -1834,9 +1836,9 @@
                 self.pkg("uninstall upgrade-p")
 
         def test_upgrade_non_preferred_to_preferred_incorporated(self):
-                """Install a package from the preferred authority, and then
+                """Install a package from the preferred publisher, and then
                 upgrade it, implicitly switching to a non-preferred
-                authority, when the package is constrained by an
+                publisher, when the package is constrained by an
                 incorporation."""
                 self.pkg("list -a upgrade-np incorp-np")
                 self.pkg("install [email protected]")
@@ -1845,22 +1847,22 @@
                 self.pkg("list [email protected]")
                 self.pkg("uninstall upgrade-np")
 
-        def test_uninstall_from_wrong_authority(self):
-                """Install a package from an authority and try to remove it
-                using a different authority name; this should fail."""
+        def test_uninstall_from_wrong_publisher(self):
+                """Install a package from a publisher and try to remove it
+                using a different publisher name; this should fail."""
                 self.pkg("install foo")
                 self.pkg("uninstall pkg://test2/foo", exit=1)
                 # Check to make sure that uninstalling using the explicit
-                # authority works
+                # publisher works
                 self.pkg("uninstall pkg://test1/foo")
 
-        def test_yyy_install_after_authority_removal(self):
-                """Install a package from an authority that has an optional
-                dependency; then change the preferred authority and remove the
-                original authority and attempt to uninstall the package."""
+        def test_yyy_install_after_publisher_removal(self):
+                """Install a package from a publisher that has an optional
+                dependency; then change the preferred publisher and remove the
+                original publisher and attempt to uninstall the package."""
                 self.pkg("install [email protected]")
-                self.pkg("set-authority -P test2")
-                self.pkg("unset-authority test1")
+                self.pkg("set-publisher -P test2")
+                self.pkg("unset-publisher test1")
                 # 
                 # 
                 self.pkg("install [email protected]")
@@ -1868,25 +1870,25 @@
                 self.pkg("image-update") 
                 # Change the image metadata back to where it was, in preparation
                 # for subsequent tests.
-                self.pkg("set-authority -O %s -P test1" % \
+                self.pkg("set-publisher -O %s -P test1" % \
                     self.dcs[1].get_depot_url())
 
-        def test_zzz_uninstall_after_preferred_authority_change(self):
-                """Install a package from the preferred authority, change the
-                preferred authority, and attempt to remove the package."""
+        def test_zzz_uninstall_after_preferred_publisher_change(self):
+                """Install a package from the preferred publisher, change the
+                preferred publisher, and attempt to remove the package."""
                 self.pkg("install [email protected]")
-                self.pkg("set-authority -P test2")
+                self.pkg("set-publisher -P test2")
                 self.pkg("uninstall foo")
                 # Change the image metadata back to where it was, in preparation
                 # for the next test.
-                self.pkg("set-authority -P test1")
+                self.pkg("set-publisher -P test1")
 
-        def test_zzz_uninstall_after_preferred_authority_removal(self):
-                """Install a package from the preferred authority, remove the
-                preferred authority, and attempt to remove the package."""
+        def test_zzz_uninstall_after_preferred_publisher_removal(self):
+                """Install a package from the preferred publisher, remove the
+                preferred publisher, and attempt to remove the package."""
                 self.pkg("install [email protected]")
-                self.pkg("set-authority -P test2")
-                self.pkg("unset-authority test1")
+                self.pkg("set-publisher -P test2")
+                self.pkg("unset-publisher test1")
                 self.pkg("uninstall foo")
 
 
--- a/src/tests/cli/t_pkg_intent.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkg_intent.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
+#
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -37,7 +39,7 @@
 import pkg.client.api_errors as api_errors
 import pkg.client.progress as progress
 
-API_VERSION = 10
+API_VERSION = 11
 PKG_CLIENT_NAME = "pkg"
 
 class TestPkgIntent(testutils.SingleDepotTestCase):
@@ -201,27 +203,14 @@
                 self.image_create(durl)
                 progresstracker = progress.NullProgressTracker()
 
-                # A new api object has to be used everytime because multiple
-                # executions of the same operation with the same api object
-                # will not (intentionally) result in intent information being
-                # sent every time.
-
                 # Test install.
                 api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
                     progresstracker, lambda x: False, PKG_CLIENT_NAME)
                 self.__do_install(api_obj, ["foo"], noexecute=True)
-
-                api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
-                    progresstracker, lambda x: False, PKG_CLIENT_NAME)
                 self.__do_install(api_obj, ["foo"])
 
                 # Test uninstall.
-                api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
-                    progresstracker, lambda x: False, PKG_CLIENT_NAME)
                 self.__do_uninstall(api_obj, ["foo"], noexecute=True)
-
-                api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
-                    progresstracker, lambda x: False, PKG_CLIENT_NAME)
                 self.__do_uninstall(api_obj, ["foo"])
 
                 entries = self.get_intent_entries()
@@ -268,35 +257,16 @@
                 self.image_create(durl)
                 progresstracker = progress.NullProgressTracker()
 
-                # A new api object has to be used everytime because multiple
-                # executions of the same operation with the same api object
-                # will not (intentionally) result in intent information being
-                # sent every time.
-
                 # Test install.
                 api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
                     progresstracker, lambda x: True, PKG_CLIENT_NAME)
                 self.__do_install(api_obj, ["[email protected]"], noexecute=True)
-
-                api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
-                    progresstracker, lambda x: True, PKG_CLIENT_NAME)
                 self.__do_install(api_obj, ["[email protected]"])
-
-                api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
-                    progresstracker, lambda x: True, PKG_CLIENT_NAME)
                 self.__do_install(api_obj, ["[email protected]"], noexecute=True)
-
-                api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
-                    progresstracker, lambda x: True, PKG_CLIENT_NAME)
                 self.__do_install(api_obj, ["[email protected]"])
 
                 # Test uninstall.
-                api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
-                    progresstracker, lambda x: True, PKG_CLIENT_NAME)
                 self.__do_uninstall(api_obj, ["foo"], noexecute=True)
-
-                api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
-                    progresstracker, lambda x: True, PKG_CLIENT_NAME)
                 self.__do_uninstall(api_obj, ["foo"])
 
                 entries = self.get_intent_entries()
@@ -428,7 +398,6 @@
                 progresstracker = progress.NullProgressTracker()
                 api_obj = api.ImageInterface(self.get_img_path(), API_VERSION,
                     progresstracker, lambda x: True, PKG_CLIENT_NAME)
-
                 self.__do_install(api_obj, ["[email protected]"])
 
                 # Only testing for process; no need to re-test for evaluate.
--- a/src/tests/cli/t_pkg_list.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkg_list.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -87,7 +89,7 @@
 
                 self.image_create(durl1, prefix = "test1")
 
-                self.pkg("set-authority -O " + durl2 + " test2")
+                self.pkg("set-publisher -O " + durl2 + " test2")
 
         def reduceSpaces(self, string):
                 """Reduce runs of spaces down to a single space."""
@@ -118,7 +120,7 @@
                 self.assertEqualDiff(expected, output)
 
         def test_list_2(self):
-                """List all "[email protected]", regardless of authority, with "pkg:/"
+                """List all "[email protected]", regardless of publisher, with "pkg:/"
                 prefix."""
                 self.pkg("list -aH pkg:/[email protected],5.11-0")
                 expected = \
@@ -128,7 +130,7 @@
                 self.assertEqualDiff(expected, output)
 
         def test_list_3(self):
-                """List all "[email protected]", regardless of authority, without "pkg:/"
+                """List all "[email protected]", regardless of publisher, without "pkg:/"
                 prefix."""
                 self.pkg("list -aH pkg:/[email protected],5.11-0")
                 expected = \
@@ -139,7 +141,7 @@
                 self.assertEqualDiff(expected, output)
 
         def test_list_4(self):
-                """List all versions of package foo, regardless of authority."""
+                """List all versions of package foo, regardless of publisher."""
                 self.pkg("list -aHf foo")
                 expected = \
                     "foo         1.2.1-0 known ----\n" \
@@ -185,7 +187,7 @@
                 self.assertEqualDiff(expected, output)
                 
         def test_list_6(self):
-                """Show versions 1.0 and 1.1 of foo only from authority test2."""
+                """Show versions 1.0 and 1.1 of foo only from publisher test2."""
                 self.pkg("list -aHf pkg://test2/foo")
                 expected = \
                     "foo (test2) 1.2.1-0 known ----\n" + \
@@ -229,7 +231,7 @@
                 self.assertEqualDiff(expected, output)
 
         def test_list_matching(self):
-                """List all versions of package foo, regardless of authority."""
+                """List all versions of package foo, regardless of publisher."""
                 self.pkg("list -aHf foo*")
                 expected = \
                     "foo         1.2.1-0 known ----\n" \
--- a/src/tests/cli/t_pkg_property.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkg_property.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -74,14 +76,14 @@
                 self.pkg("unset-property require-optional")
 
         def test_bug_4372(self):
-                """Verify that preferred-authority cannot be changed using the
+                """Verify that preferred-publisher cannot be changed using the
                 property commands, but can be read."""
                 durl = self.dc.get_depot_url()
                 self.image_create(durl)
 
-                self.pkg("set-property preferred-authority foo", exit=1)
-                self.pkg("unset-property preferred-authority", exit=1)
-                self.pkg("property preferred-authority")
+                self.pkg("set-property preferred-publisher foo", exit=1)
+                self.pkg("unset-property preferred-publisher", exit=1)
+                self.pkg("property preferred-publisher")
 
 if __name__ == "__main__":
         unittest.main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tests/cli/t_pkg_publisher.py	Mon Mar 09 16:09:13 2009 -0500
@@ -0,0 +1,318 @@
+#!/usr/bin/python2.4
+#
+# 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 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+import testutils
+if __name__ == "__main__":
+        testutils.setup_environment("../../../proto")
+
+import unittest
+import os
+import tempfile
+
+class TestPkgPublisherBasics(testutils.SingleDepotTestCase):
+        # Only start/stop the depot once (instead of for every test)
+        persistent_depot = True
+
+        def test_pkg_publisher_bogus_opts(self):
+                """ pkg bogus option checks """
+
+                durl = self.dc.get_depot_url()
+                self.image_create(durl)
+
+                self.pkg("set-publisher -@ test3", exit=2)
+                self.pkg("publisher -@ test5", exit=2)
+                self.pkg("set-publisher -k", exit=2)
+                self.pkg("set-publisher -c", exit=2)
+                self.pkg("set-publisher -O", exit=2)
+                self.pkg("unset-publisher", exit=2)
+
+        def test_publisher_add_remove(self):
+                """pkg: add and remove a publisher"""
+                durl = self.dc.get_depot_url()
+                self.image_create(durl)
+
+                self.pkg("set-publisher -O http://%s1 test1" % self.bogus_url,
+                    exit=1)
+                self.pkg("set-publisher --no-refresh -O http://%s1 test1" %
+                    self.bogus_url)
+                self.pkg("publisher | grep test")
+                self.pkg("set-publisher -P -O http://%s2 test2" %
+                    self.bogus_url, exit=1)
+                self.pkg("set-publisher -P --no-refresh -O http://%s2 test2" %
+                    self.bogus_url)
+                self.pkg("publisher | grep test2")
+                self.pkg("unset-publisher test1")
+                self.pkg("publisher | grep test1", exit=1)
+                self.pkg("unset-publisher test2", exit=1)
+
+        def test_publisher_uuid(self):
+                """verify uuid is set manually and automatically for a
+                publisher"""
+                durl = self.dc.get_depot_url()
+                self.image_create(durl)
+                self.pkg("set-publisher -O http://%s1 --no-refresh --reset-uuid test1" %
+                    self.bogus_url)
+                self.pkg("set-publisher --no-refresh --reset-uuid test1")
+                self.pkg("set-publisher -O http://%s1 --no-refresh test2" %
+                    self.bogus_url)
+                self.pkg("publisher test2 | grep 'Client UUID: '")
+                self.pkg("publisher test2 | grep -v 'Client UUID: None'")
+
+        def test_publisher_bad_opts(self):
+                """pkg: more insidious option abuse for set-publisher"""
+                durl = self.dc.get_depot_url()
+                self.image_create(durl)
+
+                key_fh, key_path = tempfile.mkstemp()
+                cert_fh, cert_path = tempfile.mkstemp()
+
+                self.pkg(
+                    "set-publisher -O http://%s1 test1 -O http://%s2 test2" %
+                    (self.bogus_url, self.bogus_url), exit=2)
+
+                self.pkg("set-publisher -O http://%s1 test1" % self.bogus_url,
+                    exit=1)
+                self.pkg("set-publisher -O http://%s2 test2" % self.bogus_url,
+                    exit=1)
+                self.pkg("set-publisher --no-refresh -O https://%s1 test1" %
+                    self.bogus_url)
+                self.pkg("set-publisher --no-refresh -O http://%s2 test2" %
+                    self.bogus_url)
+
+                self.pkg("set-publisher --no-refresh -k %s test1" % key_path)
+                os.close(key_fh)
+                os.unlink(key_path)
+                self.pkg("set-publisher --no-refresh -k %s test2" % key_path, exit=1)
+
+                self.pkg("set-publisher --no-refresh -c %s test1" % cert_path)
+                os.close(cert_fh)
+                os.unlink(cert_path)
+                self.pkg("set-publisher --no-refresh -c %s test2" % cert_path, exit=1)
+
+                self.pkg("publisher test1")
+                self.pkg("publisher test3", exit=1)
+                self.pkg("publisher -H | grep URI", exit=1)
+
+                # Now verify that setting ssl_cert or ssl_key to "" works.
+                self.pkg('set-publisher --no-refresh -c "" test1')
+                self.pkg('publisher -H test1 | grep "SSL Cert: None"')
+
+                self.pkg('set-publisher --no-refresh -k "" test1')
+                self.pkg('publisher -H test1 | grep "SSL Key: None"')
+
+        def test_publisher_validation(self):
+                """Verify that we catch poorly formed auth prefixes and URL"""
+                durl = self.dc.get_depot_url()
+                self.image_create(durl)
+
+                self.pkg("set-publisher -O http://%s1 test1" % self.bogus_url,
+                    exit=1)
+                self.pkg("set-publisher --no-refresh -O http://%s1 test1" %
+                    self.bogus_url)
+
+                self.pkg(("set-publisher -O http://%s2 " % self.bogus_url) +
+                    "$%^8", exit=1)
+                self.pkg(("set-publisher -O http://%s2 " % self.bogus_url) +
+                    "8^$%", exit=1)
+                self.pkg("set-publisher -O http://*^5$% test2", exit=1)
+                self.pkg("set-publisher -O http://%s1:abcde test2" %
+                    self.bogus_url, exit=1)
+                self.pkg("set-publisher -O ftp://%s2 test2" % self.bogus_url,
+                    exit=1)
+
+        def test_mirror(self):
+                """Test set-mirror and unset-mirror."""
+                durl = self.dc.get_depot_url()
+                pfx = "mtest"
+                self.image_create(durl, prefix = pfx)
+
+                self.pkg("set-publisher -m http://%s1 mtest" % self.bogus_url)
+                self.pkg("set-publisher -m http://%s2 mtest" %
+                    self.bogus_url)
+                self.pkg("set-publisher -m http://%s5" % self.bogus_url, exit=2)
+                self.pkg("set-publisher -m mtest", exit=2)
+                self.pkg("set-publisher -m http://%s1 mtest" % self.bogus_url,
+                    exit=1)
+                self.pkg("set-publisher -m http://%s5 test" % self.bogus_url,
+                    exit=1)
+                self.pkg("set-publisher -m %s7 mtest" % self.bogus_url, exit=1)
+
+                self.pkg("set-publisher -M http://%s1 mtest" % self.bogus_url)
+                self.pkg("set-publisher -M http://%s2 mtest" %
+                    self.bogus_url)
+                self.pkg("set-publisher -M mtest http://%s2 http://%s4" %
+                    (self.bogus_url, self.bogus_url), exit=2)
+                self.pkg("set-publisher -M http://%s5" % self.bogus_url, exit=2)
+                self.pkg("set-publisher -M mtest", exit=2)
+                self.pkg("set-publisher -M http://%s5 test" % self.bogus_url,
+                    exit=1)
+                self.pkg("set-publisher -M http://%s6 mtest" % self.bogus_url,
+                    exit=1)
+                self.pkg("set-publisher -M %s7 mtest" % self.bogus_url, exit=1)
+
+        def test_missing_perms(self):
+                """Bug 2393"""
+                durl = self.dc.get_depot_url()
+                pfx = "mtest"
+                self.image_create(durl, prefix=pfx)
+
+                self.pkg("set-publisher --no-refresh -O http://%s1 test1" %
+                    self.bogus_url, su_wrap=True, exit=1)
+                self.pkg("set-publisher --no-refresh -O http://%s1 foo" %
+                    self.bogus_url)
+                self.pkg("publisher | grep foo")
+                self.pkg("set-publisher -P --no-refresh -O http://%s2 test2" %
+                    self.bogus_url, su_wrap=True, exit=1)
+                self.pkg("unset-publisher foo", su_wrap=True, exit=1)
+                self.pkg("unset-publisher foo")
+
+                self.pkg("set-publisher -m http://%s1 mtest" % self.bogus_url, \
+                    su_wrap=True, exit=1)
+                self.pkg("set-publisher -m http://%s2 mtest" %
+                    self.bogus_url)
+
+                self.pkg("set-publisher -M http://%s2 mtest" %
+                    self.bogus_url, su_wrap=True, exit=1)
+                self.pkg("set-publisher -M http://%s2 mtest" %
+                    self.bogus_url)
+
+                # Now change the first publisher to a https URL so that
+                # certificate failure cases can be tested.
+                key_fh, key_path = tempfile.mkstemp(dir=self.get_test_prefix())
+                cert_fh, cert_path = tempfile.mkstemp(dir=self.get_test_prefix())
+
+                self.pkg("set-publisher --no-refresh -O https://%s1 test1" %
+                    self.bogus_url)
+                self.pkg("set-publisher --no-refresh -c %s test1" % cert_path)
+                self.pkg("set-publisher --no-refresh -k %s test1" % key_path)
+
+                os.close(key_fh)
+                os.close(cert_fh)
+
+                # Make the cert/key unreadable by unprivileged users.
+                os.chmod(key_path, 0000)
+                os.chmod(cert_path, 0000)
+
+                # Verify that an unreadable/invalid certificate results in a
+                # partial failure when displaying publisher information.
+                self.pkg("publisher test1", exit=3)
+                self.pkg("publisher test1", su_wrap=True, exit=3)
+
+        def test_mirror_longopt(self):
+                """Test set-mirror and unset-mirror."""
+                durl = self.dc.get_depot_url()
+                pfx = "mtest"
+                self.image_create(durl, prefix = pfx)
+
+                self.pkg("set-publisher --add-mirror=http://%s1 mtest" %
+                    self.bogus_url)
+                self.pkg("set-publisher --add-mirror=http://%s2 mtest" %
+                    self.bogus_url)
+                self.pkg("set-publisher --add-mirror=http://%s5" %
+                    self.bogus_url, exit=2)
+                self.pkg("set-publisher --add-mirror=mtest", exit=2)
+                self.pkg("set-publisher --add-mirror=http://%s1 mtest" %
+                    self.bogus_url, exit=1)
+                self.pkg("set-publisher --add-mirror=http://%s5 test" %
+                    self.bogus_url, exit=1)
+                self.pkg("set-publisher --add-mirror=%s7 mtest" %
+                    self.bogus_url, exit=1)
+
+                self.pkg("set-publisher --remove-mirror=http://%s1 mtest" %
+                    self.bogus_url)
+                self.pkg("set-publisher --remove-mirror=http://%s2 mtest" %
+                    self.bogus_url)
+                self.pkg("set-publisher --remove-mirror=mtest http://%s2 http://%s4" %
+                    (self.bogus_url, self.bogus_url), exit=2)
+                self.pkg("set-publisher --remove-mirror=http://%s5" %
+                    self.bogus_url, exit=2)
+                self.pkg("set-publisher --remove-mirror=mtest", exit=2)
+                self.pkg("set-publisher --remove-mirror=http://%s5 test" %
+                    self.bogus_url, exit=1)
+                self.pkg("set-publisher --remove-mirror=http://%s6 mtest" %
+                    self.bogus_url, exit=1)
+                self.pkg("set-publisher --remove-mirror=%s7 mtest" %
+                    self.bogus_url, exit=1)
+
+
+class TestPkgPublisherMany(testutils.ManyDepotTestCase):
+        # Only start/stop the depot once (instead of for every test)
+        persistent_depot = True
+
+        foo1 = """
+            open foo@1,5.11-0
+            close """
+
+        bar1 = """
+            open bar@1,5.11-0
+            close """
+
+        def setUp(self):
+                testutils.ManyDepotTestCase.setUp(self, 2)
+
+                durl1 = self.dcs[1].get_depot_url()
+                self.pkgsend_bulk(durl1, self.foo1)
+
+                durl2 = self.dcs[2].get_depot_url()
+                self.pkgsend_bulk(durl2, self.bar1)
+
+                self.image_create(durl1, prefix = "test1")
+                self.pkg("set-publisher -O " + durl2 + " test2")
+
+        def tearDown(self):
+                testutils.ManyDepotTestCase.tearDown(self)
+
+        def test_enable_disable(self):
+                """Test enable and disable."""
+
+                self.pkg("publisher | grep test1")
+                self.pkg("publisher | grep test2")
+
+                self.pkg("set-publisher -d test2")
+                self.pkg("publisher | grep test2", exit=1)
+                self.pkg("list -a bar", exit=1)
+                self.pkg("publisher -a | grep test2")
+                self.pkg("set-publisher -P test2", exit=1)
+                self.pkg("publisher test2")
+                self.pkg("set-publisher -e test2")
+                self.pkg("publisher | grep test2")
+                self.pkg("list -a bar")
+
+                self.pkg("set-publisher --disable test2")
+                self.pkg("publisher | grep test2", exit=1)
+                self.pkg("list -a bar", exit=1)
+                self.pkg("publisher -a | grep test2")
+                self.pkg("set-publisher --enable test2")
+                self.pkg("publisher | grep test2")
+                self.pkg("list -a bar")
+
+                # should fail because test is the preferred publisher
+                self.pkg("set-publisher -d test1", exit=1)
+                self.pkg("set-publisher --disable test1", exit=1)
+
+if __name__ == "__main__":
+        unittest.main()
--- a/src/tests/cli/t_pkg_refresh.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkg_refresh.py	Mon Mar 09 16:09:13 2009 -0500
@@ -27,11 +27,12 @@
 if __name__ == "__main__":
 	testutils.setup_environment("../../../proto")
 
-import unittest
+import difflib
 import os
 import re
 import shutil
-import difflib
+import tempfile
+import unittest
 
 class TestPkgRefreshMulti(testutils.ManyDepotTestCase):
 
@@ -64,7 +65,7 @@
 
                 self.durl1 = self.dcs[1].get_depot_url()
                 self.durl2 = self.dcs[2].get_depot_url()
-        
+
         def reduce_spaces(self, string):
                 """Reduce runs of spaces down to a single space."""
                 return re.sub(" +", " ", string)
@@ -90,7 +91,7 @@
                                 tmp_e, tmp_a,
                                 "Expected output", "Actual output",
                                 lineterm="")))
-        
+
         def checkAnswer(self,expected, actual):
                 return self._check(
                     self.reduce_spaces(expected),
@@ -105,10 +106,10 @@
                 self.pkg("refresh")
                 self.pkg("refresh --full")
                 self.pkg("refresh -F", exit=2)
-       
+
         def test_general_refresh(self):
                 self.image_create(self.durl1, prefix = "test1")
-                self.pkg("set-authority -O " + self.durl2 + " test2")
+                self.pkg("set-publisher -O " + self.durl2 + " test2")
                 self.pkgsend_bulk(self.durl1, self.foo10)
                 self.pkgsend_bulk(self.durl2, self.foo12)
                 self.pkg("refresh")
@@ -120,7 +121,7 @@
 
         def test_specific_refresh(self):
                 self.image_create(self.durl1, prefix = "test1")
-                self.pkg("set-authority -O " + self.durl2 + " test2")
+                self.pkg("set-publisher -O " + self.durl2 + " test2")
                 self.pkgsend_bulk(self.durl1, self.foo10)
                 self.pkgsend_bulk(self.durl2, self.foo12)
                 self.pkg("refresh test1")
@@ -135,7 +136,7 @@
                     "foo (test2) 1.2-0 known ----\n"
                 self.checkAnswer(expected, self.output)
                 self.pkg("refresh unknownAuth", exit=1)
-                self.pkg("set-authority -P test2")
+                self.pkg("set-publisher -P test2")
                 self.pkg("list -aH pkg:/foo")
                 expected = \
                     "foo (test1) 1.0-0 known u---\n" + \
@@ -152,7 +153,7 @@
                 self.checkAnswer(expected, self.output)
 
 
-        def test_set_authority_induces_full_refresh(self):
+        def test_set_publisher_induces_full_refresh(self):
                 self.pkgsend_bulk(self.durl2, self.foo11)
                 self.pkgsend_bulk(self.durl1, self.foo10)
                 self.image_create(self.durl1, prefix = "test1")
@@ -160,21 +161,21 @@
                 expected = \
                     "foo 1.0-0 known ----\n"
                 self.checkAnswer(expected, self.output)
-                self.pkg("set-authority --no-refresh -O " +
+                self.pkg("set-publisher --no-refresh -O " +
                     self.durl2 + " test1")
                 self.pkg("list -aH pkg:/foo", exit=1)
-                self.pkg("set-authority -O " + self.durl2 + " test1") 
+                self.pkg("set-publisher -O " + self.durl2 + " test1")
                 self.pkg("list -aH pkg:/foo")
                 expected = \
                     "foo 1.1-0 known ----\n"
                 self.checkAnswer(expected, self.output)
-                self.pkg("set-authority -O " + self.durl1 + " test2")
+                self.pkg("set-publisher -O " + self.durl1 + " test2")
                 self.pkg("list -aH pkg:/foo")
                 expected = \
                     "foo 1.1-0 known ----\n" \
                     "foo (test2) 1.0-0 known ----\n"
-                
-        def test_set_authority_induces_delayed_full_refresh(self):
+
+        def test_set_publisher_induces_delayed_full_refresh(self):
                 self.pkgsend_bulk(self.durl2, self.foo11)
                 self.pkgsend_bulk(self.durl1, self.foo10)
                 self.image_create(self.durl1, prefix = "test1")
@@ -183,7 +184,7 @@
                     "foo 1.0-0 known ----\n"
                 self.checkAnswer(expected, self.output)
                 self.dcs[2].stop()
-                self.pkg("set-authority --no-refresh -O " + self.durl2 + " test1")
+                self.pkg("set-publisher --no-refresh -O " + self.durl2 + " test1")
                 self.pkg("list -aH pkg:/foo", exit=1)
                 self.dcs[2].start()
                 self.pkg("refresh test1")
@@ -192,5 +193,31 @@
                     "foo 1.1-0 known ----\n"
                 self.checkAnswer(expected, self.output)
 
+        def test_refresh_certificate_problems(self):
+                """Verify that an invalid or inaccessible certificate does not
+                cause unexpected failure."""
+
+                self.image_create(self.durl1)
+
+                key_fh, key_path = tempfile.mkstemp(dir=self.get_test_prefix())
+                cert_fh, cert_path = tempfile.mkstemp(dir=self.get_test_prefix())
+
+                self.pkg("set-publisher --no-refresh -O https://%s1 test1" %
+                    self.bogus_url)
+                self.pkg("set-publisher --no-refresh -c %s test1" % cert_path)
+                self.pkg("set-publisher --no-refresh -k %s test1" % key_path)
+
+                os.close(key_fh)
+                os.close(cert_fh)
+
+                os.chmod(cert_path, 0000)
+                # Verify that an invalid certificate results in a normal failure
+                # when attempting to refresh.
+                self.pkg("refresh test1", exit=1)
+
+                # Verify that an inaccessible certificate results in a normal
+                # failure when attempting to refresh.
+                self.pkg("refresh test1", su_wrap=True, exit=1)
+
 if __name__ == "__main__":
         unittest.main()
--- a/src/tests/cli/t_pkg_search.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkg_search.py	Mon Mar 09 16:09:13 2009 -0500
@@ -20,8 +20,10 @@
 # CDDL HEADER END
 #
 
+#
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -314,13 +316,13 @@
 
         res_local_fat10_i386_star = res_fat10_i386.union(set([
             "variant.arch set       sparc                     pkg:/[email protected]\n",
-            "authority  set       test                      pkg:/[email protected]\n",
+            "publisher  set       test                      pkg:/[email protected]\n",
             "fmri       set       fmri                      pkg:/[email protected]\n"
         ]))
 
         res_local_fat10_sparc_star = res_fat10_sparc.union(set([
             "variant.arch set       i386                      pkg:/[email protected]\n",
-            "authority  set       test                      pkg:/[email protected]\n",
+            "publisher  set       test                      pkg:/[email protected]\n",
             "fmri       set       fmri                      pkg:/[email protected]\n"
         ]))
 
@@ -1132,7 +1134,7 @@
                 self.pkgsend_bulk(durl2, self.example_pkg10)
 
                 self.image_create(durl1, prefix = "test1")
-                self.pkg("set-authority -O " + durl2 + " test2")
+                self.pkg("set-publisher -O " + durl2 + " test2")
 
         def test_bug_2955(self):
                 """See http://defect.opensolaris.org/bz/show_bug.cgi?id=2955"""
--- a/src/tests/cli/t_pkgrecv.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/cli/t_pkgrecv.py	Mon Mar 09 16:09:13 2009 -0500
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 
@@ -78,8 +78,8 @@
         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 """
+                    depot1 is mapped to publisher test1 (preferred)
+                    depot2 is mapped to publisher test2 """
 
                 testutils.ManyDepotTestCase.setUp(self, 2)
 
--- a/src/tests/perf/actionbench.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/perf/actionbench.py	Mon Mar 09 16:09:13 2009 -0500
@@ -19,9 +19,13 @@
 #
 # CDDL HEADER END
 #
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
+
+#
 # actionbench - benchmark action creation
 #
 
@@ -148,7 +152,7 @@
 file dbd52c79aa5a1dc92232994a815718dedbc70eec elfarch=i386 elfbits=32 elfhash=2b510c241544342123d3591ea17548a075192d89 group=bin mode=0555 owner=root path=usr/lib/zones/zoneadmd pkg.size=109720
 legacy arch=i386 category=system desc="Solaris Zones Configuration and Administration" hotline="Please contact your local service provider" name="Solaris Zones (Usr)" pkg=SUNWzoneu vendor="Sun Microsystems, Inc." version=11.11,REV=2008.01.05.16.07
 file 1269a117ab6ed3eb8e86f34aabfffba1221ac829 group=bin mode=0444 opensolaris.zone=global owner=root path=etc/zones/SUNWdefault.xml pkg.size=1366
-set name=authority value=foo
+set name=publisher value=foo
 dir group=sys mode=0755 owner=root path=usr/kernel/drv/amd64
 file 2c9c0651e59cbb4b12dd9c8b9502003fd4b0af74 group=bin mode=0755 owner=root path=usr/lib/brand/native/postclone pkg.size=1635
 file 0049f03d9a0fc515a0b89579abac3f2d04f0dede elfarch=i386 elfbits=32 elfhash=4777a16b7740405428b46a221ba30e36916db4e8 group=bin mode=0555 owner=root path=usr/sbin/zoneadm pkg.size=107864
--- a/src/tests/perf/fmribench.py	Mon Mar 09 13:16:54 2009 +0000
+++ b/src/tests/perf/fmribench.py	Mon Mar 09 16:09:13 2009 -0500
@@ -19,9 +19,13 @@
 #
 # CDDL HEADER END
 #
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
+
+#
 # fmribench - benchmark fmri creation and other related operations
 #
 
@@ -116,13 +120,13 @@
         """str(f1)"""
         ],
 
-        [ "fmri to string (no authority)", 100000,
+        [ "fmri to string (no publisher)", 100000,
         """import pkg.fmri as fmri
 f1 = fmri.PkgFmri("pkg:/[email protected],5.11-0.72:20070921T203926Z")""",
         """str(f1)"""
         ],
 
-        [ "fmri to string (with authority)", 100000,
+        [ "fmri to string (with publisher)", 100000,
         """import pkg.fmri as fmri
 f1 = fmri.PkgFmri("pkg://origin/[email protected],5.11-0.72:20070921T203926Z")""",
         """str(f1)"""