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