Merge lp:~ev/ubuntu-ci-services-itself/better-structure-and-logging into lp:ubuntu-ci-services-itself

Proposed by Evan
Status: Needs review
Proposed branch: lp:~ev/ubuntu-ci-services-itself/better-structure-and-logging
Merge into: lp:ubuntu-ci-services-itself
Diff against target: 1763 lines (+1264/-121)
22 files modified
charms/precise/gunicorn/README.md (+52/-0)
charms/precise/gunicorn/config.yaml (+81/-0)
charms/precise/gunicorn/copyright (+17/-0)
charms/precise/gunicorn/hooks/hooks.py (+492/-0)
charms/precise/gunicorn/icon.svg (+377/-0)
charms/precise/gunicorn/metadata.yaml (+15/-0)
charms/precise/gunicorn/revision (+1/-0)
charms/precise/gunicorn/templates/upstart.tmpl (+34/-0)
charms/precise/python-django/hooks/hooks.py (+67/-46)
charms/precise/python-django/templates/settings.tmpl (+37/-6)
charms/precise/python-django/templates/urls.tmpl (+9/-0)
juju-deployer/branch-source-builder.yaml.tmpl (+0/-1)
juju-deployer/deploy.py (+2/-1)
juju-deployer/image-builder.yaml.tmpl (+0/-1)
juju-deployer/lander.yaml.tmpl (+0/-1)
juju-deployer/ppa-assigner.yaml.tmpl (+10/-3)
juju-deployer/production-only.yaml (+1/-1)
juju-deployer/test-runner.yaml.tmpl (+0/-1)
juju-deployer/ticket-system.yaml.tmpl (+12/-5)
ppa-assigner/ppa_assigner/ppa_sync.py (+5/-2)
ppa-assigner/ppa_assigner/settings.py (+30/-28)
ticket_system/ticket_system/settings.py (+22/-25)
To merge this branch: bzr merge lp:~ev/ubuntu-ci-services-itself/better-structure-and-logging
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Evan (community) Needs Resubmitting
Andy Doan (community) Approve
Chris Johnston (community) Needs Fixing
Review via email: mp+208581@code.launchpad.net

Commit message

- Teach gunicorn to properly write gunicorn access and error logs under /srv/${charm_name}/logs/gunicorn-{access,error}.log.
- Store all the Django configuration under /srv/${charm_name}/conf and keep it read-only.
- Store lp:ubuntu-ci-services-itself under /srv/${charm_name}/code and keep it read-only.
- Store variable data under /srv/${charm_name}/var and set it read-write.
- Log Django errors to /srv/${charm_name}/logs/django.log.

Description of the change

Deploy django/gunicorn with the following structure:

/srv/${charm_name}/{logs,code,conf}/

- Teach gunicorn to properly write gunicorn access and error logs under /srv/${charm_name}/logs/gunicorn-{access,error}.log.
- Store all the Django configuration under /srv/${charm_name}/conf and keep it read-only.
- Store lp:ubuntu-ci-services-itself under /srv/${charm_name}/code and keep it read-only.
- Log Django errors to /srv/${charm_name}/logs/django.log.

We can later add /srv/${charm_name}/var as needed for runtime data.

My intention is to move onto rabbitmq-worker and the other charms to provide the same structure. We should have a consistent place to look when problems occur.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:295
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~ev/ubuntu-ci-services-itself/better-structure-and-logging/+merge/208581/+edit-commit-message

http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/261/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/261/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:297
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/263/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/263/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Andy Doan (doanac) wrote :

1402 +# Ticket System
1403 +BASE_IMAGE_DEFAULT = _cfg.get('base_image', '')
1404 +SERIES_DEFAULT = _cfg.get('series', '')
1405 +MASTER_PPA_DEFAULT = _cfg.get('master_ppa', '')
1406 +
1407 +# PPA Assigner
1408 +LAUNCHPAD_PPA_USER = _cfg.get('launchpad_user', None)
1409 +LAUNCHPAD_API_BASE = _cfg.get('launchpad_api_base',
1410 + 'https://api.launchpad.net/1.0')
1411 +OAUTH_CONSUMER_KEY = _cfg.get('oauth_consumer_key', None)
1412 +OAUTH_TOKEN = _cfg.get('oauth_token', None)
1413 +OAUTH_TOKEN_SECRET = _cfg.get('oauth_token_secret', None)
1414 +OAUTH_REALM = _cfg.get('oauth_realm', 'https://api.launchpad.net/')
1415 +PPA_PATTERN = _cfg.get('ppa_pattern', r'ci-pool-\d+')

do we really want this stuff in our charm? its already in the components settings.py file and just seems like it will lead to confusion if we ever need to add/remove/change one of these settings.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:297
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/272/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/272/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Chris Johnston (cjohnston) wrote :

+ /home/ubuntu/jenkins/workspace/uci-engine-ci/uci-engine-autolanding/ppa-assigner/ppa_assigner/settings.py:16: 'glob' imported but unused

This one potentially looks valid

+ /home/ubuntu/jenkins/workspace/uci-engine-ci/uci-engine-autolanding/ppa-assigner/ppa_assigner/settings.py:150: 'from local_settings import *' used; unable to detect undefined names
+ /home/ubuntu/jenkins/workspace/uci-engine-ci/uci-engine-autolanding/ppa-assigner/ppa_assigner/settings.py:155: 'DATABASES' imported but unused

These two I think we should setup ignores for.

review: Needs Fixing
Revision history for this message
Evan (ev) wrote :

> do we really want this stuff in our charm? its already in the components
> settings.py file and just seems like it will lead to confusion if we ever
> need to add/remove/change one of these settings.

Yeah, I'm not happy about this either. Let me see what I can do.

Revision history for this message
Evan (ev) wrote :

> + > /home/ubuntu/jenkins/workspace/uci-engine-ci/uci-engine-autolanding/ppa-assigner/ppa_assigner/settings.py:16:
> 'glob' imported but unused

Fixed as r299.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:298
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/323/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/323/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:299
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/324/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/324/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:300
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/326/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/326/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Andy Doan (doanac) wrote :

Just nits - nothing worth not merging over:

django hook.py
1131 - p = 'PYTHONPATH=%s' % config_data.get('python_path', install_root)
1132 + def_path = '%s:%s' % (config_dir, code_dir)
1133 + p = 'PYTHONPATH=%s' % config_data.get('python_path', def_path)

1269 - relation_set({'python_path': install_root})
1270 + relation_set({'python_path': '%s:%s' % (config_dir, code_dir)})

I think the relation_set should use the same logic as line 1133 uses?

ppa-assigner.yaml:
1468 + python_path: /srv/ppa_django/conf:/srv/ppa_django/code/ci-utils:/srv/ppa_django/code/ppa-assigner

Is this needed now, or should the relation_set (now that its been updated) handle this setting properly.

Untested by me.

review: Approve
Revision history for this message
Andy Doan (doanac) wrote :

i'm getting a deployer issue. not sure the cause yet:

2014-03-07 11:04:45 Branching charm lp:~canonical-ci-engineering/charms/precise/ubuntu-ci-services-itself/rabbitmq-server @ /tmp/better-structure-and-logging/charms/precise/rabbitmq
Traceback (most recent call last):
  File "/tmp/better-structure-and-logging/branches/juju-deployer/deployer/cli.py", line 233, in <module>
    main()
  File "/tmp/better-structure-and-logging/branches/juju-deployer/deployer/cli.py", line 127, in main
    run()
  File "/tmp/better-structure-and-logging/branches/juju-deployer/deployer/cli.py", line 225, in run
    importer.Importer(env, deployment, options).run()
  File "/tmp/better-structure-and-logging/branches/juju-deployer/deployer/action/importer.py", line 182, in run
    self.get_charms()
  File "/tmp/better-structure-and-logging/branches/juju-deployer/deployer/action/importer.py", line 63, in get_charms
    no_local_mods=self.options.no_local_mods)
  File "/tmp/better-structure-and-logging/branches/juju-deployer/deployer/deployment.py", line 127, in fetch_charms
    if charm.is_modified():
  File "/tmp/better-structure-and-logging/branches/juju-deployer/deployer/charm.py", line 152, in is_modified
    return self.vcs.is_modified()
  File "/tmp/better-structure-and-logging/branches/juju-deployer/deployer/vcs.py", line 85, in is_modified
    tree = WorkingTree.open(self.path)
  File "/usr/lib/python2.7/dist-packages/bzrlib/workingtree.py", line 280, in open
    control = controldir.ControlDir.open(path, _unsupported=_unsupported)
  File "/usr/lib/python2.7/dist-packages/bzrlib/controldir.py", line 689, in open
    _unsupported=_unsupported)
  File "/usr/lib/python2.7/dist-packages/bzrlib/controldir.py", line 718, in open_from_transport
    find_format, transport, redirected)
  File "/usr/lib/python2.7/dist-packages/bzrlib/transport/__init__.py", line 1719, in do_catching_redirections
    return action(transport)
  File "/usr/lib/python2.7/dist-packages/bzrlib/controldir.py", line 706, in find_format
    probers=probers)
  File "/usr/lib/python2.7/dist-packages/bzrlib/controldir.py", line 1155, in find_format
    raise errors.NotBranchError(path=transport.base)
bzrlib.errors.NotBranchError: Not a branch: "/tmp/better-structure-and-logging/charms/precise/gunicorn/".

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:301
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/330/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/330/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:302
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/331/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/331/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:303
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/335/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/335/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Evan (ev) wrote :

Fixed the deployer issue as r303.

Revision history for this message
Andy Doan (doanac) wrote :

thanks for the updates

review: Approve
Revision history for this message
Andy Doan (doanac) wrote :

the django charms are broke now during the postgres relation join:

2014-03-07 20:55:55 INFO juju-log pgsql:28: Running pgsql-relation-changed hook
2014-03-07 20:55:56 DEBUG juju-log pgsql:28: PYTHONPATH=/srv/ts_django/conf:/srv/ts_django/code/ci-utils:/srv/ts_django/code/ticket_system /usr/bin/django-admin syncdb --noinput --settings=ticket_system.settings
2014-03-07 20:55:57 ERROR juju-log pgsql:28: status=1, output=usage: django-admin [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: django-admin --help [cmd1 cmd2 ...]
   or: django-admin --help-commands
   or: django-admin cmd --help

error: invalid command 'syncdb'

Revision history for this message
Andy Doan (doanac) wrote :

Another bug i'm seeing is with unit_config:

  Unable to use unit_config(../unit_config), defaulting values

I think our settings.py files don't know how to locate this under its new location.

Revision history for this message
Evan (ev) wrote :

Man, this is just fail all over the place. Doing a deploy now to track
these down.

Revision history for this message
Chris Johnston (cjohnston) wrote :
Download full text (101.6 KiB)

The attempt to merge lp:~ev/ubuntu-ci-services-itself/better-structure-and-logging into lp:ubuntu-ci-services-itself failed. Below is the output from the failed tests.

New python executable in /tmp/tmp.9PczZfUOrt/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.
== Testing ci-utils ....
Unpacking /tmp/tarmac/branch.KOOFkz/.deps/Babel-1.3.tar.gz
  Running setup.py egg_info for package from file:///tmp/tarmac/branch.KOOFkz/.deps/Babel-1.3.tar.gz

    warning: no previously-included files matching '*' found under directory 'docs/_build'
    warning: no previously-included files matching '*.pyc' found under directory 'tests'
    warning: no previously-included files matching '*.pyo' found under directory 'tests'
Installing collected packages: Babel
  Running setup.py install for Babel

    warning: no previously-included files matching '*' found under directory 'docs/_build'
    warning: no previously-included files matching '*.pyc' found under directory 'tests'
    warning: no previously-included files matching '*.pyo' found under directory 'tests'
    Installing pybabel script to /tmp/tmp.9PczZfUOrt/bin
Successfully installed Babel
Cleaning up...
Using saved parent location: bzr+ssh://bazaar.launchpad.net/~canonical-ci-engineering/ubuntu-ci-services-itself/deps/
No revisions or tags to pull.
Unpacking /tmp/tarmac/branch.KOOFkz/.deps/pbr-0.6.tar.gz
  Running setup.py egg_info for package from file:///tmp/tarmac/branch.KOOFkz/.deps/pbr-0.6.tar.gz
    [pbr] Processing SOURCES.txt
    warning: LocalManifestMaker: standard file '-c' not found

    warning: no previously-included files found matching '.gitignore'
    warning: no previously-included files found matching '.gitreview'
    warning: no previously-included files matching '*.pyc' found anywhere in distribution
    warning: no previously-included files found matching '.gitignore'
    warning: no previously-included files found matching '.gitreview'
    warning: no previously-included files matching '*.pyc' found anywhere in distribution
Installing collected packages: pbr
  Running setup.py install for pbr
    [pbr] Reusing existing SOURCES.txt
Successfully installed pbr
Cleaning up...
Using saved parent location: bzr+ssh://bazaar.launchpad.net/~canonical-ci-engineering/ubuntu-ci-services-itself/deps/
No revisions or tags to pull.
Unpacking /tmp/tarmac/branch.KOOFkz/.deps/iso8601-0.1.8.tar.gz
  Running setup.py egg_info for package from file:///tmp/tarmac/branch.KOOFkz/.deps/iso8601-0.1.8.tar.gz

Installing collected packages: iso8601
  Running setup.py install for iso8601

Successfully installed iso8601
Cleaning up...
Using saved parent location: bzr+ssh://bazaar.launchpad.net/~canonical-ci-engineering/ubuntu-ci-services-itself/deps/
No revisions or tags to pull.
Unpacking /tmp/tarmac/branch.KOOFkz/.deps/prettytable-0.7.2.zip
  Running setup.py egg_info for package from file:///tmp/tarmac/branch.KOOFkz/.deps/prettytable-0.7.2.zip

Installing collected package...

304. By Evan

Fix setting CONFDIR and LOGDIR.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:304
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/338/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/338/rebuild

review: Needs Fixing (continuous-integration)
305. By Evan

Safely write the lpcreds file. Put it under a writable directory.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:305
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/341/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/341/rebuild

review: Needs Fixing (continuous-integration)
306. By Evan

Actually setting the VARDIR would help.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:306
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/342/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/342/rebuild

review: Needs Fixing (continuous-integration)
307. By Evan

Missing import.

308. By Evan

Include timestamps in django log messages.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:307
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/343/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/343/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:308
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/344/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/344/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Evan (ev) wrote :

Finally!

  % curl http://15.125.108.29:8080/api/v1/status/\?format\=json
[{"label": "launchpad configured", "status": "okay", "value": true}, {"label": "total ppas", "status": "okay", "value": 2}, {"label": "available ppas", "status": "okay", "value": 2}]

review: Needs Resubmitting
Revision history for this message
Andy Doan (doanac) wrote :

This is close, but I just noticed the ppa-cleaner isn't running:

Unable to use unit_config(/srv/ppa_django/code/ppa-assigner/../unit_config), defaulting values
ERROR: oauth settings are required by this command:
  OAUTH_CONSUMER_KEY
  OAUTH_TOKEN
  OAUTH_TOKEN_SECRET

I think this is because this type of deployment requires CONFDIR to get get set. Is CONFDIR a gunicorn specific thing or something, I'm not sure where that gets set at?

Revision history for this message
Evan (ev) wrote :

On 10 March 2014 03:06, Andy Doan <email address hidden> wrote:
> This is close, but I just noticed the ppa-cleaner isn't running:
>
> Unable to use unit_config(/srv/ppa_django/code/ppa-assigner/../unit_config), defaulting values
> ERROR: oauth settings are required by this command:
> OAUTH_CONSUMER_KEY
> OAUTH_TOKEN
> OAUTH_TOKEN_SECRET
>
> I think this is because this type of deployment requires CONFDIR to get get set. Is CONFDIR a gunicorn specific thing or something, I'm not sure where that gets set at?

CONFDIR is something I invented to let us set a variable configuration
path rather than having it hardcoded everywhere, which would've made
our charms not mergeable with trunk. I think we'll need to teach the
upstart charm to support custom stanzas or specifically the
environment variables to set. We can then tell the ppa-cleaner to try
looking under CONFIDR for the unit_config first.

My rationale is this: we shouldn't be deploying configuration on top
of code. That's a recipe for disaster.

Revision history for this message
Andy Doan (doanac) wrote :

> CONFDIR is something I invented to let us set a variable configuration
> path rather than having it hardcoded everywhere, which would've made
> our charms not mergeable with trunk. I think we'll need to teach the
> upstart charm to support custom stanzas or specifically the
> environment variables to set. We can then tell the ppa-cleaner to try
> looking under CONFIDR for the unit_config first.
>
> My rationale is this: we shouldn't be deploying configuration on top
> of code. That's a recipe for disaster.

Another thing we could do (take your pick) would be to add a little more
logic to our settings.py module like:

try:
     # This should get set by local_settings.
     path = os.path.join(CONFDIR, '../unit_config')
except NameError:
     path = os.path.join(BASEDIR, '../unit_config')
     if not os.path.exists(path):
         print('hmm, maybe its one level up...')
         path = os.path.join(BASEDIR, '../../conf/unit_config')

or just throw in the towel and do:

  subprocess.check_out(['find', '/', '-name', unit_config])

:)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:308
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/402/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/402/rebuild

review: Needs Fixing (continuous-integration)

Unmerged revisions

308. By Evan

Include timestamps in django log messages.

307. By Evan

Missing import.

306. By Evan

Actually setting the VARDIR would help.

305. By Evan

Safely write the lpcreds file. Put it under a writable directory.

304. By Evan

Fix setting CONFDIR and LOGDIR.

303. By Evan

How'd this sneak in here. Gunicorn lives in the branch for now.

302. By Evan

Merge with trunk.

301. By Evan

Set the PYTHONPATH appropriately on the relation.

300. By Evan

Move the parts specific to the PPA Assigner and Ticket System back into those components. This code need not live in the python-django charm so long as we can pass a reference to the config file root.

299. By Evan

Unused import. Thanks Chris.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'charms/precise/gunicorn'
2=== added file 'charms/precise/gunicorn/README.md'
3--- charms/precise/gunicorn/README.md 1970-01-01 00:00:00 +0000
4+++ charms/precise/gunicorn/README.md 2014-03-08 15:31:44 +0000
5@@ -0,0 +1,52 @@
6+# Gunicorn
7+
8+Author:
9+
10+- Patrick Hetu <patrick@koumbit.org>
11+
12+# How to configure the charm
13+
14+To deploy a charm with this subordinate it must minimaly:
15+
16+ 1. Provide the wsgi interface.
17+ 1. Set the `working_dir` relation variable in the wsgi hook.
18+
19+The configuration of Gunicorn will use the variable pass by
20+the relation hook first. If there are not define it will
21+fallback to the global configuration of the charm.
22+
23+# Example deployment
24+
25+ 1. Deployment with python-moinmoin for example::
26+
27+ juju bootstrap
28+ juju deploy --config mywiki_with_wsgi_settings.yaml python-moinmoin
29+ juju deploy gunicorn
30+ juju add-relation gunicorn python-moinmoin
31+ juju expose python-moinmoin
32+
33+ 1. Accessing your new wiki should be ready at::
34+
35+ http://<machine-addr>:8080/
36+
37+ To find out the public address of gunicorn/python-moinmoin, look for it in
38+ the output of the `juju status` command.
39+ I recommend using a reverse proxy like Nginx in front of Gunicorn.
40+
41+# Changelog
42+
43+3:
44+
45+Notable changes:
46+
47+- Rewrite the charm using python instead of BASH scripts
48+- add listen_ip configuration variable
49+
50+Backwards incompatible changes:
51+
52+- Remove the Django mode since Gunicorn is not recommending it anymore.
53+- Use Upstart to manage daemons
54+- no start/stop hook anymore use related charms instead.
55+- no configuration change directly on the charm anymore, use related charms instead.
56+- no access logging by default
57+- exposing a port must now be done in the linked charm instead of this one
58\ No newline at end of file
59
60=== added file 'charms/precise/gunicorn/config.yaml'
61--- charms/precise/gunicorn/config.yaml 1970-01-01 00:00:00 +0000
62+++ charms/precise/gunicorn/config.yaml 2014-03-08 15:31:44 +0000
63@@ -0,0 +1,81 @@
64+options:
65+ wsgi_wsgi_file:
66+ type: string
67+ default: "wsgi"
68+ description: "The name of the WSGI application."
69+ wsgi_workers:
70+ type: int
71+ default: 0
72+ description: "The number of worker process for handling requests. 0 for count(cpu) + 1"
73+ wsgi_worker_class:
74+ type: string
75+ default: "sync"
76+ description: "Gunicorn workers type. Can be: sync, eventlet, gevent, tornado"
77+ wsgi_worker_connections:
78+ type: int
79+ default: 1000
80+ description: "The maximum number of simultaneous clients."
81+ wsgi_max_requests:
82+ type: int
83+ default: 0
84+ description: "The maximum number of requests a worker will process before restarting."
85+ wsgi_backlog:
86+ type: int
87+ default: 2048
88+ description: "The maximum number of pending connections."
89+ wsgi_timeout:
90+ type: int
91+ default: 30
92+ description: "Timeout of a request in seconds."
93+ wsgi_keep_alive:
94+ type: int
95+ default: 2
96+ description: "Keep alive time in seconds."
97+ wsgi_umask:
98+ type: string
99+ default: "0"
100+ description: "A bit mask for the file mode on files written by Gunicorn. The number 0 means Python guesses the base. Note that this affects unix socket permissions."
101+ wsgi_user:
102+ type: string
103+ default: "www-data"
104+ description: "Switch worker processes to run as this user. User id (as an int) or the name."
105+ wsgi_group:
106+ type: string
107+ default: "www-data"
108+ description: "Switch worker process to run as this group. A valid group id (as an int) or the name."
109+ wsgi_log_file:
110+ type: string
111+ default: "-"
112+ description: "The log file to write to. If empty the logs would be handle by upstart."
113+ wsgi_log_level:
114+ type: string
115+ default: "info"
116+ description: "The granularity of Error log outputs."
117+ wsgi_access_logfile:
118+ type: string
119+ default: ""
120+ description: "The Access log file to write to."
121+ wsgi_access_logformat:
122+ type: string
123+ default: ""
124+ description: "The Access log format. Don't forget to escape all quotes and round brackets."
125+ wsgi_extra:
126+ type: string
127+ default: ""
128+ description: "Space separated extra settings. For example: --debug"
129+ wsgi_timestamp:
130+ type: string
131+ default: ""
132+ description: "The variable to modify to trigger Gunicorn reload."
133+ python_path:
134+ type: string
135+ default: ""
136+ description: "Set an additionnal PYTHONPATH to the project."
137+ listen_ip:
138+ type: string
139+ default: "0.0.0.0"
140+ description: "IP adresses that Gunicorn will listen on. By default we listen on all of them."
141+ port:
142+ type: int
143+ default: 8080
144+ description: "Port the application will be listenning."
145
146=== added file 'charms/precise/gunicorn/copyright'
147--- charms/precise/gunicorn/copyright 1970-01-01 00:00:00 +0000
148+++ charms/precise/gunicorn/copyright 2014-03-08 15:31:44 +0000
149@@ -0,0 +1,17 @@
150+Format: http://dep.debian.net/deps/dep5/
151+
152+Files: *
153+Copyright: Copyright 2011, Patrick Hetu <patrick@koumbit.org>, All Rights Reserved.
154+License: GPL-3
155+ This program is free software: you can redistribute it and/or modify
156+ it under the terms of the GNU General Public License as published by
157+ the Free Software Foundation, either version 3 of the License, or
158+ (at your option) any later version.
159+ .
160+ This program is distributed in the hope that it will be useful,
161+ but WITHOUT ANY WARRANTY; without even the implied warranty of
162+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
163+ GNU General Public License for more details.
164+ .
165+ You should have received a copy of the GNU General Public License
166+ along with this program. If not, see <http://www.gnu.org/licenses/>.
167
168=== added directory 'charms/precise/gunicorn/hooks'
169=== added file 'charms/precise/gunicorn/hooks/hooks.py'
170--- charms/precise/gunicorn/hooks/hooks.py 1970-01-01 00:00:00 +0000
171+++ charms/precise/gunicorn/hooks/hooks.py 2014-03-08 15:31:44 +0000
172@@ -0,0 +1,492 @@
173+#!/usr/bin/env python
174+# vim: et ai ts=4 sw=4:
175+
176+import json
177+import os
178+import re
179+import subprocess
180+import sys
181+import time
182+from pwd import getpwnam
183+from grp import getgrnam
184+
185+CHARM_PACKAGES = ["gunicorn"]
186+
187+INJECTED_WARNING = """
188+#------------------------------------------------------------------------------
189+# The following is the import code for the settings directory injected by Juju
190+#------------------------------------------------------------------------------
191+"""
192+
193+
194+###############################################################################
195+# Supporting functions
196+###############################################################################
197+MSG_CRITICAL = "CRITICAL"
198+MSG_DEBUG = "DEBUG"
199+MSG_INFO = "INFO"
200+MSG_ERROR = "ERROR"
201+MSG_WARNING = "WARNING"
202+
203+
204+def juju_log(level, msg):
205+ subprocess.call(['juju-log', '-l', level, msg])
206+
207+#------------------------------------------------------------------------------
208+# run: Run a command, return the output
209+#------------------------------------------------------------------------------
210+def run(command, exit_on_error=True, cwd=None):
211+ try:
212+ juju_log(MSG_DEBUG, command)
213+ return subprocess.check_output(
214+ command, stderr=subprocess.STDOUT, shell=True, cwd=cwd)
215+ except subprocess.CalledProcessError, e:
216+ juju_log(MSG_ERROR, "status=%d, output=%s" % (e.returncode, e.output))
217+ if exit_on_error:
218+ sys.exit(e.returncode)
219+ else:
220+ raise
221+
222+
223+#------------------------------------------------------------------------------
224+# install_file: install a file resource. overwites existing files.
225+#------------------------------------------------------------------------------
226+def install_file(contents, dest, owner="root", group="root", mode=0600):
227+ uid = getpwnam(owner)[2]
228+ gid = getgrnam(group)[2]
229+ dest_fd = os.open(dest, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
230+ os.fchown(dest_fd, uid, gid)
231+ with os.fdopen(dest_fd, 'w') as destfile:
232+ destfile.write(str(contents))
233+
234+
235+#------------------------------------------------------------------------------
236+# install_dir: create a directory
237+#------------------------------------------------------------------------------
238+def install_dir(dirname, owner="root", group="root", mode=0700):
239+ command = \
240+ '/usr/bin/install -o {} -g {} -m {} -d {}'.format(owner, group, oct(mode),
241+ dirname)
242+ return run(command)
243+
244+#------------------------------------------------------------------------------
245+# config_get: Returns a dictionary containing all of the config information
246+# Optional parameter: scope
247+# scope: limits the scope of the returned configuration to the
248+# desired config item.
249+#------------------------------------------------------------------------------
250+def config_get(scope=None):
251+ try:
252+ config_cmd_line = ['config-get']
253+ if scope is not None:
254+ config_cmd_line.append(scope)
255+ config_cmd_line.append('--format=json')
256+ config_data = json.loads(subprocess.check_output(config_cmd_line))
257+ except:
258+ config_data = None
259+ finally:
260+ return(config_data)
261+
262+
263+#------------------------------------------------------------------------------
264+# relation_json: Returns json-formatted relation data
265+# Optional parameters: scope, relation_id
266+# scope: limits the scope of the returned data to the
267+# desired item.
268+# unit_name: limits the data ( and optionally the scope )
269+# to the specified unit
270+# relation_id: specify relation id for out of context usage.
271+#------------------------------------------------------------------------------
272+def relation_json(scope=None, unit_name=None, relation_id=None):
273+ command = ['relation-get', '--format=json']
274+ if relation_id is not None:
275+ command.extend(('-r', relation_id))
276+ if scope is not None:
277+ command.append(scope)
278+ else:
279+ command.append('-')
280+ if unit_name is not None:
281+ command.append(unit_name)
282+ output = subprocess.check_output(command, stderr=subprocess.STDOUT)
283+ return output or None
284+
285+
286+#------------------------------------------------------------------------------
287+# relation_get: Returns a dictionary containing the relation information
288+# Optional parameters: scope, relation_id
289+# scope: limits the scope of the returned data to the
290+# desired item.
291+# unit_name: limits the data ( and optionally the scope )
292+# to the specified unit
293+#------------------------------------------------------------------------------
294+def relation_get(scope=None, unit_name=None, relation_id=None):
295+ j = relation_json(scope, unit_name, relation_id)
296+ if j:
297+ return json.loads(j)
298+ else:
299+ return None
300+
301+
302+def relation_set(keyvalues, relation_id=None):
303+ args = []
304+ if relation_id:
305+ args.extend(['-r', relation_id])
306+ args.extend(["{}='{}'".format(k, v or '') for k, v in keyvalues.items()])
307+ run("relation-set {}".format(' '.join(args)))
308+
309+ ## Posting json to relation-set doesn't seem to work as documented?
310+ ## Bug #1116179
311+ ##
312+ ## cmd = ['relation-set']
313+ ## if relation_id:
314+ ## cmd.extend(['-r', relation_id])
315+ ## p = Popen(
316+ ## cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
317+ ## stderr=subprocess.PIPE)
318+ ## (out, err) = p.communicate(json.dumps(keyvalues))
319+ ## if p.returncode:
320+ ## juju_log(MSG_ERROR, err)
321+ ## sys.exit(1)
322+ ## juju_log(MSG_DEBUG, "relation-set {}".format(repr(keyvalues)))
323+
324+
325+def relation_list(relation_id=None):
326+ """Return the list of units participating in the relation."""
327+ if relation_id is None:
328+ relation_id = os.environ['JUJU_RELATION_ID']
329+ cmd = ['relation-list', '--format=json', '-r', relation_id]
330+ json_units = subprocess.check_output(cmd).strip()
331+ if json_units:
332+ return json.loads(subprocess.check_output(cmd))
333+ return []
334+
335+
336+#------------------------------------------------------------------------------
337+# relation_ids: Returns a list of relation ids
338+# optional parameters: relation_type
339+# relation_type: return relations only of this type
340+#------------------------------------------------------------------------------
341+def relation_ids(relation_types=('db',)):
342+ # accept strings or iterators
343+ if isinstance(relation_types, basestring):
344+ reltypes = [relation_types, ]
345+ else:
346+ reltypes = relation_types
347+ relids = []
348+ for reltype in reltypes:
349+ relid_cmd_line = ['relation-ids', '--format=json', reltype]
350+ json_relids = subprocess.check_output(relid_cmd_line).strip()
351+ if json_relids:
352+ relids.extend(json.loads(json_relids))
353+ return relids
354+
355+
356+#------------------------------------------------------------------------------
357+# relation_get_all: Returns a dictionary containing the relation information
358+# optional parameters: relation_type
359+# relation_type: limits the scope of the returned data to the
360+# desired item.
361+#------------------------------------------------------------------------------
362+def relation_get_all(*args, **kwargs):
363+ relation_data = []
364+ relids = relation_ids(*args, **kwargs)
365+ for relid in relids:
366+ units_cmd_line = ['relation-list', '--format=json', '-r', relid]
367+ json_units = subprocess.check_output(units_cmd_line).strip()
368+ if json_units:
369+ for unit in json.loads(json_units):
370+ unit_data = \
371+ json.loads(relation_json(relation_id=relid,
372+ unit_name=unit))
373+ for key in unit_data:
374+ if key.endswith('-list'):
375+ unit_data[key] = unit_data[key].split()
376+ unit_data['relation-id'] = relid
377+ unit_data['unit'] = unit
378+ relation_data.append(unit_data)
379+ return relation_data
380+
381+def apt_get_update():
382+ cmd_line = ['apt-get', 'update']
383+ return(subprocess.call(cmd_line))
384+
385+
386+#------------------------------------------------------------------------------
387+# apt_get_install( packages ): Installs package(s)
388+#------------------------------------------------------------------------------
389+def apt_get_install(packages=None):
390+ if packages is None:
391+ return(False)
392+ cmd_line = ['apt-get', '-y', 'install', '-qq']
393+ if isinstance(packages, list):
394+ cmd_line.extend(packages)
395+ else:
396+ cmd_line.append(packages)
397+ return(subprocess.call(cmd_line))
398+
399+
400+#------------------------------------------------------------------------------
401+# pip_install( package ): Installs a python package
402+#------------------------------------------------------------------------------
403+def pip_install(packages=None, upgrade=False):
404+ cmd_line = ['pip', 'install']
405+ if packages is None:
406+ return(False)
407+ if upgrade:
408+ cmd_line.append('-u')
409+ if packages.startswith('svn+') or packages.startswith('git+') or \
410+ packages.startswith('hg+') or packages.startswith('bzr+'):
411+ cmd_line.append('-e')
412+ cmd_line.append(packages)
413+ return run(cmd_line)
414+
415+#------------------------------------------------------------------------------
416+# pip_install_req( path ): Installs a requirements file
417+#------------------------------------------------------------------------------
418+def pip_install_req(path=None, upgrade=False):
419+ cmd_line = ['pip', 'install']
420+ if path is None:
421+ return(False)
422+ if upgrade:
423+ cmd_line.append('-u')
424+ cmd_line.append('-r')
425+ cmd_line.append(path)
426+ cwd = os.path.dirname(path)
427+ return run(cmd_line)
428+
429+#------------------------------------------------------------------------------
430+# open_port: Convenience function to open a port in juju to
431+# expose a service
432+#------------------------------------------------------------------------------
433+def open_port(port=None, protocol="TCP"):
434+ if port is None:
435+ return(None)
436+ return(subprocess.call(['open-port', "%d/%s" %
437+ (int(port), protocol)]))
438+
439+
440+#------------------------------------------------------------------------------
441+# close_port: Convenience function to close a port in juju to
442+# unexpose a service
443+#------------------------------------------------------------------------------
444+def close_port(port=None, protocol="TCP"):
445+ if port is None:
446+ return(None)
447+ return(subprocess.call(['close-port', "%d/%s" %
448+ (int(port), protocol)]))
449+
450+
451+#------------------------------------------------------------------------------
452+# update_service_ports: Convenience function that evaluate the old and new
453+# service ports to decide which ports need to be
454+# opened and which to close
455+#------------------------------------------------------------------------------
456+def update_service_port(old_service_port=None, new_service_port=None):
457+ if old_service_port is None or new_service_port is None:
458+ return(None)
459+ if new_service_port != old_service_port:
460+ close_port(old_service_port)
461+ open_port(new_service_port)
462+
463+#
464+# Utils
465+#
466+
467+def install_or_append(contents, dest, owner="root", group="root", mode=0600):
468+ if os.path.exists(dest):
469+ uid = getpwnam(owner)[2]
470+ gid = getgrnam(group)[2]
471+ dest_fd = os.open(dest, os.O_APPEND, mode)
472+ os.fchown(dest_fd, uid, gid)
473+ with os.fdopen(dest_fd, 'a') as destfile:
474+ destfile.write(str(contents))
475+ else:
476+ install_file(contents, dest, owner, group, mode)
477+
478+def token_sql_safe(value):
479+ # Only allow alphanumeric + underscore in database identifiers
480+ if re.search('[^A-Za-z0-9_]', value):
481+ return False
482+ return True
483+
484+def sanitize(s):
485+ s = s.replace(':', '_')
486+ s = s.replace('-', '_')
487+ s = s.replace('/', '_')
488+ s = s.replace('"', '_')
489+ s = s.replace("'", '_')
490+ return s
491+
492+def user_name(relid, remote_unit, admin=False, schema=False):
493+ components = [sanitize(relid), sanitize(remote_unit)]
494+ if admin:
495+ components.append("admin")
496+ elif schema:
497+ components.append("schema")
498+ return "_".join(components)
499+
500+def get_relation_host():
501+ remote_host = run("relation-get ip")
502+ if not remote_host:
503+ # remote unit $JUJU_REMOTE_UNIT uses deprecated 'ip=' component of
504+ # interface.
505+ remote_host = run("relation-get private-address")
506+ return remote_host
507+
508+
509+def get_unit_host():
510+ this_host = run("unit-get private-address")
511+ return this_host.strip()
512+
513+def process_template(template_name, template_vars, destination):
514+ # --- exported service configuration file
515+ from jinja2 import Environment, FileSystemLoader
516+ template_env = Environment(
517+ loader=FileSystemLoader(os.path.join(os.environ['CHARM_DIR'],
518+ 'templates')))
519+
520+ template = \
521+ template_env.get_template(template_name).render(template_vars)
522+
523+ with open(destination, 'w') as inject_file:
524+ inject_file.write(str(template))
525+
526+def append_template(template_name, template_vars, path, try_append=False):
527+
528+ # --- exported service configuration file
529+ from jinja2 import Environment, FileSystemLoader
530+ template_env = Environment(
531+ loader=FileSystemLoader(os.path.join(os.environ['CHARM_DIR'],
532+ 'templates')))
533+
534+ template = \
535+ template_env.get_template(template_name).render(template_vars)
536+
537+ append = False
538+ if os.path.exists(path):
539+ with open(path, 'r') as inject_file:
540+ if not str(template) in inject_file:
541+ append = True
542+ else:
543+ append = True
544+
545+ if append == True:
546+ with open(path, 'a') as inject_file:
547+ inject_file.write(INJECTED_WARNING)
548+ inject_file.write(str(template))
549+
550+
551+
552+###############################################################################
553+# Hook functions
554+###############################################################################
555+def install():
556+
557+ for retry in xrange(0,24):
558+ if apt_get_install(CHARM_PACKAGES):
559+ time.sleep(10)
560+ else:
561+ break
562+
563+def upgrade():
564+
565+ apt_get_update()
566+ for retry in xrange(0,24):
567+ if apt_get_install(CHARM_PACKAGES):
568+ time.sleep(10)
569+ else:
570+ break
571+
572+def wsgi_file_relation_joined_changed():
573+ wsgi_config = config_data
574+ relation_id = os.environ['JUJU_RELATION_ID']
575+ juju_log(MSG_INFO, "JUJU_RELATION_ID: %s".format(relation_id))
576+
577+ remote_unit_name = sanitize(os.environ['JUJU_REMOTE_UNIT'].split('/')[0])
578+ juju_log(MSG_INFO, "JUJU_REMOTE_UNIT: %s".format(remote_unit_name))
579+ wsgi_config['unit_name'] = remote_unit_name
580+
581+ project_conf = '/etc/init/%s.conf' % remote_unit_name
582+
583+ working_dir = relation_get('working_dir')
584+ if not working_dir:
585+ return
586+
587+ wsgi_config['working_dir'] = working_dir
588+ wsgi_config['project_name'] = remote_unit_name
589+
590+ for v in wsgi_config.keys():
591+ if v.startswith('wsgi_') or v in ['python_path', 'listen_ip', 'port']:
592+ upstream_value = relation_get(v)
593+ if upstream_value:
594+ wsgi_config[v] = upstream_value
595+
596+ if wsgi_config['wsgi_worker_class'] == 'eventlet':
597+ apt_get_install('python-eventlet')
598+ elif wsgi_config['wsgi_worker_class'] == 'gevent':
599+ apt_get_install('python-gevent')
600+ elif wsgi_config['wsgi_worker_class'] == 'tornado':
601+ apt_get_install('python-tornado')
602+
603+ if wsgi_config['wsgi_workers'] == 0:
604+ res = run('python -c "import multiprocessing ; print(multiprocessing.cpu_count())"')
605+ wsgi_config['wsgi_workers'] = int(res) + 1
606+
607+ if wsgi_config['wsgi_access_logfile']:
608+ wsgi_config['wsgi_extra'] = " ".join([
609+ wsgi_config['wsgi_extra'],
610+ '--access-logfile=%s' % wsgi_config['wsgi_access_logfile'],
611+ '--access-logformat=\'%s\'' % wsgi_config['wsgi_access_logformat']
612+ ])
613+
614+ wsgi_config['wsgi_wsgi_file'] = wsgi_config['wsgi_wsgi_file']
615+ wsgi_config['wsgi_log_file'] = wsgi_config['wsgi_log_file']
616+
617+ process_template('upstart.tmpl', wsgi_config, project_conf)
618+
619+
620+ # We need this because when the contained charm configuration or code changed
621+ # Gunicorn needs to restart to run the new code.
622+ run("service %s restart || service %s start" % (remote_unit_name, remote_unit_name))
623+
624+
625+def wsgi_file_relation_broken():
626+ remote_unit_name = sanitize(os.environ['JUJU_REMOTE_UNIT'].split('/')[0])
627+
628+ run('service %s stop' % remote_unit_name)
629+ run('rm /etc/init/%s.conf' % remote_unit_name)
630+
631+
632+###############################################################################
633+# Global variables
634+###############################################################################
635+config_data = config_get()
636+juju_log(MSG_DEBUG, "got config: %s" % str(config_data))
637+
638+unit_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
639+
640+hook_name = os.path.basename(sys.argv[0])
641+
642+###############################################################################
643+# Main section
644+###############################################################################
645+def main():
646+ juju_log(MSG_INFO, "Running {} hook".format(hook_name))
647+ if hook_name == "install":
648+ install()
649+
650+ elif hook_name == "upgrade-charm":
651+ upgrade()
652+
653+ elif hook_name in ["wsgi-file-relation-joined", "wsgi-file-relation-changed"]:
654+ wsgi_file_relation_joined_changed()
655+
656+ elif hook_name == "wsgi-file-relation-broken":
657+ wsgi_file_relation_broken()
658+
659+ else:
660+ print "Unknown hook {}".format(hook_name)
661+ raise SystemExit(1)
662+
663+if __name__ == '__main__':
664+ raise SystemExit(main())
665
666=== added symlink 'charms/precise/gunicorn/hooks/install'
667=== target is u'hooks.py'
668=== added symlink 'charms/precise/gunicorn/hooks/upgrade-charm'
669=== target is u'hooks.py'
670=== added symlink 'charms/precise/gunicorn/hooks/wsgi-file-relation-broken'
671=== target is u'hooks.py'
672=== added symlink 'charms/precise/gunicorn/hooks/wsgi-file-relation-changed'
673=== target is u'hooks.py'
674=== added symlink 'charms/precise/gunicorn/hooks/wsgi-file-relation-joined'
675=== target is u'hooks.py'
676=== added file 'charms/precise/gunicorn/icon.svg'
677--- charms/precise/gunicorn/icon.svg 1970-01-01 00:00:00 +0000
678+++ charms/precise/gunicorn/icon.svg 2014-03-08 15:31:44 +0000
679@@ -0,0 +1,377 @@
680+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
681+<!-- Created with Inkscape (http://www.inkscape.org/) -->
682+
683+<svg
684+ xmlns:dc="http://purl.org/dc/elements/1.1/"
685+ xmlns:cc="http://creativecommons.org/ns#"
686+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
687+ xmlns:svg="http://www.w3.org/2000/svg"
688+ xmlns="http://www.w3.org/2000/svg"
689+ xmlns:xlink="http://www.w3.org/1999/xlink"
690+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
691+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
692+ width="96"
693+ height="96"
694+ id="svg6517"
695+ version="1.1"
696+ inkscape:version="0.48.4 r9939"
697+ sodipodi:docname="icon.svg">
698+ <defs
699+ id="defs6519">
700+ <linearGradient
701+ inkscape:collect="always"
702+ xlink:href="#Background"
703+ id="linearGradient6461"
704+ gradientUnits="userSpaceOnUse"
705+ x1="0"
706+ y1="970.29498"
707+ x2="144"
708+ y2="970.29498"
709+ gradientTransform="matrix(0,-0.66666669,0.6660448,0,-866.25992,731.29077)" />
710+ <linearGradient
711+ id="Background">
712+ <stop
713+ id="stop4178"
714+ offset="0"
715+ style="stop-color:#574c4a;stop-opacity:1" />
716+ <stop
717+ id="stop4180"
718+ offset="1"
719+ style="stop-color:#80716d;stop-opacity:1" />
720+ </linearGradient>
721+ <filter
722+ style="color-interpolation-filters:sRGB;"
723+ inkscape:label="Inner Shadow"
724+ id="filter1121">
725+ <feFlood
726+ flood-opacity="0.59999999999999998"
727+ flood-color="rgb(0,0,0)"
728+ result="flood"
729+ id="feFlood1123" />
730+ <feComposite
731+ in="flood"
732+ in2="SourceGraphic"
733+ operator="out"
734+ result="composite1"
735+ id="feComposite1125" />
736+ <feGaussianBlur
737+ in="composite1"
738+ stdDeviation="1"
739+ result="blur"
740+ id="feGaussianBlur1127" />
741+ <feOffset
742+ dx="0"
743+ dy="2"
744+ result="offset"
745+ id="feOffset1129" />
746+ <feComposite
747+ in="offset"
748+ in2="SourceGraphic"
749+ operator="atop"
750+ result="composite2"
751+ id="feComposite1131" />
752+ </filter>
753+ <filter
754+ style="color-interpolation-filters:sRGB;"
755+ inkscape:label="Drop Shadow"
756+ id="filter950">
757+ <feFlood
758+ flood-opacity="0.25"
759+ flood-color="rgb(0,0,0)"
760+ result="flood"
761+ id="feFlood952" />
762+ <feComposite
763+ in="flood"
764+ in2="SourceGraphic"
765+ operator="in"
766+ result="composite1"
767+ id="feComposite954" />
768+ <feGaussianBlur
769+ in="composite1"
770+ stdDeviation="1"
771+ result="blur"
772+ id="feGaussianBlur956" />
773+ <feOffset
774+ dx="0"
775+ dy="1"
776+ result="offset"
777+ id="feOffset958" />
778+ <feComposite
779+ in="SourceGraphic"
780+ in2="offset"
781+ operator="over"
782+ result="composite2"
783+ id="feComposite960" />
784+ </filter>
785+ <clipPath
786+ clipPathUnits="userSpaceOnUse"
787+ id="clipPath873">
788+ <g
789+ transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
790+ id="g875"
791+ inkscape:label="Layer 1"
792+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
793+ <path
794+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
795+ 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"
796+ id="path877"
797+ inkscape:connector-curvature="0"
798+ sodipodi:nodetypes="sssssssss" />
799+ </g>
800+ </clipPath>
801+ <filter
802+ inkscape:collect="always"
803+ id="filter891"
804+ inkscape:label="Badge Shadow">
805+ <feGaussianBlur
806+ inkscape:collect="always"
807+ stdDeviation="0.71999962"
808+ id="feGaussianBlur893" />
809+ </filter>
810+ <clipPath
811+ clipPathUnits="userSpaceOnUse"
812+ id="clipPath874">
813+ <path
814+ sodipodi:nodetypes="cccssczcssccccscc"
815+ inkscape:connector-curvature="0"
816+ id="path876"
817+ 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"
818+ 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" />
819+ </clipPath>
820+ <clipPath
821+ clipPathUnits="userSpaceOnUse"
822+ id="clipPath896">
823+ <path
824+ sodipodi:nodetypes="cccssczcssccccscc"
825+ inkscape:connector-curvature="0"
826+ id="path898"
827+ 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"
828+ 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" />
829+ </clipPath>
830+ <linearGradient
831+ id="linearGradient3354-9">
832+ <stop
833+ id="stop3356-9"
834+ offset="0"
835+ style="stop-color:#959595;stop-opacity:1;" />
836+ <stop
837+ id="stop3358-9"
838+ offset="1"
839+ style="stop-color:#cccccc;stop-opacity:1;" />
840+ </linearGradient>
841+ <linearGradient
842+ y2="-32.881535"
843+ x2="-560.61346"
844+ y1="-40.681377"
845+ x1="-403.07309"
846+ gradientUnits="userSpaceOnUse"
847+ id="linearGradient4343"
848+ xlink:href="#linearGradient3354-9"
849+ inkscape:collect="always" />
850+ <inkscape:perspective
851+ sodipodi:type="inkscape:persp3d"
852+ inkscape:vp_x="0 : 0.5 : 1"
853+ inkscape:vp_y="0 : 1000 : 0"
854+ inkscape:vp_z="1 : 0.5 : 1"
855+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
856+ id="perspective4393" />
857+ <inkscape:perspective
858+ id="perspective4383"
859+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
860+ inkscape:vp_z="744.09448 : 526.18109 : 1"
861+ inkscape:vp_y="0 : 1000 : 0"
862+ inkscape:vp_x="0 : 526.18109 : 1"
863+ sodipodi:type="inkscape:persp3d" />
864+ <linearGradient
865+ inkscape:collect="always"
866+ xlink:href="#linearGradient3354-9"
867+ id="linearGradient3164"
868+ gradientUnits="userSpaceOnUse"
869+ x1="-403.07309"
870+ y1="-40.681377"
871+ x2="-560.61346"
872+ y2="-32.881535" />
873+ </defs>
874+ <sodipodi:namedview
875+ id="base"
876+ pagecolor="#ffffff"
877+ bordercolor="#666666"
878+ borderopacity="1.0"
879+ inkscape:pageopacity="0.0"
880+ inkscape:pageshadow="2"
881+ inkscape:zoom="2.6077032"
882+ inkscape:cx="-17.529322"
883+ inkscape:cy="74.347537"
884+ inkscape:document-units="px"
885+ inkscape:current-layer="layer1"
886+ showgrid="false"
887+ fit-margin-top="0"
888+ fit-margin-left="0"
889+ fit-margin-right="0"
890+ fit-margin-bottom="0"
891+ inkscape:window-width="1920"
892+ inkscape:window-height="1056"
893+ inkscape:window-x="0"
894+ inkscape:window-y="24"
895+ inkscape:window-maximized="1"
896+ showborder="true"
897+ showguides="false"
898+ inkscape:guide-bbox="true"
899+ inkscape:showpageshadow="false"
900+ inkscape:snap-global="false"
901+ inkscape:snap-bbox="true"
902+ inkscape:bbox-paths="true"
903+ inkscape:bbox-nodes="true"
904+ inkscape:snap-bbox-edge-midpoints="true"
905+ inkscape:snap-bbox-midpoints="true"
906+ inkscape:snap-intersection-paths="true"
907+ inkscape:object-paths="true"
908+ inkscape:object-nodes="true"
909+ inkscape:snap-smooth-nodes="true"
910+ inkscape:snap-midpoints="true"
911+ inkscape:snap-object-midpoints="false"
912+ inkscape:snap-center="false"
913+ inkscape:snap-grids="false"
914+ inkscape:snap-to-guides="false">
915+ <inkscape:grid
916+ type="xygrid"
917+ id="grid821" />
918+ <sodipodi:guide
919+ orientation="1,0"
920+ position="16,48"
921+ id="guide823" />
922+ <sodipodi:guide
923+ orientation="0,1"
924+ position="64,80"
925+ id="guide825" />
926+ <sodipodi:guide
927+ orientation="1,0"
928+ position="80,40"
929+ id="guide827" />
930+ <sodipodi:guide
931+ orientation="0,1"
932+ position="64,16"
933+ id="guide829" />
934+ </sodipodi:namedview>
935+ <metadata
936+ id="metadata6522">
937+ <rdf:RDF>
938+ <cc:Work
939+ rdf:about="">
940+ <dc:format>image/svg+xml</dc:format>
941+ <dc:type
942+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
943+ <dc:title />
944+ </cc:Work>
945+ </rdf:RDF>
946+ </metadata>
947+ <g
948+ inkscape:label="BACKGROUND"
949+ inkscape:groupmode="layer"
950+ id="layer1"
951+ transform="translate(268,-635.29076)"
952+ style="display:inline">
953+ <path
954+ style="fill:url(#linearGradient6461);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)"
955+ 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"
956+ id="path6455"
957+ inkscape:connector-curvature="0"
958+ sodipodi:nodetypes="sssssssss" />
959+ <g
960+ id="g3160"
961+ transform="matrix(2.5999788,0,0,2.5999788,820.10006,-1019.0997)">
962+ <path
963+ transform="matrix(0.13863554,0,0,0.13863554,-336.25896,668.28059)"
964+ 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"
965+ sodipodi:ry="8.4974337"
966+ sodipodi:rx="120.17799"
967+ sodipodi:cy="-32.881535"
968+ sodipodi:cx="-460.07535"
969+ id="path3423-1"
970+ style="opacity:0.26353838;fill:url(#linearGradient3164);fill-opacity:1;stroke:none"
971+ sodipodi:type="arc" />
972+ <path
973+ sodipodi:nodetypes="cssssssssssssscccsssssssssssssssssssssssssssssssssscccssssssssssssscccscccssc"
974+ style="fill:#499848;fill-opacity:1"
975+ 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"
976+ id="path3046-2-3-0"
977+ inkscape:connector-curvature="0" />
978+ </g>
979+ </g>
980+ <g
981+ inkscape:groupmode="layer"
982+ id="layer3"
983+ inkscape:label="PLACE YOUR PICTOGRAM HERE"
984+ style="display:inline" />
985+ <g
986+ inkscape:groupmode="layer"
987+ id="layer2"
988+ inkscape:label="BADGE"
989+ style="display:none"
990+ sodipodi:insensitive="true">
991+ <g
992+ style="display:inline"
993+ transform="translate(-340.00001,-581)"
994+ id="g4394"
995+ clip-path="none">
996+ <g
997+ id="g855">
998+ <g
999+ inkscape:groupmode="maskhelper"
1000+ id="g870"
1001+ clip-path="url(#clipPath873)"
1002+ style="opacity:0.6;filter:url(#filter891)">
1003+ <path
1004+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
1005+ 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"
1006+ sodipodi:ry="12"
1007+ sodipodi:rx="12"
1008+ sodipodi:cy="552.36218"
1009+ sodipodi:cx="252"
1010+ id="path844"
1011+ 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"
1012+ sodipodi:type="arc" />
1013+ </g>
1014+ <g
1015+ id="g862">
1016+ <path
1017+ sodipodi:type="arc"
1018+ 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"
1019+ id="path4398"
1020+ sodipodi:cx="252"
1021+ sodipodi:cy="552.36218"
1022+ sodipodi:rx="12"
1023+ sodipodi:ry="12"
1024+ 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"
1025+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
1026+ <path
1027+ transform="matrix(1.25,0,0,1.25,33,-100.45273)"
1028+ 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"
1029+ sodipodi:ry="12"
1030+ sodipodi:rx="12"
1031+ sodipodi:cy="552.36218"
1032+ sodipodi:cx="252"
1033+ id="path4400"
1034+ 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"
1035+ sodipodi:type="arc" />
1036+ <path
1037+ sodipodi:type="star"
1038+ 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"
1039+ id="path4459"
1040+ sodipodi:sides="5"
1041+ sodipodi:cx="666.19574"
1042+ sodipodi:cy="589.50385"
1043+ sodipodi:r1="7.2431178"
1044+ sodipodi:r2="4.3458705"
1045+ sodipodi:arg1="1.0471976"
1046+ sodipodi:arg2="1.6755161"
1047+ inkscape:flatsided="false"
1048+ inkscape:rounded="0.1"
1049+ inkscape:randomized="0"
1050+ 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"
1051+ transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
1052+ </g>
1053+ </g>
1054+ </g>
1055+ </g>
1056+</svg>
1057
1058=== added file 'charms/precise/gunicorn/metadata.yaml'
1059--- charms/precise/gunicorn/metadata.yaml 1970-01-01 00:00:00 +0000
1060+++ charms/precise/gunicorn/metadata.yaml 2014-03-08 15:31:44 +0000
1061@@ -0,0 +1,15 @@
1062+name: gunicorn
1063+summary: Gunicorn
1064+maintainer: Patrick Hetu <patrick.hetu@gmail.com>
1065+categories: ["misc"]
1066+description: |
1067+ Gunicorn or Green Unicorn is a Python WSGI HTTP Server for UNIX. It's a
1068+ pre-fork worker model ported from Ruby's Unicorn project. The Gunicorn server
1069+ is broadly compatible with various web frameworks, simply implemented, light on
1070+ server resources, and fairly speedy.
1071+subordinate: true
1072+requires:
1073+ wsgi-file:
1074+ interface: wsgi
1075+ scope: container
1076+ optional: true
1077
1078=== added file 'charms/precise/gunicorn/revision'
1079--- charms/precise/gunicorn/revision 1970-01-01 00:00:00 +0000
1080+++ charms/precise/gunicorn/revision 2014-03-08 15:31:44 +0000
1081@@ -0,0 +1,1 @@
1082+3
1083
1084=== added directory 'charms/precise/gunicorn/templates'
1085=== added file 'charms/precise/gunicorn/templates/upstart.tmpl'
1086--- charms/precise/gunicorn/templates/upstart.tmpl 1970-01-01 00:00:00 +0000
1087+++ charms/precise/gunicorn/templates/upstart.tmpl 2014-03-08 15:31:44 +0000
1088@@ -0,0 +1,34 @@
1089+#--------------------------------------------------------------
1090+# This file is managed by Juju; ANY CHANGES WILL BE OVERWRITTEN
1091+#--------------------------------------------------------------
1092+
1093+description "Gunicorn daemon for the {{ project_name }} project"
1094+
1095+start on (local-filesystems and net-device-up IFACE=eth0)
1096+stop on runlevel [!12345]
1097+
1098+# If the process quits unexpectadly trigger a respawn
1099+respawn
1100+
1101+setuid {{ wsgi_user }}
1102+setgid {{ wsgi_group }}
1103+chdir {{ working_dir }}
1104+
1105+# This line can be removed and replace with the --pythonpath {{ python_path }} \
1106+# option with Gunicorn>1.17
1107+env PYTHONPATH={{ python_path }}
1108+
1109+exec gunicorn \
1110+ --name={{ project_name }} \
1111+ --workers={{ wsgi_workers }} \
1112+ --worker-class={{ wsgi_worker_class }} \
1113+ --worker-connections={{ wsgi_worker_connections }} \
1114+ --max-requests={{ wsgi_max_requests }} \
1115+ --backlog={{ wsgi_backlog }} \
1116+ --timeout={{ wsgi_timeout }} \
1117+ --keep-alive={{ wsgi_keep_alive }} \
1118+ --umask={{ wsgi_umask }} \
1119+ --bind={{ listen_ip }}:{{ port }} \
1120+ --log-file={{ wsgi_log_file }} \
1121+ --log-level={{ wsgi_log_level }} \
1122+ {{ wsgi_extra }} {{ wsgi_wsgi_file }}
1123
1124=== modified file 'charms/precise/python-django/hooks/hooks.py'
1125--- charms/precise/python-django/hooks/hooks.py 2014-03-03 21:21:38 +0000
1126+++ charms/precise/python-django/hooks/hooks.py 2014-03-08 15:31:44 +0000
1127@@ -424,7 +424,8 @@
1128 for cmd in ['django-admin.py', 'django-admin']:
1129 django_admin_cmd = which(cmd)
1130 if django_admin_cmd:
1131- p = 'PYTHONPATH=%s' % config_data.get('python_path', install_root)
1132+ def_path = '%s:%s' % (config_dir, code_dir)
1133+ p = 'PYTHONPATH=%s' % config_data.get('python_path', def_path)
1134 return '%s %s' % (p, django_admin_cmd)
1135
1136 juju_log(MSG_ERROR, "No django-admin executable found.")
1137@@ -456,12 +457,12 @@
1138
1139 def _install_from_tarball():
1140 juju_log(MSG_INFO, 'grabbing service from tarball...')
1141- if os.path.exists(vcs_clone_dir):
1142+ if os.path.exists(code_dir):
1143 juju_log(MSG_INFO,
1144- 'deleting pre-existing service directory: %s' % vcs_clone_dir)
1145- shutil.rmtree(vcs_clone_dir)
1146- os.mkdir(vcs_clone_dir)
1147- cmd = 'curl %s | tar -xzC %s' % (repos_url, vcs_clone_dir)
1148+ 'deleting pre-existing service directory: %s' % code_dir)
1149+ shutil.rmtree(code_dir)
1150+ os.mkdir(code_dir)
1151+ cmd = 'curl %s | tar -xzC %s' % (repos_url, code_dir)
1152 subprocess.check_call(cmd, shell=True)
1153
1154
1155@@ -503,16 +504,16 @@
1156 cannot identify domain in URL {0}'''.format(repos_url))
1157
1158 if vcs == 'hg' or vcs == 'mercurial':
1159- run('hg clone %s %s' % (repos_url, vcs_clone_dir))
1160+ run('hg clone %s %s' % (repos_url, code_dir))
1161 elif vcs == 'git' or vcs == 'git-core':
1162 if repos_branch:
1163- run('git clone %s -b %s %s' % (repos_url, repos_branch, vcs_clone_dir))
1164+ run('git clone %s -b %s %s' % (repos_url, repos_branch, code_dir))
1165 else:
1166- run('git clone %s %s' % (repos_url, vcs_clone_dir))
1167+ run('git clone %s %s' % (repos_url, code_dir))
1168 elif vcs == 'bzr' or vcs == 'bazaar' or vcs == 'branch':
1169- run('bzr branch %s %s' % (repos_url, vcs_clone_dir))
1170+ run('bzr branch %s %s' % (repos_url, code_dir))
1171 elif vcs == 'svn' or vcs == 'subversion':
1172- run('svn co %s %s' % (repos_url, vcs_clone_dir))
1173+ run('svn co %s %s' % (repos_url, code_dir))
1174 elif vcs == 'tarball':
1175 _install_from_tarball()
1176 elif vcs == '' and repos_url == '':
1177@@ -531,15 +532,18 @@
1178 juju_log(MSG_ERROR, "Unknown version control")
1179 sys.exit(1)
1180
1181- run('chown -R %s:%s %s' % (wsgi_user,wsgi_group, working_dir))
1182-
1183 install_dir(settings_dir_path, owner=wsgi_user, group=wsgi_group, mode=0755)
1184 install_dir(urls_dir_path, owner=wsgi_user, group=wsgi_group, mode=0755)
1185
1186- #FIXME: Upgrades/pulls will mess those files
1187-
1188- for path, dir in ((settings_py_path, 'juju_settings'), (urls_py_path, 'juju_urls')):
1189- append_template('conf_injection.tmpl', {'dir': dir}, path)
1190+ juju_settings = os.path.join(config_dir, 'juju_settings')
1191+ juju_urls = os.path.join(config_dir, 'juju_urls')
1192+ combined = [('settings.tmpl', settings_py_path, juju_settings),
1193+ ('urls.tmpl', urls_py_path, juju_urls)]
1194+ for tmpl, path, dir in combined:
1195+ subs = {'CONFDIR': dir,
1196+ 'LOGDIR': logs_dir,
1197+ 'VARDIR': var_dir}
1198+ append_template(tmpl, subs, path)
1199
1200 if requirements_pip_files:
1201 for req_file in requirements_pip_files.split(','):
1202@@ -551,7 +555,7 @@
1203 'django_settings': django_settings}, \
1204 wsgi_py_path)
1205 if unit_config:
1206- with open(os.path.join(vcs_clone_dir, 'unit_config'), 'w') as f:
1207+ with open(os.path.join(config_dir, 'unit_config'), 'w') as f:
1208 f.write(base64.b64decode(unit_config))
1209
1210 def start():
1211@@ -605,24 +609,22 @@
1212 break
1213
1214 if vcs == 'hg' or vcs == 'mercurial':
1215- run('hg pull %s %s' % (repos_url, vcs_clone_dir))
1216+ run('hg pull %s %s' % (repos_url, code_dir))
1217 elif vcs == 'git' or vcs == 'git-core':
1218 if repos_branch:
1219- run('git pull %s -b %s %s' % (repos_url, repos_branch, vcs_clone_dir))
1220+ run('git pull %s -b %s %s' % (repos_url, repos_branch, code_dir))
1221 else:
1222- run('git pull %s %s' % (repos_url, vcs_clone_dir))
1223+ run('git pull %s %s' % (repos_url, code_dir))
1224 elif vcs == 'bzr' or vcs == 'bazaar':
1225- run('cd %s; bzr pull %s %s' % (vcs_clone_dir, repos_url))
1226+ run('cd %s; bzr pull %s %s' % (code_dir, repos_url))
1227 elif vcs == 'svn' or vcs == 'subversion':
1228- run('svn up %s %s' % (repos_url, vcs_clone_dir))
1229+ run('svn up %s %s' % (repos_url, code_dir))
1230 elif vcs == 'tarball':
1231 _install_from_tarball()
1232 else:
1233 juju_log(MSG_ERROR, "Unknown version control")
1234 sys.exit(1)
1235
1236- run('chown -R %s:%s %s' % (wsgi_user,wsgi_group, working_dir))
1237-
1238 if requirements_pip_files:
1239 for req_file in requirements_pip_files.split(','):
1240 pip_install_req(os.path.join(working_dir,req_file), upgrade=True)
1241@@ -651,8 +653,6 @@
1242 'wsgi_group': wsgi_group,
1243 })
1244
1245- run('chown -R %s:%s %s' % (wsgi_user,wsgi_group, working_dir))
1246-
1247 # Trigger WSGI reloading
1248 for relid in relation_ids('wsgi'):
1249 relation_set({'wsgi_timestamp': time.time()}, relation_id=relid)
1250@@ -690,8 +690,6 @@
1251 (django_admin_cmd, django_settings_modules),
1252 cwd=working_dir)
1253
1254- run('chown -R %s:%s %s' % (wsgi_user,wsgi_group, working_dir))
1255-
1256 # Trigger WSGI reloading
1257 for relid in relation_ids('wsgi'):
1258 relation_set({'wsgi_timestamp': time.time()}, relation_id=relid)
1259@@ -718,8 +716,6 @@
1260
1261 process_template('mongodb_engine.tmpl', templ_vars, settings_database_path % {'engine_name': 'mongodb'})
1262
1263- run('chown -R %s:%s %s' % (wsgi_user,wsgi_group, working_dir))
1264-
1265 # Trigger WSGI reloading
1266 for relid in relation_ids('wsgi'):
1267 relation_set({'wsgi_timestamp': time.time()}, relation_id=relid)
1268@@ -739,7 +735,10 @@
1269 relation_set({var: config_data[var]})
1270
1271 if not config_data['python_path']:
1272- relation_set({'python_path': install_root})
1273+ def_path = '%s:%s' % (config_dir, code_dir)
1274+ relation_set({'python_path': def_path})
1275+ else:
1276+ relation_set({'python_path': config_data['python_path']})
1277
1278 open_port(config_data['port'])
1279
1280@@ -764,9 +763,6 @@
1281
1282 process_template('cache.tmpl', templ_vars, settings_database_path % {'engine_name': 'memcache'})
1283
1284- run('chown -R %s:%s %s' % (wsgi_user,wsgi_group, working_dir))
1285-
1286-
1287 # Trigger WSGI reloading
1288 for relid in relation_ids('wsgi'):
1289 relation_set({'wsgi_timestamp': time.time()}, relation_id=relid)
1290@@ -784,6 +780,16 @@
1291 def website_relation_broken():
1292 pass
1293
1294+def chown_r(path, user, group):
1295+ uid = getpwnam(user).pw_uid
1296+ gid = getgrnam(group).gr_gid
1297+
1298+ for root, dirs, files in os.walk(path):
1299+ for p in dirs:
1300+ os.chown(os.path.join(root, p), uid, gid)
1301+ for p in files:
1302+ os.chown(os.path.join(root, p), uid, gid)
1303+ os.chown(root, uid, gid)
1304
1305 def json_status_relation_joined():
1306 path = config_data.get('json_status_path')
1307@@ -829,22 +835,37 @@
1308
1309 unit_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
1310 sanitized_unit_name = sanitize(unit_name)
1311-vcs_clone_dir = os.path.join(install_root, sanitized_unit_name)
1312+real_root = os.path.join(install_root, sanitized_unit_name)
1313+logs_dir = os.path.join(real_root, "logs")
1314+code_dir = os.path.join(real_root, "code")
1315+config_dir = os.path.join(real_root, "conf")
1316+var_dir = os.path.join(real_root, "var")
1317+
1318 if application_path:
1319- working_dir = os.path.join(vcs_clone_dir, application_path)
1320+ working_dir = os.path.join(code_dir, application_path)
1321 else:
1322- working_dir = vcs_clone_dir
1323+ working_dir = code_dir
1324+
1325+if not os.path.exists(real_root):
1326+ os.mkdir(real_root)
1327+if not os.path.exists(logs_dir):
1328+ os.mkdir(logs_dir)
1329+if not os.path.exists(config_dir):
1330+ os.mkdir(config_dir)
1331+if not os.path.exists(var_dir):
1332+ os.mkdir(var_dir)
1333+
1334+chown_r(logs_dir, wsgi_user, wsgi_group)
1335+chown_r(var_dir, wsgi_user, wsgi_group)
1336
1337 django_settings_modules = '.'.join([sanitized_unit_name, django_settings])
1338 django_settings_modules = django_settings #andy hack
1339-django_run_dir = os.path.join(working_dir, "run/")
1340-django_logs_dir = os.path.join(working_dir, "logs/")
1341-settings_py_path = os.path.join(working_dir, 'settings.py')
1342-urls_py_path = os.path.join(working_dir, 'urls.py')
1343-settings_dir_path = os.path.join(working_dir, config_data["settings_dir_name"])
1344-urls_dir_path = os.path.join(working_dir, config_data["urls_dir_name"])
1345-settings_secret_path = os.path.join(working_dir, config_data["settings_secret_key_path"])
1346-settings_database_path = os.path.join(working_dir, config_data["settings_database_path"])
1347+settings_py_path = os.path.join(config_dir, 'local_settings.py')
1348+urls_py_path = os.path.join(config_dir, 'urls.py')
1349+settings_dir_path = os.path.join(config_dir, config_data["settings_dir_name"])
1350+urls_dir_path = os.path.join(config_dir, config_data["urls_dir_name"])
1351+settings_secret_path = os.path.join(config_dir, config_data["settings_secret_key_path"])
1352+settings_database_path = os.path.join(config_dir, config_data["settings_database_path"])
1353 hook_name = os.path.basename(sys.argv[0])
1354
1355 ###############################################################################
1356
1357=== renamed file 'charms/precise/python-django/templates/conf_injection.tmpl' => 'charms/precise/python-django/templates/settings.tmpl'
1358--- charms/precise/python-django/templates/conf_injection.tmpl 2014-01-29 20:13:31 +0000
1359+++ charms/precise/python-django/templates/settings.tmpl 2014-03-08 15:31:44 +0000
1360@@ -1,10 +1,41 @@
1361 import glob
1362-from os.path import abspath, dirname, join
1363-
1364-PROJECT_DIR = abspath(dirname(__file__))
1365-
1366-conffiles = glob.glob(join(PROJECT_DIR, '{{ dir }}', '*.py'))
1367+import os
1368+import yaml
1369+
1370+CONFDIR = '{{ CONFDIR }}'
1371+LOGDIR = '{{ LOGDIR }}'
1372+VARDIR = '{{ VARDIR }}'
1373+conffiles = glob.glob(os.path.join(CONFDIR, '*.py'))
1374 conffiles.sort()
1375
1376 for f in conffiles:
1377- execfile(abspath(f))
1378+ execfile(os.path.abspath(f))
1379+
1380+LOGGING = {
1381+ 'version': 1,
1382+ 'disable_existing_loggers': False,
1383+ 'formatters': {
1384+ 'verbose': {
1385+ 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
1386+ },
1387+ },
1388+ 'filters': {
1389+ 'require_debug_false': {
1390+ '()': 'django.utils.log.RequireDebugFalse'
1391+ }
1392+ },
1393+ 'handlers': {
1394+ 'logfile': {
1395+ 'class': 'logging.handlers.WatchedFileHandler',
1396+ 'filename': os.path.join(LOGDIR, 'django-error.log'),
1397+ 'formatter': 'verbose',
1398+ },
1399+ },
1400+ 'loggers': {
1401+ 'django.request': {
1402+ 'handlers': ['logfile'],
1403+ 'level': 'ERROR',
1404+ 'propagate': True,
1405+ },
1406+ }
1407+}
1408
1409=== added file 'charms/precise/python-django/templates/urls.tmpl'
1410--- charms/precise/python-django/templates/urls.tmpl 1970-01-01 00:00:00 +0000
1411+++ charms/precise/python-django/templates/urls.tmpl 2014-03-08 15:31:44 +0000
1412@@ -0,0 +1,9 @@
1413+import glob
1414+from os.path import abspath, dirname, join
1415+
1416+confdir = '{{ confdir }}'
1417+conffiles = glob.glob(join(confdir, '*.py'))
1418+conffiles.sort()
1419+
1420+for f in conffiles:
1421+ execfile(abspath(f))
1422
1423=== modified file 'juju-deployer/branch-source-builder.yaml.tmpl'
1424--- juju-deployer/branch-source-builder.yaml.tmpl 2014-03-06 22:18:04 +0000
1425+++ juju-deployer/branch-source-builder.yaml.tmpl 2014-03-08 15:31:44 +0000
1426@@ -18,7 +18,6 @@
1427 - ""
1428 bsb-gunicorn:
1429 charm: gunicorn
1430- branch: lp:charms/precise/gunicorn@28
1431 options:
1432 wsgi_wsgi_file: bsbuilder.wsgi:app
1433 bsb-worker:
1434
1435=== modified file 'juju-deployer/deploy.py'
1436--- juju-deployer/deploy.py 2014-03-07 07:29:12 +0000
1437+++ juju-deployer/deploy.py 2014-03-08 15:31:44 +0000
1438@@ -219,7 +219,8 @@
1439 # Feed local_charms with our charms so that if anything goes wrong during
1440 # the building phase, we'll never remove them.
1441 local_charms = {'precise': ['lander-jenkins', 'python-django',
1442- 'rabbitmq-worker', 'restish', 'webui']}
1443+ 'rabbitmq-worker', 'restish', 'webui',
1444+ 'gunicorn']}
1445 clean_func = lambda: _cleanup(local_charms)
1446 atexit.register(clean_func)
1447
1448
1449=== modified file 'juju-deployer/image-builder.yaml.tmpl'
1450--- juju-deployer/image-builder.yaml.tmpl 2014-03-06 22:18:04 +0000
1451+++ juju-deployer/image-builder.yaml.tmpl 2014-03-08 15:31:44 +0000
1452@@ -17,7 +17,6 @@
1453 - ""
1454 imagebuild-gunicorn:
1455 charm: gunicorn
1456- branch: lp:charms/precise/gunicorn@28
1457 options:
1458 wsgi_wsgi_file: imagebuilder.wsgi:app
1459 imagebuild-worker:
1460
1461=== modified file 'juju-deployer/lander.yaml.tmpl'
1462--- juju-deployer/lander.yaml.tmpl 2014-03-07 21:46:28 +0000
1463+++ juju-deployer/lander.yaml.tmpl 2014-03-08 15:31:44 +0000
1464@@ -16,7 +16,6 @@
1465 - ""
1466 lander-gunicorn:
1467 charm: gunicorn
1468- branch: lp:charms/precise/gunicorn@28
1469 options:
1470 wsgi_wsgi_file: lander.wsgi:app
1471 lander-jenkins:
1472
1473=== modified file 'juju-deployer/ppa-assigner.yaml.tmpl'
1474--- juju-deployer/ppa-assigner.yaml.tmpl 2014-03-03 21:04:45 +0000
1475+++ juju-deployer/ppa-assigner.yaml.tmpl 2014-03-08 15:31:44 +0000
1476@@ -12,17 +12,24 @@
1477 application_path: ppa-assigner/
1478 django_settings: ppa_assigner.settings
1479 django_south: True
1480- python_path: /srv/ppa_django/ci-utils:/srv/ppa_django/ppa-assigner
1481 unit-config: include-base64://configs/unit_config.yaml
1482+ # The following wsgi_ options (and python_path) get shared with
1483+ # gunicorn via a relation-set in this charm. Setting them in
1484+ # gunicorn will only see them get overriden by the defaults in
1485+ # python-django.
1486+ wsgi_log_file: /srv/ppa_django/logs/gunicorn-error.log
1487+ python_path: /srv/ppa_django/conf:/srv/ppa_django/code/ci-utils:/srv/ppa_django/code/ppa-assigner
1488 json_status_path: api/v1/status/
1489 ppa-postgres:
1490 branch: lp:charms/precise/postgresql@84
1491 charm: postgresql
1492 ppa-gunicorn:
1493- branch: lp:charms/precise/gunicorn@28
1494 charm: gunicorn
1495 options:
1496- python_path: /srv/ppa_django/ci-utils:/srv/ppa_django/ppa-assigner
1497+ wsgi_access_logfile: /srv/ppa_django/logs/gunicorn-access.log
1498+ wsgi_access_logformat: '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s'
1499+ wsgi_log_level: debug
1500+ wsgi_extra: "--debug"
1501 relations:
1502 # Relations should be explicit as amulet can't infer them otherwise
1503 # even if there is a single one
1504
1505=== modified file 'juju-deployer/production-only.yaml'
1506--- juju-deployer/production-only.yaml 2014-02-20 17:35:41 +0000
1507+++ juju-deployer/production-only.yaml 2014-03-08 15:31:44 +0000
1508@@ -12,6 +12,6 @@
1509 charm: upstart
1510 options:
1511 service-name: ppa-cleaner
1512- script: ./ppa-assigner/manage.py clean_ppas -v 1
1513+ script: ./code/ppa-assigner/manage.py clean_ppas -v 1
1514 relations:
1515 - ["ppa-django:juju-info", "ppa-cleaner:juju-info"]
1516
1517=== modified file 'juju-deployer/test-runner.yaml.tmpl'
1518--- juju-deployer/test-runner.yaml.tmpl 2014-03-06 14:13:05 +0000
1519+++ juju-deployer/test-runner.yaml.tmpl 2014-03-08 15:31:44 +0000
1520@@ -3,7 +3,6 @@
1521 services:
1522 tr-gunicorn:
1523 charm: gunicorn
1524- branch: lp:charms/precise/gunicorn@28
1525 options:
1526 wsgi_wsgi_file: tstrun.wsgi:appl
1527 tr-restish:
1528
1529=== modified file 'juju-deployer/ticket-system.yaml.tmpl'
1530--- juju-deployer/ticket-system.yaml.tmpl 2014-02-25 14:14:43 +0000
1531+++ juju-deployer/ticket-system.yaml.tmpl 2014-03-08 15:31:44 +0000
1532@@ -19,21 +19,28 @@
1533 additional_distro_packages: python-lazr.enum, python-yaml, python-tastypie, python-django-south
1534 django_version: ${CI_PPA}
1535 unit-config: include-base64://configs/unit_config.yaml
1536- application_path: ticket_system/
1537+ application_path: ticket_system
1538 django_settings: ticket_system.settings
1539 django_south: True
1540 django_debug: True
1541- python_path: /srv/ts_django/ci-utils:/srv/ts_django/ticket_system
1542- # this gets shared with gunicorn via a relation-set in this charm
1543+ # The following wsgi_ options (and python_path) get shared with
1544+ # gunicorn via a relation-set in this charm. Setting them in
1545+ # gunicorn will only see them get overriden by the defaults in
1546+ # python-django.
1547 wsgi_wsgi_file: ticket_system.wsgi:application
1548+ wsgi_log_file: /srv/ts_django/logs/gunicorn-error.log
1549+ python_path: /srv/ts_django/conf:/srv/ts_django/code/ci-utils:/srv/ts_django/code/ticket_system
1550 ts-postgres:
1551 branch: lp:charms/precise/postgresql@84
1552 charm: postgresql
1553 ts-gunicorn:
1554- branch: lp:charms/precise/gunicorn@28
1555 charm: gunicorn
1556 options:
1557- python_path: /srv/ts_django/ci-utils:/srv/ts_django/ticket_system
1558+ python_path: /srv/ts_django/conf:/srv/ts_django/code/ci-utils:/srv/ts_django/code/ticket_system
1559+ wsgi_access_logfile: /srv/ts_django/logs/gunicorn-access.log
1560+ wsgi_access_logformat: '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s'
1561+ wsgi_log_level: debug
1562+ wsgi_extra: "--debug"
1563 relations:
1564 - ["ts-django:pgsql", "ts-postgres:db"]
1565 - ["ts-django:wsgi", "ts-gunicorn:wsgi-file"]
1566
1567=== modified file 'ppa-assigner/ppa_assigner/ppa_sync.py'
1568--- ppa-assigner/ppa_assigner/ppa_sync.py 2014-02-28 22:05:07 +0000
1569+++ ppa-assigner/ppa_assigner/ppa_sync.py 2014-03-08 15:31:44 +0000
1570@@ -3,19 +3,22 @@
1571 import logging
1572 import os
1573 import subprocess
1574+import tempfile
1575
1576 from django.conf import settings
1577
1578 from launchpadlib.launchpad import Launchpad
1579 from launchpadlib.credentials import UnencryptedFileCredentialStore
1580
1581-creds_file = os.path.join(settings.BASEDIR, '.ppa-assigner.lpcreds')
1582-with open(creds_file, 'w') as f:
1583+creds_file = os.path.join(settings.VARDIR, '.ppa-assigner.lpcreds')
1584+with tempfile.NamedTemporaryFile('w', delete=False) as f:
1585 f.write('[1]\n')
1586 f.write('consumer_key = %s\n' % settings.OAUTH_CONSUMER_KEY)
1587 f.write('consumer_secret = \n')
1588 f.write('access_token = %s\n' % settings.OAUTH_TOKEN)
1589 f.write('access_secret = %s\n' % settings.OAUTH_TOKEN_SECRET)
1590+# Rename to final destination
1591+os.rename(f.name, creds_file)
1592
1593
1594 def get_package_data(ppa, series, package):
1595
1596=== modified file 'ppa-assigner/ppa_assigner/settings.py'
1597--- ppa-assigner/ppa_assigner/settings.py 2014-02-28 18:43:18 +0000
1598+++ ppa-assigner/ppa_assigner/settings.py 2014-03-08 15:31:44 +0000
1599@@ -13,7 +13,6 @@
1600 # You should have received a copy of the GNU Affero General Public License
1601 # along with this program. If not, see <http://www.gnu.org/licenses/>.
1602
1603-import glob
1604 import os
1605 import yaml
1606
1607@@ -31,26 +30,6 @@
1608 MANAGERS = ADMINS
1609
1610
1611-def _unit_config():
1612- path = os.path.join(BASEDIR, '../unit_config')
1613- config = {}
1614- try:
1615- with open(path) as f:
1616- config = yaml.safe_load(f.read())
1617- except:
1618- print('Unable to use unit_config(%s), defaulting values' % path)
1619- return config
1620-_cfg = _unit_config()
1621-
1622-LAUNCHPAD_PPA_USER = _cfg.get('launchpad_user', None)
1623-LAUNCHPAD_API_BASE = _cfg.get(
1624- 'launchpad_api_base', 'https://api.launchpad.net/1.0')
1625-OAUTH_CONSUMER_KEY = _cfg.get('oauth_consumer_key', None)
1626-OAUTH_TOKEN = _cfg.get('oauth_token', None)
1627-OAUTH_TOKEN_SECRET = _cfg.get('oauth_token_secret', None)
1628-OAUTH_REALM = _cfg.get('oauth_realm', 'https://api.launchpad.net/')
1629-PPA_PATTERN = _cfg.get('ppa_pattern', r'ci-pool-\d+')
1630-
1631 DATABASES = {
1632 'default': {
1633 'ENGINE': 'django.db.backends.sqlite3',
1634@@ -145,20 +124,43 @@
1635 # Make this unique, and don't share it with anybody.
1636 SECRET_KEY = 'zlv5#5_@*5re)(s$0&r98i*5*g6k=x!$0lo)fl!+^2l6#)5hqb'
1637
1638+
1639 # Use local settings if available
1640 try:
1641- p = os.path.join(BASEDIR, 'local_settings.py')
1642- if os.path.exists(p):
1643- execfile(p)
1644+ from local_settings import *
1645 except ImportError:
1646 pass
1647
1648-# Pull in juju_settings
1649 try:
1650- p = os.path.join(BASEDIR, 'juju_settings', '*.py')
1651- for f in sorted(glob.glob(p)):
1652- execfile(f)
1653+ from db_settings import DATABASES
1654 except ImportError:
1655 pass
1656
1657+
1658+def _unit_config():
1659+ try:
1660+ # This should get set by local_settings.
1661+ path = os.path.join(CONFDIR, '../unit_config')
1662+ except NameError:
1663+ path = os.path.join(BASEDIR, '../unit_config')
1664+ config = {}
1665+ try:
1666+ with open(path) as f:
1667+ config = yaml.safe_load(f.read())
1668+ except:
1669+ print('Unable to use unit_config(%s), defaulting values' % path)
1670+ return config
1671+_cfg = _unit_config()
1672+
1673+
1674+LAUNCHPAD_PPA_USER = _cfg.get('launchpad_user', None)
1675+LAUNCHPAD_API_BASE = _cfg.get(
1676+ 'launchpad_api_base', 'https://api.launchpad.net/1.0')
1677+OAUTH_CONSUMER_KEY = _cfg.get('oauth_consumer_key', None)
1678+OAUTH_TOKEN = _cfg.get('oauth_token', None)
1679+OAUTH_TOKEN_SECRET = _cfg.get('oauth_token_secret', None)
1680+OAUTH_REALM = _cfg.get('oauth_realm', 'https://api.launchpad.net/')
1681+PPA_PATTERN = _cfg.get('ppa_pattern', r'ci-pool-\d+')
1682+
1683+
1684 INSTALLED_APPS = LOCAL_APPS + REQUIRED_APPS
1685
1686=== modified file 'ticket_system/ticket_system/settings.py'
1687--- ticket_system/ticket_system/settings.py 2014-03-10 18:03:17 +0000
1688+++ ticket_system/ticket_system/settings.py 2014-03-08 15:31:44 +0000
1689@@ -13,7 +13,6 @@
1690 # You should have received a copy of the GNU Affero General Public License
1691 # along with this program. If not, see <http://www.gnu.org/licenses/>.
1692
1693-import glob
1694 import os
1695 import yaml
1696
1697@@ -29,21 +28,6 @@
1698 MANAGERS = ADMINS
1699
1700
1701-def _unit_config():
1702- path = os.path.join(BASEDIR, '../unit_config')
1703- config = {}
1704- try:
1705- with open(path) as f:
1706- config = yaml.safe_load(f.read())
1707- except:
1708- print('Unable to use unit_config(%s), defaulting values' % path)
1709- return config
1710-_cfg = _unit_config()
1711-
1712-BASE_IMAGE_DEFAULT = _cfg.get('base_image', '')
1713-SERIES_DEFAULT = _cfg.get('series', '')
1714-MASTER_PPA_DEFAULT = _cfg.get('master_ppa', '')
1715-
1716 DATABASES = {
1717 'default': {
1718 'ENGINE': 'django.db.backends.sqlite3',
1719@@ -207,22 +191,35 @@
1720 }
1721 }
1722
1723+
1724 # Use local settings if available
1725 try:
1726 from local_settings import *
1727 except ImportError:
1728 pass
1729
1730-# Pull in juju_settings
1731-try:
1732- p = os.path.join(os.path.dirname(__file__), '../juju_settings')
1733- p = os.path.join(os.path.abspath(p), '*.py')
1734- for f in sorted(glob.glob(p)):
1735- execfile(f)
1736-except ImportError:
1737- pass
1738-
1739 try:
1740 from db_settings import DATABASES
1741 except ImportError:
1742 pass
1743+
1744+
1745+def _unit_config():
1746+ try:
1747+ # This should get set by local_settings.
1748+ path = os.path.join(CONFDIR, '../unit_config')
1749+ except NameError:
1750+ path = os.path.join(BASEDIR, '../unit_config')
1751+ config = {}
1752+ try:
1753+ with open(path) as f:
1754+ config = yaml.safe_load(f.read())
1755+ except:
1756+ print('Unable to use unit_config(%s), defaulting values' % path)
1757+ return config
1758+_cfg = _unit_config()
1759+
1760+
1761+BASE_IMAGE_DEFAULT = _cfg.get('base_image', '')
1762+SERIES_DEFAULT = _cfg.get('series', '')
1763+MASTER_PPA_DEFAULT = _cfg.get('master_ppa', '')

Subscribers

People subscribed via source and target branches

to all changes: