1 # -*- coding: utf-8 -*-
3 # This file is part of MIL/PEER, licensed under GNU Affero GPLv3 or later.
4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
6 from __future__ import with_statement
11 from string import Template
13 logging.basicConfig(stream=sys.stderr, format="%(levelname)s:: %(message)s", level=logging.INFO)
16 class DeploySite(object):
18 def __init__(self, **env):
21 for arg in ('ROOT', 'PROJECT_NAME', 'PYTHON_VERSION'):
22 if arg not in self.env:
23 raise ValueError("Argument '%s' is required." % arg)
25 if 'PYTHON_BASE' not in self.env:
26 self.env['PYTHON_BASE'] = os.path.join(self.env['ROOT'], 'pythonenv')
28 if 'PYTHON_BIN' not in self.env:
29 self.env['PYTHON_BIN'] = os.path.join(
30 self.env['PYTHON_BASE'], 'bin', 'python') + self.env['PYTHON_VERSION']
32 if 'PIP_BIN' not in self.env:
33 self.env['PIP_BIN'] = os.path.join(self.env['PYTHON_BASE'], 'bin', 'pip')
35 if 'PYTHON_SITE' not in self.env:
36 self.env['PYTHON_SITE'] = os.path.join(
37 self.env['PYTHON_BASE'], 'lib',
38 'python' + self.env['PYTHON_VERSION'], 'site-packages')
40 if 'APP_DIR' not in self.env:
41 self.env['APP_DIR'] = os.path.join(self.env['ROOT'], 'application')
43 if 'CONFIG_DIR' not in self.env:
44 self.env['CONFIG_DIR'] = os.path.join(self.env['ROOT'], 'etc')
46 if 'MEDIA_DIR' not in self.env:
47 self.env['MEDIA_DIR'] = os.path.join(self.env['ROOT'], 'www', 'media')
49 self._logger = logging.getLogger("deployment")
51 def info(self, *args, **kwargs):
52 self._logger.info(*args, **kwargs)
54 def render_template(self, source, dest, extra_context={}):
55 self.info("Rendering template: %s", source)
57 with open(source, 'rb') as source_file:
58 t = Template(source_file.read())
60 context = dict(self.env)
61 context.update(extra_context)
63 with open(dest, 'wb') as dest_file:
64 dest_file.write(t.safe_substitute(context))
68 def restart_app(self):
74 def update_config(self):
77 def install_dependencies(self):
82 self.install_dependencies()
86 def find_resource(self, path):
87 for dir in (self.env['CONFIG_DIR'], self.env['APP_DIR']):
88 full_path = os.path.join(dir, path)
89 if os.path.isfile(full_path):
92 raise ValueError("Resource '%s' not found" % path)
95 def run_deploy(cls, *args, **kwargs):
96 site = cls(*args, **kwargs)
100 class WSGISite(DeploySite):
102 def __init__(self, **env):
103 super(WSGISite, self).__init__(**env)
105 if 'WSGI_FILE' not in self.env:
106 self.env['WSGI_FILE'] = os.path.join(self.env['ROOT'], 'www',
107 'wsgi', self.env['PROJECT_NAME']) + '.wsgi'
109 self.env['WSGI_DIR'] = os.path.dirname(self.env['WSGI_FILE'])
111 if 'WSGI_SOURCE_FILE' not in self.env:
112 self.env['WSGI_SOURCE_FILE'] = 'wsgi_app.template'
114 if 'WSGI_USER' not in self.env:
115 self.env['WSGI_USER'] = 'www-data'
117 def restart_app(self):
118 self.info("Restarting wsgi application: %s", self.env['WSGI_FILE'])
119 os.system("touch %s" % self.env['WSGI_FILE'])
121 def update_config(self):
122 super(WSGISite, self).update_config()
124 source = self.find_resource(self.env['WSGI_SOURCE_FILE'])
125 self.render_template(source, self.env['WSGI_FILE'])
128 class PIPSite(DeploySite):
130 def install_dependencies(self):
131 self.info("Installing requirements")
132 os.system("%s install -r %s" % (self.env['PIP_BIN'], self.find_resource('requirements.txt')))
135 self.info("Installing local requirements")
136 os.system("%s install -r %s" % (self.env['PIP_BIN'], self.find_resource('requirements_local.txt')))
141 class GitSite(DeploySite):
143 def update_app(self):
144 self.info("Updating repository.")
145 os.system("cd %s; git pull" % self.env['APP_DIR'])
148 class ApacheSite(DeploySite):
150 def __init__(self, **env):
151 super(ApacheSite, self).__init__(**env)
153 if 'VHOST_SOURCE_FILE' not in self.env:
154 self.env['VHOST_SOURCE_FILE'] = 'apache_vhost.template'
156 if 'VHOST_FILE' not in self.env:
157 self.env['VHOST_FILE'] = os.path.join(self.env['CONFIG_DIR'], self.env['PROJECT_NAME'] + '.vhost')
159 def update_config(self):
160 super(ApacheSite, self).update_config()
162 source = self.find_resource(self.env['VHOST_SOURCE_FILE'])
163 self.render_template(source, self.env['VHOST_FILE'])