pkgrepo.py revision 2142
#
# 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", )
# globals
tmpdirs = []
import atexit
import copy
import errno
import getopt
import gettext
import locale
import logging
import os
import shlex
import shutil
import sys
import tempfile
import traceback
import warnings
import pkg
orig_cwd = None
def cleanup():
"""To be called at program finish."""
for d in tmpdirs:
"""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 [--version] uri_or_path
pkgrepo add-publisher [-s repo_uri_or_path] publisher ...
pkgrepo add-signing-ca-cert [-p publisher ...]
[-s repo_uri_or_path] path ...
pkgrepo add-signing-intermediate-cert [-p publisher ...]
[-s repo_uri_or_path] path ...
pkgrepo get [-F format] [-p publisher ...] [-s repo_uri_or_path]
pkgrepo info [-F format] [-H] [-p publisher ...]
[-s repo_uri_or_path]
pkgrepo rebuild [-p publisher ...] [-s repo_uri_or_path]
[--no-catalog] [--no-index]
pkgrepo refresh [-p publisher ...] [-s repo_uri_or_path]
[--no-catalog] [--no-index]
pkgrepo remove-signing-ca-cert [-p publisher ...]
[-s repo_uri_or_path] hash ...
pkgrepo remove-signing-intermediate-cert [-p publisher ...]
[-s repo_uri_or_path] hash ...
pkgrepo set [-p publisher ...] [-s repo_uri_or_path]
pkgrepo help
pkgrepo version
Options:
--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.
"""
if opt == "-p":
elif opt == "-s":
# Get repository object.
usage(_("A package repository location must be provided "
usage(_("At least one path to a certificate must be provided."))
failed = []
if orig_cwd:
else:
try:
if "all" in pubs:
# Default to list of all publishers.
if not pubs:
# Assume default publisher or older repository.
else:
# Add for each publisher specified.
"""Add the provided signing ca certificates to the repository for
the given publisher."""
subcommand = "add-signing-ca-cert"
if failed:
error(_("Unable to add signing ca certificates for "
"publisher '%(pfx)s':\n%(details)s") % locals(),
return EXIT_PARTIAL
return EXIT_OOPS
return EXIT_OK
subcommand = "add-signing-intermediate-cert"
if failed:
if pfx:
error(_("Unable to add signing intermediate "
"certificates for publisher '%(pfx)s':\n"
else:
error(_("Unable to add signing intermediate "
"certificates:\n%(details)s") % locals(),
return EXIT_PARTIAL
return EXIT_OOPS
return EXIT_OK
if opt == "-p":
elif opt == "-s":
# Get repository object.
usage(_("A package repository location must be provided "
usage(_("At least one certificate hash must be provided."))
failed = []
def remove_certs(pfx=None):
try:
if "all" in pubs:
# Default to list of all publishers.
if not pubs:
# Assume default publisher or older repository.
else:
# Add for each publisher specified.
subcommand = "remove-signing-ca-cert"
if failed:
error(_("Unable to remove signing ca certificates for "
"publisher '%(pfx)s':\n%(details)s") % locals(),
return EXIT_PARTIAL
return EXIT_OOPS
return EXIT_OK
subcommand = "remove-signing-intermediate-cert"
if failed:
if pfx:
error(_("Unable to remove signing intermediate "
"certificates for publisher '%(pfx)s':\n"
else:
error(_("Unable to remove signing intermediate "
"certificates:\n%(details)s") % locals(),
return EXIT_PARTIAL
return EXIT_OOPS
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]
def quote_value(val):
if out_format == "tsv":
# Expand tabs if tsv output requested.
# Escape bourne shell metacharacters.
for c in ("\\", " ", "\t", "\n", "'", "`", ";", "&", "(", ")",
"|", "^", "<", ">"):
return nval
elif val == "":
return
elif val is None:
return
else:
nval = []
for v in val:
if v == "":
# Indicate empty string value using "".
elif v is None:
# Indicate no value using empty string.
else:
# Otherwise, escape the value to be displayed.
nval = None
if multi_value:
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)
))
"""Return the repository object for current program configuration."""
usage(_("Network repositories are not currently supported "
if not path:
# Bad URI?
if not repo_uri:
# Create transport and transport config.
# Configure target publisher.
"""Add publisher(s) to the specified repository."""
subcommand = "add-publisher"
version = None
if opt == "-s":
if not repo_uri:
usage(_("Network repositories are not currently supported "
if not pargs:
usage(_("At least one publisher must be specified"),
if abort:
return EXIT_OOPS
# Elide the publishers that already exist, but retain the order
# publishers were specified in.
new_pubs = [
]
# Tricky logic; _set_pub will happily add new publishers if necessary
# and not set any properties if you didn't specify any.
if make_default:
# No publisher existed previously, so set the default publisher
# to be the first new one that was added.
# Some of the publishers that were requested for addition
# were already known.
error(_("specified publisher(s) already exist: %s") %
if new_pubs:
return EXIT_PARTIAL
return EXIT_OOPS
return rval
"""Create a package repository at the given location."""
subcommand = "create"
version = None
if opt == "-s":
elif opt == "--version":
# This option is currently private and allows creating a
# repository with a specific format based on version.
try:
except ValueError:
usage(_("Version must be an integer value."),
usage(_("Only one repository location may be specified."),
elif pargs:
if not repo_uri:
usage(_("Network repositories are not currently supported "
# Attempt to create a repository at the specified location. Allow
# whatever exceptions are raised to bubble up.
return EXIT_OK
"""Display repository properties."""
subcommand = "get"
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":
elif opt == "-p":
elif opt == "-s":
# Setup transport so configuration can be retrieved.
usage(_("A package repository location must be provided "
# Get properties.
if pubs:
"""Display repository properties."""
# Configuration index is indexed by section name and property name.
# Retrieve and flatten it to simplify listing process.
# Set minimum widths for section and property name columns by using the
# length of the column headers.
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
# Retrieve publisher information.
else:
# Establish initial return value and perform early exit if appropriate.
if out_format == "default":
# Don't pollute other output formats.
error(_("no matching publishers found"),
return EXIT_OOPS, None, None
pargs):
"""Display publisher properties."""
return rval
# Set minimum widths for section and property name columns by using the
# length of the column headers and data.
))
# For each requested publisher, retrieve the requested property data.
pub_idx = {}
continue
"publisher": {
},
}
if pub_repo:
}
else:
"collection-type": "core",
"description": "",
"legal-uris": [],
"mirrors": [],
"name": "",
"origins": [],
"refresh-seconds": "",
"registration-uri": "",
"related-uris": [],
}
# Determine possible set of properties and lengths.
# Determine properties to display.
del props
else:
def gen_listing():
yield {
"publisher": pub,
"section": sname,
"property": pname,
"value": sval,
}
# PUBLISHER SECTION PROPERTY VALUE
# <pub_1> <sec_1> <prop_1> <prop_1_value>
# <pub_1> <sec_2> <prop_2> <prop_2_value>
# ...
field_data = {
"publisher" : [("default", "tsv"), _("PUBLISHER"), ""],
"section" : [("default", "tsv"), _("SECTION"), ""],
"property" : [("default", "tsv"), _("PROPERTY"), ""],
"value" : [("default", "tsv"), _("VALUE"), ""],
}
_("VALUE"))
# Default output formatting.
if out_format == "default":
# Don't pollute other output formats.
error(_("no matching properties found"),
return rval
"""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 = "info"
out_format = "default"
if opt == "-F":
if arg not in LISTING_FORMATS:
usage(_("Unrecognized format %(format)s."
" Supported formats: %(valid)s") % \
{ "format": arg,
return EXIT_OOPS
elif opt == "-H":
elif opt == "-p":
elif opt == "-s":
if pargs:
# Setup transport so status can be retrieved.
usage(_("A package repository location must be provided "
# Retrieve repository status information.
else:
def gen_listing():
if last_update:
# Reformat the date into something more user
# friendly (and locale specific).
yield {
"publisher": pfx,
"packages": pkg_count,
"status": rstatus,
"updated": last_update,
}
# PUBLISHER PACKAGES STATUS UPDATED
# <pub_1> <num_uniq_pkgs> <status> <cat_last_modified>
# <pub_2> <num_uniq_pkgs> <status> <cat_last_modified>
# ...
field_data = {
"publisher" : [("default", "tsv"), _("PUBLISHER"), ""],
"packages" : [("default", "tsv"), _("PACKAGES"), ""],
"status" : [("default", "tsv"), _("STATUS"), ""],
"updated" : [("default", "tsv"), _("UPDATED"), ""],
}
_("UPDATED"))
# Default output formatting.
))
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 == "-p":
elif opt == "-s":
elif opt == "--no-catalog":
elif opt == "--no-index":
if pargs:
if not build_catalog and not build_index:
# Why? Who knows; but do what was requested--nothing!
return EXIT_OK
# Setup transport so operation can be performed.
usage(_("A package repository location must be provided "
if build_catalog and build_index:
elif build_catalog:
elif build_index:
xpub)
return rval
return rval
"""Refresh the repository's catalog and index data (as permitted)."""
subcommand = "refresh"
if opt == "-p":
elif opt == "-s":
elif 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
# Setup transport so operation can be performed.
usage(_("A package repository location must be provided "
if add_content and refresh_index:
elif add_content:
elif refresh_index:
xpub)
return rval
return rval
"""Set repository properties."""
subcommand = "set"
if opt == "-p":
elif opt == "-s":
props = {}
if not pargs:
else:
try:
# Attempt to parse property into components.
# Store property values by section.
# Parse the property value into a list if
# necessary, otherwise append it to the list
# of values for the property.
# Determine if previous value is already
# a list, and if not, convert and append
# the value.
else:
else:
# Otherwise, just store the value.
except ValueError:
break
if bad_args:
usage(_("a property name and value must be provided in the "
# Get repository object.
usage(_("A package repository location must be provided "
# Set properties.
if pubs:
"""Set publisher properties."""
usage(_("unknown property section "
usage(_("'%s' may not be set using "
"this command" % pname))
usage(_("unknown property '%s'") %
if "all" in pubs:
# Default to list of all publishers.
if not pubs:
# If there are still no known publishers, this
# operation cannot succeed, so fail now.
usage(_("One or more publishers must be specified to "
"create and set properties for as none exist yet."),
# Get publishers and update properties.
failed = []
try:
# Get a copy of the existing publisher.
except sr.RepositoryUnknownPublisher, e:
except sr.RepositoryError, e:
continue
try:
if sname == "publisher":
elif sname == "repository":
if not target:
# If the target property expects
# a list, transform the provided
# value into one if it isn't
# already.
if val == "":
val = []
else:
except apx.ApiException, e:
continue
if new_pub:
else:
if failed:
error(_("Unable to set properties for publisher "
"'%(pfx)s':\n%(details)s") % locals())
return EXIT_PARTIAL
return EXIT_OOPS
return EXIT_OK
"""Set repository properties."""
# Set properties.
return EXIT_OK
"""Display the version of the pkg(5) API."""
subcommand = "version"
if args:
return EXIT_OK
def main_func():
global orig_cwd
try:
except OSError, e:
try:
orig_cwd = None
except KeyError:
orig_cwd = None
try:
["help"])
except getopt.GetoptError, e:
conf = {}
if opt == "-s":
subcommand = None
if pargs:
if subcommand == "help":
if show_usage:
elif not subcommand:
usage(_("no subcommand specified"))
if not func:
try:
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
This is an internal error in pkg(5) version %(version)s. Please let the
developers know about this problem by including the information above (and
this message) when filing a bug at:
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