manifest.py revision 307
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye#!/usr/bin/python
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye#
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# CDDL HEADER START
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye#
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# The contents of this file are subject to the terms of the
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# Common Development and Distribution License (the "License").
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# You may not use this file except in compliance with the License.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye#
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# or http://www.opensolaris.org/os/licensing.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# See the License for the specific language governing permissions
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# and limitations under the License.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye#
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# When distributing Covered Code, include this CDDL HEADER in each
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# If applicable, add the following below this CDDL HEADER, with the
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# fields enclosed by brackets "[]" replaced with your own identifying
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# information: Portions Copyright [yyyy] [name of copyright owner]
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye#
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# CDDL HEADER END
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem#
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye# Use is subject to license terms.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbyeimport bisect
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbyeimport os
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbyeimport cPickle
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Koscofrom itertools import groupby, chain
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Koscoimport pkg.actions as actions
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbyeimport pkg.client.retrieve as retrieve
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbyeimport pkg.client.filter as filter
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbyefrom pkg.actions.attribute import AttributeAction
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco# The type member is used for the ordering of actions.
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir KotalACTION_DIR = 10
ff5eba819da0cf7964d884630fb13262ef12c505Trond NorbyeACTION_FILE = 20
b9a6d4c44c05df772f80a6e3c8f40f7ebfade6ddVladimir KotalACTION_LINK = 50
8ea4b8d9796de43443cdf7b66e3f185aedf7b570Jens ElknerACTION_HARDLINK = 55
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond NorbyeACTION_DEVICE = 100
bd4999e099547832430739402d07284e957f32ddTrond NorbyeACTION_USER = 200
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond NorbyeACTION_GROUP = 210
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir KotalACTION_SERVICE = 300
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir KotalACTION_RESTART = 310
fd69097910ff7088bdb89229aa52193803ac05c5Amotz TeremACTION_DEPEND = 400
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f8307bc55c5e69dfc9dfe4b0793df34dd670e0a7Jorgen AustvikDEPEND_REQUIRE = 0
f8307bc55c5e69dfc9dfe4b0793df34dd670e0a7Jorgen AustvikDEPEND_OPTIONAL = 1
f8307bc55c5e69dfc9dfe4b0793df34dd670e0a7Jorgen AustvikDEPEND_INCORPORATE =10
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f8307bc55c5e69dfc9dfe4b0793df34dd670e0a7Jorgen Austvikdepend_str = { DEPEND_REQUIRE : "require",
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye DEPEND_OPTIONAL : "optional",
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye DEPEND_INCORPORATE : "incorporate"
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye}
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbyeclass Manifest(object):
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco """A Manifest is the representation of the actions composing a specific
bd4999e099547832430739402d07284e957f32ddTrond Norbye package version on both the client and the repository. Both purposes
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye utilize the same storage format.
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir Kotal The serialized structure of a manifest is an unordered list of actions.
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye The special action, "set", represents a package attribute.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye The reserved attribute, "fmri", represents the package and version
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye described by this manifest. It is available as a string via the
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco attributes dictionary, and as an FMRI object from the fmri member.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye The list of manifest-wide reserved attributes is
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye base_directory Default base directory, for non-user images.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye fmri Package FMRI.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye isa Package is intended for a list of ISAs.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye platform Package is intended for a list of platforms.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye relocatable Suitable for User Image.
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye All non-prefixed attributes are reserved to the framework. Third
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye parties may prefix their attributes with a reversed domain name, domain
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye name, or stock symbol. An example might be
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye com.example,supported
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye as an indicator that a specific package version is supported by the
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye vendor, example.com.
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
ff5eba819da0cf7964d884630fb13262ef12c505Trond Norbye manifest.null is provided as the null manifest. Differences against the
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye null manifest result in the complete set of attributes and actions of
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye the non-null manifest, meaning that all operations can be viewed as
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye tranitions between the manifest being installed and the manifest already
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco present in the image (which may be the null manifest).
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye """
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye def __init__(self):
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye self.img = None
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye self.fmri = None
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye self.size = 0
8ea4b8d9796de43443cdf7b66e3f185aedf7b570Jens Elkner
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye self.actions = []
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye def __str__(self):
bd4999e099547832430739402d07284e957f32ddTrond Norbye r = ""
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco if self.fmri != None:
ff5eba819da0cf7964d884630fb13262ef12c505Trond Norbye r = r + "set name=fmri value=%s\n" % self.fmri
bd4999e099547832430739402d07284e957f32ddTrond Norbye
bd4999e099547832430739402d07284e957f32ddTrond Norbye for act in sorted(self.actions):
bd4999e099547832430739402d07284e957f32ddTrond Norbye r = r + "%s\n" % act
bd4999e099547832430739402d07284e957f32ddTrond Norbye
bd4999e099547832430739402d07284e957f32ddTrond Norbye return r
bd4999e099547832430739402d07284e957f32ddTrond Norbye
bd4999e099547832430739402d07284e957f32ddTrond Norbye def difference(self, origin):
bd4999e099547832430739402d07284e957f32ddTrond Norbye """Return three lists of action pairs representing origin and
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco destination actions. The first list contains the pairs
bd4999e099547832430739402d07284e957f32ddTrond Norbye representing additions, the second list contains the pairs
bd4999e099547832430739402d07284e957f32ddTrond Norbye representing updates, and the third list contains the pairs
bd4999e099547832430739402d07284e957f32ddTrond Norbye represnting removals. All three lists are in the order in which
bd4999e099547832430739402d07284e957f32ddTrond Norbye they should be executed."""
bd4999e099547832430739402d07284e957f32ddTrond Norbye # XXX Do we need to find some way to assert that the keys are
bd4999e099547832430739402d07284e957f32ddTrond Norbye # all unique?
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye sdict = dict(
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco ((a.name, a.attrs.get(a.key_attr, id(a))), a)
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye for a in self.actions
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye )
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye odict = dict(
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye ((a.name, a.attrs.get(a.key_attr, id(a))), a)
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye for a in origin.actions
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye )
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye sset = set(sdict.keys())
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco oset = set(odict.keys())
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye added = [(None, sdict[i]) for i in sset - oset]
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye removed = [(odict[i], None) for i in oset - sset]
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye # XXX for now, we force license actions to always be
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye # different to insure that existing license files for
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye # new versions are always installed
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal changed = [
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal (odict[i], sdict[i])
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco for i in oset & sset
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal if odict[i].different(sdict[i]) or i[0] == "license"
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal ]
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal # XXX Do changed actions need to be sorted at all? This is
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal # likely to be the largest list, so we might save significant
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal # time by not sorting. Should we sort above? Insert into a
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal # sorted list?
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal
eb1776903fd1f998009e97470a65fba8a499a0d9Lubos Kosco # singlesort = lambda x: x[0] or x[1]
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal addsort = lambda x: x[1]
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal remsort = lambda x: x[0]
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal removed.sort(key = remsort, reverse = True)
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal added.sort(key = addsort)
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal changed.sort(key = addsort)
7bcb663889f8c13133d37c58e284e507dab284c0Vladimir Kotal
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye return (added, changed, removed)
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye def combined_difference(self, origin):
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye """Where difference() returns three lists, combined_difference()
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye returns a single list of the concatenation of th three."""
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye return list(chain(*self.difference(origin)))
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir Kotal def humanized_differences(self, other):
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir Kotal """Output expects that self is newer than other. Use of sets
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir Kotal requires that we convert the action objects into some marshalled
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir Kotal form, otherwise set member identities are derived from the
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir Kotal object pointers, rather than the contents."""
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir Kotal
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir Kotal l = self.difference(other)
2fb25e01a103edabe94194fc7927339be9d26f35Vladimir Kotal out = ""
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem for src, dest in chain(*l):
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem if not src:
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem out += "+ %s\n" % str(dest)
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem elif not dest:
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem out += "- %s\n" + str(src)
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem else:
fd69097910ff7088bdb89229aa52193803ac05c5Amotz Terem out += "%s -> %s\n" % (src, dest)
f60d84bfe9ece4779c642dfe4849acd35ade9388Trond Norbye return out
def get_dependencies(self):
""" generate list of dependencies in this manifest """
return [
a.parse(self.img)
for a in self.actions
if a.name == "depend"
]
def filter(self, filters):
"""Filter out actions from the manifest based on filters."""
self.actions = [
a
for a in self.actions
if filter.apply_filters(a, filters)
]
def duplicates(self):
"""Find actions in the manifest which are duplicates (i.e.,
represent the same object) but which are not identical (i.e.,
have all the same attributes)."""
def fun(a):
"""Return a key on which actions can be sorted."""
return a.name, a.attrs.get(a.key_attr, id(a))
alldups = []
for k, g in groupby(sorted(self.actions, key = fun), fun):
glist = list(g)
dups = set()
for i in range(len(glist) - 1):
if glist[i].different(glist[i + 1]):
dups.add(glist[i])
dups.add(glist[i + 1])
if dups:
alldups.append((k, dups))
return alldups
def set_fmri(self, img, fmri):
self.img = img
self.fmri = fmri
@staticmethod
def make_opener(img, fmri, action):
def opener():
return retrieve.get_datastream(img, fmri, action.hash)
return opener
def set_content(self, str):
"""str is the text representation of the manifest"""
assert self.actions == []
self.size = 0
# So we could build up here the type/key_attr dictionaries like
# sdict and odict in difference() above, and have that be our
# main datastore, rather than the simple list we have now. If
# we do that here, we can even assert that the "same" action
# can't be in a manifest twice. (The problem of having the same
# action more than once in packages that can be installed
# together has to be solved somewhere else, though.)
for l in str.splitlines():
l = l.lstrip()
if not l or l[0] == "#":
continue
try:
action = actions.fromstr(l)
except KeyError:
raise SyntaxError, \
"unknown action '%s'" % l.split()[0]
if action.attrs.has_key("path"):
np = action.attrs["path"].lstrip(os.path.sep)
action.attrs["path"] = np
if hasattr(action, "hash"):
action.data = \
self.make_opener(self.img, self.fmri, action)
self.size += int(action.attrs.get("pkg.size", "0"))
if not self.actions:
self.actions.append(action)
else:
bisect.insort(self.actions, action)
return
def search_dict(self):
"""Return the dictionary used for searching."""
action_dict = {}
for a in self.actions:
for k, v in a.generate_indices().iteritems():
# The value might be a list if an indexed action
# attribute is multivalued, such as driver
# aliases.
t = (a.name, a.attrs.get(a.key_attr))
if isinstance(v, list):
if k in action_dict:
action_dict[k].update(
dict((i, t) for i in v))
else:
action_dict[k] = \
dict((i, t) for i in v)
else:
# XXX if there's more than one k,v pair
# in the manifest, only one will get
# recorded. basename,gmake is one
# example.
if k in action_dict:
action_dict[k][v] = t
else:
action_dict[k] = { v: t }
return action_dict
def pickle(self, file):
"""Pickle the indices of the manifest's actions to the 'file'.
"""
cPickle.dump(self.search_dict(), file,
protocol = cPickle.HIGHEST_PROTOCOL)
def get(self, key, default):
try:
return self[key]
except KeyError:
return default
def __getitem__(self, key):
"""Return the value for the package attribute 'key'. If
multiple attributes exist, return the first. Raises KeyError if
the attribute is not found."""
try:
values = [
a.attrs["value"]
for a in self.actions
if a.name == "set" and a.attrs["name"] == key
]
except KeyError:
# This hides the fact that we had busted attribute
# actions in the manifest, but that's probably not so
# bad.
raise KeyError, key
if values:
return values[0]
raise KeyError, key
def __setitem__(self, key, value):
"""Set the value for the package attribute 'key' to 'value'."""
for a in self.actions:
if a.name == "set" and a.attrs["name"] == key:
a.attrs["value"] = value
return
new_attr = AttributeAction(None, name=key, value=value)
self.actions.append(new_attr)
def __contains__(self, key):
for a in self.actions:
if a.name == "set" and a.attrs["name"] == key:
return True
return False
null = Manifest()