Imported Debian patch 3.1.0-1
Jonathan Carter
6 years ago
0 | # 3.1.0 | |
1 | ||
2 | 2017-10-10 | |
3 | ||
4 | * added pkg_opkg items | |
5 | * added `bw test -s` | |
6 | * improved error messages for unknown reverse triggers | |
7 | * fixed hash_method md5 on user items | |
8 | * fixed cursor sometimes not being restored | |
9 | ||
10 | ||
0 | 11 | # 3.0.3 |
1 | 12 | |
2 | 13 | 2017-10-04 |
0 | 0 | # -*- coding: utf-8 -*- |
1 | 1 | from __future__ import unicode_literals |
2 | 2 | |
3 | VERSION = (3, 0, 3) | |
3 | VERSION = (3, 1, 0) | |
4 | 4 | VERSION_STRING = ".".join([str(v) for v in VERSION]) |
6 | 6 | from os.path import abspath, dirname |
7 | 7 | from pipes import quote |
8 | 8 | from sys import argv, exit, stderr, stdout |
9 | from traceback import print_exc | |
9 | from traceback import format_exc, print_exc | |
10 | 10 | |
11 | 11 | |
12 | 12 | from ..exceptions import NoSuchRepository, MissingRepoDependency |
132 | 132 | "{x} {path} " |
133 | 133 | "is not a BundleWrap repository." |
134 | 134 | ).format(path=quote(abspath(pargs.repo_path)), x=red("!!!"))) |
135 | io.deactivate() | |
135 | 136 | exit(1) |
136 | 137 | else: |
137 | 138 | path = dirname(path) |
138 | 139 | except MissingRepoDependency as exc: |
139 | 140 | io.stderr(str(exc)) |
141 | io.deactivate() | |
142 | exit(1) | |
143 | except Exception: | |
144 | io.stderr(format_exc()) | |
145 | io.deactivate() | |
140 | 146 | exit(1) |
141 | 147 | |
142 | 148 | # convert all string args into text |
851 | 851 | help=_("check for bundles not assigned to any node"), |
852 | 852 | ) |
853 | 853 | parser_test.add_argument( |
854 | "-s", | |
855 | "--secret-rotation", | |
856 | default=None, | |
857 | dest='ignore_secret_identifiers', | |
858 | help=_("ensure every string passed to repo.vault.[human_]password_for() is used at least " | |
859 | "twice (using it only once means you're probably managing only one end of an " | |
860 | "authentication, making it dangerous to rotate your .secrets.cfg); PATTERNS is a " | |
861 | "comma-separated list of regex patterns for strings to ignore in this check " | |
862 | "(just pass an empty string if you don't need to ignore anything)"), | |
863 | metavar="PATTERNS", | |
864 | type=str, | |
865 | ) | |
866 | parser_test.add_argument( | |
854 | 867 | "-S", |
855 | 868 | "--subgroup-loops", |
856 | 869 | action='store_true', |
1 | 1 | from __future__ import unicode_literals |
2 | 2 | |
3 | 3 | from copy import copy |
4 | from re import compile as compile_regex | |
4 | 5 | from sys import exit |
5 | 6 | |
6 | 7 | from ..deps import DummyItem |
119 | 120 | exit(1) |
120 | 121 | |
121 | 122 | |
123 | def test_secret_identifiers(repo, ignore_patterns): | |
124 | # create a new object to make sure we don't double-count any calls | |
125 | # from previous tests | |
126 | pristine_repo = Repository(repo.path) | |
127 | pristine_repo.hash() # shortest way to create all configuration | |
128 | patterns = set() | |
129 | for raw_pattern in ignore_patterns.split(","): | |
130 | if raw_pattern: | |
131 | patterns.add(compile_regex(raw_pattern)) | |
132 | found_something = False | |
133 | for identifier, call_count in pristine_repo.vault._call_log.items(): | |
134 | if call_count == 1: | |
135 | ignore = False | |
136 | for pattern in patterns: | |
137 | if pattern.search(identifier): | |
138 | ignore = True | |
139 | break | |
140 | if not ignore: | |
141 | io.stderr(_( | |
142 | "{x} identifier passed only once to repo.vault.[human_]password_for(): {i}" | |
143 | ).format( | |
144 | i=bold(identifier), | |
145 | x=red("✘"), | |
146 | )) | |
147 | found_something = True | |
148 | if found_something: | |
149 | exit(1) | |
150 | else: | |
151 | io.stdout(_( | |
152 | "{x} all arguments to repo.vault.[human_]password_for() used at least twice" | |
153 | ).format(x=green("✓"))) | |
154 | ||
155 | ||
122 | 156 | def test_empty_groups(repo): |
123 | 157 | empty_groups = set() |
124 | 158 | for group in repo.groups: |
248 | 282 | args['determinism_metadata'] > 1 or |
249 | 283 | args['hooks_node'] or |
250 | 284 | args['hooks_repo'] or |
285 | args['ignore_secret_identifiers'] is not None or | |
251 | 286 | args['items'] or |
252 | 287 | args['metadata_collisions'] or |
253 | 288 | args['orphaned_bundles'] or |
270 | 305 | args['metadata_collisions'] = True |
271 | 306 | args['subgroup_loops'] = True |
272 | 307 | |
308 | if args['ignore_secret_identifiers'] is not None and not QUIT_EVENT.is_set(): | |
309 | test_secret_identifiers(repo, args['ignore_secret_identifiers']) | |
310 | ||
273 | 311 | if args['plugin_conflicts'] and not QUIT_EVENT.is_set(): |
274 | 312 | test_plugin_conflicts(repo) |
275 | 313 |
440 | 440 | """ |
441 | 441 | for item in items.values(): |
442 | 442 | for triggering_item_id in item.triggered_by: |
443 | triggering_item = items[triggering_item_id] | |
443 | try: | |
444 | triggering_item = items[triggering_item_id] | |
445 | except KeyError: | |
446 | raise ItemDependencyError(_( | |
447 | "'{item}' in bundle '{bundle}' has a reverse trigger (triggered_by) " | |
448 | "on '{dep}', which doesn't exist" | |
449 | ).format( | |
450 | item=item.id, | |
451 | bundle=item.bundle.name, | |
452 | dep=triggering_item_id, | |
453 | )) | |
444 | 454 | if triggering_item.id.startswith("bundle:"): # bundle items |
445 | 455 | bundle_name = triggering_item.id.split(":")[1] |
446 | 456 | for actual_triggering_item in items.values(): |
458 | 468 | else: |
459 | 469 | triggering_item.triggers.append(item.id) |
460 | 470 | for preceded_item_id in item.precedes: |
461 | preceded_item = items[preceded_item_id] | |
471 | try: | |
472 | preceded_item = items[preceded_item_id] | |
473 | except KeyError: | |
474 | raise ItemDependencyError(_( | |
475 | "'{item}' in bundle '{bundle}' has a reverse trigger (precedes) " | |
476 | "on '{dep}', which doesn't exist" | |
477 | ).format( | |
478 | item=item.id, | |
479 | bundle=item.bundle.name, | |
480 | dep=preceded_item_id, | |
481 | )) | |
462 | 482 | if preceded_item.id.startswith("bundle:"): # bundle items |
463 | 483 | bundle_name = preceded_item.id.split(":")[1] |
464 | 484 | for actual_preceded_item in items.values(): |
0 | # -*- coding: utf-8 -*- | |
1 | from __future__ import unicode_literals | |
2 | ||
3 | from pipes import quote | |
4 | ||
5 | from bundlewrap.items.pkg import Pkg | |
6 | ||
7 | ||
8 | class OpkgPkg(Pkg): | |
9 | """ | |
10 | A package installed by opkg. | |
11 | """ | |
12 | BUNDLE_ATTRIBUTE_NAME = "pkg_opkg" | |
13 | ITEM_TYPE_NAME = "pkg_opkg" | |
14 | ||
15 | @classmethod | |
16 | def block_concurrent(cls, node_os, node_os_version): | |
17 | return ["pkg_opkg"] | |
18 | ||
19 | def pkg_all_installed(self): | |
20 | result = self.node.run("opkg list-installed") | |
21 | for line in result.stdout.decode('utf-8').strip().split("\n"): | |
22 | if line: | |
23 | yield "{}:{}".format(self.ITEM_TYPE_NAME, line.split()[0]) | |
24 | ||
25 | def pkg_install(self): | |
26 | self.node.run("opkg install {}".format(quote(self.name)), may_fail=True) | |
27 | ||
28 | def pkg_installed(self): | |
29 | result = self.node.run( | |
30 | "opkg status {} | grep ^Status: | grep installed".format(quote(self.name)), | |
31 | may_fail=True, | |
32 | ) | |
33 | return result.return_code == 0 | |
34 | ||
35 | def pkg_remove(self): | |
36 | self.node.run("opkg remove {}".format(quote(self.name)), may_fail=True) |
243 | 243 | rounds=8, # default rounds for OpenBSD accounts |
244 | 244 | salt=_DEFAULT_BCRYPT_SALT if salt is None else salt, |
245 | 245 | ) |
246 | elif attributes.get('hash_method') == 'md5': | |
247 | attributes['password_hash'] = hash_method.encrypt( | |
248 | force_text(attributes['password']), | |
249 | salt=_DEFAULT_SALT if salt is None else salt, | |
250 | ) | |
246 | 251 | else: |
247 | 252 | attributes['password_hash'] = hash_method.encrypt( |
248 | 253 | force_text(attributes['password']), |
327 | 327 | 'amazonlinux', |
328 | 328 | 'arch', |
329 | 329 | 'opensuse', |
330 | 'openwrt', | |
330 | 331 | 'gentoo', |
331 | 332 | 'linux', |
332 | 333 | ) + \ |
74 | 74 | def __init__(self, repo): |
75 | 75 | self.repo = repo |
76 | 76 | self.keys = self._load_keys() |
77 | self._call_log = {} | |
77 | 78 | |
78 | 79 | def _decrypt(self, cryptotext=None, key='encrypt'): |
79 | 80 | """ |
310 | 311 | def human_password_for( |
311 | 312 | self, identifier, digits=2, key='generate', per_word=3, words=4, |
312 | 313 | ): |
314 | self._call_log.setdefault(identifier, 0) | |
315 | self._call_log[identifier] += 1 | |
313 | 316 | return Fault( |
314 | 317 | self._generate_human_password, |
315 | 318 | identifier=identifier, |
320 | 323 | ) |
321 | 324 | |
322 | 325 | def password_for(self, identifier, key='generate', length=32, symbols=False): |
326 | self._call_log.setdefault(identifier, 0) | |
327 | self._call_log[identifier] += 1 | |
323 | 328 | return Fault( |
324 | 329 | self._generate_password, |
325 | 330 | identifier=identifier, |
383 | 383 | except ProcessLookupError: |
384 | 384 | pass |
385 | 385 | self._clear_last_job() |
386 | if TTY: | |
387 | write_to_stream(STDOUT_WRITER, SHOW_CURSOR) | |
386 | 388 | _exit(1) |
387 | 389 | else: |
388 | 390 | if SHUTDOWN_EVENT_SOFT.wait(0.1): |
0 | bundlewrap (3.1.0-1) unstable; urgency=medium | |
1 | ||
2 | * New upstream release | |
3 | ||
4 | -- Jonathan Carter <jcc@debian.org> Tue, 10 Oct 2017 14:39:46 +0200 | |
5 | ||
0 | 6 | bundlewrap (3.0.3-1) unstable; urgency=medium |
1 | 7 | |
2 | 8 | * New upstream release |
0 | # opkg package items | |
1 | ||
2 | Handles packages installed by `opkg` on OpenWRT/LEDE. | |
3 | ||
4 | pkg_opkg = { | |
5 | "foopkg": { | |
6 | "installed": True, # default | |
7 | }, | |
8 | "bar": { | |
9 | "installed": False, | |
10 | }, | |
11 | } | |
12 | ||
13 | <br><br> | |
14 | ||
15 | # Attribute reference | |
16 | ||
17 | See also: [The list of generic builtin item attributes](../repo/items.py.md#builtin-item-attributes) | |
18 | ||
19 | <hr> | |
20 | ||
21 | ## installed | |
22 | ||
23 | `True` when the package is expected to be present on the system; `False` if it should be removed. |
41 | 41 | <tr><td><a href="../../items/group">group</a></td><td><code>groups</code></td><td>Manages groups by wrapping <code>groupadd</code>, <code>groupmod</code> and <code>groupdel</code></td></tr> |
42 | 42 | <tr><td><a href="../../items/pkg_apt">pkg_apt</a></td><td><code>pkg_apt</code></td><td>Installs and removes packages with APT</td></tr> |
43 | 43 | <tr><td><a href="../../items/pkg_dnf">pkg_dnf</a></td><td><code>pkg_dnf</code></td><td>Installs and removes packages with dnf</td></tr> |
44 | <tr><td><a href="../../items/pkg_opkg">pkg_opkg</a></td><td><code>pkg_opkg</code></td><td>Installs and removes packages with opkg</td></tr> | |
44 | 45 | <tr><td><a href="../../items/pkg_pacman">pkg_pacman</a></td><td><code>pkg_pacman</code></td><td>Installs and removes packages with pacman</td></tr> |
45 | 46 | <tr><td><a href="../../items/pkg_pip">pkg_pip</a></td><td><code>pkg_pip</code></td><td>Installs and removes Python packages with pip</td></tr> |
46 | 47 | <tr><td><a href="../../items/pkg_snap">pkg_snap</a></td><td><code>pkg_snap</code></td><td>Installs and removes packages with snap</td></tr> |
39 | 39 | - group: items/group.md |
40 | 40 | - pkg_apt: items/pkg_apt.md |
41 | 41 | - pkg_dnf: items/pkg_dnf.md |
42 | - pkg_opkg: items/pkg_opkg.md | |
42 | 43 | - pkg_pacman: items/pkg_pacman.md |
43 | 44 | - pkg_pip: items/pkg_pip.md |
44 | 45 | - pkg_snap: items/pkg_snap.md |
15 | 15 | |
16 | 16 | setup( |
17 | 17 | name="bundlewrap", |
18 | version="3.0.3", | |
18 | version="3.1.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" |
0 | from bundlewrap.utils.testing import host_os, make_repo, run | |
1 | ||
2 | ||
3 | def test_run_ok(tmpdir): | |
4 | make_repo( | |
5 | tmpdir, | |
6 | nodes={ | |
7 | "localhost": { | |
8 | 'os': host_os(), | |
9 | }, | |
10 | }, | |
11 | ) | |
12 | stdout, stderr, rcode = run("BW_TABLE_STYLE=grep bw run localhost true", path=str(tmpdir)) | |
13 | assert rcode == 0 | |
14 | assert b"localhost\t0" in stdout | |
15 | assert stderr == b"" | |
16 | ||
17 | ||
18 | def test_run_fail(tmpdir): | |
19 | make_repo( | |
20 | tmpdir, | |
21 | nodes={ | |
22 | "localhost": { | |
23 | 'os': host_os(), | |
24 | }, | |
25 | }, | |
26 | ) | |
27 | stdout, stderr, rcode = run("BW_TABLE_STYLE=grep bw run localhost false", path=str(tmpdir)) | |
28 | assert rcode == 0 | |
29 | assert b"localhost\t1" in stdout | |
30 | assert stderr == b"" |
583 | 583 | }, |
584 | 584 | ) |
585 | 585 | assert run("bw test -I", path=str(tmpdir))[2] == 1 |
586 | ||
587 | ||
588 | def test_secret_identifier_only_once(tmpdir): | |
589 | make_repo( | |
590 | tmpdir, | |
591 | nodes={ | |
592 | "node1": { | |
593 | 'bundles': ["bundle1"], | |
594 | }, | |
595 | }, | |
596 | bundles={ | |
597 | "bundle1": { | |
598 | 'files': { | |
599 | "/test": { | |
600 | 'content': "${repo.vault.password_for('testing')}", | |
601 | 'content_type': 'mako', | |
602 | }, | |
603 | }, | |
604 | }, | |
605 | }, | |
606 | ) | |
607 | assert run("bw test -s ''", path=str(tmpdir))[2] == 1 | |
608 | assert run("bw test -s 'test'", path=str(tmpdir))[2] == 0 | |
609 | assert run("bw test -s 'test,foo'", path=str(tmpdir))[2] == 0 | |
610 | ||
611 | ||
612 | def test_secret_identifier_twice(tmpdir): | |
613 | make_repo( | |
614 | tmpdir, | |
615 | nodes={ | |
616 | "node1": { | |
617 | 'bundles': ["bundle1"], | |
618 | }, | |
619 | "node2": { | |
620 | 'bundles': ["bundle1"], | |
621 | }, | |
622 | }, | |
623 | bundles={ | |
624 | "bundle1": { | |
625 | 'files': { | |
626 | "/test": { | |
627 | 'content': "${repo.vault.password_for('testing')}", | |
628 | 'content_type': 'mako', | |
629 | }, | |
630 | }, | |
631 | }, | |
632 | }, | |
633 | ) | |
634 | assert run("bw test -s ''", path=str(tmpdir))[2] == 0 | |
635 | assert run("bw test -s 'test'", path=str(tmpdir))[2] == 0 | |
636 | assert run("bw test -s 'test,foo'", path=str(tmpdir))[2] == 0 |