Imported Upstream version 2.18.0
SVN-Git Migration
6 years ago
0 | # 2.18.0 | |
1 | ||
2 | 2017-05-22 | |
3 | ||
4 | * added encoding and collation to postgres_db items | |
5 | * added the 'comment' attribute for all items | |
6 | * fixed group deletion | |
7 | * fixed accidental modification of lists in statedicts | |
8 | ||
9 | ||
0 | 10 | # 2.17.1 |
1 | 11 | |
2 | 12 | 2017-04-19 |
0 | 0 | # -*- coding: utf-8 -*- |
1 | 1 | from __future__ import unicode_literals |
2 | 2 | |
3 | VERSION = (2, 17, 1) | |
3 | VERSION = (2, 18, 0) | |
4 | 4 | VERSION_STRING = ".".join([str(v) for v in VERSION]) |
5 | 5 | from __future__ import unicode_literals |
6 | 6 | from copy import copy |
7 | 7 | from datetime import datetime |
8 | from inspect import cleandoc | |
8 | 9 | from os.path import join |
10 | from textwrap import TextWrapper | |
9 | 11 | |
10 | 12 | from bundlewrap.exceptions import BundleError, ItemDependencyError, FaultUnavailable |
11 | 13 | from bundlewrap.utils import cached_property |
12 | 14 | from bundlewrap.utils.statedict import diff_keys, diff_value, hash_statedict, validate_statedict |
13 | 15 | from bundlewrap.utils.text import force_text, mark_for_translation as _ |
14 | from bundlewrap.utils.text import blue, bold, wrap_question | |
16 | from bundlewrap.utils.text import blue, bold, italic, wrap_question | |
15 | 17 | from bundlewrap.utils.ui import io |
16 | 18 | |
17 | 19 | BUILTIN_ITEM_ATTRIBUTES = { |
18 | 20 | 'cascade_skip': None, |
21 | 'comment': None, | |
19 | 22 | 'needed_by': [], |
20 | 23 | 'needs': [], |
21 | 24 | 'preceded_by': [], |
28 | 31 | 'unless': "", |
29 | 32 | 'when_creating': {}, |
30 | 33 | } |
34 | ||
35 | wrapper = TextWrapper( | |
36 | break_long_words=False, | |
37 | break_on_hyphens=False, | |
38 | expand_tabs=False, | |
39 | replace_whitespace=False, | |
40 | ) | |
41 | ||
42 | ||
43 | def format_comment(comment): | |
44 | result = "\n\n" | |
45 | for line in wrapper.wrap(cleandoc(comment)): | |
46 | for inlineline in line.split("\n"): | |
47 | result += "# {}\n".format(italic(inlineline)) | |
48 | return result | |
31 | 49 | |
32 | 50 | |
33 | 51 | class ItemStatus(object): |
454 | 472 | keys_to_fix, |
455 | 473 | ) |
456 | 474 | question_text = self.ask(cdict, sdict, keys_to_fix) |
475 | if self.comment: | |
476 | question_text += format_comment(self.comment) | |
457 | 477 | question = wrap_question( |
458 | 478 | self.id, |
459 | 479 | question_text, |
7 | 7 | from bundlewrap.utils.text import force_text, mark_for_translation as _ |
8 | 8 | |
9 | 9 | |
10 | def create_db(node, name, owner): | |
11 | return node.run("sudo -u postgres createdb -wO {owner} {name}".format( | |
12 | name=name, | |
13 | owner=owner, | |
14 | )) | |
10 | def create_db(node, name, owner, when_creating): | |
11 | template = None | |
12 | cmd = "sudo -u postgres createdb -wO {} ".format(owner) | |
13 | ||
14 | if when_creating.get('collation') is not None: | |
15 | cmd += "--lc-collate={} ".format(when_creating['collation']) | |
16 | template = "template0" | |
17 | ||
18 | if when_creating.get('ctype') is not None: | |
19 | cmd += "--lc-ctype={} ".format(when_creating['ctype']) | |
20 | template = "template0" | |
21 | ||
22 | if when_creating.get('encoding') is not None: | |
23 | cmd += "--encoding={} ".format(when_creating['encoding']) | |
24 | template = "template0" | |
25 | ||
26 | if template is not None: | |
27 | cmd += "--template={} ".format(template) | |
28 | ||
29 | cmd += name | |
30 | ||
31 | return node.run(cmd) | |
15 | 32 | |
16 | 33 | |
17 | 34 | def drop_db(node, name): |
49 | 66 | 'owner': "postgres", |
50 | 67 | } |
51 | 68 | ITEM_TYPE_NAME = "postgres_db" |
69 | WHEN_CREATING_ATTRIBUTES = { | |
70 | 'collation': None, | |
71 | 'ctype': None, | |
72 | 'encoding': None, | |
73 | } | |
52 | 74 | |
53 | 75 | def __repr__(self): |
54 | 76 | return "<PostgresDB name:{}>".format(self.name) |
63 | 85 | if status.must_be_deleted: |
64 | 86 | drop_db(self.node, self.name) |
65 | 87 | elif status.must_be_created: |
66 | create_db(self.node, self.name, self.attributes['owner']) | |
88 | create_db(self.node, self.name, self.attributes['owner'], self.when_creating) | |
67 | 89 | elif 'owner' in status.keys_to_fix: |
68 | 90 | set_owner(self.node, self.name, self.attributes['owner']) |
69 | 91 | else: |
144 | 144 | |
145 | 145 | def get_auto_deps(self, items): |
146 | 146 | deps = [] |
147 | groups = self.attributes['groups'] or [] | |
147 | 148 | for item in items: |
148 | 149 | if item.ITEM_TYPE_NAME == "group": |
149 | if item.attributes['delete']: | |
150 | if not (item.name in groups or ( | |
151 | self.attributes['gid'] in [item.attributes['gid'], item.name] and | |
152 | self.attributes['gid'] is not None | |
153 | )): | |
154 | # we don't need to depend on this group | |
155 | continue | |
156 | elif item.attributes['delete']: | |
150 | 157 | raise BundleError(_( |
151 | 158 | "{item1} (from bundle '{bundle1}') depends on item " |
152 | 159 | "{item2} (from bundle '{bundle2}') which is set to be deleted" |
59 | 59 | if isinstance(value1, set): |
60 | 60 | value1 = sorted(value1) |
61 | 61 | value2 = sorted(value2) |
62 | elif isinstance(value1, tuple): | |
62 | else: | |
63 | # convert tuples and create copies of lists before possibly | |
64 | # appending stuff later on (see below) | |
63 | 65 | value1 = list(value1) |
64 | 66 | value2 = list(value2) |
65 | 67 | # make sure that *if* we have lines, the last one will also end with |
43 | 43 | @ansi_wrapper |
44 | 44 | def inverse(text): |
45 | 45 | return "\033[0m\033[7m{}\033[0m".format(text) |
46 | ||
47 | ||
48 | @ansi_wrapper | |
49 | def italic(text): | |
50 | return "\033[3m{}\033[0m".format(text) | |
46 | 51 | |
47 | 52 | |
48 | 53 | @ansi_wrapper |
4 | 4 | postgres_dbs = { |
5 | 5 | "mydatabase": { |
6 | 6 | "owner": "me", |
7 | "when_creating": { | |
8 | "encoding": "LATIN1", | |
9 | "collation": "de_DE.ISO-8859-1", | |
10 | "ctype": "de_DE.ISO-8859-1", | |
11 | }, | |
7 | 12 | }, |
8 | 13 | } |
9 | 14 | |
18 | 23 | ### owner |
19 | 24 | |
20 | 25 | Name of the role which owns this database (defaults to `"postgres"`). |
26 | ||
27 | ### encoding, collation, and ctype | |
28 | ||
29 | By default, BundleWrap will only create a database using your default PostgreSQL template, which most likely is `template1`. This means it will use the same encoding and collation that `template1` uses. By specifying any of the attributes `encoding`, `collation`, or `ctype`, BundleWrap will instead create a new database from `template0`, thus allowing you to override said database attributes. | |
30 | ||
31 | These options are creation-time only. |
69 | 69 | |
70 | 70 | <br> |
71 | 71 | |
72 | ### comment | |
73 | ||
74 | This is a string that will be displayed in interactive mode (`bw apply -i`) whenever the item is to be changed in any way. You can use it to warn users before they start disruptive actions. | |
75 | ||
76 | <br> | |
77 | ||
72 | 78 | ### error_on_missing_fault |
73 | 79 | |
74 | 80 | This will simply skip an item instead of raising an error when a Fault used for an attribute on the item is unavailable. Faults are special objects used by `repo.vault` to [handle secrets](../guide/secrets.md). A Fault being unavailable can mean you're missing the secret key required to decrypt a secret you're trying to use as an item attribute value. |
15 | 15 | |
16 | 16 | setup( |
17 | 17 | name="bundlewrap", |
18 | version="2.17.1", | |
18 | version="2.18.0", | |
19 | 19 | description="Config management with Python", |
20 | 20 | long_description=( |
21 | 21 | "By allowing for easy and low-overhead config management, BundleWrap fills the gap between complex deployments using Chef or Puppet and old school system administration over SSH.\n" |
490 | 490 | assert run("bw test", path=str(tmpdir))[2] == 1 |
491 | 491 | assert run("bw test group1", path=str(tmpdir))[2] == 1 |
492 | 492 | assert run("bw test group2", path=str(tmpdir))[2] == 1 |
493 | ||
494 | ||
495 | def test_group_user_dep_deleted(tmpdir): | |
496 | make_repo( | |
497 | tmpdir, | |
498 | nodes={ | |
499 | "node1": { | |
500 | 'bundles': ["bundle1"], | |
501 | }, | |
502 | }, | |
503 | bundles={ | |
504 | "bundle1": { | |
505 | "users": { | |
506 | "user1": { | |
507 | 'groups': ["group1"], | |
508 | }, | |
509 | }, | |
510 | "groups": { | |
511 | "group1": { | |
512 | 'delete': True, | |
513 | }, | |
514 | }, | |
515 | }, | |
516 | }, | |
517 | ) | |
518 | assert run("bw test", path=str(tmpdir))[2] == 1 | |
519 | ||
520 | ||
521 | def test_group_user_dep_ok(tmpdir): | |
522 | # regression test for #341 | |
523 | make_repo( | |
524 | tmpdir, | |
525 | nodes={ | |
526 | "node1": { | |
527 | 'bundles': ["bundle1"], | |
528 | }, | |
529 | }, | |
530 | bundles={ | |
531 | "bundle1": { | |
532 | "users": { | |
533 | "user1": {}, | |
534 | }, | |
535 | "groups": { | |
536 | "group1": {'delete': True}, | |
537 | }, | |
538 | }, | |
539 | }, | |
540 | ) | |
541 | assert run("bw test", path=str(tmpdir))[2] == 0 | |
542 | ||
543 | ||
544 | def test_group_user_dep_deleted_gid(tmpdir): | |
545 | make_repo( | |
546 | tmpdir, | |
547 | nodes={ | |
548 | "node1": { | |
549 | 'bundles': ["bundle1"], | |
550 | }, | |
551 | }, | |
552 | bundles={ | |
553 | "bundle1": { | |
554 | "users": { | |
555 | "user1": { | |
556 | 'gid': "group1", | |
557 | }, | |
558 | }, | |
559 | "groups": { | |
560 | "group1": { | |
561 | 'delete': True, | |
562 | }, | |
563 | }, | |
564 | }, | |
565 | }, | |
566 | ) | |
567 | assert run("bw test", path=str(tmpdir))[2] == 1 |