Commits

Alessandro Molina committed 199bb8b

Circus deployment command

Comments (0)

Files changed (4)

 a bootstrap directory inside the project ``public_dir`` with the Twitter bootstrap framework inside and will
 patch the ``master template`` to use the SCSS version instead of the plain CSS one provided by default.
 
+
 tg-hgignore
 ==================
 
 This will create a ``.hgignore`` file suitable for TurboGears2 applications inside the directory where it
 is started.
 
+deploy-circus
+==================
 
+When started inside a ``PasteDeploy`` application directory will generate a configuration for
+Mozilla Circus to deploy that application under circus supervision.

gearboxtools/deploy_circus/__init__.py

+from .command import CircusDeployCommand

gearboxtools/deploy_circus/command.py

+from logging import getLogger
+import os, sys
+
+from gearbox.command import Command
+from string import Template
+from contextlib import closing
+
+try:
+    from ConfigParser import ConfigParser
+except ImportError:
+    from configparser import ConfigParser
+
+log = getLogger('gearbox')
+
+class CircusDeployCommand(Command):
+    TEMPLATE = Template('''
+[watcher:$app_name]
+env = PATH=$virtual_env/bin,VIRTUAL_ENV=$virtual_env
+working_dir = $appdir
+cmd = chaussette --backend $backend --fd $$(circus.sockets.$app_name) paste:$config_file
+use_sockets = True
+warmup_delay = 0
+numprocesses = 1
+
+stderr_stream.class = FileStream
+stderr_stream.filename = $logging_dir/$app_name.log
+stderr_stream.refresh_time = 0.3
+
+stdout_stream.class = FileStream
+stdout_stream.filename = $logging_dir/$app_name.log
+stdout_stream.refresh_time = 0.3
+
+[socket:$app_name]
+host = localhost
+port = $port
+''')
+
+    def get_description(self):
+        return 'Generates configuration for deploying a PasteDeploy application on Mozilla Circus'
+
+    def get_parser(self, prog_name):
+        parser = super(CircusDeployCommand, self).get_parser(prog_name)
+
+        parser.add_argument("-c", "--config",
+            help='application config file to use (default: production.ini)',
+            dest='config_file', default="production.ini")
+
+        parser.add_argument("-k", "--skeleton",
+            help='template config file to use to generate application configuration file if not found',
+            dest='skeleton_file', default="development.ini")
+
+        parser.add_argument("-f", "--force", action='store_true',
+            help='If the configuration file exists, replace it anyway with the skeleton',
+            dest='force', default=False)
+
+        parser.add_argument("-e", "--environment",
+            help='Virtual environment to use for the application, by default the active one',
+            dest='virtual_env', default=None)
+
+        parser.add_argument("-d", "--app-dir",
+            help='Application working path, by default the current path',
+            dest='appdir', default=None)
+
+        parser.add_argument("-a", "--app-name",
+            help='Application name, used to create circus configuration sections, '
+                 'by default took from PasteDeploy app:main section',
+            dest='app_name', default=None)
+
+        parser.add_argument("-p", "--port",
+            help='Application port to use, by default took from PasteDeploy server:main section',
+            dest='port', default=None)
+
+        parser.add_argument("-l", "--logging-dir",
+            help='Directory where to store logging files by default',
+            dest='logging_dir', default='/var/log')
+
+        parser.add_argument("-b", "--backend",
+            help='Chaussette backend used to serve requests',
+            dest='backend', default='waitress')
+
+        parser.add_argument('configuration_options', nargs='*',
+            help='When configuration file is not available, the provided options in the form key=value '
+                 'will be replaced in the skeleton file to generate one')
+
+        return parser
+
+    def _parse_opts(self, options):
+        result = {}
+        for arg in options:
+            if '=' not in arg:
+                raise ValueError('Variable assignment %r invalid (no "=")' % arg)
+            name, value = arg.split('=', 1)
+            result[name] = value
+        return result
+
+
+    def _make_conf(self, skeleton, config_file, options):
+        log.warn('Configuration file not found, generating one from skeleton...')
+        skeleton_config = ConfigParser()
+        skeleton_config.read(skeleton)
+
+        for name, value in options.items():
+            section, name = 'DEFAULT', name
+            if '@' in name:
+                section, name = name.split('@', 1)
+            skeleton_config.set(section, name, value)
+
+        with closing(open(config_file, 'w')) as config_file:
+            skeleton_config.write(config_file)
+
+    def _get_app_and_port(self, config_file):
+        conf = ConfigParser()
+        conf.read(config_file)
+
+        app_name = conf.get('app:main', 'use')
+        server_port = conf.get('server:main', 'port')
+
+        if app_name.startswith('egg:'):
+            app_name = app_name[4:]
+
+        return app_name, server_port
+
+    def take_action(self, opts):
+        if not opts.virtual_env:
+            opts.virtual_env = os.environ.get('VIRTUAL_ENV')
+
+        if not opts.virtual_env:
+            log.error('No virtual environment specified and none active')
+            return
+
+        if not os.path.exists(opts.config_file) or opts.force:
+            self._make_conf(opts.skeleton_file, opts.config_file,
+                            self._parse_opts(opts.configuration_options))
+
+        log.info('Using Environment: %s', opts.virtual_env)
+
+        if not opts.appdir:
+            opts.appdir = os.getcwd()
+
+        log.info('Using Application Directory: %s', opts.appdir)
+
+        app_name, port = self._get_app_and_port(opts.config_file)
+
+        if not opts.app_name:
+            opts.app_name = app_name
+
+        log.info('Using Application Name: %s', opts.app_name)
+
+        if not opts.port:
+            opts.port = port
+
+        log.info('Using Port: %s', opts.port)
+
+        print(self.TEMPLATE.substitute(vars(opts)))
         'gearbox.commands': [
             'tg-hgignore = gearboxtools.tgignore:TGIgnoreCommand',
             'tg-bootstrap = gearboxtools.tgbootstrap:TGBootstrapCommand',
-            'pkgdeps = gearboxtools.pkgdeps:PackageDepsCommand'
+            'pkgdeps = gearboxtools.pkgdeps:PackageDepsCommand',
+            'deploy-circus = gearboxtools.deploy_circus:CircusDeployCommand',
         ],
       })