Skip to main content

Source code file content

Revision: 188

15771544 pkgrepo should have a way of removing an entire publisher
» Project Revision History

» Checkout URL

website-repo / webrev / xiaoshen / Bug_15771544_pkgrepo6 / src / pkgrepo.py.lhs.html

Size: 73847 bytes, 1 line
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head><meta http-equiv="cache-control" content="no-cache"></meta>
<meta http-equiv="Pragma" content="no-cache"></meta>
<meta http-equiv="Expires" content="-1"></meta>
<!--
   Note to customizers: the body of the webrev is IDed as SUNWwebrev
   to allow easy overriding by users of webrev via the userContent.css
   mechanism available in some browsers.

   For example, to have all "removed" information be red instead of
   brown, set a rule in your userContent.css file like:

       body#SUNWwebrev span.removed { color: red ! important; }
-->
<style type="text/css" media="screen">
body {
    background-color: #eeeeee;
}
hr {
    border: none 0;
    border-top: 1px solid #aaa;
    height: 1px;
}
div.summary {
    font-size: .8em;
    border-bottom: 1px solid #aaa;
    padding-left: 1em;
    padding-right: 1em;
}
div.summary h2 {
    margin-bottom: 0.3em;
}
div.summary table th {
    text-align: right;
    vertical-align: top;
    white-space: nowrap;
}
span.lineschanged {
    font-size: 0.7em;
}
span.oldmarker {
    color: red;
    font-size: large;
    font-weight: bold;
}
span.newmarker {
    color: green;
    font-size: large;
    font-weight: bold;
}
span.removed {
    color: brown;
}
span.changed {
    color: blue;
}
span.new {
    color: blue;
    font-weight: bold;
}
span.chmod {
    font-size: 0.7em;
    color: #db7800;
}
a.print { font-size: x-small; }
a:hover { background-color: #ffcc99; }
</style>

<style type="text/css" media="print">
pre { font-size: 0.8em; font-family: courier, monospace; }
span.removed { color: #444; font-style: italic }
span.changed { font-weight: bold; }
span.new { font-weight: bold; }
span.newmarker { font-size: 1.2em; font-weight: bold; }
span.oldmarker { font-size: 1.2em; font-weight: bold; }
a.print {display: none}
hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
</style>

    <script type="text/javascript" src="../ancnav.js"></script>
    </head>
    <body id="SUNWwebrev" onkeypress="keypress(event);">
    <a name="0"></a>
    <pre><a href="https://bug.oraclecorp.com/show?15771544">15771544</a> pkgrepo should have a way of removing an entire publisher</pre><hr></hr>
<pre>
   1 #!/usr/bin/python2.6
   2 #
   3 # CDDL HEADER START
   4 #
   5 # The contents of this file are subject to the terms of the
   6 # Common Development and Distribution License (the "License").
   7 # You may not use this file except in compliance with the License.
   8 #
   9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10 # or http://www.opensolaris.org/os/licensing.
  11 # See the License for the specific language governing permissions
  12 # and limitations under the License.
  13 #
  14 # When distributing Covered Code, include this CDDL HEADER in each
  15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16 # If applicable, add the following below this CDDL HEADER, with the
  17 # fields enclosed by brackets "[]" replaced with your own identifying
  18 # information: Portions Copyright [yyyy] [name of copyright owner]
  19 #
  20 # CDDL HEADER END
  21 #
  22 
  23 #
  24 # Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
  25 #
  26 
  27 PKG_CLIENT_NAME = "pkgrepo"
  28 
  29 # pkgrepo exit codes
  30 EXIT_OK      = 0
  31 EXIT_OOPS    = 1
  32 EXIT_BADOPT  = 2
  33 EXIT_PARTIAL = 3
  34 
  35 # listing constants
  36 LISTING_FORMATS = ("json", "json-formatted", "tsv")
  37 
  38 # globals
  39 tmpdirs = []
  40 
  41 import atexit
  42 import collections
  43 import copy
  44 import errno
  45 import getopt
  46 import gettext
  47 import locale
  48 import logging
  49 import shlex
  50 import shutil
  51 import sys
  52 import tempfile
  53 import textwrap
  54 import traceback
  55 import warnings
  56 
  57 from pkg.client import global_settings
  58 from pkg.client.debugvalues import DebugValues
  59 from pkg.misc import msg, PipeError
  60 import pkg
  61 import pkg.catalog
  62 import pkg.client.api_errors as apx
  63 import pkg.client.pkgdefs as pkgdefs
  64 import pkg.client.progress
  65 import pkg.client.publisher as publisher
  66 import pkg.client.transport.transport as transport
  67 import pkg.misc as misc
  68 import pkg.server.repository as sr
  69 
  70 logger = global_settings.logger
  71 
  72 @atexit.register
  73 def cleanup():
  74         """To be called at program finish."""
  75         for d in tmpdirs:
  76                 shutil.rmtree(d, True)
  77 
  78 
  79 def error(text, cmd=None):
  80         """Emit an error message prefixed by the command name """
  81 
  82         if cmd:
  83                 text = "%s: %s" % (cmd, text)
  84                 pkg_cmd = "pkgrepo "
  85         else:
  86                 pkg_cmd = "pkgrepo: "
  87 
  88                 # If we get passed something like an Exception, we can convert
  89                 # it down to a string.
  90                 text = str(text)
  91 
  92         # If the message starts with whitespace, assume that it should come
  93         # *before* the command-name prefix.
  94         text_nows = text.lstrip()
  95         ws = text[:len(text) - len(text_nows)]
  96 
  97         # This has to be a constant value as we can't reliably get our actual
  98         # program name on all platforms.
  99         logger.error(ws + pkg_cmd + text_nows)
 100 
 101 
 102 def get_tracker(quiet=False):
 103         if quiet:
 104                 progtrack = pkg.client.progress.QuietProgressTracker()
 105         else:
 106                 try:
 107                         progtrack = \
 108                             pkg.client.progress.FancyUNIXProgressTracker()
 109                 except pkg.client.progress.ProgressTrackerException:
 110                         progtrack = \
 111                             pkg.client.progress.CommandLineProgressTracker()
 112         return progtrack
 113 
 114 
 115 def usage(usage_error=None, cmd=None, retcode=2, full=False):
 116         """Emit a usage message and optionally prefix it with a more
 117         specific error message.  Causes program to exit.
 118         """
 119 
 120         if usage_error:
 121                 error(usage_error, cmd=cmd)
 122 
 123         if not full:
 124                 # The full usage message isn't desired.
 125                 logger.error(_("Try `pkgrepo --help or -?' for more "
 126                     "information."))
 127                 sys.exit(retcode)
 128 
 129         msg(_("""\
 130 Usage:
 131         pkgrepo [options] command [cmd_options] [operands]
 132 
 133 Subcommands:
 134      pkgrepo create [--version ver] uri_or_path
 135 
 136      pkgrepo add-publisher -s repo_uri_or_path publisher ...
 137 
<a name="1" id="anc1"></a>


 138      pkgrepo get [-F format] [-p publisher ...] -s repo_uri_or_path 
 139          [--key ssl_key ... --cert ssl_cert ...] [section/property ...]
 140 
 141      pkgrepo info [-F format] [-H] [-p publisher ...] -s repo_uri_or_path
 142          [--key ssl_key ... --cert ssl_cert ...]
 143 
 144      pkgrepo list [-F format] [-H] [-p publisher ...] -s repo_uri_or_path 
 145          [--key ssl_key ... --cert ssl_cert ...] [pkg_fmri_pattern ...]
 146 
 147      pkgrepo rebuild [-p publisher ...] -s repo_uri_or_path [--key ssl_key ...
 148          --cert ssl_cert ...] [--no-catalog] [--no-index]
 149 
 150      pkgrepo refresh [-p publisher ...] -s repo_uri_or_path [--key ssl_key ...
 151          --cert ssl_cert ...] [--no-catalog] [--no-index]
 152 
 153      pkgrepo remove [-n] [-p publisher ...] -s repo_uri_or_path
 154          pkg_fmri_pattern ...
 155 
 156      pkgrepo set [-p publisher ...] -s repo_uri_or_path
 157          section/property[+|-]=[value] ... or
 158          section/property[+|-]=([value]) ...
 159 
 160      pkgrepo verify [-p publisher ...] -s repo_uri_or_path
 161 
 162      pkgrepo fix [-v] [-p publisher ...] -s repo_uri_or_path
 163 
 164      pkgrepo help
 165      pkgrepo version
 166 
 167 Options:
 168         --help or -?
 169             Displays a usage message."""))
 170 
 171         sys.exit(retcode)
 172 
 173 class OptionError(Exception):
 174         """Option exception. """
 175 
 176         def __init__(self, *args):
 177                 Exception.__init__(self, *args)
 178 
 179 
 180 def parse_uri(uri):
 181         """Parse the repository location provided and attempt to transform it
 182         into a valid repository URI.
 183         """
 184 
 185         return publisher.RepositoryURI(misc.parse_uri(uri))
 186 
 187 
 188 def subcmd_remove(conf, args):
 189         subcommand = "remove"
 190 
 191         opts, pargs = getopt.getopt(args, "np:s:")
 192 
 193         dry_run = False
 194         pubs = set()
 195         for opt, arg in opts:
 196                 if opt == "-n":
 197                         dry_run = True
 198                 elif opt == "-p":
 199                         pubs.add(arg)
 200                 elif opt == "-s":
 201                         conf["repo_uri"] = parse_uri(arg)
 202 
 203         if not pargs:
 204                 usage(_("At least one package pattern must be provided."),
 205                     cmd=subcommand)
 206 
 207         # Get repository object.
 208         if not conf.get("repo_uri", None):
 209                 usage(_("A package repository location must be provided "
 210                     "using -s."), cmd=subcommand)
 211         repo = get_repo(conf, read_only=False, subcommand=subcommand)
 212 
 213         if "all" in pubs:
 214                 pubs = set()
 215 
 216         # Find matching packages.
 217         try:
 218                 matching, refs = repo.get_matching_fmris(pargs, pubs=pubs)
 219         except apx.PackageMatchErrors, e:
 220                 error(str(e), cmd=subcommand)
 221                 return EXIT_OOPS
 222 
 223         if dry_run:
 224                 # Don't make any changes; display list of packages to be
 225                 # removed and exit.
 226                 packages = set(f for m in matching.values() for f in m)
 227                 count = len(packages)
 228                 plist = "\n".join("\t%s" % p.get_fmri(include_build=False) 
 229                     for p in sorted(packages))
 230                 logger.info(_("%(count)d package(s) will be removed:\n"
 231                     "%(plist)s") % locals())
 232                 return EXIT_OK
 233 
 234         progtrack = get_tracker()
 235         packages = collections.defaultdict(list)
 236         for m in matching.values():
 237                 for f in m:
 238                         packages[f.publisher].append(f)
 239 
 240         for pub in packages:
 241                 logger.info(_("Removing packages for publisher %s ...") % pub)
 242                 repo.remove_packages(packages[pub], progtrack=progtrack,
 243                     pub=pub)
 244                 if len(packages) &gt; 1:
 245                         # Add a newline between each publisher.
 246                         logger.info("")
 247 
 248         return EXIT_OK
 249 
 250 
 251 def get_repo(conf, read_only=True, subcommand=None):
 252         """Return the repository object for current program configuration."""
 253 
 254         repo_uri = conf["repo_uri"]
 255         if repo_uri.scheme != "file":
 256                 usage(_("Network repositories are not currently supported "
 257                     "for this operation."), cmd=subcommand)
 258 
 259         path = repo_uri.get_pathname()
 260         if not path:
 261                 # Bad URI?
 262                 raise sr.RepositoryInvalidError(str(repo_uri))
 263         return sr.Repository(read_only=read_only, root=path)
 264 
 265 
 266 def setup_transport(conf, subcommand=None, verbose=False, ssl_key=None,
 267     ssl_cert=None):
 268         repo_uri = conf.get("repo_uri", None)
 269         if not repo_uri:
 270                 usage(_("No repository location specified."), cmd=subcommand)
 271 
 272         temp_root = misc.config_temp_root()
 273 
 274         tmp_dir = tempfile.mkdtemp(dir=temp_root)
 275         tmpdirs.append(tmp_dir)
 276 
 277         incoming_dir = tempfile.mkdtemp(dir=temp_root)
 278         tmpdirs.append(incoming_dir)
 279 
 280         cache_dir = tempfile.mkdtemp(dir=temp_root)
 281         tmpdirs.append(cache_dir)
 282 
 283         # Create transport and transport config.
 284         xport, xport_cfg = transport.setup_transport()
 285         xport_cfg.add_cache(cache_dir, readonly=False)
 286         xport_cfg.incoming_root = incoming_dir
 287 
 288         # Configure target publisher.
 289         src_pub = transport.setup_publisher(str(repo_uri), "target", xport,
 290             xport_cfg, remote_prefix=True, ssl_key=ssl_key, ssl_cert=ssl_cert)
 291 
 292         return xport, src_pub, tmp_dir
 293 
 294 
 295 def subcmd_add_publisher(conf, args):
 296         """Add publisher(s) to the specified repository."""
 297 
 298         subcommand = "add-publisher"
 299 
 300         opts, pargs = getopt.getopt(args, "s:")
 301         for opt, arg in opts:
 302                 if opt == "-s":
 303                         conf["repo_uri"] = parse_uri(arg)
 304 
 305         repo_uri = conf.get("repo_uri", None)
 306         if not repo_uri:
 307                 usage(_("No repository location specified."), cmd=subcommand)
 308         if repo_uri.scheme != "file":
 309                 usage(_("Network repositories are not currently supported "
 310                     "for this operation."), cmd=subcommand)
 311 
 312         if not pargs:
 313                 usage(_("At least one publisher must be specified"),
 314                     cmd=subcommand)
 315 
 316         abort = False
 317         for pfx in pargs:
 318                 if not misc.valid_pub_prefix(pfx):
 319                         error(_("Invalid publisher prefix '%s'") % pfx,
 320                             cmd=subcommand)
 321                         abort = True
 322         if abort:
 323                 return EXIT_OOPS
 324 
 325         repo = get_repo(conf, read_only=False, subcommand=subcommand)
 326         make_default = not repo.publishers
 327         existing = repo.publishers &amp; set(pargs)
 328 
 329         # Elide the publishers that already exist, but retain the order
 330         # publishers were specified in.
 331         new_pubs = [
 332             pfx for pfx in pargs
 333             if pfx not in repo.publishers
 334         ]
 335 
 336         # Tricky logic; _set_pub will happily add new publishers if necessary
 337         # and not set any properties if you didn't specify any.
 338         rval = _set_pub(conf, subcommand, {}, new_pubs, repo)
 339 
 340         if make_default:
 341                 # No publisher existed previously, so set the default publisher
 342                 # to be the first new one that was added.
 343                 _set_repo(conf, subcommand, { "publisher": {
 344                     "prefix": new_pubs[0] } }, repo)
 345 
 346         if rval == EXIT_OK and existing:
 347                 # Some of the publishers that were requested for addition
 348                 # were already known.
 349                 error(_("specified publisher(s) already exist: %s") %
 350                     ", ".join(existing), cmd=subcommand)
 351                 if new_pubs:
 352                         return EXIT_PARTIAL
 353                 return EXIT_OOPS
 354         return rval
 355 
<a name="2" id="anc2"></a>

 356 
<a name="3" id="anc3"></a>











































































 357 def subcmd_create(conf, args):
 358         """Create a package repository at the given location."""
 359 
 360         subcommand = "create"
 361 
 362         opts, pargs = getopt.getopt(args, "s:", ["version="])
 363 
 364         version = None
 365         for opt, arg in opts:
 366                 if opt == "-s":
 367                         conf["repo_uri"] = parse_uri(arg)
 368                 elif opt == "--version":
 369                         # This option is currently private and allows creating a
 370                         # repository with a specific format based on version.
 371                         try:
 372                                 version = int(arg)
 373                         except ValueError:
 374                                 usage(_("Version must be an integer value."),
 375                                     cmd=subcommand)
 376 
 377         if len(pargs) &gt; 1:
 378                 usage(_("Only one repository location may be specified."),
 379                     cmd=subcommand)
 380         elif pargs:
 381                 conf["repo_uri"] = parse_uri(pargs[0])
 382 
 383         repo_uri = conf.get("repo_uri", None)
 384         if not repo_uri:
 385                 usage(_("No repository location specified."), cmd=subcommand)
 386         if repo_uri.scheme != "file":
 387                 usage(_("Network repositories are not currently supported "
 388                     "for this operation."), cmd=subcommand)
 389 
 390         # Attempt to create a repository at the specified location.  Allow
 391         # whatever exceptions are raised to bubble up.
 392         sr.repository_create(repo_uri, version=version)
 393 
 394         return EXIT_OK
 395 
 396 
 397 def subcmd_get(conf, args):
 398         """Display repository properties."""
 399 
 400         subcommand = "get"
 401         omit_headers = False
 402         out_format = "default"
 403         pubs = set()
 404         key = None
 405         cert = None
 406 
 407         opts, pargs = getopt.getopt(args, "F:Hp:s:", ["key=", "cert="])
 408         for opt, arg in opts:
 409                 if opt == "-F":
 410                         out_format = arg
 411                         if out_format not in LISTING_FORMATS:
 412                                 usage(_("Unrecognized format %(format)s."
 413                                     " Supported formats: %(valid)s") % \
 414                                     { "format": out_format,
 415                                     "valid": LISTING_FORMATS }, cmd=subcommand)
 416                                 return EXIT_OOPS
 417                 elif opt == "-H":
 418                         omit_headers = True
 419                 elif opt == "-p":
 420                         pubs.add(arg)
 421                 elif opt == "-s":
 422                         conf["repo_uri"] = parse_uri(arg)
 423                 elif opt == "--key":
 424                         key = arg
 425                 elif opt == "--cert":
 426                         cert = arg
 427 
 428         # Setup transport so configuration can be retrieved.
 429         if not conf.get("repo_uri", None):
 430                 usage(_("A package repository location must be provided "
 431                     "using -s."), cmd=subcommand)
 432         xport, xpub, tmp_dir = setup_transport(conf, subcommand=subcommand,
 433             ssl_key=key, ssl_cert=cert)
 434 
 435         # Get properties.
 436         if pubs:
 437                 return _get_pub(conf, subcommand, xport, xpub, omit_headers,
 438                     out_format, pubs, pargs)
 439         return _get_repo(conf, subcommand, xport, xpub, omit_headers,
 440             out_format, pargs)
 441 
 442 
 443 def _get_repo(conf, subcommand, xport, xpub, omit_headers, out_format, pargs):
 444         """Display repository properties."""
 445 
 446         # Configuration index is indexed by section name and property name.
 447         # Retrieve and flatten it to simplify listing process.
 448         stat_idx = xport.get_status(xpub)
 449         cfg_idx = stat_idx.get("repository", {}).get("configuration", {})
 450         props = set()
 451 
 452         # Set minimum widths for section and property name columns by using the
 453         # length of the column headers.
 454         max_sname_len = len(_("SECTION"))
 455         max_pname_len = len(_("PROPERTY"))
 456 
 457         for sname in cfg_idx:
 458                 max_sname_len = max(max_sname_len, len(sname))
 459                 for pname in cfg_idx[sname]:
 460                         max_pname_len = max(max_pname_len, len(pname))
 461                         props.add("/".join((sname, pname)))
 462 
 463         req_props = set(pargs)
 464         if len(req_props) &gt;= 1:
 465                 found = props &amp; req_props
 466                 notfound = req_props - found
 467                 del props
 468         else:
 469                 found = props
 470                 notfound = set()
 471 
 472         def gen_listing():
 473                 for prop in sorted(found):
 474                         sname, pname = prop.rsplit("/", 1)
 475                         sval = cfg_idx[sname][pname]
 476                         yield {
 477                             "section": sname,
 478                             "property": pname,
 479                             "value": sval,
 480                         }
 481 
 482         #    SECTION PROPERTY VALUE
 483         #    &lt;sec_1&gt; &lt;prop_1&gt; &lt;prop_1_value&gt;
 484         #    &lt;sec_2&gt; &lt;prop_2&gt; &lt;prop_2_value&gt;
 485         #    ...
 486         field_data = {
 487             "section" : [("default", "json", "tsv"), _("SECTION"), ""],
 488             "property" : [("default", "json", "tsv"), _("PROPERTY"), ""],
 489             "value" : [("default", "json", "tsv"), _("VALUE"), ""],
 490         }
 491         desired_field_order = (_("SECTION"), _("PROPERTY"), _("VALUE"))
 492 
 493         # Default output formatting.
 494         def_fmt = "%-" + str(max_sname_len) + "s %-" + str(max_pname_len) + \
 495             "s %s"
 496 
 497         if found or (not req_props and out_format == "default"):
 498                 # print without trailing newline.
 499                 sys.stdout.write(misc.get_listing(desired_field_order,
 500                     field_data, gen_listing(), out_format, def_fmt,
 501                     omit_headers))
 502 
 503         if found and notfound:
 504                 return EXIT_PARTIAL
 505         if req_props and not found:
 506                 if out_format == "default":
 507                         # Don't pollute other output formats.
 508                         error(_("no matching properties found"),
 509                             cmd=subcommand)
 510                 return EXIT_OOPS
 511         return EXIT_OK
 512 
 513 
 514 def _get_matching_pubs(subcommand, pubs, xport, xpub, out_format="default",
 515     use_transport=False):
 516 
 517         # Retrieve publisher information.
 518         pub_data = xport.get_publisherdata(xpub)
 519         known_pubs = set(p.prefix for p in pub_data)
 520         if len(pubs) &gt; 0 and "all" not in pubs:
 521                 found = known_pubs &amp; pubs
 522                 notfound = pubs - found
 523                 pub_data = [p for p in pub_data if p.prefix in found]
 524         else:
 525                 found = known_pubs
 526                 notfound = set()
 527 
 528         if use_transport:
 529                 # Assign transport information.
 530                 for p in pub_data:
 531                         p.repository = xpub.repository
 532 
 533         # Establish initial return value and perform early exit if appropriate.
 534         rval = EXIT_OK
 535         if found and notfound:
 536                 rval = EXIT_PARTIAL
 537         elif pubs and not found:
 538                 if out_format == "default":
 539                         # Don't pollute other output formats.
 540                         error(_("no matching publishers found"),
 541                             cmd=subcommand)
 542                 return EXIT_OOPS, None, None
 543         return rval, found, pub_data
 544 
 545 
 546 def _get_pub(conf, subcommand, xport, xpub, omit_headers, out_format, pubs,
 547     pargs):
 548         """Display publisher properties."""
 549 
 550         rval, found, pub_data = _get_matching_pubs(subcommand, pubs, xport,
 551             xpub, out_format=out_format)
 552         if rval == EXIT_OOPS:
 553                 return rval
 554 
 555         # Set minimum widths for section and property name columns by using the
 556         # length of the column headers and data.
 557         max_pubname_len = str(max(
 558             [len(_("PUBLISHER"))] + [len(p) for p in found]
 559         ))
 560         max_sname_len = len(_("SECTION"))
 561         max_pname_len = len(_("PROPERTY"))
 562 
 563         # For each requested publisher, retrieve the requested property data.
 564         pub_idx = {}
 565         for pub in pub_data:
 566                 pub_idx[pub.prefix] = {
 567                     "publisher": {
 568                         "alias": pub.alias,
 569                         "prefix": pub.prefix,
 570                     },
 571                 }
 572 
 573                 pub_repo = pub.repository
 574                 if pub_repo:
 575                         pub_idx[pub.prefix]["repository"] = {
 576                             "collection-type": pub_repo.collection_type,
 577                             "description": pub_repo.description,
 578                             "legal-uris": pub_repo.legal_uris,
 579                             "mirrors": pub_repo.mirrors,
 580                             "name": pub_repo.name,
 581                             "origins": pub_repo.origins,
 582                             "refresh-seconds": pub_repo.refresh_seconds,
 583                             "registration-uri": pub_repo.registration_uri,
 584                             "related-uris": pub_repo.related_uris,
 585                         }
 586                 else:
 587                         pub_idx[pub.prefix]["repository"] = {
 588                             "collection-type": "core",
 589                             "description": "",
 590                             "legal-uris": [],
 591                             "mirrors": [],
 592                             "name": "",
 593                             "origins": [],
 594                             "refresh-seconds": "",
 595                             "registration-uri": "",
 596                             "related-uris": [],
 597                         }
 598 
 599         # Determine possible set of properties and lengths.
 600         props = set()
 601         for pub in pub_idx:
 602                 for sname in pub_idx[pub]:
 603                         max_sname_len = max(max_sname_len, len(sname))
 604                         for pname in pub_idx[pub][sname]:
 605                                 max_pname_len = max(max_pname_len, len(pname))
 606                                 props.add("/".join((sname, pname)))
 607 
 608         # Determine properties to display.
 609         req_props = set(pargs)
 610         if len(req_props) &gt;= 1:
 611                 found = props &amp; req_props
 612                 notfound = req_props - found
 613                 del props
 614         else:
 615                 found = props
 616                 notfound = set()
 617 
 618         def gen_listing():
 619                 for pub in sorted(pub_idx.keys()):
 620                         for prop in sorted(found):
 621                                 sname, pname = prop.rsplit("/", 1)
 622                                 sval = pub_idx[pub][sname][pname]
 623                                 yield {
 624                                     "publisher": pub,
 625                                     "section": sname,
 626                                     "property": pname,
 627                                     "value": sval,
 628                                 }
 629 
 630         #    PUBLISHER SECTION PROPERTY VALUE
 631         #    &lt;pub_1&gt;   &lt;sec_1&gt; &lt;prop_1&gt; &lt;prop_1_value&gt;
 632         #    &lt;pub_1&gt;   &lt;sec_2&gt; &lt;prop_2&gt; &lt;prop_2_value&gt;
 633         #    ...
 634         field_data = {
 635             "publisher" : [("default", "json", "tsv"), _("PUBLISHER"), ""],
 636             "section" : [("default", "json", "tsv"), _("SECTION"), ""],
 637             "property" : [("default", "json", "tsv"), _("PROPERTY"), ""],
 638             "value" : [("default", "json", "tsv"), _("VALUE"), ""],
 639         }
 640         desired_field_order = (_("PUBLISHER"), _("SECTION"), _("PROPERTY"),
 641             _("VALUE"))
 642 
 643         # Default output formatting.
 644         def_fmt = "%-" + str(max_pubname_len) + "s %-" + str(max_sname_len) + \
 645             "s %-" + str(max_pname_len) + "s %s"
 646 
 647         if found or (not req_props and out_format == "default"):
 648                 # print without trailing newline.
 649                 sys.stdout.write(misc.get_listing(desired_field_order,
 650                     field_data, gen_listing(), out_format, def_fmt,
 651                     omit_headers))
 652 
 653         if found and notfound:
 654                 rval = EXIT_PARTIAL
 655         if req_props and not found:
 656                 if out_format == "default":
 657                         # Don't pollute other output formats.
 658                         error(_("no matching properties found"),
 659                             cmd=subcommand)
 660                 rval = EXIT_OOPS
 661         return rval
 662 
 663 
 664 def subcmd_info(conf, args):
 665         """Display a list of known publishers and a summary of known packages
 666         and when the package data for the given publisher was last updated.
 667         """
 668 
 669         subcommand = "info"
 670         omit_headers = False
 671         out_format = "default"
 672         pubs = set()
 673         key = None
 674         cert = None
 675 
 676         opts, pargs = getopt.getopt(args, "F:Hp:s:", ["key=", "cert="])
 677         for opt, arg in opts:
 678                 if opt == "-F":
 679                         if arg not in LISTING_FORMATS:
 680                                 usage(_("Unrecognized format %(format)s."
 681                                     " Supported formats: %(valid)s") % \
 682                                     { "format": arg,
 683                                     "valid": LISTING_FORMATS }, cmd=subcommand)
 684                                 return EXIT_OOPS
 685                         out_format = arg
 686                 elif opt == "-H":
 687                         omit_headers = True
 688                 elif opt == "-p":
 689                         pubs.add(arg)
 690                 elif opt == "-s":
 691                         conf["repo_uri"] = parse_uri(arg)
 692                 elif opt == "--key":
 693                         key = arg
 694                 elif opt == "--cert":
 695                         cert = arg
 696 
 697         if pargs:
 698                 usage(_("command does not take operands"), cmd=subcommand)
 699 
 700         # Setup transport so status can be retrieved.
 701         if not conf.get("repo_uri", None):
 702                 usage(_("A package repository location must be provided "
 703                     "using -s."), cmd=subcommand)
 704         xport, xpub, tmp_dir = setup_transport(conf, subcommand=subcommand,
 705             ssl_key=key, ssl_cert=cert)
 706 
 707         # Retrieve repository status information.
 708         stat_idx = xport.get_status(xpub)
 709         pub_idx = stat_idx.get("repository", {}).get("publishers", {})
 710         if len(pubs) &gt; 0 and "all" not in pubs:
 711                 found = set(pub_idx.keys()) &amp; pubs
 712                 notfound = pubs - found
 713         else:
 714                 found = set(pub_idx.keys())
 715                 notfound = set()
 716 
 717         def gen_listing():
 718                 for pfx in found:
 719                         pdata = pub_idx[pfx]
 720                         pkg_count = pdata.get("package-count", 0)
 721                         last_update = pdata.get("last-catalog-update", "")
 722                         if last_update:
 723                                 # Reformat the date into something more user
 724                                 # friendly (and locale specific).
 725                                 last_update = pkg.catalog.basic_ts_to_datetime(
 726                                     last_update)
 727                                 last_update = "%sZ" % pkg.catalog.datetime_to_ts(
 728                                     last_update)
 729                         rstatus = _(pub_idx[pfx].get("status", "online"))
 730                         yield {
 731                             "publisher": pfx,
 732                             "packages": pkg_count,
 733                             "status": rstatus,
 734                             "updated": last_update,
 735                         }
 736 
 737         #    PUBLISHER PACKAGES        STATUS   UPDATED
 738         #    &lt;pub_1&gt;   &lt;num_uniq_pkgs&gt; &lt;status&gt; &lt;cat_last_modified&gt;
 739         #    &lt;pub_2&gt;   &lt;num_uniq_pkgs&gt; &lt;status&gt; &lt;cat_last_modified&gt;
 740         #    ...
 741         field_data = {
 742             "publisher" : [("default", "json", "tsv"), _("PUBLISHER"), ""],
 743             "packages" : [("default", "json", "tsv"), _("PACKAGES"), ""],
 744             "status" : [("default", "json", "tsv"), _("STATUS"), ""],
 745             "updated" : [("default", "json", "tsv"), _("UPDATED"), ""],
 746         }
 747 
 748         desired_field_order = (_("PUBLISHER"), "", _("PACKAGES"), _("STATUS"),
 749             _("UPDATED"))
 750 
 751         # Default output formatting.
 752         pub_len = str(max(
 753             [len(desired_field_order[0])] + [len(p) for p in found]
 754         ))
 755         def_fmt = "%-" + pub_len + "s %-8s %-16s %s"
 756 
 757         if found or (not pubs and out_format == "default"):
 758                 # print without trailing newline.
 759                 sys.stdout.write(misc.get_listing(desired_field_order,
 760                     field_data, gen_listing(), out_format, def_fmt,
 761                     omit_headers))
 762 
 763         if found and notfound:
 764                 return EXIT_PARTIAL
 765         if pubs and not found:
 766                 if out_format == "default":
 767                         # Don't pollute other output formats.
 768                         error(_("no matching publishers found"),
 769                             cmd=subcommand)
 770                 return EXIT_OOPS
 771         return EXIT_OK
 772 
 773 def subcmd_list(conf, args):
 774         """List all packages matching the specified patterns."""
 775 
 776         subcommand = "list"
 777         omit_headers = False
 778         out_format = "default"
 779         pubs = set()
 780         key = None
 781         cert = None
 782 
 783         opts, pargs = getopt.getopt(args, "F:Hp:s:", ["key=", "cert="])
 784         for opt, arg in opts:
 785                 if opt == "-F":
 786                         out_format = arg
 787                         if out_format not in LISTING_FORMATS:
 788                                 usage(_("Unrecognized format %(format)s."
 789                                     " Supported formats: %(valid)s") %
 790                                     { "format": out_format,
 791                                     "valid": LISTING_FORMATS }, cmd=subcommand)
 792                                 return EXIT_OOPS
 793                 elif opt == "-H":
 794                         omit_headers = True
 795                 elif opt == "-p":
 796                         pubs.add(arg)
 797                 elif opt == "-s":
 798                         conf["repo_uri"] = parse_uri(arg)
 799                 elif opt == "--key":
 800                         key = arg
 801                 elif opt == "--cert":
 802                         cert = arg
 803 
 804 
 805         # Setup transport so configuration can be retrieved.
 806         if not conf.get("repo_uri", None):
 807                 usage(_("A package repository location must be provided "
 808                     "using -s."), cmd=subcommand)
 809         xport, xpub, tmp_dir = setup_transport(conf, subcommand=subcommand,
 810             ssl_key=key, ssl_cert=cert)
 811 
 812         rval, found, pub_data = _get_matching_pubs(subcommand, pubs, xport,
 813             xpub, out_format=out_format, use_transport=True)
 814         if rval == EXIT_OOPS:
 815                 return rval
 816 
 817         temp_root = misc.config_temp_root()
 818         progtrack = get_tracker()
 819         progtrack.set_purpose(progtrack.PURPOSE_LISTING)
 820 
 821         progtrack.refresh_start(pub_cnt=len(pub_data), full_refresh=True,
 822             target_catalog=False)
 823 
 824         for pub in pub_data:
 825                 progtrack.refresh_start_pub(pub)
 826                 meta_root = tempfile.mkdtemp(dir=temp_root)
 827                 tmpdirs.append(meta_root)
 828                 pub.meta_root = meta_root
 829                 pub.transport = xport
 830 
 831                 try:
 832                         pub.refresh(True, True, progtrack=progtrack)
 833                 except apx.TransportError:
 834                         # Assume that a catalog doesn't exist for the target
 835                         # publisher and drive on.
 836                         pass
 837                 progtrack.refresh_end_pub(pub)
 838 
 839         progtrack.refresh_done()
 840         listed = {}
 841         matched = set()
 842         unmatched = set()
 843 
 844         def gen_listing():
 845                 collect_attrs = out_format.startswith("json")
 846                 for pub in pub_data:
 847                         cat = pub.catalog
 848                         for f, states, attrs in cat.gen_packages(
 849                             collect_attrs=collect_attrs, matched=matched,
 850                             patterns=pargs, pubs=[pub.prefix],
 851                             unmatched=unmatched, return_fmris=True):
 852                                 if not listed:
 853                                         listed["packages"] = True
 854 
 855                                 state = None
 856                                 if out_format == "default" or \
 857                                     out_format == "tsv":
 858                                         if pkgdefs.PKG_STATE_OBSOLETE in \
 859                                             states:
 860                                                 state = "o"
 861                                         elif pkgdefs.PKG_STATE_RENAMED in \
 862                                             states:
 863                                                 state = "r"
 864 
 865                                 if out_format == "default":
 866                                     fver = str(f.version.get_version(
 867                                         include_build=False))
 868                                     ffmri = str(f.get_fmri(include_build=False))
 869                                 else:
 870                                     fver = str(f.version)
 871                                     ffmri = str(f)
 872 
 873                                 ret = {
 874                                     "publisher": f.publisher,
 875                                     "name": f.pkg_name,
 876                                     "version": fver,
 877                                     "release": str(f.version.release),
 878                                     "build-release":
 879                                         str(f.version.build_release),
 880                                     "branch": str(f.version.branch),
 881                                     "timestamp":
 882                                         str(f.version.timestr),
 883                                     "pkg.fmri": ffmri,
 884                                     "short_state": state,
 885                                 }
 886 
 887                                 for attr in attrs:
 888                                         ret[attr] = []
 889                                         for mods in attrs[attr]:
 890                                                 d = dict(mods)
 891                                                 d["value"] = \
 892                                                     attrs[attr][mods]
 893                                                 ret[attr].append(d)
 894                                 yield ret
 895 
 896                         unmatched.difference_update(matched)
 897 
 898         field_data = {
 899             "publisher": [("default", "json", "tsv"), _("PUBLISHER"), ""],
 900             "name": [("default", "json", "tsv"), _("NAME"), ""],
 901             "version": [("default", "json"), _("VERSION"), ""],
 902             "release": [("json", "tsv",), _("RELEASE"), ""],
 903             "build-release": [("json", "tsv",), _("BUILD RELEASE"), ""],
 904             "branch": [("json", "tsv",), _("BRANCH"), ""],
 905             "timestamp": [("json", "tsv",), _("PACKAGING DATE"), ""],
 906             "pkg.fmri": [("json", "tsv",), _("FMRI"), ""],
 907             "short_state": [("default", "tsv"), "O", ""],
 908          }
 909 
 910         desired_field_order = (_("PUBLISHER"), _("NAME"), "O", _("VERSION"),
 911             _("SUMMARY"), _("DESCRIPTION"), _("CATEGORIES"), _("RELEASE"),
 912             _("BUILD RELEASE"), _("BRANCH"), _("PACKAGING DATE"), _("FMRI"),
 913             _("STATE"))
 914 
 915         # Default output formatting.
 916         max_pub_name_len = str(
 917             max(list(len(p) for p in found) + [len(_("PUBLISHER"))]))
 918         def_fmt = "%-" + max_pub_name_len + "s %-45s %-1s %-s"
 919 
 920         # print without trailing newline.
 921         sys.stdout.write(misc.get_listing(
 922             desired_field_order, field_data, gen_listing(),
 923             out_format, def_fmt, omit_headers))
 924 
 925         if not listed and pargs:
 926                 # No matching packages.
 927                 logger.error("")
 928                 if not unmatched:
 929                         unmatched = pargs
 930                 error(apx.PackageMatchErrors(unmatched_fmris=unmatched),
 931                     cmd=subcommand)
 932                 return EXIT_OOPS
 933         elif unmatched:
 934                 # One or more patterns didn't match a package from any
 935                 # publisher; only display the error.
 936                 logger.error("")
 937                 error(apx.PackageMatchErrors(unmatched_fmris=unmatched),
 938                     cmd=subcommand)
 939                 return EXIT_PARTIAL
 940 
 941         return EXIT_OK
 942 
 943 
 944 def subcmd_rebuild(conf, args):
 945         """Rebuild the repository's catalog and index data (as permitted)."""
 946 
 947         subcommand = "rebuild"
 948         build_catalog = True
 949         build_index = True
 950         key = None
 951         cert = None
 952 
 953         opts, pargs = getopt.getopt(args, "p:s:", ["no-catalog", "no-index",
 954             "key=", "cert="])
 955         pubs = set()
 956         for opt, arg in opts:
 957                 if opt == "-p":
 958                         if not misc.valid_pub_prefix(arg):
 959                                 error(_("Invalid publisher prefix '%s'") % arg,
 960                                     cmd=subcommand)
 961                         pubs.add(arg)
 962                 elif opt == "-s":
 963                         conf["repo_uri"] = parse_uri(arg)
 964                 elif opt == "--no-catalog":
 965                         build_catalog = False
 966                 elif opt == "--no-index":
 967                         build_index = False
 968                 elif opt == "--key":
 969                         key = arg
 970                 elif opt == "--cert":
 971                         cert = arg
 972 
 973         if pargs:
 974                 usage(_("command does not take operands"), cmd=subcommand)
 975 
 976         if not build_catalog and not build_index:
 977                 # Why?  Who knows; but do what was requested--nothing!
 978                 return EXIT_OK
 979 
 980         # Setup transport so operation can be performed.
 981         if not conf.get("repo_uri", None):
 982                 usage(_("A package repository location must be provided "
 983                     "using -s."), cmd=subcommand)
 984 
 985         def do_rebuild(xport, xpub):
 986                 if build_catalog and build_index:
 987                         xport.publish_rebuild(xpub)
 988                 elif build_catalog:
 989                         xport.publish_rebuild_packages(xpub)
 990                 elif build_index:
 991                         xport.publish_rebuild_indexes(xpub)
 992 
 993         xport, xpub, tmp_dir = setup_transport(conf, subcommand=subcommand,
 994             ssl_key=key, ssl_cert=cert)
 995         rval, found, pub_data = _get_matching_pubs(subcommand, pubs, xport,
 996             xpub)
 997         if rval == EXIT_OOPS:
 998                 return rval
 999 
1000         logger.info("Initiating repository rebuild.")
1001         for pfx in found:
1002                 xpub.prefix = pfx
1003                 do_rebuild(xport, xpub)
1004 
1005         return rval
1006 
1007 
1008 def subcmd_refresh(conf, args):
1009         """Refresh the repository's catalog and index data (as permitted)."""
1010 
1011         subcommand = "refresh"
1012         add_content = True
1013         refresh_index = True
1014         key = None
1015         cert = None
1016 
1017         opts, pargs = getopt.getopt(args, "p:s:", ["no-catalog", "no-index",
1018             "key=", "cert="])
1019         pubs = set()
1020         for opt, arg in opts:
1021                 if opt == "-p":
1022                         if not misc.valid_pub_prefix(arg):
1023                                 error(_("Invalid publisher prefix '%s'") % arg,
1024                                     cmd=subcommand)
1025                         pubs.add(arg)
1026                 elif opt == "-s":
1027                         conf["repo_uri"] = parse_uri(arg)
1028                 elif opt == "--no-catalog":
1029                         add_content = False
1030                 elif opt == "--no-index":
1031                         refresh_index = False
1032                 elif opt == "--key":
1033                         key = arg
1034                 elif opt == "--cert":
1035                         cert = arg
1036 
1037         if pargs:
1038                 usage(_("command does not take operands"), cmd=subcommand)
1039 
1040         if not add_content and not refresh_index:
1041                 # Why?  Who knows; but do what was requested--nothing!
1042                 return EXIT_OK
1043 
1044         # Setup transport so operation can be performed.
1045         if not conf.get("repo_uri", None):
1046                 usage(_("A package repository location must be provided "
1047                     "using -s."), cmd=subcommand)
1048 
1049         def do_refresh(xport, xpub):
1050                 if add_content and refresh_index:
1051                         xport.publish_refresh(xpub)
1052                 elif add_content:
1053                         xport.publish_refresh_packages(xpub)
1054                 elif refresh_index:
1055                         xport.publish_refresh_indexes(xpub)
1056 
1057         xport, xpub, tmp_dir = setup_transport(conf, subcommand=subcommand,
1058             ssl_key=key, ssl_cert=cert)
1059         rval, found, pub_data = _get_matching_pubs(subcommand, pubs, xport,
1060             xpub)
1061         if rval == EXIT_OOPS:
1062                 return rval
1063 
1064         logger.info("Initiating repository refresh.")
1065         for pfx in found:
1066                 xpub.prefix = pfx
1067                 do_refresh(xport, xpub)
1068 
1069         return rval
1070 
1071 
1072 def subcmd_set(conf, args):
1073         """Set repository properties."""
1074 
1075         subcommand = "set"
1076         pubs = set()
1077 
1078         opts, pargs = getopt.getopt(args, "p:s:")
1079         for opt, arg in opts:
1080                 if opt == "-p":
1081                         pubs.add(arg)
1082                 elif opt == "-s":
1083                         conf["repo_uri"] = parse_uri(arg)
1084 
1085         bad_args = False
1086         props = {}
1087         if not pargs:
1088                 bad_args = True
1089         else:
1090                 for arg in pargs:
1091                         try:
1092                                 # Attempt to parse property into components.
1093                                 prop, val = arg.split("=", 1)
1094                                 sname, pname = prop.rsplit("/", 1)
1095 
1096                                 # Store property values by section.
1097                                 props.setdefault(sname, {})
1098 
1099                                 # Parse the property value into a list if
1100                                 # necessary, otherwise append it to the list
1101                                 # of values for the property.
1102                                 if len(val) &gt; 0  and val[0] == "(" and \
1103                                     val[-1] == ")":
1104                                         val = shlex.split(val.strip("()"))
1105 
1106                                 if sname in props and pname in props[sname]:
1107                                         # Determine if previous value is already
1108                                         # a list, and if not, convert and append
1109                                         # the value.
1110                                         pval = props[sname][pname]
1111                                         if not isinstance(pval, list):
1112                                                 pval = [pval]
1113                                         if isinstance(val, list):
1114                                                 pval.extend(val)
1115                                         else:
1116                                                 pval.append(val)
1117                                         props[sname][pname] = pval
1118                                 else:
1119                                         # Otherwise, just store the value.
1120                                         props[sname][pname] = val
1121                         except ValueError:
1122                                 bad_args = True
1123                                 break
1124 
1125         if bad_args:
1126                 usage(_("a property name and value must be provided in the "
1127                     "form &lt;section/property&gt;=&lt;value&gt; or "
1128                     "&lt;section/property&gt;=([\"&lt;value&gt;\" ...])"))
1129 
1130         # Get repository object.
1131         if not conf.get("repo_uri", None):
1132                 usage(_("A package repository location must be provided "
1133                     "using -s."), cmd=subcommand)
1134         repo = get_repo(conf, read_only=False, subcommand=subcommand)
1135 
1136         # Set properties.
1137         if pubs:
1138                 return _set_pub(conf, subcommand, props, pubs, repo)
1139 
1140         return _set_repo(conf, subcommand, props, repo)
1141 
1142 
1143 def _set_pub(conf, subcommand, props, pubs, repo):
1144         """Set publisher properties."""
1145 
1146         for sname, sprops in props.iteritems():
1147                 if sname not in ("publisher", "repository"):
1148                         usage(_("unknown property section "
1149                             "'%s'") % sname, cmd=subcommand)
1150                 for pname in sprops:
1151                         if sname == "publisher" and pname == "prefix":
1152                                 usage(_("'%s' may not be set using "
1153                                     "this command" % pname))
1154                         attrname = pname.replace("-", "_")
1155                         if not hasattr(publisher.Publisher, attrname) and \
1156                             not hasattr(publisher.Repository, attrname):
1157                                 usage(_("unknown property '%s'") %
1158                                     pname, cmd=subcommand)
1159 
1160         if "all" in pubs:
1161                 # Default to list of all publishers.
1162                 pubs = repo.publishers
1163                 if not pubs:
1164                         # If there are still no known publishers, this
1165                         # operation cannot succeed, so fail now.
1166                         usage(_("One or more publishers must be specified to "
1167                             "create and set properties for as none exist yet."),
1168                             cmd=subcommand)
1169 
1170         # Get publishers and update properties.
1171         failed = []
1172         new_pub = False
1173         for pfx in pubs:
1174                 try:
1175                         # Get a copy of the existing publisher.
1176                         pub = copy.copy(repo.get_publisher(pfx))
1177                 except sr.RepositoryUnknownPublisher, e:
1178                         pub = publisher.Publisher(pfx)
1179                         new_pub = True
1180                 except sr.RepositoryError, e:
1181                         failed.append((pfx, e))
1182                         continue
1183 
1184                 try:
1185                         # Set/update the publisher's properties.
1186                         for sname, sprops in props.iteritems():
1187                                 if sname == "publisher":
1188                                         target = pub
1189                                 elif sname == "repository":
1190                                         target = pub.repository
1191                                         if not target:
1192                                                 target = publisher.Repository()
1193                                                 pub.repository = target
1194 
1195                                 for pname, val in sprops.iteritems():
1196                                         attrname = pname.replace("-", "_")
1197                                         pval = getattr(target, attrname)
1198                                         if isinstance(pval, list) and \
1199                                             not isinstance(val, list):
1200                                                 # If the target property expects
1201                                                 # a list, transform the provided
1202                                                 # value into one if it isn't
1203                                                 # already.
1204                                                 if val == "":
1205                                                         val = []
1206                                                 else:
1207                                                         val = [val]
1208                                         setattr(target, attrname, val)
1209                 except apx.ApiException, e:
1210                         failed.append((pfx, e))
1211                         continue
1212 
1213                 if new_pub:
1214                         repo.add_publisher(pub)
1215                 else:
1216                         repo.update_publisher(pub)
1217 
1218         if failed:
1219                 for pfx, details in failed:
1220                         error(_("Unable to set properties for publisher "
1221                             "'%(pfx)s':\n%(details)s") % locals())
1222                 if len(failed) &lt; len(pubs):
1223                         return EXIT_PARTIAL
1224                 return EXIT_OOPS
1225         return EXIT_OK
1226 
1227 
1228 def _set_repo(conf, subcommand, props, repo):
1229         """Set repository properties."""
1230 
1231         # Set properties.
1232         for sname, props in props.iteritems():
1233                 for pname, val in props.iteritems():
1234                         repo.cfg.set_property(sname, pname, val)
1235         repo.write_config()
1236 
1237         return EXIT_OK
1238 
1239 
1240 def subcmd_version(conf, args):
1241         """Display the version of the pkg(5) API."""
1242 
1243         subcommand = "version"
1244         if args:
1245                 usage(_("command does not take operands"), cmd=subcommand)
1246         msg(pkg.VERSION)
1247         return EXIT_OK
1248 
1249 
1250 verify_error_header = None
1251 verify_warning_header = None
1252 verify_reason_headers = None
1253 
1254 def __load_verify_msgs():
1255         """Since our gettext isn't loaded we need to ensure our globals have
1256         correct content by calling this method.  These values are used by both
1257         fix when in verbose mode, and verify"""
1258 
1259         global verify_error_header
1260         global verify_warning_header
1261         global verify_reason_headers
1262 
1263         # A map of error detail types to the human-readable description of each
1264         # type.  These correspond to keys in the dictionary returned by
1265         # sr.Repository.verify(..)
1266         verify_reason_headers = {
1267             "path": _("Repository path"),
1268             "actual": _("Computed hash"),
1269             "fpath": _("Path"),
1270             "permissionspath": _("Path"),
1271             "pkg": _("Package"),
1272             "err": _("Detail")
1273         }
1274 
1275         verify_error_header = _("ERROR")
1276         verify_warning_header = _("WARNING")
1277 
1278 
1279 def __fmt_verify(verify_tuple):
1280         """Format a verify_tuple, of the form (error, path, message, reason)
1281         returning a formatted error message, and an FMRI indicating what
1282         packages within the repository are affected. Note that the returned FMRI
1283         may not be valid, in which case a path to the broken manifest in the
1284         repository is returned instead."""
1285 
1286         error, path, message, reason = verify_tuple
1287 
1288         formatted_message = "%(error_type)16s: %(message)s\n" % \
1289             {"error_type": verify_error_header, "message": message}
1290         reason["path"] = path
1291 
1292         if error == sr.REPO_VERIFY_BADMANIFEST:
1293                 reason_keys = ["path", "err"]
1294         elif error in [sr.REPO_VERIFY_PERM, sr.REPO_VERIFY_MFPERM]:
1295                 reason_keys = ["pkg", "path"]
1296         elif error == sr.REPO_VERIFY_BADHASH:
1297                 reason_keys = ["pkg", "path", "actual", "fpath"]
1298         elif error == sr.REPO_VERIFY_UNKNOWN:
1299                 reason_keys = ["path", "err"]
1300         elif error == sr.REPO_VERIFY_BADSIG:
1301                 reason_keys = ["pkg", "path", "err"]
1302         elif error == sr.REPO_VERIFY_WARN_OPENPERMS:
1303                 formatted_message = \
1304                     "%(error_type)16s: %(message)s\n" % \
1305                     {"error_type": verify_warning_header, "message": message}
1306                 reason_keys = ["permissionspath", "err"]
1307         else:
1308                 # A list of the details we provide.  Some error codes
1309                 # have different details associated with them.
1310                 reason_keys = ["pkg", "path", "fpath"]
1311 
1312 
1313         # the detailed error message can be long, so we'll wrap it.  If what we
1314         # have fits on a single line, use it, otherwise begin displaying the
1315         # message on the next line.
1316         if "err" in reason_keys:
1317                 err_str = ""
1318                 lines = textwrap.wrap(reason["err"])
1319                 if len(lines) != 1:
1320                         for line in lines:
1321                                 err_str += "%18s\n" % line
1322                         reason["err"] = "\n" + err_str.rstrip()
1323                 else:
1324                         reason["err"] = lines[0]
1325 
1326         for key in reason_keys:
1327                 # sometimes we don't have the key we want, for example we may
1328                 # not have a file path from the package if the error is a
1329                 # missing repository file for a 'license' action (which don't
1330                 # have 'path' attributes, hence no 'fpath' dictionary entry)
1331                 if key not in reason:
1332                         continue
1333                 formatted_message += "%(key)16s: %(value)s\n" % \
1334                     {"key": verify_reason_headers[key], "value": reason[key]}
1335 
1336         formatted_message += "\n"
1337 
1338         if error == sr.REPO_VERIFY_WARN_OPENPERMS:
1339                 return formatted_message, None
1340         elif "pkg" in reason:
1341                 return formatted_message, reason["pkg"]
1342         return formatted_message, reason["path"]
1343 
1344 
1345 def subcmd_verify(conf, args):
1346         """Verify the repository content (file and manifest content only)."""
1347 
1348         subcommand = "verify"
1349         __load_verify_msgs()
1350 
1351         opts, pargs = getopt.getopt(args, "p:s:")
1352         pubs = set()
1353         for opt, arg in opts:
1354                 if opt == "-s":
1355                         conf["repo_uri"] = parse_uri(arg)
1356                 if opt == "-p":
1357                         if not misc.valid_pub_prefix(arg):
1358                                 error(_("Invalid publisher prefix '%s'") % arg,
1359                                     cmd=subcommand)
1360                         pubs.add(arg)
1361 
1362         if pargs:
1363                 usage(_("command does not take operands"), cmd=subcommand)
1364 
1365         repo_uri = conf.get("repo_uri", None)
1366         if not repo_uri:
1367                 usage(_("A package repository location must be provided "
1368                     "using -s."), cmd=subcommand)
1369 
1370         if repo_uri.scheme != "file":
1371                 usage(_("Network repositories are not currently supported "
1372                     "for this operation."), cmd=subcommand)
1373 
1374         xport, xpub, tmp_dir = setup_transport(conf, subcommand=subcommand)
1375         rval, found, pub_data = _get_matching_pubs(subcommand, pubs, xport,
1376             xpub)
1377         if rval == EXIT_OOPS:
1378                 return rval
1379 
1380         logger.info("Initiating repository verification.")
1381         bad_fmris = set()
1382         for pfx in found:
1383                 progtrack = get_tracker()
1384                 repo = sr.Repository(root=repo_uri.get_pathname())
1385                 xpub.prefix = pfx
1386                 xpub.transport = xport
1387                 for verify_tuple in repo.verify(pub=xpub, progtrack=progtrack):
1388                         message, bad_fmri = __fmt_verify(verify_tuple)
1389                         if bad_fmri:
1390                                 bad_fmris.add(bad_fmri)
1391                         progtrack.repo_verify_yield_error(bad_fmri, message)
1392         if bad_fmris:
1393                 return EXIT_OOPS
1394         return EXIT_OK
1395 
1396 
1397 def subcmd_fix(conf, args):
1398         """Fix the repository content (file and manifest content only)
1399         For index and catalog content corruption, a rebuild should be
1400         performed."""
1401 
1402         subcommand = "fix"
1403         __load_verify_msgs()
1404         verbose = False
1405 
1406         opts, pargs = getopt.getopt(args, "vp:s:")
1407         pubs = set()
1408         for opt, arg in opts:
1409                 if opt == "-s":
1410                         conf["repo_uri"] = parse_uri(arg)
1411                 if opt == "-v":
1412                         verbose = True
1413                 if opt == "-p":
1414                         if not misc.valid_pub_prefix(arg):
1415                                 error(_("Invalid publisher prefix '%s'") % arg,
1416                                     cmd=subcommand)
1417                         pubs.add(arg)
1418 
1419         if pargs:
1420                 usage(_("command does not take operands"), cmd=subcommand)
1421 
1422         repo_uri = conf.get("repo_uri", None)
1423         if not repo_uri:
1424                 usage(_("A package repository location must be provided "
1425                     "using -s."), cmd=subcommand)
1426 
1427         if repo_uri.scheme != "file":
1428                 usage(_("Network repositories are not currently supported "
1429                     "for this operation."), cmd=subcommand)
1430 
1431         xport, xpub, tmp_dir = setup_transport(conf, subcommand=subcommand)
1432         rval, found, pub_data = _get_matching_pubs(subcommand, pubs, xport,
1433             xpub)
1434         if rval == EXIT_OOPS:
1435                 return rval
1436 
1437         logger.info("Initiating repository fix.")
1438         progtrack = get_tracker()
1439 
1440         def verify_cb(tracker, verify_tuple):
1441                 """A method passed to sr.Repository.fix(..) to emit verify
1442                 messages if verbose mode is enabled."""
1443                 if not verbose:
1444                         return
1445                 formatted_message, bad_fmri = __fmt_verify(verify_tuple)
1446                 tracker.repo_verify_yield_error(bad_fmri, formatted_message)
1447 
1448         repo = sr.Repository(root=repo_uri.get_pathname())
1449         broken_fmris = set()
1450         failed_fix_paths = set()
1451         for pfx in found:
1452                 xpub.prefix = pfx
1453                 xpub.transport = xport
1454                 progtrack = get_tracker()
1455                 for status_code, path, message, reason in \
1456                     repo.fix(pub=xpub, progtrack=progtrack,
1457                         verify_callback=verify_cb):
1458                         if status_code == sr.REPO_FIX_ITEM:
1459                                 # When we can't get the FMRI, eg. in the case
1460                                 # of a corrupt manifest, use the path instead.
1461                                 fmri = reason["pkg"]
1462                                 if not fmri:
1463                                         fmri = path
1464                                 broken_fmris.add(fmri)
1465                                 if verbose:
1466                                         progtrack.repo_fix_yield_info(fmri,
1467                                             message)
1468                         else:
1469                                 failed_fix_paths.add(path)
1470 
1471         progtrack.flush()
1472         logger.info("")
1473 
1474         if broken_fmris:
1475                 logger.info(_("Use pkgsend(1) or pkgrecv(1) to republish the\n"
1476                     "following packages or paths which were quarantined:\n\n\t"
1477                     "%s") % \
1478                     "\n\t".join([str(f) for f in broken_fmris]))
1479         if failed_fix_paths:
1480                 logger.info(_("\npkgrepo could not repair the following paths "
1481                     "in the repository:\n\n\t%s") %
1482                     "\n\t".join([p for p in failed_fix_paths]))
1483 
1484         if not (broken_fmris or failed_fix_paths):
1485                 logger.info(_("No repository fixes required."))
1486         else:
1487                 logger.info(_("Repository repairs completed."))
1488 
1489         if failed_fix_paths:
1490                 return EXIT_OOPS
1491         return EXIT_OK
1492 
1493 
1494 def main_func():
1495         global_settings.client_name = PKG_CLIENT_NAME
1496 
1497         try:
1498                 opts, pargs = getopt.getopt(sys.argv[1:], "s:D:?",
1499                     ["help", "debug="])
1500         except getopt.GetoptError, e:
1501                 usage(_("illegal global option -- %s") % e.opt)
1502 
1503         conf = {}
1504         show_usage = False
1505         for opt, arg in opts:
1506                 if opt == "-s":
1507                         conf["repo_uri"] = parse_uri(arg)
1508                 elif opt in ("--help", "-?"):
1509                         show_usage = True
1510                 elif opt == "-D" or opt == "--debug":
1511                         try:
1512                                 key, value = arg.split("=", 1)
1513                         except (AttributeError, ValueError):
1514                                 usage(_("%(opt)s takes argument of form "
1515                                    "name=value, not %(arg)s") % {
1516                                    "opt":  opt, "arg": arg })
1517                         DebugValues.set_value(key, value)
1518 
1519         subcommand = None
1520         if pargs:
1521                 subcommand = pargs.pop(0)
1522                 if subcommand == "help":
1523                         show_usage = True
1524 
1525         if show_usage:
1526                 usage(retcode=0, full=True)
1527         elif not subcommand:
1528                 usage(_("no subcommand specified"))
1529 
1530         subcommand = subcommand.replace("-", "_")
1531         func = globals().get("subcmd_%s" % subcommand, None)
1532         if not func:
1533                 subcommand = subcommand.replace("_", "-")
1534                 usage(_("unknown subcommand '%s'") % subcommand)
1535 
1536         try:
1537                 return func(conf, pargs)
1538         except getopt.GetoptError, e:
1539                 if e.opt in ("help", "?"):
1540                         usage(full=True)
1541                 usage(_("illegal option -- %s") % e.opt, cmd=subcommand)
1542 
1543 
1544 #
1545 # Establish a specific exit status which means: "python barfed an exception"
1546 # so that we can more easily detect these in testing of the CLI commands.
1547 #
1548 def handle_errors(func, *args, **kwargs):
1549         """Catch exceptions raised by the main program function and then print
1550         a message and/or exit with an appropriate return code.
1551         """
1552 
1553         traceback_str = misc.get_traceback_message()
1554 
1555         try:
1556                 # Out of memory errors can be raised as EnvironmentErrors with
1557                 # an errno of ENOMEM, so in order to handle those exceptions
1558                 # with other errnos, we nest this try block and have the outer
1559                 # one handle the other instances.
1560                 try:
1561                         __ret = func(*args, **kwargs)
1562                 except (MemoryError, EnvironmentError), __e:
1563                         if isinstance(__e, EnvironmentError) and \
1564                             __e.errno != errno.ENOMEM:
1565                                 raise
1566                         error("\n" + misc.out_of_memory())
1567                         __ret = EXIT_OOPS
1568         except SystemExit, __e:
1569                 raise __e
1570         except (IOError, PipeError, KeyboardInterrupt), __e:
1571                 # Don't display any messages here to prevent possible further
1572                 # broken pipe (EPIPE) errors.
1573                 if isinstance(__e, IOError) and __e.errno != errno.EPIPE:
1574                         error(str(__e))
1575                 __ret = EXIT_OOPS
1576         except apx.VersionException, __e:
1577                 error(_("The pkgrepo command appears out of sync with the "
1578                     "libraries provided\nby pkg:/package/pkg. The client "
1579                     "version is %(client)s while the library\nAPI version is "
1580                     "%(api)s.") % {'client': __e.received_version,
1581                      'api': __e.expected_version
1582                     })
1583                 __ret = EXIT_OOPS
1584         except apx.BadRepositoryURI, __e:
1585                 error(str(__e))
1586                 __ret = EXIT_BADOPT
1587         except (apx.ApiException, sr.RepositoryError), __e:
1588                 error(str(__e))
1589                 __ret = EXIT_OOPS
1590         except:
1591                 traceback.print_exc()
1592                 error(traceback_str)
1593                 __ret = 99
1594         return __ret
1595 
1596 
1597 if __name__ == "__main__":
1598         misc.setlocale(locale.LC_ALL, "", error)
1599         gettext.install("pkg", "/usr/share/locale",
1600             codeset=locale.getpreferredencoding())
1601 
1602         # Make all warnings be errors.
1603         warnings.simplefilter('error')
1604 
1605         __retval = handle_errors(main_func)
1606         try:
1607                 logging.shutdown()
1608         except IOError:
1609                 # Ignore python's spurious pipe problems.
1610                 pass
1611         sys.exit(__retval)
<a name="4" id="anc4"></a><b style="font-size: large; color: red">--- EOF ---</b>















































































</pre><form name="eof"><input name="value" value="4" type="hidden"></input></form></body></html>
 
 
Close
loading
Please Confirm
Close