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
1=== removed file 'README'
2--- README 2012-10-04 12:14:32 +0000
3+++ README 1970-01-01 00:00:00 +0000
4@@ -1,38 +0,0 @@
5-Juju charm gunicorn
6-===================
7-
8-:Author: Patrick Hetu <patrick@koumbit.org>
9-
10-How to configure the charm
11---------------------------
12-
13-To deploy a charm with this subordinate it must minimaly:
14-
15- 1. Provide the wsgi interface.
16- 2. Set the `working_dir` relation variable in the wsgi hook.
17-
18-The configuration of Gunicorn will use the variable pass by
19-the relation hook first. If there are not define it will
20-fallback to the global configuration of the charm.
21-
22-Example deployment
23-------------------
24-
25-
26-
27-1. Deployment with python-moinmoin for example::
28-
29- juju bootstrap
30- juju deploy --config mywiki_with_wsgi_settings.yaml python-moinmoin
31- juju deploy gunicorn
32- juju add-relation gunicorn python-moinmoin
33- juju expose gunicorn
34-
35-2. Accessing your new wiki should be ready at::
36-
37- http://<machine-addr>:8080/
38-
39- To find out the public address of gunicorn/python-moinmoin, look for it in
40- the output of the `juju status` command.
41- I recommend using a reverse proxy like Nginx in front of Gunicorn.
42-
43
44=== added file 'README.rst'
45--- README.rst 1970-01-01 00:00:00 +0000
46+++ README.rst 2013-06-05 17:10:31 +0000
47@@ -0,0 +1,52 @@
48+Juju charm gunicorn
49+===================
50+
51+:Author: Patrick Hetu <patrick@koumbit.org>
52+
53+How to configure the charm
54+--------------------------
55+
56+To deploy a charm with this subordinate it must minimaly:
57+
58+ 1. Provide the wsgi interface.
59+ 2. Set the `working_dir` relation variable in the wsgi hook.
60+
61+The configuration of Gunicorn will use the variable pass by
62+the relation hook first. If there are not define it will
63+fallback to the global configuration of the charm.
64+
65+Example deployment
66+------------------
67+
68+1. Deployment with python-moinmoin for example::
69+
70+ juju bootstrap
71+ juju deploy --config mywiki_with_wsgi_settings.yaml python-moinmoin
72+ juju deploy gunicorn
73+ juju add-relation gunicorn python-moinmoin
74+ juju expose python-moinmoin
75+
76+2. Accessing your new wiki should be ready at::
77+
78+ http://<machine-addr>:8080/
79+
80+ To find out the public address of gunicorn/python-moinmoin, look for it in
81+ the output of the `juju status` command.
82+ I recommend using a reverse proxy like Nginx in front of Gunicorn.
83+
84+Changelog
85+---------
86+3:
87+
88+ Notable changes:
89+
90+ * Rewrite the charm using python instead of BASH scripts
91+ * add listen_ip configuration variable
92+
93+ Backwards incompatible changes:
94+ * Remove the Django mode since Gunicorn is not recommending it anymore.
95+ * Use Upstart to manage daemons
96+ * no start/stop hook anymore use related charms instead.
97+ * no configuration change directly on the charm anymore, use related charms instead.
98+ * no access logging by default
99+ * exposing a port must now be done in the linked charm instead of this one
100
101=== modified file 'config.yaml'
102--- config.yaml 2012-10-03 17:57:11 +0000
103+++ config.yaml 2013-06-05 17:10:31 +0000
104@@ -1,15 +1,16 @@
105 options:
106 wsgi_wsgi_file:
107 type: string
108+ default: "wsgi"
109 description: "The name of the WSGI application."
110 wsgi_workers:
111 type: int
112- default: 2
113- description: "The number of worker process for handling requests."
114+ default: 0
115+ description: "The number of worker process for handling requests. 0 for count(cpu) + 1"
116 wsgi_worker_class:
117 type: string
118 default: "sync"
119- description: "Gunicorn workers type."
120+ description: "Gunicorn workers type. Can be: sync, eventlet, gevent, tornado"
121 wsgi_worker_connections:
122 type: int
123 default: 1000
124@@ -25,11 +26,11 @@
125 wsgi_timeout:
126 type: int
127 default: 30
128- description: ""
129+ description: "Timeout of a request in seconds."
130 wsgi_keep_alive:
131 type: int
132 default: 2
133- description: ""
134+ description: "Keep alive time in seconds."
135 wsgi_umask:
136 type: string
137 default: "0"
138@@ -45,36 +46,36 @@
139 wsgi_log_file:
140 type: string
141 default: "-"
142- description: "The log file to write to. If empty the file would be <your_application_dir>/gunicorn.log"
143+ description: "The log file to write to. If empty the logs would be handle by upstart."
144 wsgi_log_level:
145 type: string
146 default: "info"
147 description: "The granularity of Error log outputs."
148 wsgi_access_logfile:
149 type: string
150- default: "-"
151+ default: ""
152 description: "The Access log file to write to."
153 wsgi_access_logformat:
154 type: string
155- default: '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
156- description: "The Access log format ."
157+ default: ""
158+ description: "The Access log format. Don't forget to escape all quotes and round brackets."
159 wsgi_extra:
160 type: string
161 default: ""
162- description: "Extra settings. For example: {--debug},"
163+ description: "Space separated extra settings. For example: --debug"
164 wsgi_timestamp:
165 type: string
166 default: ""
167- description: "The variable to modify to trigger Gunicorn reload"
168- django_settings:
169- type: string
170- default: ""
171- description: "The Python path to a Django settings module."
172+ description: "The variable to modify to trigger Gunicorn reload."
173 python_path:
174 type: string
175 default: ""
176- description: "Set PYTHONPATH environment variable"
177+ description: "Set an additionnal PYTHONPATH to the project."
178+ listen_ip:
179+ type: string
180+ default: "0.0.0.0"
181+ description: "IP adresses that Gunicorn will listen on. By default we listen on all of them."
182 port:
183 type: int
184 default: 8080
185- description: "Default listen port"
186+ description: "Port the application will be listenning."
187
188=== removed file 'hooks/config-changed'
189--- hooks/config-changed 2012-07-06 16:06:15 +0000
190+++ hooks/config-changed 1970-01-01 00:00:00 +0000
191@@ -1,6 +0,0 @@
192-#!/bin/bash
193-set -e
194-
195-juju-log "reloading Gunicorn"
196-
197-service gunicorn reload || true
198
199=== removed symlink 'hooks/django-settings-relation-changed'
200=== target was u'wsgi-file-relation-joined'
201=== removed symlink 'hooks/django-settings-relation-joined'
202=== target was u'wsgi-file-relation-joined'
203=== added file 'hooks/hooks.py'
204--- hooks/hooks.py 1970-01-01 00:00:00 +0000
205+++ hooks/hooks.py 2013-06-05 17:10:31 +0000
206@@ -0,0 +1,491 @@
207+#!/usr/bin/env python
208+# vim: et ai ts=4 sw=4:
209+
210+import json
211+import os
212+import re
213+import subprocess
214+import sys
215+import time
216+from pwd import getpwnam
217+from grp import getgrnam
218+
219+CHARM_PACKAGES = ["gunicorn"]
220+
221+INJECTED_WARNING = """
222+#------------------------------------------------------------------------------
223+# The following is the import code for the settings directory injected by Juju
224+#------------------------------------------------------------------------------
225+"""
226+
227+
228+###############################################################################
229+# Supporting functions
230+###############################################################################
231+MSG_CRITICAL = "CRITICAL"
232+MSG_DEBUG = "DEBUG"
233+MSG_INFO = "INFO"
234+MSG_ERROR = "ERROR"
235+MSG_WARNING = "WARNING"
236+
237+
238+def juju_log(level, msg):
239+ subprocess.call(['juju-log', '-l', level, msg])
240+
241+#------------------------------------------------------------------------------
242+# run: Run a command, return the output
243+#------------------------------------------------------------------------------
244+def run(command, exit_on_error=True, cwd=None):
245+ try:
246+ juju_log(MSG_DEBUG, command)
247+ return subprocess.check_output(
248+ command, stderr=subprocess.STDOUT, shell=True, cwd=cwd)
249+ except subprocess.CalledProcessError, e:
250+ juju_log(MSG_ERROR, "status=%d, output=%s" % (e.returncode, e.output))
251+ if exit_on_error:
252+ sys.exit(e.returncode)
253+ else:
254+ raise
255+
256+
257+#------------------------------------------------------------------------------
258+# install_file: install a file resource. overwites existing files.
259+#------------------------------------------------------------------------------
260+def install_file(contents, dest, owner="root", group="root", mode=0600):
261+ uid = getpwnam(owner)[2]
262+ gid = getgrnam(group)[2]
263+ dest_fd = os.open(dest, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
264+ os.fchown(dest_fd, uid, gid)
265+ with os.fdopen(dest_fd, 'w') as destfile:
266+ destfile.write(str(contents))
267+
268+
269+#------------------------------------------------------------------------------
270+# install_dir: create a directory
271+#------------------------------------------------------------------------------
272+def install_dir(dirname, owner="root", group="root", mode=0700):
273+ command = \
274+ '/usr/bin/install -o {} -g {} -m {} -d {}'.format(owner, group, oct(mode),
275+ dirname)
276+ return run(command)
277+
278+#------------------------------------------------------------------------------
279+# config_get: Returns a dictionary containing all of the config information
280+# Optional parameter: scope
281+# scope: limits the scope of the returned configuration to the
282+# desired config item.
283+#------------------------------------------------------------------------------
284+def config_get(scope=None):
285+ try:
286+ config_cmd_line = ['config-get']
287+ if scope is not None:
288+ config_cmd_line.append(scope)
289+ config_cmd_line.append('--format=json')
290+ config_data = json.loads(subprocess.check_output(config_cmd_line))
291+ except:
292+ config_data = None
293+ finally:
294+ return(config_data)
295+
296+
297+#------------------------------------------------------------------------------
298+# relation_json: Returns json-formatted relation data
299+# Optional parameters: scope, relation_id
300+# scope: limits the scope of the returned data to the
301+# desired item.
302+# unit_name: limits the data ( and optionally the scope )
303+# to the specified unit
304+# relation_id: specify relation id for out of context usage.
305+#------------------------------------------------------------------------------
306+def relation_json(scope=None, unit_name=None, relation_id=None):
307+ command = ['relation-get', '--format=json']
308+ if relation_id is not None:
309+ command.extend(('-r', relation_id))
310+ if scope is not None:
311+ command.append(scope)
312+ else:
313+ command.append('-')
314+ if unit_name is not None:
315+ command.append(unit_name)
316+ output = subprocess.check_output(command, stderr=subprocess.STDOUT)
317+ return output or None
318+
319+
320+#------------------------------------------------------------------------------
321+# relation_get: Returns a dictionary containing the relation information
322+# Optional parameters: scope, relation_id
323+# scope: limits the scope of the returned data to the
324+# desired item.
325+# unit_name: limits the data ( and optionally the scope )
326+# to the specified unit
327+#------------------------------------------------------------------------------
328+def relation_get(scope=None, unit_name=None, relation_id=None):
329+ j = relation_json(scope, unit_name, relation_id)
330+ if j:
331+ return json.loads(j)
332+ else:
333+ return None
334+
335+
336+def relation_set(keyvalues, relation_id=None):
337+ args = []
338+ if relation_id:
339+ args.extend(['-r', relation_id])
340+ args.extend(["{}='{}'".format(k, v or '') for k, v in keyvalues.items()])
341+ run("relation-set {}".format(' '.join(args)))
342+
343+ ## Posting json to relation-set doesn't seem to work as documented?
344+ ## Bug #1116179
345+ ##
346+ ## cmd = ['relation-set']
347+ ## if relation_id:
348+ ## cmd.extend(['-r', relation_id])
349+ ## p = Popen(
350+ ## cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
351+ ## stderr=subprocess.PIPE)
352+ ## (out, err) = p.communicate(json.dumps(keyvalues))
353+ ## if p.returncode:
354+ ## juju_log(MSG_ERROR, err)
355+ ## sys.exit(1)
356+ ## juju_log(MSG_DEBUG, "relation-set {}".format(repr(keyvalues)))
357+
358+
359+def relation_list(relation_id=None):
360+ """Return the list of units participating in the relation."""
361+ if relation_id is None:
362+ relation_id = os.environ['JUJU_RELATION_ID']
363+ cmd = ['relation-list', '--format=json', '-r', relation_id]
364+ json_units = subprocess.check_output(cmd).strip()
365+ if json_units:
366+ return json.loads(subprocess.check_output(cmd))
367+ return []
368+
369+
370+#------------------------------------------------------------------------------
371+# relation_ids: Returns a list of relation ids
372+# optional parameters: relation_type
373+# relation_type: return relations only of this type
374+#------------------------------------------------------------------------------
375+def relation_ids(relation_types=('db',)):
376+ # accept strings or iterators
377+ if isinstance(relation_types, basestring):
378+ reltypes = [relation_types, ]
379+ else:
380+ reltypes = relation_types
381+ relids = []
382+ for reltype in reltypes:
383+ relid_cmd_line = ['relation-ids', '--format=json', reltype]
384+ json_relids = subprocess.check_output(relid_cmd_line).strip()
385+ if json_relids:
386+ relids.extend(json.loads(json_relids))
387+ return relids
388+
389+
390+#------------------------------------------------------------------------------
391+# relation_get_all: Returns a dictionary containing the relation information
392+# optional parameters: relation_type
393+# relation_type: limits the scope of the returned data to the
394+# desired item.
395+#------------------------------------------------------------------------------
396+def relation_get_all(*args, **kwargs):
397+ relation_data = []
398+ relids = relation_ids(*args, **kwargs)
399+ for relid in relids:
400+ units_cmd_line = ['relation-list', '--format=json', '-r', relid]
401+ json_units = subprocess.check_output(units_cmd_line).strip()
402+ if json_units:
403+ for unit in json.loads(json_units):
404+ unit_data = \
405+ json.loads(relation_json(relation_id=relid,
406+ unit_name=unit))
407+ for key in unit_data:
408+ if key.endswith('-list'):
409+ unit_data[key] = unit_data[key].split()
410+ unit_data['relation-id'] = relid
411+ unit_data['unit'] = unit
412+ relation_data.append(unit_data)
413+ return relation_data
414+
415+def apt_get_update():
416+ cmd_line = ['apt-get', 'update']
417+ return(subprocess.call(cmd_line))
418+
419+
420+#------------------------------------------------------------------------------
421+# apt_get_install( packages ): Installs package(s)
422+#------------------------------------------------------------------------------
423+def apt_get_install(packages=None):
424+ if packages is None:
425+ return(False)
426+ cmd_line = ['apt-get', '-y', 'install', '-qq']
427+ if isinstance(packages, list):
428+ cmd_line.extend(packages)
429+ else:
430+ cmd_line.append(packages)
431+ return(subprocess.call(cmd_line))
432+
433+
434+#------------------------------------------------------------------------------
435+# pip_install( package ): Installs a python package
436+#------------------------------------------------------------------------------
437+def pip_install(packages=None, upgrade=False):
438+ cmd_line = ['pip', 'install']
439+ if packages is None:
440+ return(False)
441+ if upgrade:
442+ cmd_line.append('-u')
443+ if packages.startswith('svn+') or packages.startswith('git+') or \
444+ packages.startswith('hg+') or packages.startswith('bzr+'):
445+ cmd_line.append('-e')
446+ cmd_line.append(packages)
447+ return run(cmd_line)
448+
449+#------------------------------------------------------------------------------
450+# pip_install_req( path ): Installs a requirements file
451+#------------------------------------------------------------------------------
452+def pip_install_req(path=None, upgrade=False):
453+ cmd_line = ['pip', 'install']
454+ if path is None:
455+ return(False)
456+ if upgrade:
457+ cmd_line.append('-u')
458+ cmd_line.append('-r')
459+ cmd_line.append(path)
460+ cwd = os.path.dirname(path)
461+ return run(cmd_line)
462+
463+#------------------------------------------------------------------------------
464+# open_port: Convenience function to open a port in juju to
465+# expose a service
466+#------------------------------------------------------------------------------
467+def open_port(port=None, protocol="TCP"):
468+ if port is None:
469+ return(None)
470+ return(subprocess.call(['open-port', "%d/%s" %
471+ (int(port), protocol)]))
472+
473+
474+#------------------------------------------------------------------------------
475+# close_port: Convenience function to close a port in juju to
476+# unexpose a service
477+#------------------------------------------------------------------------------
478+def close_port(port=None, protocol="TCP"):
479+ if port is None:
480+ return(None)
481+ return(subprocess.call(['close-port', "%d/%s" %
482+ (int(port), protocol)]))
483+
484+
485+#------------------------------------------------------------------------------
486+# update_service_ports: Convenience function that evaluate the old and new
487+# service ports to decide which ports need to be
488+# opened and which to close
489+#------------------------------------------------------------------------------
490+def update_service_port(old_service_port=None, new_service_port=None):
491+ if old_service_port is None or new_service_port is None:
492+ return(None)
493+ if new_service_port != old_service_port:
494+ close_port(old_service_port)
495+ open_port(new_service_port)
496+
497+#
498+# Utils
499+#
500+
501+def install_or_append(contents, dest, owner="root", group="root", mode=0600):
502+ if os.path.exists(dest):
503+ uid = getpwnam(owner)[2]
504+ gid = getgrnam(group)[2]
505+ dest_fd = os.open(dest, os.O_APPEND, mode)
506+ os.fchown(dest_fd, uid, gid)
507+ with os.fdopen(dest_fd, 'a') as destfile:
508+ destfile.write(str(contents))
509+ else:
510+ install_file(contents, dest, owner, group, mode)
511+
512+def token_sql_safe(value):
513+ # Only allow alphanumeric + underscore in database identifiers
514+ if re.search('[^A-Za-z0-9_]', value):
515+ return False
516+ return True
517+
518+def sanitize(s):
519+ s = s.replace(':', '_')
520+ s = s.replace('-', '_')
521+ s = s.replace('/', '_')
522+ s = s.replace('"', '_')
523+ s = s.replace("'", '_')
524+ return s
525+
526+def user_name(relid, remote_unit, admin=False, schema=False):
527+ components = [sanitize(relid), sanitize(remote_unit)]
528+ if admin:
529+ components.append("admin")
530+ elif schema:
531+ components.append("schema")
532+ return "_".join(components)
533+
534+def get_relation_host():
535+ remote_host = run("relation-get ip")
536+ if not remote_host:
537+ # remote unit $JUJU_REMOTE_UNIT uses deprecated 'ip=' component of
538+ # interface.
539+ remote_host = run("relation-get private-address")
540+ return remote_host
541+
542+
543+def get_unit_host():
544+ this_host = run("unit-get private-address")
545+ return this_host.strip()
546+
547+def process_template(template_name, template_vars, destination):
548+ # --- exported service configuration file
549+ from jinja2 import Environment, FileSystemLoader
550+ template_env = Environment(
551+ loader=FileSystemLoader(os.path.join(os.environ['CHARM_DIR'],
552+ 'templates')))
553+
554+ template = \
555+ template_env.get_template(template_name).render(template_vars)
556+
557+ with open(destination, 'w') as inject_file:
558+ inject_file.write(str(template))
559+
560+def append_template(template_name, template_vars, path, try_append=False):
561+
562+ # --- exported service configuration file
563+ from jinja2 import Environment, FileSystemLoader
564+ template_env = Environment(
565+ loader=FileSystemLoader(os.path.join(os.environ['CHARM_DIR'],
566+ 'templates')))
567+
568+ template = \
569+ template_env.get_template(template_name).render(template_vars)
570+
571+ append = False
572+ if os.path.exists(path):
573+ with open(path, 'r') as inject_file:
574+ if not str(template) in inject_file:
575+ append = True
576+ else:
577+ append = True
578+
579+ if append == True:
580+ with open(path, 'a') as inject_file:
581+ inject_file.write(INJECTED_WARNING)
582+ inject_file.write(str(template))
583+
584+
585+
586+###############################################################################
587+# Hook functions
588+###############################################################################
589+def install():
590+
591+ for retry in xrange(0,24):
592+ if apt_get_install(CHARM_PACKAGES):
593+ time.sleep(10)
594+ else:
595+ break
596+
597+def upgrade():
598+
599+ apt_get_update()
600+ for retry in xrange(0,24):
601+ if apt_get_install(CHARM_PACKAGES):
602+ time.sleep(10)
603+ else:
604+ break
605+
606+def wsgi_file_relation_joined_changed():
607+ wsgi_config = config_data
608+ relation_id = os.environ['JUJU_RELATION_ID']
609+ juju_log(MSG_INFO, "JUJU_RELATION_ID: %s".format(relation_id))
610+
611+ remote_unit_name = sanitize(os.environ['JUJU_REMOTE_UNIT'].split('/')[0])
612+ juju_log(MSG_INFO, "JUJU_REMOTE_UNIT: %s".format(remote_unit_name))
613+ wsgi_config['unit_name'] = remote_unit_name
614+
615+ project_conf = '/etc/init/%s.conf' % remote_unit_name
616+
617+ working_dir = relation_get('working_dir')
618+ if not working_dir:
619+ return
620+
621+ wsgi_config['working_dir'] = working_dir
622+ wsgi_config['project_name'] = remote_unit_name
623+
624+ for v in wsgi_config.keys():
625+ if v.startswith('wsgi_') or v in ['python_path', 'listen_ip', 'port']:
626+ upstream_value = relation_get(v)
627+ if upstream_value:
628+ wsgi_config[v] = upstream_value
629+
630+ if wsgi_config['wsgi_worker_class'] == 'eventlet':
631+ apt_get_install('python-eventlet')
632+ elif wsgi_config['wsgi_worker_class'] == 'gevent':
633+ apt_get_install('python-gevent')
634+ elif wsgi_config['wsgi_worker_class'] == 'tornado':
635+ apt_get_install('python-tornado')
636+
637+ if wsgi_config['wsgi_workers'] == 0:
638+ res = run('python -c "import multiprocessing ; print(multiprocessing.cpu_count())"')
639+ wsgi_config['wsgi_workers'] = int(res) + 1
640+
641+ if wsgi_config['wsgi_access_logfile']:
642+ wsgi_config['wsgi_extra'] = " ".join([
643+ wsgi_config['wsgi_extra'],
644+ '--access-logformat=%s' % wsgi_config['wsgi_access_logfile'],
645+ '--access-logformat="%s"' % wsgi_config['wsgi_access_logformat']
646+ ])
647+
648+ wsgi_config['wsgi_wsgi_file'] = wsgi_config['wsgi_wsgi_file']
649+
650+ process_template('upstart.tmpl', wsgi_config, project_conf)
651+
652+
653+ # We need this because when the contained charm configuration or code changed
654+ # Gunicorn needs to restart to run the new code.
655+ run("service %s restart || service %s start" % (remote_unit_name, remote_unit_name))
656+
657+
658+def wsgi_file_relation_broken():
659+ remote_unit_name = sanitize(os.environ['JUJU_REMOTE_UNIT'].split('/')[0])
660+
661+ run('service %s stop' % remote_unit_name)
662+ run('rm /etc/init/%s.conf' % remote_unit_name)
663+
664+
665+###############################################################################
666+# Global variables
667+###############################################################################
668+config_data = config_get()
669+juju_log(MSG_DEBUG, "got config: %s" % str(config_data))
670+
671+unit_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
672+
673+hook_name = os.path.basename(sys.argv[0])
674+
675+###############################################################################
676+# Main section
677+###############################################################################
678+def main():
679+ juju_log(MSG_INFO, "Running {} hook".format(hook_name))
680+ if hook_name == "install":
681+ install()
682+
683+ elif hook_name == "upgrade-charm":
684+ upgrade()
685+
686+ elif hook_name in ["wsgi-file-relation-joined", "wsgi-file-relation-changed"]:
687+ wsgi_file_relation_joined_changed()
688+
689+ elif hook_name == "wsgi-file-relation-broken":
690+ wsgi_file_relation_broken()
691+
692+ else:
693+ print "Unknown hook {}".format(hook_name)
694+ raise SystemExit(1)
695+
696+if __name__ == '__main__':
697+ raise SystemExit(main())
698
699=== modified file 'hooks/install'
700--- hooks/install 2012-11-30 11:50:39 +0000
701+++ hooks/install 1970-01-01 00:00:00 +0000
702@@ -1,24 +0,0 @@
703-#!/bin/bash
704-set -e
705-
706-# Suboridnate charm hooks can run in parallel with other charm hooks
707-# on the same unit Bug #1068624. So a hook may fail to get a apt-get lock.
708-COUNTER=0
709-APT_LOCKED=1
710-while true; do
711- lsof /var/lib/dpkg/lock > /dev/null 2>&1 || APT_LOCKED=0
712- if [[ $APT_LOCKED -eq 0 ]]; then
713- apt-get install --no-install-recommends -q -y gunicorn \
714- python-eventlet \
715- python-gevent \
716- python-tornado
717- break
718- fi
719- if [[ $COUNTER -gt 5 ]]; then
720- echo "Failed to obtain apt lock to install python-amqplib"
721- exit 1
722- fi
723- sleep 60
724- COUNTER=$[$COUNTER+1]
725-done
726-
727
728=== target is u'hooks.py'
729=== removed file 'hooks/start'
730--- hooks/start 2012-07-06 16:06:15 +0000
731+++ hooks/start 1970-01-01 00:00:00 +0000
732@@ -1,4 +0,0 @@
733-#!/bin/bash
734-set -e
735-
736-service gunicorn start || service gunicorn restart
737
738=== removed file 'hooks/stop'
739--- hooks/stop 2012-07-06 16:06:15 +0000
740+++ hooks/stop 1970-01-01 00:00:00 +0000
741@@ -1,4 +0,0 @@
742-#!/bin/bash
743-set -e
744-
745-service gunicorn stop || true
746
747=== modified file 'hooks/upgrade-charm'
748--- hooks/upgrade-charm 2012-07-08 16:35:05 +0000
749+++ hooks/upgrade-charm 1970-01-01 00:00:00 +0000
750@@ -1,11 +0,0 @@
751-#!/bin/bash
752-set -e
753-
754-home=`dirname $0`
755-
756-juju-log "Upgrading charm by running install hook again."
757-$home/install
758-
759-juju-log "Upgrading charm, running config-changed hook again."
760-$home/config-changed
761-
762
763=== target is u'hooks.py'
764=== added symlink 'hooks/wsgi-file-relation-broken'
765=== target is u'hooks.py'
766=== modified symlink 'hooks/wsgi-file-relation-changed'
767=== target changed u'wsgi-file-relation-joined' => u'hooks.py'
768=== modified file 'hooks/wsgi-file-relation-joined'
769--- hooks/wsgi-file-relation-joined 2013-01-24 06:06:09 +0000
770+++ hooks/wsgi-file-relation-joined 1970-01-01 00:00:00 +0000
771@@ -1,102 +0,0 @@
772-#!/bin/bash
773-set -e
774-
775-unit_name=${JUJU_REMOTE_UNIT//\//-}
776-working_dir=$(relation-get working_dir)
777-
778-if [ -z "$working_dir" ] ; then
779- juju-log "No working_dir yet: skipping."
780- exit 0 # wait for future handshake
781-fi
782-
783-variables="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"
784-
785-if [[ $JUJU_RELATION_ID =~ django.* ]]; then
786- mode=django
787-elif [[ $JUJU_RELATION_ID =~ wsgi.* ]]; then
788- mode=wsgi
789-else
790- juju-log "Unknown mode ($JUJU_RELATION_ID)"
791- exit 1
792-fi
793-
794-juju-log "Running in ${mode} mode"
795-
796-
797-declare -A VAR
798-for v in $variables;do
799- VAR[$v]=$(relation-get $v)
800- if [ -z "${VAR[$v]}" ] ; then
801- VAR[$v]=$(config-get $v)
802- fi
803-done
804-
805-juju-log "Got variables: ${VAR[@]}"
806-
807-
808-if [ -z "$wsgi_error_logfile" ] ; then
809- error_logfile="${working_dir}/gunicorn.log"
810-fi
811-
812-if [ -n "$VAR[django_settings]" ] ; then
813- django_settings="'django_settings': '${VAR[django_settings]}',"
814-fi
815-
816-# If running in wsgi mode then set a default wsgi file
817-if [[ $mode -eq "wsgi" ]] && [[ $(relation-get wsgi_wsgi_file) -eq "" ]] ; then
818- VAR[wsgi_wsgi_file]="wsgi"
819-fi
820-
821-if [[ -n ${VAR[wsgi_wsgi_file]} ]] ; then
822- wsgi_wsgi_file="'${VAR[wsgi_wsgi_file]}',"
823-fi
824-
825-if [[ -z ${VAR[python_path]} ]] ; then
826- python_path=${working_dir}
827-else
828- python_path="${VAR[python_path]}"
829-fi
830-
831-juju-log "Writing config file: /etc/gunicorn.d/${unit_name}.conf"
832-
833-cat > /etc/gunicorn.d/${unit_name}.conf <<EOF
834-CONFIG = {
835- 'mode': '${mode}',
836- 'environment': {
837- 'PYTHONPATH': '${python_path}',
838- ${VAR[env_extra]}
839- },
840- ${django_settings}
841- 'working_dir': '${working_dir}',
842- 'user': '${VAR[wsgi_user]}',
843- 'group': '${VAR[wsgi_group]}',
844- 'args': (
845- '--name=${unit_name}',
846- '--workers=${VAR[wsgi_workers]}',
847- '--worker-class=${VAR[wsgi_worker_class]}',
848- '--worker-connections=${VAR[wsgi_worker_connections]}',
849- '--max-requests=${VAR[wsgi_max_requests]}',
850- '--backlog=${VAR[wsgi_backlog]}',
851- '--timeout=${VAR[wsgi_timeout]}',
852- '--keep-alive=${VAR[wsgi_keep_alive]}',
853- '--umask=${VAR[wsgi_umask]}',
854- '--bind=0.0.0.0:${VAR[port]}',
855- '--log-file=${VAR[wsgi_log_file]}',
856- '--log-level=${VAR[wsgi_log_level]}',
857- '--access-logfile=${VAR[wsgi_access_logfile]}',
858- '--access-logformat=${VAR[wsgi_access_logformat]}',
859- ${VAR[wsgi_extra]}
860- ${wsgi_wsgi_file}
861- ),
862-}
863-EOF
864-
865-
866-juju-log "start/restart gunicorn"
867-
868-# We need this because when the contained charm configuration or code changed
869-# Gunicorn needs to restart to run the new code.
870-service gunicorn restart
871-
872-juju-log "Opening port: ${VAR[port]}"
873-open-port "${VAR[port]}/tcp"
874
875=== target is u'hooks.py'
876=== added file 'icon.svg'
877--- icon.svg 1970-01-01 00:00:00 +0000
878+++ icon.svg 2013-06-05 17:10:31 +0000
879@@ -0,0 +1,377 @@
880+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
881+<!-- Created with Inkscape (http://www.inkscape.org/) -->
882+
883+<svg
884+ xmlns:dc="http://purl.org/dc/elements/1.1/"
885+ xmlns:cc="http://creativecommons.org/ns#"
886+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
887+ xmlns:svg="http://www.w3.org/2000/svg"
888+ xmlns="http://www.w3.org/2000/svg"
889+ xmlns:xlink="http://www.w3.org/1999/xlink"
890+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
891+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
892+ width="96"
893+ height="96"
894+ id="svg6517"
895+ version="1.1"
896+ inkscape:version="0.48.4 r9939"
897+ sodipodi:docname="icon.svg">
898+ <defs
899+ id="defs6519">
900+ <linearGradient
901+ inkscape:collect="always"
902+ xlink:href="#Background"
903+ id="linearGradient6461"
904+ gradientUnits="userSpaceOnUse"
905+ x1="0"
906+ y1="970.29498"
907+ x2="144"
908+ y2="970.29498"
909+ gradientTransform="matrix(0,-0.66666669,0.6660448,0,-866.25992,731.29077)" />
910+ <linearGradient
911+ id="Background">
912+ <stop
913+ id="stop4178"
914+ offset="0"
915+ style="stop-color:#574c4a;stop-opacity:1" />
916+ <stop
917+ id="stop4180"
918+ offset="1"
919+ style="stop-color:#80716d;stop-opacity:1" />
920+ </linearGradient>
921+ <filter
922+ style="color-interpolation-filters:sRGB;"
923+ inkscape:label="Inner Shadow"
924+ id="filter1121">
925+ <feFlood
926+ flood-opacity="0.59999999999999998"
927+ flood-color="rgb(0,0,0)"
928+ result="flood"
929+ id="feFlood1123" />
930+ <feComposite
931+ in="flood"
932+ in2="SourceGraphic"
933+ operator="out"
934+ result="composite1"
935+ id="feComposite1125" />
936+ <feGaussianBlur
937+ in="composite1"
938+ stdDeviation="1"
939+ result="blur"
940+ id="feGaussianBlur1127" />
941+ <feOffset
942+ dx="0"
943+ dy="2"
944+ result="offset"
945+ id="feOffset1129" />
946+ <feComposite
947+ in="offset"
948+ in2="SourceGraphic"
949+ operator="atop"
950+ result="composite2"
951+ id="feComposite1131" />
952+ </filter>
953+ <filter
954+ style="color-interpolation-filters:sRGB;"
955+ inkscape:label="Drop Shadow"
956+ id="filter950">
957+ <feFlood
958+ flood-opacity="0.25"
959+ flood-color="rgb(0,0,0)"
960+ result="flood"
961+ id="feFlood952" />
962+ <feComposite
963+ in="flood"
964+ in2="SourceGraphic"
965+ operator="in"
966+ result="composite1"
967+ id="feComposite954" />
968+ <feGaussianBlur
969+ in="composite1"
970+ stdDeviation="1"
971+ result="blur"
972+ id="feGaussianBlur956" />
973+ <feOffset
974+ dx="0"
975+ dy="1"
976+ result="offset"
977+ id="feOffset958" />
978+ <feComposite
979+ in="SourceGraphic"
980+ in2="offset"
981+ operator="over"
982+ result="composite2"
983+ id="feComposite960" />
984+ </filter>
985+ <clipPath
986+ clipPathUnits="userSpaceOnUse"
987+ id="clipPath873">
988+ <g
989+ transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
990+ id="g875"
991+ inkscape:label="Layer 1"
992+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
993+ <path
994+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
995+ 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"
996+ id="path877"
997+ inkscape:connector-curvature="0"
998+ sodipodi:nodetypes="sssssssss" />
999+ </g>
1000+ </clipPath>
1001+ <filter
1002+ inkscape:collect="always"
1003+ id="filter891"
1004+ inkscape:label="Badge Shadow">
1005+ <feGaussianBlur
1006+ inkscape:collect="always"
1007+ stdDeviation="0.71999962"
1008+ id="feGaussianBlur893" />
1009+ </filter>
1010+ <clipPath
1011+ clipPathUnits="userSpaceOnUse"
1012+ id="clipPath874">
1013+ <path
1014+ sodipodi:nodetypes="cccssczcssccccscc"
1015+ inkscape:connector-curvature="0"
1016+ id="path876"
1017+ 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"
1018+ 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" />
1019+ </clipPath>
1020+ <clipPath
1021+ clipPathUnits="userSpaceOnUse"
1022+ id="clipPath896">
1023+ <path
1024+ sodipodi:nodetypes="cccssczcssccccscc"
1025+ inkscape:connector-curvature="0"
1026+ id="path898"
1027+ 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"
1028+ 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" />
1029+ </clipPath>
1030+ <linearGradient
1031+ id="linearGradient3354-9">
1032+ <stop
1033+ id="stop3356-9"
1034+ offset="0"
1035+ style="stop-color:#959595;stop-opacity:1;" />
1036+ <stop
1037+ id="stop3358-9"
1038+ offset="1"
1039+ style="stop-color:#cccccc;stop-opacity:1;" />
1040+ </linearGradient>
1041+ <linearGradient
1042+ y2="-32.881535"
1043+ x2="-560.61346"
1044+ y1="-40.681377"
1045+ x1="-403.07309"
1046+ gradientUnits="userSpaceOnUse"
1047+ id="linearGradient4343"
1048+ xlink:href="#linearGradient3354-9"
1049+ inkscape:collect="always" />
1050+ <inkscape:perspective
1051+ sodipodi:type="inkscape:persp3d"
1052+ inkscape:vp_x="0 : 0.5 : 1"
1053+ inkscape:vp_y="0 : 1000 : 0"
1054+ inkscape:vp_z="1 : 0.5 : 1"
1055+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
1056+ id="perspective4393" />
1057+ <inkscape:perspective
1058+ id="perspective4383"
1059+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
1060+ inkscape:vp_z="744.09448 : 526.18109 : 1"
1061+ inkscape:vp_y="0 : 1000 : 0"
1062+ inkscape:vp_x="0 : 526.18109 : 1"
1063+ sodipodi:type="inkscape:persp3d" />
1064+ <linearGradient
1065+ inkscape:collect="always"
1066+ xlink:href="#linearGradient3354-9"
1067+ id="linearGradient3164"
1068+ gradientUnits="userSpaceOnUse"
1069+ x1="-403.07309"
1070+ y1="-40.681377"
1071+ x2="-560.61346"
1072+ y2="-32.881535" />
1073+ </defs>
1074+ <sodipodi:namedview
1075+ id="base"
1076+ pagecolor="#ffffff"
1077+ bordercolor="#666666"
1078+ borderopacity="1.0"
1079+ inkscape:pageopacity="0.0"
1080+ inkscape:pageshadow="2"
1081+ inkscape:zoom="2.6077032"
1082+ inkscape:cx="-17.529322"
1083+ inkscape:cy="74.347537"
1084+ inkscape:document-units="px"
1085+ inkscape:current-layer="layer1"
1086+ showgrid="false"
1087+ fit-margin-top="0"
1088+ fit-margin-left="0"
1089+ fit-margin-right="0"
1090+ fit-margin-bottom="0"
1091+ inkscape:window-width="1920"
1092+ inkscape:window-height="1056"
1093+ inkscape:window-x="0"
1094+ inkscape:window-y="24"
1095+ inkscape:window-maximized="1"
1096+ showborder="true"
1097+ showguides="false"
1098+ inkscape:guide-bbox="true"
1099+ inkscape:showpageshadow="false"
1100+ inkscape:snap-global="false"
1101+ inkscape:snap-bbox="true"
1102+ inkscape:bbox-paths="true"
1103+ inkscape:bbox-nodes="true"
1104+ inkscape:snap-bbox-edge-midpoints="true"
1105+ inkscape:snap-bbox-midpoints="true"
1106+ inkscape:snap-intersection-paths="true"
1107+ inkscape:object-paths="true"
1108+ inkscape:object-nodes="true"
1109+ inkscape:snap-smooth-nodes="true"
1110+ inkscape:snap-midpoints="true"
1111+ inkscape:snap-object-midpoints="false"
1112+ inkscape:snap-center="false"
1113+ inkscape:snap-grids="false"
1114+ inkscape:snap-to-guides="false">
1115+ <inkscape:grid
1116+ type="xygrid"
1117+ id="grid821" />
1118+ <sodipodi:guide
1119+ orientation="1,0"
1120+ position="16,48"
1121+ id="guide823" />
1122+ <sodipodi:guide
1123+ orientation="0,1"
1124+ position="64,80"
1125+ id="guide825" />
1126+ <sodipodi:guide
1127+ orientation="1,0"
1128+ position="80,40"
1129+ id="guide827" />
1130+ <sodipodi:guide
1131+ orientation="0,1"
1132+ position="64,16"
1133+ id="guide829" />
1134+ </sodipodi:namedview>
1135+ <metadata
1136+ id="metadata6522">
1137+ <rdf:RDF>
1138+ <cc:Work
1139+ rdf:about="">
1140+ <dc:format>image/svg+xml</dc:format>
1141+ <dc:type
1142+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
1143+ <dc:title />
1144+ </cc:Work>
1145+ </rdf:RDF>
1146+ </metadata>
1147+ <g
1148+ inkscape:label="BACKGROUND"
1149+ inkscape:groupmode="layer"
1150+ id="layer1"
1151+ transform="translate(268,-635.29076)"
1152+ style="display:inline">
1153+ <path
1154+ style="fill:url(#linearGradient6461);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)"
1155+ 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"
1156+ id="path6455"
1157+ inkscape:connector-curvature="0"
1158+ sodipodi:nodetypes="sssssssss" />
1159+ <g
1160+ id="g3160"
1161+ transform="matrix(2.5999788,0,0,2.5999788,820.10006,-1019.0997)">
1162+ <path
1163+ transform="matrix(0.13863554,0,0,0.13863554,-336.25896,668.28059)"
1164+ 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"
1165+ sodipodi:ry="8.4974337"
1166+ sodipodi:rx="120.17799"
1167+ sodipodi:cy="-32.881535"
1168+ sodipodi:cx="-460.07535"
1169+ id="path3423-1"
1170+ style="opacity:0.26353838;fill:url(#linearGradient3164);fill-opacity:1;stroke:none"
1171+ sodipodi:type="arc" />
1172+ <path
1173+ sodipodi:nodetypes="cssssssssssssscccsssssssssssssssssssssssssssssssssscccssssssssssssscccscccssc"
1174+ style="fill:#499848;fill-opacity:1"
1175+ 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"
1176+ id="path3046-2-3-0"
1177+ inkscape:connector-curvature="0" />
1178+ </g>
1179+ </g>
1180+ <g
1181+ inkscape:groupmode="layer"
1182+ id="layer3"
1183+ inkscape:label="PLACE YOUR PICTOGRAM HERE"
1184+ style="display:inline" />
1185+ <g
1186+ inkscape:groupmode="layer"
1187+ id="layer2"
1188+ inkscape:label="BADGE"
1189+ style="display:none"
1190+ sodipodi:insensitive="true">
1191+ <g
1192+ style="display:inline"
1193+ transform="translate(-340.00001,-581)"
1194+ id="g4394"
1195+ clip-path="none">
1196+ <g
1197+ id="g855">
1198+ <g
1199+ inkscape:groupmode="maskhelper"
1200+ id="g870"
1201+ clip-path="url(#clipPath873)"
1202+ style="opacity:0.6;filter:url(#filter891)">
1203+ <path
1204+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
1205+ 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"
1206+ sodipodi:ry="12"
1207+ sodipodi:rx="12"
1208+ sodipodi:cy="552.36218"
1209+ sodipodi:cx="252"
1210+ id="path844"
1211+ 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"
1212+ sodipodi:type="arc" />
1213+ </g>
1214+ <g
1215+ id="g862">
1216+ <path
1217+ sodipodi:type="arc"
1218+ 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"
1219+ id="path4398"
1220+ sodipodi:cx="252"
1221+ sodipodi:cy="552.36218"
1222+ sodipodi:rx="12"
1223+ sodipodi:ry="12"
1224+ 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"
1225+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
1226+ <path
1227+ transform="matrix(1.25,0,0,1.25,33,-100.45273)"
1228+ 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"
1229+ sodipodi:ry="12"
1230+ sodipodi:rx="12"
1231+ sodipodi:cy="552.36218"
1232+ sodipodi:cx="252"
1233+ id="path4400"
1234+ 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"
1235+ sodipodi:type="arc" />
1236+ <path
1237+ sodipodi:type="star"
1238+ 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"
1239+ id="path4459"
1240+ sodipodi:sides="5"
1241+ sodipodi:cx="666.19574"
1242+ sodipodi:cy="589.50385"
1243+ sodipodi:r1="7.2431178"
1244+ sodipodi:r2="4.3458705"
1245+ sodipodi:arg1="1.0471976"
1246+ sodipodi:arg2="1.6755161"
1247+ inkscape:flatsided="false"
1248+ inkscape:rounded="0.1"
1249+ inkscape:randomized="0"
1250+ 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"
1251+ transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
1252+ </g>
1253+ </g>
1254+ </g>
1255+ </g>
1256+</svg>
1257
1258=== modified file 'metadata.yaml'
1259--- metadata.yaml 2012-09-10 05:28:47 +0000
1260+++ metadata.yaml 2013-06-05 17:10:31 +0000
1261@@ -1,6 +1,7 @@
1262 name: gunicorn
1263 summary: Gunicorn
1264 maintainer: Patrick Hetu <patrick.hetu@gmail.com>
1265+categories: ["misc"]
1266 description: |
1267 Gunicorn or Green Unicorn is a Python WSGI HTTP Server for UNIX. It's a
1268 pre-fork worker model ported from Ruby's Unicorn project. The Gunicorn server
1269@@ -12,8 +13,3 @@
1270 interface: wsgi
1271 scope: container
1272 optional: true
1273- django-settings:
1274- interface: django
1275- scope: container
1276- optional: true
1277-
1278
1279=== modified file 'revision'
1280--- revision 2012-03-27 01:16:08 +0000
1281+++ revision 2013-06-05 17:10:31 +0000
1282@@ -1,1 +1,1 @@
1283-1
1284+3
1285
1286=== added directory 'templates'
1287=== added file 'templates/upstart.tmpl'
1288--- templates/upstart.tmpl 1970-01-01 00:00:00 +0000
1289+++ templates/upstart.tmpl 2013-06-05 17:10:31 +0000
1290@@ -0,0 +1,34 @@
1291+#--------------------------------------------------------------
1292+# This file is managed by Juju; ANY CHANGES WILL BE OVERWRITTEN
1293+#--------------------------------------------------------------
1294+
1295+description "Gunicorn daemon for the {{ project_name }} project"
1296+
1297+start on (local-filesystems and net-device-up IFACE=eth0)
1298+stop on runlevel [!12345]
1299+
1300+# If the process quits unexpectadly trigger a respawn
1301+respawn
1302+
1303+setuid {{ wsgi_user }}
1304+setgid {{ wsgi_group }}
1305+chdir {{ working_dir }}
1306+
1307+# This line can be removed and replace with the --pythonpath {{ python_path }} \
1308+# option with Gunicorn>1.17
1309+env PYTHONPATH={{ python_path }}
1310+
1311+exec gunicorn \
1312+ --name={{ project_name }} \
1313+ --workers={{ wsgi_workers }} \
1314+ --worker-class={{ wsgi_worker_class }} \
1315+ --worker-connections={{ wsgi_worker_connections }} \
1316+ --max-requests={{ wsgi_max_requests }} \
1317+ --backlog={{ wsgi_backlog }} \
1318+ --timeout={{ wsgi_timeout }} \
1319+ --keep-alive={{ wsgi_keep_alive }} \
1320+ --umask={{ wsgi_umask }} \
1321+ --bind={{ listen_ip }}:{{ port }} \
1322+ --log-file={{ wsgi_log_file }} \
1323+ --log-level={{ wsgi_log_level }} \
1324+ {{ wsgi_extra }} {{ wsgi_wsgi_file }}

Subscribers

People subscribed via source and target branches

to all changes: