cli2 4.0.4__tar.gz → 4.0.5__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 (56) hide show
  1. {cli2-4.0.4/cli2.egg-info → cli2-4.0.5}/PKG-INFO +1 -1
  2. {cli2-4.0.4 → cli2-4.0.5}/cli2/__init__.py +12 -0
  3. {cli2-4.0.4 → cli2-4.0.5}/cli2/ansible/__init__.py +1 -0
  4. cli2-4.0.5/cli2/ansible/variables.py +115 -0
  5. {cli2-4.0.4 → cli2-4.0.5/cli2.egg-info}/PKG-INFO +1 -1
  6. {cli2-4.0.4 → cli2-4.0.5}/cli2.egg-info/SOURCES.txt +2 -0
  7. {cli2-4.0.4 → cli2-4.0.5}/setup.py +1 -1
  8. cli2-4.0.5/tests/test_ansible_variables.py +39 -0
  9. {cli2-4.0.4 → cli2-4.0.5}/MANIFEST.in +0 -0
  10. {cli2-4.0.4 → cli2-4.0.5}/README.rst +0 -0
  11. {cli2-4.0.4 → cli2-4.0.5}/classifiers.txt +0 -0
  12. {cli2-4.0.4 → cli2-4.0.5}/cli2/ansible/action.py +0 -0
  13. {cli2-4.0.4 → cli2-4.0.5}/cli2/ansible/playbook.py +0 -0
  14. {cli2-4.0.4 → cli2-4.0.5}/cli2/ansible/pytest.py +0 -0
  15. {cli2-4.0.4 → cli2-4.0.5}/cli2/asyncio.py +0 -0
  16. {cli2-4.0.4 → cli2-4.0.5}/cli2/cli.py +0 -0
  17. {cli2-4.0.4 → cli2-4.0.5}/cli2/cli2.py +0 -0
  18. {cli2-4.0.4 → cli2-4.0.5}/cli2/client.py +0 -0
  19. {cli2-4.0.4 → cli2-4.0.5}/cli2/colors.py +0 -0
  20. {cli2-4.0.4 → cli2-4.0.5}/cli2/configuration.py +0 -0
  21. {cli2-4.0.4 → cli2-4.0.5}/cli2/decorators.py +0 -0
  22. {cli2-4.0.4 → cli2-4.0.5}/cli2/display.py +0 -0
  23. {cli2-4.0.4 → cli2-4.0.5}/cli2/examples/__init__.py +0 -0
  24. {cli2-4.0.4 → cli2-4.0.5}/cli2/examples/client.py +0 -0
  25. {cli2-4.0.4 → cli2-4.0.5}/cli2/examples/conf.py +0 -0
  26. {cli2-4.0.4 → cli2-4.0.5}/cli2/examples/example.py +0 -0
  27. {cli2-4.0.4 → cli2-4.0.5}/cli2/examples/example_obj.py +0 -0
  28. {cli2-4.0.4 → cli2-4.0.5}/cli2/examples/nesting.py +0 -0
  29. {cli2-4.0.4 → cli2-4.0.5}/cli2/examples/obj.py +0 -0
  30. {cli2-4.0.4 → cli2-4.0.5}/cli2/examples/obj2.py +0 -0
  31. {cli2-4.0.4 → cli2-4.0.5}/cli2/examples/test.py +0 -0
  32. {cli2-4.0.4 → cli2-4.0.5}/cli2/lock.py +0 -0
  33. {cli2-4.0.4 → cli2-4.0.5}/cli2/log.py +0 -0
  34. {cli2-4.0.4 → cli2-4.0.5}/cli2/node.py +0 -0
  35. {cli2-4.0.4 → cli2-4.0.5}/cli2/sphinx.py +0 -0
  36. {cli2-4.0.4 → cli2-4.0.5}/cli2/table.py +0 -0
  37. {cli2-4.0.4 → cli2-4.0.5}/cli2/test.py +0 -0
  38. {cli2-4.0.4 → cli2-4.0.5}/cli2.egg-info/dependency_links.txt +0 -0
  39. {cli2-4.0.4 → cli2-4.0.5}/cli2.egg-info/entry_points.txt +0 -0
  40. {cli2-4.0.4 → cli2-4.0.5}/cli2.egg-info/requires.txt +0 -0
  41. {cli2-4.0.4 → cli2-4.0.5}/cli2.egg-info/top_level.txt +0 -0
  42. {cli2-4.0.4 → cli2-4.0.5}/setup.cfg +0 -0
  43. {cli2-4.0.4 → cli2-4.0.5}/tests/test_ansible.py +0 -0
  44. {cli2-4.0.4 → cli2-4.0.5}/tests/test_cli.py +0 -0
  45. {cli2-4.0.4 → cli2-4.0.5}/tests/test_client.py +0 -0
  46. {cli2-4.0.4 → cli2-4.0.5}/tests/test_command.py +0 -0
  47. {cli2-4.0.4 → cli2-4.0.5}/tests/test_configuration.py +0 -0
  48. {cli2-4.0.4 → cli2-4.0.5}/tests/test_decorators.py +0 -0
  49. {cli2-4.0.4 → cli2-4.0.5}/tests/test_display.py +0 -0
  50. {cli2-4.0.4 → cli2-4.0.5}/tests/test_entry_point.py +0 -0
  51. {cli2-4.0.4 → cli2-4.0.5}/tests/test_group.py +0 -0
  52. {cli2-4.0.4 → cli2-4.0.5}/tests/test_inject.py +0 -0
  53. {cli2-4.0.4 → cli2-4.0.5}/tests/test_lock.py +0 -0
  54. {cli2-4.0.4 → cli2-4.0.5}/tests/test_node.py +0 -0
  55. {cli2-4.0.4 → cli2-4.0.5}/tests/test_restful.py +0 -0
  56. {cli2-4.0.4 → cli2-4.0.5}/tests/test_table.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cli2
3
- Version: 4.0.4
3
+ Version: 4.0.5
4
4
  Summary: image:: https://yourlabs.io/oss/cli2/badges/master/pipeline.svg
5
5
  Home-page: https://yourlabs.io/oss/cli2
6
6
  Author: James Pic
@@ -40,3 +40,15 @@ from .display import diff, diff_data, render, print, highlight
40
40
  from .lock import Lock
41
41
  from .log import configure, log, get_logger
42
42
  from .table import Table
43
+
44
+
45
+ def which(cmd):
46
+ """ Wrapper around shutil.which, and also check for ~/.local/bin. """
47
+ import shutil
48
+ path = shutil.which(cmd)
49
+ if path:
50
+ return path
51
+
52
+ path = Path(os.getenv('HOME')) / '.local/bin' / cmd
53
+ if path.exists():
54
+ return str(path)
@@ -6,3 +6,4 @@ from .action import (
6
6
  AnsibleOptionError,
7
7
  ActionBase,
8
8
  )
9
+ from .variables import Variables
@@ -0,0 +1,115 @@
1
+ """
2
+ Ansible variables file reader with vault support
3
+
4
+ Why not use the Ansible Python API? We don't have a lot to do here, and the CLI
5
+ are less likely to be subject to changes.
6
+ """
7
+
8
+ import cli2
9
+ import functools
10
+ import subprocess
11
+ import yaml
12
+ from pathlib import Path
13
+
14
+
15
+ class Vault(yaml.YAMLObject):
16
+ yaml_tag = '!vault'
17
+
18
+ @classmethod
19
+ def from_yaml(cls, loader, node):
20
+ """
21
+ Convert a representation node to a Python object.
22
+ """
23
+ return subprocess.check_output(
24
+ f'echo \'{node.value}\''
25
+ f' | {cls.ansible_vault}'
26
+ f' decrypt --vault-password-file {cls.pass_path}',
27
+ shell=True,
28
+ ).decode().strip()
29
+
30
+
31
+ class Variables(dict):
32
+ """
33
+ Ansible variables reader.
34
+
35
+ In general, it should be instanciated with :py:attr:`root_path` and
36
+ :py:attr:`pass_path` to fully function correctly.
37
+
38
+ Example:
39
+
40
+ .. code-block:: python
41
+
42
+ import cli2.ansible
43
+ variables = cli2.ansible.Variables(
44
+ root_path=Path(__file__).parent,
45
+ pass_path='~/.vault_password',
46
+ )
47
+ print(variables['playbooks/vars/example.yml'])
48
+
49
+ Every file read is cached in the variables object.
50
+
51
+ .. py:attribute:: root_path
52
+
53
+ Unless you feed this with only absolute path, you'll need a root_path
54
+ so that relative paths can be resolved. This should be your collection
55
+ root.
56
+
57
+ .. py:attribute:: pass_path
58
+
59
+ Unless you don't use ansible-vault, you'll need to give the pass to the
60
+ vault password here.
61
+ """
62
+ def __init__(self, root_path=None, pass_path=None):
63
+ self.root_path = Path(root_path) if root_path else None
64
+ self.pass_path = Path(pass_path) if pass_path else None
65
+
66
+ def __getitem__(self, key):
67
+ if key not in self:
68
+ self.read(key)
69
+ return super().__getitem__(key)
70
+
71
+ @functools.cached_property
72
+ def ansible_vault(self):
73
+ return cli2.which('ansible-vault')
74
+
75
+ def read(self, path):
76
+ """
77
+ Read an ansible YAML variable file.
78
+
79
+ :param path: Absolute path or path relative to :py:attr:`root_path`
80
+ """
81
+ key = path
82
+ path = Path(path)
83
+
84
+ if path.is_absolute():
85
+ path = path
86
+ elif self.root_path:
87
+ path = self.root_path / path
88
+ else:
89
+ raise Exception(f'{path} must be absolute if root_path not set')
90
+
91
+ if not path.exists():
92
+ raise Exception(f'{path} does not exist')
93
+
94
+ with path.open('r') as f:
95
+ content = f.read()
96
+
97
+ if content.strip().startswith('$ANSIBLE_VAULT'):
98
+ if not self.pass_path:
99
+ raise Exception('Vault password required in pass_path')
100
+ if not self.pass_path.exists():
101
+ raise Exception(f'{self.pass_path} does not exist')
102
+ args = [
103
+ self.ansible_vault,
104
+ 'view',
105
+ '--vault-password-file',
106
+ str(self.pass_path),
107
+ str(path),
108
+ ]
109
+ content = subprocess.check_output(args)
110
+
111
+ # todo: find a thread safe way to use our YAMLObject
112
+ Vault.ansible_vault = self.ansible_vault
113
+ Vault.pass_path = self.pass_path
114
+ self[key] = yaml.load(content, Loader=yaml.FullLoader)
115
+ return self[key]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cli2
3
- Version: 4.0.4
3
+ Version: 4.0.5
4
4
  Summary: image:: https://yourlabs.io/oss/cli2/badges/master/pipeline.svg
5
5
  Home-page: https://yourlabs.io/oss/cli2
6
6
  Author: James Pic
@@ -27,6 +27,7 @@ cli2/ansible/__init__.py
27
27
  cli2/ansible/action.py
28
28
  cli2/ansible/playbook.py
29
29
  cli2/ansible/pytest.py
30
+ cli2/ansible/variables.py
30
31
  cli2/examples/__init__.py
31
32
  cli2/examples/client.py
32
33
  cli2/examples/conf.py
@@ -37,6 +38,7 @@ cli2/examples/obj.py
37
38
  cli2/examples/obj2.py
38
39
  cli2/examples/test.py
39
40
  tests/test_ansible.py
41
+ tests/test_ansible_variables.py
40
42
  tests/test_cli.py
41
43
  tests/test_client.py
42
44
  tests/test_command.py
@@ -3,7 +3,7 @@ from setuptools import setup
3
3
 
4
4
  setup(
5
5
  name='cli2',
6
- version='4.0.4',
6
+ version='4.0.5',
7
7
  setup_requires='setupmeta',
8
8
  install_requires=[
9
9
  'docstring_parser',
@@ -0,0 +1,39 @@
1
+ from cli2 import ansible
2
+ import pytest
3
+ import os
4
+
5
+
6
+ def test_story():
7
+ variables = ansible.Variables(
8
+ root_path=os.path.dirname(__file__),
9
+ pass_path=os.path.dirname(__file__) + '/vault_pass',
10
+ )
11
+ variables.read('variables.yml')
12
+ assert variables['variables.yml'] == dict(foo='bar', vaulted='foobar')
13
+ assert variables['variables_vault.yml'] == dict(bar='foo')
14
+
15
+
16
+ def test_exceptions():
17
+ variables = ansible.Variables()
18
+ with pytest.raises(Exception) as exc:
19
+ variables['variables.yml']
20
+ assert exc.value.args == (
21
+ 'variables.yml must be absolute if root_path not set',
22
+ )
23
+
24
+ with pytest.raises(Exception) as exc:
25
+ variables['/variables_vault.yml']
26
+ assert exc.value.args == ('/variables_vault.yml does not exist',)
27
+
28
+ variables = ansible.Variables(root_path=os.path.dirname(__file__))
29
+ with pytest.raises(Exception) as exc:
30
+ variables['variables_vault.yml']
31
+ assert exc.value.args == ('Vault password required in pass_path',)
32
+
33
+ variables = ansible.Variables(
34
+ root_path=os.path.dirname(__file__),
35
+ pass_path='/does/not/exist',
36
+ )
37
+ with pytest.raises(Exception) as exc:
38
+ variables['variables_vault.yml']
39
+ assert exc.value.args == ('/does/not/exist does not exist',)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes