Merge lp:~patrick-hetu/charms/precise/gunicorn/python-rewrite into lp:~charmers/charms/precise/gunicorn/trunk

Proposed by Patrick Hetu
Status: Merged
Approved by: Mark Mims
Approved revision: 33
Merged at revision: 27
Proposed branch: lp:~patrick-hetu/charms/precise/gunicorn/python-rewrite
Merge into: lp:~charmers/charms/precise/gunicorn/trunk
Diff against target: 1324 lines (+974/-212)
14 files modified
README (+0/-38)
README.rst (+52/-0)
config.yaml (+18/-17)
hooks/config-changed (+0/-6)
hooks/hooks.py (+491/-0)
hooks/install (+0/-24)
hooks/start (+0/-4)
hooks/stop (+0/-4)
hooks/upgrade-charm (+0/-11)
hooks/wsgi-file-relation-joined (+0/-102)
icon.svg (+377/-0)
metadata.yaml (+1/-5)
revision (+1/-1)
templates/upstart.tmpl (+34/-0)
To merge this branch: bzr merge lp:~patrick-hetu/charms/precise/gunicorn/python-rewrite
Reviewer Review Type Date Requested Status
Mark Mims (community) Approve
James Page Needs Resubmitting
charmers Pending
Review via email: mp+167088@code.launchpad.net

Description of the change

Python rewrite and compatibility with the new Django Charm.

To post a comment you must log in.
Revision history for this message
Mark Mims (mark-mims) wrote :

This looks awesome... glad you took the time to do the rewrite.

One thing, can you please make this sub provide a website http relation? This way it can be related to a load balancer or reverse proxy.

There're a couple of other things that `charm proof gunicorn` is turning up:

    W: Metadata is missing categories.
    W: No icon.svg file.
    W: missing recommended hook start
    W: missing recommended hook stop

I've filed bugs for a couple of them.

    https://bugs.launchpad.net/charms/+source/gunicorn/+bug/1187419
    https://bugs.launchpad.net/charms/+source/gunicorn/+bug/1187421

for start/stop you might want to stub out empty hooks to quiet `charm proof` or not... your call.

No need to get all of this into this MP... just the website-relation-joined if you would please.

Thanks!

review: Needs Fixing
30. By Patrick Hetu

add an icon

31. By Patrick Hetu

add a category

Revision history for this message
Patrick Hetu (patrick-hetu) wrote :

Hi Mark,

I've added the icon and the category to the metadata.

I've not added fake start/stop hook since I don't want them
to be list in the juju store if they don't do anything.

The website relation will be handled in the python-django charm,
where I've tried to "centralize" all the interactions.

Revision history for this message
Patrick Hetu (patrick-hetu) wrote :

I just realize that the open/close port commands are in Gunicorn.
I'll move it to python-django so that the expose command will we run
on the Django charm also.

32. By Patrick Hetu

move ports control to the python-django charm

33. By Patrick Hetu

modify the docs for the port exposition changes

Revision history for this message
Patrick Hetu (patrick-hetu) wrote :

This review is ready for a second round of checks. I'm not sure
if I need to change something here to reflect that.

Revision history for this message
James Page (james-page) :
review: Needs Resubmitting
Revision history for this message
Mark Mims (mark-mims) wrote :

ok, this looks good... I like the idea of gunicorn as a sub to django, but having django delegate down to gunicorn instead of gunicorn implementing website relations directly.

Taking it now b/c it works, but please address the following related bugs when you get a chance:

    https://bugs.launchpad.net/charms/+source/gunicorn/+bug/1193026
    https://bugs.launchpad.net/charms/+source/python-moinmoin/+bug/1193037
    https://bugs.launchpad.net/charms/+source/python-django/+bug/1193038

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed file 'README'
--- README 2012-10-04 12:14:32 +0000
+++ README 1970-01-01 00:00:00 +0000
@@ -1,38 +0,0 @@
1Juju charm gunicorn
2===================
3
4:Author: Patrick Hetu <patrick@koumbit.org>
5
6How to configure the charm
7--------------------------
8
9To deploy a charm with this subordinate it must minimaly:
10
11 1. Provide the wsgi interface.
12 2. Set the `working_dir` relation variable in the wsgi hook.
13
14The configuration of Gunicorn will use the variable pass by
15the relation hook first. If there are not define it will
16fallback to the global configuration of the charm.
17
18Example deployment
19------------------
20
21
22
231. Deployment with python-moinmoin for example::
24
25 juju bootstrap
26 juju deploy --config mywiki_with_wsgi_settings.yaml python-moinmoin
27 juju deploy gunicorn
28 juju add-relation gunicorn python-moinmoin
29 juju expose gunicorn
30
312. Accessing your new wiki should be ready at::
32
33 http://<machine-addr>:8080/
34
35 To find out the public address of gunicorn/python-moinmoin, look for it in
36 the output of the `juju status` command.
37 I recommend using a reverse proxy like Nginx in front of Gunicorn.
38
390
=== added file 'README.rst'
--- README.rst 1970-01-01 00:00:00 +0000
+++ README.rst 2013-06-05 17:10:31 +0000
@@ -0,0 +1,52 @@
1Juju charm gunicorn
2===================
3
4:Author: Patrick Hetu <patrick@koumbit.org>
5
6How to configure the charm
7--------------------------
8
9To deploy a charm with this subordinate it must minimaly:
10
11 1. Provide the wsgi interface.
12 2. Set the `working_dir` relation variable in the wsgi hook.
13
14The configuration of Gunicorn will use the variable pass by
15the relation hook first. If there are not define it will
16fallback to the global configuration of the charm.
17
18Example deployment
19------------------
20
211. Deployment with python-moinmoin for example::
22
23 juju bootstrap
24 juju deploy --config mywiki_with_wsgi_settings.yaml python-moinmoin
25 juju deploy gunicorn
26 juju add-relation gunicorn python-moinmoin
27 juju expose python-moinmoin
28
292. Accessing your new wiki should be ready at::
30
31 http://<machine-addr>:8080/
32
33 To find out the public address of gunicorn/python-moinmoin, look for it in
34 the output of the `juju status` command.
35 I recommend using a reverse proxy like Nginx in front of Gunicorn.
36
37Changelog
38---------
393:
40
41 Notable changes:
42
43 * Rewrite the charm using python instead of BASH scripts
44 * add listen_ip configuration variable
45
46 Backwards incompatible changes:
47 * Remove the Django mode since Gunicorn is not recommending it anymore.
48 * Use Upstart to manage daemons
49 * no start/stop hook anymore use related charms instead.
50 * no configuration change directly on the charm anymore, use related charms instead.
51 * no access logging by default
52 * exposing a port must now be done in the linked charm instead of this one
053
=== modified file 'config.yaml'
--- config.yaml 2012-10-03 17:57:11 +0000
+++ config.yaml 2013-06-05 17:10:31 +0000
@@ -1,15 +1,16 @@
1options:1options:
2 wsgi_wsgi_file:2 wsgi_wsgi_file:
3 type: string3 type: string
4 default: "wsgi"
4 description: "The name of the WSGI application."5 description: "The name of the WSGI application."
5 wsgi_workers:6 wsgi_workers:
6 type: int7 type: int
7 default: 28 default: 0
8 description: "The number of worker process for handling requests."9 description: "The number of worker process for handling requests. 0 for count(cpu) + 1"
9 wsgi_worker_class:10 wsgi_worker_class:
10 type: string11 type: string
11 default: "sync"12 default: "sync"
12 description: "Gunicorn workers type."13 description: "Gunicorn workers type. Can be: sync, eventlet, gevent, tornado"
13 wsgi_worker_connections:14 wsgi_worker_connections:
14 type: int15 type: int
15 default: 100016 default: 1000
@@ -25,11 +26,11 @@
25 wsgi_timeout:26 wsgi_timeout:
26 type: int27 type: int
27 default: 3028 default: 30
28 description: ""29 description: "Timeout of a request in seconds."
29 wsgi_keep_alive:30 wsgi_keep_alive:
30 type: int31 type: int
31 default: 232 default: 2
32 description: ""33 description: "Keep alive time in seconds."
33 wsgi_umask:34 wsgi_umask:
34 type: string35 type: string
35 default: "0"36 default: "0"
@@ -45,36 +46,36 @@
45 wsgi_log_file:46 wsgi_log_file:
46 type: string47 type: string
47 default: "-"48 default: "-"
48 description: "The log file to write to. If empty the file would be <your_application_dir>/gunicorn.log"49 description: "The log file to write to. If empty the logs would be handle by upstart."
49 wsgi_log_level:50 wsgi_log_level:
50 type: string51 type: string
51 default: "info"52 default: "info"
52 description: "The granularity of Error log outputs."53 description: "The granularity of Error log outputs."
53 wsgi_access_logfile:54 wsgi_access_logfile:
54 type: string55 type: string
55 default: "-"56 default: ""
56 description: "The Access log file to write to."57 description: "The Access log file to write to."
57 wsgi_access_logformat:58 wsgi_access_logformat:
58 type: string59 type: string
59 default: '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'60 default: ""
60 description: "The Access log format ."61 description: "The Access log format. Don't forget to escape all quotes and round brackets."
61 wsgi_extra:62 wsgi_extra:
62 type: string63 type: string
63 default: ""64 default: ""
64 description: "Extra settings. For example: {--debug},"65 description: "Space separated extra settings. For example: --debug"
65 wsgi_timestamp:66 wsgi_timestamp:
66 type: string67 type: string
67 default: ""68 default: ""
68 description: "The variable to modify to trigger Gunicorn reload"69 description: "The variable to modify to trigger Gunicorn reload."
69 django_settings:
70 type: string
71 default: ""
72 description: "The Python path to a Django settings module."
73 python_path:70 python_path:
74 type: string71 type: string
75 default: ""72 default: ""
76 description: "Set PYTHONPATH environment variable"73 description: "Set an additionnal PYTHONPATH to the project."
74 listen_ip:
75 type: string
76 default: "0.0.0.0"
77 description: "IP adresses that Gunicorn will listen on. By default we listen on all of them."
77 port:78 port:
78 type: int79 type: int
79 default: 808080 default: 8080
80 description: "Default listen port"81 description: "Port the application will be listenning."
8182
=== removed file 'hooks/config-changed'
--- hooks/config-changed 2012-07-06 16:06:15 +0000
+++ hooks/config-changed 1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
1#!/bin/bash
2set -e
3
4juju-log "reloading Gunicorn"
5
6service gunicorn reload || true
70
=== removed symlink 'hooks/django-settings-relation-changed'
=== target was u'wsgi-file-relation-joined'
=== removed symlink 'hooks/django-settings-relation-joined'
=== target was u'wsgi-file-relation-joined'
=== added file 'hooks/hooks.py'
--- hooks/hooks.py 1970-01-01 00:00:00 +0000
+++ hooks/hooks.py 2013-06-05 17:10:31 +0000
@@ -0,0 +1,491 @@
1#!/usr/bin/env python
2# vim: et ai ts=4 sw=4:
3
4import json
5import os
6import re
7import subprocess
8import sys
9import time
10from pwd import getpwnam
11from grp import getgrnam
12
13CHARM_PACKAGES = ["gunicorn"]
14
15INJECTED_WARNING = """
16#------------------------------------------------------------------------------
17# The following is the import code for the settings directory injected by Juju
18#------------------------------------------------------------------------------
19"""
20
21
22###############################################################################
23# Supporting functions
24###############################################################################
25MSG_CRITICAL = "CRITICAL"
26MSG_DEBUG = "DEBUG"
27MSG_INFO = "INFO"
28MSG_ERROR = "ERROR"
29MSG_WARNING = "WARNING"
30
31
32def juju_log(level, msg):
33 subprocess.call(['juju-log', '-l', level, msg])
34
35#------------------------------------------------------------------------------
36# run: Run a command, return the output
37#------------------------------------------------------------------------------
38def run(command, exit_on_error=True, cwd=None):
39 try:
40 juju_log(MSG_DEBUG, command)
41 return subprocess.check_output(
42 command, stderr=subprocess.STDOUT, shell=True, cwd=cwd)
43 except subprocess.CalledProcessError, e:
44 juju_log(MSG_ERROR, "status=%d, output=%s" % (e.returncode, e.output))
45 if exit_on_error:
46 sys.exit(e.returncode)
47 else:
48 raise
49
50
51#------------------------------------------------------------------------------
52# install_file: install a file resource. overwites existing files.
53#------------------------------------------------------------------------------
54def install_file(contents, dest, owner="root", group="root", mode=0600):
55 uid = getpwnam(owner)[2]
56 gid = getgrnam(group)[2]
57 dest_fd = os.open(dest, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
58 os.fchown(dest_fd, uid, gid)
59 with os.fdopen(dest_fd, 'w') as destfile:
60 destfile.write(str(contents))
61
62
63#------------------------------------------------------------------------------
64# install_dir: create a directory
65#------------------------------------------------------------------------------
66def install_dir(dirname, owner="root", group="root", mode=0700):
67 command = \
68 '/usr/bin/install -o {} -g {} -m {} -d {}'.format(owner, group, oct(mode),
69 dirname)
70 return run(command)
71
72#------------------------------------------------------------------------------
73# config_get: Returns a dictionary containing all of the config information
74# Optional parameter: scope
75# scope: limits the scope of the returned configuration to the
76# desired config item.
77#------------------------------------------------------------------------------
78def config_get(scope=None):
79 try:
80 config_cmd_line = ['config-get']
81 if scope is not None:
82 config_cmd_line.append(scope)
83 config_cmd_line.append('--format=json')
84 config_data = json.loads(subprocess.check_output(config_cmd_line))
85 except:
86 config_data = None
87 finally:
88 return(config_data)
89
90
91#------------------------------------------------------------------------------
92# relation_json: Returns json-formatted relation data
93# Optional parameters: scope, relation_id
94# scope: limits the scope of the returned data to the
95# desired item.
96# unit_name: limits the data ( and optionally the scope )
97# to the specified unit
98# relation_id: specify relation id for out of context usage.
99#------------------------------------------------------------------------------
100def relation_json(scope=None, unit_name=None, relation_id=None):
101 command = ['relation-get', '--format=json']
102 if relation_id is not None:
103 command.extend(('-r', relation_id))
104 if scope is not None:
105 command.append(scope)
106 else:
107 command.append('-')
108 if unit_name is not None:
109 command.append(unit_name)
110 output = subprocess.check_output(command, stderr=subprocess.STDOUT)
111 return output or None
112
113
114#------------------------------------------------------------------------------
115# relation_get: Returns a dictionary containing the relation information
116# Optional parameters: scope, relation_id
117# scope: limits the scope of the returned data to the
118# desired item.
119# unit_name: limits the data ( and optionally the scope )
120# to the specified unit
121#------------------------------------------------------------------------------
122def relation_get(scope=None, unit_name=None, relation_id=None):
123 j = relation_json(scope, unit_name, relation_id)
124 if j:
125 return json.loads(j)
126 else:
127 return None
128
129
130def relation_set(keyvalues, relation_id=None):
131 args = []
132 if relation_id:
133 args.extend(['-r', relation_id])
134 args.extend(["{}='{}'".format(k, v or '') for k, v in keyvalues.items()])
135 run("relation-set {}".format(' '.join(args)))
136
137 ## Posting json to relation-set doesn't seem to work as documented?
138 ## Bug #1116179
139 ##
140 ## cmd = ['relation-set']
141 ## if relation_id:
142 ## cmd.extend(['-r', relation_id])
143 ## p = Popen(
144 ## cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
145 ## stderr=subprocess.PIPE)
146 ## (out, err) = p.communicate(json.dumps(keyvalues))
147 ## if p.returncode:
148 ## juju_log(MSG_ERROR, err)
149 ## sys.exit(1)
150 ## juju_log(MSG_DEBUG, "relation-set {}".format(repr(keyvalues)))
151
152
153def relation_list(relation_id=None):
154 """Return the list of units participating in the relation."""
155 if relation_id is None:
156 relation_id = os.environ['JUJU_RELATION_ID']
157 cmd = ['relation-list', '--format=json', '-r', relation_id]
158 json_units = subprocess.check_output(cmd).strip()
159 if json_units:
160 return json.loads(subprocess.check_output(cmd))
161 return []
162
163
164#------------------------------------------------------------------------------
165# relation_ids: Returns a list of relation ids
166# optional parameters: relation_type
167# relation_type: return relations only of this type
168#------------------------------------------------------------------------------
169def relation_ids(relation_types=('db',)):
170 # accept strings or iterators
171 if isinstance(relation_types, basestring):
172 reltypes = [relation_types, ]
173 else:
174 reltypes = relation_types
175 relids = []
176 for reltype in reltypes:
177 relid_cmd_line = ['relation-ids', '--format=json', reltype]
178 json_relids = subprocess.check_output(relid_cmd_line).strip()
179 if json_relids:
180 relids.extend(json.loads(json_relids))
181 return relids
182
183
184#------------------------------------------------------------------------------
185# relation_get_all: Returns a dictionary containing the relation information
186# optional parameters: relation_type
187# relation_type: limits the scope of the returned data to the
188# desired item.
189#------------------------------------------------------------------------------
190def relation_get_all(*args, **kwargs):
191 relation_data = []
192 relids = relation_ids(*args, **kwargs)
193 for relid in relids:
194 units_cmd_line = ['relation-list', '--format=json', '-r', relid]
195 json_units = subprocess.check_output(units_cmd_line).strip()
196 if json_units:
197 for unit in json.loads(json_units):
198 unit_data = \
199 json.loads(relation_json(relation_id=relid,
200 unit_name=unit))
201 for key in unit_data:
202 if key.endswith('-list'):
203 unit_data[key] = unit_data[key].split()
204 unit_data['relation-id'] = relid
205 unit_data['unit'] = unit
206 relation_data.append(unit_data)
207 return relation_data
208
209def apt_get_update():
210 cmd_line = ['apt-get', 'update']
211 return(subprocess.call(cmd_line))
212
213
214#------------------------------------------------------------------------------
215# apt_get_install( packages ): Installs package(s)
216#------------------------------------------------------------------------------
217def apt_get_install(packages=None):
218 if packages is None:
219 return(False)
220 cmd_line = ['apt-get', '-y', 'install', '-qq']
221 if isinstance(packages, list):
222 cmd_line.extend(packages)
223 else:
224 cmd_line.append(packages)
225 return(subprocess.call(cmd_line))
226
227
228#------------------------------------------------------------------------------
229# pip_install( package ): Installs a python package
230#------------------------------------------------------------------------------
231def pip_install(packages=None, upgrade=False):
232 cmd_line = ['pip', 'install']
233 if packages is None:
234 return(False)
235 if upgrade:
236 cmd_line.append('-u')
237 if packages.startswith('svn+') or packages.startswith('git+') or \
238 packages.startswith('hg+') or packages.startswith('bzr+'):
239 cmd_line.append('-e')
240 cmd_line.append(packages)
241 return run(cmd_line)
242
243#------------------------------------------------------------------------------
244# pip_install_req( path ): Installs a requirements file
245#------------------------------------------------------------------------------
246def pip_install_req(path=None, upgrade=False):
247 cmd_line = ['pip', 'install']
248 if path is None:
249 return(False)
250 if upgrade:
251 cmd_line.append('-u')
252 cmd_line.append('-r')
253 cmd_line.append(path)
254 cwd = os.path.dirname(path)
255 return run(cmd_line)
256
257#------------------------------------------------------------------------------
258# open_port: Convenience function to open a port in juju to
259# expose a service
260#------------------------------------------------------------------------------
261def open_port(port=None, protocol="TCP"):
262 if port is None:
263 return(None)
264 return(subprocess.call(['open-port', "%d/%s" %
265 (int(port), protocol)]))
266
267
268#------------------------------------------------------------------------------
269# close_port: Convenience function to close a port in juju to
270# unexpose a service
271#------------------------------------------------------------------------------
272def close_port(port=None, protocol="TCP"):
273 if port is None:
274 return(None)
275 return(subprocess.call(['close-port', "%d/%s" %
276 (int(port), protocol)]))
277
278
279#------------------------------------------------------------------------------
280# update_service_ports: Convenience function that evaluate the old and new
281# service ports to decide which ports need to be
282# opened and which to close
283#------------------------------------------------------------------------------
284def update_service_port(old_service_port=None, new_service_port=None):
285 if old_service_port is None or new_service_port is None:
286 return(None)
287 if new_service_port != old_service_port:
288 close_port(old_service_port)
289 open_port(new_service_port)
290
291#
292# Utils
293#
294
295def install_or_append(contents, dest, owner="root", group="root", mode=0600):
296 if os.path.exists(dest):
297 uid = getpwnam(owner)[2]
298 gid = getgrnam(group)[2]
299 dest_fd = os.open(dest, os.O_APPEND, mode)
300 os.fchown(dest_fd, uid, gid)
301 with os.fdopen(dest_fd, 'a') as destfile:
302 destfile.write(str(contents))
303 else:
304 install_file(contents, dest, owner, group, mode)
305
306def token_sql_safe(value):
307 # Only allow alphanumeric + underscore in database identifiers
308 if re.search('[^A-Za-z0-9_]', value):
309 return False
310 return True
311
312def sanitize(s):
313 s = s.replace(':', '_')
314 s = s.replace('-', '_')
315 s = s.replace('/', '_')
316 s = s.replace('"', '_')
317 s = s.replace("'", '_')
318 return s
319
320def user_name(relid, remote_unit, admin=False, schema=False):
321 components = [sanitize(relid), sanitize(remote_unit)]
322 if admin:
323 components.append("admin")
324 elif schema:
325 components.append("schema")
326 return "_".join(components)
327
328def get_relation_host():
329 remote_host = run("relation-get ip")
330 if not remote_host:
331 # remote unit $JUJU_REMOTE_UNIT uses deprecated 'ip=' component of
332 # interface.
333 remote_host = run("relation-get private-address")
334 return remote_host
335
336
337def get_unit_host():
338 this_host = run("unit-get private-address")
339 return this_host.strip()
340
341def process_template(template_name, template_vars, destination):
342 # --- exported service configuration file
343 from jinja2 import Environment, FileSystemLoader
344 template_env = Environment(
345 loader=FileSystemLoader(os.path.join(os.environ['CHARM_DIR'],
346 'templates')))
347
348 template = \
349 template_env.get_template(template_name).render(template_vars)
350
351 with open(destination, 'w') as inject_file:
352 inject_file.write(str(template))
353
354def append_template(template_name, template_vars, path, try_append=False):
355
356 # --- exported service configuration file
357 from jinja2 import Environment, FileSystemLoader
358 template_env = Environment(
359 loader=FileSystemLoader(os.path.join(os.environ['CHARM_DIR'],
360 'templates')))
361
362 template = \
363 template_env.get_template(template_name).render(template_vars)
364
365 append = False
366 if os.path.exists(path):
367 with open(path, 'r') as inject_file:
368 if not str(template) in inject_file:
369 append = True
370 else:
371 append = True
372
373 if append == True:
374 with open(path, 'a') as inject_file:
375 inject_file.write(INJECTED_WARNING)
376 inject_file.write(str(template))
377
378
379
380###############################################################################
381# Hook functions
382###############################################################################
383def install():
384
385 for retry in xrange(0,24):
386 if apt_get_install(CHARM_PACKAGES):
387 time.sleep(10)
388 else:
389 break
390
391def upgrade():
392
393 apt_get_update()
394 for retry in xrange(0,24):
395 if apt_get_install(CHARM_PACKAGES):
396 time.sleep(10)
397 else:
398 break
399
400def wsgi_file_relation_joined_changed():
401 wsgi_config = config_data
402 relation_id = os.environ['JUJU_RELATION_ID']
403 juju_log(MSG_INFO, "JUJU_RELATION_ID: %s".format(relation_id))
404
405 remote_unit_name = sanitize(os.environ['JUJU_REMOTE_UNIT'].split('/')[0])
406 juju_log(MSG_INFO, "JUJU_REMOTE_UNIT: %s".format(remote_unit_name))
407 wsgi_config['unit_name'] = remote_unit_name
408
409 project_conf = '/etc/init/%s.conf' % remote_unit_name
410
411 working_dir = relation_get('working_dir')
412 if not working_dir:
413 return
414
415 wsgi_config['working_dir'] = working_dir
416 wsgi_config['project_name'] = remote_unit_name
417
418 for v in wsgi_config.keys():
419 if v.startswith('wsgi_') or v in ['python_path', 'listen_ip', 'port']:
420 upstream_value = relation_get(v)
421 if upstream_value:
422 wsgi_config[v] = upstream_value
423
424 if wsgi_config['wsgi_worker_class'] == 'eventlet':
425 apt_get_install('python-eventlet')
426 elif wsgi_config['wsgi_worker_class'] == 'gevent':
427 apt_get_install('python-gevent')
428 elif wsgi_config['wsgi_worker_class'] == 'tornado':
429 apt_get_install('python-tornado')
430
431 if wsgi_config['wsgi_workers'] == 0:
432 res = run('python -c "import multiprocessing ; print(multiprocessing.cpu_count())"')
433 wsgi_config['wsgi_workers'] = int(res) + 1
434
435 if wsgi_config['wsgi_access_logfile']:
436 wsgi_config['wsgi_extra'] = " ".join([
437 wsgi_config['wsgi_extra'],
438 '--access-logformat=%s' % wsgi_config['wsgi_access_logfile'],
439 '--access-logformat="%s"' % wsgi_config['wsgi_access_logformat']
440 ])
441
442 wsgi_config['wsgi_wsgi_file'] = wsgi_config['wsgi_wsgi_file']
443
444 process_template('upstart.tmpl', wsgi_config, project_conf)
445
446
447 # We need this because when the contained charm configuration or code changed
448 # Gunicorn needs to restart to run the new code.
449 run("service %s restart || service %s start" % (remote_unit_name, remote_unit_name))
450
451
452def wsgi_file_relation_broken():
453 remote_unit_name = sanitize(os.environ['JUJU_REMOTE_UNIT'].split('/')[0])
454
455 run('service %s stop' % remote_unit_name)
456 run('rm /etc/init/%s.conf' % remote_unit_name)
457
458
459###############################################################################
460# Global variables
461###############################################################################
462config_data = config_get()
463juju_log(MSG_DEBUG, "got config: %s" % str(config_data))
464
465unit_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
466
467hook_name = os.path.basename(sys.argv[0])
468
469###############################################################################
470# Main section
471###############################################################################
472def main():
473 juju_log(MSG_INFO, "Running {} hook".format(hook_name))
474 if hook_name == "install":
475 install()
476
477 elif hook_name == "upgrade-charm":
478 upgrade()
479
480 elif hook_name in ["wsgi-file-relation-joined", "wsgi-file-relation-changed"]:
481 wsgi_file_relation_joined_changed()
482
483 elif hook_name == "wsgi-file-relation-broken":
484 wsgi_file_relation_broken()
485
486 else:
487 print "Unknown hook {}".format(hook_name)
488 raise SystemExit(1)
489
490if __name__ == '__main__':
491 raise SystemExit(main())
0492
=== modified file 'hooks/install'
--- hooks/install 2012-11-30 11:50:39 +0000
+++ hooks/install 1970-01-01 00:00:00 +0000
@@ -1,24 +0,0 @@
1#!/bin/bash
2set -e
3
4# Suboridnate charm hooks can run in parallel with other charm hooks
5# on the same unit Bug #1068624. So a hook may fail to get a apt-get lock.
6COUNTER=0
7APT_LOCKED=1
8while true; do
9 lsof /var/lib/dpkg/lock > /dev/null 2>&1 || APT_LOCKED=0
10 if [[ $APT_LOCKED -eq 0 ]]; then
11 apt-get install --no-install-recommends -q -y gunicorn \
12 python-eventlet \
13 python-gevent \
14 python-tornado
15 break
16 fi
17 if [[ $COUNTER -gt 5 ]]; then
18 echo "Failed to obtain apt lock to install python-amqplib"
19 exit 1
20 fi
21 sleep 60
22 COUNTER=$[$COUNTER+1]
23done
24
250
=== target is u'hooks.py'
=== removed file 'hooks/start'
--- hooks/start 2012-07-06 16:06:15 +0000
+++ hooks/start 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1#!/bin/bash
2set -e
3
4service gunicorn start || service gunicorn restart
50
=== removed file 'hooks/stop'
--- hooks/stop 2012-07-06 16:06:15 +0000
+++ hooks/stop 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1#!/bin/bash
2set -e
3
4service gunicorn stop || true
50
=== modified file 'hooks/upgrade-charm'
--- hooks/upgrade-charm 2012-07-08 16:35:05 +0000
+++ hooks/upgrade-charm 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1#!/bin/bash
2set -e
3
4home=`dirname $0`
5
6juju-log "Upgrading charm by running install hook again."
7$home/install
8
9juju-log "Upgrading charm, running config-changed hook again."
10$home/config-changed
11
120
=== target is u'hooks.py'
=== added symlink 'hooks/wsgi-file-relation-broken'
=== target is u'hooks.py'
=== modified symlink 'hooks/wsgi-file-relation-changed'
=== target changed u'wsgi-file-relation-joined' => u'hooks.py'
=== modified file 'hooks/wsgi-file-relation-joined'
--- hooks/wsgi-file-relation-joined 2013-01-24 06:06:09 +0000
+++ hooks/wsgi-file-relation-joined 1970-01-01 00:00:00 +0000
@@ -1,102 +0,0 @@
1#!/bin/bash
2set -e
3
4unit_name=${JUJU_REMOTE_UNIT//\//-}
5working_dir=$(relation-get working_dir)
6
7if [ -z "$working_dir" ] ; then
8 juju-log "No working_dir yet: skipping."
9 exit 0 # wait for future handshake
10fi
11
12variables="wsgi_wsgi_file wsgi_workers wsgi_worker_class wsgi_worker_connections wsgi_max_requests wsgi_timeout wsgi_backlog wsgi_keep_alive wsgi_extra wsgi_user wsgi_group wsgi_umask wsgi_log_file wsgi_log_level wsgi_access_logfile wsgi_access_logformat env_extra django_settings python_path port"
13
14if [[ $JUJU_RELATION_ID =~ django.* ]]; then
15 mode=django
16elif [[ $JUJU_RELATION_ID =~ wsgi.* ]]; then
17 mode=wsgi
18else
19 juju-log "Unknown mode ($JUJU_RELATION_ID)"
20 exit 1
21fi
22
23juju-log "Running in ${mode} mode"
24
25
26declare -A VAR
27for v in $variables;do
28 VAR[$v]=$(relation-get $v)
29 if [ -z "${VAR[$v]}" ] ; then
30 VAR[$v]=$(config-get $v)
31 fi
32done
33
34juju-log "Got variables: ${VAR[@]}"
35
36
37if [ -z "$wsgi_error_logfile" ] ; then
38 error_logfile="${working_dir}/gunicorn.log"
39fi
40
41if [ -n "$VAR[django_settings]" ] ; then
42 django_settings="'django_settings': '${VAR[django_settings]}',"
43fi
44
45# If running in wsgi mode then set a default wsgi file
46if [[ $mode -eq "wsgi" ]] && [[ $(relation-get wsgi_wsgi_file) -eq "" ]] ; then
47 VAR[wsgi_wsgi_file]="wsgi"
48fi
49
50if [[ -n ${VAR[wsgi_wsgi_file]} ]] ; then
51 wsgi_wsgi_file="'${VAR[wsgi_wsgi_file]}',"
52fi
53
54if [[ -z ${VAR[python_path]} ]] ; then
55 python_path=${working_dir}
56else
57 python_path="${VAR[python_path]}"
58fi
59
60juju-log "Writing config file: /etc/gunicorn.d/${unit_name}.conf"
61
62cat > /etc/gunicorn.d/${unit_name}.conf <<EOF
63CONFIG = {
64 'mode': '${mode}',
65 'environment': {
66 'PYTHONPATH': '${python_path}',
67 ${VAR[env_extra]}
68 },
69 ${django_settings}
70 'working_dir': '${working_dir}',
71 'user': '${VAR[wsgi_user]}',
72 'group': '${VAR[wsgi_group]}',
73 'args': (
74 '--name=${unit_name}',
75 '--workers=${VAR[wsgi_workers]}',
76 '--worker-class=${VAR[wsgi_worker_class]}',
77 '--worker-connections=${VAR[wsgi_worker_connections]}',
78 '--max-requests=${VAR[wsgi_max_requests]}',
79 '--backlog=${VAR[wsgi_backlog]}',
80 '--timeout=${VAR[wsgi_timeout]}',
81 '--keep-alive=${VAR[wsgi_keep_alive]}',
82 '--umask=${VAR[wsgi_umask]}',
83 '--bind=0.0.0.0:${VAR[port]}',
84 '--log-file=${VAR[wsgi_log_file]}',
85 '--log-level=${VAR[wsgi_log_level]}',
86 '--access-logfile=${VAR[wsgi_access_logfile]}',
87 '--access-logformat=${VAR[wsgi_access_logformat]}',
88 ${VAR[wsgi_extra]}
89 ${wsgi_wsgi_file}
90 ),
91}
92EOF
93
94
95juju-log "start/restart gunicorn"
96
97# We need this because when the contained charm configuration or code changed
98# Gunicorn needs to restart to run the new code.
99service gunicorn restart
100
101juju-log "Opening port: ${VAR[port]}"
102open-port "${VAR[port]}/tcp"
1030
=== target is u'hooks.py'
=== added file 'icon.svg'
--- icon.svg 1970-01-01 00:00:00 +0000
+++ icon.svg 2013-06-05 17:10:31 +0000
@@ -0,0 +1,377 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4<svg
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:cc="http://creativecommons.org/ns#"
7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8 xmlns:svg="http://www.w3.org/2000/svg"
9 xmlns="http://www.w3.org/2000/svg"
10 xmlns:xlink="http://www.w3.org/1999/xlink"
11 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13 width="96"
14 height="96"
15 id="svg6517"
16 version="1.1"
17 inkscape:version="0.48.4 r9939"
18 sodipodi:docname="icon.svg">
19 <defs
20 id="defs6519">
21 <linearGradient
22 inkscape:collect="always"
23 xlink:href="#Background"
24 id="linearGradient6461"
25 gradientUnits="userSpaceOnUse"
26 x1="0"
27 y1="970.29498"
28 x2="144"
29 y2="970.29498"
30 gradientTransform="matrix(0,-0.66666669,0.6660448,0,-866.25992,731.29077)" />
31 <linearGradient
32 id="Background">
33 <stop
34 id="stop4178"
35 offset="0"
36 style="stop-color:#574c4a;stop-opacity:1" />
37 <stop
38 id="stop4180"
39 offset="1"
40 style="stop-color:#80716d;stop-opacity:1" />
41 </linearGradient>
42 <filter
43 style="color-interpolation-filters:sRGB;"
44 inkscape:label="Inner Shadow"
45 id="filter1121">
46 <feFlood
47 flood-opacity="0.59999999999999998"
48 flood-color="rgb(0,0,0)"
49 result="flood"
50 id="feFlood1123" />
51 <feComposite
52 in="flood"
53 in2="SourceGraphic"
54 operator="out"
55 result="composite1"
56 id="feComposite1125" />
57 <feGaussianBlur
58 in="composite1"
59 stdDeviation="1"
60 result="blur"
61 id="feGaussianBlur1127" />
62 <feOffset
63 dx="0"
64 dy="2"
65 result="offset"
66 id="feOffset1129" />
67 <feComposite
68 in="offset"
69 in2="SourceGraphic"
70 operator="atop"
71 result="composite2"
72 id="feComposite1131" />
73 </filter>
74 <filter
75 style="color-interpolation-filters:sRGB;"
76 inkscape:label="Drop Shadow"
77 id="filter950">
78 <feFlood
79 flood-opacity="0.25"
80 flood-color="rgb(0,0,0)"
81 result="flood"
82 id="feFlood952" />
83 <feComposite
84 in="flood"
85 in2="SourceGraphic"
86 operator="in"
87 result="composite1"
88 id="feComposite954" />
89 <feGaussianBlur
90 in="composite1"
91 stdDeviation="1"
92 result="blur"
93 id="feGaussianBlur956" />
94 <feOffset
95 dx="0"
96 dy="1"
97 result="offset"
98 id="feOffset958" />
99 <feComposite
100 in="SourceGraphic"
101 in2="offset"
102 operator="over"
103 result="composite2"
104 id="feComposite960" />
105 </filter>
106 <clipPath
107 clipPathUnits="userSpaceOnUse"
108 id="clipPath873">
109 <g
110 transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
111 id="g875"
112 inkscape:label="Layer 1"
113 style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
114 <path
115 style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
116 d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
117 id="path877"
118 inkscape:connector-curvature="0"
119 sodipodi:nodetypes="sssssssss" />
120 </g>
121 </clipPath>
122 <filter
123 inkscape:collect="always"
124 id="filter891"
125 inkscape:label="Badge Shadow">
126 <feGaussianBlur
127 inkscape:collect="always"
128 stdDeviation="0.71999962"
129 id="feGaussianBlur893" />
130 </filter>
131 <clipPath
132 clipPathUnits="userSpaceOnUse"
133 id="clipPath874">
134 <path
135 sodipodi:nodetypes="cccssczcssccccscc"
136 inkscape:connector-curvature="0"
137 id="path876"
138 d="m -414.0975,764.53909 c -7.8125,17.9106 -1.95313,49.75167 -1.95313,49.75167 l 12.69531,0 c 0,-2.9851 -0.83592,-4.55148 -1.19017,-6.62319 -3.77705,-22.08828 -2.54859,-29.19801 -0.76295,-29.19801 1.95312,0 10.74219,24.87583 10.74219,24.87583 0,0 1.95313,-0.49751 3.90625,-0.49751 1.95312,0 3.90625,0.49751 3.90625,0.49751 0,0 8.78906,-24.87583 10.74218,-24.87583 1.78565,0 3.01411,7.10973 -0.76293,29.19801 -0.35426,2.07171 -1.19019,3.63809 -1.19019,6.62319 l 12.69532,0 c 0,0 5.85937,-31.84107 -1.95314,-49.75167 l -11.71874,0 c -3.3378,-0.20005 -10.74219,14.9255 -11.71875,14.9255 C -391.63657,779.46459 -399.04095,764.33904 -402.37875,764.53909 Z"
139 style="opacity:0.47400004;color:#000000;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
140 </clipPath>
141 <clipPath
142 clipPathUnits="userSpaceOnUse"
143 id="clipPath896">
144 <path
145 sodipodi:nodetypes="cccssczcssccccscc"
146 inkscape:connector-curvature="0"
147 id="path898"
148 d="m -414.0975,764.53909 c -7.8125,17.9106 -1.95313,49.75167 -1.95313,49.75167 l 12.69531,0 c 0,-2.9851 -0.83592,-4.55148 -1.19017,-6.62319 -3.77705,-22.08828 -2.54859,-29.19801 -0.76295,-29.19801 1.95312,0 10.74219,24.87583 10.74219,24.87583 0,0 1.95313,-0.49751 3.90625,-0.49751 1.95312,0 3.90625,0.49751 3.90625,0.49751 0,0 8.78906,-24.87583 10.74218,-24.87583 1.78565,0 3.01411,7.10973 -0.76293,29.19801 -0.35426,2.07171 -1.19019,3.63809 -1.19019,6.62319 l 12.69532,0 c 0,0 5.85937,-31.84107 -1.95314,-49.75167 l -11.71874,0 c -3.3378,-0.20005 -10.74219,14.9255 -11.71875,14.9255 C -391.63657,779.46459 -399.04095,764.33904 -402.37875,764.53909 Z"
149 style="color:#000000;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
150 </clipPath>
151 <linearGradient
152 id="linearGradient3354-9">
153 <stop
154 id="stop3356-9"
155 offset="0"
156 style="stop-color:#959595;stop-opacity:1;" />
157 <stop
158 id="stop3358-9"
159 offset="1"
160 style="stop-color:#cccccc;stop-opacity:1;" />
161 </linearGradient>
162 <linearGradient
163 y2="-32.881535"
164 x2="-560.61346"
165 y1="-40.681377"
166 x1="-403.07309"
167 gradientUnits="userSpaceOnUse"
168 id="linearGradient4343"
169 xlink:href="#linearGradient3354-9"
170 inkscape:collect="always" />
171 <inkscape:perspective
172 sodipodi:type="inkscape:persp3d"
173 inkscape:vp_x="0 : 0.5 : 1"
174 inkscape:vp_y="0 : 1000 : 0"
175 inkscape:vp_z="1 : 0.5 : 1"
176 inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
177 id="perspective4393" />
178 <inkscape:perspective
179 id="perspective4383"
180 inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
181 inkscape:vp_z="744.09448 : 526.18109 : 1"
182 inkscape:vp_y="0 : 1000 : 0"
183 inkscape:vp_x="0 : 526.18109 : 1"
184 sodipodi:type="inkscape:persp3d" />
185 <linearGradient
186 inkscape:collect="always"
187 xlink:href="#linearGradient3354-9"
188 id="linearGradient3164"
189 gradientUnits="userSpaceOnUse"
190 x1="-403.07309"
191 y1="-40.681377"
192 x2="-560.61346"
193 y2="-32.881535" />
194 </defs>
195 <sodipodi:namedview
196 id="base"
197 pagecolor="#ffffff"
198 bordercolor="#666666"
199 borderopacity="1.0"
200 inkscape:pageopacity="0.0"
201 inkscape:pageshadow="2"
202 inkscape:zoom="2.6077032"
203 inkscape:cx="-17.529322"
204 inkscape:cy="74.347537"
205 inkscape:document-units="px"
206 inkscape:current-layer="layer1"
207 showgrid="false"
208 fit-margin-top="0"
209 fit-margin-left="0"
210 fit-margin-right="0"
211 fit-margin-bottom="0"
212 inkscape:window-width="1920"
213 inkscape:window-height="1056"
214 inkscape:window-x="0"
215 inkscape:window-y="24"
216 inkscape:window-maximized="1"
217 showborder="true"
218 showguides="false"
219 inkscape:guide-bbox="true"
220 inkscape:showpageshadow="false"
221 inkscape:snap-global="false"
222 inkscape:snap-bbox="true"
223 inkscape:bbox-paths="true"
224 inkscape:bbox-nodes="true"
225 inkscape:snap-bbox-edge-midpoints="true"
226 inkscape:snap-bbox-midpoints="true"
227 inkscape:snap-intersection-paths="true"
228 inkscape:object-paths="true"
229 inkscape:object-nodes="true"
230 inkscape:snap-smooth-nodes="true"
231 inkscape:snap-midpoints="true"
232 inkscape:snap-object-midpoints="false"
233 inkscape:snap-center="false"
234 inkscape:snap-grids="false"
235 inkscape:snap-to-guides="false">
236 <inkscape:grid
237 type="xygrid"
238 id="grid821" />
239 <sodipodi:guide
240 orientation="1,0"
241 position="16,48"
242 id="guide823" />
243 <sodipodi:guide
244 orientation="0,1"
245 position="64,80"
246 id="guide825" />
247 <sodipodi:guide
248 orientation="1,0"
249 position="80,40"
250 id="guide827" />
251 <sodipodi:guide
252 orientation="0,1"
253 position="64,16"
254 id="guide829" />
255 </sodipodi:namedview>
256 <metadata
257 id="metadata6522">
258 <rdf:RDF>
259 <cc:Work
260 rdf:about="">
261 <dc:format>image/svg+xml</dc:format>
262 <dc:type
263 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
264 <dc:title />
265 </cc:Work>
266 </rdf:RDF>
267 </metadata>
268 <g
269 inkscape:label="BACKGROUND"
270 inkscape:groupmode="layer"
271 id="layer1"
272 transform="translate(268,-635.29076)"
273 style="display:inline">
274 <path
275 style="fill:url(#linearGradient6461);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)"
276 d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z"
277 id="path6455"
278 inkscape:connector-curvature="0"
279 sodipodi:nodetypes="sssssssss" />
280 <g
281 id="g3160"
282 transform="matrix(2.5999788,0,0,2.5999788,820.10006,-1019.0997)">
283 <path
284 transform="matrix(0.13863554,0,0,0.13863554,-336.25896,668.28059)"
285 d="m -339.89735,-32.881535 c 0,4.693003 -53.80552,8.497434 -120.178,8.497434 -66.37247,0 -120.17799,-3.804431 -120.17799,-8.497434 0,-4.693003 53.80552,-8.497433 120.17799,-8.497433 66.37248,0 120.178,3.80443 120.178,8.497433 z"
286 sodipodi:ry="8.4974337"
287 sodipodi:rx="120.17799"
288 sodipodi:cy="-32.881535"
289 sodipodi:cx="-460.07535"
290 id="path3423-1"
291 style="opacity:0.26353838;fill:url(#linearGradient3164);fill-opacity:1;stroke:none"
292 sodipodi:type="arc" />
293 <path
294 sodipodi:nodetypes="cssssssssssssscccsssssssssssssssssssssssssssssssssscccssssssssssssscccscccssc"
295 style="fill:#499848;fill-opacity:1"
296 d="m -402.29334,664.1964 c -0.32255,-0.10267 -0.3237,-0.14801 -0.0265,-1.05026 0.29676,-0.90103 0.16075,-1.73779 -0.38936,-2.39552 -0.74807,-0.89441 -1.10829,-1.6576 -1.22312,-2.59137 -0.0986,-0.80166 -0.12982,-0.86522 -0.4562,-0.92788 -0.21891,-0.042 -0.52133,0.0412 -1.24209,0.34186 -1.04863,0.43742 -1.29839,0.62949 -1.54583,1.18878 -0.12999,0.29381 -0.14561,0.46981 -0.0719,0.80967 0.09,0.41499 0.45901,1.08302 0.59869,1.08391 0.0365,2.5e-4 0.23784,0.13817 0.44752,0.30652 0.34793,0.27934 0.37182,0.32788 0.27336,0.55559 -0.21392,0.49478 -0.72219,1.09466 -0.92748,1.09466 -0.11518,0 -0.58554,-0.32946 -1.10329,-0.77279 -1.01695,-0.87078 -1.05958,-0.95808 -1.16462,-2.38541 -0.12532,-1.70303 0.29346,-2.49783 2.15866,-4.09684 l 1.16985,-1.00289 0.0747,-0.60834 c 0.077,-0.62712 -0.0316,-2.12038 -0.21414,-2.94465 -0.10786,-0.48695 -0.4638,-0.95075 -0.80158,-1.04446 -0.36801,-0.1021 -0.58375,0.10856 -0.86371,0.84341 -0.30207,0.79287 -0.59076,1.06948 -1.1162,1.06948 -0.32728,0 -0.6327,-0.20936 -0.84359,-0.57827 -0.15111,-0.26434 -0.23883,-1.85237 -0.14849,-2.68831 0.0434,-0.40211 0.0459,-0.80924 0.006,-0.90473 -0.0658,-0.15538 -0.63198,-0.49149 -1.96689,-1.16755 -0.24267,-0.1229 -0.52558,-0.28837 -0.62868,-0.36772 -0.10311,-0.0794 -0.37065,-0.22525 -0.59455,-0.32421 -0.50715,-0.22415 -1.82947,-0.99732 -1.70567,-0.99732 0.23419,0 1.22669,0.30547 1.75505,0.54017 0.32253,0.14327 0.63128,0.2605 0.6861,0.2605 0.0548,0 0.67664,0.20016 1.38183,0.44482 0.70519,0.24464 1.34972,0.44481 1.4323,0.44481 0.19613,0 0.22689,-0.16968 0.16404,-0.90503 -0.0285,-0.33404 -0.0273,-0.60735 0.003,-0.60735 0.0301,0 0.16004,0.0645 0.28877,0.14332 0.39331,0.24081 1.23931,0.3883 2.23366,0.38941 0.95078,0.001 2.19183,0.1897 2.66616,0.40525 0.68043,0.30922 1.46971,1.27502 2.47094,3.02357 0.69607,1.21563 1.444,1.73037 2.51588,1.73149 0.43331,5.1e-4 1.58939,-0.17815 3.15493,-0.48741 1.29085,-0.25501 2.99494,-0.31232 3.6564,-0.12298 0.80694,0.23098 1.87391,0.84848 2.75609,1.59507 0.82568,0.69877 0.95284,0.8539 1.46004,1.78116 0.60531,1.10661 1.27338,2.67936 1.86747,4.39633 0.0847,0.24465 0.26345,0.65618 0.39732,0.91452 0.3572,0.68927 0.3394,0.73131 -0.30971,0.73131 -0.70453,0 -1.03494,-0.1111 -1.49679,-0.50328 -0.44295,-0.37615 -0.55866,-0.60628 -1.25198,-2.48999 -0.35513,-0.96487 -0.70083,-1.54387 -0.92179,-1.54387 -0.15139,0 -0.45783,1.03396 -0.45783,1.54481 0,0.36283 0.22737,0.85298 0.81521,1.75738 0.95494,1.4692 1.45727,3.54612 1.2562,5.19382 l -0.0869,0.71171 -0.63641,0.0265 c -1.00197,0.0417 -0.99194,0.0556 -0.99194,-1.37711 0,-1.52531 -0.10388,-1.85564 -0.77699,-2.47079 -0.27959,-0.25551 -0.90686,-0.98147 -1.39393,-1.61323 -0.48708,-0.63176 -0.95354,-1.14866 -1.03657,-1.14866 -0.19081,0 -0.28595,0.51953 -0.29609,1.61676 -0.007,0.72259 -0.0351,0.8449 -0.24832,1.06756 -0.57796,0.60364 -0.98365,1.70279 -1.23376,3.34261 -0.0554,0.36297 -0.10282,0.43087 -0.3381,0.4837 -0.34594,0.0777 -1.30461,0.0904 -1.38722,0.0184 -0.0713,-0.0621 0.0536,-1.04774 0.33682,-2.65912 0.1075,-0.61162 0.22483,-1.43231 0.26074,-1.82375 0.0639,-0.69684 -0.079,-2.0024 -0.24805,-2.26654 -0.0703,-0.10982 -0.42833,-0.13362 -2.10288,-0.13977 -1.10998,-0.004 -2.32728,-0.0406 -2.70511,-0.0811 l -0.68696,-0.0737 0,0.30485 c 0,0.32644 0.0837,0.61418 0.64321,2.21185 0.19707,0.56269 0.48264,1.42052 0.63459,1.90628 l 0.27629,0.88321 -0.23315,0.71813 c -0.12823,0.39498 -0.31854,0.86827 -0.42291,1.05176 -0.18409,0.32363 -0.20568,0.3334 -0.722,0.32658 -0.29273,-0.004 -0.6626,-0.0485 -0.82193,-0.0992 z"
297 id="path3046-2-3-0"
298 inkscape:connector-curvature="0" />
299 </g>
300 </g>
301 <g
302 inkscape:groupmode="layer"
303 id="layer3"
304 inkscape:label="PLACE YOUR PICTOGRAM HERE"
305 style="display:inline" />
306 <g
307 inkscape:groupmode="layer"
308 id="layer2"
309 inkscape:label="BADGE"
310 style="display:none"
311 sodipodi:insensitive="true">
312 <g
313 style="display:inline"
314 transform="translate(-340.00001,-581)"
315 id="g4394"
316 clip-path="none">
317 <g
318 id="g855">
319 <g
320 inkscape:groupmode="maskhelper"
321 id="g870"
322 clip-path="url(#clipPath873)"
323 style="opacity:0.6;filter:url(#filter891)">
324 <path
325 transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
326 d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 z"
327 sodipodi:ry="12"
328 sodipodi:rx="12"
329 sodipodi:cy="552.36218"
330 sodipodi:cx="252"
331 id="path844"
332 style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
333 sodipodi:type="arc" />
334 </g>
335 <g
336 id="g862">
337 <path
338 sodipodi:type="arc"
339 style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
340 id="path4398"
341 sodipodi:cx="252"
342 sodipodi:cy="552.36218"
343 sodipodi:rx="12"
344 sodipodi:ry="12"
345 d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 z"
346 transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
347 <path
348 transform="matrix(1.25,0,0,1.25,33,-100.45273)"
349 d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 z"
350 sodipodi:ry="12"
351 sodipodi:rx="12"
352 sodipodi:cy="552.36218"
353 sodipodi:cx="252"
354 id="path4400"
355 style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
356 sodipodi:type="arc" />
357 <path
358 sodipodi:type="star"
359 style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
360 id="path4459"
361 sodipodi:sides="5"
362 sodipodi:cx="666.19574"
363 sodipodi:cy="589.50385"
364 sodipodi:r1="7.2431178"
365 sodipodi:r2="4.3458705"
366 sodipodi:arg1="1.0471976"
367 sodipodi:arg2="1.6755161"
368 inkscape:flatsided="false"
369 inkscape:rounded="0.1"
370 inkscape:randomized="0"
371 d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 -0.18379,0.41279 0.0427,4.27917 -0.34859,4.5051 z"
372 transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
373 </g>
374 </g>
375 </g>
376 </g>
377</svg>
0378
=== modified file 'metadata.yaml'
--- metadata.yaml 2012-09-10 05:28:47 +0000
+++ metadata.yaml 2013-06-05 17:10:31 +0000
@@ -1,6 +1,7 @@
1name: gunicorn1name: gunicorn
2summary: Gunicorn2summary: Gunicorn
3maintainer: Patrick Hetu <patrick.hetu@gmail.com>3maintainer: Patrick Hetu <patrick.hetu@gmail.com>
4categories: ["misc"]
4description: |5description: |
5 Gunicorn or Green Unicorn is a Python WSGI HTTP Server for UNIX. It's a6 Gunicorn or Green Unicorn is a Python WSGI HTTP Server for UNIX. It's a
6 pre-fork worker model ported from Ruby's Unicorn project. The Gunicorn server7 pre-fork worker model ported from Ruby's Unicorn project. The Gunicorn server
@@ -12,8 +13,3 @@
12 interface: wsgi13 interface: wsgi
13 scope: container14 scope: container
14 optional: true15 optional: true
15 django-settings:
16 interface: django
17 scope: container
18 optional: true
19
2016
=== modified file 'revision'
--- revision 2012-03-27 01:16:08 +0000
+++ revision 2013-06-05 17:10:31 +0000
@@ -1,1 +1,1 @@
1113
22
=== added directory 'templates'
=== added file 'templates/upstart.tmpl'
--- templates/upstart.tmpl 1970-01-01 00:00:00 +0000
+++ templates/upstart.tmpl 2013-06-05 17:10:31 +0000
@@ -0,0 +1,34 @@
1#--------------------------------------------------------------
2# This file is managed by Juju; ANY CHANGES WILL BE OVERWRITTEN
3#--------------------------------------------------------------
4
5description "Gunicorn daemon for the {{ project_name }} project"
6
7start on (local-filesystems and net-device-up IFACE=eth0)
8stop on runlevel [!12345]
9
10# If the process quits unexpectadly trigger a respawn
11respawn
12
13setuid {{ wsgi_user }}
14setgid {{ wsgi_group }}
15chdir {{ working_dir }}
16
17# This line can be removed and replace with the --pythonpath {{ python_path }} \
18# option with Gunicorn>1.17
19env PYTHONPATH={{ python_path }}
20
21exec gunicorn \
22 --name={{ project_name }} \
23 --workers={{ wsgi_workers }} \
24 --worker-class={{ wsgi_worker_class }} \
25 --worker-connections={{ wsgi_worker_connections }} \
26 --max-requests={{ wsgi_max_requests }} \
27 --backlog={{ wsgi_backlog }} \
28 --timeout={{ wsgi_timeout }} \
29 --keep-alive={{ wsgi_keep_alive }} \
30 --umask={{ wsgi_umask }} \
31 --bind={{ listen_ip }}:{{ port }} \
32 --log-file={{ wsgi_log_file }} \
33 --log-level={{ wsgi_log_level }} \
34 {{ wsgi_extra }} {{ wsgi_wsgi_file }}

Subscribers

People subscribed via source and target branches

to all changes: