Merge lp:~ev/ubuntu-ci-services-itself/better-structure-and-logging into lp:ubuntu-ci-services-itself
- better-structure-and-logging
- Merge into trunk
| 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 | ||||
| Related bugs: |
|
| 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:
|
|||
Commit message
- Teach gunicorn to properly write gunicorn access and error logs under /srv/${
- Store all the Django configuration under /srv/${
- Store lp:ubuntu-ci-services-itself under /srv/${
- Store variable data under /srv/${
- Log Django errors to /srv/${
Description of the change
Deploy django/gunicorn with the following structure:
/srv/${
- Teach gunicorn to properly write gunicorn access and error logs under /srv/${
- Store all the Django configuration under /srv/${
- Store lp:ubuntu-ci-services-itself under /srv/${
- Log Django errors to /srv/${
We can later add /srv/${
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.
| PS Jenkins bot (ps-jenkins) wrote : | # |
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:297
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| Andy Doan (doanac) wrote : | # |
1402 +# Ticket System
1403 +BASE_IMAGE_DEFAULT = _cfg.get(
1404 +SERIES_DEFAULT = _cfg.get('series', '')
1405 +MASTER_PPA_DEFAULT = _cfg.get(
1406 +
1407 +# PPA Assigner
1408 +LAUNCHPAD_PPA_USER = _cfg.get(
1409 +LAUNCHPAD_API_BASE = _cfg.get(
1410 + 'https:/
1411 +OAUTH_CONSUMER_KEY = _cfg.get(
1412 +OAUTH_TOKEN = _cfg.get(
1413 +OAUTH_TOKEN_SECRET = _cfg.get(
1414 +OAUTH_REALM = _cfg.get(
1415 +PPA_PATTERN = _cfg.get(
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.
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:297
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| Chris Johnston (cjohnston) wrote : | # |
+ /home/ubuntu/
This one potentially looks valid
+ /home/ubuntu/
+ /home/ubuntu/
These two I think we should setup ignores for.
> 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.
> + > /home/ubuntu/
> 'glob' imported but unused
Fixed as r299.
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:298
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:299
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:300
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| Andy Doan (doanac) wrote : | # |
Just nits - nothing worth not merging over:
django hook.py
1131 - p = 'PYTHONPATH=%s' % config_
1132 + def_path = '%s:%s' % (config_dir, code_dir)
1133 + p = 'PYTHONPATH=%s' % config_
1269 - relation_
1270 + relation_
I think the relation_set should use the same logic as line 1133 uses?
ppa-assigner.yaml:
1468 + python_path: /srv/ppa_
Is this needed now, or should the relation_set (now that its been updated) handle this setting properly.
Untested by me.
| 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-
Traceback (most recent call last):
File "/tmp/better-
main()
File "/tmp/better-
run()
File "/tmp/better-
importer.
File "/tmp/better-
self.
File "/tmp/better-
no_
File "/tmp/better-
if charm.is_
File "/tmp/better-
return self.vcs.
File "/tmp/better-
tree = WorkingTree.
File "/usr/lib/
control = controldir.
File "/usr/lib/
_unsupporte
File "/usr/lib/
find_format, transport, redirected)
File "/usr/lib/
return action(transport)
File "/usr/lib/
probers=
File "/usr/lib/
raise errors.
bzrlib.
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:301
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:302
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:303
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| Andy Doan (doanac) wrote : | # |
thanks for the updates
| 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-
2014-03-07 20:55:56 DEBUG juju-log pgsql:28: PYTHONPATH=
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'
| Andy Doan (doanac) wrote : | # |
Another bug i'm seeing is with unit_config:
Unable to use unit_config(
I think our settings.py files don't know how to locate this under its new location.
Man, this is just fail all over the place. Doing a deploy now to track
these down.
| Chris Johnston (cjohnston) wrote : | # |
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.
Installing distribute.
Installing pip....
== Testing ci-utils ....
Unpacking /tmp/tarmac/
Running setup.py egg_info for package from file://
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.
Successfully installed Babel
Cleaning up...
Using saved parent location: bzr+ssh:
No revisions or tags to pull.
Unpacking /tmp/tarmac/
Running setup.py egg_info for package from file://
[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:
No revisions or tags to pull.
Unpacking /tmp/tarmac/
Running setup.py egg_info for package from file://
Installing collected packages: iso8601
Running setup.py install for iso8601
Successfully installed iso8601
Cleaning up...
Using saved parent location: bzr+ssh:
No revisions or tags to pull.
Unpacking /tmp/tarmac/
Running setup.py egg_info for package from file://
Installing collected package...
- 304. By Evan
-
Fix setting CONFDIR and LOGDIR.
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:304
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 305. By Evan
-
Safely write the lpcreds file. Put it under a writable directory.
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:305
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 306. By Evan
-
Actually setting the VARDIR would help.
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:306
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:307
http://
Executed test runs:
Click here to trigger a rebuild:
http://
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:308
http://
Executed test runs:
Click here to trigger a rebuild:
http://
Finally!
% curl http://
[{"label": "launchpad configured", "status": "okay", "value": true}, {"label": "total ppas", "status": "okay", "value": 2}, {"label": "available ppas", "status": "okay", "value": 2}]
| Andy Doan (doanac) wrote : | # |
This is close, but I just noticed the ppa-cleaner isn't running:
Unable to use unit_config(
ERROR: oauth settings are required by this command:
OAUTH_
OAUTH_TOKEN
OAUTH_
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?
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(
> 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.
| 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.
except NameError:
path = os.path.
if not os.path.
path = os.path.
or just throw in the towel and do:
subprocess.
:)
| PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:308
http://
Executed test runs:
Click here to trigger a rebuild:
http://
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
| 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', '') |
FAILED: Continuous integration, rev:295 /code.launchpad .net/~ev/ ubuntu- ci-services- itself/ better- structure- and-logging/ +merge/ 208581/ +edit-commit- message
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:/
http:// s-jenkins. ubuntu- ci:8080/ job/uci- engine- ci/261/
Executed test runs:
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/uci- engine- ci/261/ rebuild
http://