pkgplan.py revision 195
17848N/A#!/usr/bin/python
17848N/A#
17848N/A# CDDL HEADER START
17848N/A#
17848N/A# The contents of this file are subject to the terms of the
17848N/A# Common Development and Distribution License (the "License").
17848N/A# You may not use this file except in compliance with the License.
17848N/A#
17848N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
18902N/A# or http://www.opensolaris.org/os/licensing.
17848N/A# See the License for the specific language governing permissions
17848N/A# and limitations under the License.
18003N/A#
18003N/A# When distributing Covered Code, include this CDDL HEADER in each
18003N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
18003N/A# If applicable, add the following below this CDDL HEADER, with the
18003N/A# fields enclosed by brackets "[]" replaced with your own identifying
18003N/A# information: Portions Copyright [yyyy] [name of copyright owner]
18003N/A#
17848N/A# CDDL HEADER END
17848N/A#
17848N/A
18688N/A# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
18688N/A# Use is subject to license terms.
17848N/A
17848N/Aimport errno
17848N/Aimport os
17848N/A
17848N/Aimport pkg.manifest as manifest
17848N/Aimport pkg.client.filelist as filelist
17848N/A
17848N/Aclass PkgPlan(object):
18017N/A """A package plan takes two package FMRIs and an Image, and produces the
18993N/A set of actions required to take the Image from the origin FMRI to the
18993N/A destination FMRI.
18017N/A
17848N/A If the destination FMRI is None, the package is removed.
17848N/A """
17848N/A
17848N/A def __init__(self, image):
18003N/A self.origin_fmri = None
18003N/A self.destination_fmri = None
18003N/A self.origin_mfst = manifest.null
18003N/A self.destination_mfst = manifest.null
18003N/A
18003N/A self.image = image
18003N/A
17848N/A self.actions = []
17848N/A
18003N/A def __str__(self):
18164N/A s = "%s -> %s\n" % (self.origin_fmri, self.destination_fmri)
18164N/A
17848N/A for src, dest in self.actions:
18003N/A s += " %s -> %s\n" % (src, dest)
18003N/A
17848N/A return s
18003N/A
18003N/A def set_origin(self, fmri):
18003N/A self.origin_fmri = fmri
17848N/A self.origin_mfst = manifest.retrieve(fmri)
17848N/A
17848N/A def propose_destination(self, fmri, manifest):
17848N/A self.destination_fmri = fmri
18003N/A self.destination_mfst = manifest
18003N/A
18003N/A if self.image.install_file_present(fmri):
18003N/A raise RuntimeError, "already installed"
18003N/A
17848N/A def propose_removal(self, fmri, manifest):
17848N/A self.origin_fmri = fmri
17848N/A self.origin_mfst = manifest
17848N/A
17848N/A if not self.image.install_file_present(fmri):
17848N/A raise RuntimeError, "not installed"
17848N/A
17848N/A def is_valid(self):
17848N/A if self.origin_fmri == None:
17848N/A return True
17848N/A
17848N/A if not self.origin_fmri.is_same_pkg(self.destination_fmri):
17848N/A return False
17848N/A
17848N/A if self.origin_fmri > self.destination_fmri:
17848N/A return False
18003N/A
18003N/A return True
18003N/A
18003N/A def get_actions(self):
18003N/A return []
18003N/A
18003N/A def evaluate(self, filters = []):
17848N/A """Determine the actions required to transition the package."""
17848N/A # if origin unset, determine if we're dealing with an previously
17848N/A # installed version or if we're dealing with the null package
17848N/A #
17848N/A # XXX Perhaps make the pkgplan creator make this explicit, so we
18164N/A # don't have to check?
18164N/A f = None
18017N/A if not self.origin_fmri:
18017N/A try:
18003N/A f = self.image.get_version_installed(
18003N/A self.destination_fmri)
17848N/A self.origin_fmri = f
17848N/A self.origin_mfst = self.image.get_manifest(f)
17848N/A 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:
self.image.remove_install_file(self.origin_fmri)
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:
self.image.remove_install_file(self.origin_fmri)
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:
self.image.add_install_file(self.destination_fmri)
# 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()