Viewing file: InstallUtil.py (42.87 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
######################################################################## # $Header: /var/local/cvsroot/4Suite/Ft/Server/Common/Install/InstallUtil.py,v 1.72 2005/04/07 00:18:47 jkloth Exp $ """ Definitions and functions used in the installation process
Copyright 2004 Fourthought, Inc. (USA). Detailed license and copyright information: http://4suite.org/COPYRIGHT Project home, documentation, distributions: http://4suite.org/ """
import sys, os, sha, urllib, stat from Ft.Server.Common import Schema, AclConstants from Ft.Server.Common.ResourceTypes import ResourceType from Ft.Lib import Time, ProgressIndicator from distutils import file_util, dep_util
from Ft.Xml import XPath, Domlette, ReaderException, EMPTY_NAMESPACE from Ft.Xml.Xslt import XSL_NAMESPACE from Ft.Lib import Uri from Ft.Lib import Resolvers from Ft.Xml import InputSource
class StatusFlags: UNKNOWN = -1 NO_CHANGE = 0 NOT_PRESENT = 1 CONTENT_CHANGE = 2 IMT_CHANGE = 4 ACL_CHANGE = 8 OWNER_CHANGE = 16 OTHER_CHANGE = 32
ACTION_REQUIRED = NOT_PRESENT | CONTENT_CHANGE | IMT_CHANGE | ACL_CHANGE | OWNER_CHANGE | OTHER_CHANGE
g_safeAclIdents = [AclConstants.SUPER_USER_GROUP_NAME, AclConstants.WORLD_GROUP_NAME, AclConstants.USERS_GROUP_NAME, AclConstants.ANONYMOUS_USER_NAME, AclConstants.OWNER]
class IndicatorBase: indicator = None def _message(self, msg, quiet): if not quiet: if self.indicator: self.indicator.message(msg) else: print msg
def _setIndicator(self, indicator): """ Store information about where we are in the indicator list """ self.indicator = indicator
class RawFile(IndicatorBase): resourceType = ResourceType.RAW_FILE
contentUri = None stringContent = None stringLastModified = None #specified in iso 8601 lmds = None owners = {} imts = {}
def __init__(self, path, acl, owner, imt): self.path = path self.absPath = path + ';no-traverse'
#Cache any changes to ACL self._aclToUpdate = {} self._aclToRemove = []
self.owner = owner self.imt = imt or 'text/plain'
self.status = StatusFlags.UNKNOWN self.dependencies = [] if acl is None: acl = [] self._setAcl(acl) return
def _setAcl(self, acl): """ Helper function for test harnesses only """ self.acl = acl self.aclDict = {} for access, ident, allowed in acl: if access not in self.aclDict: self.aclDict[access] = {} self.aclDict[access][ident] = allowed return
def setPath(self, contentUri): self.contentUri = contentUri return
def setSource(self, stringContent, stringLastModified): self.stringContent = stringContent self.stringLastModified = stringLastModified return
def checkStatus(self, repo, force=0, checkAcl=1): """ Get the status of this resource from against the supplied repo, Note, force here specifies that we force update to the status. Not force install """
if not force and self.status != StatusFlags.UNKNOWN: return self.status
self._initRdfCaches(repo)
if self.path not in self.lmds: self.status = StatusFlags.ACTION_REQUIRED
#Force the ACL changes self._aclToUpdate = self.aclDict
#Exit here return self.status self.status = StatusFlags.NO_CHANGE
# Compare the file times. localTime = None if self.contentUri is not None: urlfile = Uri.UrlOpen(self.contentUri) try: mtime = os.fstat(urlfile.fileno())[stat.ST_MTIME] except OSError: # Window's socket streams cannot be stat'ed # Get the last modified date from the headers, if possible modified = urlfile.info().getheader('Last-Modified') if modified: datetime = Time.FromRFC822(modified) else: # Unable to determine last modified date, use current # time to force an update datetime = Time.FromPythonTime() else: datetime = Time.FromPythonTime(mtime)
urlfile.close() localTime = datetime.asISO8601DateTime() elif self.stringLastModified is not None: localTime = self.stringLastModified
if localTime is not None: # Compare it with the repo. Some things (i.e., containers) don't # have a modified date check. if localTime > self.lmds[self.path]: self.status = self.status | StatusFlags.CONTENT_CHANGE
# Check for other kinds of changes if checkAcl: self._checkAclChange(repo)
self._checkImtChange(repo) self._checkOwnerChange(repo) self._checkOtherChange(repo)
return self.status
def createContent(self, repo, quiet): """ Called when we need to add our content to the repo """ self._message("Create RawFile: %s IMT=%s"%(self.path, self.imt), quiet) repo.createRawFile(self.path, self.imt, self._getContent()) #Don't need to reset this on a create self.status &= ~StatusFlags.IMT_CHANGE return
def updateContent(self, repo, quiet): """ Called when we need to update our content in the repo """ self._message("Modify %s content"%self.path, quiet) repo.fetchResource(self.path).setContent(self._getContent()) return
def updateImt(self, repo, quiet): """ Called when we need to update our imt in the repo """ self._message("Setting IMT of %s --> %s" % (self.path, self.imt), quiet) repo.fetchResource(self.absPath).setImt(self.imt) return
def updateOwner(self, repo, quiet): """ Called when we need to update our owner in the repo """ self._message("Setting Owner of %s --> %s" % (self.path, self.owner), quiet) owner = repo.fetchUserOrGroupByName(self.owner) #Owner will be None if 'world' if owner: repo.fetchResource(self.absPath).setOwner(owner) return
def updateAcl(self, repo, quiet): """ Called when we need to update our acl in the repo """ res = repo.fetchResource(self.absPath) for access in self._aclToRemove: self._message("Removing ACL: %s from %s" % (access, self.path), quiet) res.inheritAcl(access)
for access, info in self._aclToUpdate.items(): self._message("Setting ACL: %s for %s" % (access, self.path), quiet) l = info.items() for ident, allowed in l[:1]: res.setAcl(access, ident, allowed) for ident, allowed in l[1:]: res.addAcl(access, ident, allowed) return
def updateOther(self, repo, quiet): """ Called when we need to update our other stuff in the repo """ #The resource type might be changing, in which case we need to abort #the update, delete the existing resource, and install the new. #Aborting at this point could get ugly, so for now, just raise an #exception. FIXME! doc = repo.fetchResource(self.absPath) if doc.resourceType != self.resourceType: raise RuntimeError("Resource type change for %s not supported; please delete it first." % self.path) return
def _getContent(self): if self.stringContent: return self.stringContent elif self.contentUri: urlfile = Uri.UrlOpen(self.contentUri) content = urlfile.read() urlfile.close() return content else: raise RuntimeError("Attempting to update %s with no content" % self.path) return
def _checkAclChange(self, repo): """ Look for changes in the ACL """ res = repo.fetchResource(self.absPath)
cur = res.getAcl()
for access, info in self.aclDict.items(): if access not in cur: #Add the entire access self._aclToUpdate[access] = info else: #Verify all of the keys are the same for ident, allowed in info.items(): if cur[access].get(ident, -1) != allowed: #The access is the same, but the ident/allowed is different #Add the whole thing self._aclToUpdate[access] = info break else: #Make sure that cur doesn't define any extra idents for ident, allowed in cur[access].items(): if ident not in self.aclDict[access]: #It does so rewrite the entire access self._aclToUpdate[access] = info break
#Lastly, make sure that cur does not define any other access for access in cur: if access not in self.aclDict: self._aclToRemove.append(access)
if len(self._aclToUpdate) or len(self._aclToRemove): self.status |= StatusFlags.ACL_CHANGE
return
def _checkImtChange(self, repo): """ Look for changes in the IMT """ if self.imt and self.imt != self.imts[self.path]: self.status |= StatusFlags.IMT_CHANGE return
def _checkOwnerChange(self, repo): """ Look for changes in the Owner """ if self.owner and self.owner != self.owners[self.path]: self.status |= StatusFlags.OWNER_CHANGE return
def _checkOtherChange(self, repo): """ Look for other changes """ pass
def _setBase(self, basePath): """ Adjust for a relative installation """ self.path = Uri.BaseJoin(basePath, self.path) self.absPath = self.path + ';no-traverse' return
def _gatherDependencies(self, basePath): """ Get a list of dependencies for this resource. At the RawFile level, it is dependencies based on path """ self.dependencies = [] base = os.path.dirname(self.path) if not (base == basePath or base + '/' == basePath): self.dependencies.append(base) return
def _initRdfCaches(self, repo): """ We use RDF completes to get a lot of the information about the resources. This speeds up installation a lot. """ if self.lmds is not None: return RawFile.lmds = {} for lmd in repo.getModel().complete(None, Schema.MODIFIED_DATE, None): self.lmds[lmd.subject] = lmd.object
for imt in repo.getModel().complete(None, Schema.IMT, None): self.imts[imt.subject] = imt.object
for owner in repo.getModel().complete(None, Schema.OWNER, None): self.owners[owner.subject] = owner.object return
def __repr__(self): return "<%s to be installed at %s id=%s>" % (self.__class__.__name__, self.path, str(hex(id(self)))) class UriReferenceFile(RawFile): resourceType = ResourceType.URI_REFERENCE_FILE
_refs = None
def __init__(self, path, acl, owner, imt, href): self.href = href RawFile.__init__(self, path, acl, owner, imt) return
def _checkOtherChange(self, repo): """ See if the doc def has changed """ RawFile._checkOtherChange(self, repo) if self.path not in self._refs or self.href != self._refs[self.path]: self.status |= StatusFlags.OTHER_CHANGE return
def createContent(self, repo, quiet): """ Called when we need to add our content to the repo """ self._message("Create UriReferenceFile: %s IMT=%s reference=%s"%(self.path, self.imt, self.href), quiet) repo.createUriReferenceFile(self.path, self.imt, self.href) #Don't need to reset this on a create self.status &= ~StatusFlags.IMT_CHANGE return
def updateContent(self, repo, quiet): """ Called when we need to update our content in the repo """ raise RuntimeError("Unable to update content for %s" % self.path)
def _initRdfCaches(self, repo): RawFile._initRdfCaches(self, repo)
if self._refs is not None: return UriReferenceFile._refs = {} for ref in repo.getModel().complete(None, Schema.URI_REFERENCE_LOCATION, None): self._refs[ref.subject] = ref.object return
class XmlDocument(RawFile): resourceType = ResourceType.XML_DOCUMENT
_dds = None
docName='XmlDocument'
def __init__(self, path, acl, owner, imt, docDef): RawFile.__init__(self, path, acl, owner, imt) self.docDef = docDef if imt is None: self.imt = 'text/xml' return
def createContent(self, repo, quiet): """ Called when we need to add our content to the repo """ self._message("Create %s: %s IMT=%s%s" % (self.docName, self.path, self.imt, " docDef=" * (not not self.docDef) + (self.docDef or "")), quiet)
cnt = self._getContent() repo.createDocument(self.path, cnt, docDef = self.docDef, imt=self.imt, forcedType = self.resourceType)
#Don't need to reset this on a create self.status &= ~StatusFlags.IMT_CHANGE
#Note, we need to be smart in updateOther cause we already set the doc def return
def updateOther(self, repo, quiet): RawFile.updateOther(self, repo, quiet) doc = repo.fetchResource(self.absPath) curDD = doc.getDocumentDefinition() if (curDD is None and self.docDef is not None) or (curDD is not None and curDD.getAbsolutePath() != self.docDef):
self._message("Setting %s document definition to %s" % (self.path, self.docDef), quiet) dd = None if self.docDef is not None: dd = doc.fetchResource(self.docDef) doc.setDocumentDefinition(dd) return
def _setBase(self, basePath): """ Adjust for a relative installation """ RawFile._setBase(self, basePath) if self.docDef is not None: self.docDef = Uri.BaseJoin(basePath, self.docDef) return
def _checkOtherChange(self, repo): """ See if the doc def has changed """ RawFile._checkOtherChange(self, repo) if self.path not in self._dds or self.docDef != self._dds[self.path]: self.status = self.status | StatusFlags.OTHER_CHANGE return
def _getContent(self): # note this might be overridden by a descendant class if self.stringContent: return self.stringContent elif self.contentUri: urlfile = Uri.UrlOpen(self.contentUri) content = urlfile.read() urlfile.close() return content return
def _gatherDependencies(self, basePath): RawFile._gatherDependencies(self, basePath) #Find dependencies from resolution needed during parsing #FIXME: Only works for one resolution dependency at a time # # the general idea here is to attempt to parse the XML. # if one of the document's external entities or xincludes # aren't readable, then the XML reader will report this as # a UriException from which we can usually get the URI of # whatever couldn't be read. self.cachedDoc = None resolve_requests = [] #Use a closure to log all requests to the resolver def resolve_hook(uri, base): resolve_requests.append(uri) resolver = Resolvers.FacadeResolver(observer=resolve_hook) factory = InputSource.InputSourceFactory(resolver=resolver) #Using NoExtDtdReaderBase is a cop out, basically, for the difficulty #Of determining how to dummy out reader = Domlette.NonvalidatingReaderBase(inputSourceFactory=factory) doc = self._getContent() if not doc: # nothing to parse, which is probably a problem, # but as far as dependency checking goes, is OK return if self.contentUri: uri = self.contentUri else: # if we don't have a real base URI for the content # then we won't be able to properly resolve any # relative URI refs of external entities / xincludes. # but we should try to parse anyway in order to detect # unreadable ext. entities / xincludes that are # referenced by absolute URIs. uri = 'urn:dummy:xml-string-content-for-%s' % self.path #Until parse is successful... while not self.cachedDoc: try: doc = reader.parseString(doc, uri) self.cachedDoc = doc except Uri.UriException, e: #print >> sys.stderr, 'dependency found:', e.params[0]['uri'], '\n', e.params code = e.errorCode if code == Uri.UriException.RESOURCE_ERROR: # 1st arg to the err msg is assumed to be a dict # in which the 'loc' item is the URI # of the unreadable entity or xinclude self.dependencies.append(e.params[0]['loc']) #Can't be '' in case it's to be used as stand-in for an #XInclude resolver.cache[e.params[0]['uri']] = '<dummy/>' elif code in (Uri.UriException.INVALID_BASE_URI, Uri.UriException.RELATIVE_BASE_URI, Uri.UriException.SCHEME_REQUIRED): # 1st arg to the err msg is assumed to be a dict # in which the 'ref' item is the URI ref of the # entity or xinclude that we couldn't attempt to # read, possibly due to a bad base URI self.dependencies.append( '%s - possibly bad a base URI %s' % (e.params[0]['ref'], e.params[0]['base'])) else: self.dependencies.append('an external entity or XInclude (URI unknown)') except ReaderException, e: if not resolver.cache.get(e.params[0]): #Either this didn't come from cached resolution (get() -> None) or #We've already fallen back to trying the empty/dummy resource (value == '') #So just abort raise else: resolver.cache[e.params[0]] = '' except: raise if self.docDef is not None: self.dependencies.append(self.docDef) #Everything encountered in the facade resolver is also a dependency #Comment out for now because the blasted dependency sorting logic is broken #self.dependencies.extend(resolve_requests) return
def _initRdfCaches(self, repo): RawFile._initRdfCaches(self, repo)
if self._dds is not None: return XmlDocument._dds = {} for dd in repo.getModel().complete(None, Schema.DOCDEF, None): if dd.object == Schema.NULL_DOCDEF: self._dds[dd.subject] = None else: self._dds[dd.subject] = dd.object return
class Container(XmlDocument): resourceType = ResourceType.CONTAINER
def __init__(self, path, acl, owner, imt, docDef): if path[-1] == '/' and len(path) > 1: path = path[:-1] XmlDocument.__init__(self, path, acl, owner, imt, docDef) return
def createContent(self, repo, quiet): self._message("Create Container: %s" % self.path, quiet) repo.createContainer(self.path, 1) if self.imt == 'text/xml': self.status &= ~StatusFlags.IMT_CHANGE return
def updateContent(self, repo, quiet): """ Called when we need to update our content in the repo """ raise RuntimeError("Unable to update content for %s" % self.path)
from Ft.Share import ExtensionModules _EXT_NAME = ExtensionModules.__name__ _EXT_DIR = os.path.dirname(ExtensionModules.__file__) del ExtensionModules
class ExtensionModule(IndicatorBase): resourceType = None owner=-1 def __init__(self, file): if not file: raise Exception('File attribute not given') self._file = Uri.UriToOsPath(file, attemptAbsolute=False) self.path = ''
basename = os.path.basename(self._file) self._dest = os.path.join(_EXT_DIR, basename) self.moduleName = _EXT_NAME + '.' + os.path.splitext(basename)[0] self.status = StatusFlags.UNKNOWN return
def createContent(self, repo, quiet): #repo.copyFile(open(self._file, 'r').read(), ) res = file_util.copy_file(self._file, self._dest, update=1, ) if res[1]: self._message("Copy extension module: %s" % self._file, quiet) return
def updateContent(self, repo, quiet): self.createContent(repo, quiet) repo.reloadModule(self.moduleName)
def checkStatus(self, repo, force=0, checkAcl = 1): self.status = StatusFlags.NO_CHANGE if os.path.exists(self._dest): if force or dep_util.newer(self._file, self._dest): self.status = StatusFlags.CONTENT_CHANGE else: self.status = StatusFlags.NOT_PRESENT return self.status
def _setBase(self, basePath): return
def _gatherDependencies(self, basePath): self.dependencies = []
def setPath(self, fName): pass
class XsltDocument(XmlDocument): resourceType = ResourceType.XSLT_DOCUMENT dependencyXPath = XPath.Compile('/xsl:*/xsl:import | /xsl:*/xsl:include') docName = 'XsltDocument'
def _gatherDependencies(self, basePath): """ The dependencies of a Xslt document are all xslt:include and xslt:import elements """ XmlDocument._gatherDependencies(self, basePath) if not self.cachedDoc: return con = XPath.Context.Context(self.cachedDoc, processorNss={'xsl': XSL_NAMESPACE})
for dep in self.dependencyXPath.evaluate(con): d = dep.getAttributeNS(EMPTY_NAMESPACE, 'href') if d: if ':' in d: #External to the system (not a safe assumption, really) continue if d[0] != '/': d = Uri.BaseJoin(self.path, d) self.dependencies.append(d) return
class SchematronDocument(XmlDocument): resourceType = ResourceType.SCHEMATRON_DOCUMENT docName = 'SchematronDocument'
class RdfDocument(XmlDocument): resourceType = ResourceType.RDF_DOCUMENT docName = 'RdfDocument'
class XsltDocumentDefinition(XmlDocument): resourceType = ResourceType.XSLT_DOCUMENT_DEFINITION docName = 'XsltDocumentDefinition'
class XPathDocumentDefinition(XmlDocument): resourceType = ResourceType.XPATH_DOCUMENT_DEFINITION docName = 'XPathDocumentDefinition'
class Server(XmlDocument): resourceType = ResourceType.SERVER docName = 'Server'
class Command(XmlDocument): resourceType = ResourceType.COMMAND docName = 'Command'
class Alias(XmlDocument): resourceType = ResourceType.ALIAS _refs = None
def __init__(self, path, acl, owner, imt, docDef, reference): self.reference = reference XmlDocument.__init__(self, path, acl, owner, imt, docDef)
if self.reference[0] != '/': #Make it absolute self.reference = Uri.BaseJoin(self.path, self.reference) return
def createContent(self, repo, quiet): """ Called when we need to add our content to the repo """ self._message("Create Alias: %s" % self.path, quiet)
doc = repo.fetchResource(self.reference) doc.addAlias(self.path)
if self.imt == 'text/xml': self.status &= ~StatusFlags.IMT_CHANGE
#Note, we need to be smart in updateOther cause we already set the reference return
def updateContent(self, repo, quiet): """ Called when we need to update our content in the repo """ raise RuntimeError("Unable to update content for %s" % self.path)
def updateOther(self, repo, quiet): XmlDocument.updateOther(self, repo, quiet)
#You cannot change an aliases reference, so raise exception for now, we could lose ACL, owner, etc if repo.fetchResource(self.absPath).getReference().getAbsolutePath() != self.reference: raise RuntimeError("Unable to change the alias reference for %s, please remove alias by hand. You can do so on the command line using '4ss delete --no-traverse %s'" % (self.path, self.path)) return
def _checkOtherChange(self, repo): """ See if the doc def has changed """ XmlDocument._checkOtherChange(self, repo) if self.path not in self._refs or self.reference != self._refs[self.path]: self.status |= StatusFlags.OTHER_CHANGE return
def _setBase(self, basePath): """ Adjust for a relative installation """ XmlDocument._setBase(self, basePath) self.reference = Uri.BaseJoin(self.path, self.reference) return
def _gatherDependencies(self, basePath): XmlDocument._gatherDependencies(self, basePath) self.dependencies.append(self.reference) return
def _initRdfCaches(self, repo): XmlDocument._initRdfCaches(self, repo)
if self._refs is not None: return Alias._refs = {} for ref in repo.getModel().complete(None, Schema.ALIAS_REFERENCE, None): self._refs[ref.subject] = ref.object return
class User(XmlDocument): resourceType = ResourceType.USER
def __init__(self, userName, acl, owner, imt, docDef, basePath, password=None, data={}, passwdHash=None): XmlDocument.__init__(self, basePath + '/' + userName, acl, owner, imt, docDef) self.userName = userName self.basePath = basePath
if passwdHash: self.passwordHash = passwdHash self.password = password else: self.passwordHash = sha.new(password).hexdigest() self.password = password
self.data = data return
def createContent(self, repo, quiet): """ Called when we need to add our content to the repo """ self._message("Create User: %s%s" % (self.path, " docDef=" * (not not self.docDef) + (self.docDef or "")), quiet)
newUser=repo.fetchResource(self.basePath).createUser(self.userName, self.passwordHash, self.docDef) newUser.setUserData(self.data)
#Note, we need to be smart in updateOther cause we already set the password if self.imt == 'text/xml': self.status &= ~StatusFlags.IMT_CHANGE return
def updateContent(self, repo, quiet): """ Called when we need to update our content in the repo """ raise RuntimeError("Unable to update content for %s" % self.path)
def updateOther(self, repo, quiet): XmlDocument.updateOther(self, repo, quiet)
if repo.fetchResource(self.path).getPassword() != self.passwordHash: self._message("Updating User %s password" % self.userName, quiet) repo.fetchResource(self.path).setPassword(self.passwordHash)
if repo.fetchResource(self.path).getUserData() != self.data: self._message("Updating User %s's data" % self.userName, quiet) repo.fetchResource(self.path).setUserData(self.data) return
def _checkAclChange(self, repo):
#Users get thier ACL set automatically by the system. We need to account for that here. #So, we need to update the ACL list before we do the check, unless an ACL list was specified
if not self.acl: for key in [AclConstants.DELETE_ACCESS, AclConstants.CHANGE_PERMISSIONS_ACCESS, AclConstants.WRITE_USER_MODEL_ACCESS, AclConstants.CHANGE_OWNER_ACCESS, AclConstants.READ_ACCESS, AclConstants.EXECUTE_ACCESS, AclConstants.WRITE_ACCESS]: for ident in [self.userName, AclConstants.OWNER, repo.getUserName()]: self.acl.append((key, ident, AclConstants.ALLOWED)) return XmlDocument._checkAclChange(self, repo)
def _checkOtherChange(self, repo): """ See if the doc def has changed """ XmlDocument._checkOtherChange(self, repo) if self.passwordHash != repo.fetchResource(self.path).getPassword(): self.status |= StatusFlags.OTHER_CHANGE return
def _setBase(self, basePath): """ Adjust for a relative installation """ XmlDocument._setBase(self, basePath) self.basePath = Uri.BaseJoin(basePath, self.basePath) return
class Group(XmlDocument): resourceType = ResourceType.GROUP
def __init__(self, groupName, acl, owner, imt, docDef, basePath, members): XmlDocument.__init__(self, basePath + '/' + groupName, acl, owner, imt, docDef)
if not groupName: raise SyntaxError("Must specify a group name in a member definition") self.groupName = groupName self.members = members self.basePath = basePath
self._membersToAdd = [] self._membersToRemove = [] return
def checkStatus(self, repo, force=0, checkAcl=True): rt = XmlDocument.checkStatus(self, repo, force, checkAcl)
## Should this be 'self.status & StatusFlags.ACTION_REQUIRED'?? if self.status & StatusFlags.NOT_PRESENT: self._membersToAdd = self.members return rt
def createContent(self, repo, quiet): """ Called when we need to add our content to the repo """ self._message("Create Group: %s"%self.path, quiet) self._message("creating members: %s" % self.members, quiet) g = repo.fetchResource(self.basePath).createGroup(self.groupName) if self.imt == 'text/xml': self.status &= ~StatusFlags.IMT_CHANGE return
def updateContent(self, repo, quiet): """ Called when we need to update our content in the repo """ raise RuntimeError("Unable to update content for %s" % self.path)
def updateOther(self, repo, quiet): XmlDocument.updateOther(self, repo, quiet)
g = repo.fetchUserOrGroupByName(self.groupName) for m in self._membersToAdd: self._message("Adding member %s to group %s" % (m, self.groupName), quiet) mem = repo.fetchUserOrGroupByName(m) g.addMember(mem) for m in self._membersToRemove: self._message("Removing member %s from group %s" % (m, self.groupName), quiet) mem = repo.fetchUserOrGroupByName(m) g.removeMember(mem) return
def _checkAclChange(self, repo):
#Groups get thier ACL set automatically by the system. We need to account for that here. #So, we need to update the ACL list before we do the check, unless an ACL list was specified
if not self.acl: for key in [AclConstants.DELETE_ACCESS, AclConstants.CHANGE_PERMISSIONS_ACCESS, AclConstants.WRITE_USER_MODEL_ACCESS, AclConstants.CHANGE_OWNER_ACCESS, AclConstants.READ_ACCESS, AclConstants.EXECUTE_ACCESS, AclConstants.WRITE_ACCESS]: for ident in [self.groupName, AclConstants.OWNER, repo.getUserName()]: self.acl.append((key, ident, AclConstants.ALLOWED)) return XmlDocument._checkAclChange(self, repo)
def _checkOtherChange(self, repo): """ See if the doc def has changed """ XmlDocument._checkOtherChange(self, repo)
curMembers = [r.getUsername() for r in repo.fetchUserOrGroupByName(self.groupName).getMembers()] for m in curMembers: if m not in self.members: self._membersToRemove.append(m) for m in self.members: if m not in curMembers: self._membersToAdd.append(m) if len(self._membersToRemove) or len(self._membersToAdd): self.status |= StatusFlags.OTHER_CHANGE return
def _setBase(self, basePath): """ Adjust for a relative installation """ XmlDocument._setBase(self, basePath) self.basePath = Uri.BaseJoin(basePath, self.basePath) return
def _gatherDependencies(self, basePath): XmlDocument._gatherDependencies(self, basePath) for m in self.members: if m not in g_safeAclIdents: self.dependencies.append(m) return
class Product: name = "" version = 0 description = ""
def __init__(self, resourceList, name='', version=0, description='', useIndicator=None): self.resourceList = resourceList self.filteredList = None self.sortedList = None self.notDirectlyChangedList = None self.basePath = '/' self.name = name self.version = version self.description = description if useIndicator is None: useIndicator = sys.stdout.isatty() self.useIndicator = useIndicator return
def setBase(self, basePath): """ Processes self.resourceList, setting the destination container on each resource to be the given basePath. """ # append leading and trailing slashes if absent if basePath[-1] != '/': basePath += '/' if basePath[0] != '/': basePath = '/' + basePath
for r in self.resourceList: r._setBase(basePath) self.basePath = basePath return
def setIndicator(self): """ Processes self.resourceList, hooking each resource into the progress indicator. """ for r in self.resourceList: r._setIndicator(self.progressIndicator) return
def filter(self, repo, checkAcl=True): """ Processes self.resourceList, creating (if not already created) 3 more lists: self.sortedList, which is the resources that have content or docdef changes and thus need to have their dependencies expanded; self.filteredList, which is the resources that have other changes; and self.notDirectlyChangedList, which is resources with no changes. """ if self.filteredList is not None: return
# resources w/content or docdef changes self.sortedList = [] # resources w/minor changes self.filteredList = [] # resources with no direct changes self.notDirectlyChangedList = []
for r in self.resourceList: status = r.checkStatus(repo, checkAcl=checkAcl) if status == StatusFlags.UNKNOWN: raise RuntimeError("Status of %s not determined" % r.path) if status & (StatusFlags.CONTENT_CHANGE | StatusFlags.NOT_PRESENT): self.sortedList.append(r) elif status == StatusFlags.NO_CHANGE: self.notDirectlyChangedList.append(r) else: self.filteredList.append(r) return
def sort(self, repo, quiet=True): # Sort our resources by dependency
# We assume that the resources have been filtered, so first gather # dependencies on the sortedList for r in self.sortedList: r._gatherDependencies(self.basePath)
sorted = [] provides = [] # list of resource paths unsorted = self.sortedList
# Transfer from the unsorted list to the res list. If we don't # transfer any, then blindly move one over and print an error satisfied = True while unsorted and satisfied: # Start off by assuming nothing will be satified this round satisfied = []
# Find all resources whose dependencies are satisfied for resource in unsorted: for path in resource.dependencies: if path not in provides: if repo.hasResource(path): provides.append(path) else: # Unknown break else: # All dependencies satisfied satisfied.append(resource) provides.append(resource.path) if resource.resourceType == ResourceType.USER: provides.append(resource.userName) elif resource.resourceType == ResourceType.GROUP: provides.append(resource.groupName)
for resource in satisfied: sorted.append(resource) unsorted.remove(resource)
# Gather the paths which have failed for the remaining resources if unsorted: failed = [] for resource in unsorted: for path in resource.dependencies: if path not in provides: failed.append((path, resource.path))
failed.sort() sys.stderr.write("Failed dependencies:\n") for item in failed: sys.stderr.write("\t%s needed by %s\n" % item) sys.stderr.flush()
#Add to sorted list any whose deps have changed sorted_paths = dict([ (r.path, None) for r in sorted ]) lookupPath = dict([ (r.path, r) for r in self.notDirectlyChangedList ]) deps_cache = {} def needsUpdate(path): if unicode(path) in sorted_paths: return True resource = lookupPath.get(path) if not resource: return False deps = deps_cache.get(resource) if deps is None: resource._gatherDependencies(self.basePath) deps = resource.dependencies deps_cache[resource] = deps #print "*"*10, "Dependencies:", deps for dep in deps: if needsUpdate(dep): sorted.append(resource) sorted_paths[resource.path] = None #print "*"*10, "Appending to sorted", path return True return False
#import pprint; pprint.pprint(lookupPath.keys()) for resource in self.notDirectlyChangedList: #Mutates sorted list itself, so we don't care about return val #Blah blah ugly use of side effects blah blah. :-) needsUpdate(resource.path)
self.sortedList = sorted return not unsorted
def serialize(self, stream=sys.stdout, refUri=''): import Serialize stream.write(Serialize.Serialize(self, refUri)) return
def install(self, repo, basePath, quiet, do_update=False, checkAcl=True): """ Install the product into the repo """ action = ('Installing/Updating', 'Updating')[do_update and 1 or 0] header = "" if self.version: header = "\n%s %s version %s" % (action, self.name, self.version) else: header = "\n%s %s" % (action, self.name) print header
self.setBase(basePath) self.filter(repo, checkAcl=checkAcl)
if not self.sort(repo): raise RuntimeError("Unresolved dependencies exist")
if self.useIndicator: #Calculate the total size #We go over the resource list twice, filter, sort, header total = len(self.sortedList) + len(self.sortedList) + len(self.filteredList) if total: self.progressIndicator = ProgressIndicator.ProgressIndicator(self.name) self.progressIndicator.newIndicator(total) self.setIndicator()
ctr = 0 #First install the sorted list for r in self.sortedList: if r.status & StatusFlags.NOT_PRESENT: r.createContent(repo, quiet) #Might need to remove owner update if not specified if r.owner is None: r.status &= ~StatusFlags.OWNER_CHANGE
else: r.updateContent(repo, quiet) r.status = r.status & ~StatusFlags.CONTENT_CHANGE
# save some memory, assuming we're done with cached docs if hasattr(r, 'cachedDoc'): r.cachedDoc = None if hasattr(r, 'stringContent'): r.stringContent = None
if self.useIndicator: self.progressIndicator.updateProgress(ctr) ctr += 1
#Then the filtered list for r in self.filteredList + self.sortedList: if r.status & StatusFlags.IMT_CHANGE: r.updateImt(repo, quiet) r.status &= ~StatusFlags.IMT_CHANGE if r.status & StatusFlags.ACL_CHANGE: r.updateAcl(repo, quiet) r.status &= ~StatusFlags.ACL_CHANGE if r.status & StatusFlags.OWNER_CHANGE: r.updateOwner(repo, quiet) r.status &= ~StatusFlags.OWNER_CHANGE if r.status & StatusFlags.OTHER_CHANGE: r.updateOther(repo, quiet) r.status &= ~StatusFlags.OTHER_CHANGE
# save some memory, assuming we're done with cached docs if hasattr(r, 'cachedDoc'): r.cachedDoc = None if hasattr(r, 'stringContent'): r.stringContent = None
if self.useIndicator: self.progressIndicator.updateProgress(ctr) ctr += 1
# more potential memory savings self.sortedList = None self.filteredList = None
return
def Deserialize(stream, refUri=''): import Serialize return Serialize.Deserialize(stream, refUri)
|