changeset 4554:2dad9708c89f

paster: add install-iis command to automate IIS handler generation A new paster command, install-iis, is added that automates generating the ISAPI-WSGI file that allows IIS to serve up Kallithea's WSGI application using IIS' ISAPI filters. The paster command's output also describes the final steps necessary to complete IIS installation.
author Henrik Stuart <hg@hstuart.dk>
date Sun, 14 Sep 2014 07:39:06 +0200
parents e98aa842683c
children 0ece1a4cf6e3
files docs/installation_iis.rst kallithea/lib/paster_commands/install_iis.py setup.py
diffstat 3 files changed, 98 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/docs/installation_iis.rst	Thu Oct 02 23:49:16 2014 +0200
+++ b/docs/installation_iis.rst	Sun Sep 14 07:39:06 2014 +0200
@@ -44,50 +44,24 @@
 ISAPI Handler
 .............
 
-The ISAPI handler needs to be generated from a custom file. Imagining that the
-Kallithea installation is in ``c:\inetpub\kallithea``, we would have a file in
-the same directory called, e.g. ``dispatch.py`` with the following contents::
-
-    import sys
+The ISAPI handler can be generated using::
 
-    if hasattr(sys, "isapidllhandle"):
-        import win32traceutil
-
-    import isapi_wsgi
-
-    def __ExtensionFactory__():
-        from paste.deploy import loadapp
-        from paste.script.util.logging_config import fileConfig
-        fileConfig('c:\\inetpub\\kallithea\\production.ini')
-        application = loadapp('config:c:\\inetpub\\kallithea\\production.ini')
+    paster install-iis my.ini --root=/
 
-        def app(environ, start_response):
-            user = environ.get('REMOTE_USER', None)
-            if user is not None:
-                os.environ['REMOTE_USER'] = user
-            return application(environ, start_response)
-
-        return isapi_wsgi.ISAPIThreadPoolHandler(app)
+This will generate a ``dispatch.py`` file in the current directory that contains
+the necessary components to finalize an installation into IIS. Once this file
+has been generated, it is necessary to run the following command due to the way
+that ISAPI-WSGI is made::
 
-    if __name__=='__main__':
-        from isapi.install import *
-        params = ISAPIParameters()
-        sm = [ScriptMapParams(Extension="*", Flags=0)]
-        vd = VirtualDirParameters(Name="/",
-                                  Description = "ISAPI-WSGI Echo Test",
-                                  ScriptMaps = sm,
-                                  ScriptMapUpdate = "replace")
-        params.VirtualDirs = [vd]
-        HandleCommandLine(params)
+    python dispatch.py install
 
-This script has two parts: First, when run directly using Python, it will
-install a script map ISAPI handler into the root application of the default
-website, and secondly it will be called from the ISAPI handler when invoked
-from the website.
+This accomplishes two things: generating an ISAPI compliant DLL file,
+``_dispatch.dll``, and installing a script map handler into IIS for the
+``--root`` specified above pointing to ``_dispatch.dll``.
 
 The ISAPI handler is registered to all file extensions, so it will automatically
-be the one handling all requests to the website. When the website starts the
-ISAPI handler, it will start a thread pool managed wrapper around the paster
+be the one handling all requests to the specified root. When the website starts
+the ISAPI handler, it will start a thread pool managed wrapper around the paster
 middleware WSGI handler that Kallithea runs within and each HTTP request to the
 site will be processed through this logic henceforth.
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/lib/paster_commands/install_iis.py	Sun Sep 14 07:39:06 2014 +0200
@@ -0,0 +1,85 @@
+import os
+import sys
+from paste.script.appinstall import AbstractInstallCommand
+from paste.script.command import BadCommand
+
+# Add location of top level folder to sys.path
+from os.path import dirname as dn
+rc_path = dn(dn(dn(os.path.realpath(__file__))))
+sys.path.append(rc_path)
+
+class Command(AbstractInstallCommand):
+    default_verbosity = 1
+    max_args = 1
+    min_args = 1
+    summary = 'Setup IIS given a config file'
+    usage = 'CONFIG_FILE'
+
+    description = '''
+    Script for installing into IIS using isapi-wsgi.
+    '''
+    parser = AbstractInstallCommand.standard_parser(
+        simulate=True, quiet=True, interactive=True)
+    parser.add_option('--virtualdir',
+                      action='store',
+                      dest='virtualdir',
+                      default='/',
+                      help='The virtual folder to install into on IIS')
+
+    def command(self):
+        config_spec = self.args[0]
+        if not config_spec.startswith('config:'):
+            config_spec = 'config:' + config_spec
+        config_file = config_spec[len('config:'):].split('#', 1)[0]
+        config_file = os.path.join(os.getcwd(), config_file)
+        try:
+            import isapi_wsgi
+        except ImportError:
+            raise BadCommand('missing requirement: isapi-wsgi not installed')
+
+        file = '''import sys
+
+if hasattr(sys, "isapidllhandle"):
+    import win32traceutil
+
+import isapi_wsgi
+import os
+
+def __ExtensionFactory__():
+    from paste.deploy import loadapp
+    from paste.script.util.logging_config import fileConfig
+    fileConfig('%(inifile)s')
+    application = loadapp('config:%(inifile)s')
+
+    def app(environ, start_response):
+        user = environ.get('REMOTE_USER', None)
+        if user is not None:
+            os.environ['REMOTE_USER'] = user
+        return application(environ, start_response)
+
+    return isapi_wsgi.ISAPIThreadPoolHandler(app)
+
+if __name__=='__main__':
+    from isapi.install import *
+    params = ISAPIParameters()
+    sm = [ScriptMapParams(Extension="*", Flags=0)]
+    vd = VirtualDirParameters(Name="%(virtualdir)s",
+                              Description = "Kallithea",
+                              ScriptMaps = sm,
+                              ScriptMapUpdate = "replace")
+    params.VirtualDirs = [vd]
+    HandleCommandLine(params)
+'''
+
+        outdata = file % {
+                'inifile': config_file.replace('\\', '\\\\'),
+                'virtualdir': self.options.virtualdir
+                }
+
+        dispatchfile = os.path.join(os.getcwd(), 'dispatch.py')
+        self.ensure_file(dispatchfile, outdata, False)
+        print 'generating', dispatchfile
+
+        print ('run \'python "%s" install\' with administrative privileges '
+            'to generate the _dispatch.dll file and install it into the '
+            'default web site') % (dispatchfile,)
--- a/setup.py	Thu Oct 02 23:49:16 2014 +0200
+++ b/setup.py	Sun Sep 14 07:39:06 2014 +0200
@@ -180,5 +180,6 @@
     make-index=kallithea.lib.paster_commands.make_index:Command
     upgrade-db=kallithea.lib.dbmigrate:UpgradeDb
     celeryd=kallithea.lib.celerypylons.commands:CeleryDaemonCommand
+    install-iis=kallithea.lib.paster_commands.install_iis:Command
     """,
 )