ansible-vars 1.0.16__tar.gz → 1.0.18__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ansible-vars
3
- Version: 1.0.16
3
+ Version: 1.0.18
4
4
  Summary: Manage vaults and variable files for Ansible
5
5
  Project-URL: Homepage, https://github.com/xorwow/ansible-vars
6
6
  Project-URL: Issues, https://github.com/xorwow/ansible-vars/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ansible-vars"
7
- version = "1.0.16"
7
+ version = "1.0.18"
8
8
  authors = [
9
9
  { name="xorwow", email="pip@xorwow.de" },
10
10
  ]
@@ -33,7 +33,7 @@ from pygments.formatters import TerminalFormatter, Terminal256Formatter, Termina
33
33
  from .vault import VaultFile, EncryptedVar, ProtoEncryptedVar
34
34
  from .vault_crypt import VaultKey, VaultKeyring
35
35
  from .util import DiffFileLogger, VaultDaemon
36
- from .constants import Unset, MatchLocation, SENTINEL_KEY
36
+ from .constants import Unset, MatchLocation, SENTINEL_KEY, APPEND_SENTINEL
37
37
  from .errors import YAMLFormatError, UnsupportedGenericFileOperation
38
38
 
39
39
  ## CLI argument parsing
@@ -183,13 +183,13 @@ The value will be shown in (recursively) decrypted form.
183
183
  JSON mode formatting:
184
184
  - [ ... ] or { ... } for lists/dictionaries, "<value>" for strings, <value> for numbers
185
185
  ''',
186
- 'cmd_set': '''
186
+ 'cmd_set': f"""
187
187
  Creates or updates a node in a vault with a YAML value, optionally encrypting the value('s string leaves) first using the configured encryption key.
188
- For creating a new list entry, the last specified key segment has to equal the largest index of the list plus one (e.g. `[5]` for a list of length 5).
188
+ Any unknown keys along the given path will be created as dictionaries. You can append an element to a list by using the special key '{ APPEND_SENTINEL }'.
189
189
  The value is interpreted as YAML code.
190
190
 
191
191
  [!] Creating new nodes or changing non-leaf nodes may break/remove trailing comments and Jinja2 blocks.
192
- ''',
192
+ """,
193
193
  'cmd_del': '''
194
194
  Deletes a node from a vault if it exists.
195
195
 
@@ -45,6 +45,11 @@ EDIT_MODE_HEADER: str = f"""
45
45
 
46
46
  """.lstrip('\n')
47
47
 
48
+ # Special codes
49
+
50
+ # Marks that a new element should be appended to a list in a `Vault.set` call
51
+ APPEND_SENTINEL: str = '+'
52
+
48
53
  # Diff log filenames
49
54
 
50
55
  # Default filename for a plaintext vault log
@@ -3,7 +3,6 @@
3
3
  # Standard library imports
4
4
  import os, atexit
5
5
  from glob import glob
6
- from pathlib import Path
7
6
  from functools import wraps
8
7
  from datetime import datetime
9
8
  from typing import Callable
@@ -38,7 +37,7 @@ class DiffLogger():
38
37
  You can specify an optional comment string which will be included.
39
38
  '''
40
39
  # Check if any changes happened
41
- diff: list[str] = curr_vault.diff(prev_vault, context_lines=0, show_filenames=True).split('\n')
40
+ diff: str | None = curr_vault.diff(prev_vault, context_lines=0, show_filenames=True)
42
41
  if not force and not diff:
43
42
  return None
44
43
  # Build entry
@@ -55,7 +54,7 @@ class DiffLogger():
55
54
  # Diff
56
55
  lines.append('DIFF')
57
56
  if diff:
58
- lines += diff
57
+ lines += diff.split('\n')
59
58
  else:
60
59
  lines.append('No changes.')
61
60
  #lines.append(OUTER_SEP)
@@ -15,7 +15,8 @@ from ruamel.yaml.constructor import Constructor
15
15
  from ruamel.yaml.comments import CommentedMap
16
16
 
17
17
  # Internal module imports
18
- from .constants import ThrowError, octal, Indexable, ChangeList, MatchLocation, SENTINEL_KEY, EDIT_MODE_HEADER, ENCRYPTED_VAR_TAG
18
+ from .constants import ThrowError, octal, Indexable, ChangeList, MatchLocation, \
19
+ SENTINEL_KEY, EDIT_MODE_HEADER, ENCRYPTED_VAR_TAG, APPEND_SENTINEL
19
20
  from .vault_crypt import VaultKey, VaultKeyring
20
21
  from .errors import KeyExistsError, NoVaultKeysError, YAMLFormatError
21
22
 
@@ -242,11 +243,12 @@ class Vault():
242
243
  self, path: DictPath, value: Any, overwrite: bool | Type[ThrowError] = True, # type: ignore
243
244
  create_parents: bool | Type[ThrowError] = True, encrypt: bool = False
244
245
  ) -> bool:
245
- '''
246
+ f"""
246
247
  Creates or updates a value in the vault's variables. The value has to be serializable into YAML.
247
248
  If the last/only key of the key path does not exist yet, it will be created.
248
249
  If the variable is set successfully, `True` is returned. If any of the below checks fail, `False` is returned.
249
- Be aware that appending a new entry to a list requires the key to be equal to the length of the list (i.e. largest index + 1).
250
+ Be aware that appending a new entry to a list requires the key to be equal to the length of the list (i.e. largest index + 1),
251
+ or to be the special symbol '{ APPEND_SENTINEL }'.
250
252
  On updated leaf values, comment and Jinja2 metadata is preserved.
251
253
  When appending a new value or editing an indexable item, metadata may get messed up.
252
254
 
@@ -262,7 +264,7 @@ class Vault():
262
264
  - `encrypt`: Controls if the value should be recursively encrypted before storing it (only plain `str`s get encrypted)
263
265
  - `True`: Attempt to copy and convert the value('s leaf values) into an `EncryptedVar` before storing it
264
266
  - `False`: Store the value as-is
265
- '''
267
+ """
266
268
  path: tuple[Hashable, ...] = Vault._to_path(path) # XXX typer complains if not explicitly re-typed
267
269
  # Encrypt value if necessary
268
270
  if encrypt:
@@ -288,10 +290,13 @@ class Vault():
288
290
  if not isinstance(parent, dict | list):
289
291
  raise TypeError(f"Indexing into a { type(parent) } is not supported ({ par_path })")
290
292
  # Check if index is of correct type
291
- if isinstance(parent, list) and type(segment) is not int:
292
- raise TypeError(f"Type of list index has to be int, got { type(segment) } ({ par_path }[{ segment }])")
293
+ if isinstance(parent, list) and segment != APPEND_SENTINEL and type(segment) is not int:
294
+ raise TypeError(f"Type of list index has to be int (or str as '{ APPEND_SENTINEL }'), got { type(segment) } ({ par_path }[{ segment }])")
293
295
  # Check if the current segment has to be created in the parent
294
- if (isinstance(parent, dict) and segment not in parent) or (isinstance(parent, list) and cast(int, segment) >= len(parent)):
296
+ if (
297
+ (isinstance(parent, dict) and segment not in parent) or \
298
+ (isinstance(parent, list) and (segment == APPEND_SENTINEL or cast(int, segment) >= len(parent)))
299
+ ):
295
300
  if not is_last:
296
301
  if create_parents is ThrowError:
297
302
  raise KeyError(f"Parents of { '.'.join(map(str, path)) } could not be resolved ({ segment } not in { par_path })")
@@ -299,9 +304,9 @@ class Vault():
299
304
  return False
300
305
  # Create nested dictionary as next parent or set value of leaf, depending on index
301
306
  segment_value: Any = value if is_last else CommentedMap()
302
- # Check that a new list item has a specified index of (largest list index + 1)
307
+ # Check that a new list item has a specified index of (largest list index + 1) or is the special append sentinel
303
308
  # This is done because the method of creating an index of e.g. 7 in a list of length 3 is ambiguous
304
- if isinstance(parent, list) and segment != len(parent):
309
+ if isinstance(parent, list) and segment != len(parent) and segment != APPEND_SENTINEL:
305
310
  raise IndexError(f"Creating new list item, but index { segment } exceeds appendment index { len(parent) } ({ par_path })")
306
311
  # Set value
307
312
  if isinstance(parent, dict):
@@ -316,6 +321,8 @@ class Vault():
316
321
  return False
317
322
  parent[segment] = value # type: ignore
318
323
  # Advance parent
324
+ if isinstance(parent, list) and segment == APPEND_SENTINEL:
325
+ segment = len(parent) - 1 # element has already been appended
319
326
  parent = parent[segment] # type: ignore
320
327
  par_path = f"{ par_path }.{ segment }"
321
328
  return True
@@ -10,6 +10,7 @@ from ruamel.yaml import YAML
10
10
 
11
11
  from ansible_vars.vault import VaultFile, Vault, EncryptedVar
12
12
  from ansible_vars.vault_crypt import VaultKeyring, VaultKey
13
+ from ansible_vars.constants import APPEND_SENTINEL
13
14
 
14
15
  JSONObject: TypeAlias = Any
15
16
 
@@ -218,7 +219,7 @@ class TestVault:
218
219
  got: Any = vault.get('a')
219
220
  assert isinstance(got, list) and len(got) == 1, 'expected vault data to contain list'
220
221
  # Add item to list
221
- vault.set(( 'a', 1 ), 2)
222
+ vault.set(( 'a', APPEND_SENTINEL ), 2)
222
223
  got = vault.get('a')
223
224
  assert len(got) == 2 and got[1] == 2, 'expected new value in list'
224
225
 
File without changes
File without changes
File without changes