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