__init__.py revision 2456
660N/A#!/usr/bin/python
660N/A#
660N/A# CDDL HEADER START
660N/A#
660N/A# The contents of this file are subject to the terms of the
660N/A# Common Development and Distribution License (the "License").
660N/A# You may not use this file except in compliance with the License.
660N/A#
660N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
660N/A# or http://www.opensolaris.org/os/licensing.
660N/A# See the License for the specific language governing permissions
660N/A# and limitations under the License.
660N/A#
660N/A# When distributing Covered Code, include this CDDL HEADER in each
660N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
660N/A# If applicable, add the following below this CDDL HEADER, with the
660N/A# fields enclosed by brackets "[]" replaced with your own identifying
678N/A# information: Portions Copyright [yyyy] [name of copyright owner]
678N/A#
678N/A# CDDL HEADER END
678N/A#
678N/A
678N/A#
678N/A# Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
678N/A#
678N/A
678N/A"""
678N/Apackage containing packaging action (file type) modules
678N/A
678N/AThis package contains modules describing packaging actions, or file types. The
678N/Aactions are dynamically discovered, so that new modules can be placed in this
678N/Apackage directory and they'll just be picked up. The current package contents
678N/Acan be seen in the section "PACKAGE CONTENTS", below.
678N/A
678N/AThis package has one data member: "types". This is a dictionary which maps the
678N/Aaction names to the classes that represent them.
678N/A
678N/AThis package also has one function: "fromstr", which creates an action instance
678N/Abased on a str() representation of an action.
678N/A"""
678N/A
678N/Aimport inspect
678N/Aimport os
678N/A
678N/A# All modules in this package (all python files except __init__.py with their
678N/A# extensions stripped off).
678N/A__all__ = [
678N/A f[:-3]
678N/A for f in os.listdir(__path__[0])
678N/A if f.endswith(".py") and f != "__init__.py"
678N/A]
678N/A
678N/A# A dictionary of all the types in this package, mapping to the classes that
678N/A# define them.
678N/Atypes = {}
678N/Afor modname in __all__:
678N/A module = __import__("%s.%s" % (__name__, modname),
678N/A globals(), locals(), [modname])
678N/A
678N/A nvlist = inspect.getmembers(module, inspect.isclass)
678N/A
678N/A # Pull the class objects out of nvlist, keeping only those that are
678N/A # actually defined in this package.
678N/A classes = [
678N/A c[1]
678N/A for c in nvlist
678N/A if '.'.join(c[1].__module__.split('.')[:-1]) == __name__
678N/A ]
678N/A for cls in classes:
678N/A if hasattr(cls, "name"):
678N/A types[cls.name] = cls
678N/A
678N/A# Clean up after ourselves
678N/Adel f, modname, module, nvlist, classes, c, cls
678N/A
678N/A
678N/Aclass ActionError(Exception):
678N/A """Base exception class for Action errors."""
678N/A
678N/A def __unicode__(self):
678N/A # To workaround python issues 6108 and 2517, this provides a
678N/A # a standard wrapper for this class' exceptions so that they
678N/A # have a chance of being stringified correctly.
678N/A return str(self)
678N/A
678N/A def __str__(self):
678N/A raise NotImplementedError()
678N/A
678N/Aclass UnknownActionError(ActionError):
678N/A def __init__(self, *args):
678N/A ActionError.__init__(self)
678N/A self.actionstr = args[0]
678N/A self.type = args[1]
678N/A
678N/A def __str__(self):
678N/A if hasattr(self, "fmri") and self.fmri is not None:
678N/A return _("unknown action type '%(type)s' in package "
678N/A "'%(fmri)s' in action '%(action)s'") % {
678N/A "type": self.type, "fmri": self.fmri,
678N/A "action": self.actionstr }
678N/A return _("unknown action type '%(type)s' in action "
678N/A "'%(action)s'") % { "type": self.type,
678N/A "action": self.actionstr }
678N/A
678N/Aclass MalformedActionError(ActionError):
678N/A def __init__(self, *args):
678N/A ActionError.__init__(self)
678N/A self.actionstr = args[0]
678N/A self.position = args[1]
678N/A self.errorstr = args[2]
678N/A
678N/A def __str__(self):
678N/A marker = " " * (4 + self.position) + "^"
678N/A if hasattr(self, "fmri") and self.fmri is not None:
678N/A return _("Malformed action in package '%(fmri)s' at "
678N/A "position: %(pos)d:\n %(action)s\n"
678N/A "%(marker)s") % { "fmri": self.fmri,
678N/A "pos": self.position, "action": self.actionstr,
678N/A "marker": marker }
678N/A return _("Malformed action at position: %(pos)d:\n "
678N/A "%(action)s\n%(marker)s") % { "pos": self.position,
678N/A "action": self.actionstr, "marker": marker }
678N/A
678N/A
678N/Aclass ActionDataError(ActionError):
678N/A """Used to indicate that a file-related error occuring during action
678N/A initialization."""
678N/A
678N/A def __init__(self, *args, **kwargs):
678N/A ActionError.__init__(self)
678N/A self.error = args[0]
678N/A self.path = kwargs.get("path", None)
678N/A
678N/A def __str__(self):
678N/A return str(self.error)
678N/A
678N/A
678N/Aclass InvalidActionError(ActionError):
678N/A """Used to indicate that attributes provided were invalid, or required
678N/A attributes were missing for an action."""
660N/A
660N/A def __init__(self, *args):
678N/A ActionError.__init__(self)
678N/A self.actionstr = args[0]
678N/A self.errorstr = args[1]
678N/A
678N/A def __str__(self):
678N/A if hasattr(self, "fmri") and self.fmri is not None:
678N/A return _("invalid action in package %(fmri)s: "
678N/A "%(action)s: %(error)s") % { "fmri": self.fmri,
678N/A "action": self.actionstr, "error": self.errorstr }
678N/A return _("invalid action, '%(action)s': %(error)s") % {
678N/A "action": self.actionstr, "error": self.errorstr }
678N/A
678N/A
678N/Aclass InvalidActionAttributesError(ActionError):
678N/A """Used to indicate that one more action attributes were invalid."""
678N/A
678N/A def __init__(self, act, errors, fmri=None):
678N/A """'act' is an Action (object or string).
678N/A
678N/A 'errors' is a list of tuples of the form (name, error) where
678N/A 'name' is the action attribute name, and 'error' is a string
678N/A indicating what attribute is invalid and why.
678N/A
678N/A 'fmri' is an optional package FMRI (object or string)
678N/A indicating what package contained the actions with invalid
678N/A attributes."""
678N/A
678N/A ActionError.__init__(self)
678N/A self.action = act
678N/A self.errors = errors
678N/A self.fmri = fmri
678N/A
678N/A def __str__(self):
678N/A act_errors = "\n ".join(err for name, err in self.errors)
678N/A if self.fmri:
678N/A return _("The action '%(action)s' in package "
678N/A "'%(fmri)s' has invalid attribute(s):\n"
678N/A " %(act_errors)s") % { "action": self.action,
678N/A "fmri": self.fmri, "act_errors": act_errors }
678N/A return _("The action '%(action)s' has invalid attribute(s):\n"
678N/A " %(act_errors)s") % { "action": self.action,
678N/A "act_errors": act_errors }
678N/A
678N/A
678N/Afrom _actions import _fromstr
678N/A
678N/Adef attrsfromstr(string):
678N/A """Create an attribute dict given a string w/ key=value pairs.
678N/A
678N/A Raises MalformedActionError if the attributes have syntactic problems.
678N/A """
678N/A return _fromstr("bogus %s" % string)[2]
678N/A
660N/Adef fromstr(string, data=None):
678N/A """Create an action instance based on a str() representation of an
678N/A action.
678N/A
678N/A Raises UnknownActionError if the action type is unknown.
678N/A Raises MalformedActionError if the action has other syntactic problems.
678N/A """
678N/A
678N/A atype, ahash, attr_dict = _fromstr(string)
678N/A
678N/A if atype not in types:
678N/A raise UnknownActionError(string, atype)
678N/A
678N/A action = types[atype](data=data, **attr_dict)
678N/A
678N/A if not action.key_attr_opt:
678N/A ka = action.key_attr
678N/A if ka is not None and (ka not in action.attrs or
678N/A action.attrs[ka] is None):
678N/A raise InvalidActionError(string,
678N/A _("required attribute '%s' was not provided.") % ka)
678N/A
678N/A if ahash:
678N/A action.hash = ahash
678N/A
678N/A return action
678N/A
678N/Adef internalizelist(atype, args, ahash=None, basedirs=None):
678N/A """Create an action instance based on a sequence of "key=value" strings.
678N/A This function also translates external representations of actions with
678N/A payloads (like file and license which can use NOHASH or file paths to
678N/A point to the payload) to an internal representation which sets the
678N/A data field of the action returned.
678N/A
678N/A The "atype" parameter is the type of action to be built.
678N/A
678N/A The "args" parameter is the sequence of "key=value" strings.
678N/A
678N/A The "ahash" parameter is used to set the hash value for the action.
678N/A
678N/A The "basedirs" parameter is the list of directories to look in to find
678N/A any payload for the action.
678N/A
678N/A Raises MalformedActionError if the attribute strings are malformed.
678N/A """
678N/A
678N/A if atype not in types:
678N/A raise UnknownActionError(("%s %s" % (atype,
678N/A " ".join(args))).strip(), atype)
678N/A
678N/A data = None
678N/A
678N/A if atype in ("file", "license"):
678N/A data = args.pop(0)
678N/A
678N/A attrs = {}
678N/A
678N/A try:
678N/A for a, v in [kv.split("=", 1) for kv in args]:
678N/A if v == '' or a == '':
678N/A kvi = args.index(kv) + 1
678N/A p1 = " ".join(args[:kvi])
678N/A p2 = " ".join(args[kvi:])
678N/A raise MalformedActionError(
678N/A "%s %s %s" % (atype, p1, p2), len(p1) + 1,
678N/A "attribute '%s'" % kv)
678N/A
678N/A # This is by far the common case-- an attribute with
678N/A # a single value.
678N/A if a not in attrs:
678N/A attrs[a] = v
678N/A else:
678N/A av = attrs[a]
678N/A if isinstance(av, list):
678N/A attrs[a].append(v)
678N/A else:
678N/A attrs[a] = [ av, v ]
678N/A except ValueError:
678N/A # We're only here if the for: statement above throws a
678N/A # MalformedActionError. That can happen if split yields a
678N/A # single element, which is possible if e.g. an attribute lacks
678N/A # an =.
678N/A kvi = args.index(kv) + 1
660N/A p1 = " ".join(args[:kvi])
660N/A p2 = " ".join(args[kvi:])
678N/A raise MalformedActionError("%s %s %s" % (atype, p1, p2),
678N/A len(p1) + 2, "attribute '%s'" % kv)
660N/A
678N/A # keys called 'data' cause problems due to the named parameter being
678N/A # passed to the action constructor below. Check for these. Note that
678N/A # _fromstr also checks for this.
678N/A if "data" in attrs:
678N/A astr = atype + " " + " ".join(args)
678N/A raise InvalidActionError(astr,
678N/A "%s action cannot have a 'data' attribute" % atype)
678N/A
678N/A action = types[atype](data=None, **attrs)
660N/A
678N/A ka = action.key_attr
678N/A if ka is not None and (ka not in action.attrs or
678N/A action.attrs[ka] is None):
678N/A raise InvalidActionError(("%s %s" % (atype,
678N/A " ".join(args))).strip(), _("required attribute, "
678N/A "'%s', was not provided.") % ka)
678N/A
678N/A if ahash:
678N/A action.hash = ahash
678N/A
678N/A local_path, used_basedir = set_action_data(data, action,
678N/A basedirs=basedirs)
678N/A return action, local_path
678N/A
678N/Adef internalizestr(string, basedirs=None, load_data=True):
678N/A """Create an action instance based on a sequence of strings.
678N/A This function also translates external representations of actions with
678N/A payloads (like file and license which can use NOHASH or file paths to
678N/A point to the payload) to an internal representation which sets the
678N/A data field of the action returned.
678N/A
678N/A In general, each string should be in the form of "key=value". The
678N/A exception is a payload for certain actions which should be the first
660N/A item in the sequence.
678N/A
678N/A Raises MalformedActionError if the attribute strings are malformed.
678N/A """
678N/A
678N/A string = string.strip()
678N/A args = string.split()
678N/A atype = args.pop(0)
678N/A
678N/A if atype not in types:
678N/A raise UnknownActionError(("%s %s" % (atype,
678N/A " ".join(args))).strip(), atype)
678N/A
678N/A action = fromstr(string)
678N/A
660N/A if atype not in ("file", "license") or not load_data:
678N/A return action, None, None
678N/A
678N/A local_path, used_basedir = set_action_data(args[0], action,
678N/A basedirs=basedirs)
678N/A return action, local_path, used_basedir
678N/A
678N/Adef set_action_data(payload, action, basedirs=None, bundles=None):
678N/A """Sets the data field of an action using the information in the
678N/A payload and returns the actual path used to set the data and the
678N/A source used to find the data (this may be a path or a bundle
678N/A object).
678N/A
660N/A The "payload" parameter is the representation of the data to assign to
678N/A the action's data field. It can either be NOHASH or a path to the file.
678N/A
678N/A The "action" parameter is the action to modify.
678N/A
678N/A The "basedirs" parameter contains the directories to examine to find the
678N/A payload in.
678N/A
678N/A The "bundles" parameter contains a list of bundle objects to find the
678N/A payload in.
660N/A
678N/A "basedirs" and/or "bundles" must be specified.
678N/A """
678N/A
678N/A if not payload:
678N/A return None, None
678N/A
678N/A if payload == "NOHASH":
678N/A filepath = os.path.sep + action.attrs["path"]
678N/A else:
660N/A filepath = payload
660N/A
678N/A if not basedirs:
678N/A basedirs = []
678N/A if not bundles:
678N/A bundles = []
678N/A
678N/A # Attempt to find directory or bundle containing source of file data.
678N/A data = None
678N/A used_src = None
678N/A path = filepath.lstrip(os.path.sep)
678N/A for bd in basedirs:
678N/A # look for file in specified dir
678N/A npath = os.path.join(bd, path)
678N/A if os.path.isfile(npath):
678N/A used_src = bd
678N/A data = npath
678N/A break
678N/A else:
678N/A for bundle in bundles:
678N/A act = bundle.get_action(path)
678N/A if act:
678N/A data = act.data
678N/A used_src = bundle
678N/A action.attrs["pkg.size"] = \
678N/A act.attrs["pkg.size"]
660N/A break
678N/A
678N/A if not data and basedirs:
678N/A raise ActionDataError(_("Action payload '%(name)s' "
678N/A "was not found in any of the provided locations:"
678N/A "\n%(basedirs)s") % { "name": filepath,
678N/A "basedirs": "\n".join(basedirs) }, path=filepath)
678N/A elif not data and bundles:
678N/A raise ActionDataError(_("Action payload '%(name)s' was "
678N/A "not found in any of the provided sources:"
678N/A "\n%(sources)s") % { "name": filepath,
678N/A "sources": "\n".join(b.filename for b in bundles) },
678N/A path=filepath)
678N/A elif not data:
678N/A # Only if no explicit sources were provided should a
678N/A # fallback to filepath be performed.
678N/A data = filepath
678N/A
678N/A # This relies on data having a value set by the code above so that an
678N/A # ActionDataError will be raised if the file doesn't exist or is not
678N/A # accessible.
678N/A action.set_data(data)
678N/A return data, used_src
678N/A