ansible-vars 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl

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.
ansible_vars/cli.py CHANGED
@@ -4,7 +4,7 @@
4
4
  # CLI entry point for ansible-vars
5
5
 
6
6
  # Standard library imports
7
- import os, re, json, atexit, signal
7
+ import sys, os, re, json, atexit, signal
8
8
  from glob import glob
9
9
  from time import sleep
10
10
  from enum import StrEnum
@@ -12,7 +12,7 @@ from pathlib import Path
12
12
  from shutil import rmtree
13
13
  from builtins import print as std_print
14
14
  from subprocess import run as sys_command
15
- from tempfile import NamedTemporaryFile, mkdtemp
15
+ from tempfile import NamedTemporaryFile, gettempdir
16
16
  from typing import Iterator, Type, Hashable, Callable, Any
17
17
  from argparse import ArgumentParser, RawDescriptionHelpFormatter
18
18
 
@@ -51,6 +51,7 @@ ansible-vars --add-key my_key '<passphrase>' --add-key other_key '<passphrase>'
51
51
  # Check if a string value is encrypted (no keys need to be loaded for this)
52
52
  ansible-vars is-encrypted string '<value>'
53
53
  # Recursively search the directory `./host_vars` of vault files for decrypted text matches on a regex pattern
54
+ # `h:` is a shorthand for `./host_vars`, see tips section below for more information about advanced path resolution
54
55
  ansible-vars grep '# TODO' h:
55
56
  # Get the diff of two vaults
56
57
  ansible-vars diff my_vault.yml.old my_vault.yml
@@ -123,7 +124,7 @@ Encrypt a string and return it or fully encrypt a file in-place. This uses the c
123
124
  Decrypt a string and return it or fully decrypt a file in-place. Uses the first matching one of the loaded keys.
124
125
  ''',
125
126
  'cmd_is_enc': '''
126
- Check if a string or file is vault-encrypted. Works without any loaded keys.
127
+ Check if a string or file is (fully) vault-encrypted.
127
128
  ''',
128
129
  'cmd_convert': '''
129
130
  Switch a file between full outer and full inner encryption for convenient migrating between encryption schemes.
@@ -189,6 +190,9 @@ Deletes a node from a vault if it exists.
189
190
  }
190
191
 
191
192
  DEFAULT_EDITOR: str = os.environ.get('EDITOR', 'notepad.exe' if os.name == 'nt' else 'vi')
193
+ DEFAULT_COLOR_MODE: str = os.environ.get('AV_COLOR_MODE', '256')
194
+ DEFAULT_TEMP_DIR: str = os.environ.get('AV_TEMP_DIR', gettempdir())
195
+ DEFAULT_CREATE_PLAIN: bool = os.environ.get('AV_CREATE_PLAIN', 'no').lower() in [ 'yes', 'y', 'true', 't', '1' ]
192
196
 
193
197
  args: ArgumentParser = ArgumentParser(
194
198
  prog = 'ansible-vars',
@@ -201,7 +205,7 @@ args: ArgumentParser = ArgumentParser(
201
205
  def _prefixed_path_completer(prefix: str, **_) -> list[str]:
202
206
  has_prefix: bool = len(prefix) > 1 and prefix[:2] in ( 'h:', 'g:', 'v:' )
203
207
  resolved_prefix: str | None = { 'h:': 'host_vars', 'g:': 'group_vars', 'v:': 'vars' }[prefix[:2]] if has_prefix else None
204
- if resolved_prefix and os.path.isdir(os.path.abspath(resolved_prefix + 'x')):
208
+ if resolved_prefix and os.path.isdir(os.path.abspath(resolved_prefix)):
205
209
  # Replace prefix with actual path
206
210
  path_prefix: str = prefix[:3] if (len(prefix) > 2 and prefix[2] == os.path.sep) else prefix[:2]
207
211
  new_prefix: str = os.path.join(resolved_prefix, prefix[len(path_prefix):])
@@ -218,7 +222,14 @@ def _prefixed_path_completer(prefix: str, **_) -> list[str]:
218
222
  # Base args
219
223
 
220
224
  args.add_argument('--debug', '-d', action='store_true', help='print debug information')
221
- args.add_argument('--color-mode', '-C', type=str, choices=['none', 'basic', '256', 'truecolor'], default='256', help='set terminal color capability (default: 256)')
225
+ args.add_argument(
226
+ '--color-mode', '-C', type=str, choices=['none', 'basic', '256', 'truecolor'], default=DEFAULT_COLOR_MODE,
227
+ help=f"set terminal color capability (default: { DEFAULT_COLOR_MODE })"
228
+ )
229
+ args.add_argument(
230
+ '--temp-dir', '-T', type=str, metavar='<path>', default=DEFAULT_TEMP_DIR,
231
+ help=f"use this directory for vault staging instead of the system\'s TMP directory (default: { DEFAULT_TEMP_DIR })"
232
+ )
222
233
 
223
234
  key_args = args.add_argument_group('vault key management', description=HELP['key_args'])
224
235
  # This arg can be repeated (results in [ [id, passphrase], ... ])
@@ -232,7 +243,7 @@ log_args = args.add_argument_group('logging vault changes', description=HELP['lo
232
243
  log_mutex = log_args.add_mutually_exclusive_group()
233
244
  log_mutex.add_argument('--log', '-l', type=str, metavar='<log path>', help='log to an encrypted logfile (uses the encryption key)')
234
245
  log_mutex.add_argument('--log-plain', '-L', type=str, metavar='<log path>', help='log to a plain logfile (dangerous!)')
235
- log_args.add_argument('--logging-key', type=str, metavar='<identifier>', help='use this loaded key for logging instead of the encryption key')
246
+ log_args.add_argument('--logging-key', '-Q', type=str, metavar='<identifier>', help='use this loaded key for logging instead of the encryption key')
236
247
 
237
248
  # Commands
238
249
  commands = args.add_subparsers(dest='command', metavar='<command>', required=True)
@@ -244,7 +255,11 @@ cmd_keyring.add_argument('--keys-only', '-o', action='store_false', dest='show_p
244
255
  cmd_create = commands.add_parser('create', help='create a new vault', description=HELP['cmd_create'])
245
256
  cmd_create.add_argument('vault_path', type=str, metavar='<vault path>', help='path to create a new vault at') \
246
257
  .completer = _prefixed_path_completer # type: ignore
247
- cmd_create.add_argument('--plain', '-p', action='store_false', dest='encrypt_vault', help='create without full file encryption')
258
+ # Invert flag if the user wants plain mode by default
259
+ if DEFAULT_CREATE_PLAIN:
260
+ cmd_create.add_argument('--no-plain', '-P', action='store_true', dest='encrypt_vault', help='create with full file encryption')
261
+ else:
262
+ cmd_create.add_argument('--plain', '-p', action='store_false', dest='encrypt_vault', help='create without full file encryption')
248
263
  cmd_create.add_argument('--make-parents', '-m', action='store_true', help='create all directories in the given path')
249
264
  cmd_mutex = cmd_create.add_mutually_exclusive_group()
250
265
  cmd_mutex.add_argument('--no-edit', '-n', action='store_false', dest='open_edit_mode', help='just create the file, don\'t open it for editing')
@@ -318,8 +333,8 @@ cmd_changes.add_argument('new_vault', type=str, metavar='<new vault vault path>'
318
333
  .completer = _prefixed_path_completer # type: ignore
319
334
  cmd_changes.add_argument('--json', '-j', action='store_true', dest='as_json', help='print added/changed/removed/decrypted vars as JSON and nothing else')
320
335
 
321
- cmd_daemon = commands.add_parser('file-daemon', help='create a folder and sync decrypted vaults to it', description=HELP['cmd_daemon'])
322
- cmd_daemon.add_argument('target_root', type=str, metavar='<target root path>', help='root path the decrypted files and folders should be synced into') \
336
+ cmd_daemon = commands.add_parser('file-daemon', help='sync decrypted vault copies into a folder', description=HELP['cmd_daemon'])
337
+ cmd_daemon.add_argument('target_root', type=str, metavar='<target root path>', help='root folder the decrypted files and folders should be synced into (non-existent or empty)') \
323
338
  .completer = _prefixed_path_completer # type: ignore
324
339
  # This arg can be repeated (results in [ [source, rel_target], ... ])
325
340
  cmd_daemon.add_argument(
@@ -328,14 +343,16 @@ cmd_daemon.add_argument(
328
343
  ).completer = _prefixed_path_completer # type: ignore
329
344
  cmd_daemon.add_argument('--no-recurse', '-n', action='store_false', dest='recurse', help='don\'t recurse into source folders\' subfolders')
330
345
  cmd_daemon.add_argument('--no-default-dirs', '-N', action='store_false', dest='include_default_dirs', help='don\'t include default sync sources')
331
- cmd_daemon.add_argument('--force', '-f', action='store_true', help='if the target root already exists, delete it')
346
+ cmd_daemon.add_argument('--force', '-f', action='store_true', help='if the target root already exists and is not empty, delete its contents')
332
347
 
333
348
  cmd_get = commands.add_parser('get', help='get a key\'s (decrypted) value if it exists', description=HELP['cmd_get'])
334
349
  cmd_get.add_argument('vault_path', type=str, metavar='<vault path>', help='path of vault to get value from') \
335
350
  .completer = _prefixed_path_completer # type: ignore
336
351
  cmd_get.add_argument('key_segments', type=str, nargs='+', metavar='<key segment> [<key segment> ...]', help='segment(s) of the key to look up (`[<num>]` for numbers)')
337
352
  cmd_get.add_argument('--no-decrypt', '-n', action='store_false', dest='decrypt_value', help='don\'t decrypt the value if it is encrypted')
338
- cmd_get.add_argument('--json', '-j', action='store_true', dest='as_json', help='print only value as JSON and set the rc to 0 if the key exists or 100 if it doesn\'t')
353
+ get_mutex_format = cmd_get.add_mutually_exclusive_group()
354
+ get_mutex_format.add_argument('--quiet', '-q', action='store_true', help='only output the raw YAML value or set the rc to 100 if the key doesn\'t exist')
355
+ get_mutex_format.add_argument('--json', '-j', action='store_true', dest='as_json', help='print the value as JSON or set the rc to 100 if the key doesn\'t exist')
339
356
 
340
357
  cmd_set = commands.add_parser('set', help='update a key\'s value or add a new key (experimental!)', description=HELP['cmd_set'])
341
358
  cmd_set.add_argument('vault_path', type=str, metavar='<vault path>', help='path of vault to set value in') \
@@ -483,6 +500,15 @@ def resolve_vault_path(search_path: str, create_mode: bool = False, allow_dirs:
483
500
 
484
501
  ## CLI logic
485
502
 
503
+ # Print all exceptions unless we're in debug mode
504
+ def _exc_hook(exctype, value, traceback) -> None:
505
+ if config.debug:
506
+ sys.__excepthook__(exctype, value, traceback)
507
+ else:
508
+ print(f"{ value.__class__.__name__ }: { value }", Color.BAD)
509
+ print('Use --debug to get the full stacktrace')
510
+ sys.excepthook = _exc_hook
511
+
486
512
  # Load vault keys
487
513
 
488
514
  _explicit_keys: list[VaultKey] = [ VaultKey(passphrase, vault_id=id) for id, passphrase in config.keys ]
@@ -556,7 +582,7 @@ if config.command in [ 'create', 'edit' ]:
556
582
  if getattr(config, 'open_edit_mode', True):
557
583
  print(f"Editing vault at { vault_path }")
558
584
  # Create a secure temporary file to host the editable content
559
- with NamedTemporaryFile(mode='w+', prefix='vault_', suffix='.yml') as edit_file:
585
+ with NamedTemporaryFile(mode='w+', dir=config.temp_dir, prefix='vault_', suffix='.yml') as edit_file:
560
586
  # Write vault contents to temp file
561
587
  editable: str = vault.as_editable()
562
588
  edit_file.write(editable)
@@ -586,6 +612,17 @@ if config.command in [ 'create', 'edit' ]:
586
612
  if (changes := new_vault.changes(vault))[0]:
587
613
  print(f"\n[!] The following vars have been decrypted in this edit:", Color.MEH)
588
614
  print('\n'.join([ f"- { format_key_path(path) }" for path in changes[0] ]))
615
+ # Inform about new plain leaf variables
616
+ new_plain_leaves: list[tuple[Hashable, ...]] = []
617
+ def _find_new_plain_vars(path: tuple[Hashable, ...], value: Any) -> Any:
618
+ if path != ( SENTINEL_KEY, ) and type(value) is not EncryptedVar:
619
+ if vault.get(path, default=Unset) is Unset:
620
+ new_plain_leaves.append(path)
621
+ return value
622
+ vault._transform_leaves(new_vault._data, _find_new_plain_vars, tuple())
623
+ if new_plain_leaves:
624
+ print(f"\n[!] The following plain vars have been added in this edit:", Color.MEH)
625
+ print('\n'.join([ f"- { format_key_path(path) }" for path in new_plain_leaves ]))
589
626
  # Log changes
590
627
  if log_enabled:
591
628
  logger.add_log_entry(vault, new_vault, comment=f"{ config.command } command via CLI")
@@ -654,7 +691,7 @@ if config.command in [ 'encrypt', 'decrypt', 'is-encrypted' ]:
654
691
  if config.quiet:
655
692
  exit(0 if vault.full_encryption else 100)
656
693
  else:
657
- print(f"Vault is { 'encrypted' if vault.full_encryption else 'plain' }.", Color.GOOD if vault.full_encryption else Color.MEH)
694
+ print(f"Vault is { 'encrypted' if vault.full_encryption else 'plain or hybrid' }.", Color.GOOD if vault.full_encryption else Color.MEH)
658
695
  # String target
659
696
  else:
660
697
  is_encrypted: bool = VaultKey.is_encrypted(config.target)
@@ -866,11 +903,15 @@ if config.command in [ 'diff', 'changes' ]:
866
903
 
867
904
  if config.command == 'file-daemon':
868
905
  target_path: str = os.path.abspath(config.target_root)
869
- if os.path.exists(target_path):
906
+ if os.path.isfile(target_path) or (os.path.isdir(target_path) and os.listdir(target_path)):
870
907
  if config.force:
871
- rmtree(target_path, ignore_errors=True)
908
+ for child in map(lambda c: os.path.join(target_path, c), os.listdir(target_path)):
909
+ if os.path.isfile(child):
910
+ os.unlink(child)
911
+ else:
912
+ rmtree(child, ignore_errors=True)
872
913
  else:
873
- raise FileExistsError(f"Cannot mount sync root to { target_path } as the path already exist")
914
+ raise FileExistsError(f"Cannot create sync root at { target_path } as the path already exist and is not empty")
874
915
  # Resolve sources
875
916
  if config.include_default_dirs:
876
917
  for dir_path in ( 'host_vars', 'group_vars', 'vars' ):
@@ -886,14 +927,22 @@ if config.command == 'file-daemon':
886
927
  for _path, _target in sources:
887
928
  if _target == subtarget and _path != path:
888
929
  raise ValueError(f"Sources may not have the same target subpath, found identical target for { _path } and { path }")
889
- # Create target dir
890
- _target_tmp_path = mkdtemp()
891
- os.rename(_target_tmp_path, target_path)
930
+ # Create target dir and record if we had to create it for later cleanup
931
+ already_existed: bool = os.path.isdir(target_path)
932
+ os.makedirs(target_path, mode=0o700, exist_ok=True)
892
933
  # Cleanup handler
893
- def _cleanup_daemons(daemons) -> None:
934
+ def _cleanup_daemons(daemons, delete_root) -> None:
894
935
  print('\nStopping file daemons and cleaning up files...')
895
936
  [ daemon.stop(delete=False) for daemon in daemons ] # type: ignore
896
- rmtree(target_path, ignore_errors=True)
937
+ # Only delete the root directory if we created it ourselves
938
+ if delete_root:
939
+ rmtree(target_path, ignore_errors=True)
940
+ else:
941
+ for child in map(lambda c: os.path.join(target_path, c), os.listdir(target_path)):
942
+ if os.path.isfile(child):
943
+ os.unlink(child)
944
+ else:
945
+ rmtree(child, ignore_errors=True)
897
946
  print('Goodbye.')
898
947
  # Create daemons
899
948
  def _error_callback(daemon: VaultDaemon, operation: str, err: Exception) -> None:
@@ -905,13 +954,13 @@ if config.command == 'file-daemon':
905
954
  VaultDaemon(path, target, keyring, recurse=config.recurse, error_callback=_error_callback, debug_out=_debug_out)
906
955
  for path, target in sources
907
956
  ]
908
- atexit.register(_cleanup_daemons, daemons=daemons)
957
+ atexit.register(_cleanup_daemons, daemons=daemons, delete_root=(not already_existed))
909
958
  # Extra interrupt handler to silence error
910
959
  signal.signal(signal.SIGINT, lambda *_: exit(0))
911
960
  # Start file daemons
912
961
  [ daemon.start(stop_on_exit=False) for daemon in daemons ]
913
- print(f"{ len(daemons) } file daemons are running.", Color.GOOD)
914
- print('Interrupt the program to stop them and delete the target directory.')
962
+ print(f"{ len(daemons) } file daemon(s) running.", Color.GOOD)
963
+ print('Interrupt the program to stop and delete the target directory.')
915
964
  # Idle
916
965
  try:
917
966
  while True:
@@ -932,10 +981,11 @@ if config.command in [ 'get', 'set', 'del' ]:
932
981
  value = value.cipher
933
982
  if type(value) is ProtoEncryptedVar:
934
983
  value = value.plaintext
984
+ # Early abort
985
+ if (config.as_json or config.quiet) and value is Unset:
986
+ exit(100)
935
987
  # Output JSON
936
988
  if config.as_json:
937
- if value is Unset:
938
- exit(100)
939
989
  # Custom decoder for encrypted vars
940
990
  def _decode_vars(obj) -> Any:
941
991
  if type(obj) is EncryptedVar:
@@ -945,7 +995,11 @@ if config.command in [ 'get', 'set', 'del' ]:
945
995
  raise TypeError(f"{ type(obj) } cannot be serialized into JSON")
946
996
  json_code: str = json.dumps(value, default=_decode_vars, indent=2)
947
997
  print_json(json_code)
948
- # Output text
998
+ # Output nothing but the raw YAML
999
+ elif config.quiet:
1000
+ yaml_code: str = vault._dump_to_str(value).strip('\n') if isinstance(value, dict | list | tuple) else str(value)
1001
+ print_yaml(yaml_code)
1002
+ # Output text with extra messages
949
1003
  else:
950
1004
  print(f"Key: { format_key_path(key) }")
951
1005
  if value is Unset:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ansible-vars
3
- Version: 1.0.1
3
+ Version: 1.0.3
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
@@ -172,7 +172,7 @@ Shows the amounts of encrypted and decrypted variables in a vault file. Supports
172
172
 
173
173
  #### encrypt, decrypt, is-encrypted
174
174
 
175
- En-/Decrypts or checks the encryption status of a file or string value. No loaded secrets are required for `is-encrypted`.
175
+ En-/Decrypts or checks the encryption status of a file or string value. Note that only full file encryption is considered in file mode, a hybrid vault with individually encrypted variables will be counted as plain.
176
176
 
177
177
  #### convert
178
178
 
@@ -192,7 +192,7 @@ Compares two vaults or variable files and prints a tree structure showing differ
192
192
 
193
193
  #### file-daemon
194
194
 
195
- Starts a daemon which mirrors the decrypted contents of one or multiple vault or variable file(s)/directories to a target directory. By default, this includes the directories `./host_vars`, `./group_vars`, and `./vars`. Changes to the source files are reflected in the decrypted targets. Changes to the target files are ignored.
195
+ Starts a daemon which mirrors the decrypted contents of one or multiple vault or variable file(s)/directories to a target directory. By default, this includes the directories `./host_vars`, `./group_vars`, and `./vars`. Changes to the source files are reflected in the decrypted targets. Changes to the target files are ignored. For added security, consider syncing the files to a mounted ramdisk.
196
196
 
197
197
  #### get
198
198
 
@@ -202,6 +202,20 @@ Displays the (decrypted) value of a specified key in a vault or variable file. S
202
202
 
203
203
  Creates, updates, or deletes a key-value pair from a vault or variable file. When setting a value, you may provide a YAML string which will be parsed into the corresponding objects. Note that these are experimental features, as the current parser has difficulty preserving the metadata for programmatic variable changes. Comments and Jinja2 blocks between the affected key and the next key in the file may be lost.
204
204
 
205
+ ### Environment variables
206
+
207
+ #### AV_COLOR_MODE
208
+
209
+ Set the color mode as you would with `-C <mode>`.
210
+
211
+ #### AV_TEMP_DIR
212
+
213
+ Set the tempfile/staging root as you would with `-T <path>`.
214
+
215
+ #### AV_CREATE_PLAIN
216
+
217
+ Invert the default creation mode for files: If unset or `no`, files are created with full encryption unless specified otherwise via the `--plain|-p` flag. This behavior mirrors that of `ansible-vault`. When set to `yes`, the behavior and flag are inverted as files are created without encryption by default unless specified otherwise via the `--no-plain|-P` flag.
218
+
205
219
  ### Python library
206
220
 
207
221
  When using `ansible-vars` as a library, import any of these modules from the `ansible_vars` module.
@@ -230,6 +244,10 @@ The `VaultDaemon` syncs changes from a source file or directory to a target usin
230
244
 
231
245
  Custom types and exceptions, and static values. Mostly useful for type hints.
232
246
 
247
+ ## Security considerations
248
+
249
+ When editing a file or creating a daemon, decrypted vaults are written to disk temporarily. The temporary files can only be accessed by the current user, but could potentially be restored through data recovery methods after deletion. To mitigate this issue, consider creating an in-RAM filesystem ("ramdisk") and using it as the staging directory (`--temp-dir <path>`) or the daemon target.
250
+
233
251
  ## Known issues and limitations
234
252
 
235
253
  - YAML round-trip parser
@@ -1,12 +1,12 @@
1
1
  ansible_vars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- ansible_vars/cli.py,sha256=HZLvlwyTMQubwMubjT0eLgsZ1clXBKWS-YH5uCjPwSE,55401
2
+ ansible_vars/cli.py,sha256=-DJ8aEV4Sr1XAelntQ-NHRCfr1rxd6o5hqG5VYFeHns,58744
3
3
  ansible_vars/constants.py,sha256=Nd3sIuSoOvyfUfHfnsnJBDGMW7eNzbMm1NAvEQio9hE,1624
4
4
  ansible_vars/errors.py,sha256=VnViEBMR3rIeioMj460DgdBA5S5FYiDObaDDhG2FMBs,857
5
5
  ansible_vars/util.py,sha256=BzS7n3UzaKqVZ3W78HVkJtdVCYofprqQDtU8wYH1d0Q,21325
6
6
  ansible_vars/vault.py,sha256=_dp1K5_UAFGgcg6iO4on4-L_BaJO2cHP6My3EU6enA4,45593
7
7
  ansible_vars/vault_crypt.py,sha256=JcFc6dTZ6EqhKXv_C5ofggTpBK8hWG3ZwrBrDNYcEIg,9501
8
- ansible_vars-1.0.1.dist-info/METADATA,sha256=DSTVVasM2MZB3uT7_tv40gbf85KVhqGCx3Uw6FDB9Vk,15717
9
- ansible_vars-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- ansible_vars-1.0.1.dist-info/entry_points.txt,sha256=RrhkEH0MbfRzflguVrfYfthsFC5V2fkFnizUG3uHMtQ,55
11
- ansible_vars-1.0.1.dist-info/licenses/LICENSE,sha256=ocyJHLG5wD12qB4uam2pqWTHIJmzloiyNyTex6Q2DKo,1062
12
- ansible_vars-1.0.1.dist-info/RECORD,,
8
+ ansible_vars-1.0.3.dist-info/METADATA,sha256=3vcaDPiNAm0JbRP7iWbXDaSERQQQ23KMMdZxVJJzmmU,16863
9
+ ansible_vars-1.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ ansible_vars-1.0.3.dist-info/entry_points.txt,sha256=RrhkEH0MbfRzflguVrfYfthsFC5V2fkFnizUG3uHMtQ,55
11
+ ansible_vars-1.0.3.dist-info/licenses/LICENSE,sha256=ocyJHLG5wD12qB4uam2pqWTHIJmzloiyNyTex6Q2DKo,1062
12
+ ansible_vars-1.0.3.dist-info/RECORD,,