[Checkins] SVN: zc.zope3recipes/trunk/ - Implemented partial
windows support
Roger Ineichen
roger at projekt01.ch
Sun Jul 29 19:36:42 EDT 2007
Log message for revision 78476:
- Implemented partial windows support
- Added separate unit tests for windows
and make sure that the linux test don't run on a windows box
because they generate different things and will generate even
more different things in the future
Note:
I guess the *nix tests are totaly broken on a linux box.
This happens because we can't handle test dependencies anymore with eggs.
The changes in zc.buildout broke all the tests in this package
and nobody was recognizing it. I totaly dislike it that we do not have
a trunk centric development because this will make sure that we can run
tests over at least a minimal set of core components. I'm sure that we
sometimes later will switch back to a trunk development.
I really believe that development and distribution are two different
things we mix together right now in our development process.
Changed:
U zc.zope3recipes/trunk/README.txt
A zc.zope3recipes/trunk/zc/zope3recipes/WINDOWS.txt
U zc.zope3recipes/trunk/zc/zope3recipes/recipes.py
U zc.zope3recipes/trunk/zc/zope3recipes/tests.py
A zc.zope3recipes/trunk/zc/zope3recipes/winctl.py
-=-
Modified: zc.zope3recipes/trunk/README.txt
===================================================================
--- zc.zope3recipes/trunk/README.txt 2007-07-29 22:15:48 UTC (rev 78475)
+++ zc.zope3recipes/trunk/README.txt 2007-07-29 23:36:41 UTC (rev 78476)
@@ -10,10 +10,18 @@
- Don't support package-includes
-Unfortunately, no Windows support at this time.
+Unfortunately, partial Windows support at this time. It works but it's alpha.
.. contents::
+
+Trunk
+*****
+
+Added windows support. Included a custom ZDCmd and a suprocess wrapper
+in winctl.py. Now the basic commands will also work on a windows box.
+
+
Releases
********
Added: zc.zope3recipes/trunk/zc/zope3recipes/WINDOWS.txt
===================================================================
--- zc.zope3recipes/trunk/zc/zope3recipes/WINDOWS.txt (rev 0)
+++ zc.zope3recipes/trunk/zc/zope3recipes/WINDOWS.txt 2007-07-29 23:36:41 UTC (rev 78476)
@@ -0,0 +1,1690 @@
+=============
+Zope3 Recipes
+=============
+
+This documentation describes the windows installation. See README for more
+general information.
+
+The 'application' recipe accepts the following options:
+
+site.zcml
+ The contents of site.zcml.
+
+eggs
+ The names of one or more eggs, with their dependencies that should
+ be included in the Python path of the generated scripts.
+
+
+Lets define some (bogus) eggs that we can use in our application:
+
+ >>> mkdir('demo1')
+ >>> write('demo1', 'setup.py',
+ ... '''
+ ... from setuptools import setup
+ ... setup(name = 'demo1')
+ ... ''')
+
+ >>> mkdir('demo2')
+ >>> write('demo2', 'setup.py',
+ ... '''
+ ... from setuptools import setup
+ ... setup(name = 'demo2', install_requires='demo1')
+ ... ''')
+
+We'll create a buildout.cfg file that defines our application:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = myapp
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:application
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ... ''' % globals())
+
+Now, Let's run the buildout and see what we get:
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Installing myapp.
+ Generated script '/sample-buildout/parts/myapp/runzope'.
+ Generated script '/sample-buildout/parts/myapp/debugzope'.
+
+The runzope script runs the Web server:
+
+ >>> cat('parts', 'myapp', 'runzope')
+ #!/usr/local/bin/python2.4
+ <BLANKLINE>
+ import sys
+ sys.path[0:0] = [
+ '/sample-buildout/demo2',
+ '/sample-buildout/demo1',
+ ]
+ <BLANKLINE>
+ import zope.app.twisted.main
+ <BLANKLINE>
+ if __name__ == '__main__':
+ zope.app.twisted.main.main()
+
+Here, unlike the above example the location path is not included
+in sys.path . Similarly debugzope script is also changed:
+
+ >>> cat('parts', 'myapp', 'debugzope')
+ #!/usr/local/bin/python2.4
+ <BLANKLINE>
+ import sys
+ sys.path[0:0] = [
+ '/sample-buildout/demo2',
+ '/sample-buildout/demo1',
+ '/zope3recipes',
+ ]
+ <BLANKLINE>
+ import zope.app.twisted.main
+ <BLANKLINE>
+ <BLANKLINE>
+ import zc.zope3recipes.debugzope
+ <BLANKLINE>
+ if __name__ == '__main__':
+ zc.zope3recipes.debugzope.debug(main_module=zope.app.twisted.main)
+
+
+Building Zope 3 Applications (from Zope 3 checkouts/tarballs)
+=============================================================
+
+The 'app' recipe works much like the 'application' recipe. It takes
+the same configuration options plus the following one:
+
+zope3
+ The name of a section defining a location option that gives the
+ location of a Zope installation. This can be either a checkout or a
+ distribution. If the location has a lib/python subdirectory, it is
+ treated as a distribution, otherwise, it must have a src
+ subdirectory and will be treated as a checkout. This option defaults
+ to "zope3". And if location is empty, the application will run solely
+ from eggs.
+
+Let's look at an example. We'll make a faux zope installation:
+
+ >>> zope3 = tmpdir('zope3')
+ >>> mkdir(zope3, 'src')
+
+Now we'll create a buildout.cfg file that defines our application:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = myapp
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ... ''' % globals())
+
+Note that our site.zcml file is very small. It expect the application
+zcml to define almost everything. In fact, a site.zcml file will often
+include just a single include directive. We don't need to include the
+surrounding configure element, unless we want a namespace other than
+the zope namespace. A configure directive will be included for us.
+
+Let's run the buildout and see what we get:
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling myapp.
+ Installing myapp.
+ Generated script '/sample-buildout/parts/myapp/runzope'.
+ Generated script '/sample-buildout/parts/myapp/debugzope'.
+
+A directory is created in the parts directory for our application files:
+
+ >>> ls('parts')
+ d myapp
+
+ >>> ls('parts', 'myapp')
+ - debugzope-script.py
+ - debugzope.exe
+ - runzope-script.py
+ - runzope.exe
+ - site.zcml
+
+We get 3 files, two scripts and a site.zcml file. The site.zcml file
+is just what we had in the buildout configuration:
+
+ >>> cat('parts', 'myapp', 'site.zcml')
+ <configure xmlns='http://namespaces.zope.org/zope'
+ xmlns:meta="http://namespaces.zope.org/meta"
+ >
+ <include package="demo2" />
+ <principal
+ id="zope.manager"
+ title="Manager"
+ login="jim"
+ password_manager="SHA1"
+ password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ />
+ <grant
+ role="zope.Manager"
+ principal="zope.manager"
+ />
+ </configure>
+
+Unfortunately, the leading whitespace is stripped from the
+configuration file lines. This is a consequence of the way
+ConfigParser works.
+
+The runzope script runs the Web server:
+
+ >>> cat('parts', 'myapp', 'runzope')
+ #!/usr/local/bin/python2.4
+ <BLANKLINE>
+ import sys
+ sys.path[0:0] = [
+ '/sample-buildout/demo2',
+ '/sample-buildout/demo1',
+ '/zope3/src',
+ ]
+ <BLANKLINE>
+ import zope.app.twisted.main
+ <BLANKLINE>
+ if __name__ == '__main__':
+ zope.app.twisted.main.main()
+
+It includes in it's path the eggs we specified in the configuration
+file, along with their dependencies. Note that we haven't specified a
+configuration file. When runzope is run, a -C option must be used to
+provide a configuration file. -X options can also be provided to
+override configuration file options.
+
+The debugzope script provides access to the object system. When
+debugzope is run, a -C option must be used to provide a configuration
+file. -X options can also be provided to override configuration file
+options. If run without any additional arguments, then an interactive
+interpreter will be started with databases specified in the
+configuration file opened and with the variable root set to the
+application root object. The debugger variable is set to a Zope 3
+debugger. If additional arguments are provided, then the first
+argument should be a script name and the remaining arguments are
+script arguments. The script will be run with the root and debugger
+variables available as global variables.
+
+..
+
+ >>> cat('parts', 'myapp', 'debugzope')
+ #!/usr/local/bin/python2.4
+ <BLANKLINE>
+ import sys
+ sys.path[0:0] = [
+ '/sample-buildout/demo2',
+ '/sample-buildout/demo1',
+ '/zope3/src',
+ '/zope3recipes',
+ ]
+ <BLANKLINE>
+ import zope.app.twisted.main
+ <BLANKLINE>
+ <BLANKLINE>
+ import zc.zope3recipes.debugzope
+ <BLANKLINE>
+ if __name__ == '__main__':
+ zc.zope3recipes.debugzope.debug(main_module=zope.app.twisted.main)
+
+Note that the runzope shown above uses the default, twisted-based
+server components. It's possible to specify which set of server
+components is used: the "servers" setting can be set to either
+"zserver" or "twisted". For the application, this affects the runzope
+script; we'll see additional differences when we create instances of
+the application.
+
+Let's continue to use the twisted servers, but make the selection
+explicit:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = myapp
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... servers = twisted
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Updating myapp.
+
+Note that this is recognized as not being a change to the
+configuration; the messages say that myapp was updated, not
+uninstalled and then re-installed.
+
+The runzope script generated is identical to what we saw before:
+
+ >>> cat('parts', 'myapp', 'runzope')
+ #!/usr/local/bin/python2.4
+ <BLANKLINE>
+ import sys
+ sys.path[0:0] = [
+ '/sample-buildout/demo2',
+ '/sample-buildout/demo1',
+ '/zope3/src',
+ ]
+ <BLANKLINE>
+ import zope.app.twisted.main
+ <BLANKLINE>
+ if __name__ == '__main__':
+ zope.app.twisted.main.main()
+
+We can also specify the ZServer servers explicitly:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = myapp
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... servers = zserver
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling myapp.
+ Installing myapp.
+ Generated script '/sample-buildout/parts/myapp/runzope'.
+ Generated script '/sample-buildout/parts/myapp/debugzope'.
+
+The part has been re-installed, and the runzope script generated is
+different now. Note that the main() function is imported from a
+different package this time:
+
+ >>> cat('parts', 'myapp', 'runzope')
+ #!/usr/local/bin/python2.4
+ <BLANKLINE>
+ import sys
+ sys.path[0:0] = [
+ '/sample-buildout/demo2',
+ '/sample-buildout/demo1',
+ '/zope3/src',
+ ]
+ <BLANKLINE>
+ import zope.app.server.main
+ <BLANKLINE>
+ if __name__ == '__main__':
+ zope.app.server.main.main()
+
+The debugzope script has also been modified to take this into account.
+
+ >>> cat('parts', 'myapp', 'debugzope')
+ #!/usr/local/bin/python2.4
+ <BLANKLINE>
+ import sys
+ sys.path[0:0] = [
+ '/sample-buildout/demo2',
+ '/sample-buildout/demo1',
+ '/zope3/src',
+ '/zope3recipes',
+ ]
+ <BLANKLINE>
+ import zope.app.server.main
+ <BLANKLINE>
+ <BLANKLINE>
+ import zc.zope3recipes.debugzope
+ <BLANKLINE>
+ if __name__ == '__main__':
+ zc.zope3recipes.debugzope.debug(main_module=zope.app.server.main)
+
+
+Legacy Functional Testing Support
+---------------------------------
+
+Zope 3's functional testing support is based on zope.testing test
+layers. There is a default functional test layer that older functional
+tests use. This layer loads the default configuration for the Zope
+application server. It exists to provide support for older functional
+tests that were written before layers were added to the testing
+infrastructure. The default testing layer has a number of
+disadvantages:
+
+- It loads configurations for a large number of packages. This has
+ the potential to introduce testing dependency on all of these
+ packages.
+
+- It required a ftesting.zcml file and makes assumptions about where
+ that file is. In particular, it assumes a location relative to the
+ current working directory when the test is run.
+
+Newer software and maintained software should use their own functional
+testing layers that use test-configuration files defined in packages.
+
+To support older packages that use the default layer, a ftesting.zcml
+option is provided. If it is used, then the contents of the option
+are written to a ftesting.zcml file in the application. In addition,
+an ftesting-base.zcml file is written that includes configuration
+traditionally found in a Zope 3 ftesting-base.zcml excluding reference
+to package-includes.
+
+If we modify our buildout to include an ftesting.zcml option:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = myapp
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... ftesting.zcml =
+ ... <meta:provides feature="devmode" />
+ ... <include file="ftesting-base.zcml" />
+ ... <includeOverrides package="demo2" />
+ ... eggs = demo2
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling myapp.
+ Installing myapp.
+ Generated script '/sample-buildout/parts/myapp/runzope'.
+ Generated script '/sample-buildout/parts/myapp/debugzope'.
+
+We'll get ftesting.zcml files and ftesting-base.zcml files created in
+the application:
+
+ >>> cat('parts', 'myapp', 'ftesting.zcml')
+ <configure xmlns='http://namespaces.zope.org/zope'
+ xmlns:meta="http://namespaces.zope.org/meta"
+ >
+ <BLANKLINE>
+ <meta:provides feature="devmode" />
+ <include file="ftesting-base.zcml" />
+ <includeOverrides package="demo2" />
+ </configure>
+
+ >>> cat('parts', 'myapp', 'ftesting-base.zcml')
+ <BLANKLINE>
+ <configure
+ xmlns="http://namespaces.zope.org/zope"
+ i18n_domain="zope"
+ >
+ <include package="zope.app" />
+ <include package="zope.app" file="ftesting.zcml" />
+ <include package="zope.app.securitypolicy" file="meta.zcml" />
+ <include package="zope.app.securitypolicy" />
+ <securityPolicy
+ component="zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
+ <role id="zope.Anonymous" title="Everybody"
+ description="All users have this role implicitly" />
+ <role id="zope.Manager" title="Site Manager" />
+ <role id="zope.Member" title="Site Member" />
+ <grant permission="zope.View"
+ role="zope.Anonymous" />
+ <grant permission="zope.app.dublincore.view"
+ role="zope.Anonymous" />
+ <grantAll role="zope.Manager" />
+ <include package="zope.app.securitypolicy.tests"
+ file="functional.zcml" />
+ <unauthenticatedPrincipal
+ id="zope.anybody"
+ title="Unauthenticated User"
+ />
+ <unauthenticatedGroup
+ id="zope.Anybody"
+ title="Unauthenticated Users"
+ />
+ <authenticatedGroup
+ id="zope.Authenticated"
+ title="Authenticated Users"
+ />
+ <everybodyGroup
+ id="zope.Everybody"
+ title="All Users"
+ />
+ <principal
+ id="zope.mgr"
+ title="Manager"
+ login="mgr"
+ password="mgrpw" />
+ <principal
+ id="zope.globalmgr"
+ title="Manager"
+ login="globalmgr"
+ password="globalmgrpw" />
+ <grant role="zope.Manager" principal="zope.globalmgr" />
+ </configure>
+
+Defining Zope3 instances
+========================
+
+Having defined an application, we can define one or more instances of
+the application. We do this using the zc.zope3recipes instance
+recipe. The instance recipe has 2 modes, a development and a
+production mode. We'll start with the development mode. In
+development mode, a part directory will be created for each instance
+containing the instance's configuration files. This directory will
+also contain run-time files created by the instances, such as log
+files or zdaemon socket files.
+
+When defining an instance, we need to specify a zope.conf file. The
+recipe can do most of the work for us. Let's look at a a basic
+example:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf = ${database:zconfig}
+ ...
+ ... [database]
+ ... recipe = zc.recipe.filestorage
+ ... ''' % globals())
+
+The application option names an application part. The application
+part will be used to determine the location of the site.zcml file and
+the name of the control script to run.
+
+We specified a zope.conf option which contains a start at our final
+zope.conf file. The recipe will add some bits we leave out. The one
+thing we really need to have is a database definition. We simply
+include the zconfig option from the database section, which we provide
+as a file storage part using the zc.recipe.filestorage recipe. The
+filestorage recipe will create a directory to hold our database and
+compute a zconfig option that we can use in our instance section.
+
+Note that we've replaced the myapp part with the instance part. The
+myapp part will be included by virtue of the reference from the
+instance part.
+
+Let's run the buildout, and see what we get:
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling myapp.
+ Installing database.
+ Installing myapp.
+ Generated script '/sample-buildout/parts/myapp/runzope'.
+ Generated script '/sample-buildout/parts/myapp/debugzope'.
+ Installing instance.
+ Generated script '/sample-buildout/bin/instance'.
+
+We see that the database and myapp parts were included by virtue of
+being referenced from the instance part.
+
+We get new directories for our database and instance:
+
+ >>> ls('parts')
+ d database
+ d instance
+ d myapp
+
+The instance directory contains zdaemon.conf and zope.conf files:
+
+ >>> ls('parts', 'instance')
+ - zdaemon.conf
+ - zope.conf
+
+Let's look at the zope.conf file that was generated:
+
+ >>> cat('parts', 'instance', 'zope.conf')
+ site-definition /sample-buildout/parts/myapp/site.zcml
+ <BLANKLINE>
+ <zodb>
+ <filestorage>
+ path /sample-buildout/parts/database/Data.fs
+ </filestorage>
+ </zodb>
+ <BLANKLINE>
+ <server>
+ address 8080
+ type HTTP
+ </server>
+ <BLANKLINE>
+ <accesslog>
+ <logfile>
+ path /sample-buildout/parts/instance/access.log
+ </logfile>
+ </accesslog>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ formatter zope.exceptions.log.Formatter
+ path STDOUT
+ </logfile>
+ </eventlog>
+
+This uses the twisted server types, since that's the default
+configuration for Zope 3. If we specify use of the ZServer servers,
+the names of the server types are adjusted appropriately:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... servers = zserver
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf = ${database:zconfig}
+ ...
+ ... [database]
+ ... recipe = zc.recipe.filestorage
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling instance.
+ Uninstalling myapp.
+ Updating database.
+ Installing myapp.
+ Generated script '/sample-buildout/parts/myapp/runzope'.
+ Generated script '/sample-buildout/parts/myapp/debugzope'.
+ Installing instance.
+
+
+The generated zope.conf file now uses the ZServer server components
+instead:
+
+ >>> cat('parts', 'instance', 'zope.conf')
+ site-definition /sample-buildout/parts/myapp/site.zcml
+ <BLANKLINE>
+ <zodb>
+ <filestorage>
+ path /sample-buildout/parts/database/Data.fs
+ </filestorage>
+ </zodb>
+ <BLANKLINE>
+ <server>
+ address 8080
+ type WSGI-HTTP
+ </server>
+ <BLANKLINE>
+ <accesslog>
+ <logfile>
+ path /sample-buildout/parts/instance/access.log
+ </logfile>
+ </accesslog>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ formatter zope.exceptions.log.Formatter
+ path STDOUT
+ </logfile>
+ </eventlog>
+
+The Twisted-based servers can also be specified explicitly:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... servers = twisted
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf = ${database:zconfig}
+ ...
+ ... [database]
+ ... recipe = zc.recipe.filestorage
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling instance.
+ Uninstalling myapp.
+ Updating database.
+ Installing myapp.
+ Generated script '/sample-buildout/parts/myapp/runzope'.
+ Generated script '/sample-buildout/parts/myapp/debugzope'.
+ Installing instance.
+
+The generated zope.conf file now uses the Twisted server components
+once more:
+
+ >>> cat('parts', 'instance', 'zope.conf')
+ site-definition /sample-buildout/parts/myapp/site.zcml
+ <BLANKLINE>
+ <zodb>
+ <filestorage>
+ path /sample-buildout/parts/database/Data.fs
+ </filestorage>
+ </zodb>
+ <BLANKLINE>
+ <server>
+ address 8080
+ type HTTP
+ </server>
+ <BLANKLINE>
+ <accesslog>
+ <logfile>
+ path /sample-buildout/parts/instance/access.log
+ </logfile>
+ </accesslog>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ formatter zope.exceptions.log.Formatter
+ path STDOUT
+ </logfile>
+ </eventlog>
+
+It includes the database definition that we provided in the zope.conf
+option. It has a site-definition option that names the site.zcml file
+from our application directory.
+
+We didn't specify any server or logging ZConfig sections, so some were
+generated for us.
+
+Note that, by default, the event-log output goes to standard output.
+We'll say more about that when we talk about the zdaemon
+configuration later.
+
+If we specify a server section ourselves:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf = ${database:zconfig}
+ ... <server>
+ ... type PostmortemDebuggingHTTP
+ ... address 8080
+ ... </server>
+ ...
+ ... [database]
+ ... recipe = zc.recipe.filestorage
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling instance.
+ Updating database.
+ Updating myapp.
+ Installing instance.
+
+Then the section (or sections) we provide will be used and new ones
+won't be added:
+
+ >>> cat('parts', 'instance', 'zope.conf')
+ site-definition /sample-buildout/parts/myapp/site.zcml
+ <BLANKLINE>
+ <zodb>
+ <filestorage>
+ path /sample-buildout/parts/database/Data.fs
+ </filestorage>
+ </zodb>
+ <BLANKLINE>
+ <server>
+ address 8080
+ type PostmortemDebuggingHTTP
+ </server>
+ <BLANKLINE>
+ <accesslog>
+ <logfile>
+ path /sample-buildout/parts/instance/access.log
+ </logfile>
+ </accesslog>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ formatter zope.exceptions.log.Formatter
+ path STDOUT
+ </logfile>
+ </eventlog>
+
+If we just want to specify alternate ports or addresses, we can use
+the address option which accepts zero or more address specifications:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf = ${database:zconfig}
+ ... address = 8081 foo.com:8082
+ ...
+ ... [database]
+ ... recipe = zc.recipe.filestorage
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling instance.
+ Updating database.
+ Updating myapp.
+ Installing instance.
+
+ >>> cat('parts', 'instance', 'zope.conf')
+ site-definition /sample-buildout/parts/myapp/site.zcml
+ <BLANKLINE>
+ <zodb>
+ <filestorage>
+ path /sample-buildout/parts/database/Data.fs
+ </filestorage>
+ </zodb>
+ <BLANKLINE>
+ <server>
+ address 8081
+ type HTTP
+ </server>
+ <BLANKLINE>
+ <server>
+ address foo.com:8082
+ type HTTP
+ </server>
+ <BLANKLINE>
+ <accesslog>
+ <logfile>
+ path /sample-buildout/parts/instance/access.log
+ </logfile>
+ </accesslog>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ formatter zope.exceptions.log.Formatter
+ path STDOUT
+ </logfile>
+ </eventlog>
+
+We can specify our own accesslog and eventlog configuration. For
+example, to send the event-log output to a file and suppress the
+access log:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf = ${database:zconfig}
+ ... <eventlog>
+ ... <logfile>
+ ... path ${buildout:parts-directory}/instance/event.log
+ ... formatter zope.exceptions.log.Formatter
+ ... </logfile>
+ ... </eventlog>
+ ... <accesslog>
+ ... </accesslog>
+ ...
+ ... address = 8081
+ ...
+ ... [database]
+ ... recipe = zc.recipe.filestorage
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling instance.
+ Updating database.
+ Updating myapp.
+ Installing instance.
+
+ >>> cat('parts', 'instance', 'zope.conf')
+ site-definition /sample-buildout/parts/myapp/site.zcml
+ <BLANKLINE>
+ <zodb>
+ <filestorage>
+ path /sample-buildout/parts/database/Data.fs
+ </filestorage>
+ </zodb>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ formatter zope.exceptions.log.Formatter
+ path /sample-buildout/parts/instance/event.log
+ </logfile>
+ </eventlog>
+ <BLANKLINE>
+ <accesslog>
+ </accesslog>
+ <BLANKLINE>
+ <server>
+ address 8081
+ type HTTP
+ </server>
+
+Let's look at the zdaemon.conf file:
+
+ >>> cat('parts', 'instance', 'zdaemon.conf')
+ <runner>
+ daemon on
+ directory /sample-buildout/parts/instance
+ program /sample-buildout/parts/myapp/runzope -C /sample-buildout/parts/instance/zope.conf
+ socket-name /sample-buildout/parts/instance/zdaemon.sock
+ transcript /sample-buildout/parts/instance/z3.log
+ </runner>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ path /sample-buildout/parts/instance/z3.log
+ </logfile>
+ </eventlog>
+
+Here we see a fairly ordinary zdaemon.conf file. The program option
+refers to the runzope script in our application directory. The socket
+file, used for communication between the zdaemon command-line script
+and the zademon manager is placed in the instance directory.
+
+If you want to override any part of the generated zdaemon output,
+simply provide a zdaemon.conf option in your instance section:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf = ${database:zconfig}
+ ... address = 8081
+ ... zdaemon.conf =
+ ... <runner>
+ ... daemon off
+ ... socket-name /sample-buildout/parts/instance/sock
+ ... transcript /dev/null
+ ... </runner>
+ ... <eventlog>
+ ... </eventlog>
+ ...
+ ... [database]
+ ... recipe = zc.recipe.filestorage
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling instance.
+ Updating database.
+ Updating myapp.
+ Installing instance.
+
+ >>> cat('parts', 'instance', 'zdaemon.conf')
+ <runner>
+ daemon off
+ directory /sample-buildout/parts/instance
+ program /sample-buildout/parts/myapp/runzope -C /sample-buildout/parts/instance/zope.conf
+ socket-name /sample-buildout/parts/instance/sock
+ transcript /dev/null
+ </runner>
+ <BLANKLINE>
+ <eventlog>
+ </eventlog>
+
+In addition to the configuration files, a control script is generated
+in the buildout bin directory:
+
+ >>> ls('bin')
+ - buildout-script.py
+ - buildout.exe
+ - instance-script.py
+ - instance.exe
+
+..
+
+ >>> cat('bin', 'instance')
+ #!/usr/local/bin/python2.4
+ <BLANKLINE>
+ import sys
+ sys.path[0:0] = [
+ '/sample-buildout/eggs/zdaemon-2.0-py2.4.egg',
+ '/sample-buildout/eggs/setuptools-0.6-py2.4.egg',
+ '/sample-buildout/eggs/ZConfig-2.3-py2.4.egg',
+ '/zope3recipes',
+ ]
+ <BLANKLINE>
+ import zc.zope3recipes.winctl
+ <BLANKLINE>
+ if __name__ == '__main__':
+ zc.zope3recipes.winctl.main([
+ '/sample-buildout/parts/myapp/debugzope',
+ '/sample-buildout/parts/instance/zope.conf',
+ '-C', '/sample-buildout/parts/instance/zdaemon.conf',
+ ]+sys.argv[1:]
+ )
+
+Some configuration sections can include a key multiple times; the ZEO
+client section works this way. When a key is given multiple times,
+all values are included in the resulting configuration in the order in
+which they're give in the input::
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf =
+ ... <zodb>
+ ... <zeoclient>
+ ... server 127.0.0.1:8001
+ ... server 127.0.0.1:8002
+ ... </zeoclient>
+ ... </zodb>
+ ... address = 8081
+ ... zdaemon.conf =
+ ... <runner>
+ ... daemon off
+ ... socket-name /sample-buildout/parts/instance/sock
+ ... transcript /dev/null
+ ... </runner>
+ ... <eventlog>
+ ... </eventlog>
+ ...
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling instance.
+ Uninstalling database.
+ Updating myapp.
+ Installing instance.
+
+ >>> cat('parts', 'instance', 'zope.conf')
+ site-definition /sample-buildout/parts/myapp/site.zcml
+ <BLANKLINE>
+ <zodb>
+ <zeoclient>
+ server 127.0.0.1:8001
+ server 127.0.0.1:8002
+ </zeoclient>
+ </zodb>
+ <BLANKLINE>
+ <server>
+ address 8081
+ type HTTP
+ </server>
+ <BLANKLINE>
+ <accesslog>
+ <logfile>
+ path /sample-buildout/parts/instance/access.log
+ </logfile>
+ </accesslog>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ formatter zope.exceptions.log.Formatter
+ path STDOUT
+ </logfile>
+ </eventlog>
+
+
+Log files
+---------
+
+The log file settings deserver some explanation. The Zope event log
+only captures output from logging calls. In particular, it doesn't
+capture startup errors written to standard error. The zdaemon
+transcript log is very useful for capturing this output. Without it,
+error written to standard error are lost when running as a daemon.
+The default Zope 3 configuration in the past was to write the Zope
+access and event log output to both files and standard output and to
+define a transcript log. This had the effect that the transcript
+duplicated the contents of the event log and access logs, in addition
+to capturing other output. This was space inefficient.
+
+This recipe's approach is to combine the zope and zdaemon event-log
+information as well as Zope error output into a single log file. We
+do this by directing Zope's event log to standard output, where it is
+useful when running Zope in foreground mode and where it can be
+captured by the zdaemon transcript log.
+
+Unix Deployments
+----------------
+
+The instance recipe provides support for Unix deployments, as provided
+by the zc.recipe.deployment recipe. A deployment part defines a number of
+options used by the instance recipe:
+
+etc-directory
+ The name of the directory where configuration files should be
+ placed. This defaults to /etc/NAME, where NAME is the deployment
+ name.
+
+log-directory
+ The name of the directory where application instances should write
+ their log files. This defaults to /var/log/NAME, where NAME is
+ the deployment name.
+
+run-directory
+ The name of the directory where application instances should put
+ their run-time files such as pid files and inter-process
+ communication socket files. This defaults to /var/run/NAME, where
+ NAME is the deployment name.
+
+rc-directory
+ The name of the directory where run-control scripts should be
+ installed.
+
+user
+ The name of a user that processes should run as.
+
+The deployment recipe has to be run as root for various reasons, but
+we can create a faux deployment by providing a section with the needed
+data. Let's update our configuration to use a deployment. We'll first
+create a faux installation root:
+
+ >>> root = tmpdir('root')
+ >>> mkdir(root, 'etc')
+ >>> mkdir(root, 'etc', 'myapp-run')
+ >>> mkdir(root, 'etc', 'init.d')
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf = ${database:zconfig}
+ ... address = 8081
+ ... deployment = myapp-run
+ ...
+ ... [database]
+ ... recipe = zc.recipe.filestorage
+ ...
+ ... [myapp-run]
+ ... etc-directory = %(root)s/etc/myapp-run
+ ... rc-directory = %(root)s/etc/init.d
+ ... log-directory = %(root)s/var/log/myapp-run
+ ... run-directory = %(root)s/var/run/myapp-run
+ ... user = zope
+ ... ''' % globals())
+
+Here we've added a deployment section, myapp-run, and added a
+deployment option to our instance part telling the instance recipe to
+use the deployment. If we rerun the buildout:
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling instance.
+ Installing database.
+ Updating myapp.
+ Installing instance.
+ Generated script '/root/etc/init.d/myapp-run-instance'.
+
+The installer files will move. We'll no-longer have the instance part:
+
+ >>> ls('parts')
+ d database
+ d myapp
+
+or the control script:
+
+ >>> ls('bin')
+ - buildout-script.py
+ - buildout.exe
+ - instance-script.py
+ - instance.exe
+
+Rather, we'll get our configuration files in the /etc/myapp-run directory:
+
+ >>> ls(root, 'etc', 'myapp-run')
+ - instance-zdaemon.conf
+ - instance-zope.conf
+
+Note that the instance name was added as a prefix to the file names,
+since we'll typically have additional instances in the deployment.
+
+The control script is in the init.d directory:
+
+ >>> ls(root, 'etc', 'init.d')
+ - myapp-run-instance-script.py
+ - myapp-run-instance.exe
+
+Note that the deployment name is added as a prefix of the control
+script name.
+
+The configuration files have changed to reflect the deployment
+locations:
+
+ >>> cat(root, 'etc', 'myapp-run', 'instance-zope.conf')
+ site-definition /sample-buildout/parts/myapp/site.zcml
+ <BLANKLINE>
+ <zodb>
+ <filestorage>
+ path /sample-buildout/parts/database/Data.fs
+ </filestorage>
+ </zodb>
+ <BLANKLINE>
+ <server>
+ address 8081
+ type HTTP
+ </server>
+ <BLANKLINE>
+ <accesslog>
+ <logfile>
+ path /root/var/log/myapp-run/instance-access.log
+ </logfile>
+ </accesslog>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ formatter zope.exceptions.log.Formatter
+ path STDOUT
+ </logfile>
+ </eventlog>
+
+ >>> cat(root, 'etc', 'myapp-run', 'instance-zdaemon.conf')
+ <runner>
+ daemon on
+ directory /root/var/run/myapp-run
+ program /sample-buildout/parts/myapp/runzope -C /root/etc/myapp-run/instance-zope.conf
+ socket-name /root/var/run/myapp-run/instance-zdaemon.sock
+ transcript /root/var/log/myapp-run/instance-z3.log
+ user zope
+ </runner>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ path /root/var/log/myapp-run/instance-z3.log
+ </logfile>
+ </eventlog>
+
+Defining multiple similar instances
+-----------------------------------
+
+Often you want to define multiple instances that differ only by one or
+two options (e.g. an address). The extends option lets you name a
+section from which default options should be loaded. Any options in
+the source section not defined in the extending section are added to
+the extending section.
+
+Let's update our buildout to add a new instance:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... develop = demo1 demo2
+ ... parts = instance instance2
+ ...
+ ... [zope3]
+ ... location = %(zope3)s
+ ...
+ ... [myapp]
+ ... recipe = zc.zope3recipes:app
+ ... site.zcml = <include package="demo2" />
+ ... <principal
+ ... id="zope.manager"
+ ... title="Manager"
+ ... login="jim"
+ ... password_manager="SHA1"
+ ... password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+ ... />
+ ... <grant
+ ... role="zope.Manager"
+ ... principal="zope.manager"
+ ... />
+ ... eggs = demo2
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf = ${database:zconfig}
+ ... address = 8081
+ ... deployment = myapp-run
+ ...
+ ... [instance2]
+ ... recipe = zc.zope3recipes:instance
+ ... extends = instance
+ ... address = 8082
+ ...
+ ... [database]
+ ... recipe = zc.recipe.filestorage
+ ...
+ ... [myapp-run]
+ ... etc-directory = %(root)s/etc/myapp-run
+ ... rc-directory = %(root)s/etc/init.d
+ ... log-directory = %(root)s/var/log/myapp-run
+ ... run-directory = %(root)s/var/run/myapp-run
+ ... user = zope
+ ... ''' % globals())
+
+ >>> print system(join('bin', 'buildout')),
+ Develop: '/sample-buildout/demo1'
+ Develop: '/sample-buildout/demo2'
+ Uninstalling instance.
+ Updating database.
+ Updating myapp.
+ Installing instance.
+ Installing instance2.
+ Generated script '/root/etc/init.d/myapp-run-instance2'.
+
+Now, we have the new instance configuration files:
+
+ >>> ls(root, 'etc', 'myapp-run')
+ - instance-zdaemon.conf
+ - instance-zope.conf
+ - instance2-zdaemon.conf
+ - instance2-zope.conf
+
+ >>> cat(root, 'etc', 'myapp-run', 'instance2-zope.conf')
+ site-definition /sample-buildout/parts/myapp/site.zcml
+ <BLANKLINE>
+ <zodb>
+ <filestorage>
+ path /sample-buildout/parts/database/Data.fs
+ </filestorage>
+ </zodb>
+ <BLANKLINE>
+ <server>
+ address 8082
+ type HTTP
+ </server>
+ <BLANKLINE>
+ <accesslog>
+ <logfile>
+ path /root/var/log/myapp-run/instance2-access.log
+ </logfile>
+ </accesslog>
+ <BLANKLINE>
+ <eventlog>
+ <logfile>
+ formatter zope.exceptions.log.Formatter
+ path STDOUT
+ </logfile>
+ </eventlog>
+
+
+test_winctl
+-----------
+
+The winctl script is an extended version of zdaemon that provides an
+extra command, run. Let's create a buildout that installs it as an
+ordinary script:
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... parts = winctl
+ ...
+ ... [winctl]
+ ... recipe = zc.recipe.egg
+ ... eggs = zc.zope3recipes
+ ... zdaemon
+ ... entry-points = winctl=zc.zope3recipes.winctl:main
+ ... scripts = winctl
+ ... ''')
+
+ >>> print system(join('bin', 'buildout')),
+ Uninstalling instance2.
+ Uninstalling instance.
+ Uninstalling myapp.
+ Uninstalling database.
+ Installing winctl.
+ Generated script '/sample-buildout/bin/winctl'.
+
+We'll create a configuration file:
+
+ >>> write('doecho.bat', 'echo %*')
+
+ >>> write('conf',
+ ... '''
+ ... <runner>
+ ... program doecho.bat hi
+ ... </runner>
+ ... ''')
+
+The configuration doesn't matter much. :)
+
+Unlike a normal zdaemon script, we have to pass two extra arguments, a
+script to run the zope debugger with, and the name of a zope
+configuration file. For demonstration purposes, we'll just use echo.
+
+confPath = os.path.abspath(conf)
+
+ >>> conf = join(sample_buildout, 'conf')
+ >>> echo = join(sample_buildout, 'doecho.bat')
+ >>> cmd = '%s %s zope.conf -C%s fg there' % (join('bin', 'winctl'), echo, conf)
+ >>> print system(cmd) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+ <BLANKLINE>
+ /sample-buildout>echo hi there
+ hi there
+ Zope3 started in forground: doecho.bat hi there
+ <BLANKLINE>
+
+Notice:
+
+ - The first 2 arguments were ignored.
+
+ - It got the program, 'echo.bat hi', from the configuration file.
+
+ - We ran the program in the foreground, passing the extra argument, there.
+
+Now, if we use the run command, it will run the script we passed as
+the first argument:
+
+ >>> cmd = '%s %s zope.conf -C%s run there' % (join('bin', 'winctl'), echo, conf)
+ >>> print system(cmd) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+ <BLANKLINE>
+ /sample-buildout>echo -C zope.conf there
+ -C zope.conf there
+ Debug Zope3: /sample-buildout/doecho.bat -C zope.conf there
+ Zope3 started in debug mode, pid=...
+ <BLANKLINE>
+
+debug is another name for run:
+
+ >>> cmd = '%s %s zope.conf -C%s debug there' % (join('bin', 'winctl'), echo, conf)
+ >>> print system(cmd) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+ <BLANKLINE>
+ /sample-buildout>echo -C zope.conf there
+ -C zope.conf there
+ Debug Zope3: /sample-buildout/doecho.bat -C zope.conf there
+ Zope3 started in debug mode, pid=...
+ <BLANKLINE>
+
+test_sane_errors_from_recipe
+----------------------------
+
+ >>> write('buildout.cfg',
+ ... '''
+ ... [buildout]
+ ... parts = instance
+ ...
+ ... [myapp]
+ ... location = foo
+ ... ;; Note that 'servers' has a default value when the
+ ... ;; application recipe is involved.
+ ... servers = twisted
+ ...
+ ... [instance]
+ ... recipe = zc.zope3recipes:instance
+ ... application = myapp
+ ... zope.conf =
+ ... ''')
+
+ >>> print system(join('bin', 'buildout')),
+ Couldn't find index page for 'zc.recipe.egg' (maybe misspelled?)
+ Uninstalling winctl.
+ Installing instance.
+ While:
+ Installing instance.
+ Error: No database sections have been defined.
Modified: zc.zope3recipes/trunk/zc/zope3recipes/recipes.py
===================================================================
--- zc.zope3recipes/trunk/zc/zope3recipes/recipes.py 2007-07-29 22:15:48 UTC (rev 78475)
+++ zc.zope3recipes/trunk/zc/zope3recipes/recipes.py 2007-07-29 23:36:41 UTC (rev 78476)
@@ -14,7 +14,7 @@
"""Collected Zope 3 recipes
"""
-import os, shutil
+import os, sys, shutil
import zc.buildout
import zc.recipe.egg
import pkg_resources
@@ -30,6 +30,10 @@
'zserver': ('zope.app.server.main', 'WSGI-HTTP'),
}
+WIN = False
+if sys.platform[:3].lower() == "win":
+ WIN = True
+
class Application(object):
def __init__(self, buildout, name, options):
@@ -292,22 +296,40 @@
open(zope_conf_path, 'w').write(str(zope_conf))
open(zdaemon_conf_path, 'w').write(str(zdaemon_conf))
- zc.buildout.easy_install.scripts(
- [(rc, 'zc.zope3recipes.ctl', 'main')],
- ws, options['executable'], options['bin-directory'],
- extra_paths = [this_loc],
- arguments = ('['
- '\n %r,'
- '\n %r,'
- '\n %r, %r,'
- '\n ]+sys.argv[1:]'
- '\n '
- % (os.path.join(app_loc, 'debugzope'),
- zope_conf_path,
- '-C', zdaemon_conf_path,
- )
- ),
- )
+ if WIN:
+ zc.buildout.easy_install.scripts(
+ [(rc, 'zc.zope3recipes.winctl', 'main')],
+ ws, options['executable'], options['bin-directory'],
+ extra_paths = [this_loc],
+ arguments = ('['
+ '\n %r,'
+ '\n %r,'
+ '\n %r, %r,'
+ '\n ]+sys.argv[1:]'
+ '\n '
+ % (os.path.join(app_loc, 'debugzope'),
+ zope_conf_path,
+ '-C', zdaemon_conf_path,
+ )
+ ),
+ )
+ else:
+ zc.buildout.easy_install.scripts(
+ [(rc, 'zc.zope3recipes.ctl', 'main')],
+ ws, options['executable'], options['bin-directory'],
+ extra_paths = [this_loc],
+ arguments = ('['
+ '\n %r,'
+ '\n %r,'
+ '\n %r, %r,'
+ '\n ]+sys.argv[1:]'
+ '\n '
+ % (os.path.join(app_loc, 'debugzope'),
+ zope_conf_path,
+ '-C', zdaemon_conf_path,
+ )
+ ),
+ )
return creating
Modified: zc.zope3recipes/trunk/zc/zope3recipes/tests.py
===================================================================
--- zc.zope3recipes/trunk/zc/zope3recipes/tests.py 2007-07-29 22:15:48 UTC (rev 78475)
+++ zc.zope3recipes/trunk/zc/zope3recipes/tests.py 2007-07-29 23:36:41 UTC (rev 78476)
@@ -140,19 +140,22 @@
),
])
+
def test_suite():
- return unittest.TestSuite((
- doctest.DocTestSuite(
+ suite = unittest.TestSuite()
+ if sys.platform[:3].lower() == "win":
+ suite.addTest(doctest.DocFileSuite('WINDOWS.txt',
setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
- checker=checker,
- ),
- doctest.DocFileSuite(
- 'README.txt',
+ checker=checker))
+ else:
+ suite.addTest(doctest.DocTestSuite(
setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
- checker=checker,
- ),
-
- ))
+ checker=checker))
+ suite.addTest(doctest.DocFileSuite('README.txt',
+ setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
+ checker=checker))
+ return suite
+
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Added: zc.zope3recipes/trunk/zc/zope3recipes/winctl.py
===================================================================
--- zc.zope3recipes/trunk/zc/zope3recipes/winctl.py (rev 0)
+++ zc.zope3recipes/trunk/zc/zope3recipes/winctl.py 2007-07-29 23:36:41 UTC (rev 78476)
@@ -0,0 +1,336 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Top-level controller for 'zopectl'.
+"""
+
+import os, sys
+import socket
+import subprocess
+import errno
+import zdaemon.zdctl
+from zdaemon.zdctl import ZDCmd
+from zdaemon.zdoptions import ZDOptions
+from ZConfig.components.logger.handlers import FileHandlerFactory
+from ZConfig.datatypes import existing_dirpath
+
+if sys.platform[:3].lower() == "win":
+ import win32api
+ import win32com.client
+ import win32process
+ from win32file import ReadFile, WriteFile
+ from win32pipe import PeekNamedPipe
+ import msvcrt
+ import select
+
+
+def getChildrenPidsOfPid(pid):
+ """Returns the children pids of a pid"""
+ wmi = win32com.client.GetObject('winmgmts:')
+ children = wmi.ExecQuery('Select * from win32_process where ParentProcessId=%s' % pid)
+ pids = []
+ for proc in children:
+ pids.append(proc.Properties_('ProcessId'))
+ return pids
+
+
+def getDaemonProcess(pid):
+ """Returns the daemon proces."""
+ wmi = win32com.client.GetObject('winmgmts:')
+ children = wmi.ExecQuery('Select * from win32_process where ProcessId=%s' % pid)
+ pids = []
+ for proc in children:
+ pids.append(proc.Properties_('ProcessId'))
+ if len(pids) == 1:
+ return pids[0]
+ return None
+
+
+def getZopeScriptProcess(pid):
+ """Returns the daemon proces."""
+ wmi = win32com.client.GetObject('winmgmts:')
+ children = wmi.ExecQuery('Select * from win32_process where ParentProcessId=%s' % pid)
+ pids = []
+ for proc in children:
+ pids.append(proc.Properties_('ProcessId'))
+ if len(pids) == 1:
+ return pids[0]
+ return None
+
+
+def kill(pid):
+ """kill function for Win32"""
+ handle = win32api.OpenProcess(1, 0, pid)
+ win32api.TerminateProcess(handle, 0)
+ win32api.CloseHandle(handle)
+
+
+def killAll(pid):
+ """Kill runzope and the python process started by runzope."""
+ pids = getChildrenPidsOfPid(pid)
+ for pid in pids:
+ kill(pid)
+
+
+class Popen(subprocess.Popen):
+ def recv(self, maxsize=None):
+ return self._recv('stdout', maxsize)
+
+ def recv_err(self, maxsize=None):
+ return self._recv('stderr', maxsize)
+
+ def send_recv(self, input='', maxsize=None):
+ return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
+
+ def get_conn_maxsize(self, which, maxsize):
+ if maxsize is None:
+ maxsize = 1024
+ elif maxsize < 1:
+ maxsize = 1
+ return getattr(self, which), maxsize
+
+ def _close(self, which):
+ getattr(self, which).close()
+ setattr(self, which, None)
+
+ def send(self, input):
+ if not self.stdin:
+ return None
+
+ try:
+ x = msvcrt.get_osfhandle(self.stdin.fileno())
+ (errCode, written) = WriteFile(x, input)
+ except ValueError:
+ return self._close('stdin')
+ except (subprocess.pywintypes.error, Exception), why:
+ if why[0] in (109, errno.ESHUTDOWN):
+ return self._close('stdin')
+ raise
+
+ return written
+
+ def _recv(self, which, maxsize):
+ conn, maxsize = self.get_conn_maxsize(which, maxsize)
+ if conn is None:
+ return None
+
+ try:
+ x = msvcrt.get_osfhandle(conn.fileno())
+ (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
+ if maxsize < nAvail:
+ nAvail = maxsize
+ if nAvail > 0:
+ (errCode, read) = ReadFile(x, nAvail, None)
+ except ValueError:
+ return self._close(which)
+ except (subprocess.pywintypes.error, Exception), why:
+ if why[0] in (109, errno.ESHUTDOWN):
+ return self._close(which)
+ raise
+
+ if self.universal_newlines:
+ read = self._translate_newlines(read)
+ return read
+
+
+# TODO: implement a win service script and add install and remove methods for
+# the service.
+# Also implement start and stop methods controlling the windows service daemon
+# if the service is installed. Use the allready defined methods if no service
+# is installed.
+
+#class ZopeCtlOptions(ZDOptions):
+# """Zope controller options."""
+#
+# def realize(self, *args, **kwds):
+# ZopeCtlOptions.realize(self, *args, **kwds)
+#
+# # Add the path to the zopeservice.py script, which is needed for
+# # some of the Windows specific commands
+# servicescript = os.path.join(self.directory, 'bin', 'zopeservice.py')
+# self.servicescript = '"%s" %s' % (self.python, servicescript)
+
+
+class ZopectlCmd(zdaemon.zdctl.ZDCmd):
+ """Manages Zope start and stop etc.
+
+ This implementation uses a subprocess for execute the given python script.
+
+ There is also a windows service daemon which can get installed with the
+ install and remove methods. If a windows service is installed, the
+ controller dispatches the start and stop commands to the service. If no
+ service is installed, we use the subprocess instead.
+ """
+
+ zd_up = 0
+ zd_pid = 0
+ zd_status = None
+ proc = None
+
+ def do_install(self, arg):
+ """Install the windows service."""
+ #program = "%s install" % self.options.servicescript
+ #print program
+ #subprocess.Popen(program)
+ print "Not implemented right now"
+
+ def help_install(self):
+ print "install -- Installs Zope3 as a Windows service."
+ print "Not implemented right now"
+
+ def do_remove(self, arg):
+ """Remove the windows service."""
+ #program = "%s remove" % self.options.servicescript
+ #print program
+ #subprocess.Popen(program)
+ print "Not implemented right now"
+
+ def help_remove(self):
+ print "remove -- Removes the Zope3 Windows service."
+ print "Not implemented right now"
+
+ def do_debug(self, arg):
+ # Start the process
+ if self.zd_pid:
+ print "Zope3 already running; pid=%d" % self.zd_pid
+ return
+ args = " ".join(self.options.args[1:])
+ cmds = [self._debugzope, '-C', self._zope_conf, args]
+ program = " ".join(cmds)
+ print "Debug Zope3: ", program
+ self.proc = Popen(program)
+ self.zd_pid = self.proc.pid
+ self.zd_up = 1
+ self.awhile(lambda: self.zd_pid,
+ "Zope3 started in debug mode, pid=%(zd_pid)d")
+
+ def help_debug(self):
+ print "debug -- Initialize the Zope application, providing a"
+ print " debugger object at an interactive Python prompt."
+
+ do_run = do_debug
+
+ def help_run(self):
+ print "run <script> [args] -- run a Python script with the Zope "
+ print " environment set up. The script has "
+ print " 'root' exposed as the root container."
+
+ def send_action(self, action):
+ """Dispatch actions to subprocess."""
+ try:
+ self.proc.send(action + "\n")
+ response = ""
+ while 1:
+ data = self.proc.recv(1000)
+ if not data:
+ break
+ response += data
+ return response
+ except (subprocess.pywintypes.error, Exception):
+ return None
+
+ def get_status(self):
+ if not self.zd_pid:
+ return None
+ proc = getDaemonProcess(self.zd_pid)
+ if proc is not None:
+ self.zd_up = 1
+ proc = getZopeScriptProcess(self.zd_pid)
+ if proc is not None:
+ self.zd_status = "Zope3 is running"
+ return self.zd_status
+ return None
+
+ def do_stop(self, arg):
+ # Stop the Windows process
+ if not self.zd_pid:
+ print "Zope3 is not running"
+ return
+
+ killAll(self.zd_pid)
+ self.zd_up = 0
+ self.zd_pid = 0
+ cpid = win32process.GetCurrentProcessId()
+ self.awhile(lambda: not getChildrenPidsOfPid(cpid), "Zope3 stopped")
+
+ def do_kill(self, arg):
+ self.do_stop(arg)
+
+ def do_restart(self, arg):
+ pid = self.zd_pid
+ if self.zd_pid:
+ self.do_stop(arg)
+ self.do_start(arg)
+ else:
+ self.do_start(arg)
+ self.awhile(lambda: self.zd_pid not in (0, pid),
+ "Zope3 restarted, pid=%(zd_pid)d")
+
+ def show_options(self):
+ print "winctl options:"
+ print "configfile: ", repr(self.options.configfile)
+ print "python: ", repr(self.options.python)
+ print "program: ", repr(self.options.program)
+ print "user: ", repr(self.options.user)
+ print "directory: ", repr(self.options.directory)
+ print "logfile: ", repr(self.options.logfile)
+
+ def do_start(self, arg):
+ # Start the process
+ if self.zd_pid:
+ print "Zope3 already running; pid=%d" % self.zd_pid
+ return
+ program = " ".join(self.options.program)
+ print "Starting Zope3: ", program
+ self.proc = Popen(program)
+ self.zd_pid = self.proc.pid
+ self.zd_up = 1
+ self.awhile(lambda: self.zd_pid,
+ "Zope3 started, pid=%(zd_pid)d")
+
+ def do_fg(self, arg):
+ self.do_foreground(arg)
+
+ def help_fg(self):
+ self.help_foreground()
+
+ def do_foreground(self, arg):
+ # Start the process
+ if self.zd_pid:
+ print "To run the Zope3 in the foreground, please stop it first."
+ return
+
+ program = self.options.program + self.options.args[1:]
+ program = " ".join(program)
+ sys.stdout.flush()
+ try:
+ subprocess.call(program)
+ print "Zope3 started in forground: ", program
+ except KeyboardInterrupt:
+ print
+
+ def help_foreground(self):
+ print "foreground -- Run the program in the forground."
+ print "fg -- an alias for foreground."
+ print "Not supported on windows will call start"
+
+
+def main(args=None):
+ if args is None:
+ args = sys.argv[1:]
+
+ class Cmd(ZopectlCmd):
+ _debugzope = args.pop(0)
+ _zope_conf = args.pop(0)
+
+ zdaemon.zdctl.main(args, None, Cmd)
More information about the Checkins
mailing list