pkgplan.py revision 136
0N/A#!/usr/bin/python
594N/A#
0N/A# CDDL HEADER START
0N/A#
0N/A# The contents of this file are subject to the terms of the
0N/A# Common Development and Distribution License (the "License").
0N/A# You may not use this file except in compliance with the License.
0N/A#
0N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
0N/A# or http://www.opensolaris.org/os/licensing.
0N/A# See the License for the specific language governing permissions
0N/A# and limitations under the License.
0N/A#
0N/A# When distributing Covered Code, include this CDDL HEADER in each
0N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
0N/A# If applicable, add the following below this CDDL HEADER, with the
0N/A# fields enclosed by brackets "[]" replaced with your own identifying
0N/A# information: Portions Copyright [yyyy] [name of copyright owner]
0N/A#
0N/A# CDDL HEADER END
0N/A#
0N/A
0N/A# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
0N/A# Use is subject to license terms.
0N/A
0N/Aimport errno
658N/Aimport os
0N/A
0N/Aimport pkg.manifest as manifest
0N/Aimport pkg.client.filelist as filelist
2080N/A
2080N/Aclass PkgPlan(object):
2080N/A """A package plan takes two package FMRIs and an Image, and produces the
0N/A set of actions required to take the Image from the origin FMRI to the
0N/A destination FMRI.
0N/A
0N/A If the destination FMRI is None, the package is removed.
0N/A """
0N/A
0N/A def __init__(self, image):
0N/A self.origin_fmri = None
0N/A self.destination_fmri = None
0N/A self.origin_mfst = manifest.null
0N/A self.destination_mfst = manifest.null
0N/A
0N/A self.image = image
0N/A
0N/A self.actions = []
0N/A
0N/A def __str__(self):
0N/A s = "%s -> %s\n" % (self.origin_fmri, self.destination_fmri)
0N/A
0N/A for src, dest in self.actions:
0N/A s += " %s -> %s\n" % (src, dest)
0N/A
0N/A return s
0N/A
0N/A def set_origin(self, fmri):
0N/A self.origin_fmri = fmri
0N/A self.origin_mfst = manifest.retrieve(fmri)
0N/A
0N/A def propose_destination(self, fmri, manifest):
0N/A self.destination_fmri = fmri
0N/A self.destination_mfst = manifest
0N/A
0N/A if os.path.exists("%s/pkg/%s/installed" % (self.image.imgdir,
0N/A fmri.get_dir_path())):
0N/A raise RuntimeError, "already installed"
0N/A
0N/A def propose_removal(self, fmri, manifest):
0N/A self.origin_fmri = fmri
0N/A self.origin_mfst = manifest
459N/A
0N/A if not os.path.exists("%s/pkg/%s/installed" % \
0N/A (self.image.imgdir, fmri.get_dir_path())):
0N/A raise RuntimeError, "not installed"
0N/A
0N/A def is_valid(self):
0N/A if self.origin_fmri == None:
0N/A return True
if not self.origin_fmri.is_same_pkg(self.destination_fmri):
return False
if self.origin_fmri > self.destination_fmri:
return False
return True
def get_actions(self):
return []
def evaluate(self, filters = []):
"""Determine the actions required to transition the package."""
# if origin unset, determine if we're dealing with an previously
# installed version or if we're dealing with the null package
#
# XXX Perhaps make the pkgplan creator make this explicit, so we
# don't have to check?
f = None
if not self.origin_fmri:
try:
f = self.image.get_version_installed(
self.destination_fmri)
self.origin_fmri = f
self.origin_mfst = self.image.get_manifest(f)
except LookupError:
pass
self.destination_filters = filters
# Try to load the filter used for the last install of the
# package.
self.origin_filters = []
if self.origin_fmri:
try:
f = file("%s/pkg/%s/filters" % \
(self.image.imgdir,
self.origin_fmri.get_dir_path()), "r")
except IOError, e:
if e.errno != errno.ENOENT:
raise
else:
self.origin_filters = [
(l.strip(), compile(
l.strip(), "<filter string>", "eval"))
for l in f.readlines()
]
self.destination_mfst.filter(self.destination_filters)
self.origin_mfst.filter(self.origin_filters)
# Assume that origin actions are unique, but make sure that
# destination ones are.
ddups = self.destination_mfst.duplicates()
if ddups:
raise RuntimeError, ["Duplicate actions", ddups]
self.actions = self.destination_mfst.difference(
self.origin_mfst)
def preexecute(self):
"""Perform actions required prior to installation or removal of a package.
This method executes each action's preremove() or preinstall()
methods, as well as any package-wide steps that need to be taken
at such a time.
"""
flist = None
flist_supported = True
# retrieval step
if self.destination_fmri == None:
os.unlink("%s/pkg/%s/installed" % (self.image.imgdir,
self.origin_fmri.get_dir_path()))
try:
os.unlink("%s/pkg/%s/filters" % (
self.image.imgdir,
self.origin_fmri.get_dir_path()))
except OSError, e:
if e.errno != errno.ENOENT:
raise
for src, dest in self.actions:
if dest:
dest.preinstall(self, src)
else:
src.preremove(self)
if dest and dest.needsdata(src) and flist_supported:
if flist and flist.is_full():
try:
flist.get_files()
except filelist.FileListException:
flist_supported = False
flist = None
continue
flist = None
if flist is None:
flist = filelist.FileList(
self.image,
self.destination_fmri)
flist.add_action(dest)
# Get any remaining files
if flist:
try:
flist.get_files()
except filelist.FileListException:
pass
flist = None
def execute(self):
"""Perform actions for installation or removal of a package.
This method executes each action's remove() or install()
methods.
"""
# record that we are in an intermediate state
# It might be nice to have a single action.execute() method, but
# I can't think of an example where it would make especially
# good sense (i.e., where "remove" is as similar to "upgrade" as
# is "install").
for src, dest in self.actions:
if dest:
try:
dest.install(self, src)
except Exception, e:
print "Action install failed for '%s' (%s):\n %s: %s" % \
(dest.attrs.get(dest.key_attr, id(dest)),
self.destination_fmri.get_pkg_stem(),
e.__class__.__name__, e)
raise
else:
src.remove(self)
def postexecute(self):
"""Perform actions required after installation or removal of a package.
This method executes each action's postremove() or postinstall()
methods, as well as any package-wide steps that need to be taken
at such a time.
"""
# record that package states are consistent
for src, dest in self.actions:
if dest:
dest.postinstall(self, src)
else:
src.postremove(self)
# In the case of an upgrade, remove the installation turds from
# the origin's directory.
# XXX should this just go in preexecute?
if self.origin_fmri != None and self.destination_fmri != None:
os.unlink("%s/pkg/%s/installed" % (self.image.imgdir,
self.origin_fmri.get_dir_path()))
try:
os.unlink("%s/pkg/%s/filters" % (
self.image.imgdir,
self.origin_fmri.get_dir_path()))
except OSError, e:
if e.errno != errno.ENOENT:
raise
if self.destination_fmri != None:
file("%s/pkg/%s/installed" % (self.image.imgdir,
self.destination_fmri.get_dir_path()), "w")
# Save the filters we used to install the package, so
# they can be referenced later.
if self.destination_filters:
f = file("%s/pkg/%s/filters" % \
(self.image.imgdir,
self.destination_fmri.get_dir_path()), "w")
f.writelines([
filter + "\n"
for filter, code in self.destination_filters
])
f.close()
def make_indices(self):
"""Create the reverse index databases for a particular package.
These are the databases mapping packaging object attribute
values back to their corresponding packages, allowing the
packaging system to look up a package based on, say, the
basename of a file that was installed.
XXX Need a method to remove what we put down here.
"""
# XXX bail out, for now.
if self.destination_fmri == None:
return
target = os.path.join("..", "..", "..", "pkg",
self.destination_fmri.get_dir_path())
gen = (
(k, v)
for src, dest in self.actions
if dest
for k, v in dest.generate_indices().iteritems()
)
for idx, val in gen:
idxdir = os.path.join(self.image.imgdir, "index", idx)
try:
os.makedirs(idxdir)
except OSError, e:
if e.errno != errno.EEXIST:
raise
if not isinstance(val, list):
val = [ val ]
for v in val:
dir = os.path.join(idxdir, v)
try:
os.makedirs(dir)
except OSError, e:
if e.errno != errno.EEXIST:
raise
link = os.path.join(dir,
self.destination_fmri.get_url_path())
# If the value has slashes in it, link will be
# that many directories further down, so we need
# to add suffcient parent dirs to get back up to
# the right level.
extra = os.path.sep.join(("..",) * v.count("/"))
if not os.path.lexists(link):
os.symlink(
os.path.join(extra, target), link)