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