bundlewrap 5.0.2__tar.gz → 5.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/AUTHORS +1 -0
  2. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/CHANGELOG.md +18 -0
  3. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/PKG-INFO +1 -1
  4. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/__init__.py +1 -1
  5. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/bundle.py +4 -0
  6. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/apply.py +2 -0
  7. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/parser.py +9 -0
  8. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/itemqueue.py +4 -7
  9. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/kubernetes.py +10 -5
  10. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/svc_systemd.py +5 -5
  11. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/node.py +21 -1
  12. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/repo.py +10 -2
  13. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/secrets.py +34 -9
  14. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/__init__.py +2 -2
  15. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/crypto.py +6 -3
  16. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/remote.py +1 -0
  17. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap.egg-info/PKG-INFO +1 -1
  18. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/setup.py +1 -1
  19. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/LICENSE +0 -0
  20. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/MANIFEST.in +0 -0
  21. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/README.md +0 -0
  22. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/__init__.py +0 -0
  23. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/debug.py +0 -0
  24. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/diff.py +0 -0
  25. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/generate_completions.py +0 -0
  26. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/groups.py +0 -0
  27. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/hash.py +0 -0
  28. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/ipmi.py +0 -0
  29. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/items.py +0 -0
  30. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/lock.py +0 -0
  31. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/metadata.py +0 -0
  32. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/nodes.py +0 -0
  33. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/plot.py +0 -0
  34. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/pw.py +0 -0
  35. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/repo.py +0 -0
  36. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/run.py +0 -0
  37. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/stats.py +0 -0
  38. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/test.py +0 -0
  39. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/verify.py +0 -0
  40. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/cmdline/zen.py +0 -0
  41. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/concurrency.py +0 -0
  42. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/deps.py +0 -0
  43. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/exceptions.py +0 -0
  44. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/group.py +0 -0
  45. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/__init__.py +0 -0
  46. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/actions.py +0 -0
  47. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/dconf.py +0 -0
  48. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/directories.py +0 -0
  49. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/files.py +0 -0
  50. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/git_deploy.py +0 -0
  51. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/groups.py +0 -0
  52. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg.py +0 -0
  53. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_apk.py +0 -0
  54. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_apt.py +0 -0
  55. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_dnf.py +0 -0
  56. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_freebsd.py +0 -0
  57. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_openbsd.py +0 -0
  58. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_opkg.py +0 -0
  59. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_pacman.py +0 -0
  60. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_pamac.py +0 -0
  61. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_pip.py +0 -0
  62. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_snap.py +0 -0
  63. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_yum.py +0 -0
  64. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/pkg_zypper.py +0 -0
  65. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/postgres_dbs.py +0 -0
  66. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/postgres_roles.py +0 -0
  67. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/routeros.py +0 -0
  68. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/svc_freebsd.py +0 -0
  69. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/svc_openbsd.py +0 -0
  70. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/svc_openrc.py +0 -0
  71. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/svc_systemv.py +0 -0
  72. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/symlinks.py +0 -0
  73. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/users.py +0 -0
  74. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/zfs_dataset.py +0 -0
  75. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/items/zfs_pool.py +0 -0
  76. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/lock.py +0 -0
  77. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/metadata.py +0 -0
  78. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/metagen.py +0 -0
  79. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/operations.py +0 -0
  80. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/cmdline.py +0 -0
  81. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/dicts.py +0 -0
  82. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/magic_strings.py +0 -0
  83. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/metastack.py +0 -0
  84. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/node_lambda.py +0 -0
  85. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/plot.py +0 -0
  86. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/scm.py +0 -0
  87. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/table.py +0 -0
  88. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/testing.py +0 -0
  89. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/text.py +0 -0
  90. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap/utils/ui.py +0 -0
  91. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap.egg-info/SOURCES.txt +0 -0
  92. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap.egg-info/dependency_links.txt +0 -0
  93. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap.egg-info/entry_points.txt +0 -0
  94. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap.egg-info/not-zip-safe +0 -0
  95. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap.egg-info/requires.txt +0 -0
  96. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/bundlewrap.egg-info/top_level.txt +0 -0
  97. {bundlewrap-5.0.2 → bundlewrap-5.1.0}/setup.cfg +0 -0
@@ -13,3 +13,4 @@ Simon Brielbeck <simon.brielbeck@seibert.group>
13
13
  Peter Körner <git@mazdermind.de>
14
14
  Manuel Domke <manuel-domke@gmx.org>
15
15
  Sophie Schiller <sophie.schiller@seibert.group>
16
+ Max Jäger <xje4@xje4.eu>
@@ -1,3 +1,21 @@
1
+ # 5.1.0
2
+
3
+ 2026-06-18
4
+
5
+ * added [new hooks](https://docs.bundlewrap.org/repo/hooks/) `repo_init` and `secret_key_use`
6
+ * added `bw apply --add-lock-for-fixed-items` to automatically lock all items that were changed during an apply
7
+ * `BUNDLE_DIR` and `BUNDLE_DATA_DIR` are now available as variables inside bundles
8
+ * fixed triggers, items can now trigger `tag:foo` or any other valid [item selector](https://docs.bundlewrap.org/guide/selectors/)
9
+ * fixed `svc_systemd` items so masking happens only after disabling (and unmasking before enabling); the previous order left orphaned `.wants` symlinks and broke units with `Also=` dependencies such as `NetworkManager`
10
+ * fixed `kubernetes` items so `delete: True` works on its own without requiring `manifest` or `apiVersion`
11
+ * fixed `PathInfo` so `OpenSSH ECDSA public key` is also recognized as text
12
+
13
+ # 5.0.3
14
+
15
+ 2026-03-17
16
+
17
+ * `as_htpasswd_entry()` of `Fault` objects now supports the argument `cost=...` (defaults to `12`) to set the bcrypt Cost Factor. The default value was found to be too expensive for some webserver deployments.
18
+
1
19
  # 5.0.2
2
20
 
3
21
  2026-02-25
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bundlewrap
3
- Version: 5.0.2
3
+ Version: 5.1.0
4
4
  Summary: Config management with Python
5
5
  Home-page: http://bundlewrap.org
6
6
  Author: Torsten Rehn
@@ -1,2 +1,2 @@
1
- VERSION = (5, 0, 2)
1
+ VERSION = (5, 1, 0)
2
2
  VERSION_STRING = ".".join([str(v) for v in VERSION])
@@ -86,6 +86,8 @@ class Bundle:
86
86
  base_env = {
87
87
  'node': self.node,
88
88
  'repo': self.repo,
89
+ 'BUNDLE_DIR': self.bundle_dir,
90
+ 'BUNDLE_DATA_DIR': self.bundle_data_dir,
89
91
  }
90
92
 
91
93
  # TODO prevent access to node.metadata
@@ -103,6 +105,8 @@ class Bundle:
103
105
  base_env = {
104
106
  'node': self.node,
105
107
  'repo': self.repo,
108
+ 'BUNDLE_DIR': self.bundle_dir,
109
+ 'BUNDLE_DATA_DIR': self.bundle_data_dir,
106
110
  }
107
111
  for item_class in self.repo.item_classes:
108
112
  base_env[item_class.BUNDLE_ATTRIBUTE_NAME] = {}
@@ -72,6 +72,8 @@ def bw_apply(repo, args):
72
72
  'show_skipped_items': args['show_skipped_items'],
73
73
  'skip_list': skip_list,
74
74
  'workers': args['item_workers'],
75
+ 'add_lock_for_fixed_items': bool(args['expiry']),
76
+ 'lock_expiry': args['expiry'],
75
77
  },
76
78
  }
77
79
 
@@ -221,6 +221,15 @@ bundle:my_bundle # items in this bundle
221
221
  metavar=_("PATH"),
222
222
  type=str,
223
223
  )
224
+ parser_apply.add_argument(
225
+ "--add-lock-for-fixed-items",
226
+ default="",
227
+ const=DEFAULT_softlock_expiry,
228
+ nargs='?',
229
+ dest='expiry',
230
+ help=HELP_softlock_expiry,
231
+ type=str,
232
+ )
224
233
 
225
234
  # bw debug
226
235
  help_debug = _("Start an interactive Python shell for this repository")
@@ -1,10 +1,10 @@
1
1
  from collections import defaultdict
2
2
 
3
3
  from .deps import (
4
- find_item,
5
4
  prepare_dependencies,
6
5
  remove_item_dependents,
7
6
  remove_dep_from_items,
7
+ resolve_selector,
8
8
  split_items_without_deps,
9
9
  )
10
10
  from .exceptions import NoSuchItem, MetadataUnavailable
@@ -142,12 +142,9 @@ class ItemQueue(BaseQueue):
142
142
  def _fire_triggers_for_item(self, item):
143
143
  for triggered_item_id in item.triggers:
144
144
  try:
145
- triggered_item = find_item(
146
- triggered_item_id,
147
- self.all_items,
148
- )
149
- triggered_item.has_been_triggered = True
150
- except NoSuchItem:
145
+ for triggered_item in resolve_selector(triggered_item_id, self.all_items):
146
+ triggered_item.has_been_triggered = True
147
+ except (NoSuchItem, ValueError):
151
148
  io.debug(_(
152
149
  "{item} tried to trigger {triggered_item}, "
153
150
  "but it wasn't available. It must have been skipped previously."
@@ -206,11 +206,16 @@ class KubernetesItem(Item, metaclass=ABCMeta):
206
206
  def validate_attributes(cls, bundle, item_id, attributes):
207
207
  if attributes.get('delete', False):
208
208
  for attr in attributes.keys():
209
- if attr not in ['delete'] + list(BUILTIN_ITEM_ATTRIBUTES.keys()):
210
- raise BundleError(_(
211
- "{item} from bundle '{bundle}' cannot have other "
212
- "attributes besides 'delete'"
213
- ).format(item=item_id, bundle=bundle.name))
209
+ if attr in ['delete'] + list(BUILTIN_ITEM_ATTRIBUTES.keys()):
210
+ continue
211
+
212
+ if attr == 'manifest' and set(attributes['manifest'].keys()) == {'apiVersion'}:
213
+ continue
214
+
215
+ raise BundleError(_(
216
+ "{item} from bundle '{bundle}' cannot have other "
217
+ "attributes besides 'delete' and 'manifest/apiVersion'"
218
+ ).format(item=item_id, bundle=bundle.name))
214
219
  if attributes.get('manifest') and attributes.get('manifest_file'):
215
220
  raise BundleError(_(
216
221
  "{item} from bundle '{bundle}' cannot have both 'manifest' and 'manifest_file'"
@@ -87,11 +87,8 @@ class SvcSystemd(Item):
87
87
  return state
88
88
 
89
89
  def fix(self, status):
90
- if 'masked' in status.keys_to_fix:
91
- if self.attributes['masked']:
92
- svc_mask(self.node, self.name)
93
- else:
94
- svc_unmask(self.node, self.name)
90
+ if 'masked' in status.keys_to_fix and not self.attributes['masked']:
91
+ svc_unmask(self.node, self.name)
95
92
 
96
93
  if 'enabled' in status.keys_to_fix:
97
94
  if self.attributes['enabled']:
@@ -105,6 +102,9 @@ class SvcSystemd(Item):
105
102
  else:
106
103
  svc_stop(self.node, self.name)
107
104
 
105
+ if 'masked' in status.keys_to_fix and self.attributes['masked']:
106
+ svc_mask(self.node, self.name)
107
+
108
108
  def get_canned_actions(self):
109
109
  return {
110
110
  'stop': {
@@ -26,7 +26,7 @@ from .exceptions import (
26
26
  from .group import GROUP_ATTR_DEFAULTS, GROUP_ATTR_TYPES, GROUP_ATTR_TYPES_ENFORCED
27
27
  from .itemqueue import ItemQueue
28
28
  from .items import Item
29
- from .lock import NodeLock
29
+ from .lock import NodeLock, softlock_add
30
30
  from .metadata import hash_metadata
31
31
  from .utils import (
32
32
  cached_property,
@@ -55,6 +55,7 @@ from .utils.text import (
55
55
  green,
56
56
  mark_for_translation as _,
57
57
  prefix_lines,
58
+ randstr,
58
59
  red,
59
60
  toml_clean,
60
61
  validate_name,
@@ -739,6 +740,8 @@ class Node:
739
740
  show_skipped_items=True,
740
741
  skip_list=(),
741
742
  workers=4,
743
+ add_lock_for_fixed_items=False,
744
+ lock_expiry="8h",
742
745
  ):
743
746
  if not list(self.items):
744
747
  io.stdout(_("{x} {node} has no items").format(
@@ -801,6 +804,23 @@ class Node:
801
804
  show_diff=show_diff,
802
805
  show_skipped_items=show_skipped_items,
803
806
  )
807
+
808
+ if add_lock_for_fixed_items:
809
+ items_to_lock = set()
810
+
811
+ for item in item_results:
812
+ item_id, status_code, _duration = item
813
+ if status_code in (Item.STATUS_FIXED, Item.STATUS_ACTION_SUCCEEDED):
814
+ items_to_lock.add(item_id)
815
+
816
+ if items_to_lock:
817
+ lock_id = "APPLY_" + randstr(length=4).upper()
818
+ softlock_add(
819
+ self,
820
+ lock_id,
821
+ item_selectors=sorted(items_to_lock),
822
+ expiry=lock_expiry,
823
+ )
804
824
  except NodeLockedException as e:
805
825
  if not interactive:
806
826
  io.stderr(_(
@@ -17,7 +17,7 @@ if version_info >= VERSION_NEW_PACKAGING:
17
17
  else:
18
18
  from pkg_resources import DistributionNotFound, require, VersionConflict # needs setuptools
19
19
 
20
- from . import items, VERSION_STRING
20
+ from . import items, VERSION, VERSION_STRING
21
21
  from .bundle import FILENAME_ITEMS
22
22
  from .exceptions import (
23
23
  NoSuchGroup,
@@ -68,8 +68,10 @@ HOOK_EVENTS = (
68
68
  'node_apply_start',
69
69
  'node_run_end',
70
70
  'node_run_start',
71
+ 'repo_init',
71
72
  'run_end',
72
73
  'run_start',
74
+ 'secret_key_use',
73
75
  'test',
74
76
  'test_node',
75
77
  )
@@ -178,7 +180,7 @@ class HooksProxy:
178
180
 
179
181
  def __getattr__(self, attrname):
180
182
  if attrname not in HOOK_EVENTS:
181
- raise AttributeError
183
+ raise AttributeError(attrname)
182
184
 
183
185
  if self.__registered_hooks is None:
184
186
  self._register_hooks()
@@ -289,6 +291,12 @@ class Repository(MetadataGenerator):
289
291
  else:
290
292
  self.item_classes = list(self.items_from_dir(items.__path__[0]))
291
293
 
294
+ self.hooks.repo_init(
295
+ repo=self,
296
+ version_string=VERSION_STRING,
297
+ version_tuple=VERSION,
298
+ )
299
+
292
300
  def __eq__(self, other):
293
301
  if self.path == "/dev/null":
294
302
  # in-memory repos are never equal
@@ -6,6 +6,7 @@ from os import environ
6
6
  from os.path import join
7
7
  from string import ascii_letters, punctuation, digits
8
8
  from subprocess import PIPE, run
9
+ from threading import Lock
9
10
 
10
11
  from cryptography.fernet import Fernet
11
12
 
@@ -70,6 +71,20 @@ class SecretProxy:
70
71
  def __init__(self, repo):
71
72
  self.repo = repo
72
73
  self.keys = self._load_keys()
74
+ self.key_hook_lock = Lock()
75
+ self.key_hook_in_use = {}
76
+
77
+ def __hook(self, key):
78
+ with self.key_hook_lock:
79
+ if key not in self.key_hook_in_use:
80
+ self.key_hook_in_use[key] = Lock()
81
+ if self.key_hook_in_use[key].locked():
82
+ return
83
+ with self.key_hook_in_use[key]:
84
+ self.repo.hooks.secret_key_use(
85
+ repo=self.repo,
86
+ key=key,
87
+ )
73
88
 
74
89
  def _decrypt(self, cryptotext=None, key=None):
75
90
  """
@@ -78,7 +93,8 @@ class SecretProxy:
78
93
  if environ.get("BW_VAULT_DUMMY_MODE", "0") != "0":
79
94
  return "decrypted text"
80
95
 
81
- key, cryptotext = self._determine_key_to_use(cryptotext.encode('utf-8'), key, cryptotext)
96
+ key, key_name, cryptotext = self._determine_key_to_use(cryptotext.encode('utf-8'), key, cryptotext)
97
+ self.__hook(key_name)
82
98
  return Fernet(key).decrypt(cryptotext).decode('utf-8')
83
99
 
84
100
  def _decrypt_file(self, source_path=None, binary=False, key=None):
@@ -90,7 +106,8 @@ class SecretProxy:
90
106
  return "decrypted file"
91
107
 
92
108
  cryptotext = get_file_contents(join(self.repo.data_dir, source_path))
93
- key, cryptotext = self._determine_key_to_use(cryptotext, key, source_path)
109
+ key, key_name, cryptotext = self._determine_key_to_use(cryptotext, key, source_path)
110
+ self.__hook(key_name)
94
111
 
95
112
  f = Fernet(key)
96
113
  if binary:
@@ -107,12 +124,13 @@ class SecretProxy:
107
124
  return b64encode(b"decrypted file as base64").decode('utf-8')
108
125
 
109
126
  cryptotext = get_file_contents(join(self.repo.data_dir, source_path))
110
- key, cryptotext = self._determine_key_to_use(cryptotext, key, source_path)
127
+ key, key_name, cryptotext = self._determine_key_to_use(cryptotext, key, source_path)
128
+ self.__hook(key_name)
111
129
 
112
130
  f = Fernet(key)
113
131
  return b64encode(f.decrypt(cryptotext)).decode('utf-8')
114
132
 
115
- def _determine_key_to_use(self, cryptotext, key, entity_description):
133
+ def _determine_key_to_use(self, cryptotext, key_name, entity_description):
116
134
  key_delim = cryptotext.find(b'$')
117
135
  if key_delim > -1:
118
136
  key_from_text = cryptotext[:key_delim].decode('utf-8')
@@ -120,14 +138,15 @@ class SecretProxy:
120
138
  else:
121
139
  key_from_text = None
122
140
 
123
- if key is None:
141
+ if key_name is None:
124
142
  if key_from_text is not None:
125
- key = key_from_text
143
+ key_name = key_from_text
126
144
  else:
127
- key = 'encrypt'
145
+ key_name = 'encrypt'
128
146
 
147
+ key = None
129
148
  try:
130
- key = self.keys[key]
149
+ key = self.keys[key_name]
131
150
  except KeyError:
132
151
  raise FaultUnavailable(_(
133
152
  "Key '{key}' not available for decryption of the following entity, "
@@ -138,7 +157,7 @@ class SecretProxy:
138
157
  entity_description=entity_description,
139
158
  ))
140
159
 
141
- return key, cryptotext
160
+ return key, key_name, cryptotext
142
161
 
143
162
  def _generate_human_password(
144
163
  self, identifier=None, digits=2, key='generate', per_word=3, words=4,
@@ -159,6 +178,8 @@ class SecretProxy:
159
178
  if environ.get("BW_VAULT_DUMMY_MODE", "0") != "0":
160
179
  return "generatedpassword"
161
180
 
181
+ self.__hook(key)
182
+
162
183
  prng = self._get_prng(identifier, key)
163
184
 
164
185
  pwd = ""
@@ -208,6 +229,8 @@ class SecretProxy:
208
229
  if environ.get("BW_VAULT_DUMMY_MODE", "0") != "0":
209
230
  return ("generatedpassword"*length)[:length]
210
231
 
232
+ self.__hook(key)
233
+
211
234
  prng = self._get_prng(identifier, key)
212
235
 
213
236
  alphabet = ascii_letters + digits
@@ -220,6 +243,8 @@ class SecretProxy:
220
243
  if environ.get("BW_VAULT_DUMMY_MODE", "0") != "0":
221
244
  return b64encode(bytearray([ord("a") for i in range(length)])).decode()
222
245
 
246
+ self.__hook(key)
247
+
223
248
  prng = self._get_prng(identifier, key)
224
249
  return b64encode(bytearray([next(prng) for i in range(length)])).decode()
225
250
 
@@ -190,7 +190,7 @@ class Fault:
190
190
  return format_string.format(self.value)
191
191
  return Fault(self.id_list + ['format_into ' + format_string], callback)
192
192
 
193
- def as_htpasswd_entry(self, username):
193
+ def as_htpasswd_entry(self, username, cost=None):
194
194
  def callback():
195
195
  # bcrypt demands to have a salt (16 random bytes, encoded
196
196
  # with a special `b64encode_bcrypt()` function).
@@ -227,7 +227,7 @@ class Fault:
227
227
 
228
228
  return '{}:{}'.format(
229
229
  username,
230
- bcrypt(self.value, salt=salt),
230
+ bcrypt(self.value, cost=cost, salt=salt),
231
231
  )
232
232
  return Fault(self.id_list + ['as_htpasswd_entry ' + username], callback)
233
233
 
@@ -32,7 +32,7 @@ def b64encode_bcrypt(payload_bytes):
32
32
  def bcrypt(
33
33
  payload,
34
34
  encoding='UTF-8',
35
- cost=_DEFAULT_BCRYPT_COST,
35
+ cost=None,
36
36
  salt=None,
37
37
  ):
38
38
  """
@@ -42,10 +42,13 @@ def bcrypt(
42
42
  servers.
43
43
 
44
44
  - `encoding`: `payload` will be encoded using this encoding.
45
- - `cost`: Use this cost factor instead of bcrypt's default.
46
- - `salt`: Must be a valid bcrypt salt.
45
+ - `cost`: Use this cost factor, or `None` for bcrypt's default.
46
+ - `salt`: Must be a valid bcrypt salt, or `None` for bcrypt's default.
47
47
  """
48
48
 
49
+ if cost is None:
50
+ cost = _DEFAULT_BCRYPT_COST
51
+
49
52
  if salt is None:
50
53
  salt = _DEFAULT_BCRYPT_SALT
51
54
 
@@ -82,6 +82,7 @@ class PathInfo:
82
82
  self.desc in (
83
83
  "empty",
84
84
  "JSON data",
85
+ "OpenSSH ECDSA public key",
85
86
  "OpenSSH ED25519 public key",
86
87
  "OpenSSH RSA public key",
87
88
  "OpenSSH DSA public key",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bundlewrap
3
- Version: 5.0.2
3
+ Version: 5.1.0
4
4
  Summary: Config management with Python
5
5
  Home-page: http://bundlewrap.org
6
6
  Author: Torsten Rehn
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
2
2
 
3
3
  setup(
4
4
  name="bundlewrap",
5
- version="5.0.2",
5
+ version="5.1.0",
6
6
  description="Config management with Python",
7
7
  long_description=(
8
8
  "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"
File without changes
File without changes
File without changes
File without changes