Merge lp:~stub/charms/precise/postgresql-psql/trunk into lp:charms/postgresql-psql
- Precise Pangolin (12.04)
- trunk
- Merge into trunk
Proposed by
Stuart Bishop
Status: | Merged | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Stuart Bishop | ||||||||||||
Approved revision: | 20 | ||||||||||||
Merged at revision: | 16 | ||||||||||||
Proposed branch: | lp:~stub/charms/precise/postgresql-psql/trunk | ||||||||||||
Merge into: | lp:charms/postgresql-psql | ||||||||||||
Diff against target: |
1261 lines (+994/-171) 5 files modified
charm-helpers.yaml (+4/-0) hooks/charmhelpers/core/hookenv.py (+339/-0) hooks/charmhelpers/core/host.py (+272/-0) hooks/hooks.py (+36/-94) icon.svg (+343/-77) |
||||||||||||
To merge this branch: | bzr merge lp:~stub/charms/precise/postgresql-psql/trunk | ||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Kapil Thangavelu (community) | Approve | ||
Review via email: mp+174773@code.launchpad.net |
Commit message
Wait until the new 'allowed-units' property tells us permissions have been granted before creating database access scripts.
Description of the change
The first change on this branch is to make use of the 'allowed-units' setting on the relation, allowing us to confirm that https:/
The bulk of the changes is tearing out our helpers and using charm-helpers instead. The included version of charm-helpers is current trunk.
To post a comment you must log in.
Revision history for this message
Stuart Bishop (stub) wrote : | # |
- 20. By Stuart Bishop
-
New icon using template
Revision history for this message
Stuart Bishop (stub) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'charm-helpers.yaml' |
2 | --- charm-helpers.yaml 1970-01-01 00:00:00 +0000 |
3 | +++ charm-helpers.yaml 2013-07-16 12:39:27 +0000 |
4 | @@ -0,0 +1,4 @@ |
5 | +destination: hooks/charmhelpers |
6 | +branch: lp:charm-helpers |
7 | +include: |
8 | + - core |
9 | |
10 | === added directory 'hooks/charmhelpers' |
11 | === added file 'hooks/charmhelpers/__init__.py' |
12 | === added directory 'hooks/charmhelpers/core' |
13 | === added file 'hooks/charmhelpers/core/__init__.py' |
14 | === added file 'hooks/charmhelpers/core/hookenv.py' |
15 | --- hooks/charmhelpers/core/hookenv.py 1970-01-01 00:00:00 +0000 |
16 | +++ hooks/charmhelpers/core/hookenv.py 2013-07-16 12:39:27 +0000 |
17 | @@ -0,0 +1,339 @@ |
18 | +"Interactions with the Juju environment" |
19 | +# Copyright 2013 Canonical Ltd. |
20 | +# |
21 | +# Authors: |
22 | +# Charm Helpers Developers <juju@lists.ubuntu.com> |
23 | + |
24 | +import os |
25 | +import json |
26 | +import yaml |
27 | +import subprocess |
28 | +import UserDict |
29 | + |
30 | +CRITICAL = "CRITICAL" |
31 | +ERROR = "ERROR" |
32 | +WARNING = "WARNING" |
33 | +INFO = "INFO" |
34 | +DEBUG = "DEBUG" |
35 | +MARKER = object() |
36 | + |
37 | +cache = {} |
38 | + |
39 | + |
40 | +def cached(func): |
41 | + ''' Cache return values for multiple executions of func + args |
42 | + |
43 | + For example: |
44 | + |
45 | + @cached |
46 | + def unit_get(attribute): |
47 | + pass |
48 | + |
49 | + unit_get('test') |
50 | + |
51 | + will cache the result of unit_get + 'test' for future calls. |
52 | + ''' |
53 | + def wrapper(*args, **kwargs): |
54 | + global cache |
55 | + key = str((func, args, kwargs)) |
56 | + try: |
57 | + return cache[key] |
58 | + except KeyError: |
59 | + res = func(*args, **kwargs) |
60 | + cache[key] = res |
61 | + return res |
62 | + return wrapper |
63 | + |
64 | + |
65 | +def flush(key): |
66 | + ''' Flushes any entries from function cache where the |
67 | + key is found in the function+args ''' |
68 | + flush_list = [] |
69 | + for item in cache: |
70 | + if key in item: |
71 | + flush_list.append(item) |
72 | + for item in flush_list: |
73 | + del cache[item] |
74 | + |
75 | + |
76 | +def log(message, level=None): |
77 | + "Write a message to the juju log" |
78 | + command = ['juju-log'] |
79 | + if level: |
80 | + command += ['-l', level] |
81 | + command += [message] |
82 | + subprocess.call(command) |
83 | + |
84 | + |
85 | +class Serializable(UserDict.IterableUserDict): |
86 | + "Wrapper, an object that can be serialized to yaml or json" |
87 | + |
88 | + def __init__(self, obj): |
89 | + # wrap the object |
90 | + UserDict.IterableUserDict.__init__(self) |
91 | + self.data = obj |
92 | + |
93 | + def __getattr__(self, attr): |
94 | + # See if this object has attribute. |
95 | + if attr in ("json", "yaml", "data"): |
96 | + return self.__dict__[attr] |
97 | + # Check for attribute in wrapped object. |
98 | + got = getattr(self.data, attr, MARKER) |
99 | + if got is not MARKER: |
100 | + return got |
101 | + # Proxy to the wrapped object via dict interface. |
102 | + try: |
103 | + return self.data[attr] |
104 | + except KeyError: |
105 | + raise AttributeError(attr) |
106 | + |
107 | + def __getstate__(self): |
108 | + # Pickle as a standard dictionary. |
109 | + return self.data |
110 | + |
111 | + def __setstate__(self, state): |
112 | + # Unpickle into our wrapper. |
113 | + self.data = state |
114 | + |
115 | + def json(self): |
116 | + "Serialize the object to json" |
117 | + return json.dumps(self.data) |
118 | + |
119 | + def yaml(self): |
120 | + "Serialize the object to yaml" |
121 | + return yaml.dump(self.data) |
122 | + |
123 | + |
124 | +def execution_environment(): |
125 | + """A convenient bundling of the current execution context""" |
126 | + context = {} |
127 | + context['conf'] = config() |
128 | + if relation_id(): |
129 | + context['reltype'] = relation_type() |
130 | + context['relid'] = relation_id() |
131 | + context['rel'] = relation_get() |
132 | + context['unit'] = local_unit() |
133 | + context['rels'] = relations() |
134 | + context['env'] = os.environ |
135 | + return context |
136 | + |
137 | + |
138 | +def in_relation_hook(): |
139 | + "Determine whether we're running in a relation hook" |
140 | + return 'JUJU_RELATION' in os.environ |
141 | + |
142 | + |
143 | +def relation_type(): |
144 | + "The scope for the current relation hook" |
145 | + return os.environ.get('JUJU_RELATION', None) |
146 | + |
147 | + |
148 | +def relation_id(): |
149 | + "The relation ID for the current relation hook" |
150 | + return os.environ.get('JUJU_RELATION_ID', None) |
151 | + |
152 | + |
153 | +def local_unit(): |
154 | + "Local unit ID" |
155 | + return os.environ['JUJU_UNIT_NAME'] |
156 | + |
157 | + |
158 | +def remote_unit(): |
159 | + "The remote unit for the current relation hook" |
160 | + return os.environ['JUJU_REMOTE_UNIT'] |
161 | + |
162 | + |
163 | +def service_name(): |
164 | + "The name service group this unit belongs to" |
165 | + return local_unit().split('/')[0] |
166 | + |
167 | + |
168 | +@cached |
169 | +def config(scope=None): |
170 | + "Juju charm configuration" |
171 | + config_cmd_line = ['config-get'] |
172 | + if scope is not None: |
173 | + config_cmd_line.append(scope) |
174 | + config_cmd_line.append('--format=json') |
175 | + try: |
176 | + return json.loads(subprocess.check_output(config_cmd_line)) |
177 | + except ValueError: |
178 | + return None |
179 | + |
180 | + |
181 | +@cached |
182 | +def relation_get(attribute=None, unit=None, rid=None): |
183 | + _args = ['relation-get', '--format=json'] |
184 | + if rid: |
185 | + _args.append('-r') |
186 | + _args.append(rid) |
187 | + _args.append(attribute or '-') |
188 | + if unit: |
189 | + _args.append(unit) |
190 | + try: |
191 | + return json.loads(subprocess.check_output(_args)) |
192 | + except ValueError: |
193 | + return None |
194 | + |
195 | + |
196 | +def relation_set(relation_id=None, relation_settings={}, **kwargs): |
197 | + relation_cmd_line = ['relation-set'] |
198 | + if relation_id is not None: |
199 | + relation_cmd_line.extend(('-r', relation_id)) |
200 | + for k, v in (relation_settings.items() + kwargs.items()): |
201 | + if v is None: |
202 | + relation_cmd_line.append('{}='.format(k)) |
203 | + else: |
204 | + relation_cmd_line.append('{}={}'.format(k, v)) |
205 | + subprocess.check_call(relation_cmd_line) |
206 | + # Flush cache of any relation-gets for local unit |
207 | + flush(local_unit()) |
208 | + |
209 | + |
210 | +@cached |
211 | +def relation_ids(reltype=None): |
212 | + "A list of relation_ids" |
213 | + reltype = reltype or relation_type() |
214 | + relid_cmd_line = ['relation-ids', '--format=json'] |
215 | + if reltype is not None: |
216 | + relid_cmd_line.append(reltype) |
217 | + return json.loads(subprocess.check_output(relid_cmd_line)) or [] |
218 | + return [] |
219 | + |
220 | + |
221 | +@cached |
222 | +def related_units(relid=None): |
223 | + "A list of related units" |
224 | + relid = relid or relation_id() |
225 | + units_cmd_line = ['relation-list', '--format=json'] |
226 | + if relid is not None: |
227 | + units_cmd_line.extend(('-r', relid)) |
228 | + return json.loads(subprocess.check_output(units_cmd_line)) or [] |
229 | + |
230 | + |
231 | +@cached |
232 | +def relation_for_unit(unit=None, rid=None): |
233 | + "Get the json represenation of a unit's relation" |
234 | + unit = unit or remote_unit() |
235 | + relation = relation_get(unit=unit, rid=rid) |
236 | + for key in relation: |
237 | + if key.endswith('-list'): |
238 | + relation[key] = relation[key].split() |
239 | + relation['__unit__'] = unit |
240 | + return relation |
241 | + |
242 | + |
243 | +@cached |
244 | +def relations_for_id(relid=None): |
245 | + "Get relations of a specific relation ID" |
246 | + relation_data = [] |
247 | + relid = relid or relation_ids() |
248 | + for unit in related_units(relid): |
249 | + unit_data = relation_for_unit(unit, relid) |
250 | + unit_data['__relid__'] = relid |
251 | + relation_data.append(unit_data) |
252 | + return relation_data |
253 | + |
254 | + |
255 | +@cached |
256 | +def relations_of_type(reltype=None): |
257 | + "Get relations of a specific type" |
258 | + relation_data = [] |
259 | + reltype = reltype or relation_type() |
260 | + for relid in relation_ids(reltype): |
261 | + for relation in relations_for_id(relid): |
262 | + relation['__relid__'] = relid |
263 | + relation_data.append(relation) |
264 | + return relation_data |
265 | + |
266 | + |
267 | +@cached |
268 | +def relation_types(): |
269 | + "Get a list of relation types supported by this charm" |
270 | + charmdir = os.environ.get('CHARM_DIR', '') |
271 | + mdf = open(os.path.join(charmdir, 'metadata.yaml')) |
272 | + md = yaml.safe_load(mdf) |
273 | + rel_types = [] |
274 | + for key in ('provides', 'requires', 'peers'): |
275 | + section = md.get(key) |
276 | + if section: |
277 | + rel_types.extend(section.keys()) |
278 | + mdf.close() |
279 | + return rel_types |
280 | + |
281 | + |
282 | +@cached |
283 | +def relations(): |
284 | + rels = {} |
285 | + for reltype in relation_types(): |
286 | + relids = {} |
287 | + for relid in relation_ids(reltype): |
288 | + units = {local_unit(): relation_get(unit=local_unit(), rid=relid)} |
289 | + for unit in related_units(relid): |
290 | + reldata = relation_get(unit=unit, rid=relid) |
291 | + units[unit] = reldata |
292 | + relids[relid] = units |
293 | + rels[reltype] = relids |
294 | + return rels |
295 | + |
296 | + |
297 | +def open_port(port, protocol="TCP"): |
298 | + "Open a service network port" |
299 | + _args = ['open-port'] |
300 | + _args.append('{}/{}'.format(port, protocol)) |
301 | + subprocess.check_call(_args) |
302 | + |
303 | + |
304 | +def close_port(port, protocol="TCP"): |
305 | + "Close a service network port" |
306 | + _args = ['close-port'] |
307 | + _args.append('{}/{}'.format(port, protocol)) |
308 | + subprocess.check_call(_args) |
309 | + |
310 | + |
311 | +@cached |
312 | +def unit_get(attribute): |
313 | + _args = ['unit-get', '--format=json', attribute] |
314 | + try: |
315 | + return json.loads(subprocess.check_output(_args)) |
316 | + except ValueError: |
317 | + return None |
318 | + |
319 | + |
320 | +def unit_private_ip(): |
321 | + return unit_get('private-address') |
322 | + |
323 | + |
324 | +class UnregisteredHookError(Exception): |
325 | + pass |
326 | + |
327 | + |
328 | +class Hooks(object): |
329 | + def __init__(self): |
330 | + super(Hooks, self).__init__() |
331 | + self._hooks = {} |
332 | + |
333 | + def register(self, name, function): |
334 | + self._hooks[name] = function |
335 | + |
336 | + def execute(self, args): |
337 | + hook_name = os.path.basename(args[0]) |
338 | + if hook_name in self._hooks: |
339 | + self._hooks[hook_name]() |
340 | + else: |
341 | + raise UnregisteredHookError(hook_name) |
342 | + |
343 | + def hook(self, *hook_names): |
344 | + def wrapper(decorated): |
345 | + for hook_name in hook_names: |
346 | + self.register(hook_name, decorated) |
347 | + else: |
348 | + self.register(decorated.__name__, decorated) |
349 | + if '_' in decorated.__name__: |
350 | + self.register( |
351 | + decorated.__name__.replace('_', '-'), decorated) |
352 | + return decorated |
353 | + return wrapper |
354 | + |
355 | +def charm_dir(): |
356 | + return os.environ.get('CHARM_DIR') |
357 | |
358 | === added file 'hooks/charmhelpers/core/host.py' |
359 | --- hooks/charmhelpers/core/host.py 1970-01-01 00:00:00 +0000 |
360 | +++ hooks/charmhelpers/core/host.py 2013-07-16 12:39:27 +0000 |
361 | @@ -0,0 +1,272 @@ |
362 | +"""Tools for working with the host system""" |
363 | +# Copyright 2012 Canonical Ltd. |
364 | +# |
365 | +# Authors: |
366 | +# Nick Moffitt <nick.moffitt@canonical.com> |
367 | +# Matthew Wedgwood <matthew.wedgwood@canonical.com> |
368 | + |
369 | +import apt_pkg |
370 | +import os |
371 | +import pwd |
372 | +import grp |
373 | +import subprocess |
374 | +import hashlib |
375 | + |
376 | +from collections import OrderedDict |
377 | + |
378 | +from hookenv import log, execution_environment |
379 | + |
380 | + |
381 | +def service_start(service_name): |
382 | + service('start', service_name) |
383 | + |
384 | + |
385 | +def service_stop(service_name): |
386 | + service('stop', service_name) |
387 | + |
388 | + |
389 | +def service_restart(service_name): |
390 | + service('restart', service_name) |
391 | + |
392 | + |
393 | +def service_reload(service_name, restart_on_failure=False): |
394 | + if not service('reload', service_name) and restart_on_failure: |
395 | + service('restart', service_name) |
396 | + |
397 | + |
398 | +def service(action, service_name): |
399 | + cmd = ['service', service_name, action] |
400 | + return subprocess.call(cmd) == 0 |
401 | + |
402 | + |
403 | +def service_running(service): |
404 | + try: |
405 | + output = subprocess.check_output(['service', service, 'status']) |
406 | + except subprocess.CalledProcessError: |
407 | + return False |
408 | + else: |
409 | + if ("start/running" in output or "is running" in output): |
410 | + return True |
411 | + else: |
412 | + return False |
413 | + |
414 | + |
415 | +def adduser(username, password=None, shell='/bin/bash', system_user=False): |
416 | + """Add a user""" |
417 | + try: |
418 | + user_info = pwd.getpwnam(username) |
419 | + log('user {0} already exists!'.format(username)) |
420 | + except KeyError: |
421 | + log('creating user {0}'.format(username)) |
422 | + cmd = ['useradd'] |
423 | + if system_user or password is None: |
424 | + cmd.append('--system') |
425 | + else: |
426 | + cmd.extend([ |
427 | + '--create-home', |
428 | + '--shell', shell, |
429 | + '--password', password, |
430 | + ]) |
431 | + cmd.append(username) |
432 | + subprocess.check_call(cmd) |
433 | + user_info = pwd.getpwnam(username) |
434 | + return user_info |
435 | + |
436 | + |
437 | +def add_user_to_group(username, group): |
438 | + """Add a user to a group""" |
439 | + cmd = [ |
440 | + 'gpasswd', '-a', |
441 | + username, |
442 | + group |
443 | + ] |
444 | + log("Adding user {} to group {}".format(username, group)) |
445 | + subprocess.check_call(cmd) |
446 | + |
447 | + |
448 | +def rsync(from_path, to_path, flags='-r', options=None): |
449 | + """Replicate the contents of a path""" |
450 | + context = execution_environment() |
451 | + options = options or ['--delete', '--executability'] |
452 | + cmd = ['/usr/bin/rsync', flags] |
453 | + cmd.extend(options) |
454 | + cmd.append(from_path.format(**context)) |
455 | + cmd.append(to_path.format(**context)) |
456 | + log(" ".join(cmd)) |
457 | + return subprocess.check_output(cmd).strip() |
458 | + |
459 | + |
460 | +def symlink(source, destination): |
461 | + """Create a symbolic link""" |
462 | + context = execution_environment() |
463 | + log("Symlinking {} as {}".format(source, destination)) |
464 | + cmd = [ |
465 | + 'ln', |
466 | + '-sf', |
467 | + source.format(**context), |
468 | + destination.format(**context) |
469 | + ] |
470 | + subprocess.check_call(cmd) |
471 | + |
472 | + |
473 | +def mkdir(path, owner='root', group='root', perms=0555, force=False): |
474 | + """Create a directory""" |
475 | + context = execution_environment() |
476 | + log("Making dir {} {}:{} {:o}".format(path, owner, group, |
477 | + perms)) |
478 | + uid = pwd.getpwnam(owner.format(**context)).pw_uid |
479 | + gid = grp.getgrnam(group.format(**context)).gr_gid |
480 | + realpath = os.path.abspath(path) |
481 | + if os.path.exists(realpath): |
482 | + if force and not os.path.isdir(realpath): |
483 | + log("Removing non-directory file {} prior to mkdir()".format(path)) |
484 | + os.unlink(realpath) |
485 | + else: |
486 | + os.makedirs(realpath, perms) |
487 | + os.chown(realpath, uid, gid) |
488 | + |
489 | + |
490 | +def write_file(path, content, owner='root', group='root', perms=0444): |
491 | + """Create or overwrite a file with the contents of a string""" |
492 | + log("Writing file {} {}:{} {:o}".format(path, owner, group, perms)) |
493 | + uid = pwd.getpwnam(owner).pw_uid |
494 | + gid = grp.getgrnam(group).gr_gid |
495 | + with open(path, 'w') as target: |
496 | + os.fchown(target.fileno(), uid, gid) |
497 | + os.fchmod(target.fileno(), perms) |
498 | + target.write(content) |
499 | + |
500 | + |
501 | +def filter_installed_packages(packages): |
502 | + """Returns a list of packages that require installation""" |
503 | + apt_pkg.init() |
504 | + cache = apt_pkg.Cache() |
505 | + _pkgs = [] |
506 | + for package in packages: |
507 | + try: |
508 | + p = cache[package] |
509 | + p.current_ver or _pkgs.append(package) |
510 | + except KeyError: |
511 | + log('Package {} has no installation candidate.'.format(package), |
512 | + level='WARNING') |
513 | + _pkgs.append(package) |
514 | + return _pkgs |
515 | + |
516 | + |
517 | +def apt_install(packages, options=None, fatal=False): |
518 | + """Install one or more packages""" |
519 | + options = options or [] |
520 | + cmd = ['apt-get', '-y'] |
521 | + cmd.extend(options) |
522 | + cmd.append('install') |
523 | + if isinstance(packages, basestring): |
524 | + cmd.append(packages) |
525 | + else: |
526 | + cmd.extend(packages) |
527 | + log("Installing {} with options: {}".format(packages, |
528 | + options)) |
529 | + if fatal: |
530 | + subprocess.check_call(cmd) |
531 | + else: |
532 | + subprocess.call(cmd) |
533 | + |
534 | + |
535 | +def apt_update(fatal=False): |
536 | + """Update local apt cache""" |
537 | + cmd = ['apt-get', 'update'] |
538 | + if fatal: |
539 | + subprocess.check_call(cmd) |
540 | + else: |
541 | + subprocess.call(cmd) |
542 | + |
543 | + |
544 | +def mount(device, mountpoint, options=None, persist=False): |
545 | + '''Mount a filesystem''' |
546 | + cmd_args = ['mount'] |
547 | + if options is not None: |
548 | + cmd_args.extend(['-o', options]) |
549 | + cmd_args.extend([device, mountpoint]) |
550 | + try: |
551 | + subprocess.check_output(cmd_args) |
552 | + except subprocess.CalledProcessError, e: |
553 | + log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output)) |
554 | + return False |
555 | + if persist: |
556 | + # TODO: update fstab |
557 | + pass |
558 | + return True |
559 | + |
560 | + |
561 | +def umount(mountpoint, persist=False): |
562 | + '''Unmount a filesystem''' |
563 | + cmd_args = ['umount', mountpoint] |
564 | + try: |
565 | + subprocess.check_output(cmd_args) |
566 | + except subprocess.CalledProcessError, e: |
567 | + log('Error unmounting {}\n{}'.format(mountpoint, e.output)) |
568 | + return False |
569 | + if persist: |
570 | + # TODO: update fstab |
571 | + pass |
572 | + return True |
573 | + |
574 | + |
575 | +def mounts(): |
576 | + '''List of all mounted volumes as [[mountpoint,device],[...]]''' |
577 | + with open('/proc/mounts') as f: |
578 | + # [['/mount/point','/dev/path'],[...]] |
579 | + system_mounts = [m[1::-1] for m in [l.strip().split() |
580 | + for l in f.readlines()]] |
581 | + return system_mounts |
582 | + |
583 | + |
584 | +def file_hash(path): |
585 | + ''' Generate a md5 hash of the contents of 'path' or None if not found ''' |
586 | + if os.path.exists(path): |
587 | + h = hashlib.md5() |
588 | + with open(path, 'r') as source: |
589 | + h.update(source.read()) # IGNORE:E1101 - it does have update |
590 | + return h.hexdigest() |
591 | + else: |
592 | + return None |
593 | + |
594 | + |
595 | +def restart_on_change(restart_map): |
596 | + ''' Restart services based on configuration files changing |
597 | + |
598 | + This function is used a decorator, for example |
599 | + |
600 | + @restart_on_change({ |
601 | + '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ] |
602 | + }) |
603 | + def ceph_client_changed(): |
604 | + ... |
605 | + |
606 | + In this example, the cinder-api and cinder-volume services |
607 | + would be restarted if /etc/ceph/ceph.conf is changed by the |
608 | + ceph_client_changed function. |
609 | + ''' |
610 | + def wrap(f): |
611 | + def wrapped_f(*args): |
612 | + checksums = {} |
613 | + for path in restart_map: |
614 | + checksums[path] = file_hash(path) |
615 | + f(*args) |
616 | + restarts = [] |
617 | + for path in restart_map: |
618 | + if checksums[path] != file_hash(path): |
619 | + restarts += restart_map[path] |
620 | + for service_name in list(OrderedDict.fromkeys(restarts)): |
621 | + service('restart', service_name) |
622 | + return wrapped_f |
623 | + return wrap |
624 | + |
625 | + |
626 | +def lsb_release(): |
627 | + '''Return /etc/lsb-release in a dict''' |
628 | + d = {} |
629 | + with open('/etc/lsb-release', 'r') as lsb: |
630 | + for l in lsb: |
631 | + k, v = l.split('=') |
632 | + d[k.strip()] = v.strip() |
633 | + return d |
634 | |
635 | === modified file 'hooks/hooks.py' |
636 | --- hooks/hooks.py 2013-05-28 09:53:30 +0000 |
637 | +++ hooks/hooks.py 2013-07-16 12:39:27 +0000 |
638 | @@ -1,89 +1,52 @@ |
639 | #!/usr/bin/env python |
640 | |
641 | -from grp import getgrnam |
642 | -import json |
643 | import os.path |
644 | -from pwd import getpwnam |
645 | import shutil |
646 | -import subprocess |
647 | +import sys |
648 | from textwrap import dedent |
649 | |
650 | +from charmhelpers.core import hookenv, host |
651 | +from charmhelpers.core.hookenv import log, DEBUG, INFO |
652 | + |
653 | |
654 | CLIENT_RELATION_TYPES = frozenset(['db', 'db-admin']) |
655 | |
656 | |
657 | -def relation_ids(relation_types): |
658 | - relids = [] |
659 | - for reltype in relation_types: |
660 | - relid_cmd_line = ['relation-ids', '--format=json', reltype] |
661 | - json_relids = subprocess.check_output(relid_cmd_line).strip() |
662 | - if json_relids: |
663 | - relids.extend(json.loads(json_relids)) |
664 | - return relids |
665 | - |
666 | - |
667 | -def relation_list(relation_id): |
668 | - """Return the list of units participating in the relation.""" |
669 | - cmd = ['relation-list', '--format=json', '-r', relation_id] |
670 | - json_units = subprocess.check_output(cmd).strip() |
671 | - if json_units: |
672 | - return json.loads(json_units) |
673 | - return [] |
674 | - |
675 | - |
676 | -def relation_get(relation_id, unit): |
677 | - cmd = ['relation-get', '--format=json', '-r', relation_id, '-', unit] |
678 | - json_relation = subprocess.check_output(cmd) |
679 | - if json_relation: |
680 | - return json.loads(json_relation) |
681 | - return {} |
682 | - |
683 | - |
684 | -def relation_set(relation_id, keyvalues): |
685 | - command = ['relation-set', '-r', relation_id] |
686 | - command.extend( |
687 | - ["{}={}".format(k, v or '') for k, v in keyvalues.items()]) |
688 | - subprocess.check_call(command) |
689 | - |
690 | - |
691 | -def config_get(): |
692 | - command = ['config-get', '--format=json'] |
693 | - json_config = subprocess.check_output(command) |
694 | - if json_config: |
695 | - return json.loads(json_config) |
696 | - return {} |
697 | - |
698 | - |
699 | def all_relations(relation_types=CLIENT_RELATION_TYPES): |
700 | for reltype in relation_types: |
701 | - for relid in relation_ids(relation_types=[reltype]): |
702 | - for unit in relation_list(relation_id=relid): |
703 | - yield reltype, relid, unit, relation_get(relid, unit) |
704 | - |
705 | - |
706 | + for relid in hookenv.relation_ids(reltype): |
707 | + for unit in hookenv.related_units(relid): |
708 | + yield reltype, relid, unit, hookenv.relation_get( |
709 | + unit=unit, rid=relid) |
710 | + |
711 | +hooks = hookenv.Hooks() |
712 | + |
713 | +@hooks.hook( |
714 | + 'config-changed', 'upgrade-charm', |
715 | + 'db-relation-changed', 'db-relation-joined', |
716 | + 'db-admin-relation-changed', 'db-admin-relation-joined') |
717 | def rebuild_all_relations(): |
718 | - config = config_get() |
719 | + config = hookenv.config() |
720 | |
721 | # Clear out old scripts and pgpass files |
722 | if os.path.exists('bin'): |
723 | shutil.rmtree('bin') |
724 | if os.path.exists('pgpass'): |
725 | shutil.rmtree('pgpass') |
726 | - install_dir('bin', mode=0o755) |
727 | - install_dir('pgpass', group='ubuntu', mode=0o750) |
728 | + host.mkdir('bin', perms=0o755) |
729 | + host.mkdir('pgpass', group='ubuntu', perms=0o750) |
730 | |
731 | for _, relid, unit, relation in all_relations(relation_types=['db']): |
732 | - juju_log(MSG_DEBUG, "{} {} {!r}".format(relid, unit, relation)) |
733 | + log("{} {} {!r}".format(relid, unit, relation), DEBUG) |
734 | if config['database'] and relation['database'] != config['database']: |
735 | - juju_log( |
736 | - MSG_INFO, "Overriding generated database {} with {}".format( |
737 | - relation['database'], config['database'])) |
738 | - relation_set(relid, dict(database=config['database'])) |
739 | + log("Overriding generated database {} with {}".format( |
740 | + relation['database'], config['database']), INFO) |
741 | + hookenv.relation_set(relid, database=config['database']) |
742 | elif 'user' in relation: |
743 | rebuild_relation(relid, unit, relation) |
744 | |
745 | for _, relid, unit, relation in all_relations(relation_types=['db-admin']): |
746 | - juju_log(MSG_DEBUG, "{} {} {!r}".format(relid, unit, relation)) |
747 | + log("{} {} {!r}".format(relid, unit, relation), DEBUG) |
748 | if 'user' in relation: |
749 | rebuild_relation(relid, unit, relation) |
750 | |
751 | @@ -91,11 +54,17 @@ |
752 | def rebuild_relation(relid, unit, relation): |
753 | relname = relid.split(':')[0] |
754 | unitname = unit.replace('/', '-') |
755 | + this_unit = hookenv.local_unit() |
756 | + |
757 | + allowed_units = relation.get('allowed-units', '') |
758 | + if this_unit not in allowed_units.split(): |
759 | + log("Not yet authorized on {}".format(relid), INFO) |
760 | + return |
761 | |
762 | script_name = 'psql-{}-{}'.format(relname, unitname) |
763 | build_script(script_name, relation) |
764 | state = relation.get('state', None) |
765 | - if state and state != 'standalone': |
766 | + if state in ('master', 'hot standby'): |
767 | script_name = 'psql-{}-{}'.format(relname, state.replace(' ', '-')) |
768 | build_script(script_name, relation) |
769 | |
770 | @@ -117,45 +86,18 @@ |
771 | database=relation.get('database', ''), # db-admin has no database |
772 | user=relation['user'], |
773 | pgpass=pgpass_path) |
774 | - juju_log(MSG_INFO, "Generating wrapper {}".format(script_path)) |
775 | - install_file( |
776 | - script, script_path, owner="ubuntu", group="ubuntu", mode=0o700) |
777 | + log("Generating wrapper {}".format(script_path), INFO) |
778 | + host.write_file( |
779 | + script_path, script, owner="ubuntu", group="ubuntu", perms=0o700) |
780 | |
781 | # The wrapper requires access to the password, stored in a .pgpass |
782 | # file so it isn't exposed in an environment variable or on the |
783 | # command line. |
784 | pgpass = "*:*:*:{user}:{password}".format( |
785 | user=relation['user'], password=relation['password']) |
786 | - install_file( |
787 | - pgpass, pgpass_path, owner="ubuntu", group="ubuntu", mode=0o400) |
788 | - |
789 | - |
790 | -def install_file(contents, dest, owner="root", group="root", mode=0o600): |
791 | - uid = getpwnam(owner)[2] |
792 | - gid = getgrnam(group)[2] |
793 | - dest_fd = os.open(dest, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) |
794 | - os.fchown(dest_fd, uid, gid) |
795 | - with os.fdopen(dest_fd, 'w') as destfile: |
796 | - destfile.write(str(contents)) |
797 | - |
798 | - |
799 | -def install_dir(dirname, owner="root", group="root", mode=0o700): |
800 | - command = [ |
801 | - '/usr/bin/install', |
802 | - '-o', owner, '-g', group, '-m', oct(mode), '-d', dirname] |
803 | - subprocess.check_call(command) |
804 | - |
805 | - |
806 | -MSG_CRITICAL = "CRITICAL" |
807 | -MSG_DEBUG = "DEBUG" |
808 | -MSG_INFO = "INFO" |
809 | -MSG_ERROR = "ERROR" |
810 | -MSG_WARNING = "WARNING" |
811 | - |
812 | - |
813 | -def juju_log(level, msg): |
814 | - subprocess.call(['juju-log', '-l', level, msg]) |
815 | + host.write_file( |
816 | + pgpass_path, pgpass, owner="ubuntu", group="ubuntu", mode=0o400) |
817 | |
818 | |
819 | if __name__ == '__main__': |
820 | - raise SystemExit(rebuild_all_relations()) |
821 | + hooks.execute(sys.argv) |
822 | |
823 | === modified file 'icon.svg' |
824 | --- icon.svg 2013-06-04 11:32:34 +0000 |
825 | +++ icon.svg 2013-07-16 12:39:27 +0000 |
826 | @@ -1,4 +1,6 @@ |
827 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> |
828 | +<!-- Created with Inkscape (http://www.inkscape.org/) --> |
829 | + |
830 | <svg |
831 | xmlns:dc="http://purl.org/dc/elements/1.1/" |
832 | xmlns:cc="http://creativecommons.org/ns#" |
833 | @@ -7,86 +9,350 @@ |
834 | xmlns="http://www.w3.org/2000/svg" |
835 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |
836 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |
837 | - width="432.071pt" |
838 | - height="445.383pt" |
839 | - viewBox="0 0 432.071 445.383" |
840 | - xml:space="preserve" |
841 | - id="svg2" |
842 | + width="96" |
843 | + height="96" |
844 | + id="svg6517" |
845 | version="1.1" |
846 | inkscape:version="0.48.4 r9939" |
847 | - sodipodi:docname="icon.svg"><metadata |
848 | - id="metadata34"><rdf:RDF><cc:Work |
849 | - rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type |
850 | - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs |
851 | - id="defs32" /><sodipodi:namedview |
852 | + sodipodi:docname="icon.svg"> |
853 | + <defs |
854 | + id="defs6519"> |
855 | + <linearGradient |
856 | + id="linearGradient3947"> |
857 | + <stop |
858 | + style="stop-color:#ffffff;stop-opacity:1;" |
859 | + offset="0" |
860 | + id="stop3949" /> |
861 | + <stop |
862 | + style="stop-color:#ffffff;stop-opacity:0;" |
863 | + offset="1" |
864 | + id="stop3951" /> |
865 | + </linearGradient> |
866 | + <linearGradient |
867 | + id="Background"> |
868 | + <stop |
869 | + id="stop4178" |
870 | + offset="0" |
871 | + style="stop-color:#b8b8b8;stop-opacity:1" /> |
872 | + <stop |
873 | + id="stop4180" |
874 | + offset="1" |
875 | + style="stop-color:#c9c9c9;stop-opacity:1" /> |
876 | + </linearGradient> |
877 | + <filter |
878 | + style="color-interpolation-filters:sRGB;" |
879 | + inkscape:label="Inner Shadow" |
880 | + id="filter1121"> |
881 | + <feFlood |
882 | + flood-opacity="0.59999999999999998" |
883 | + flood-color="rgb(0,0,0)" |
884 | + result="flood" |
885 | + id="feFlood1123" /> |
886 | + <feComposite |
887 | + in="flood" |
888 | + in2="SourceGraphic" |
889 | + operator="out" |
890 | + result="composite1" |
891 | + id="feComposite1125" /> |
892 | + <feGaussianBlur |
893 | + in="composite1" |
894 | + stdDeviation="1" |
895 | + result="blur" |
896 | + id="feGaussianBlur1127" /> |
897 | + <feOffset |
898 | + dx="0" |
899 | + dy="2" |
900 | + result="offset" |
901 | + id="feOffset1129" /> |
902 | + <feComposite |
903 | + in="offset" |
904 | + in2="SourceGraphic" |
905 | + operator="atop" |
906 | + result="composite2" |
907 | + id="feComposite1131" /> |
908 | + </filter> |
909 | + <filter |
910 | + style="color-interpolation-filters:sRGB;" |
911 | + inkscape:label="Drop Shadow" |
912 | + id="filter950"> |
913 | + <feFlood |
914 | + flood-opacity="0.25" |
915 | + flood-color="rgb(0,0,0)" |
916 | + result="flood" |
917 | + id="feFlood952" /> |
918 | + <feComposite |
919 | + in="flood" |
920 | + in2="SourceGraphic" |
921 | + operator="in" |
922 | + result="composite1" |
923 | + id="feComposite954" /> |
924 | + <feGaussianBlur |
925 | + in="composite1" |
926 | + stdDeviation="1" |
927 | + result="blur" |
928 | + id="feGaussianBlur956" /> |
929 | + <feOffset |
930 | + dx="0" |
931 | + dy="1" |
932 | + result="offset" |
933 | + id="feOffset958" /> |
934 | + <feComposite |
935 | + in="SourceGraphic" |
936 | + in2="offset" |
937 | + operator="over" |
938 | + result="composite2" |
939 | + id="feComposite960" /> |
940 | + </filter> |
941 | + <clipPath |
942 | + clipPathUnits="userSpaceOnUse" |
943 | + id="clipPath873"> |
944 | + <g |
945 | + transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)" |
946 | + id="g875" |
947 | + inkscape:label="Layer 1" |
948 | + style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"> |
949 | + <path |
950 | + style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline" |
951 | + 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" |
952 | + id="path877" |
953 | + inkscape:connector-curvature="0" |
954 | + sodipodi:nodetypes="sssssssss" /> |
955 | + </g> |
956 | + </clipPath> |
957 | + <filter |
958 | + inkscape:collect="always" |
959 | + id="filter891" |
960 | + inkscape:label="Badge Shadow"> |
961 | + <feGaussianBlur |
962 | + inkscape:collect="always" |
963 | + stdDeviation="0.71999962" |
964 | + id="feGaussianBlur893" /> |
965 | + </filter> |
966 | + </defs> |
967 | + <sodipodi:namedview |
968 | + id="base" |
969 | pagecolor="#ffffff" |
970 | bordercolor="#666666" |
971 | - borderopacity="1" |
972 | - objecttolerance="10" |
973 | - gridtolerance="10" |
974 | - guidetolerance="10" |
975 | - inkscape:pageopacity="0" |
976 | + borderopacity="1.0" |
977 | + inkscape:pageopacity="0.0" |
978 | inkscape:pageshadow="2" |
979 | - inkscape:window-width="1111" |
980 | - inkscape:window-height="783" |
981 | - id="namedview30" |
982 | - showgrid="false" |
983 | - inkscape:zoom="0.42390481" |
984 | - inkscape:cx="270.04437" |
985 | - inkscape:cy="278.36438" |
986 | - inkscape:window-x="195" |
987 | - inkscape:window-y="148" |
988 | + inkscape:zoom="4.0745362" |
989 | + inkscape:cx="49.256842" |
990 | + inkscape:cy="43.407288" |
991 | + inkscape:document-units="px" |
992 | + inkscape:current-layer="layer3" |
993 | + showgrid="true" |
994 | + fit-margin-top="0" |
995 | + fit-margin-left="0" |
996 | + fit-margin-right="0" |
997 | + fit-margin-bottom="0" |
998 | + inkscape:window-width="1417" |
999 | + inkscape:window-height="799" |
1000 | + inkscape:window-x="65" |
1001 | + inkscape:window-y="24" |
1002 | inkscape:window-maximized="0" |
1003 | - inkscape:current-layer="svg2" /><g |
1004 | - id="orginal" |
1005 | - style="fill-rule:nonzero;clip-rule:nonzero;stroke:#000000;stroke-miterlimit:4;" /><g |
1006 | - id="Layer_x0020_3" |
1007 | - style="fill-rule:nonzero;clip-rule:nonzero;fill:none;stroke:#FFFFFF;stroke-width:12.4651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;"><path |
1008 | - style="fill:#000000;stroke:#000000;stroke-width:37.3953;stroke-linecap:butt;stroke-linejoin:miter;" |
1009 | - d="M323.205,324.227c2.833-23.601,1.984-27.062,19.563-23.239l4.463,0.392c13.517,0.615,31.199-2.174,41.587-7c22.362-10.376,35.622-27.7,13.572-23.148c-50.297,10.376-53.755-6.655-53.755-6.655c53.111-78.803,75.313-178.836,56.149-203.322 C352.514-5.534,262.036,26.049,260.522,26.869l-0.482,0.089c-9.938-2.062-21.06-3.294-33.554-3.496c-22.761-0.374-40.032,5.967-53.133,15.904c0,0-161.408-66.498-153.899,83.628c1.597,31.936,45.777,241.655,98.47,178.31 c19.259-23.163,37.871-42.748,37.871-42.748c9.242,6.14,20.307,9.272,31.912,8.147l0.897-0.765c-0.281,2.876-0.157,5.689,0.359,9.019c-13.572,15.167-9.584,17.83-36.723,23.416c-27.457,5.659-11.326,15.734-0.797,18.367c12.768,3.193,42.305,7.716,62.268-20.224 l-0.795,3.188c5.325,4.26,4.965,30.619,5.72,49.452c0.756,18.834,2.017,36.409,5.856,46.771c3.839,10.36,8.369,37.05,44.036,29.406c29.809-6.388,52.6-15.582,54.677-101.107" |
1010 | - id="path6" /><path |
1011 | - style="fill:#336791;stroke:none;" |
1012 | - d="M402.395,271.23c-50.302,10.376-53.76-6.655-53.76-6.655c53.111-78.808,75.313-178.843,56.153-203.326c-52.27-66.785-142.752-35.2-144.262-34.38l-0.486,0.087c-9.938-2.063-21.06-3.292-33.56-3.496c-22.761-0.373-40.026,5.967-53.127,15.902 c0,0-161.411-66.495-153.904,83.63c1.597,31.938,45.776,241.657,98.471,178.312c19.26-23.163,37.869-42.748,37.869-42.748c9.243,6.14,20.308,9.272,31.908,8.147l0.901-0.765c-0.28,2.876-0.152,5.689,0.361,9.019c-13.575,15.167-9.586,17.83-36.723,23.416 c-27.459,5.659-11.328,15.734-0.796,18.367c12.768,3.193,42.307,7.716,62.266-20.224l-0.796,3.188c5.319,4.26,9.054,27.711,8.428,48.969c-0.626,21.259-1.044,35.854,3.147,47.254c4.191,11.4,8.368,37.05,44.042,29.406c29.809-6.388,45.256-22.942,47.405-50.555 c1.525-19.631,4.976-16.729,5.194-34.28l2.768-8.309c3.192-26.611,0.507-35.196,18.872-31.203l4.463,0.392c13.517,0.615,31.208-2.174,41.591-7c22.358-10.376,35.618-27.7,13.573-23.148z" |
1013 | - id="path8" /><path |
1014 | - d="M215.866,286.484c-1.385,49.516,0.348,99.377,5.193,111.495c4.848,12.118,15.223,35.688,50.9,28.045c29.806-6.39,40.651-18.756,45.357-46.051c3.466-20.082,10.148-75.854,11.005-87.281" |
1015 | - id="path10" /><path |
1016 | - d="M173.104,38.256c0,0-161.521-66.016-154.012,84.109c1.597,31.938,45.779,241.664,98.473,178.316c19.256-23.166,36.671-41.335,36.671-41.335" |
1017 | - id="path12" /><path |
1018 | - d="M260.349,26.207c-5.591,1.753,89.848-34.889,144.087,34.417c19.159,24.484-3.043,124.519-56.153,203.329" |
1019 | - id="path14" /><path |
1020 | - style="stroke-linejoin:bevel;" |
1021 | - d="M348.282,263.953c0,0,3.461,17.036,53.764,6.653c22.04-4.552,8.776,12.774-13.577,23.155c-18.345,8.514-59.474,10.696-60.146-1.069c-1.729-30.355,21.647-21.133,19.96-28.739c-1.525-6.85-11.979-13.573-18.894-30.338 c-6.037-14.633-82.796-126.849,21.287-110.183c3.813-0.789-27.146-99.002-124.553-100.599c-97.385-1.597-94.19,119.762-94.19,119.762" |
1022 | - id="path16" /><path |
1023 | - d="M188.604,274.334c-13.577,15.166-9.584,17.829-36.723,23.417c-27.459,5.66-11.326,15.733-0.797,18.365c12.768,3.195,42.307,7.718,62.266-20.229c6.078-8.509-0.036-22.086-8.385-25.547c-4.034-1.671-9.428-3.765-16.361,3.994z" |
1024 | - id="path18" /><path |
1025 | - d="M187.715,274.069c-1.368-8.917,2.93-19.528,7.536-31.942c6.922-18.626,22.893-37.255,10.117-96.339c-9.523-44.029-73.396-9.163-73.436-3.193c-0.039,5.968,2.889,30.26-1.067,58.548c-5.162,36.913,23.488,68.132,56.479,64.938" |
1026 | - id="path20" /><path |
1027 | - style="fill:#FFFFFF;stroke-width:4.155;stroke-linecap:butt;stroke-linejoin:miter;" |
1028 | - d="M172.517,141.7c-0.288,2.039,3.733,7.48,8.976,8.207c5.234,0.73,9.714-3.522,9.998-5.559c0.284-2.039-3.732-4.285-8.977-5.015c-5.237-0.731-9.719,0.333-9.996,2.367z" |
1029 | - id="path22" /><path |
1030 | - style="fill:#FFFFFF;stroke-width:2.0775;stroke-linecap:butt;stroke-linejoin:miter;" |
1031 | - d="M331.941,137.543c0.284,2.039-3.732,7.48-8.976,8.207c-5.238,0.73-9.718-3.522-10.005-5.559c-0.277-2.039,3.74-4.285,8.979-5.015c5.239-0.73,9.718,0.333,10.002,2.368z" |
1032 | - id="path24" /><path |
1033 | - d="M350.676,123.432c0.863,15.994-3.445,26.888-3.988,43.914c-0.804,24.748,11.799,53.074-7.191,81.435" |
1034 | - id="path26" /><path |
1035 | - style="stroke-width:3;" |
1036 | - d="M0,60.232" |
1037 | - id="path28" /></g><rect |
1038 | - style="fill:#4d4d4d;stroke:#000000;stroke-width:0.73861116" |
1039 | - id="rect3015" |
1040 | - width="258.81003" |
1041 | - height="105.94549" |
1042 | - x="-2.0179098" |
1043 | - y="341.45541" /><text |
1044 | - xml:space="preserve" |
1045 | - style="font-size:68.27682495px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Courier 10 Pitch;-inkscape-font-specification:Courier 10 Pitch" |
1046 | - x="10.404695" |
1047 | - y="396.7569" |
1048 | - id="text3011-6" |
1049 | - sodipodi:linespacing="125%" |
1050 | - transform="scale(0.96172302,1.0398004)"><tspan |
1051 | - sodipodi:role="line" |
1052 | - id="tspan3013-6" |
1053 | - x="10.404695" |
1054 | - y="396.7569">psql>_</tspan></text> |
1055 | -</svg> |
1056 | \ No newline at end of file |
1057 | + showborder="true" |
1058 | + showguides="true" |
1059 | + inkscape:guide-bbox="true" |
1060 | + inkscape:showpageshadow="false"> |
1061 | + <inkscape:grid |
1062 | + type="xygrid" |
1063 | + id="grid821" /> |
1064 | + <sodipodi:guide |
1065 | + orientation="1,0" |
1066 | + position="16,48" |
1067 | + id="guide823" /> |
1068 | + <sodipodi:guide |
1069 | + orientation="0,1" |
1070 | + position="64,80" |
1071 | + id="guide825" /> |
1072 | + <sodipodi:guide |
1073 | + orientation="1,0" |
1074 | + position="80,40" |
1075 | + id="guide827" /> |
1076 | + <sodipodi:guide |
1077 | + orientation="0,1" |
1078 | + position="64,16" |
1079 | + id="guide829" /> |
1080 | + </sodipodi:namedview> |
1081 | + <metadata |
1082 | + id="metadata6522"> |
1083 | + <rdf:RDF> |
1084 | + <cc:Work |
1085 | + rdf:about=""> |
1086 | + <dc:format>image/svg+xml</dc:format> |
1087 | + <dc:type |
1088 | + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> |
1089 | + <dc:title></dc:title> |
1090 | + </cc:Work> |
1091 | + </rdf:RDF> |
1092 | + </metadata> |
1093 | + <g |
1094 | + inkscape:label="BACKGROUND" |
1095 | + inkscape:groupmode="layer" |
1096 | + id="layer1" |
1097 | + transform="translate(268,-635.29076)" |
1098 | + style="display:inline"> |
1099 | + <path |
1100 | + style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121);opacity:1" |
1101 | + 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" |
1102 | + id="path6455" |
1103 | + inkscape:connector-curvature="0" |
1104 | + sodipodi:nodetypes="sssssssss" /> |
1105 | + </g> |
1106 | + <g |
1107 | + inkscape:groupmode="layer" |
1108 | + id="layer3" |
1109 | + inkscape:label="PLACE YOUR PICTOGRAM HERE" |
1110 | + style="display:inline"> |
1111 | + <g |
1112 | + transform="matrix(0.18231452,0,0,0.16578318,13.59438,4.5326341)" |
1113 | + id="Layer_x0020_3" |
1114 | + style="fill:none;stroke:#ffffff;stroke-width:12.46510029;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;display:inline"> |
1115 | + <path |
1116 | + inkscape:connector-curvature="0" |
1117 | + style="fill:#000000;stroke:#000000;stroke-width:37.39530182;stroke-linecap:butt;stroke-linejoin:miter" |
1118 | + d="m 323.205,324.227 c 2.833,-23.601 1.984,-27.062 19.563,-23.239 l 4.463,0.392 c 13.517,0.615 31.199,-2.174 41.587,-7 22.362,-10.376 35.622,-27.7 13.572,-23.148 -50.297,10.376 -53.755,-6.655 -53.755,-6.655 C 401.746,185.774 423.948,85.741 404.784,61.255 352.514,-5.534 262.036,26.049 260.522,26.869 l -0.482,0.089 c -9.938,-2.062 -21.06,-3.294 -33.554,-3.496 -22.761,-0.374 -40.032,5.967 -53.133,15.904 0,0 -161.408,-66.498 -153.899,83.628 1.597,31.936 45.777,241.655 98.47,178.31 19.259,-23.163 37.871,-42.748 37.871,-42.748 9.242,6.14 20.307,9.272 31.912,8.147 l 0.897,-0.765 c -0.281,2.876 -0.157,5.689 0.359,9.019 -13.572,15.167 -9.584,17.83 -36.723,23.416 -27.457,5.659 -11.326,15.734 -0.797,18.367 12.768,3.193 42.305,7.716 62.268,-20.224 l -0.795,3.188 c 5.325,4.26 4.965,30.619 5.72,49.452 0.756,18.834 2.017,36.409 5.856,46.771 3.839,10.36 8.369,37.05 44.036,29.406 29.809,-6.388 52.6,-15.582 54.677,-101.107" |
1119 | + id="path3095" /> |
1120 | + <path |
1121 | + inkscape:connector-curvature="0" |
1122 | + style="fill:#336791;stroke:none" |
1123 | + d="m 402.395,271.23 c -50.302,10.376 -53.76,-6.655 -53.76,-6.655 53.111,-78.808 75.313,-178.843 56.153,-203.326 -52.27,-66.785 -142.752,-35.2 -144.262,-34.38 l -0.486,0.087 c -9.938,-2.063 -21.06,-3.292 -33.56,-3.496 -22.761,-0.373 -40.026,5.967 -53.127,15.902 0,0 -161.411,-66.495 -153.904,83.63 1.597,31.938 45.776,241.657 98.471,178.312 19.26,-23.163 37.869,-42.748 37.869,-42.748 9.243,6.14 20.308,9.272 31.908,8.147 l 0.901,-0.765 c -0.28,2.876 -0.152,5.689 0.361,9.019 -13.575,15.167 -9.586,17.83 -36.723,23.416 -27.459,5.659 -11.328,15.734 -0.796,18.367 12.768,3.193 42.307,7.716 62.266,-20.224 l -0.796,3.188 c 5.319,4.26 9.054,27.711 8.428,48.969 -0.626,21.259 -1.044,35.854 3.147,47.254 4.191,11.4 8.368,37.05 44.042,29.406 29.809,-6.388 45.256,-22.942 47.405,-50.555 1.525,-19.631 4.976,-16.729 5.194,-34.28 l 2.768,-8.309 c 3.192,-26.611 0.507,-35.196 18.872,-31.203 l 4.463,0.392 c 13.517,0.615 31.208,-2.174 41.591,-7 22.358,-10.376 35.618,-27.7 13.573,-23.148 z" |
1124 | + id="path3097" /> |
1125 | + <path |
1126 | + inkscape:connector-curvature="0" |
1127 | + d="m 215.866,286.484 c -1.385,49.516 0.348,99.377 5.193,111.495 4.848,12.118 15.223,35.688 50.9,28.045 29.806,-6.39 40.651,-18.756 45.357,-46.051 3.466,-20.082 10.148,-75.854 11.005,-87.281" |
1128 | + id="path3099" /> |
1129 | + <path |
1130 | + inkscape:connector-curvature="0" |
1131 | + d="m 173.104,38.256 c 0,0 -161.521,-66.016 -154.012,84.109 1.597,31.938 45.779,241.664 98.473,178.316 19.256,-23.166 36.671,-41.335 36.671,-41.335" |
1132 | + id="path3101" /> |
1133 | + <path |
1134 | + inkscape:connector-curvature="0" |
1135 | + d="m 260.349,26.207 c -5.591,1.753 89.848,-34.889 144.087,34.417 19.159,24.484 -3.043,124.519 -56.153,203.329" |
1136 | + id="path3103" /> |
1137 | + <path |
1138 | + inkscape:connector-curvature="0" |
1139 | + style="stroke-linejoin:bevel" |
1140 | + d="m 348.282,263.953 c 0,0 3.461,17.036 53.764,6.653 22.04,-4.552 8.776,12.774 -13.577,23.155 -18.345,8.514 -59.474,10.696 -60.146,-1.069 -1.729,-30.355 21.647,-21.133 19.96,-28.739 -1.525,-6.85 -11.979,-13.573 -18.894,-30.338 -6.037,-14.633 -82.796,-126.849 21.287,-110.183 3.813,-0.789 -27.146,-99.002 -124.553,-100.599 -97.385,-1.597 -94.19,119.762 -94.19,119.762" |
1141 | + id="path3105" /> |
1142 | + <path |
1143 | + inkscape:connector-curvature="0" |
1144 | + d="m 188.604,274.334 c -13.577,15.166 -9.584,17.829 -36.723,23.417 -27.459,5.66 -11.326,15.733 -0.797,18.365 12.768,3.195 42.307,7.718 62.266,-20.229 6.078,-8.509 -0.036,-22.086 -8.385,-25.547 -4.034,-1.671 -9.428,-3.765 -16.361,3.994 z" |
1145 | + id="path3107" /> |
1146 | + <path |
1147 | + inkscape:connector-curvature="0" |
1148 | + d="m 187.715,274.069 c -1.368,-8.917 2.93,-19.528 7.536,-31.942 6.922,-18.626 22.893,-37.255 10.117,-96.339 -9.523,-44.029 -73.396,-9.163 -73.436,-3.193 -0.039,5.968 2.889,30.26 -1.067,58.548 -5.162,36.913 23.488,68.132 56.479,64.938" |
1149 | + id="path3109" /> |
1150 | + <path |
1151 | + inkscape:connector-curvature="0" |
1152 | + style="fill:#ffffff;stroke-width:4.15500021;stroke-linecap:butt;stroke-linejoin:miter" |
1153 | + d="m 172.517,141.7 c -0.288,2.039 3.733,7.48 8.976,8.207 5.234,0.73 9.714,-3.522 9.998,-5.559 0.284,-2.039 -3.732,-4.285 -8.977,-5.015 -5.237,-0.731 -9.719,0.333 -9.996,2.367 z" |
1154 | + id="path3111" /> |
1155 | + <path |
1156 | + inkscape:connector-curvature="0" |
1157 | + style="fill:#ffffff;stroke-width:2.0775001;stroke-linecap:butt;stroke-linejoin:miter" |
1158 | + d="m 331.941,137.543 c 0.284,2.039 -3.732,7.48 -8.976,8.207 -5.238,0.73 -9.718,-3.522 -10.005,-5.559 -0.277,-2.039 3.74,-4.285 8.979,-5.015 5.239,-0.73 9.718,0.333 10.002,2.368 z" |
1159 | + id="path3113" /> |
1160 | + <path |
1161 | + inkscape:connector-curvature="0" |
1162 | + d="m 350.676,123.432 c 0.863,15.994 -3.445,26.888 -3.988,43.914 -0.804,24.748 11.799,53.074 -7.191,81.435" |
1163 | + id="path3115" /> |
1164 | + <path |
1165 | + inkscape:connector-curvature="0" |
1166 | + style="stroke-width:3" |
1167 | + d="M 0,60.232" |
1168 | + id="path3117" /> |
1169 | + </g> |
1170 | + <rect |
1171 | + style="fill:#4d4d4d;stroke:#000000;stroke-width:0.12356114" |
1172 | + id="rect3015" |
1173 | + width="40.634895" |
1174 | + height="18.884109" |
1175 | + x="8.1120977" |
1176 | + y="67.328201" /> |
1177 | + <text |
1178 | + xml:space="preserve" |
1179 | + style="font-size:11.4219265px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Courier 10 Pitch;-inkscape-font-specification:Courier 10 Pitch" |
1180 | + x="11.078934" |
1181 | + y="72.209114" |
1182 | + id="text3011-6" |
1183 | + sodipodi:linespacing="125%" |
1184 | + transform="scale(0.9026139,1.1078934)"><tspan |
1185 | + sodipodi:role="line" |
1186 | + id="tspan3013-6" |
1187 | + x="11.078934" |
1188 | + y="72.209114">psql>_</tspan></text> |
1189 | + </g> |
1190 | + <g |
1191 | + inkscape:groupmode="layer" |
1192 | + id="layer2" |
1193 | + inkscape:label="BADGE" |
1194 | + style="display:none" |
1195 | + sodipodi:insensitive="true"> |
1196 | + <g |
1197 | + style="display:inline" |
1198 | + transform="translate(-340.00001,-581)" |
1199 | + id="g4394" |
1200 | + clip-path="none"> |
1201 | + <g |
1202 | + id="g855"> |
1203 | + <g |
1204 | + inkscape:groupmode="maskhelper" |
1205 | + id="g870" |
1206 | + clip-path="url(#clipPath873)" |
1207 | + style="opacity:0.6;filter:url(#filter891)"> |
1208 | + <path |
1209 | + transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)" |
1210 | + d="m 264,552.36218 a 12,12 0 1 1 -24,0 12,12 0 1 1 24,0 z" |
1211 | + sodipodi:ry="12" |
1212 | + sodipodi:rx="12" |
1213 | + sodipodi:cy="552.36218" |
1214 | + sodipodi:cx="252" |
1215 | + id="path844" |
1216 | + 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" |
1217 | + sodipodi:type="arc" /> |
1218 | + </g> |
1219 | + <g |
1220 | + id="g862"> |
1221 | + <path |
1222 | + sodipodi:type="arc" |
1223 | + 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" |
1224 | + id="path4398" |
1225 | + sodipodi:cx="252" |
1226 | + sodipodi:cy="552.36218" |
1227 | + sodipodi:rx="12" |
1228 | + sodipodi:ry="12" |
1229 | + d="m 264,552.36218 a 12,12 0 1 1 -24,0 12,12 0 1 1 24,0 z" |
1230 | + transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" /> |
1231 | + <path |
1232 | + transform="matrix(1.25,0,0,1.25,33,-100.45273)" |
1233 | + d="m 264,552.36218 a 12,12 0 1 1 -24,0 12,12 0 1 1 24,0 z" |
1234 | + sodipodi:ry="12" |
1235 | + sodipodi:rx="12" |
1236 | + sodipodi:cy="552.36218" |
1237 | + sodipodi:cx="252" |
1238 | + id="path4400" |
1239 | + 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" |
1240 | + sodipodi:type="arc" /> |
1241 | + <path |
1242 | + sodipodi:type="star" |
1243 | + 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" |
1244 | + id="path4459" |
1245 | + sodipodi:sides="5" |
1246 | + sodipodi:cx="666.19574" |
1247 | + sodipodi:cy="589.50385" |
1248 | + sodipodi:r1="7.2431178" |
1249 | + sodipodi:r2="4.3458705" |
1250 | + sodipodi:arg1="1.0471976" |
1251 | + sodipodi:arg2="1.6755161" |
1252 | + inkscape:flatsided="false" |
1253 | + inkscape:rounded="0.1" |
1254 | + inkscape:randomized="0" |
1255 | + 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" |
1256 | + transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" /> |
1257 | + </g> |
1258 | + </g> |
1259 | + </g> |
1260 | + </g> |
1261 | +</svg> |
Lines 755-760 are the new feature.