--- a/doc/api_versions.txt Fri Nov 07 16:55:36 2008 -0800
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,196 +0,0 @@
-Version 5:
-Compatible with clients using versions 1-4 as long as they have a generic
-APIException. This is the case for PackageManager and UpdateManaget.
-Changes:
-plan_install and plan_update_all can now raise PermissionsException.
-
-Version 4:
-Compatible with clients using versions 1, 2, and 3
-Changes:
-Modifies where certain progress tracking calls were made, calling
- evaluate_start much sooner.
-Adds refresh tracking to progress.py. This allows for active feedback when
- the catalogs of authorities are being refreshed.
-
-Version 3:
-Compatible with clients using Versions 1 and 2
-Changes:
-Adds an optional argument to info which determines whether detailed information
- about actions will be returned
-Adds the following new fields to PackageInfo objects: links, hardlinks,
- files, dirs, dependencies
-
-Version 2:
-Compatible with clients using Version 1
-Changes:
-Adds the optional argument update_index to plan_install, plan_uninstall, and
- plan_update_all. When the argument is false, no automatic update to the
- index occurs. By default, the argument is true.
-
-Version 1:
-Incompatible with clients using Version 0
-Changes:
-plan_install now returns a tuple of whether there is anything to do and
- a catalog refresh exception, if one was caught. In this, it mirrors the
- first and third return values from plan_update_all.
-
-Version 0:
- def __init__(self, img_path, version_id, progesstracker,
- cancel_state_callable, pkg_client_name):
- """Constructs an ImageInterface. img_path should point to an
- existing image. version_id indicates the version of the api
- the client is expecting to use. progesstracker is the
- progresstracker the client wants the api to use for UI
- callbacks. cancel_state_callable is a function which the client
- wishes to have called each time whether the operation can be
- canceled changes. It can raise VersionException and
- ImageNotFoundException."""
-
- def plan_install(self, pkg_list, filters, refresh_catalogs=True,
- noexecute=False, verbose=False):
- """Contructs a plan to install the packages provided in
- pkg_list. pkg_list is a list of packages to install. filters
- is a list of filters to apply to the actions of the installed
- packages. refresh_catalogs controls whether the catalogs will
- automatically be refreshed. noexecute determines whether the
- history will be recorded after planning is finished. verbose
- controls whether verbose debugging output will be printed to the
- terminal. Its existence is temporary. If there are things to do
- to complete the install, it returns True, otherwise it returns
- False. It can raise InvalidCertException, PlanCreationException,
- NetworkUnavailableException, and InventoryException. The
- noexecute argument is included for compatibility with
- operational history. The hope is it can be removed in the
- future."""
-
- def plan_uninstall(self, pkg_list, recursive_removal, noexecute=False,
- verbose=False):
- """Contructs a plan to uninstall the packages provided in
- pkg_list. pkg_list is a list of packages to install.
- recursive_removal controls whether recursive removal is
- allowed. noexecute determines whether the history will be
- recorded after planning is finished. verbose controls whether
- verbose debugging output will be printed to the terminal. Its
- existence is temporary. If there are things to do to complete
- the uninstall, it returns True, otherwise it returns False. It
- can raise NonLeafPackageException and PlanCreationException."""
-
- def plan_update_all(self, actual_cmd, refresh_catalogs=True,
- noexecute=False, force=False, pkgs_must_be_up_to_date=None,
- verbose=False):
- """Creates a plan to update all packages on the system to the
- latest known versions. actual_cmd is the command used to start
- the client. It is used to determine the image to check whether
- SUNWipkg is up to date. refresh_catalogs controls whether the
- catalogs will automatically be refreshed. noexecute determines
- whether the history will be recorded after planning is finished.
- force controls whether update should proceed even if ipkg is not
- up to date. verbose controls whether verbose debugging output
- will be printed to the terminal. Its existence is temporary. It
- returns a tuple of three things. The first is a boolean which
- tells the client whether there is anything to do. The second
- tells whether the image is an opensolaris image. The third is
- either None, or an exception which indicates partial success.
- This is currently used to indicate a failure in refreshing
- catalogs. It can raise CatalogRefreshException,
- IpkgOutOfDateException, NetworkUnavailableException, and
- PlanCreationException."""
-
- def describe(self):
- """Returns None if no plan is ready yet, otherwise returns
- a PlanDescription"""
-
- def prepare(self):
- """Takes care of things which must be done before the plan
- can be executed. This includes downloading the packages to
- disk and preparing the indexes to be updated during
- execution. It can raise ProblematicPermissionsIndexException,
- and PlanMissingException. Should only be called once a
- plan_X method has been called."""
-
- def execute_plan(self, be_name=None):
- """Executes the plan. This is uncancelable one it begins. It
- can raise CorruptedIndexException,
- ProblematicPermissionsIndexException, ImageplanStateException,
- ImageUpdateOnLiveImageException, and PlanMissingException.
- Should only be called after the prepare method has been
- called."""
-
- def refresh(self, full_refresh, auths=None):
- """Refreshes the catalogs. full_refresh controls whether to do
- a full retrieval of the catalog from the authority or only
- update the existing catalog. auths is a list of authorities to
- refresh. Passing an empty list or using the default value means
- all known authorities will be refreshed. While it currently
- returns an image object, this is an expedient for allowing
- existing code to work while the rest of the API is put into
- place."""
-
- def info(self, fmri_strings, local, get_licenses):
- """Gathers information about fmris. fmri_strings is a list
- of fmri_names for which information is desired. local
- determines whether to retrieve the information locally.
- get_licenses determines whether to retrieve the text of
- the licenses. It returns a dictionary of lists. The keys
- for the dictionary are the constants specified in the class
- definition. The values are lists of PackageInfo objects or
- strings."""
-
- def can_be_canceled(self):
- """Returns true if the API is in a cancelable state."""
-
- def reset(self):
- """Resets the API back the the initial state. Note:
- this does not necessarily return the disk to its initial state
- since the indexes or download cache may have been changed by
- the prepare method."""
-
- def cancel(self):
- """Used for asynchronous cancelation. It returns the API
- to the state it was in prior to the current method being
- invoked. Canceling during a plan phase returns the API to
- its initial state. Canceling during prepare puts the API
- into the state it was in just after planning had completed.
- Plan execution cannot be canceled. A call to this method blocks
- until the canelation has happened. Note: this does not
- necessarily return the disk to its initial state since the
- indexes or download cache may have been changed by the
- prepare method."""
-
-class PlanDescription(object):
- """A class which describes the changes the plan will make. It
- provides a list of tuples of PackageInfo's. The first item in the
- tuple is the package that is being changed. The second item in the
- tuple is the package that will be in the image after the change."""
-
- def get_changes(self):
-
-class LicenseInfo(object):
- """A class representing the license information a package
- provides."""
-
- def get_text(self):
-
-class PackageInfo(object):
- """A class capturing the information about packages that a client
- could need. The fmri is guaranteed to be set. All other values may
- be None, depending on how the PackageInfo instance was created."""
-
- # Possible package installation states
- INSTALLED = 1
- NOT_INSTALLED = 2
-
- self.pkg_stem
- self.summary
- self.state
- self.authority
- self.preferred_authority
- self.version
- self.build_release
- self.branch
- self.packaging_date
- self.size
- self.fmri
- self.licenses
-
- def __str__(self):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/client_api_versions.txt Sat Nov 08 11:08:13 2008 -0800
@@ -0,0 +1,203 @@
+Version 6:
+Compatible with clients using versions 1-5
+Changes:
+Adds a new field to PackageInfo, category_info_list, which is a list of
+ PackageCategory objects. These objects contain the scheme and category
+ information for packages.
+
+Version 5:
+Compatible with clients using versions 1-4 as long as they have a generic
+APIException. This is the case for PackageManager and UpdateManaget.
+Changes:
+plan_install and plan_update_all can now raise PermissionsException.
+
+Version 4:
+Compatible with clients using versions 1, 2, and 3
+Changes:
+Modifies where certain progress tracking calls were made, calling
+ evaluate_start much sooner.
+Adds refresh tracking to progress.py. This allows for active feedback when
+ the catalogs of authorities are being refreshed.
+
+Version 3:
+Compatible with clients using Versions 1 and 2
+Changes:
+Adds an optional argument to info which determines whether detailed information
+ about actions will be returned
+Adds the following new fields to PackageInfo objects: links, hardlinks,
+ files, dirs, dependencies
+
+Version 2:
+Compatible with clients using Version 1
+Changes:
+Adds the optional argument update_index to plan_install, plan_uninstall, and
+ plan_update_all. When the argument is false, no automatic update to the
+ index occurs. By default, the argument is true.
+
+Version 1:
+Incompatible with clients using Version 0
+Changes:
+plan_install now returns a tuple of whether there is anything to do and
+ a catalog refresh exception, if one was caught. In this, it mirrors the
+ first and third return values from plan_update_all.
+
+Version 0:
+ def __init__(self, img_path, version_id, progesstracker,
+ cancel_state_callable, pkg_client_name):
+ """Constructs an ImageInterface. img_path should point to an
+ existing image. version_id indicates the version of the api
+ the client is expecting to use. progesstracker is the
+ progresstracker the client wants the api to use for UI
+ callbacks. cancel_state_callable is a function which the client
+ wishes to have called each time whether the operation can be
+ canceled changes. It can raise VersionException and
+ ImageNotFoundException."""
+
+ def plan_install(self, pkg_list, filters, refresh_catalogs=True,
+ noexecute=False, verbose=False):
+ """Contructs a plan to install the packages provided in
+ pkg_list. pkg_list is a list of packages to install. filters
+ is a list of filters to apply to the actions of the installed
+ packages. refresh_catalogs controls whether the catalogs will
+ automatically be refreshed. noexecute determines whether the
+ history will be recorded after planning is finished. verbose
+ controls whether verbose debugging output will be printed to the
+ terminal. Its existence is temporary. If there are things to do
+ to complete the install, it returns True, otherwise it returns
+ False. It can raise InvalidCertException, PlanCreationException,
+ NetworkUnavailableException, and InventoryException. The
+ noexecute argument is included for compatibility with
+ operational history. The hope is it can be removed in the
+ future."""
+
+ def plan_uninstall(self, pkg_list, recursive_removal, noexecute=False,
+ verbose=False):
+ """Contructs a plan to uninstall the packages provided in
+ pkg_list. pkg_list is a list of packages to install.
+ recursive_removal controls whether recursive removal is
+ allowed. noexecute determines whether the history will be
+ recorded after planning is finished. verbose controls whether
+ verbose debugging output will be printed to the terminal. Its
+ existence is temporary. If there are things to do to complete
+ the uninstall, it returns True, otherwise it returns False. It
+ can raise NonLeafPackageException and PlanCreationException."""
+
+ def plan_update_all(self, actual_cmd, refresh_catalogs=True,
+ noexecute=False, force=False, pkgs_must_be_up_to_date=None,
+ verbose=False):
+ """Creates a plan to update all packages on the system to the
+ latest known versions. actual_cmd is the command used to start
+ the client. It is used to determine the image to check whether
+ SUNWipkg is up to date. refresh_catalogs controls whether the
+ catalogs will automatically be refreshed. noexecute determines
+ whether the history will be recorded after planning is finished.
+ force controls whether update should proceed even if ipkg is not
+ up to date. verbose controls whether verbose debugging output
+ will be printed to the terminal. Its existence is temporary. It
+ returns a tuple of three things. The first is a boolean which
+ tells the client whether there is anything to do. The second
+ tells whether the image is an opensolaris image. The third is
+ either None, or an exception which indicates partial success.
+ This is currently used to indicate a failure in refreshing
+ catalogs. It can raise CatalogRefreshException,
+ IpkgOutOfDateException, NetworkUnavailableException, and
+ PlanCreationException."""
+
+ def describe(self):
+ """Returns None if no plan is ready yet, otherwise returns
+ a PlanDescription"""
+
+ def prepare(self):
+ """Takes care of things which must be done before the plan
+ can be executed. This includes downloading the packages to
+ disk and preparing the indexes to be updated during
+ execution. It can raise ProblematicPermissionsIndexException,
+ and PlanMissingException. Should only be called once a
+ plan_X method has been called."""
+
+ def execute_plan(self, be_name=None):
+ """Executes the plan. This is uncancelable one it begins. It
+ can raise CorruptedIndexException,
+ ProblematicPermissionsIndexException, ImageplanStateException,
+ ImageUpdateOnLiveImageException, and PlanMissingException.
+ Should only be called after the prepare method has been
+ called."""
+
+ def refresh(self, full_refresh, auths=None):
+ """Refreshes the catalogs. full_refresh controls whether to do
+ a full retrieval of the catalog from the authority or only
+ update the existing catalog. auths is a list of authorities to
+ refresh. Passing an empty list or using the default value means
+ all known authorities will be refreshed. While it currently
+ returns an image object, this is an expedient for allowing
+ existing code to work while the rest of the API is put into
+ place."""
+
+ def info(self, fmri_strings, local, get_licenses):
+ """Gathers information about fmris. fmri_strings is a list
+ of fmri_names for which information is desired. local
+ determines whether to retrieve the information locally.
+ get_licenses determines whether to retrieve the text of
+ the licenses. It returns a dictionary of lists. The keys
+ for the dictionary are the constants specified in the class
+ definition. The values are lists of PackageInfo objects or
+ strings."""
+
+ def can_be_canceled(self):
+ """Returns true if the API is in a cancelable state."""
+
+ def reset(self):
+ """Resets the API back the the initial state. Note:
+ this does not necessarily return the disk to its initial state
+ since the indexes or download cache may have been changed by
+ the prepare method."""
+
+ def cancel(self):
+ """Used for asynchronous cancelation. It returns the API
+ to the state it was in prior to the current method being
+ invoked. Canceling during a plan phase returns the API to
+ its initial state. Canceling during prepare puts the API
+ into the state it was in just after planning had completed.
+ Plan execution cannot be canceled. A call to this method blocks
+ until the canelation has happened. Note: this does not
+ necessarily return the disk to its initial state since the
+ indexes or download cache may have been changed by the
+ prepare method."""
+
+class PlanDescription(object):
+ """A class which describes the changes the plan will make. It
+ provides a list of tuples of PackageInfo's. The first item in the
+ tuple is the package that is being changed. The second item in the
+ tuple is the package that will be in the image after the change."""
+
+ def get_changes(self):
+
+class LicenseInfo(object):
+ """A class representing the license information a package
+ provides."""
+
+ def get_text(self):
+
+class PackageInfo(object):
+ """A class capturing the information about packages that a client
+ could need. The fmri is guaranteed to be set. All other values may
+ be None, depending on how the PackageInfo instance was created."""
+
+ # Possible package installation states
+ INSTALLED = 1
+ NOT_INSTALLED = 2
+
+ self.pkg_stem
+ self.summary
+ self.state
+ self.authority
+ self.preferred_authority
+ self.version
+ self.build_release
+ self.branch
+ self.packaging_date
+ self.size
+ self.fmri
+ self.licenses
+
+ def __str__(self):
--- a/src/client.py Fri Nov 07 16:55:36 2008 -0800
+++ b/src/client.py Sat Nov 08 11:08:13 2008 -0800
@@ -87,7 +87,7 @@
from pkg.client.retrieve import CatalogRetrievalError
from pkg.client.filelist import FileListRetrievalError
-CLIENT_API_VERSION = 5
+CLIENT_API_VERSION = 6
PKG_CLIENT_NAME = "pkg"
def error(text):
@@ -1039,9 +1039,18 @@
else:
raise RuntimeError("Encountered unknown package "
"information state: %d" % pi.state )
-
- msg(_(" Name:"), pi.pkg_stem)
+ name_str = _(" Name:")
+ msg(name_str, pi.pkg_stem)
msg(_(" Summary:"), pi.summary)
+ if pi.category_info_list:
+ verbose = len(pi.category_info_list) > 1
+ msg(_(" Category:"),
+ pi.category_info_list[0].__str__(verbose))
+ if len(pi.category_info_list) > 1:
+ for ci in pi.category_info_list[1:]:
+ msg(" " * len(name_str),
+ ci.__str__(verbose))
+
msg(_(" State:"), state)
# XXX even more info on the authority would be nice?
--- a/src/modules/actions/attribute.py Fri Nov 07 16:55:36 2008 -0800
+++ b/src/modules/actions/attribute.py Sat Nov 08 11:08:13 2008 -0800
@@ -63,12 +63,13 @@
def generate_indices(self):
"""Generates the indices needed by the search dictionary."""
- if self.attrs["name"] == "info.classification":
+ if self.has_category_info():
try:
- scheme, cats = self.attrs["value"].split(":", 1)
- return {
- cats: cats.split("/") + [cats]
- }
+ return dict(
+ (all_levels, [all_levels] + all_levels.split("/"))
+ for scheme, all_levels
+ in self.parse_category_info()
+ )
except ValueError:
pass
@@ -104,3 +105,16 @@
return {
self.attrs["value"]: self.attrs["value"]
}
+
+ def has_category_info(self):
+ return self.attrs["name"] == "info.classification"
+
+ def parse_category_info(self):
+ if not self.has_category_info():
+ return []
+ else:
+ try:
+ scheme, cats = self.attrs["value"].split(":", 1)
+ return [ (scheme, cats) ]
+ except ValueError:
+ return []
--- a/src/modules/client/api.py Fri Nov 07 16:55:36 2008 -0800
+++ b/src/modules/client/api.py Sat Nov 08 11:08:13 2008 -0800
@@ -33,7 +33,7 @@
import threading
-CURRENT_API_VERSION = 5
+CURRENT_API_VERSION = 6
class ImageInterface(object):
"""This class presents an interface to images that clients may use.
@@ -68,7 +68,7 @@
canceled changes. It can raise VersionException and
ImageNotFoundException."""
- compatible_versions = set([1, 2, 3, 4, 5])
+ compatible_versions = set([1, 2, 3, 4, 5, 6])
if version_id not in compatible_versions:
raise api_errors.VersionException(CURRENT_API_VERSION,
@@ -691,9 +691,15 @@
dependencies = list(
mfst.gen_key_attribute_value_by_type(
"depend"))
+ cat_info = [
+ PackageCategory(scheme, cat)
+ for ca in mfst.gen_actions_by_type("set")
+ if ca.has_category_info()
+ for scheme, cat in ca.parse_category_info()
+ ]
pis.append(PackageInfo(pkg_stem=name, summary=summary,
- state=state,
+ category_info_list=cat_info, state=state,
authority=authority,
preferred_authority=pref_auth,
version=version.release,
@@ -810,6 +816,17 @@
def __str__(self):
return self.__text
+class PackageCategory(object):
+ def __init__(self, scheme, category):
+ self.scheme = scheme
+ self.category = category
+
+ def __str__(self, verbose=False):
+ if verbose:
+ return "%s (%s)" % (self.category, self.scheme)
+ else:
+ return "%s" % self.category
+
class PackageInfo(object):
"""A class capturing the information about packages that a client
could need. The fmri is guaranteed to be set. All other values may
@@ -819,13 +836,17 @@
INSTALLED = 1
NOT_INSTALLED = 2
- def __init__(self, pfmri, pkg_stem=None, summary=None, state=None,
- authority=None, preferred_authority=None, version=None,
- build_release=None, branch=None, packaging_date=None,
- size=None, licenses=None, links=None, hardlinks=None, files=None,
- dirs=None, dependencies=None):
+ def __init__(self, pfmri, pkg_stem=None, summary=None,
+ category_info_list=None, state=None, authority=None,
+ preferred_authority=None, version=None, build_release=None,
+ branch=None, packaging_date=None, size=None, licenses=None,
+ links=None, hardlinks=None, files=None, dirs=None,
+ dependencies=None):
self.pkg_stem = pkg_stem
self.summary = summary
+ if category_info_list is None:
+ category_info_list = []
+ self.category_info_list = category_info_list
self.state = state
self.authority = authority
self.preferred_authority = preferred_authority
--- a/src/tests/cli/t_api_info.py Fri Nov 07 16:55:36 2008 -0800
+++ b/src/tests/cli/t_api_info.py Sat Nov 08 11:08:13 2008 -0800
@@ -37,7 +37,7 @@
import pkg.client.api_errors as api_errors
import pkg.client.progress as progress
-API_VERSION = 1
+API_VERSION = 6
PKG_CLIENT_NAME = "pkg"
class TestApiInfo(testutils.SingleDepotTestCase):
@@ -56,12 +56,14 @@
pkg1 = """
open [email protected],5.11-0
add dir mode=0755 owner=root group=bin path=/bin
+ add set name=info.classification value="org.opensolaris.category.2008:Applications/Sound and Video
close
"""
pkg2 = """
open [email protected],5.11-0
add dir mode=0755 owner=root group=bin path=/bin
+ add set name=info.classification value=org.opensolaris.category.2008:System/Security/Foo/bar/Baz
close
"""
@@ -93,6 +95,7 @@
self.assert_(pis[0].pkg_stem == 'jade')
self.assert_(len(notfound) == 2)
self.assert_(len(illegals) == 0)
+ self.assert_(len(pis[0].category_info_list) == 1)
ret = api_obj.info(["j*"], local, get_license)
pis = ret[api.ImageInterface.INFO_FOUND]
@@ -114,6 +117,7 @@
illegals = ret[api.ImageInterface.INFO_ILLEGALS]
self.assert_(len(pis) == 1)
self.assert_(pis[0].state == api.PackageInfo.INSTALLED)
+ self.assert_(len(pis[0].category_info_list) == 1)
ret = api_obj.info(["turquoise"], local, get_license)
pis = ret[api.ImageInterface.INFO_FOUND]
@@ -121,6 +125,7 @@
illegals = ret[api.ImageInterface.INFO_ILLEGALS]
self.assert_(len(pis) == 1)
self.assert_(pis[0].state == api.PackageInfo.NOT_INSTALLED)
+ self.assert_(len(pis[0].category_info_list) == 1)
ret = api_obj.info(["emerald"], local, get_license)
pis = ret[api.ImageInterface.INFO_FOUND]
--- a/src/tests/cli/t_pkg_info.py Fri Nov 07 16:55:36 2008 -0800
+++ b/src/tests/cli/t_pkg_info.py Sat Nov 08 11:08:13 2008 -0800
@@ -127,12 +127,14 @@
pkg1 = """
open [email protected],5.11-0
add dir mode=0755 owner=root group=bin path=/bin
+ add set name=info.classification value="org.opensolaris.category.2008:Applications/Sound and Video
close
"""
pkg2 = """
open [email protected],5.11-0
add dir mode=0755 owner=root group=bin path=/bin
+ add set name=info.classification value=org.opensolaris.category.2008:System/Security/Foo/bar/Baz
close
"""
@@ -149,6 +151,8 @@
# Check local info
self.pkg("info jade | grep 'State: Installed'")
+ self.pkg("info jade | grep ' Category: Applications/Sound and Video'")
+ self.pkg("info jade | grep ' Category: Applications/Sound and Video (org.opensolaris.category.2008)'", exit=1)
self.pkg("info turquoise 2>&1 | grep 'no packages matching'")
self.pkg("info emerald", exit=1)
self.pkg("info emerald 2>&1 | grep 'no packages matching'")
@@ -157,7 +161,11 @@
# Check remote info
self.pkg("info -r jade | grep 'State: Installed'")
- self.pkg("info -r turquoise| grep 'State: Not installed'")
+ self.pkg("info -r jade | grep ' Category: Applications/Sound and Video'")
+ self.pkg("info -r jade | grep ' Category: Applications/Sound and Video (org.opensolaris.category.2008)'", exit=1)
+ self.pkg("info -r turquoise | grep 'State: Not installed'")
+ self.pkg("info -r turquoise | grep ' Category: System/Security/Foo/bar/Baz'")
+ self.pkg("info -r turquoise | grep ' Category: System/Security/Foo/bar/Baz (org.opensolaris.category.2008)'", exit=1)
self.pkg("info -r emerald", exit=1)
self.pkg("info -r emerald 2>&1 | grep 'no packages matching'")