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#
3339N/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
2843N/AThis package has two data members:
2843N/A "types", a dictionary which maps the action names to the classes that
2843N/A represent them.
2843N/A
2843N/A "payload_types", a dictionary which maps action names that deliver payload
2843N/A to the classes that represent them.
51N/A
51N/AThis package also has one function: "fromstr", which creates an action instance
51N/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 = {}
2843N/A
2843N/A# A dictionary of the action names in this package that deliver payload,
2843N/A# according to the 'has_payload' member in each class. The dictionary is keyed
2843N/A# by the action name, and has the action class as its values.
2843N/Apayload_types = {}
2843N/A
49N/Afor modname in __all__:
3158N/A module = __import__("{0}.{1}".format(__name__, modname),
49N/A globals(), locals(), [modname])
49N/A
49N/A nvlist = inspect.getmembers(module, inspect.isclass)
49N/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 = [
49N/A c[1]
49N/A for c in nvlist
49N/A if '.'.join(c[1].__module__.split('.')[:-1]) == __name__
49N/A ]
49N/A for cls in classes:
2277N/A if hasattr(cls, "name"):
2277N/A types[cls.name] = cls
2843N/A if hasattr(cls, "has_payload") and cls.has_payload:
2843N/A payload_types[cls.name] = cls
49N/A
49N/A# Clean up after ourselves
3339N/Adel modname, module, nvlist, classes, cls
51N/A
1846N/A
591N/Aclass ActionError(Exception):
873N/A """Base exception class for Action errors."""
873N/A
873N/A def __str__(self):
873N/A raise NotImplementedError()
591N/A
3255N/Aclass ActionRetry(ActionError):
3255N/A def __init__(self, *args):
3255N/A ActionError.__init__(self)
3255N/A self.actionstr = str(args[0])
3255N/A
3255N/A def __str__(self):
3255N/A return _("Need to try installing {action} again").format(
3255N/A action=self.actionstr)
3255N/A
591N/Aclass UnknownActionError(ActionError):
591N/A def __init__(self, *args):
1755N/A ActionError.__init__(self)
591N/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:
3158N/A return _("unknown action type '{type}' in package "
3158N/A "'{fmri}' in action '{action}'").format(
3158N/A type=self.type, fmri=self.fmri,
3158N/A action=self.actionstr)
3158N/A return _("unknown action type '{type}' in action "
3158N/A "'{action}'").format(type=self.type,
3158N/A action=self.actionstr)
591N/A
591N/Aclass MalformedActionError(ActionError):
591N/A def __init__(self, *args):
1755N/A ActionError.__init__(self)
591N/A self.actionstr = args[0]
591N/A self.position = args[1]
591N/A self.errorstr = args[2]
591N/A
591N/A def __str__(self):
591N/A marker = " " * (4 + self.position) + "^"
591N/A if hasattr(self, "fmri") and self.fmri is not None:
3158N/A return _("Malformed action in package '{fmri}' at "
3158N/A "position: {pos:d}: {error}:\n {action}\n"
3158N/A "{marker}").format(fmri=self.fmri,
3158N/A pos=self.position, action=self.actionstr,
3158N/A marker=marker, error=self.errorstr)
3158N/A return _("Malformed action at position: {pos:d}: {error}:\n "
3158N/A "{action}\n{marker}").format(pos=self.position,
3158N/A action=self.actionstr, marker=marker,
3158N/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."""
873N/A
1544N/A def __init__(self, *args, **kwargs):
1755N/A ActionError.__init__(self)
873N/A self.error = args[0]
1544N/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
873N/A attributes were missing for an action."""
873N/A
873N/A def __init__(self, *args):
1755N/A ActionError.__init__(self)
873N/A self.actionstr = args[0]
873N/A self.errorstr = args[1]
873N/A
873N/A def __str__(self):
873N/A if hasattr(self, "fmri") and self.fmri is not None:
3158N/A return _("invalid action in package {fmri}: "
3158N/A "{action}: {error}").format(fmri=self.fmri,
3158N/A action=self.actionstr, error=self.errorstr)
3158N/A return _("invalid action, '{action}': {error}").format(
3158N/A action=self.actionstr, error=self.errorstr)
873N/A
591N/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]),
3158N/A _("no value specified for key attribute '{0}'").format(
3158N/A args[1]))
2639N/A
2639N/A
2639N/Aclass KeyAttributeMultiValueError(InvalidActionError):
2639N/A """Used to indicate that an action's key attribute was specified
2639N/A multiple times for an action that expects it only once."""
2639N/A
2639N/A def __init__(self, *args):
2639N/A InvalidActionError.__init__(self, str(args[0]),
3158N/A _("{0} attribute may only be specified once").format(
3158N/A args[1]))
2639N/A
2639N/A
2639N/Aclass InvalidPathAttributeError(InvalidActionError):
2639N/A """Used to indicate that an action's path attribute value was either
2639N/A empty, '/', or not a string."""
2639N/A
2639N/A def __init__(self, *args):
2639N/A InvalidActionError.__init__(self, str(args[0]),
2639N/A _("Empty or invalid path attribute"))
2639N/A
2639N/A
1755N/Aclass InvalidActionAttributesError(ActionError):
2476N/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
1755N/A ActionError.__init__(self)
1755N/A self.action = act
1755N/A self.errors = errors
1755N/A self.fmri = fmri
1755N/A
1755N/A def __str__(self):
1755N/A act_errors = "\n ".join(err for name, err in self.errors)
1755N/A if self.fmri:
3158N/A return _("The action '{action}' in package "
3158N/A "'{fmri}' has invalid attribute(s):\n"
3158N/A " {act_errors}").format(action=self.action,
3158N/A fmri=self.fmri, act_errors=act_errors)
3158N/A return _("The action '{action}' has invalid attribute(s):\n"
3158N/A " {act_errors}").format(action=self.action,
3158N/A act_errors=act_errors)
1755N/A
1755N/A
2639N/A# This must be imported *after* all of the exception classes are defined as
2639N/A# _actions module init needs the exception objects.
3339N/Afrom ._actions import fromstr
591N/A
1292N/Adef attrsfromstr(string):
1292N/A """Create an attribute dict given a string w/ key=value pairs.
1292N/A
1292N/A Raises MalformedActionError if the attributes have syntactic problems.
1292N/A """
3158N/A return fromstr("unknown {0}".format(string)).attrs
57N/A
1500N/Adef internalizelist(atype, args, ahash=None, basedirs=None):
279N/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
1500N/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.
1500N/A
1500N/A The "ahash" parameter is used to set the hash value for the action.
1500N/A
1500N/A The "basedirs" parameter is the list of directories to look in to find
1500N/A any payload for the action.
279N/A
591N/A Raises MalformedActionError if the attribute strings are malformed.
279N/A """
305N/A
1500N/A if atype not in types:
3158N/A raise UnknownActionError(("{0} {1}".format(atype,
1500N/A " ".join(args))).strip(), atype)
873N/A
1500N/A data = None
1500N/A
1500N/A if atype in ("file", "license"):
1500N/A data = args.pop(0)
2456N/A
305N/A attrs = {}
305N/A
305N/A try:
3339N/A # list comprehension in Python 3 doesn't leak loop control
3339N/A # variable to surrounding variable, so use a regular loop
3339N/A for kv in args:
3339N/A a, v = kv.split("=", 1)
305N/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(
3158N/A "{0} {1} {2}".format(atype, p1, p2),
3158N/A len(p1) + 1,
3158N/A "attribute '{0}'".format(kv))
57N/A
305N/A # This is by far the common case-- an attribute with
305N/A # a single value.
305N/A if a not in attrs:
305N/A attrs[a] = v
305N/A else:
305N/A av = attrs[a]
305N/A if isinstance(av, list):
305N/A attrs[a].append(v)
305N/A else:
305N/A attrs[a] = [ av, v ]
591N/A except ValueError:
305N/A # We're only here if the for: statement above throws a
591N/A # MalformedActionError. That can happen if split yields a
591N/A # single element, which is possible if e.g. an attribute lacks
591N/A # an =.
591N/A kvi = args.index(kv) + 1
591N/A p1 = " ".join(args[:kvi])
591N/A p2 = " ".join(args[kvi:])
3158N/A raise MalformedActionError("{0} {1} {2}".format(atype, p1, p2),
3158N/A len(p1) + 2, "attribute '{0}'".format(kv))
51N/A
1659N/A # keys called 'data' cause problems due to the named parameter being
1659N/A # passed to the action constructor below. Check for these. Note that
1659N/A # _fromstr also checks for this.
1659N/A if "data" in attrs:
1659N/A astr = atype + " " + " ".join(args)
1659N/A raise InvalidActionError(astr,
3158N/A "{0} action cannot have a 'data' attribute".format(
3158N/A atype))
1659N/A
3339N/A action = types[atype](data, **attrs)
1500N/A if ahash:
1500N/A action.hash = ahash
1500N/A
2456N/A local_path, used_basedir = set_action_data(data, action,
2456N/A basedirs=basedirs)
1500N/A return action, local_path
1500N/A
1500N/Adef internalizestr(string, basedirs=None, load_data=True):
1500N/A """Create an action instance based on a sequence of 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
1500N/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.
1500N/A """
1500N/A
1500N/A action = fromstr(string)
1500N/A
2460N/A if action.name not in ("file", "license") or not load_data:
1908N/A return action, None, None
51N/A
2460N/A local_path, used_basedir = set_action_data(action.hash, action,
2456N/A basedirs=basedirs)
1908N/A return action, local_path, used_basedir
1500N/A
2456N/Adef set_action_data(payload, action, basedirs=None, bundles=None):
1500N/A """Sets the data field of an action using the information in the
2456N/A payload and returns the actual path used to set the data and the
2456N/A source used to find the data (this may be a path or a bundle
2456N/A object).
1500N/A
1500N/A The "payload" parameter is the representation of the data to assign to
1500N/A the action's data field. It can either be NOHASH or a path to the file.
1500N/A
1500N/A The "action" parameter is the action to modify.
1500N/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 """
1500N/A
1500N/A if not payload:
1908N/A return None, None
1908N/A
1500N/A if payload == "NOHASH":
2800N/A try:
2800N/A filepath = os.path.sep + action.attrs["path"]
2800N/A except KeyError:
2800N/A raise InvalidPathAttributeError(action)
1500N/A else:
1500N/A filepath = payload
1500N/A
2456N/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:
2456N/A data = act.data
2456N/A used_src = bundle
2456N/A action.attrs["pkg.size"] = \
2456N/A act.attrs["pkg.size"]
1500N/A break
1500N/A
2456N/A if not data and basedirs:
3158N/A raise ActionDataError(_("Action payload '{name}' "
2456N/A "was not found in any of the provided locations:"
3158N/A "\n{basedirs}").format(name=filepath,
3158N/A basedirs="\n".join(basedirs)), path=filepath)
2456N/A elif not data and bundles:
3158N/A raise ActionDataError(_("Action payload '{name}' was "
2456N/A "not found in any of the provided sources:"
3158N/A "\n{sources}").format(name=filepath,
3158N/A sources="\n".join(b.filename for b in bundles)),
2456N/A path=filepath)
2456N/A elif not data:
2456N/A # Only if no explicit sources were provided should a
2456N/A # fallback to filepath be performed.
2456N/A data = filepath
2456N/A
2456N/A # This relies on data having a value set by the code above so that an
2456N/A # ActionDataError will be raised if the file doesn't exist or is not
2456N/A # accessible.
1500N/A action.set_data(data)
2456N/A return data, used_src