pkgrepo.py revision 2026
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
#
PKG_CLIENT_NAME = "pkgrepo"
# pkgrepo exit codes
EXIT_OK = 0
EXIT_OOPS = 1
EXIT_BADOPT = 2
EXIT_PARTIAL = 3
# listing constants
LISTING_FORMATS = ("tsv", )
import errno
import getopt
import gettext
import locale
import logging
import os
import sys
import urllib
import urlparse
import warnings
import pkg
import shlex
import traceback
"""Emit an error message prefixed by the command name """
if cmd:
pkg_cmd = "pkgrepo "
else:
pkg_cmd = "pkgrepo: "
# If we get passed something like an Exception, we can convert
# it down to a string.
# If the message starts with whitespace, assume that it should come
# *before* the command-name prefix.
# This has to be a constant value as we can't reliably get our actual
# program name on all platforms.
"""Emit a usage message and optionally prefix it with a more
specific error message. Causes program to exit.
"""
if usage_error:
if not full:
# The full usage message isn't desired.
"information."))
msg(_("""\
Usage:
pkgrepo [options] command [cmd_options] [operands]
Subcommands:
pkgrepo create uri_or_path
pkgrepo publisher [-F format] [-H] [publisher ...]
pkgrepo rebuild [--no-index]
pkgrepo refresh [--no-catalog] [--no-index]
pkgrepo add-signing-ca-cert path ...
pkgrepo add-signing-intermediate-cert path ...
pkgrepo remove-signing-ca-cert hash ...
pkgrepo remove-signing-intermediate-cert hash ...
pkgrepo version
pkgrepo help
Options:
-s repo_uri_or_path
A URI or filesystem path representing the location of a package
repository. Currently, only filesystem-based repositories are
supported.
--help or -?
Displays a usage message."""))
class OptionError(Exception):
"""Option exception. """
"""Parse the repository location provided and attempt to transform it
into a valid repository URI.
"""
# Convert the file path to a URI.
if scheme != "file":
usage(_("Network repositories are not currently supported."),
retcode=1)
if scheme == "file":
# During urlunparsing below, ensure that the path starts with
# only one '/' character, if any are present.
# Rebuild the url with the sanitized components.
"""Return the repository object for current program configuration."""
if not path:
# Bad URI?
subcommand = "add-signing-ca-cert"
usage(_("At least one path to a certificate must be provided."))
subcommand = "add-signing-intermediate-cert"
usage(_("At least one path to a certificate must be provided."))
subcommand = "remove-signing-ca-cert"
usage(_("At least one certificate hash must be provided."))
subcommand = "remove-signing-intermediate-cert"
usage(_("At least one certificate hash must be provided."))
"""Create a package repository at the given location."""
subcommand = "create"
usage(_("Only one repository location may be specified."),
elif pargs:
if not repo_root:
# Attempt to create a repository at the specified location. Allow
# whatever exceptions are raised to bubble up.
return EXIT_OK
"""Print a columnar listing defined by provided values."""
# Custom sort function for preserving field ordering
# Functions for manipulating field_data records
def filter_default(record):
def filter_tsv(record):
def get_header(record):
return record[1]
return record[2]
if out_format == "default":
# Create a formatting string for the default output
# format.
elif out_format == "tsv":
# Create a formatting string for the tsv output
# format.
# Extract the list of headers from the field_data dictionary. Ensure
# they are extracted in the desired order by using the custom sort
# function.
# Output a header if desired.
if not omit_headers:
for entry in field_values:
(field_data[f], v)
))
"""Display the list of properties for the repository."""
subcommand = "property"
out_format = "default"
if opt == "-F":
if out_format not in LISTING_FORMATS:
usage(_("Unrecognized format %(format)s."
" Supported formats: %(valid)s") % \
{ "format": out_format,
return EXIT_OOPS
elif opt == "-H":
# Configuration index is indexed by section name and property name.
# Flatten it to simplify listing process.
# Set minimum widths for section and property name columns by using the
# length of the column headers.
del cfg_idx
del props
else:
def gen_listing():
yield {
"section": sname,
"property": pname,
"value": sval,
}
# SECTION PROPERTY VALUE
# <sec_1> <prop_1> <prop_1_value>
# <sec_2> <prop_2> <prop_2_value>
# ...
field_data = {
"section" : [("default", "tsv"), _("SECTION"), ""],
"property" : [("default", "tsv"), _("PROPERTY"), ""],
"value" : [("default", "tsv"), _("VALUE"), ""],
}
# Default output formatting.
"s %s"
return EXIT_PARTIAL
if out_format == "default":
# Don't pollute other output formats.
error(_("no matching properties found"),
return EXIT_OOPS
return EXIT_OK
"""Set a repository property."""
subcommand = "property"
out_format = "default"
else:
try:
except ValueError:
if bad_args:
usage(_("a property name and value must be provided in the "
"""Display a list of known publishers and a summary of known packages
and when the package data for the given publisher was last updated.
"""
subcommand = "publisher"
out_format = "default"
if opt == "-F":
if out_format not in LISTING_FORMATS:
usage(_("Unrecognized format %(format)s."
" Supported formats: %(valid)s") % \
{ "format": out_format,
return EXIT_OOPS
elif opt == "-H":
pub_idx = {}
else:
def gen_listing():
yield {
"publisher": pfx,
"packages": pkg_count,
"versions": pkg_ver_count,
}
# PUBLISHER PACKAGES VERSIONS UPDATED
# <pub_1> <num_uniq_pkgs> <num_pkg_vers> <cat_last_modified>
# <pub_2> <num_uniq_pkgs> <num_pkg_vers> <cat_last_modified>
# ...
field_data = {
"publisher" : [("default", "tsv"), _("PUBLISHER"), ""],
"packages" : [("default", "tsv"), _("PACKAGES"), ""],
"versions" : [("default", "tsv"), _("VERSIONS"), ""],
"updated" : [("default", "tsv"), _("UPDATED"), ""],
}
_("UPDATED"))
# Default output formatting.
def_fmt = "%-24s %-8s %-8s %s"
return EXIT_PARTIAL
if out_format == "default":
# Don't pollute other output formats.
error(_("no matching publishers found"),
return EXIT_OOPS
return EXIT_OK
"""Rebuild the repository's catalog and index data (as permitted)."""
subcommand = "rebuild"
if opt == "--no-index":
if pargs:
if build_index:
# Always build search indexes seperately (and if permitted).
return EXIT_OK
"""Refresh the repository's catalog and index data (as permitted)."""
subcommand = "refresh"
if opt == "--no-catalog":
elif opt == "--no-index":
if pargs:
if not add_content and not refresh_index:
# Why? Who knows; but do what was requested--nothing!
return EXIT_OK
if add_content:
if refresh_index:
# Always update search indexes separately (and if permitted).
return EXIT_OK
"""Display the version of the pkg(5) API."""
subcommand = "version"
usage(_("-s not allowed for %s subcommand") %
if args:
return EXIT_OK
def main_func():
try:
["help"])
except getopt.GetoptError, e:
conf = {}
if opt == "-s":
if not arg:
continue
subcommand = None
if pargs:
if subcommand == "help":
if show_usage:
elif not subcommand:
usage(_("no subcommand specified"))
if not func:
try:
usage(_("A package repository location must be "
except getopt.GetoptError, e:
#
# Establish a specific exit status which means: "python barfed an exception"
# so that we can more easily detect these in testing of the CLI commands.
#
"""Catch exceptions raised by the main program function and then print
"""
traceback_str = _("\n\nThis is an internal error. Please let the "
"developers know about this\nproblem by filing a bug at "
"http://defect.opensolaris.org and including the\nabove "
"traceback and this message. The version of pkg(5) is "
try:
# Out of memory errors can be raised as EnvironmentErrors with
# an errno of ENOMEM, so in order to handle those exceptions
# with other errnos, we nest this try block and have the outer
# one handle the other instances.
try:
raise
except SystemExit, __e:
raise __e
except (PipeError, KeyboardInterrupt):
# Don't display any messages here to prevent possible further
# broken pipe (EPIPE) errors.
error(_("The pkgrepo command appears out of sync with the "
"version is %(client)s while the library\nAPI version is "
})
except:
__ret = 99
return __ret
if __name__ == "__main__":
# Make all warnings be errors.
try:
except IOError:
# Ignore python's spurious pipe problems.
pass