manifest.py revision 429
409N/A#!/usr/bin/python2.4
39N/A#
39N/A# CDDL HEADER START
39N/A#
39N/A# The contents of this file are subject to the terms of the
39N/A# Common Development and Distribution License (the "License").
39N/A# You may not use this file except in compliance with the License.
39N/A#
39N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
39N/A# or http://www.opensolaris.org/os/licensing.
39N/A# See the License for the specific language governing permissions
39N/A# and limitations under the License.
39N/A#
39N/A# When distributing Covered Code, include this CDDL HEADER in each
39N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
39N/A# If applicable, add the following below this CDDL HEADER, with the
39N/A# fields enclosed by brackets "[]" replaced with your own identifying
39N/A# information: Portions Copyright [yyyy] [name of copyright owner]
39N/A#
39N/A# CDDL HEADER END
39N/A#
227N/A# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
39N/A# Use is subject to license terms.
39N/A
39N/Aimport os
342N/Aimport errno
203N/Afrom itertools import groupby, chain
39N/A
51N/Aimport pkg.actions as actions
111N/Aimport pkg.client.filter as filter
296N/Afrom pkg.actions.attribute import AttributeAction
39N/A
39N/A# The type member is used for the ordering of actions.
39N/AACTION_DIR = 10
39N/AACTION_FILE = 20
39N/AACTION_LINK = 50
39N/AACTION_HARDLINK = 55
39N/AACTION_DEVICE = 100
39N/AACTION_USER = 200
39N/AACTION_GROUP = 210
39N/AACTION_SERVICE = 300
39N/AACTION_RESTART = 310
48N/AACTION_DEPEND = 400
48N/A
48N/ADEPEND_REQUIRE = 0
48N/ADEPEND_OPTIONAL = 1
429N/ADEPEND_INCORPORATE = 10
48N/A
48N/Adepend_str = { DEPEND_REQUIRE : "require",
48N/A DEPEND_OPTIONAL : "optional",
48N/A DEPEND_INCORPORATE : "incorporate"
48N/A}
39N/A
39N/Aclass Manifest(object):
39N/A """A Manifest is the representation of the actions composing a specific
39N/A package version on both the client and the repository. Both purposes
39N/A utilize the same storage format.
39N/A
205N/A The serialized structure of a manifest is an unordered list of actions.
39N/A
205N/A The special action, "set", represents a package attribute.
39N/A
39N/A The reserved attribute, "fmri", represents the package and version
39N/A described by this manifest. It is available as a string via the
39N/A attributes dictionary, and as an FMRI object from the fmri member.
39N/A
39N/A The list of manifest-wide reserved attributes is
39N/A
39N/A base_directory Default base directory, for non-user images.
39N/A fmri Package FMRI.
39N/A isa Package is intended for a list of ISAs.
39N/A platform Package is intended for a list of platforms.
39N/A relocatable Suitable for User Image.
39N/A
39N/A All non-prefixed attributes are reserved to the framework. Third
39N/A parties may prefix their attributes with a reversed domain name, domain
39N/A name, or stock symbol. An example might be
39N/A
39N/A com.example,supported
39N/A
39N/A as an indicator that a specific package version is supported by the
39N/A vendor, example.com.
39N/A
48N/A manifest.null is provided as the null manifest. Differences against the
48N/A null manifest result in the complete set of attributes and actions of
48N/A the non-null manifest, meaning that all operations can be viewed as
48N/A tranitions between the manifest being installed and the manifest already
48N/A present in the image (which may be the null manifest).
59N/A """
48N/A
39N/A def __init__(self):
104N/A self.img = None
39N/A self.fmri = None
39N/A
301N/A self.size = 0
39N/A self.actions = []
39N/A
39N/A def __str__(self):
39N/A r = ""
39N/A if self.fmri != None:
315N/A r += "set name=fmri value=%s\n" % self.fmri
39N/A
227N/A for act in sorted(self.actions):
315N/A r += "%s\n" % act
315N/A return r
39N/A
315N/A def tostr_unsorted(self):
315N/A r = ""
315N/A if self.fmri != None:
315N/A r += "set name=fmri value=%s\n" % self.fmri
429N/A
315N/A for act in self.actions:
315N/A r += "%s\n" % act
39N/A return r
429N/A
39N/A
72N/A def difference(self, origin):
203N/A """Return three lists of action pairs representing origin and
203N/A destination actions. The first list contains the pairs
203N/A representing additions, the second list contains the pairs
203N/A representing updates, and the third list contains the pairs
203N/A represnting removals. All three lists are in the order in which
203N/A they should be executed."""
72N/A # XXX Do we need to find some way to assert that the keys are
72N/A # all unique?
59N/A
72N/A sdict = dict(
72N/A ((a.name, a.attrs.get(a.key_attr, id(a))), a)
72N/A for a in self.actions
72N/A )
72N/A odict = dict(
72N/A ((a.name, a.attrs.get(a.key_attr, id(a))), a)
72N/A for a in origin.actions
72N/A )
59N/A
72N/A sset = set(sdict.keys())
72N/A oset = set(odict.keys())
59N/A
72N/A added = [(None, sdict[i]) for i in sset - oset]
72N/A removed = [(odict[i], None) for i in oset - sset]
307N/A # XXX for now, we force license actions to always be
307N/A # different to insure that existing license files for
307N/A # new versions are always installed
72N/A changed = [
72N/A (odict[i], sdict[i])
72N/A for i in oset & sset
237N/A if odict[i].different(sdict[i]) or i[0] == "license"
72N/A ]
59N/A
72N/A # XXX Do changed actions need to be sorted at all? This is
72N/A # likely to be the largest list, so we might save significant
72N/A # time by not sorting. Should we sort above? Insert into a
72N/A # sorted list?
59N/A
72N/A # singlesort = lambda x: x[0] or x[1]
72N/A addsort = lambda x: x[1]
72N/A remsort = lambda x: x[0]
202N/A removed.sort(key = remsort, reverse = True)
72N/A added.sort(key = addsort)
72N/A changed.sort(key = addsort)
59N/A
202N/A return (added, changed, removed)
59N/A
203N/A def combined_difference(self, origin):
203N/A """Where difference() returns three lists, combined_difference()
315N/A returns a single list of the concatenation of the three."""
203N/A return list(chain(*self.difference(origin)))
203N/A
181N/A def humanized_differences(self, other):
48N/A """Output expects that self is newer than other. Use of sets
48N/A requires that we convert the action objects into some marshalled
48N/A form, otherwise set member identities are derived from the
48N/A object pointers, rather than the contents."""
48N/A
72N/A l = self.difference(other)
203N/A out = ""
48N/A
203N/A for src, dest in chain(*l):
72N/A if not src:
181N/A out += "+ %s\n" % str(dest)
72N/A elif not dest:
181N/A out += "- %s\n" + str(src)
46N/A else:
181N/A out += "%s -> %s\n" % (src, dest)
237N/A return out
46N/A
342N/A def gen_actions_by_type(self, type):
342N/A """Generate actions in the manifest of type "type"."""
342N/A
342N/A return (a for a in self.actions if a.name == type)
307N/A
111N/A def filter(self, filters):
111N/A """Filter out actions from the manifest based on filters."""
111N/A
111N/A self.actions = [
111N/A a
111N/A for a in self.actions
111N/A if filter.apply_filters(a, filters)
111N/A ]
111N/A
111N/A def duplicates(self):
111N/A """Find actions in the manifest which are duplicates (i.e.,
111N/A represent the same object) but which are not identical (i.e.,
111N/A have all the same attributes)."""
111N/A
111N/A def fun(a):
111N/A """Return a key on which actions can be sorted."""
111N/A return a.name, a.attrs.get(a.key_attr, id(a))
111N/A
113N/A alldups = []
111N/A for k, g in groupby(sorted(self.actions, key = fun), fun):
113N/A glist = list(g)
113N/A dups = set()
113N/A for i in range(len(glist) - 1):
113N/A if glist[i].different(glist[i + 1]):
113N/A dups.add(glist[i])
113N/A dups.add(glist[i + 1])
113N/A if dups:
113N/A alldups.append((k, dups))
113N/A return alldups
111N/A
104N/A def set_fmri(self, img, fmri):
104N/A self.img = img
39N/A self.fmri = fmri
39N/A
39N/A def set_content(self, str):
39N/A """str is the text representation of the manifest"""
307N/A assert self.actions == []
301N/A
72N/A # So we could build up here the type/key_attr dictionaries like
72N/A # sdict and odict in difference() above, and have that be our
72N/A # main datastore, rather than the simple list we have now. If
72N/A # we do that here, we can even assert that the "same" action
72N/A # can't be in a manifest twice. (The problem of having the same
72N/A # action more than once in packages that can be installed
72N/A # together has to be solved somewhere else, though.)
39N/A for l in str.splitlines():
146N/A l = l.lstrip()
146N/A if not l or l[0] == "#":
50N/A continue
51N/A
51N/A try:
51N/A action = actions.fromstr(l)
315N/A except (KeyError, ValueError), e:
315N/A raise SyntaxError, "%s: %s" % (self.fmri, e[0])
51N/A
123N/A if action.attrs.has_key("path"):
123N/A np = action.attrs["path"].lstrip(os.path.sep)
123N/A action.attrs["path"] = np
123N/A
307N/A self.size += int(action.attrs.get("pkg.size", "0"))
429N/A self.actions.append(action)
39N/A
39N/A return
39N/A
144N/A def search_dict(self):
144N/A """Return the dictionary used for searching."""
144N/A action_dict = {}
144N/A for a in self.actions:
144N/A for k, v in a.generate_indices().iteritems():
429N/A # Special handling of AttributeActions is
429N/A # needed inorder to place the correct values
429N/A # into the correct output columns. This is
429N/A # the pattern of which information changes
429N/A # on an item by item basis is differs for
429N/A # AttributeActions.
429N/A #
429N/A # The right solution is probably to reorganize
429N/A # this function and all the generate_indicies
429N/A # functions to allow the necessary flexibility.
429N/A if isinstance(a,
429N/A actions.attribute.AttributeAction):
429N/A tok_type = a.attrs.get(a.key_attr)
429N/A t = (a.name, k)
429N/A else:
429N/A tok_type = k
429N/A t = (a.name, a.attrs.get(a.key_attr))
429N/A # The value might be a list if an indexed
429N/A # action attribute is multivalued, such as
429N/A # driver aliases.
144N/A if isinstance(v, list):
429N/A if tok_type in action_dict:
429N/A action_dict[tok_type].update(
429N/A dict((i, [t]) for i in v))
429N/A else:
429N/A action_dict[tok_type] = \
429N/A dict((i, [t]) for i in v)
302N/A else:
429N/A if tok_type not in action_dict:
429N/A action_dict[tok_type] = \
429N/A { v: [t] }
429N/A elif v not in action_dict[tok_type]:
429N/A action_dict[tok_type][v] = [t]
144N/A else:
429N/A action_dict[tok_type][v].append(t)
429N/A assert action_dict[tok_type][v]
144N/A return action_dict
144N/A
429N/A def store(self, mfst_path):
429N/A """Store the manifest contents to disk."""
144N/A
315N/A try:
315N/A mfile = file(mfst_path, "w")
315N/A except IOError:
342N/A try:
342N/A os.makedirs(os.path.dirname(mfst_path))
342N/A except OSError, e:
342N/A if e.errno != errno.EEXIST:
342N/A raise
315N/A mfile = file(mfst_path, "w")
315N/A
315N/A #
315N/A # We specifically avoid sorting manifests before writing
315N/A # them to disk-- there's really no point in doing so, since
315N/A # we'll sort actions globally during packaging operations.
315N/A #
315N/A mfile.write(self.tostr_unsorted())
315N/A mfile.close()
144N/A
205N/A def get(self, key, default):
257N/A try:
257N/A return self[key]
257N/A except KeyError:
205N/A return default
205N/A
205N/A def __getitem__(self, key):
257N/A """Return the value for the package attribute 'key'. If
257N/A multiple attributes exist, return the first. Raises KeyError if
257N/A the attribute is not found."""
257N/A try:
257N/A values = [
257N/A a.attrs["value"]
257N/A for a in self.actions
257N/A if a.name == "set" and a.attrs["name"] == key
257N/A ]
257N/A except KeyError:
257N/A # This hides the fact that we had busted attribute
257N/A # actions in the manifest, but that's probably not so
257N/A # bad.
257N/A raise KeyError, key
205N/A
205N/A if values:
205N/A return values[0]
257N/A
257N/A raise KeyError, key
205N/A
296N/A def __setitem__(self, key, value):
296N/A """Set the value for the package attribute 'key' to 'value'."""
296N/A for a in self.actions:
296N/A if a.name == "set" and a.attrs["name"] == key:
296N/A a.attrs["value"] = value
296N/A return
296N/A
296N/A new_attr = AttributeAction(None, name=key, value=value)
296N/A self.actions.append(new_attr)
296N/A
205N/A def __contains__(self, key):
205N/A for a in self.actions:
205N/A if a.name == "set" and a.attrs["name"] == key:
205N/A return True
205N/A return False
205N/A
48N/Anull = Manifest()