309 lines
14 KiB
Python
309 lines
14 KiB
Python
'''This script allows to create a brand new ready-to-use Plone/Zone instance.
|
|
As prerequisite, you must have installed Plone through the Unifier installer
|
|
available at http://plone.org.'''
|
|
|
|
# ------------------------------------------------------------------------------
|
|
import os, os.path, sys, shutil, re
|
|
from optparse import OptionParser
|
|
from appy.shared.utils import cleanFolder, copyFolder
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class NewError(Exception): pass
|
|
ERROR_CODE = 1
|
|
WRONG_NB_OF_ARGS = 'Wrong number of args.'
|
|
WRONG_PLONE_VERSION = 'Plone version must be among %s.'
|
|
WRONG_PLONE_PATH = 'Path "%s" is not an existing folder.'
|
|
PYTHON_NOT_FOUND = 'Python interpreter was not found in "%s". Are you sure ' \
|
|
'we are in the folder hierarchy created by the Plone installer?'
|
|
PYTHON_EXE_NOT_FOUND = '"%s" does not exist.'
|
|
MKZOPE_NOT_FOUND = 'Script mkzopeinstance.py not found in "%s and ' \
|
|
'subfolders. Are you sure we are in the folder hierarchy created by ' \
|
|
'the Plone installer?'
|
|
WRONG_INSTANCE_PATH = '"%s" must be an existing folder for creating the ' \
|
|
'instance in it.'
|
|
|
|
zopeCtl = '''#!/bin/sh
|
|
PYTHON="%s"
|
|
INSTANCE_HOME="%s"
|
|
CONFIG_FILE="$INSTANCE_HOME/etc/zope.conf"
|
|
PYTHONPATH="$INSTANCE_HOME/lib/python"
|
|
ZDCTL="%s/Zope2/Startup/zopectl.py"
|
|
export INSTANCE_HOME
|
|
export PYTHON
|
|
export PYTHONPATH
|
|
exec "$PYTHON" "$ZDCTL" -C "$CONFIG_FILE" "$@"
|
|
'''
|
|
runZope = '''#! /bin/sh
|
|
PYTHON="%s"
|
|
INSTANCE_HOME="%s"
|
|
CONFIG_FILE="$INSTANCE_HOME/etc/zope.conf"
|
|
PYTHONPATH="$INSTANCE_HOME/lib/python"
|
|
ZOPE_RUN="%s/Zope2/Startup/run.py"
|
|
export INSTANCE_HOME
|
|
export PYTHON
|
|
export PYTHONPATH
|
|
exec "$PYTHON" "$ZOPE_RUN" -C "$CONFIG_FILE" "$@"
|
|
'''
|
|
pkgResourcesPatch = '''import os, os.path
|
|
productsFolder = os.path.join(os.environ["INSTANCE_HOME"], "Products")
|
|
for name in os.listdir(productsFolder):
|
|
if os.path.isdir(os.path.join(productsFolder, name)):
|
|
if name not in appyVersions:
|
|
appyVersions[name] = "1.0"
|
|
appyVersions['Products.%s' % name] = "1.0"
|
|
|
|
def getAppyVersion(req, location):
|
|
global appyVersions
|
|
if req.project_name not in appyVersions:
|
|
raise DistributionNotFound(req)
|
|
return Distribution(project_name=req.project_name,
|
|
version=appyVersions[req.project_name],
|
|
platform='linux2', location=location)
|
|
'''
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class NewScript:
|
|
'''usage: %prog ploneVersion plonePath instancePath
|
|
|
|
"ploneVersion" can be plone25, plone30, plone3x or plone4
|
|
(plone3x represents Plone 3.2.x, Plone 3.3.5...)
|
|
|
|
"plonePath" is the (absolute) path to you plone installation.
|
|
Plone 2.5 and 3.0 are typically installed in
|
|
/opt/Plone-x.x.x, while Plone 3 > 3.0 is typically
|
|
installed in in /usr/local/Plone.
|
|
"instancePath" is the (absolute) path where you want to create your
|
|
instance (should not already exist).'''
|
|
ploneVersions = ('plone25', 'plone30', 'plone3x', 'plone4')
|
|
|
|
def installPlone25or30Stuff(self, linksForProducts):
|
|
'''Here, we will copy all Plone2-related stuff in the Zope instance
|
|
we've created, to get a full Plone-ready Zope instance. If
|
|
p_linksForProducts is True, we do not perform a real copy: we will
|
|
create symlinks to products lying within Plone installer files.'''
|
|
j = os.path.join
|
|
if self.ploneVersion == 'plone25':
|
|
sourceFolders = ('zeocluster/Products',)
|
|
else:
|
|
sourceFolders = ('zinstance/Products', 'zinstance/lib/python')
|
|
for sourceFolder in sourceFolders:
|
|
sourceBase = j(self.plonePath, sourceFolder)
|
|
destBase = j(self.instancePath,
|
|
sourceFolder[sourceFolder.find('/')+1:])
|
|
for name in os.listdir(sourceBase):
|
|
folderName = j(sourceBase, name)
|
|
if os.path.isdir(folderName):
|
|
destFolder = j(destBase, name)
|
|
# This is a Plone product. Copy it to the instance.
|
|
if linksForProducts:
|
|
# Create a symlink to this product in the instance
|
|
cmd = 'ln -s %s %s' % (folderName, destFolder)
|
|
os.system(cmd)
|
|
else:
|
|
# Copy thre product into the instance
|
|
copyFolder(folderName, destFolder)
|
|
|
|
filesToPatch = ('meta.zcml', 'configure.zcml', 'overrides.zcml')
|
|
patchRex = re.compile('<includePlugins.*?/>', re.S)
|
|
def patchPlone3x(self):
|
|
'''Auto-proclaimed ugly code in z3c forces us to patch some files
|
|
in Products.CMFPlone because these guys make the assumption that
|
|
"plone.xxx" packages are within eggs when they've implemented their
|
|
ZCML directives "includePlugins" and "includePluginsOverrides".
|
|
So in this method, I remove every call to those directives in
|
|
CMFPlone files. It does not seem to affect Plone behaviour. Indeed,
|
|
these directives seem to be useful only when adding sad (ie, non
|
|
Appy) Plone plug-ins.'''
|
|
j = os.path.join
|
|
ploneFolder = os.path.join(self.productsFolder, 'CMFPlone')
|
|
# Patch files
|
|
for fileName in self.filesToPatch:
|
|
filePath = os.path.join(ploneFolder, fileName)
|
|
f = file(filePath)
|
|
fileContent = f.read()
|
|
f.close()
|
|
f = file(filePath, 'w')
|
|
f.write(self.patchRex.sub('<!--Del. includePlugins-->',fileContent))
|
|
f.close()
|
|
|
|
missingIncludes = ('plone.app.upgrade', 'plonetheme.sunburst',
|
|
'plonetheme.classic')
|
|
def patchPlone4(self, versions):
|
|
'''Patches Plone 4 that can't live without buildout as-is.'''
|
|
self.patchPlone3x() # We still need this for Plone 4 as well.
|
|
# bin/zopectl
|
|
content = zopeCtl % (self.pythonPath, self.instancePath, self.zopePath)
|
|
f = file('%s/bin/zopectl' % self.instancePath, 'w')
|
|
f.write(content)
|
|
f.close()
|
|
# bin/runzope
|
|
content = runZope % (self.pythonPath, self.instancePath, self.zopePath)
|
|
f = file('%s/bin/runzope' % self.instancePath, 'w')
|
|
f.write(content)
|
|
f.close()
|
|
j = os.path.join
|
|
# As eggs have been deleted, versions of components are lost. Reify
|
|
# them from p_versions.
|
|
dVersions = ['"%s":"%s"' % (n, v) for n, v in versions.iteritems()]
|
|
sVersions = 'appyVersions = {' + ','.join(dVersions) + '}'
|
|
codeFile = "%s/pkg_resources.py" % self.libFolder
|
|
f = file(codeFile)
|
|
content = f.read().replace("raise DistributionNotFound(req)",
|
|
"dist = getAppyVersion(req, '%s')" % self.instancePath)
|
|
content = sVersions + '\n' + pkgResourcesPatch + '\n' + content
|
|
f.close()
|
|
f = file(codeFile, 'w')
|
|
f.write(content)
|
|
f.close()
|
|
# Some 'include' directives must be added with our install.
|
|
configPlone = j(self.productsFolder, 'CMFPlone', 'configure.zcml')
|
|
f = file(configPlone)
|
|
content = f.read()
|
|
f.close()
|
|
missing = ''
|
|
for missingInclude in self.missingIncludes:
|
|
missing += ' <include package="%s"/>\n' % missingInclude
|
|
content = content.replace('</configure>', '%s\n</configure>' % missing)
|
|
f = file(configPlone, 'w')
|
|
f.write(content)
|
|
f.close()
|
|
|
|
def copyEggs(self):
|
|
'''Copy content of eggs into the Zope instance. This method also
|
|
retrieves every egg version and returns a dict {s_egg:s_version}.'''
|
|
j = os.path.join
|
|
eggsFolder = j(self.plonePath, 'buildout-cache/eggs')
|
|
res = {}
|
|
for name in os.listdir(eggsFolder):
|
|
if name == 'EGG-INFO': continue
|
|
splittedName = name.split('-')
|
|
res[splittedName[0]] = splittedName[1]
|
|
if splittedName[0].startswith('Products.'):
|
|
res[splittedName[0][9:]] = splittedName[1]
|
|
absName = j(eggsFolder, name)
|
|
# Copy every file or sub-folder into self.libFolder or
|
|
# self.productsFolder.
|
|
for fileName in os.listdir(absName):
|
|
absFileName = j(absName, fileName)
|
|
if fileName == 'Products' and not name.startswith('Zope2-'):
|
|
# Copy every sub-folder into self.productsFolder
|
|
for folderName in os.listdir(absFileName):
|
|
absFolder = j(absFileName, folderName)
|
|
if not os.path.isdir(absFolder): continue
|
|
copyFolder(absFolder, j(self.productsFolder,folderName))
|
|
elif os.path.isdir(absFileName):
|
|
copyFolder(absFileName, j(self.libFolder, fileName))
|
|
else:
|
|
shutil.copy(absFileName, self.libFolder)
|
|
return res
|
|
|
|
def createInstance(self, linksForProducts):
|
|
'''Calls the Zope script that allows to create a Zope instance and copy
|
|
into it all the Plone packages and products.'''
|
|
j = os.path.join
|
|
# Find the Python interpreter running Zope
|
|
for elem in os.listdir(self.plonePath):
|
|
pythonPath = None
|
|
elemPath = j(self.plonePath, elem)
|
|
if elem.startswith('Python-') and os.path.isdir(elemPath):
|
|
pythonPath = elemPath + '/bin/python'
|
|
if not os.path.exists(pythonPath):
|
|
raise NewError(PYTHON_EXE_NOT_FOUND % pythonPath)
|
|
break
|
|
if not pythonPath:
|
|
raise NewError(PYTHON_NOT_FOUND % self.plonePath)
|
|
self.pythonPath = pythonPath
|
|
# Find the Zope script mkzopeinstance.py and Zope itself
|
|
makeInstancePath = None
|
|
self.zopePath = None
|
|
for dirname, dirs, files in os.walk(self.plonePath):
|
|
# Find Zope
|
|
for folderName in dirs:
|
|
if folderName.startswith('Zope2-'):
|
|
self.zopePath = j(dirname, folderName)
|
|
# Find mkzopeinstance
|
|
for fileName in files:
|
|
if fileName == 'mkzopeinstance.py':
|
|
if self.ploneVersion == 'plone4':
|
|
makeInstancePath = j(dirname, fileName)
|
|
else:
|
|
if ('/buildout-cache/' not in dirname):
|
|
makeInstancePath = j(dirname, fileName)
|
|
if not makeInstancePath:
|
|
raise NewError(MKZOPE_NOT_FOUND % self.plonePath)
|
|
# Execute mkzopeinstance.py with the right Python interpreter.
|
|
# For Plone4, we will call it later.
|
|
cmd = '%s %s -d %s' % (pythonPath, makeInstancePath, self.instancePath)
|
|
if self.ploneVersion != 'plone4':
|
|
print cmd
|
|
os.system(cmd)
|
|
# Now, make the instance Plone-ready
|
|
action = 'Copying'
|
|
if linksForProducts:
|
|
action = 'Symlinking'
|
|
print '%s Plone stuff in the Zope instance...' % action
|
|
if self.ploneVersion in ('plone25', 'plone30'):
|
|
self.installPlone25or30Stuff(linksForProducts)
|
|
elif self.ploneVersion in ('plone3x', 'plone4'):
|
|
versions = self.copyEggs()
|
|
if self.ploneVersion == 'plone3x':
|
|
self.patchPlone3x()
|
|
elif self.ploneVersion == 'plone4':
|
|
# Create the Zope instance
|
|
os.environ['PYTHONPATH'] = '%s:%s' % \
|
|
(j(self.instancePath,'Products'),
|
|
j(self.instancePath, 'lib/python'))
|
|
print cmd
|
|
os.system(cmd)
|
|
self.patchPlone4(versions)
|
|
# Remove .bat files under Linux
|
|
if os.name == 'posix':
|
|
cleanFolder(j(self.instancePath, 'bin'), exts=('.bat',))
|
|
|
|
def manageArgs(self, args):
|
|
'''Ensures that the script was called with the right parameters.'''
|
|
if len(args) != 3: raise NewError(WRONG_NB_OF_ARGS)
|
|
self.ploneVersion, self.plonePath, self.instancePath = args
|
|
# Add some more folder definitions
|
|
j = os.path.join
|
|
self.productsFolder = j(self.instancePath, 'Products')
|
|
self.libFolder = j(self.instancePath, 'lib/python')
|
|
# Check Plone version
|
|
if self.ploneVersion not in self.ploneVersions:
|
|
raise NewError(WRONG_PLONE_VERSION % str(self.ploneVersions))
|
|
# Check Plone path
|
|
if not os.path.exists(self.plonePath) \
|
|
or not os.path.isdir(self.plonePath):
|
|
raise NewError(WRONG_PLONE_PATH % self.plonePath)
|
|
# Check instance path
|
|
parentFolder = os.path.dirname(self.instancePath)
|
|
if not os.path.exists(parentFolder) or not os.path.isdir(parentFolder):
|
|
raise NewError(WRONG_INSTANCE_PATH % parentFolder)
|
|
|
|
def run(self):
|
|
optParser = OptionParser(usage=NewScript.__doc__)
|
|
optParser.add_option("-l", "--links", action="store_true",
|
|
help="[Linux, plone25 or plone30 only] Within the created " \
|
|
"instance, symlinks to Products lying within the Plone " \
|
|
"installer files are created instead of copying them into " \
|
|
"the instance. This avoids duplicating the Products source " \
|
|
"code and is interesting if you create a lot of Zope " \
|
|
"instances.")
|
|
(options, args) = optParser.parse_args()
|
|
linksForProducts = options.links
|
|
try:
|
|
self.manageArgs(args)
|
|
print 'Creating new %s instance...' % self.ploneVersion
|
|
self.createInstance(linksForProducts)
|
|
except NewError, ne:
|
|
optParser.print_help()
|
|
print
|
|
sys.stderr.write(str(ne))
|
|
sys.stderr.write('\n')
|
|
sys.exit(ERROR_CODE)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
if __name__ == '__main__':
|
|
NewScript().run()
|
|
# ------------------------------------------------------------------------------
|