pyinfra 2.9.2__py2.py3-none-any.whl → 3.0b1__py2.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.
Files changed (126) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +261 -255
  3. pyinfra/api/arguments_typed.py +77 -0
  4. pyinfra/api/command.py +66 -53
  5. pyinfra/api/config.py +27 -22
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +2 -24
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +77 -113
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +17 -25
  13. pyinfra/api/operation.py +232 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +55 -70
  17. pyinfra/connectors/base.py +150 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +227 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +195 -207
  22. pyinfra/connectors/ssh.py +528 -615
  23. pyinfra/connectors/ssh_util.py +114 -0
  24. pyinfra/connectors/sshuserclient/client.py +5 -3
  25. pyinfra/connectors/terraform.py +86 -65
  26. pyinfra/connectors/util.py +212 -137
  27. pyinfra/connectors/vagrant.py +55 -48
  28. pyinfra/context.py +3 -2
  29. pyinfra/facts/docker.py +1 -0
  30. pyinfra/facts/files.py +45 -32
  31. pyinfra/facts/git.py +3 -1
  32. pyinfra/facts/gpg.py +1 -1
  33. pyinfra/facts/hardware.py +4 -2
  34. pyinfra/facts/iptables.py +5 -3
  35. pyinfra/facts/mysql.py +1 -0
  36. pyinfra/facts/postgres.py +168 -0
  37. pyinfra/facts/postgresql.py +5 -161
  38. pyinfra/facts/selinux.py +3 -1
  39. pyinfra/facts/server.py +77 -30
  40. pyinfra/facts/systemd.py +29 -12
  41. pyinfra/facts/sysvinit.py +10 -10
  42. pyinfra/facts/util/packaging.py +4 -2
  43. pyinfra/local.py +4 -5
  44. pyinfra/operations/apk.py +3 -3
  45. pyinfra/operations/apt.py +25 -47
  46. pyinfra/operations/brew.py +7 -14
  47. pyinfra/operations/bsdinit.py +4 -4
  48. pyinfra/operations/cargo.py +1 -1
  49. pyinfra/operations/choco.py +1 -1
  50. pyinfra/operations/dnf.py +4 -4
  51. pyinfra/operations/files.py +108 -321
  52. pyinfra/operations/gem.py +1 -1
  53. pyinfra/operations/git.py +6 -37
  54. pyinfra/operations/iptables.py +2 -10
  55. pyinfra/operations/launchd.py +1 -1
  56. pyinfra/operations/lxd.py +1 -9
  57. pyinfra/operations/mysql.py +5 -28
  58. pyinfra/operations/npm.py +1 -1
  59. pyinfra/operations/openrc.py +1 -1
  60. pyinfra/operations/pacman.py +3 -3
  61. pyinfra/operations/pip.py +14 -15
  62. pyinfra/operations/pkg.py +1 -1
  63. pyinfra/operations/pkgin.py +3 -3
  64. pyinfra/operations/postgres.py +347 -0
  65. pyinfra/operations/postgresql.py +17 -380
  66. pyinfra/operations/python.py +2 -17
  67. pyinfra/operations/selinux.py +5 -28
  68. pyinfra/operations/server.py +59 -84
  69. pyinfra/operations/snap.py +1 -3
  70. pyinfra/operations/ssh.py +8 -23
  71. pyinfra/operations/systemd.py +7 -7
  72. pyinfra/operations/sysvinit.py +3 -12
  73. pyinfra/operations/upstart.py +4 -4
  74. pyinfra/operations/util/__init__.py +12 -0
  75. pyinfra/operations/util/files.py +2 -2
  76. pyinfra/operations/util/packaging.py +6 -24
  77. pyinfra/operations/util/service.py +18 -37
  78. pyinfra/operations/vzctl.py +2 -2
  79. pyinfra/operations/xbps.py +3 -3
  80. pyinfra/operations/yum.py +4 -4
  81. pyinfra/operations/zypper.py +4 -4
  82. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/METADATA +19 -22
  83. pyinfra-3.0b1.dist-info/RECORD +163 -0
  84. pyinfra-3.0b1.dist-info/entry_points.txt +11 -0
  85. pyinfra_cli/__main__.py +2 -0
  86. pyinfra_cli/commands.py +7 -2
  87. pyinfra_cli/exceptions.py +83 -42
  88. pyinfra_cli/inventory.py +19 -4
  89. pyinfra_cli/log.py +17 -3
  90. pyinfra_cli/main.py +133 -90
  91. pyinfra_cli/prints.py +93 -129
  92. pyinfra_cli/util.py +60 -29
  93. tests/test_api/test_api.py +2 -0
  94. tests/test_api/test_api_arguments.py +13 -13
  95. tests/test_api/test_api_deploys.py +28 -29
  96. tests/test_api/test_api_facts.py +60 -98
  97. tests/test_api/test_api_operations.py +100 -200
  98. tests/test_cli/test_cli.py +18 -49
  99. tests/test_cli/test_cli_deploy.py +11 -37
  100. tests/test_cli/test_cli_exceptions.py +50 -19
  101. tests/test_cli/util.py +1 -1
  102. tests/test_connectors/test_chroot.py +6 -6
  103. tests/test_connectors/test_docker.py +4 -4
  104. tests/test_connectors/test_dockerssh.py +38 -50
  105. tests/test_connectors/test_local.py +11 -12
  106. tests/test_connectors/test_ssh.py +66 -107
  107. tests/test_connectors/test_terraform.py +9 -15
  108. tests/test_connectors/test_util.py +24 -46
  109. tests/test_connectors/test_vagrant.py +4 -4
  110. pyinfra/api/operation.pyi +0 -117
  111. pyinfra/connectors/ansible.py +0 -171
  112. pyinfra/connectors/mech.py +0 -186
  113. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  114. pyinfra/connectors/winrm.py +0 -320
  115. pyinfra/facts/windows.py +0 -366
  116. pyinfra/facts/windows_files.py +0 -90
  117. pyinfra/operations/windows.py +0 -59
  118. pyinfra/operations/windows_files.py +0 -551
  119. pyinfra-2.9.2.dist-info/RECORD +0 -170
  120. pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
  121. tests/test_connectors/test_ansible.py +0 -64
  122. tests/test_connectors/test_mech.py +0 -126
  123. tests/test_connectors/test_winrm.py +0 -76
  124. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
  125. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
  126. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/top_level.txt +0 -0
@@ -3,90 +3,68 @@
3
3
  from unittest import TestCase
4
4
 
5
5
  from pyinfra.api import Config, State
6
- from pyinfra.connectors.util import (
7
- make_unix_command,
8
- make_unix_command_for_host,
9
- split_combined_output,
10
- )
6
+ from pyinfra.connectors.util import make_unix_command, make_unix_command_for_host
11
7
 
12
8
  from ..util import make_inventory
13
9
 
14
10
 
15
- class TestConnectorUtil(TestCase):
16
- def test_split_combined_output_works(self):
17
- results = split_combined_output(
18
- [
19
- ("stdout", "stdout1"),
20
- ("stdout", "stdout2"),
21
- ("stderr", "stderr1"),
22
- ("stdout", "stdout3"),
23
- ],
24
- )
25
-
26
- assert results == (["stdout1", "stdout2", "stdout3"], ["stderr1"])
27
-
28
- def test_split_combined_output_raises(self):
29
- with self.assertRaises(ValueError):
30
- split_combined_output(["nope", ""])
31
-
32
-
33
11
  class TestMakeUnixCommandConnectorUtil(TestCase):
34
12
  def test_command(self):
35
13
  command = make_unix_command("echo Šablony")
36
14
  assert command.get_raw_value() == "sh -c 'echo Šablony'"
37
15
 
38
16
  def test_doas_command(self):
39
- command = make_unix_command("uptime", doas=True)
17
+ command = make_unix_command("uptime", _doas=True)
40
18
  assert command.get_raw_value() == "doas -n sh -c uptime"
41
19
 
42
20
  def test_doas_user_command(self):
43
- command = make_unix_command("uptime", doas=True, doas_user="pyinfra")
21
+ command = make_unix_command("uptime", _doas=True, _doas_user="pyinfra")
44
22
  assert command.get_raw_value() == "doas -n -u pyinfra sh -c uptime"
45
23
 
46
24
  def test_sudo_command(self):
47
- command = make_unix_command("uptime", sudo=True)
25
+ command = make_unix_command("uptime", _sudo=True)
48
26
  assert command.get_raw_value() == "sudo -H -n sh -c uptime"
49
27
 
50
28
  def test_sudo_multi_arg_command(self):
51
- command = make_unix_command("echo hi", sudo=True, preserve_sudo_env=True)
29
+ command = make_unix_command("echo hi", _sudo=True, _preserve_sudo_env=True)
52
30
  assert command.get_raw_value() == "sudo -H -n -E sh -c 'echo hi'"
53
31
 
54
32
  def test_sudo_preserve_env_command(self):
55
- command = make_unix_command("uptime", sudo=True, preserve_sudo_env=True)
33
+ command = make_unix_command("uptime", _sudo=True, _preserve_sudo_env=True)
56
34
  assert command.get_raw_value() == "sudo -H -n -E sh -c uptime"
57
35
 
58
36
  def test_use_sudo_login_command(self):
59
- command = make_unix_command("uptime", sudo=True, use_sudo_login=True)
37
+ command = make_unix_command("uptime", _sudo=True, _use_sudo_login=True)
60
38
  assert command.get_raw_value() == "sudo -H -n -i sh -c uptime"
61
39
 
62
40
  def test_sudo_user_command(self):
63
- command = make_unix_command("uptime", sudo=True, sudo_user="pyinfra")
41
+ command = make_unix_command("uptime", _sudo=True, _sudo_user="pyinfra")
64
42
  assert command.get_raw_value() == "sudo -H -n -u pyinfra sh -c uptime"
65
43
 
66
44
  def test_su_command(self):
67
- command = make_unix_command("uptime", su_user="pyinfra")
45
+ command = make_unix_command("uptime", _su_user="pyinfra")
68
46
  assert command.get_raw_value() == "su pyinfra -c 'sh -c uptime'"
69
47
 
70
48
  def test_su_multi_arg_command(self):
71
- command = make_unix_command("echo hi", su_user="pyinfra")
49
+ command = make_unix_command("echo hi", _su_user="pyinfra")
72
50
  assert command.get_raw_value() == "su pyinfra -c 'sh -c '\"'\"'echo hi'\"'\"''"
73
51
 
74
52
  def test_use_su_login_command(self):
75
- command = make_unix_command("uptime", su_user="pyinfra", use_su_login=True)
53
+ command = make_unix_command("uptime", _su_user="pyinfra", _use_su_login=True)
76
54
  assert command.get_raw_value() == "su -l pyinfra -c 'sh -c uptime'"
77
55
 
78
56
  def test_preserve_su_env_command(self):
79
- command = make_unix_command("uptime", su_user="pyinfra", preserve_su_env=True)
57
+ command = make_unix_command("uptime", _su_user="pyinfra", _preserve_su_env=True)
80
58
  assert command.get_raw_value() == "su -m pyinfra -c 'sh -c uptime'"
81
59
 
82
60
  def test_su_shell_command(self):
83
- command = make_unix_command("uptime", su_user="pyinfra", su_shell="bash")
61
+ command = make_unix_command("uptime", _su_user="pyinfra", _su_shell="bash")
84
62
  assert command.get_raw_value() == "su -s `which bash` pyinfra -c 'sh -c uptime'"
85
63
 
86
64
  def test_command_env(self):
87
65
  command = make_unix_command(
88
66
  "uptime",
89
- env={
67
+ _env={
90
68
  "key": "value",
91
69
  "anotherkey": "anothervalue",
92
70
  },
@@ -97,23 +75,23 @@ class TestMakeUnixCommandConnectorUtil(TestCase):
97
75
  ]
98
76
 
99
77
  def test_command_chdir(self):
100
- command = make_unix_command("uptime", chdir="/opt/somedir")
78
+ command = make_unix_command("uptime", _chdir="/opt/somedir")
101
79
  assert command.get_raw_value() == "sh -c 'cd /opt/somedir && uptime'"
102
80
 
103
81
  def test_custom_shell_command(self):
104
- command = make_unix_command("uptime", shell_executable="bash")
82
+ command = make_unix_command("uptime", _shell_executable="bash")
105
83
  assert command.get_raw_value() == "bash -c uptime"
106
84
 
107
85
  def test_mixed_command(self):
108
86
  command = make_unix_command(
109
87
  "echo hi",
110
- chdir="/opt/somedir",
111
- env={"key": "value"},
112
- sudo=True,
113
- sudo_user="root",
114
- preserve_sudo_env=True,
115
- su_user="pyinfra",
116
- shell_executable="bash",
88
+ _chdir="/opt/somedir",
89
+ _env={"key": "value"},
90
+ _sudo=True,
91
+ _sudo_user="root",
92
+ _preserve_sudo_env=True,
93
+ _su_user="pyinfra",
94
+ _shell_executable="bash",
117
95
  )
118
96
  assert command.get_raw_value() == (
119
97
  "sudo -H -n -E -u root " # sudo bit
@@ -125,7 +103,7 @@ class TestMakeUnixCommandConnectorUtil(TestCase):
125
103
  def test_command_exists_su_config_only(self):
126
104
  """
127
105
  This tests covers a bug that appeared when `make_unix_command` is called
128
- with `su_user=False` (default) but `SU_USER` set on the config object,
106
+ with `_su_user=False` (default) but `SU_USER` set on the config object,
129
107
  resulting in an empty command output.
130
108
  """
131
109
  state = State(make_inventory(), Config(SU_USER=True))
@@ -3,7 +3,7 @@ from unittest import TestCase
3
3
  from unittest.mock import mock_open, patch
4
4
 
5
5
  from pyinfra.api.exceptions import InventoryError
6
- from pyinfra.connectors.vagrant import get_vagrant_options, make_names_data
6
+ from pyinfra.connectors.vagrant import VagrantInventoryConnector, get_vagrant_options
7
7
 
8
8
  FAKE_VAGRANT_OPTIONS = {
9
9
  "groups": {
@@ -69,7 +69,7 @@ class TestVagrantConnector(TestCase):
69
69
  )
70
70
  @patch("pyinfra.connectors.vagrant.path.exists", lambda path: True)
71
71
  def test_make_names_data_with_options(self):
72
- data = make_names_data()
72
+ data = list(VagrantInventoryConnector.make_names_data())
73
73
 
74
74
  assert data == [
75
75
  (
@@ -103,7 +103,7 @@ class TestVagrantConnector(TestCase):
103
103
  ]
104
104
 
105
105
  def test_make_names_data_with_limit(self):
106
- data = make_names_data(limit=("ubuntu16",))
106
+ data = list(VagrantInventoryConnector.make_names_data(name=("ubuntu16",)))
107
107
 
108
108
  assert data == [
109
109
  (
@@ -120,4 +120,4 @@ class TestVagrantConnector(TestCase):
120
120
 
121
121
  def test_make_names_data_no_matches(self):
122
122
  with self.assertRaises(InventoryError):
123
- make_names_data(limit="nope")
123
+ list(VagrantInventoryConnector.make_names_data(name="nope"))
pyinfra/api/operation.pyi DELETED
@@ -1,117 +0,0 @@
1
- from typing import (
2
- Callable,
3
- Dict,
4
- Generator,
5
- Generic,
6
- Iterable,
7
- List,
8
- Mapping,
9
- Protocol,
10
- Tuple,
11
- overload,
12
- )
13
-
14
- from typing_extensions import ParamSpec
15
-
16
- from pyinfra.api.command import (
17
- FileDownloadCommand,
18
- FileUploadCommand,
19
- FunctionCommand,
20
- StringCommand,
21
- )
22
- from pyinfra.api.host import Host
23
- from pyinfra.api.state import State
24
-
25
- P = ParamSpec("P")
26
-
27
- Command = str | StringCommand | FileDownloadCommand | FileUploadCommand | FunctionCommand
28
-
29
- class OperationMeta:
30
- changed: bool
31
- commands: List[str] | None
32
- hash: str | None
33
-
34
- stdout_lines: List[str]
35
- stdout: str
36
- stderr_lines: List[str]
37
- stderr: str
38
-
39
- class Operation(Generic[P], Protocol):
40
- def __call__(
41
- self,
42
- _sudo: bool | None = None,
43
- _sudo_user: str | None = None,
44
- _use_sudo_login: bool | None = None,
45
- _use_sudo_password: bool | None = None,
46
- _preserve_sudo_env: bool | None = None,
47
- _su_user: str | None = None,
48
- _use_su_login: bool | None = None,
49
- _preserve_su_env: bool | None = None,
50
- _su_shell: str | None = None,
51
- _doas: bool | None = None,
52
- _doas_user: str | None = None,
53
- _shell_executable: str | None = None,
54
- _chdir: str | None = None,
55
- _env: Mapping[str, str] | None = None,
56
- _success_exit_codes: Iterable[int] | None = None,
57
- _timeout: int | None = None,
58
- _get_pty: bool | None = None,
59
- _stdin: str | List[str] | Tuple[str, ...] | None = None,
60
- name: str | None = None,
61
- _ignore_errors: bool | None = None,
62
- _continue_on_error: bool | None = None,
63
- _precondition: str | None = None,
64
- _postcondition: str | None = None,
65
- _on_success: Callable[[State, Host, str], None] | None = None,
66
- _on_error: Callable[[State, Host, str], None] | None = None,
67
- _parallel: int | None = None,
68
- _run_once: bool | None = None,
69
- _serial: bool | None = None,
70
- *args: P.args,
71
- **kwargs: P.kwargs,
72
- ) -> OperationMeta: ...
73
-
74
- def add_op(
75
- state: State,
76
- op_func: Operation[P],
77
- _sudo: bool | None = None,
78
- _sudo_user: str | None = None,
79
- _use_sudo_login: bool | None = None,
80
- _use_sudo_password: bool | None = None,
81
- _preserve_sudo_env: bool | None = None,
82
- _su_user: str | None = None,
83
- _use_su_login: bool | None = None,
84
- _preserve_su_env: bool | None = None,
85
- _su_shell: str | None = None,
86
- _doas: bool | None = None,
87
- _doas_user: str | None = None,
88
- _shell_executable: str | None = None,
89
- _chdir: str | None = None,
90
- _env: Mapping[str, str] | None = None,
91
- _success_exit_codes: Iterable[int] | None = None,
92
- _timeout: int | None = None,
93
- _get_pty: bool | None = None,
94
- _stdin: str | List[str] | Tuple[str, ...] | None = None,
95
- name: str | None = None,
96
- _ignore_errors: bool | None = None,
97
- _continue_on_error: bool | None = None,
98
- _precondition: str | None = None,
99
- _postcondition: str | None = None,
100
- _on_success: Callable[[State, Host, str], None] | None = None,
101
- _on_error: Callable[[State, Host, str], None] | None = None,
102
- _parallel: int | None = None,
103
- _run_once: bool | None = None,
104
- _serial: bool | None = None,
105
- host: Iterable[Host] | Host | None = None,
106
- *args: P.args,
107
- **kwargs: P.kwargs,
108
- ) -> Dict[Host, OperationMeta]: ...
109
- @overload
110
- def operation(func: Callable[P, Generator[Command, None, None]]) -> Operation[P]: ...
111
- @overload
112
- def operation(
113
- pipeline_facts=None,
114
- is_idempotent: bool = True,
115
- idempotent_notice=None,
116
- frame_offset=1,
117
- ) -> Callable[[Callable[P, Generator[Command, None, None]]], Operation[P]]: ...
@@ -1,171 +0,0 @@
1
- """
2
- **Note**: this connector is a work in progress! While it parses the list of
3
- hosts OK, it doesn't handle nested groups properly yet.
4
-
5
- The `@ansible` connector can be used to parse Ansible inventory files.
6
-
7
- .. code:: python
8
-
9
- # Load an Ansible inventory relative to the current directory
10
- pyinfra @ansible/path/to/inventory
11
-
12
- # Load using an absolute path
13
- pyinfra @ansible//absolute/path/to/inventory
14
- """
15
- import json
16
- import re
17
- from collections import defaultdict
18
- from configparser import ConfigParser
19
- from os import path
20
- from typing import TYPE_CHECKING, Optional
21
-
22
- from pyinfra import logger
23
- from pyinfra.api.exceptions import InventoryError
24
- from pyinfra.api.util import memoize
25
-
26
- if TYPE_CHECKING:
27
- from pyinfra.api.host import Host
28
-
29
- try:
30
- import yaml
31
- except ImportError:
32
- yaml = None # type: ignore
33
-
34
-
35
- @memoize
36
- def show_warning():
37
- logger.warning("The @ansible connector is in alpha!")
38
-
39
-
40
- def make_names_data(inventory_filename: Optional[str] = None):
41
- show_warning()
42
-
43
- if not inventory_filename:
44
- raise InventoryError("No Ansible inventory filename provided!")
45
-
46
- if not path.exists(inventory_filename):
47
- raise InventoryError(
48
- ("Could not find Ansible inventory file: {0}").format(inventory_filename),
49
- )
50
-
51
- return parse_inventory(inventory_filename)
52
-
53
-
54
- def parse_inventory(inventory_filename: str):
55
- # fallback to INI if no extension
56
- extension = inventory_filename.split(".")[-1] if "." in inventory_filename else "ini"
57
-
58
- # host:set(groups) mapping
59
- host_to_groups = {}
60
-
61
- if extension in ["ini"]:
62
- host_to_groups = parse_inventory_ini(inventory_filename)
63
- elif extension in ["json"]:
64
- with open(inventory_filename, encoding="utf-8") as inventory_file:
65
- inventory_tree = json.load(inventory_file)
66
- # close file early
67
- host_to_groups = parse_inventory_tree(inventory_tree)
68
- elif extension in ["yaml", "yml"]:
69
- if yaml is None:
70
- raise Exception(
71
- (
72
- "To parse YAML Ansible inventories requires `pyyaml`. "
73
- "Install it with `pip install pyyaml`."
74
- ),
75
- )
76
- with open(inventory_filename, encoding="utf-8") as inventory_file:
77
- inventory_tree = yaml.safe_load(inventory_file)
78
- # close file early
79
- host_to_groups = parse_inventory_tree(inventory_tree)
80
- else:
81
- raise InventoryError(("Ansible inventory file format not supported: {0}").format(extension))
82
-
83
- return [(host, {}, sorted(list(host_to_groups.get(host, [])))) for host in host_to_groups]
84
-
85
-
86
- def parse_inventory_ini(inventory_filename: str):
87
- config = ConfigParser(
88
- delimiters=(" "), # we only handle the hostnames for now
89
- allow_no_value=True, # we don't by default have = values
90
- interpolation=None, # remove any Python interpolation
91
- )
92
- config.read(inventory_filename)
93
-
94
- host_to_groups = defaultdict(set)
95
- group_to_hosts = defaultdict(set)
96
- hosts = []
97
-
98
- # First pass - load hosts/groups of hosts
99
- for section in config.sections():
100
- if ":" in section: # ignore :children and :vars sections this time
101
- continue
102
-
103
- options = config.options(section)
104
- for host in _parse_ansible_hosts(options):
105
- hosts.append(host)
106
- host_to_groups[host].add(section)
107
- group_to_hosts[section].add(host)
108
-
109
- # Second pass - load any children groups
110
- for section in config.sections():
111
- if not section.endswith(":children"): # we only support :children for now
112
- continue
113
-
114
- group_name = section.replace(":children", "")
115
-
116
- options = config.options(section)
117
- for sub_group_name in options:
118
- sub_group_hosts = group_to_hosts[sub_group_name]
119
- for host in sub_group_hosts:
120
- host_to_groups[host].add(group_name)
121
-
122
- return host_to_groups
123
-
124
-
125
- def _parse_ansible_hosts(hosts):
126
- for host in hosts:
127
- expand_match = re.search(r"\[[0-9:]+\]", host)
128
- if expand_match:
129
- expand_string = host[expand_match.start() : expand_match.end()]
130
- bits = expand_string[1:-1].split(":") # remove the [] either side
131
-
132
- zfill = 0
133
- if bits[0].startswith("0"):
134
- zfill = len(bits[0])
135
-
136
- start, end = int(bits[0]), int(bits[1])
137
- step = int(bits[2]) if len(bits) > 2 else 1
138
-
139
- for n in range(start, end + 1, step):
140
- number_as_string = "{0}".format(n)
141
- if zfill:
142
- number_as_string = number_as_string.zfill(zfill)
143
-
144
- hostname = host.replace(expand_string, number_as_string)
145
- yield hostname
146
- else:
147
- yield host
148
-
149
-
150
- def parse_inventory_tree(inventory_tree, host_to_groups=dict(), group_stack=set()):
151
- for group in inventory_tree:
152
- # set logic adds tolerance for duplicate group names
153
- groups = group_stack.union({group})
154
-
155
- if "hosts" in inventory_tree[group]:
156
- for host in inventory_tree[group]["hosts"]:
157
- append_groups_to_host(host, groups, host_to_groups)
158
-
159
- if "children" in inventory_tree[group]:
160
- # recursively parse inventory tree
161
- parse_inventory_tree(inventory_tree[group]["children"], host_to_groups, groups)
162
-
163
- return host_to_groups
164
-
165
-
166
- def append_groups_to_host(host: "Host", groups, host_to_groups):
167
- if host in host_to_groups:
168
- # set logic handles de-duplication
169
- host_to_groups[host] = host_to_groups[host].union(groups)
170
- else:
171
- host_to_groups[host] = groups
@@ -1,186 +0,0 @@
1
- """
2
- The ``@mech`` connector reads the current mech status and generates an inventory
3
- for any running VMs.
4
-
5
- .. code:: python
6
-
7
- # Run on all hosts
8
- pyinfra @mech ...
9
-
10
- # Run on a specific VM
11
- pyinfra @mech/my-vm-name ...
12
-
13
- # Run on multiple named VMs
14
- pyinfra @mech/my-vm-name,@mech/another-vm-name ...
15
- """
16
-
17
- import json
18
- from os import path
19
- from queue import Queue
20
- from threading import Thread
21
-
22
- from pyinfra import local, logger
23
- from pyinfra.api.exceptions import InventoryError
24
- from pyinfra.api.util import memoize
25
- from pyinfra.progress import progress_spinner
26
-
27
-
28
- def _get_mech_ssh_config(queue, progress, target):
29
- logger.debug("Loading SSH config for %s", target)
30
-
31
- # Note: We have to work-around the fact that "mech ssh-config somehost"
32
- # does not return the correct "Host" value. When "mech" fixes this
33
- # issue we can simply this code.
34
- lines = local.shell(
35
- "mech ssh-config {0}".format(target),
36
- splitlines=True,
37
- )
38
-
39
- newlines = []
40
- for line in lines:
41
- if line.startswith("Host "):
42
- newlines.append("Host " + target)
43
- else:
44
- newlines.append(line)
45
-
46
- queue.put(newlines)
47
-
48
- progress(target)
49
-
50
-
51
- @memoize
52
- def get_mech_config(limit=None):
53
- logger.info("Getting Mech config...")
54
-
55
- if limit and not isinstance(limit, (list, tuple)):
56
- limit = [limit]
57
-
58
- # Note: There is no "--machine-readable" option to 'mech status'
59
- with progress_spinner({"mech ls"}) as progress:
60
- output = local.shell(
61
- "mech ls",
62
- splitlines=True,
63
- )
64
- progress("mech ls")
65
-
66
- targets = []
67
-
68
- for line in output:
69
-
70
- address = ""
71
-
72
- data = line.split()
73
- target = data[0]
74
-
75
- if len(data) == 5:
76
- address = data[1]
77
-
78
- # Skip anything not in the limit
79
- if limit is not None and target not in limit:
80
- continue
81
-
82
- # For each vm that has an address, fetch it's SSH config in a thread
83
- if address != "" and address[0].isdigit():
84
- targets.append(target)
85
-
86
- threads = []
87
- config_queue = Queue()
88
-
89
- with progress_spinner(targets) as progress:
90
- for target in targets:
91
- thread = Thread(
92
- target=_get_mech_ssh_config,
93
- args=(config_queue, progress, target),
94
- )
95
- threads.append(thread)
96
- thread.start()
97
-
98
- for thread in threads:
99
- thread.join()
100
-
101
- queue_items = list(config_queue.queue)
102
-
103
- lines = []
104
- for output in queue_items:
105
- lines.extend(output)
106
-
107
- return lines
108
-
109
-
110
- @memoize
111
- def get_mech_options():
112
- if path.exists("@mech.json"):
113
- with open("@mech.json", "r", encoding="utf-8") as f:
114
- return json.loads(f.read())
115
- return {}
116
-
117
-
118
- def _make_name_data(host):
119
- mech_options = get_mech_options()
120
- mech_host = host["Host"]
121
-
122
- data = {
123
- "ssh_hostname": host["HostName"],
124
- }
125
-
126
- for config_key, data_key in (
127
- ("Port", "ssh_port"),
128
- ("User", "ssh_user"),
129
- ("IdentityFile", "ssh_key"),
130
- ):
131
- if config_key in host:
132
- data[data_key] = host[config_key]
133
-
134
- # Update any configured JSON data
135
- if mech_host in mech_options.get("data", {}):
136
- data.update(mech_options["data"][mech_host])
137
-
138
- # Work out groups
139
- groups = mech_options.get("groups", {}).get(mech_host, [])
140
-
141
- if "@mech" not in groups:
142
- groups.append("@mech")
143
-
144
- return "@mech/{0}".format(host["Host"]), data, groups
145
-
146
-
147
- def make_names_data(limit=None):
148
- mech_ssh_info = get_mech_config(limit)
149
-
150
- logger.debug("Got Mech SSH info: \n%s", mech_ssh_info)
151
-
152
- hosts = []
153
- current_host = None
154
-
155
- for line in mech_ssh_info:
156
- if not line:
157
- if current_host:
158
- hosts.append(_make_name_data(current_host))
159
-
160
- current_host = None
161
- continue
162
-
163
- key, value = line.strip().split(" ", 1)
164
-
165
- if key == "Host":
166
- if current_host:
167
- hosts.append(_make_name_data(current_host))
168
-
169
- # Set the new host
170
- current_host = {
171
- key: value,
172
- }
173
-
174
- elif current_host:
175
- current_host[key] = value
176
-
177
- else:
178
- logger.debug("Extra Mech SSH key/value (%s=%s)", key, value)
179
-
180
- if current_host:
181
- hosts.append(_make_name_data(current_host))
182
-
183
- if not hosts:
184
- raise InventoryError("No running Mech instances found!")
185
-
186
- return hosts
@@ -1,28 +0,0 @@
1
- import base64
2
-
3
- import winrm
4
-
5
-
6
- class PyinfraWinrmSession(winrm.Session):
7
- """This is our subclassed Session that allows for env setting"""
8
-
9
- def run_cmd(self, command, args=(), env=None):
10
- shell_id = self.protocol.open_shell(env_vars=env)
11
- command_id = self.protocol.run_command(shell_id, command, args)
12
- rs = winrm.Response(self.protocol.get_command_output(shell_id, command_id))
13
- self.protocol.cleanup_command(shell_id, command_id)
14
- self.protocol.close_shell(shell_id)
15
- return rs
16
-
17
- def run_ps(self, script, env=None):
18
- """base64 encodes a Powershell script and executes the powershell
19
- encoded script command
20
- """
21
- # must use utf16 little endian on windows
22
- encoded_ps = base64.b64encode(script.encode("utf_16_le")).decode("ascii")
23
- rs = self.run_cmd("powershell -encodedcommand {0}".format(encoded_ps), env=env)
24
- if len(rs.std_err):
25
- # if there was an error message, clean it it up and make it human
26
- # readable
27
- rs.std_err = self._clean_error_msg(rs.std_err)
28
- return rs