4
from urllib import urlretrieve
5
from os import path, listdir, remove
6
from base64 import b64decode
7
from datetime import datetime
10
lib_dir = path.join(path.dirname(__file__), 'lib')
11
sys.path.append(lib_dir)
14
from jinja2 import Environment, FileSystemLoader
15
from helpers import create_dir, install_packages, parent_dir, run
16
from charmhelpers.core.host import service_restart, service_stop
17
from charmhelpers.core.hookenv import config
18
from charmhelpers.core.host import log
22
install_parent = "/srv"
23
live_link_name = "current"
24
live_link_path = path.join(install_parent, live_link_name)
25
charm_dir = parent_dir(__file__)
26
scriptrc_path = path.join(charm_dir, 'scripts/scriptrc')
27
apache_dir = "/etc/apache2"
28
sites_enabled_dir = path.join(apache_dir, "sites-enabled")
29
sites_enabled_path = path.join(sites_enabled_dir, "wsgi-app.conf")
30
sites_available_dir = path.join(apache_dir, "sites-available")
31
timefile_name = '.timestamp.txt'
35
# Install charm dependencies
36
install_packages('apache2', 'python-pip', 'libapache2-mod-wsgi')
38
# Make sure extra packages are installed
39
install_packages(config('apt_dependencies'))
43
# Make sure required packages are installed
44
install_packages(config('apt_dependencies'))
46
app_tgz_url = config('app_tgz_url')
49
timestamp = get_timestamp()
51
extract_app_files(app_tgz_url, timestamp)
53
install_dependencies(timestamp)
55
save_environment_variables_string(config('environment_variables'))
57
setup_apache_wsgi(timestamp)
59
set_current(timestamp)
64
Generate a timestamp and save it in a file
65
which can be removed with remove_timestamp
66
In case the install fails at any point, it can continue where it left off
70
timestamp = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
72
if path.exists(timefile_name):
73
# Read existing timestamp
74
with open(timefile_name, 'r') as timefile:
75
timestamp = timefile.read().rstrip()
77
# Save generate timestamp into file
78
with open(timefile_name, 'w') as timefile:
79
timefile.write(timestamp)
84
def remove_timestamp():
88
def extract_app_files(url, timestamp):
90
Extract the app zip file
91
into an install directory (specified in config)
94
install_path = path.join(install_parent, timestamp)
96
# Unless install dir already exists, extract it
97
if not (path.exists(install_path) and listdir(install_path)):
98
tempfile_path = '/tmp/wsgi-app-package.tgz'
100
create_dir(install_path)
103
"Extracting '{url}' to '{dir}'".format(
104
url=url, dir=install_path
108
run(sh.rm, tempfile_path, f=True)
110
urlretrieve(url, tempfile_path)
112
# Extract files into install dir
116
directory=install_path,
117
strip="1", z=True, x=True
121
def install_dependencies(timestamp):
123
Install pip and gem dependencies for the app
125
TODO: test for pip connectivity?
128
app_path = path.join(install_parent, timestamp)
130
pip_dependencies(app_path)
133
def pip_dependencies(app_path):
135
Install pip dependencies from requirements file
136
and from the dependencies directory
139
# Read paths from config
140
requirements_path = path.join(app_path, config('pip_requirements_path'))
141
dependencies_path = path.join(app_path, config('pip_dependencies_path'))
143
if path.isfile(requirements_path):
144
# Install from requirements file if possible
145
log("Installing pip requirements from {0}".format(requirements_path))
147
# Install dependencies in dependencies directory
151
find_links=dependencies_path, # Path to local package files
152
no_index=config('pip_no_index') # Use PyPi?
156
def setup_apache_wsgi(timestamp):
157
run(sh.a2enmod, "ssl", "proxy_http")
159
available_path = path.join(sites_available_dir, timestamp)
161
(keyfile_path, certificate_path) = copy_ssl_certificates(timestamp)
163
template_dir = path.join(charm_dir, 'templates')
164
jinja_env = Environment(loader=FileSystemLoader(template_dir))
165
conf_template = jinja_env.get_template('wsgi-app.conf')
167
wsgi_path = path.join(live_link_path, config('wsgi_file_path'))
169
conf_content = conf_template.render({
170
'wsgi_path': wsgi_path,
171
'wsgi_app_name': config('wsgi_app_name'),
172
'wsgi_dir': path.dirname(wsgi_path),
173
'wsgi_file': path.basename(wsgi_path),
174
'static_url_path': config('static_url_path'),
175
'static_path': path.join(live_link_path, config('static_path')),
176
'keyfile_path': keyfile_path,
177
'certificate_path': certificate_path
180
# Save it to sites-available
181
with open(available_path, 'w') as conf:
182
conf.write(conf_content)
184
# Add line to bottom of envvars to source scriptrc
185
apache_env_file = path.join(apache_dir, 'envvars')
187
source_file_path = path.join(charm_dir, 'scripts/scriptrc')
188
source_command = '. {0}'.format(source_file_path)
190
comment = '# scriptrc link added by apache2-wsgi charm:'
192
comment_exists = False
194
with open(apache_env_file) as env_file_read:
195
comment_exists = comment in env_file_read.read()
197
if not comment_exists:
198
with open(apache_env_file, 'a') as env_file:
199
env_file.write(comment + '\n')
200
env_file.write(source_command + '\n')
203
def copy_ssl_certificates(timestamp):
205
Copy either the default self-signed certificate
206
or the provided custom ones
207
into /etc/ssl/certs/wsgi-app.*
208
Return the locations of the created files
211
certs_dir = '/etc/ssl/certs'
212
app_path = path.join(install_parent, timestamp)
213
keyfile_path = path.join(certs_dir, 'wsgi-app.key')
214
certificate_path = path.join(certs_dir, 'wsgi-app.crt')
215
config_path = path.join(charm_dir, 'ssl/wsgi-app.conf')
217
custom_keyfile = config('ssl_keyfile')
218
custom_certificate = config('ssl_certificate')
220
create_dir(certs_dir)
222
log('Saving certificate files')
224
if custom_keyfile and custom_certificate:
225
keyfile_content = b64decode(path.join(app_path, custom_keyfile))
226
certificate_content = b64decode(path.join(app_path, custom_certificate))
228
with open(keyfile_path, 'w') as keyfile:
229
keyfile.write(keyfile_content)
231
with open(certificate_path, 'w') as certificate:
232
certificate.write(certificate_content)
236
"-new", "-nodes", "-x509", "-newkey", "rsa:2048", "-days",
237
"365", "-keyout", keyfile_path, "-out", certificate_path,
238
"-config", config_path
241
return (keyfile_path, certificate_path)
244
def set_current(timestamp):
246
Set an app directory to the currently live app
247
by creating a symlink as specified in config
250
app_path = path.join(install_parent, timestamp)
253
"Linking live path '{live}' to app dir: {app_dir}".format(
254
app_dir=app_path, live=live_link_path
258
run(sh.rm, live_link_path, force=True)
259
run(sh.ln, app_path, live_link_path, symbolic=True)
261
site_to_enable = path.join(sites_available_dir, timestamp)
263
site_links = sh.glob(path.join(sites_enabled_dir, '*'))
265
# Delete existing site links
266
run(sh.rm, site_links, f=True)
268
# Add our link into sites-enabled
269
run(sh.ln, site_to_enable, sites_enabled_path, s=True)
273
service_restart("apache2")
274
log('Restarted apache')
278
service_stop("apache2")
281
def setup_http_server():
282
public_address = sh.unit_get('public-address').rstrip()
284
log('setting up "http-server" with address "{0}"'.format(public_address))
286
sh.relation_set('hostname={0}'.format(public_address))
291
def store_relation_hostname_in_env(environment_variable_name):
292
# Get the hostname of the relation
293
relation_hostname = sh.relation_get('hostname').rstrip()
295
# Save it as an environment variable
296
save_environment_variable(environment_variable_name, relation_hostname)
301
def save_environment_variable(name, value):
302
variables_string = '{name}={value}'.format(name=name, value=value)
304
save_environment_variables_string(variables_string)
307
def save_environment_variables_string(env_vars):
308
log('setting environment variable: {0}'.format(env_vars))
310
export_string = 'export {0}\n'.format(env_vars)
314
with open(scriptrc_path) as scriptrc_read:
315
already_set = export_string in scriptrc_read.read()
317
# Save into scripts/scriptrc
319
with open(scriptrc_path, 'a') as scriptrc:
320
scriptrc.write(export_string)