src/client.py
changeset 2026 d1b30615bc99
parent 1970 60471574c487
child 2028 b2c674e6ee28
--- a/src/client.py	Thu Aug 12 09:48:48 2010 -0700
+++ b/src/client.py	Mon Aug 16 16:48:50 2010 -0700
@@ -172,6 +172,8 @@
         pkg variant [-H] [<variant_spec>]
         pkg facet [-H] [<facet_spec>]
         pkg set-property propname propvalue
+        pkg add-property-value propname propvalue
+        pkg remove-property-value propname propvalue
         pkg unset-property propname ...
         pkg property [-H] [propname ...]
 
@@ -184,6 +186,13 @@
             [--reset-uuid] [--non-sticky] [--sticky]
             [--search-after=publisher]
             [--search-before=publisher]
+            [--approve-ca-cert=path_to_CA]
+            [--revoke-ca-cert=hash_of_CA_to_revoke]
+            [--unset-ca-cert=hash_of_CA_to_unset]
+            [--set-property name_of_property=value]
+            [--add-property-value name_of_property=value_to_add]
+            [--remove-property-value name_of_property=value_to_remove]
+            [--unset-property name_of_property_to_delete]
             [publisher]
         pkg unset-publisher publisher ...
         pkg publisher [-HPn] [publisher ...]
@@ -540,7 +549,7 @@
                                         continue
 
                                 # Informational messages are ignored by fix.
-                                entries.append((act, errors, warnings ))
+                                entries.append((act, errors, warnings))
 
                         if not entries:
                                 # Nothing to fix for this package.
@@ -552,8 +561,9 @@
 
                         failed = []
                         for act, errors, warnings in entries:
-                                failed.append(act)
-                                msg("\t%s" % act.distinguished_name())
+                                if act:
+                                        failed.append(act)
+                                        msg("\t%s" % act.distinguished_name())
                                 for x in errors:
                                         msg("\t\t%s" % x)
                                 for x in warnings:
@@ -597,7 +607,9 @@
                 except (api_errors.InvalidPlanError,
                     api_errors.InvalidPackageErrors,
                     api_errors.ActionExecutionError,
-                    api_errors.PermissionsException), e:
+                    api_errors.PermissionsException,
+                    api_errors.SigningException,
+                    api_errors.InvalidResourceLocation), e:
                         logger.error("\n")
                         logger.error(str(e))
                 except api_errors.PlanLicenseErrors, e:
@@ -702,7 +714,8 @@
                             "result": result })
 
                         for act, errors, warnings, pinfo in entries:
-                                msg("\t%s" % act.distinguished_name())
+                                if act:
+                                        msg("\t%s" % act.distinguished_name())
                                 for x in errors:
                                         msg("\t\t%s" % x)
                                 for x in warnings:
@@ -929,7 +942,9 @@
         if e_type in (api_errors.CertificateError,
             api_errors.UnknownErrors,
             api_errors.PlanCreationException,
-            api_errors.PermissionsException):
+            api_errors.PermissionsException,
+            api_errors.InvalidPropertyValue,
+            api_errors.InvalidResourceLocation):
                 # Prepend a newline because otherwise the exception will
                 # be printed on the same line as the spinner.
                 error("\n" + str(e))
@@ -937,6 +952,9 @@
         if e_type == fmri.IllegalFmri:
                 error(e, cmd=op)
                 return False
+        if isinstance(e, api_errors.SigningException):
+                error(e)
+                return False
 
         # if we didn't deal with the exception above, pass it on.
         raise
@@ -2397,7 +2415,17 @@
             remove] [-m|--add-mirror mirror to add] [-M|--remove-mirror mirror
             to remove] [-p repo_uri] [--enable] [--disable] [--no-refresh]
             [--sticky] [--non-sticky ] [--search-before=publisher]
-            [--search-after=publisher] [publisher]"""
+            [--search-after=publisher]
+            [--approve-ca-cert path to CA]
+            [--revoke-ca-cert hash of CA to remove]
+            [--unset-ca-cert hash of CA to unset]
+            [--set-property name of property=value]
+            [--add-property-value name of property=value to add]
+            [--remove-property-value name of property=value to remove]
+            [--unset-property name of property to delete]
+            [publisher] """
+
+        cmd_name = "set-publisher"
 
         preferred = False
         ssl_key = None
@@ -2418,11 +2446,21 @@
         sys_repo_uri = None
         socket_path = None
 
+        approved_ca_certs = []
+        revoked_ca_certs = []
+        unset_ca_certs = []
+        set_props = {}
+        add_prop_values = {}
+        remove_prop_values = {}
+        unset_props = set()
+
         opts, pargs = getopt.getopt(args, "Pedk:c:O:G:g:M:m:p:",
             ["add-mirror=", "remove-mirror=", "add-origin=",
             "clear-system-repo", "remove-origin=", "no-refresh", "reset-uuid",
             "enable", "disable", "sticky", "non-sticky", "search-before=",
-            "search-after=", "system-repo=", "socket-path="])
+            "search-after=", "system-repo=", "socket-path=", "approve-ca-cert=",
+            "revoke-ca-cert=", "unset-ca-cert=", "set-property=",
+            "add-property-value=", "remove-property-value=", "unset-property="])
 
         for opt, arg in opts:
                 if opt == "-c":
@@ -2465,6 +2503,41 @@
                         sys_repo_uri = arg
                 elif opt == "--socket-path":
                         socket_path = arg
+                elif opt == "--approve-ca-cert":
+                        approved_ca_certs.append(arg)
+                elif opt == "--revoke-ca-cert":
+                        revoked_ca_certs.append(arg)
+                elif opt == "--unset-ca-cert":
+                        unset_ca_certs.append(arg)
+                elif opt == "--set-property":
+                        t = arg.split("=", 1)
+                        if len(t) < 2:
+                                usage(_("properties to be set must be of the "
+                                    "form '<name>=<value>'. This is what was "
+                                    "given: %s") % arg, cmd=cmd_name)
+                        if t[0] in set_props:
+                                usage(_("a property may only be set once in a "
+                                    "command. %s was set twice") % t[0],
+                                    cmd=cmd_name)
+                        set_props[t[0]] = t[1]
+                elif opt == "--add-property-value":
+                        t = arg.split("=", 1)
+                        if len(t) < 2:
+                                usage(_("property values to be added must be "
+                                    "of the form '<name>=<value>'. This is "
+                                    "what was given: %s") % arg, cmd=cmd_name)
+                        add_prop_values.setdefault(t[0], [])
+                        add_prop_values[t[0]].append(t[1])
+                elif opt == "--remove-property-value":
+                        t = arg.split("=", 1)
+                        if len(t) < 2:
+                                usage(_("property values to be removed must be "
+                                    "of the form '<name>=<value>'. This is "
+                                    "what was given: %s") % arg, cmd=cmd_name)
+                        remove_prop_values.setdefault(t[0], [])
+                        remove_prop_values[t[0]].append(t[1])
+                elif opt == "--unset-property":
+                        unset_props.add(arg)
 
         name = None
         if len(pargs) == 0 and not repo_uri:
@@ -2523,7 +2596,13 @@
                     search_after=search_after, reset_uuid=reset_uuid,
                     refresh_allowed=refresh_allowed, preferred=preferred,
                     socket_path=socket_path, clear_sys_repo=clear_sys_repo,
-                    sys_repo_uri=sys_repo_uri)
+                    sys_repo_uri=sys_repo_uri,
+                    set_props=set_props, add_prop_values=add_prop_values,
+                    remove_prop_values=remove_prop_values,
+                    unset_props=unset_props, approved_cas=approved_ca_certs,
+                    revoked_cas=revoked_ca_certs, unset_cas=unset_ca_certs,
+                    img=img)
+
                 rval, rmsg = ret
                 if rmsg:
                         error(rmsg, cmd="set-publisher")
@@ -2601,7 +2680,11 @@
                             add_origins=add_origins, ssl_cert=ssl_cert,
                             ssl_key=ssl_key, sticky=sticky,
                             search_after=search_after,
-                            search_before=search_before)
+                            search_before=search_before,
+                            set_props=set_props,
+                            add_prop_values=add_prop_values,
+                            remove_prop_values=remove_prop_values,
+                            unset_props=unset_props)
                         if rval == EXIT_OK:
                                 added.append(prefix)
                 else:
@@ -2664,7 +2747,11 @@
 
                         rval, rmsg = _set_pub_error_wrap(_add_update_pub, name,
                             [], api_inst, prefix, pub=dest_pub,
-                            add_mirrors=add_mirrors, add_origins=add_origins)
+                            add_mirrors=add_mirrors, add_origins=add_origins,
+                            set_props=set_props,
+                            add_prop_values=add_prop_values,
+                            remove_prop_values=remove_prop_values,
+                            unset_props=unset_props)
 
                         if rval == EXIT_OK:
                                 updated.append(prefix)
@@ -2714,7 +2801,9 @@
     add_origins=EmptyI, remove_origins=EmptyI, ssl_cert=None, ssl_key=None,
     search_before=None, search_after=None, socket_path=None, sys_repo_uri=None,
     reset_uuid=None, refresh_allowed=False, preferred=False,
-    clear_sys_repo=False):
+    clear_sys_repo=False, set_props=EmptyI, add_prop_values=EmptyI,
+    remove_prop_values=EmptyI, unset_props=EmptyI, approved_cas=EmptyI,
+    revoked_cas=EmptyI, unset_cas=EmptyI, img=None):
 
         repo = None
         new_pub = False
@@ -2810,10 +2899,39 @@
                         if ssl_key is not None:
                                 uri.ssl_key = ssl_key
 
+        if set_props or add_prop_values or remove_prop_values or unset_props:
+                pub.update_props(set_props=set_props,
+                    add_prop_values=add_prop_values,
+                    remove_prop_values=remove_prop_values,
+                    unset_props=unset_props)
+
         if new_pub:
                 api_inst.add_publisher(pub,
-                    refresh_allowed=refresh_allowed)
+                    refresh_allowed=refresh_allowed, approved_cas=approved_cas,
+                    revoked_cas=revoked_cas, unset_cas=unset_cas)
         else:
+                for ca in approved_cas:
+                        try:
+                                ca = os.path.normpath(
+                                    os.path.join(orig_cwd, ca))
+                                with open(ca, "rb") as fh:
+                                        s = fh.read()
+                        except EnvironmentError, e:
+                                if e.errno == errno.ENOENT:
+                                        raise api_errors.MissingFileArgumentException(
+                                            ca)
+                                elif e.errno == errno.EACCES:
+                                        raise api_errors.PermissionsException(
+                                            ca)
+                                raise
+                        pub.approve_ca_cert(s, manual=True)
+
+                for hsh in revoked_cas:
+                        pub.revoke_ca_cert(hsh)
+
+                for hsh in unset_cas:
+                        pub.unset_ca_cert(hsh)
+
                 api_inst.update_publisher(pub,
                     refresh_allowed=refresh_allowed)
 
@@ -3127,6 +3245,18 @@
                                         retcode = EXIT_PARTIAL
                         return retcode
 
+                def display_signing_certs(p):
+                        if p.approved_ca_certs:
+                                msg(_("         Approved CAs:"),
+                                    p.approved_ca_certs[0])
+                                for h in p.approved_ca_certs[1:]:
+                                        msg(_("                     :"), h)
+                        if p.revoked_ca_certs:
+                                msg(_("          Revoked CAs:"),
+                                    p.revoked_ca_certs[0])
+                                for h in p.revoked_ca_certs[1:]:
+                                        msg(_("                     :"), h)
+
                 for name in pargs:
                         # detailed print
                         pub = api_inst.get_publisher(prefix=name, alias=name)
@@ -3148,14 +3278,15 @@
 
                         msg(_("          Client UUID:"), pub.client_uuid)
                         msg(_("      Catalog Updated:"), dt)
+                        display_signing_certs(pub)
                         if pub.disabled:
                                 msg(_("              Enabled:"), _("No"))
                         else:
                                 msg(_("              Enabled:"), _("Yes"))
         return retcode
 
-def property_set(img, args):
-        """pkg set-property propname propvalue"""
+def property_add_value(img, args):
+        """pkg add-property-value propname propvalue"""
 
         # ensure no options are passed in
         opts, pargs = getopt.getopt(args, "")
@@ -3163,6 +3294,62 @@
                 propname, propvalue = pargs
         except ValueError:
                 usage(_("requires a property name and value"),
+                    cmd="property-add-value")
+
+        if propname == "preferred-publisher":
+                error(_("set-publisher must be used to change the preferred "
+                    "publisher"), cmd="property-add-value")
+                return EXIT_OOPS
+
+        try:
+                img.add_property_value(propname, propvalue)
+        except (api_errors.PermissionsException,
+            api_errors.InvalidPropertyValue), e:
+                # Prepend a newline because otherwise the exception
+                # will be printed on the same line as the spinner.
+                error("\n" + str(e), cmd="property-add-value")
+                return EXIT_OOPS
+        return EXIT_OK
+
+def property_remove_value(img, args):
+        """pkg remove-property-value propname propvalue"""
+
+        # ensure no options are passed in
+        opts, pargs = getopt.getopt(args, "")
+        try:
+                propname, propvalue = pargs
+        except ValueError:
+                usage(_("requires a property name and value"),
+                    cmd="property-remove-value")
+
+        if propname == "preferred-publisher":
+                error(_("set-publisher must be used to change the preferred "
+                    "publisher"), cmd="property-remove-value")
+                return EXIT_OOPS
+
+        try:
+                img.remove_property_value(propname, propvalue)
+        except (api_errors.PermissionsException,
+            api_errors.InvalidPropertyValue), e:
+                # Prepend a newline because otherwise the exception
+                # will be printed on the same line as the spinner.
+                error("\n" + str(e), cmd="property-remove-value")
+                return EXIT_OOPS
+        return EXIT_OK
+
+def property_set(img, args):
+        """pkg set-property propname propvalue [propvalue ...]"""
+
+        # ensure no options are passed in
+        opts, pargs = getopt.getopt(args, "")
+        try:
+                propname = pargs[0]
+                propvalues = pargs[1:]
+        except IndexError:
+                usage(_("requires a property name and at least one value"),
+                    cmd="set-property")
+        if len(propvalues) == 0:
+                usage(_("requires a property name and at least one value"),
                     cmd="set-property")
 
         if propname == "preferred-publisher":
@@ -3171,8 +3358,9 @@
                 return EXIT_OOPS
 
         try:
-                img.set_property(propname, propvalue)
-        except api_errors.PermissionsException, e:
+                img.set_property(propname, propvalues)
+        except (api_errors.PermissionsException,
+            api_errors.InvalidPropertyValue), e:
                 # Prepend a newline because otherwise the exception
                 # will be printed on the same line as the spinner.
                 error("\n" + str(e), cmd="set-property")
@@ -3314,6 +3502,8 @@
         component that consumes global zone-only information, such as various
         kernel statistics or device information."""
 
+        cmd_name = "image-create"
+        
         force = False
         imgtype = IMG_TYPE_USER
         is_zone = False
@@ -3329,11 +3519,12 @@
         sys_repo_uri = None
         variants = {}
         facets = pkg.facet.Facets()
+        set_props = {}
 
         opts, pargs = getopt.getopt(args, "fFPUza:g:m:p:k:c:",
             ["force", "full", "partial", "user", "zone", "authority=", "facet=",
                 "mirror=", "origin=", "publisher=", "no-refresh", "variant=",
-                "system-repo", "socket-path="])
+                "system-repo", "socket-path=", "set-property="])
 
         for opt, arg in opts:
                 # -a is deprecated and will be removed at a future date.
@@ -3377,9 +3568,9 @@
                         except ValueError:
                                 usage(_("variant arguments must be of the "
                                     "form '<name>=<value>'."),
-                                    cmd="image-create")
+                                    cmd=cmd_name)
                         variants[v_name] = v_value
-                if opt == "--facet":
+                elif opt == "--facet":
                         allow = { "TRUE":True, "FALSE":False }
                         f_name, f_value = arg.split("=", 1)
                         if not f_name.startswith("facet."):
@@ -3387,20 +3578,31 @@
                         if f_value.upper() not in allow:
                                 usage(_("Facet arguments must be"
                                     "form 'facet..=[True|False]'"),
-                                    cmd="image-create")
+                                    cmd=cmd_name)
                         facets[f_name] = allow[f_value.upper()]
+                elif opt == "--set-property":
+                        t = arg.split("=", 1)
+                        if len(t) < 2:
+                                usage(_("properties to be set must be of the "
+                                    "form '<name>=<value>'. This is what was "
+                                    "given: %s") % arg, cmd=cmd_name)
+                        if t[0] in set_props:
+                                usage(_("a property may only be set once in a "
+                                    "command. %s was set twice") % t[0],
+                                    cmd=cmd_name)
+                        set_props[t[0]] = t[1]
 
         if not pargs:
                 usage(_("an image directory path must be specified"),
-                    cmd="image-create")
+                    cmd=cmd_name)
         elif len(pargs) > 1:
                 usage(_("only one image directory path may be specified"),
-                    cmd="image-create")
+                    cmd=cmd_name)
         image_dir = pargs[0]
 
         if not pub_name and not pub_url:
                 usage(_("publisher argument must be of the form "
-                    "'<prefix>=<uri> or '<uri>''."), cmd="image-create")
+                    "'<prefix>=<uri> or '<uri>''."), cmd=cmd_name)
         elif not pub_name and not refresh_allowed:
                 usage(_("--no-refresh cannot be used with -p unless a "
                     "publisher prefix is provided."))
@@ -3429,7 +3631,7 @@
                     progtrack=progtrack, refresh_allowed=refresh_allowed,
                     socket_path=sock_path, ssl_cert=ssl_cert,
                     ssl_key=ssl_key, sys_repo=sys_repo_uri, repo_uri=repo_uri,
-                    variants=variants)
+                    variants=variants, props=set_props)
                 __img = api_inst.img
         except api_errors.InvalidDepotResponseException, e:
                 # Ensure messages are displayed after the spinner.
@@ -3439,18 +3641,18 @@
                     "location and the client's network configuration."
                     "\nAdditional details:\n\n%(error)s") %
                     { "pub_url": pub_url, "error": e },
-                    cmd="image-create")
+                    cmd=cmd_name)
                 print_proxy_config()
                 return EXIT_OOPS
         except api_errors.CatalogRefreshException, cre:
                 # Ensure messages are displayed after the spinner.
-                error("", cmd="image-create")
+                error("", cmd=cmd_name)
                 if display_catalog_failures(cre) == 0:
                         return EXIT_OOPS
                 else:
                         return EXIT_PARTIAL
         except api_errors.ApiException, e:
-                error(str(e), cmd="image-create")
+                error(str(e), cmd=cmd_name)
                 return EXIT_OOPS
         return EXIT_OK
 
@@ -3742,6 +3944,7 @@
                 return EXIT_OOPS
 
         cmds = {
+                "add-property-value"     : property_add_value,
                 "authority"        : publisher_list,
                 "change-facet"     : change_facet,
                 "change-variant"   : change_variant,
@@ -3759,6 +3962,7 @@
                 "purge-history"    : history_purge,
                 "rebuild-index"    : rebuild_index,
                 "refresh"          : publisher_refresh,
+                "remove-property-value"  : property_remove_value,
                 "search"           : search,
                 "set-authority"    : publisher_set,
                 "set-property"     : property_set,