Merge branch 'master' into production
[redakcja.git] / deployment.py
1 from __future__ import with_statement
2
3 import shutil
4 import os
5 import sys
6 import logging
7
8 logging.basicConfig(stream=sys.stderr, format="%(levelname)s:: %(message)s", level=logging.INFO)
9
10 from string import Template
11
12 class DeploySite(object):
13
14     def __init__(self, **env):
15         self.env = env
16
17         for arg in ('ROOT', 'PROJECT_NAME', 'PYTHON_VERSION'):
18             if arg not in self.env:
19                 raise ValueError("Argument '%s' is required." % arg)
20
21         if 'PYTHON_BASE' not in self.env:
22             self.env['PYTHON_BASE'] = os.path.join(self.env['ROOT'], 'pythonenv')
23
24         if 'PYTHON_BIN' not in self.env:
25             self.env['PYTHON_BIN'] = os.path.join(
26                         self.env['PYTHON_BASE'], 'bin', 'python') + self.env['PYTHON_VERSION']
27
28         if 'PIP_BIN' not in self.env:
29             self.env['PIP_BIN'] = os.path.join(self.env['PYTHON_BASE'], 'bin', 'pip')
30
31         if 'PYTHON_SITE' not in self.env:
32             self.env['PYTHON_SITE'] = os.path.join(
33                         self.env['PYTHON_BASE'], 'lib',
34                         'python' + self.env['PYTHON_VERSION'], 'site-packages')
35
36         if 'APP_DIR' not in self.env:
37             self.env['APP_DIR'] = os.path.join(self.env['ROOT'], 'application')
38
39         if 'CONFIG_DIR' not in self.env:
40             self.env['CONFIG_DIR'] = os.path.join(self.env['ROOT'], 'etc')
41
42         if 'MEDIA_DIR' not in self.env:
43             self.env['MEDIA_DIR'] = os.path.join(self.env['ROOT'], 'www', 'media')
44
45         self._logger = logging.getLogger("deployment")
46
47     def info(self, *args, **kwargs):
48         self._logger.info(*args, **kwargs)
49
50     def render_template(self, source, dest, extra_context={}):
51         self.info("Rendering template: %s", source)
52
53         with open(source, 'rb') as source_file:
54             t = Template(source_file.read())
55
56         context = dict(self.env)
57         context.update(extra_context)
58
59         with open(dest, 'wb') as dest_file:
60             dest_file.write(t.safe_substitute(context))
61
62         self.info("Done.")
63
64     def restart_app(self):
65         pass
66
67     def update_app(self):
68         pass
69
70     def update_config(self):
71         pass
72
73     def install_dependencies(self):
74         pass
75
76     def deploy(self):
77         self.update_app()
78         self.install_dependencies()
79         self.update_config()
80         self.restart_app()
81
82     def find_resource(self, path):
83         for dir in (self.env['CONFIG_DIR'], self.env['APP_DIR']):
84             full_path = os.path.join(dir, path)
85             if os.path.isfile(full_path):
86                 return full_path
87
88         raise ValueError("Resource '%s' not found" % path)
89
90     @classmethod
91     def run_deploy(cls, *args, **kwargs):
92         site = cls(*args, **kwargs)
93         return site.deploy()
94
95 class WSGISite(DeploySite):
96
97     def __init__(self, **env):
98         super(WSGISite, self).__init__(**env)
99
100         if 'WSGI_FILE' not in self.env:
101             self.env['WSGI_FILE'] = os.path.join(self.env['ROOT'], 'www',
102                                         'wsgi', self.env['PROJECT_NAME']) + '.wsgi'
103
104         self.env['WSGI_DIR'] = os.path.dirname(self.env['WSGI_FILE'])
105
106         if 'WSGI_SOURCE_FILE' not in self.env:
107             self.env['WSGI_SOURCE_FILE'] = 'wsgi_app.template'
108
109         if 'WSGI_USER' not in self.env:
110             self.env['WSGI_USER'] = 'www-data'
111
112     def restart_app(self):
113         self.info("Restarting wsgi application: %s", self.env['WSGI_FILE'])
114         os.system("touch %s" % self.env['WSGI_FILE'])
115
116     def update_config(self):
117         super(WSGISite, self).update_config()
118
119         source = self.find_resource(self.env['WSGI_SOURCE_FILE'])
120         self.render_template(source, self.env['WSGI_FILE'])
121
122 class PIPSite(DeploySite):
123
124     def install_dependencies(self):
125         self.info("Installing requirements")
126         os.system("%s install -r %s" % (self.env['PIP_BIN'], self.find_resource('requirements.txt')))
127
128         try:
129             self.info("Installing local requirements")
130             os.system("%s install -r %s" % (self.env['PIP_BIN'], self.find_resource('requirements_local.txt')))
131         except ValueError:
132             pass
133
134 class GitSite(DeploySite):
135
136     def update_app(self):
137         self.info("Updating repository.")
138         os.system("cd %s; git pull" % self.env['APP_DIR'])
139
140 class ApacheSite(DeploySite):
141
142     def __init__(self, **env):
143         super(ApacheSite, self).__init__(**env)
144
145         if 'VHOST_SOURCE_FILE' not in self.env:
146             self.env['VHOST_SOURCE_FILE'] = 'apache_vhost.template'
147
148         if 'VHOST_FILE' not in self.env:
149             self.env['VHOST_FILE'] = os.path.join(self.env['CONFIG_DIR'], self.env['PROJECT_NAME'] + '.vhost')
150
151     def update_config(self):
152         super(ApacheSite, self).update_config()
153
154         source = self.find_resource(self.env['VHOST_SOURCE_FILE'])
155         self.render_template(source, self.env['VHOST_FILE'])