Class-based deployment config.
authorŁukasz Rekucki <lrekucki@gmail.com>
Tue, 8 Jun 2010 08:56:59 +0000 (10:56 +0200)
committerŁukasz Rekucki <lrekucki@gmail.com>
Tue, 8 Jun 2010 09:25:23 +0000 (11:25 +0200)
.gitignore
deployment.py

index a1290ed..0d55584 100644 (file)
@@ -2,10 +2,12 @@ localsettings.py
 dev.sqlite
 requirements.pybundle
 *~
+*.orig
 
 # Python garbage
 *.pyc
 .coverage
+.coveragerc
 coverage.xml
 nosetests.xml
 pip-log.txt
index 3553aa1..a9a4f58 100644 (file)
-#!/srv/library/redakcja/pythonenv/bin/python
 from __future__ import with_statement
 
 import shutil
 import os
 import sys
+import logging
+
+logging.basicConfig(stream=sys.stderr, format="%(levelname)s:: %(message)s")
 
 from string import Template
 
-def render_template(source, dest, context={}):
-    print "Rendering template:",
-    with open(source, 'rb') as source_file:
-        t = Template(source_file.read())
-    with open(dest, 'wb') as dest_file:
-        dest_file.write(t.safe_substitute(context))
-    print "done."
+class DeploySite(object):
+
+    def __init__(self, **env):
+        self.env = env
+
+        for arg in ('ROOT', 'PROJECT_NAME', 'PYTHON_VERSION'):
+            if arg not in self.env:
+                raise ValueError("Argument '%s' is required." % arg)
+
+        if 'PYTHON_BASE' not in self.env:
+            self.env['PYTHON_BASE'] = os.path.join(self.env['ROOT'], 'pythonenv')
+
+        if 'PYTHON_BIN' not in self.env:
+            self.env['PYTHON_BIN'] = os.path.join(
+                        self.env['PYTHON_BASE'], 'bin', 'python') + self.env['PYTHON_VERSION']
+
+        if 'PYTHON_SITE' not in self.env:
+            self.env['PYTHON_SITE'] = os.path.join(
+                        self.env['PYTHON_BASE'], 'lib',
+                        'python' + self.env['PYTHON_VERSION'], 'site-packages')
+
+        if 'APP_DIR' not in self.env:
+            self.env['APP_DIR'] = os.path.join(self.env['ROOT'], 'application')
+
+        if 'CONFIG_DIR' not in self.env:
+            self.env['CONFIG_DIR'] = os.path.join(self.env['ROOT'], 'etc')
+
+        if 'MEDIA_DIR' not in self.env:
+            self.env['MEDIA_DIR'] = os.path.join(self.env['ROOT'], 'www', 'media')
+
+        self._logger = logging.getLogger("deployment")
+
+    def info(self, *args, **kwargs):
+        self._logger.info(*args, **kwargs)
+
+    def render_template(self, source, dest, extra_context={}):
+        self.info("Rendering template: %s", source)
+
+        with open(source, 'rb') as source_file:
+            t = Template(source_file.read())
+
+        context = dict(self.env)
+        context.update(extra_context)
+
+        with open(dest, 'wb') as dest_file:
+            dest_file.write(t.safe_substitute(context))
+
+        self.info("Done.")
+
+    def restart_app(self):
+        pass
+
+    def update_app(self):
+        pass
+
+    def install_dependencies(self):
+        pass
+
+    def deploy(self):
+        self.update_app()
+        self.install_dependencies()
+        self.update_config()
+        self.restart_app()
+
+    def find_resource(self, path):
+        full_path = os.path.join(self.env['APP_DIR'], path)
+        if os.path.isfile(full_path):
+            return full_path
+        raise ValueError("Resource '%s' not found" % path)
+
+    @classmethod
+    def run_deploy(cls, *args, **kwargs):
+        site = cls(*args, **kwargs)
+        return site.deploy()
+
+class WSGISite(DeploySite):
+
+    def __init__(self, **env):
+        super(WSGISite, self).__init__(**env)
+
+        if 'WSGI_FILE' not in self.env:
+            self.env['WSGI_FILE'] = os.path.join(self.env['ROOT'], 'www',
+                                        'wsgi', self.env['PROJECT_NAME']) + '.wsgi'
+
+        if 'WSGI_SOURCE_FILE' not in self.env:
+            self.env['WSGI_SOURCE_FILE'] = 'wsgi_app.template'
 
-def restart_wsgi():
-    print "Restarting wsgi application:",
-    os.system("touch %s" % WSGI_TARGET)
-    print "done."
+        if 'WSGI_USER' not in self.env:
+            self.env['WSGI_USER'] = 'www-data'
 
-def update_application():
-    print "Updating repository.",
-    os.system("cd %s; git pull" % PROJECT_ROOT)
-    print "Installing requirements"
-    os.system("pip install -r %s" % os.path.join(PROJECT_ROOT, 'requirements.txt'))
-    print "Installing local requirements"
-    os.system("pip install -r %s" % os.path.join(ROOT, 'etc', 'requirements.txt'))
-    print "done."
+    def restart_app(self):
+        self.info("Restarting wsgi application: %s", self.env['WSGI_FILE'])
+        os.system("touch %s" % self.env['WSGI_FILE'])
 
+    def update_config(self):
+        source = self.find_resource(self.env['WSGI_SOURCE_FILE'])
+        self.render_template(source, self.env['WSGI_FILE'])
 
-PYTHON = os.path.join(ROOT, 'pythonenv', 'bin', 'python')
-PYTHON_SITE = os.path.join(ROOT, 'pythonenv', 'lib', 'python2.6', 'site-packages')
+class PIPSite(DeploySite):
 
-PROJECT_NAME = 'redakcja'
-PROJECT_ROOT = os.path.join(ROOT, 'application')
+    def install_dependencies(self):
+        self.info("Installing requirements")
+        os.system("pip install -r %s" % self.find_resource('requirements.txt'))
 
-MEDIA_ROOT = os.path.join(ROOT, 'www', 'media')
+        try:
+            self.info("Installing local requirements")
+            os.system("pip install -r %s" % self.find_resource('requirements_local.txt'))
+        except ValueError:
+            pass
 
-ADMIN_EMAIL = 'lrekucki@gmail.com'
+class GitSite(DeploySite):
 
-WSGI_TARGET = os.path.join(ROOT, 'www', 'wsgi', PROJECT_NAME + '.wsgi')
-WSGI_DIR = os.path.dirname(WSGI_TARGET)
+    def update_app(self):
+        self.info("Updating repository.")
+        os.system("cd %s; git pull" % self.env['APP_DIR'])
 
-WSGI_USER = PROJECT_NAME
-WSGI_PROCESSES = 5
-WSGI_THREADS = 1
+class ApacheSite(DeploySite):
 
-DOMAIN = 'redakcja.wolnelektury.pl'
-DOMAIN_ALIASES = 'redakcja.nowoczesnapolska.org.pl'
+    def __init__(self, **env):
+        super(ApacheSite, self).__init__(**env)
 
-#
-# Load local configuration
-#
-sys.path = [ os.path.join(ROOT, 'etc') ] + sys.path
+        if 'VHOST_SOURCE_FILE' not in self.env:
+            self.env['VHOST_SOURCE_FILE'] = 'apache_vhost.template'
 
-from local_deployment import *
+        if 'VHOST_FILE' not in self.env:
+            self.env['VHOST_FILE'] = os.path.join(self.env['CONFIG_DIR'], self.env['PROJECT_NAME'] + '.vhost')
 
-if __name__ == '__main__':
-    update_application()
-    render_template(os.path.join(PROJECT_ROOT, PROJECT_NAME + '.wsgi.template'), WSGI_TARGET, context=globals())
-    render_template(os.path.join(PROJECT_ROOT, PROJECT_NAME + '.vhost.template'), os.path.join(ROOT, 'etc', PROJECT_NAME + '.vhost'), context=globals())
-    restart_wsgi()
+    def update_config(self):
+        source = self.find_resource(self.env['VHOST_SOURCE_FILE'])
+        self.render_template(source, self.env['VHOST_CONFIG_FILE'])