appy.shared: improved deployment of a Appy app (creation of a Zope instance is no more required; corresponding folders are created in standard unix locations: /etc for the config file, /var/log for logs, /var/lib for the database, /usr/bin for scripts that start and stop the instance). appy.gen: first draft of a migration script that allows to migrate data from Plone-dependent Appy apps (<= 0.7.1) to Ploneless Appy 0.8.0.
This commit is contained in:
parent
95a899f3de
commit
1275df5753
|
@ -80,8 +80,9 @@ class GeneratorScript:
|
||||||
if options.debian:
|
if options.debian:
|
||||||
app = args[0]
|
app = args[0]
|
||||||
appDir = os.path.dirname(app)
|
appDir = os.path.dirname(app)
|
||||||
|
appName = os.path.basename(app)
|
||||||
# Get the app version from zope/version.txt
|
# Get the app version from zope/version.txt
|
||||||
f = file(os.path.join(app, 'zope', 'version.txt'))
|
f = file(os.path.join(app, 'zope', appName, 'version.txt'))
|
||||||
version = f.read()
|
version = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
version = version[:version.find('build')-1]
|
version = version[:version.find('build')-1]
|
||||||
|
|
49
bin/new.py
49
bin/new.py
|
@ -6,6 +6,7 @@
|
||||||
import os, os.path, sys, shutil, re
|
import os, os.path, sys, shutil, re
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
from appy.shared.utils import cleanFolder, copyFolder
|
from appy.shared.utils import cleanFolder, copyFolder
|
||||||
|
from appy.shared.packaging import ooStart, zopeConf
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class NewError(Exception): pass
|
class NewError(Exception): pass
|
||||||
|
@ -34,7 +35,7 @@ exec "$ZDCTL" -C "$CONFIG_FILE" "$@"
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# runzope template file for a pure Zope instance -------------------------------
|
# runzope template file for a pure Zope instance -------------------------------
|
||||||
runZope = '''#! /bin/sh
|
runZope = '''#!/bin/sh
|
||||||
INSTANCE_HOME="%s"
|
INSTANCE_HOME="%s"
|
||||||
CONFIG_FILE="$INSTANCE_HOME/etc/zope.conf"
|
CONFIG_FILE="$INSTANCE_HOME/etc/zope.conf"
|
||||||
ZOPE_RUN="/usr/lib/zope2.12/bin/runzope"
|
ZOPE_RUN="/usr/lib/zope2.12/bin/runzope"
|
||||||
|
@ -42,46 +43,6 @@ export INSTANCE_HOME
|
||||||
exec "$ZOPE_RUN" -C "$CONFIG_FILE" "$@"
|
exec "$ZOPE_RUN" -C "$CONFIG_FILE" "$@"
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# zope.conf template file for a pure Zope instance -----------------------------
|
|
||||||
zopeConf = '''# Zope configuration.
|
|
||||||
%%define INSTANCE %s
|
|
||||||
%%define HTTPPORT 8080
|
|
||||||
%%define ZOPE_USER zope
|
|
||||||
|
|
||||||
instancehome $INSTANCE
|
|
||||||
effective-user $ZOPE_USER
|
|
||||||
<eventlog>
|
|
||||||
level info
|
|
||||||
<logfile>
|
|
||||||
path $INSTANCE/log/event.log
|
|
||||||
level info
|
|
||||||
</logfile>
|
|
||||||
</eventlog>
|
|
||||||
<logger access>
|
|
||||||
level WARN
|
|
||||||
<logfile>
|
|
||||||
path $INSTANCE/log/Z2.log
|
|
||||||
format %%(message)s
|
|
||||||
</logfile>
|
|
||||||
</logger>
|
|
||||||
<http-server>
|
|
||||||
address $HTTPPORT
|
|
||||||
</http-server>
|
|
||||||
<zodb_db main>
|
|
||||||
<filestorage>
|
|
||||||
path $INSTANCE/var/Data.fs
|
|
||||||
</filestorage>
|
|
||||||
mount-point /
|
|
||||||
</zodb_db>
|
|
||||||
<zodb_db temporary>
|
|
||||||
<temporarystorage>
|
|
||||||
name temporary storage for sessioning
|
|
||||||
</temporarystorage>
|
|
||||||
mount-point /temp_folder
|
|
||||||
container-class Products.TemporaryFolder.TemporaryContainer
|
|
||||||
</zodb_db>
|
|
||||||
'''
|
|
||||||
|
|
||||||
# zopectl template for a Plone (4) Zope instance -------------------------------
|
# zopectl template for a Plone (4) Zope instance -------------------------------
|
||||||
zopeCtlPlone = '''#!/bin/sh
|
zopeCtlPlone = '''#!/bin/sh
|
||||||
PYTHON="%s"
|
PYTHON="%s"
|
||||||
|
@ -153,14 +114,14 @@ class ZopeInstanceCreator:
|
||||||
os.chmod('bin/runzope', 0744) # Make it executable by owner.
|
os.chmod('bin/runzope', 0744) # Make it executable by owner.
|
||||||
# Create bin/startoo
|
# Create bin/startoo
|
||||||
f = file('bin/startoo', 'w')
|
f = file('bin/startoo', 'w')
|
||||||
f.write('#!/bin/sh\nsoffice -invisible -headless -nofirststartwizard '\
|
f.write(ooStart)
|
||||||
'"-accept=socket,host=localhost,port=2002;urp;"&\n')
|
|
||||||
f.close()
|
f.close()
|
||||||
os.chmod('bin/startoo', 0744) # Make it executable by owner.
|
os.chmod('bin/startoo', 0744) # Make it executable by owner.
|
||||||
# Create etc/zope.conf
|
# Create etc/zope.conf
|
||||||
os.mkdir('etc')
|
os.mkdir('etc')
|
||||||
f = file('etc/zope.conf', 'w')
|
f = file('etc/zope.conf', 'w')
|
||||||
f.write(zopeConf % self.instancePath)
|
f.write(zopeConf % (self.instancePath, '%s/var' % self.instancePath,
|
||||||
|
'%s/log' % self.instancePath, ''))
|
||||||
f.close()
|
f.close()
|
||||||
# Create other folders
|
# Create other folders
|
||||||
for name in ('Extensions', 'log', 'Products', 'var'): os.mkdir(name)
|
for name in ('Extensions', 'log', 'Products', 'var'): os.mkdir(name)
|
||||||
|
|
|
@ -424,7 +424,7 @@ class Publisher:
|
||||||
f.write(toc)
|
f.write(toc)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
privateScripts = ('publish.py', 'zip.py', 'runOpenOffice.sh')
|
privateScripts = ('publish.py', 'zip.py', 'startoo.sh')
|
||||||
def prepareGenFolder(self, minimalist=False):
|
def prepareGenFolder(self, minimalist=False):
|
||||||
'''Creates the basic structure of the temp folder where the appy
|
'''Creates the basic structure of the temp folder where the appy
|
||||||
website will be generated.'''
|
website will be generated.'''
|
||||||
|
|
23
bin/zopectl.py
Normal file
23
bin/zopectl.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
import sys, os, os.path
|
||||||
|
import Zope2.Startup.zopectl as zctl
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class ZopeRunner:
|
||||||
|
'''This class allows to run a Appy/Zope instance.'''
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# Check that an arg has been given (start, stop, fg, run)
|
||||||
|
if not sys.argv[3].strip():
|
||||||
|
print 'Argument required.'
|
||||||
|
sys.exit(-1)
|
||||||
|
# Identify the name of the application for which Zope must run.
|
||||||
|
app = os.path.splitext(os.path.basename(sys.argv[2]))[0].lower()
|
||||||
|
# Launch Zope.
|
||||||
|
options = zctl.ZopeCtlOptions()
|
||||||
|
options.realize(None)
|
||||||
|
options.program = ['/usr/bin/%srun' % app]
|
||||||
|
c = zctl.ZopeCmd(options)
|
||||||
|
c.onecmd(" ".join(options.args))
|
||||||
|
return min(c._exitstatus, 1)
|
||||||
|
# ------------------------------------------------------------------------------
|
|
@ -120,7 +120,8 @@ class Generator:
|
||||||
# Determine application name
|
# Determine application name
|
||||||
self.applicationName = os.path.basename(application)
|
self.applicationName = os.path.basename(application)
|
||||||
# Determine output folder (where to store the generated product)
|
# Determine output folder (where to store the generated product)
|
||||||
self.outputFolder = os.path.join(application, 'zope')
|
self.outputFolder = os.path.join(application, 'zope',
|
||||||
|
self.applicationName)
|
||||||
self.options = options
|
self.options = options
|
||||||
# Determine templates folder
|
# Determine templates folder
|
||||||
genFolder = os.path.dirname(__file__)
|
genFolder = os.path.dirname(__file__)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import appy.version
|
||||||
import appy.gen as gen
|
import appy.gen as gen
|
||||||
from appy.gen.po import PoParser
|
from appy.gen.po import PoParser
|
||||||
from appy.gen.utils import updateRolesForPermission, createObject
|
from appy.gen.utils import updateRolesForPermission, createObject
|
||||||
|
from appy.gen.migrator import Migrator
|
||||||
from appy.shared.data import languages
|
from appy.shared.data import languages
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@ -87,14 +88,16 @@ class ZopeInstaller:
|
||||||
|
|
||||||
def installUi(self):
|
def installUi(self):
|
||||||
'''Installs the user interface.'''
|
'''Installs the user interface.'''
|
||||||
# Delete the existing folder if it existed.
|
|
||||||
zopeContent = self.app.objectIds()
|
|
||||||
if 'ui' in zopeContent: self.app.manage_delObjects(['ui'])
|
|
||||||
self.app.manage_addFolder('ui')
|
|
||||||
# Some useful imports
|
# Some useful imports
|
||||||
|
from OFS.Folder import manage_addFolder
|
||||||
|
from OFS.Image import manage_addImage, manage_addFile
|
||||||
from Products.PythonScripts.PythonScript import PythonScript
|
from Products.PythonScripts.PythonScript import PythonScript
|
||||||
from Products.PageTemplates.ZopePageTemplate import \
|
from Products.PageTemplates.ZopePageTemplate import \
|
||||||
manage_addPageTemplate
|
manage_addPageTemplate
|
||||||
|
# Delete the existing folder if it existed.
|
||||||
|
zopeContent = self.app.objectIds()
|
||||||
|
if 'ui' in zopeContent: self.app.manage_delObjects(['ui'])
|
||||||
|
manage_addFolder(self.app, 'ui')
|
||||||
# Browse the physical folder and re-create it in the Zope folder
|
# Browse the physical folder and re-create it in the Zope folder
|
||||||
j = os.path.join
|
j = os.path.join
|
||||||
ui = j(j(appy.getPath(), 'gen'), 'ui')
|
ui = j(j(appy.getPath(), 'gen'), 'ui')
|
||||||
|
@ -106,13 +109,13 @@ class ZopeInstaller:
|
||||||
for name in folderName.strip(os.sep).split(os.sep):
|
for name in folderName.strip(os.sep).split(os.sep):
|
||||||
zopeFolder = zopeFolder._getOb(name)
|
zopeFolder = zopeFolder._getOb(name)
|
||||||
# Create sub-folders at this level
|
# Create sub-folders at this level
|
||||||
for name in dirs: zopeFolder.manage_addFolder(name)
|
for name in dirs: manage_addFolder(zopeFolder, name)
|
||||||
# Create files at this level
|
# Create files at this level
|
||||||
for name in files:
|
for name in files:
|
||||||
baseName, ext = os.path.splitext(name)
|
baseName, ext = os.path.splitext(name)
|
||||||
f = file(j(root, name))
|
f = file(j(root, name))
|
||||||
if ext in gen.File.imageExts:
|
if ext in gen.File.imageExts:
|
||||||
zopeFolder.manage_addImage(name, f)
|
manage_addImage(zopeFolder, name, f)
|
||||||
elif ext == '.pt':
|
elif ext == '.pt':
|
||||||
manage_addPageTemplate(zopeFolder, baseName, '', f.read())
|
manage_addPageTemplate(zopeFolder, baseName, '', f.read())
|
||||||
elif ext == '.py':
|
elif ext == '.py':
|
||||||
|
@ -120,7 +123,7 @@ class ZopeInstaller:
|
||||||
zopeFolder._setObject(baseName, obj)
|
zopeFolder._setObject(baseName, obj)
|
||||||
zopeFolder._getOb(baseName).write(f.read())
|
zopeFolder._getOb(baseName).write(f.read())
|
||||||
else:
|
else:
|
||||||
zopeFolder.manage_addFile(name, f)
|
manage_addFile(zopeFolder, name, f)
|
||||||
f.close()
|
f.close()
|
||||||
# Update the home page
|
# Update the home page
|
||||||
if 'index_html' in zopeContent:
|
if 'index_html' in zopeContent:
|
||||||
|
@ -199,9 +202,10 @@ class ZopeInstaller:
|
||||||
'''Creates the tool and the root data folder if they do not exist.'''
|
'''Creates the tool and the root data folder if they do not exist.'''
|
||||||
# Create or update the base folder for storing data
|
# Create or update the base folder for storing data
|
||||||
zopeContent = self.app.objectIds()
|
zopeContent = self.app.objectIds()
|
||||||
|
from OFS.Folder import manage_addFolder
|
||||||
|
|
||||||
if 'data' not in zopeContent:
|
if 'data' not in zopeContent:
|
||||||
self.app.manage_addFolder('data')
|
manage_addFolder(self.app, 'data')
|
||||||
data = self.app.data
|
data = self.app.data
|
||||||
# Manager has been granted Add permissions for all root classes.
|
# Manager has been granted Add permissions for all root classes.
|
||||||
# This may not be desired, so remove this.
|
# This may not be desired, so remove this.
|
||||||
|
@ -240,7 +244,13 @@ class ZopeInstaller:
|
||||||
appyTool.log('Appy version is "%s".' % appy.version.short)
|
appyTool.log('Appy version is "%s".' % appy.version.short)
|
||||||
|
|
||||||
# Create the admin user if no user exists.
|
# Create the admin user if no user exists.
|
||||||
if not self.app.acl_users.getUsers():
|
try:
|
||||||
|
users = self.app.acl_users.getUsers()
|
||||||
|
except:
|
||||||
|
# When Plone has installed PAS in acl_users this may fail. Plone
|
||||||
|
# may still be in the way for migration purposes.
|
||||||
|
users = ('admin') # We suppose there is at least a user.
|
||||||
|
if not users:
|
||||||
self.app.acl_users._doAddUser('admin', 'admin', ['Manager'], ())
|
self.app.acl_users._doAddUser('admin', 'admin', ['Manager'], ())
|
||||||
appyTool.log('Admin user "admin" created.')
|
appyTool.log('Admin user "admin" created.')
|
||||||
|
|
||||||
|
@ -399,6 +409,10 @@ class ZopeInstaller:
|
||||||
self.installCatalog()
|
self.installCatalog()
|
||||||
self.installTool()
|
self.installTool()
|
||||||
self.installUi()
|
self.installUi()
|
||||||
|
# Perform migrations if required
|
||||||
|
Migrator(self).run()
|
||||||
|
# Update Appy version in the database
|
||||||
|
self.app.config.appy().appyVersion = appy.version.short
|
||||||
# Empty the fake REQUEST object, only used at Zope startup.
|
# Empty the fake REQUEST object, only used at Zope startup.
|
||||||
del self.app.config.getProductConfig().fakeRequest.wrappers
|
del self.app.config.getProductConfig().fakeRequest.wrappers
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
159
gen/migrator.py
159
gen/migrator.py
|
@ -7,56 +7,125 @@ class Migrator:
|
||||||
installation, we've detected a new Appy version.'''
|
installation, we've detected a new Appy version.'''
|
||||||
def __init__(self, installer):
|
def __init__(self, installer):
|
||||||
self.installer = installer
|
self.installer = installer
|
||||||
|
self.logger = installer.logger
|
||||||
|
self.app = installer.app
|
||||||
|
|
||||||
def migrateTo_0_7_1(self):
|
bypassRoles = ('Authenticated', 'Member')
|
||||||
'''Appy 0.7.1 has its own management of Ref fields. So we must
|
bypassGroups = ('Administrators', 'Reviewers')
|
||||||
update data structures that store Ref info on instances.'''
|
def migrateUsers(self, ploneSite):
|
||||||
ins = self.installer
|
'''Migrate users from Plone's acl_users to Zope acl_users with
|
||||||
ins.info('Migrating to Appy 0.7.1...')
|
corresponding Appy objects.'''
|
||||||
allClassNames = [ins.tool.__class__.__name__] + ins.config.allClassNames
|
# First of all, remove the Plone-patched root acl_users by a standard
|
||||||
for className in allClassNames:
|
# (hum, Appy-patched) Zope UserFolder.
|
||||||
i = -1
|
tool = self.app.config.appy()
|
||||||
updated = 0
|
from AccessControl.User import manage_addUserFolder
|
||||||
ins.info('Analysing class "%s"...' % className)
|
self.app.manage_delObjects(ids=['acl_users'])
|
||||||
refFields = None
|
manage_addUserFolder(self.app)
|
||||||
for obj in ins.tool.executeQuery(className,\
|
# Put an admin user into it
|
||||||
noSecurity=True)['objects']:
|
newUsersDb = self.app.acl_users
|
||||||
i += 1
|
newUsersDb._doAddUser('admin', 'admin', ['Manager'], ())
|
||||||
if i == 0:
|
# Copy users from Plone acl_users to Zope acl_users
|
||||||
# Get the Ref fields for objects of this class
|
for user in ploneSite.acl_users.getUsers():
|
||||||
refFields = [f for f in obj.getAllAppyTypes() \
|
id = user.getId()
|
||||||
if (f.type == 'Ref') and not f.isBack]
|
userRoles = user.getRoles()
|
||||||
if refFields:
|
for br in self.bypassRoles:
|
||||||
refNames = ', '.join([rf.name for rf in refFields])
|
if br in userRoles: userRoles.remove(br)
|
||||||
ins.info(' Ref fields found: %s' % refNames)
|
userInfo = ploneSite.portal_membership.getMemberById(id)
|
||||||
|
userName = userInfo.getProperty('fullname') or id
|
||||||
|
userEmail = userInfo.getProperty('email') or ''
|
||||||
|
appyUser = tool.create('users', login=id,
|
||||||
|
password1='fake', password2='fake', roles=userRoles,
|
||||||
|
name=userName, firstName=' ', email=userEmail)
|
||||||
|
appyUser.title = appyUser.title.strip()
|
||||||
|
# Set the correct password
|
||||||
|
password = ploneSite.acl_users.source_users._user_passwords[id]
|
||||||
|
newUsersDb.data[id].__ = password
|
||||||
|
# Manage groups. Exclude not-used default Plone groups.
|
||||||
|
for groupId in user.getGroups():
|
||||||
|
if groupId in self.bypassGroups: continue
|
||||||
|
if tool.count('Group', login=groupId):
|
||||||
|
# The Appy group already exists, get it
|
||||||
|
appyGroup = tool.search('Group', login=groupId)[0]
|
||||||
else:
|
else:
|
||||||
ins.info(' No Ref field found.')
|
# Create the group. Todo: get Plone group roles and title
|
||||||
|
appyGroup = tool.create('groups', login=groupId,
|
||||||
|
title=groupId)
|
||||||
|
appyGroup.addUser(appyUser)
|
||||||
|
|
||||||
|
def reindexObject(self, obj):
|
||||||
|
obj.reindex()
|
||||||
|
i = 1
|
||||||
|
for subObj in obj.objectValues():
|
||||||
|
i += self.reindexObject(subObj)
|
||||||
|
return i # The number of reindexed (sub-)object(s)
|
||||||
|
|
||||||
|
def migrateTo_0_8_0(self):
|
||||||
|
'''Migrates a Plone-based (<= 0.7.1) Appy app to a Ploneless (0.8.0)
|
||||||
|
Appy app.'''
|
||||||
|
self.logger.info('Migrating to Appy 0.8.0...')
|
||||||
|
# Find the Plone site. It must be at the root of the Zope tree.
|
||||||
|
ploneSite = None
|
||||||
|
for obj in self.app.objectValues():
|
||||||
|
if obj.__class__.__name__ == 'PloneSite':
|
||||||
|
ploneSite = obj
|
||||||
break
|
break
|
||||||
isUpdated = False
|
# As a preamble: delete translation objects from self.app.config: they
|
||||||
for field in refFields:
|
# will be copied from the old tool.
|
||||||
# Attr for storing UIDs of referred objects has moved
|
self.app.config.manage_delObjects(ids=self.app.config.objectIds())
|
||||||
# from _appy_[fieldName] to [fieldName].
|
# Migrate data objects:
|
||||||
refs = getattr(obj, '_appy_%s' % field.name)
|
# - from oldDataFolder to self.app.data
|
||||||
if refs:
|
# - from oldTool to self.app.config (excepted translation
|
||||||
isUpdated = True
|
# objects that were re-created from i18n files).
|
||||||
setattr(obj, field.name, refs)
|
appName = self.app.config.getAppName()
|
||||||
exec 'del obj._appy_%s' % field.name
|
for oldFolderName in (appName, 'portal_%s' % appName.lower()):
|
||||||
# Set the back references
|
oldFolder = getattr(ploneSite, oldFolderName)
|
||||||
for refObject in field.getValue(obj):
|
objectIds = [id for id in oldFolder.objectIds()]
|
||||||
refObject.link(field.back.name, obj, back=True)
|
cutted = oldFolder.manage_cutObjects(ids=objectIds)
|
||||||
if isUpdated: updated += 1
|
if oldFolderName == appName:
|
||||||
if updated:
|
destFolder = self.app.data
|
||||||
ins.info(' %d/%d object(s) updated.' % (updated, i+1))
|
else:
|
||||||
|
destFolder = self.app.config
|
||||||
|
destFolder.manage_pasteObjects(cutted)
|
||||||
|
i = 0
|
||||||
|
for obj in destFolder.objectValues():
|
||||||
|
i += self.reindexObject(obj)
|
||||||
|
self.logger.info('%d objects imported into %s.' % \
|
||||||
|
(i, destFolder.getId()))
|
||||||
|
if oldFolderName != appName:
|
||||||
|
# Re-link objects copied into the self.app.config with the Tool
|
||||||
|
# through Ref fields.
|
||||||
|
tool = self.app.config.appy()
|
||||||
|
pList = tool.o.getProductConfig().PersistentList
|
||||||
|
for field in tool.fields:
|
||||||
|
if field.type != 'Ref': continue
|
||||||
|
n = field.name
|
||||||
|
if n in ('users', 'groups'): continue
|
||||||
|
uids = getattr(oldFolder, n)
|
||||||
|
if uids:
|
||||||
|
# Update the forward reference
|
||||||
|
setattr(tool.o, n, pList(uids))
|
||||||
|
# Update the back reference
|
||||||
|
for obj in getattr(tool, n):
|
||||||
|
backList = getattr(obj.o, field.back.name)
|
||||||
|
backList.remove(oldFolder._at_uid)
|
||||||
|
backList.append(tool.uid)
|
||||||
|
self.logger.info('config.%s: linked %d object(s)' % \
|
||||||
|
(n, len(uids)))
|
||||||
|
else:
|
||||||
|
self.logger.info('config.%s: no object to link.' % n)
|
||||||
|
self.migrateUsers(ploneSite)
|
||||||
|
self.logger.info('Migration done.')
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
i = self.installer
|
if self.app.acl_users.__class__.__name__ == 'UserFolder':
|
||||||
installedVersion = i.appyTool.appyVersion
|
return # Already Ploneless
|
||||||
|
tool = self.app.config.appy()
|
||||||
|
appyVersion = tool.appyVersion
|
||||||
|
if not appyVersion or (appyVersion < '0.8.0'):
|
||||||
|
# Migration is required.
|
||||||
startTime = time.time()
|
startTime = time.time()
|
||||||
migrationRequired = False
|
self.migrateTo_0_8_0()
|
||||||
if not installedVersion or (installedVersion <= '0.7.0'):
|
|
||||||
migrationRequired = True
|
|
||||||
self.migrateTo_0_7_1()
|
|
||||||
stopTime = time.time()
|
stopTime = time.time()
|
||||||
if migrationRequired:
|
elapsed = (stopTime-startTime) / 60.0
|
||||||
i.info('Migration done in %d minute(s).'% ((stopTime-startTime)/60))
|
self.logger.info('Migration done in %d minute(s).' % elapsed)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -129,7 +129,7 @@ class ModelClass:
|
||||||
class User(ModelClass):
|
class User(ModelClass):
|
||||||
# In a ModelClass we need to declare attributes in the following list.
|
# In a ModelClass we need to declare attributes in the following list.
|
||||||
_appy_attributes = ['title', 'name', 'firstName', 'login', 'password1',
|
_appy_attributes = ['title', 'name', 'firstName', 'login', 'password1',
|
||||||
'password2', 'roles']
|
'password2', 'email', 'roles']
|
||||||
# All methods defined below are fake. Real versions are in the wrapper.
|
# All methods defined below are fake. Real versions are in the wrapper.
|
||||||
title = gen.String(show=False, indexed=True)
|
title = gen.String(show=False, indexed=True)
|
||||||
gm = {'group': 'main', 'multiplicity': (1,1), 'width': 25}
|
gm = {'group': 'main', 'multiplicity': (1,1), 'width': 25}
|
||||||
|
@ -144,6 +144,7 @@ class User(ModelClass):
|
||||||
password1 = gen.String(format=gen.String.PASSWORD, show=showPassword,
|
password1 = gen.String(format=gen.String.PASSWORD, show=showPassword,
|
||||||
validator=validatePassword, **gm)
|
validator=validatePassword, **gm)
|
||||||
password2 = gen.String(format=gen.String.PASSWORD, show=showPassword, **gm)
|
password2 = gen.String(format=gen.String.PASSWORD, show=showPassword, **gm)
|
||||||
|
email = gen.String(group='main', width=25)
|
||||||
gm['multiplicity'] = (0, None)
|
gm['multiplicity'] = (0, None)
|
||||||
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
|
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
|
||||||
indexed=True, **gm)
|
indexed=True, **gm)
|
||||||
|
@ -177,7 +178,7 @@ class Translation(ModelClass):
|
||||||
def show(self, name): pass
|
def show(self, name): pass
|
||||||
|
|
||||||
# The Tool class ---------------------------------------------------------------
|
# The Tool class ---------------------------------------------------------------
|
||||||
# Here are the prefixes of the fields generated on the Tool.
|
# Prefixes of the fields generated on the Tool.
|
||||||
toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
|
toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
|
||||||
'enableAdvancedSearch', 'numberOfSearchColumns',
|
'enableAdvancedSearch', 'numberOfSearchColumns',
|
||||||
'searchFields', 'optionalFields', 'showWorkflow',
|
'searchFields', 'optionalFields', 'showWorkflow',
|
||||||
|
|
|
@ -100,7 +100,7 @@
|
||||||
</tal:operator>
|
</tal:operator>
|
||||||
<tal:comment replace="nothing">The list of values</tal:comment>
|
<tal:comment replace="nothing">The list of values</tal:comment>
|
||||||
<select tal:attributes="name widgetName; size widget/height" multiple="multiple">
|
<select tal:attributes="name widgetName; size widget/height" multiple="multiple">
|
||||||
<option tal:repeat="v python:tool.getPossibleValues(name, withTranslations=True, withBlankValue=False, className=contentType)"
|
<option tal:repeat="v python:tool.getPossibleValues(name, withTranslations=True, withBlankValue=False, className=className)"
|
||||||
tal:attributes="value python:v[0]; title python: v[1]"
|
tal:attributes="value python:v[0]; title python: v[1]"
|
||||||
tal:content="python: tool.truncateValue(v[1], widget)">
|
tal:content="python: tool.truncateValue(v[1], widget)">
|
||||||
</option>
|
</option>
|
||||||
|
|
|
@ -110,6 +110,7 @@ class ZopeUserPatches:
|
||||||
def getRolesInContext(self, object):
|
def getRolesInContext(self, object):
|
||||||
'''Return the list of global and local (to p_object) roles granted to
|
'''Return the list of global and local (to p_object) roles granted to
|
||||||
this user (or to any of its groups).'''
|
this user (or to any of its groups).'''
|
||||||
|
if isinstance(object, AbstractWrapper): object = object.o
|
||||||
object = getattr(object, 'aq_inner', object)
|
object = getattr(object, 'aq_inner', object)
|
||||||
# Start with user global roles
|
# Start with user global roles
|
||||||
res = self.getRoles()
|
res = self.getRoles()
|
||||||
|
@ -120,7 +121,8 @@ class ZopeUserPatches:
|
||||||
groups = getattr(self, 'groups', ())
|
groups = getattr(self, 'groups', ())
|
||||||
for id, roles in localRoles.iteritems():
|
for id, roles in localRoles.iteritems():
|
||||||
if (id != userId) and (id not in groups): continue
|
if (id != userId) and (id not in groups): continue
|
||||||
for role in roles: res.add(role)
|
for role in roles:
|
||||||
|
if role not in res: res.append(role)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def allowed(self, object, object_roles=None):
|
def allowed(self, object, object_roles=None):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import os, os.path, subprocess, md5, shutil
|
import os, os.path, subprocess, md5, shutil
|
||||||
from appy.shared.utils import getOsTempFolder, FolderDeleter
|
from appy.shared.utils import getOsTempFolder, FolderDeleter, cleanFolder
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
debianInfo = '''Package: python-appy%s
|
debianInfo = '''Package: python-appy%s
|
||||||
|
@ -8,23 +8,77 @@ Version: %s
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Maintainer: Gaetan Delannay <gaetan.delannay@geezteem.com>
|
Maintainer: Gaetan Delannay <gaetan.delannay@geezteem.com>
|
||||||
Installed-Size: %d
|
Installed-Size: %d
|
||||||
Depends: python (>= %s), python (<= %s)%s
|
Depends: python (>= %s)%s
|
||||||
Section: python
|
Section: python
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Homepage: http://appyframework.org
|
Homepage: http://appyframework.org
|
||||||
Description: Appy builds simple but complex web Python apps.
|
Description: Appy builds simple but complex web Python apps.
|
||||||
'''
|
'''
|
||||||
|
appCtl = '''#!/usr/lib/zope2.12/bin/python
|
||||||
|
import sys
|
||||||
|
from appy.bin.zopectl import ZopeRunner
|
||||||
|
args = ' '.join(sys.argv[1:])
|
||||||
|
sys.argv = [sys.argv[0], '-C', '/etc/%s.conf', args]
|
||||||
|
ZopeRunner().run()
|
||||||
|
'''
|
||||||
|
appRun = '''#!/bin/sh
|
||||||
|
exec "/usr/lib/zope2.12/bin/runzope" -C "/etc/%s.conf" "$@"
|
||||||
|
'''
|
||||||
|
ooStart = '#!/bin/sh\nsoffice -invisible -headless -nofirststartwizard ' \
|
||||||
|
'"-accept=socket,host=localhost,port=2002;urp;"&\n'
|
||||||
|
zopeConf = '''# Zope configuration.
|
||||||
|
%%define INSTANCE %s
|
||||||
|
%%define DATA %s
|
||||||
|
%%define LOG %s
|
||||||
|
%%define HTTPPORT 8080
|
||||||
|
%%define ZOPE_USER zope
|
||||||
|
|
||||||
|
instancehome $INSTANCE
|
||||||
|
effective-user $ZOPE_USER
|
||||||
|
%s
|
||||||
|
<eventlog>
|
||||||
|
level info
|
||||||
|
<logfile>
|
||||||
|
path $LOG/event.log
|
||||||
|
level info
|
||||||
|
</logfile>
|
||||||
|
</eventlog>
|
||||||
|
<logger access>
|
||||||
|
level WARN
|
||||||
|
<logfile>
|
||||||
|
path $LOG/Z2.log
|
||||||
|
format %%(message)s
|
||||||
|
</logfile>
|
||||||
|
</logger>
|
||||||
|
<http-server>
|
||||||
|
address $HTTPPORT
|
||||||
|
</http-server>
|
||||||
|
<zodb_db main>
|
||||||
|
<filestorage>
|
||||||
|
path $DATA/Data.fs
|
||||||
|
</filestorage>
|
||||||
|
mount-point /
|
||||||
|
</zodb_db>
|
||||||
|
<zodb_db temporary>
|
||||||
|
<temporarystorage>
|
||||||
|
name temporary storage for sessioning
|
||||||
|
</temporarystorage>
|
||||||
|
mount-point /temp_folder
|
||||||
|
container-class Products.TemporaryFolder.TemporaryContainer
|
||||||
|
</zodb_db>
|
||||||
|
'''
|
||||||
|
|
||||||
class Debianizer:
|
class Debianizer:
|
||||||
'''This class allows to produce a Debian package from a Python (Appy)
|
'''This class allows to produce a Debian package from a Python (Appy)
|
||||||
package.'''
|
package.'''
|
||||||
|
|
||||||
def __init__(self, app, out, appVersion='0.1.0',
|
def __init__(self, app, out, appVersion='0.1.0',
|
||||||
pythonVersions=('2.6', '2.7'),
|
pythonVersions=('2.6',),
|
||||||
depends=('zope2.12', 'openoffice.org', 'imagemagick')):
|
depends=('zope2.12', 'openoffice.org', 'imagemagick')):
|
||||||
# app is the path to the Python package to Debianize.
|
# app is the path to the Python package to Debianize.
|
||||||
self.app = app
|
self.app = app
|
||||||
self.appName = os.path.basename(app)
|
self.appName = os.path.basename(app)
|
||||||
|
self.appNameLower = self.appName.lower()
|
||||||
# out is the folder where the Debian package will be generated.
|
# out is the folder where the Debian package will be generated.
|
||||||
self.out = out
|
self.out = out
|
||||||
# What is the version number for this app ?
|
# What is the version number for this app ?
|
||||||
|
@ -33,6 +87,8 @@ class Debianizer:
|
||||||
self.pythonVersions = pythonVersions
|
self.pythonVersions = pythonVersions
|
||||||
# Debian package dependencies
|
# Debian package dependencies
|
||||||
self.depends = depends
|
self.depends = depends
|
||||||
|
# Zope 2.12 requires Python 2.6
|
||||||
|
if 'zope2.12' in depends: self.pythonVersions = ('2.6',)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
'''Generates the Debian package.'''
|
'''Generates the Debian package.'''
|
||||||
|
@ -49,19 +105,74 @@ class Debianizer:
|
||||||
for version in self.pythonVersions:
|
for version in self.pythonVersions:
|
||||||
libFolder = j(srcFolder, 'python%s' % version)
|
libFolder = j(srcFolder, 'python%s' % version)
|
||||||
os.makedirs(libFolder)
|
os.makedirs(libFolder)
|
||||||
shutil.copytree(self.app, j(libFolder, self.appName))
|
destFolder = j(libFolder, self.appName)
|
||||||
# Create data.tar.gz based on it.
|
shutil.copytree(self.app, destFolder)
|
||||||
os.chdir(debFolder)
|
# Clean dest folder (.svn/.bzr files)
|
||||||
os.system('tar czvf data.tar.gz ./usr')
|
cleanFolder(destFolder, folders=('.svn', '.bzr'))
|
||||||
|
# When packaging Appy itself, everything is in /usr/lib/pythonX. When
|
||||||
|
# packaging an Appy app, we will generate more files for creating a
|
||||||
|
# running instance.
|
||||||
|
if self.appName != 'appy':
|
||||||
|
# Create the folders that will collectively represent the deployed
|
||||||
|
# Zope instance.
|
||||||
|
binFolder = j(debFolder, 'usr', 'bin')
|
||||||
|
os.makedirs(binFolder)
|
||||||
|
# <app>ctl
|
||||||
|
name = '%s/%sctl' % (binFolder, self.appNameLower)
|
||||||
|
f = file(name, 'w')
|
||||||
|
f.write(appCtl % self.appNameLower)
|
||||||
|
os.chmod(name, 0744) # Make it executable by owner.
|
||||||
|
f.close()
|
||||||
|
# <app>run
|
||||||
|
name = '%s/%srun' % (binFolder, self.appNameLower)
|
||||||
|
f = file(name, 'w')
|
||||||
|
f.write(appRun % self.appNameLower)
|
||||||
|
os.chmod(name, 0744) # Make it executable by owner.
|
||||||
|
f.close()
|
||||||
|
# startoo
|
||||||
|
name = '%s/startoo' % binFolder
|
||||||
|
f = file(name, 'w')
|
||||||
|
f.write(ooStart)
|
||||||
|
f.close()
|
||||||
|
os.chmod(name, 0744) # Make it executable by owner.
|
||||||
|
# /var/lib/<app> (will store Data.fs, lock files, etc)
|
||||||
|
varLibFolder = j(debFolder, 'var', 'lib', self.appNameLower)
|
||||||
|
os.makedirs(varLibFolder)
|
||||||
|
f = file('%s/README' % varLibFolder, 'w')
|
||||||
|
f.write('This folder stores the %s database.\n' % self.appName)
|
||||||
|
f.close()
|
||||||
|
# /var/log/<app> (will store event.log and Z2.log)
|
||||||
|
varLogFolder = j(debFolder, 'var', 'log', self.appNameLower)
|
||||||
|
os.makedirs(varLogFolder)
|
||||||
|
f = file('%s/README' % varLogFolder, 'w')
|
||||||
|
f.write('This folder stores the log files for %s.\n' % self.appName)
|
||||||
|
f.close()
|
||||||
|
# /etc/<app>.conf (Zope configuration file)
|
||||||
|
etcFolder = j(debFolder, 'etc')
|
||||||
|
os.makedirs(etcFolder)
|
||||||
|
name = '%s/%s.conf' % (etcFolder, self.appNameLower)
|
||||||
|
n = self.appNameLower
|
||||||
|
f = file(name, 'w')
|
||||||
|
productsFolder = '/usr/lib/python%s/%s/zope' % \
|
||||||
|
(self.pythonVersions[0], self.appName)
|
||||||
|
f.write(zopeConf % ('/var/lib/%s' % n, '/var/lib/%s' % n,
|
||||||
|
'/var/log/%s' % n,
|
||||||
|
'products %s\n' % productsFolder))
|
||||||
|
f.close()
|
||||||
# Get the size of the app, in Kb.
|
# Get the size of the app, in Kb.
|
||||||
cmd = subprocess.Popen(['du', '-b', '-s', 'usr'],stdout=subprocess.PIPE)
|
os.chdir(tempFolder)
|
||||||
|
cmd = subprocess.Popen(['du', '-b', '-s', 'debian'],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
size = int(int(cmd.stdout.read().split()[0])/1024.0)
|
size = int(int(cmd.stdout.read().split()[0])/1024.0)
|
||||||
|
os.chdir(debFolder)
|
||||||
|
# Create data.tar.gz based on it.
|
||||||
|
os.system('tar czvf data.tar.gz *')
|
||||||
# Create the control file
|
# Create the control file
|
||||||
f = file('control', 'w')
|
f = file('control', 'w')
|
||||||
nameSuffix = ''
|
nameSuffix = ''
|
||||||
dependencies = []
|
dependencies = []
|
||||||
if self.appName != 'appy':
|
if self.appName != 'appy':
|
||||||
nameSuffix = '-%s' % self.appName.lower()
|
nameSuffix = '-%s' % self.appNameLower
|
||||||
dependencies.append('python-appy')
|
dependencies.append('python-appy')
|
||||||
if self.depends:
|
if self.depends:
|
||||||
for d in self.depends: dependencies.append(d)
|
for d in self.depends: dependencies.append(d)
|
||||||
|
@ -69,12 +180,15 @@ class Debianizer:
|
||||||
if dependencies:
|
if dependencies:
|
||||||
depends = ', ' + ', '.join(dependencies)
|
depends = ', ' + ', '.join(dependencies)
|
||||||
f.write(debianInfo % (nameSuffix, self.appVersion, size,
|
f.write(debianInfo % (nameSuffix, self.appVersion, size,
|
||||||
self.pythonVersions[0], self.pythonVersions[1],
|
self.pythonVersions[0], depends))
|
||||||
depends))
|
|
||||||
f.close()
|
f.close()
|
||||||
# Create md5sum file
|
# Create md5sum file
|
||||||
f = file('md5sums', 'w')
|
f = file('md5sums', 'w')
|
||||||
for dir, dirnames, filenames in os.walk('usr'):
|
toWalk = ['usr']
|
||||||
|
if self.appName != 'appy':
|
||||||
|
toWalk += ['etc', 'var']
|
||||||
|
for folderToWalk in toWalk:
|
||||||
|
for dir, dirnames, filenames in os.walk(folderToWalk):
|
||||||
for name in filenames:
|
for name in filenames:
|
||||||
m = md5.new()
|
m = md5.new()
|
||||||
pathName = j(dir, name)
|
pathName = j(dir, name)
|
||||||
|
@ -90,7 +204,7 @@ class Debianizer:
|
||||||
f.close()
|
f.close()
|
||||||
# Create postinst, a script that will:
|
# Create postinst, a script that will:
|
||||||
# - bytecompile Python files after the Debian install
|
# - bytecompile Python files after the Debian install
|
||||||
# - create a Zope instance (excepted if we are installing Appy itself).
|
# - change ownership of some files if required
|
||||||
f = file('postinst', 'w')
|
f = file('postinst', 'w')
|
||||||
content = '#!/bin/sh\nset -e\n'
|
content = '#!/bin/sh\nset -e\n'
|
||||||
for version in self.pythonVersions:
|
for version in self.pythonVersions:
|
||||||
|
@ -98,22 +212,16 @@ class Debianizer:
|
||||||
lib = '/usr/lib/python%s' % version
|
lib = '/usr/lib/python%s' % version
|
||||||
cmds = ' %s -m compileall -q %s/%s 2> /dev/null\n' % (bin, lib,
|
cmds = ' %s -m compileall -q %s/%s 2> /dev/null\n' % (bin, lib,
|
||||||
self.appName)
|
self.appName)
|
||||||
if self.appName != 'appy':
|
|
||||||
inst = '/home/zope/%sInstance' % self.appName
|
|
||||||
cmds += ' if [ -e %s ]\n then\n' % inst
|
|
||||||
# If the Zope instance already exists, simply restart it.
|
|
||||||
cmds += ' %s/bin/zopectl restart\n else\n' % inst
|
|
||||||
# Else, create a Zope instance in the home of user "zope".
|
|
||||||
cmds += ' %s %s/appy/bin/new.py zope /usr/lib/zope2.12 ' \
|
|
||||||
'%s\n' % (bin, lib, inst)
|
|
||||||
# Within this instance, create a symlink to the Zope product
|
|
||||||
cmds += ' ln -s %s/%s/zope %s/Products/%s\n' % \
|
|
||||||
(lib, self.appName, inst, self.appName)
|
|
||||||
# Launch the instance
|
|
||||||
cmds += ' %s/bin/zopectl start\n' % inst
|
|
||||||
# Launch OpenOffice in server mode
|
|
||||||
cmds += ' %s/bin/startoo\n fi\n' % inst
|
|
||||||
content += 'if [ -e %s ]\nthen\n%sfi\n' % (bin, cmds)
|
content += 'if [ -e %s ]\nthen\n%sfi\n' % (bin, cmds)
|
||||||
|
if self.appName != 'appy':
|
||||||
|
# Allow user "zope", that runs the Zope instance, to write the
|
||||||
|
# database and log files.
|
||||||
|
content += 'chown -R zope:root /var/lib/%s\n' % self.appNameLower
|
||||||
|
content += 'chown -R zope:root /var/log/%s\n' % self.appNameLower
|
||||||
|
# (re-)start the app
|
||||||
|
content += '%sctl restart\n' % self.appNameLower
|
||||||
|
# (re-)start oo
|
||||||
|
content += 'startoo\n'
|
||||||
f.write(content)
|
f.write(content)
|
||||||
f.close()
|
f.close()
|
||||||
# Create prerm, a script that will remove all pyc files before removing
|
# Create prerm, a script that will remove all pyc files before removing
|
||||||
|
|
|
@ -36,18 +36,28 @@ class FolderDeleter:
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
extsToClean = ('.pyc', '.pyo', '.fsz', '.deltafsz', '.dat', '.log')
|
extsToClean = ('.pyc', '.pyo', '.fsz', '.deltafsz', '.dat', '.log')
|
||||||
def cleanFolder(folder, exts=extsToClean, verbose=False):
|
def cleanFolder(folder, exts=extsToClean, folders=(), verbose=False):
|
||||||
'''This function allows to remove, in p_folder and subfolders, any file
|
'''This function allows to remove, in p_folder and subfolders, any file
|
||||||
whose extension is in p_exts.'''
|
whose extension is in p_exts, and any folder whose name is in
|
||||||
|
p_folders.'''
|
||||||
if verbose: print 'Cleaning folder', folder, '...'
|
if verbose: print 'Cleaning folder', folder, '...'
|
||||||
# Remove files with an extension listed in exts
|
# Remove files with an extension listed in p_exts
|
||||||
|
if exts:
|
||||||
for root, dirs, files in os.walk(folder):
|
for root, dirs, files in os.walk(folder):
|
||||||
for fileName in files:
|
for fileName in files:
|
||||||
ext = os.path.splitext(fileName)[1]
|
ext = os.path.splitext(fileName)[1]
|
||||||
if (ext in exts) or ext.endswith('~'):
|
if (ext in exts) or ext.endswith('~'):
|
||||||
fileToRemove = os.path.join(root, fileName)
|
fileToRemove = os.path.join(root, fileName)
|
||||||
if verbose: print 'Removing %s...' % fileToRemove
|
if verbose: print 'Removing file %s...' % fileToRemove
|
||||||
os.remove(fileToRemove)
|
os.remove(fileToRemove)
|
||||||
|
# Remove folders whose names are in p_folders.
|
||||||
|
if folders:
|
||||||
|
for root, dirs, files in os.walk(folder):
|
||||||
|
for folderName in dirs:
|
||||||
|
if folderName in folders:
|
||||||
|
toDelete = os.path.join(root, folderName)
|
||||||
|
if verbose: print 'Removing folder %s...' % toDelete
|
||||||
|
FolderDeleter.delete(toDelete)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
def copyFolder(source, dest, cleanDest=False):
|
def copyFolder(source, dest, cleanDest=False):
|
||||||
|
|
Loading…
Reference in a new issue