pkgplan.py revision 66
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/Aimport re
50N/Aimport urllib
50N/A
50N/Aimport pkg.catalog as catalog
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
50N/A self.origin_mfst = None
50N/A self.destination_mfst = None
50N/A
50N/A self.image = image
50N/A
50N/A self.actions = []
50N/A
63N/A def __str__(self):
63N/A return "%s -> %s" % (self.origin_fmri, self.destination_fmri)
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
50N/A def evaluate(self):
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
50N/A if self.origin_fmri == None:
50N/A try:
59N/A f = self.image.get_version_installed(
59N/A self.destination_fmri)
59N/A self.origin_fmri = f
50N/A except LookupError:
50N/A pass
50N/A
50N/A # if null package, then our plan is the set of actions for the
50N/A # destination version
59N/A if self.origin_fmri == None:
50N/A self.actions = self.destination_mfst.actions
66N/A elif self.destination_fmri == None:
66N/A # XXX
66N/A self.actions = sorted(self.origin_mfst.actions,
66N/A reverse = True)
50N/A else:
59N/A # if a previous package, then our plan is derived from
59N/A # the set differences between the previous manifest's
59N/A # actions and the union of the destination manifest's
59N/A # actions with the critical actions of the critical
59N/A # versions in the version interval between origin and
59N/A # destination.
59N/A if not self.image.has_manifest(self.origin_fmri):
59N/A retrieve.get_manifest(self.image,
59N/A self.origin_fmri)
59N/A
59N/A self.origin_mfst = self.image.get_manifest(
59N/A self.origin_fmri)
59N/A
50N/A self.actions = self.destination_mfst.difference(
50N/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 """
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
66N/A for a in self.actions:
66N/A if self.destination_fmri == None:
66N/A a.preremove(self.image)
66N/A else:
66N/A a.preinstall(self.image)
66N/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 """
66N/A # record that we are in an intermediate state
66N/A for a in self.actions:
66N/A if self.destination_fmri == None:
66N/A a.remove(self.image)
66N/A else:
66N/A a.install(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
66N/A for a in self.actions:
66N/A if self.destination_fmri == None:
66N/A a.postremove()
66N/A else:
66N/A a.postinstall()
66N/A
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
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
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)
60N/A for action in self.actions
60N/A for k, v in action.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
60N/A if not os.path.lexists(link):
60N/A os.symlink(target, link)