pyinfra 2.9.2__py2.py3-none-any.whl → 3.0__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.
- pyinfra/api/__init__.py +3 -0
- pyinfra/api/arguments.py +265 -253
- pyinfra/api/arguments_typed.py +80 -0
- pyinfra/api/command.py +68 -53
- pyinfra/api/config.py +139 -32
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +7 -26
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +102 -137
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +21 -25
- pyinfra/api/operation.py +240 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +79 -86
- pyinfra/connectors/base.py +147 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +220 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +196 -208
- pyinfra/connectors/ssh.py +530 -613
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +211 -137
- pyinfra/connectors/vagrant.py +60 -53
- pyinfra/context.py +4 -2
- pyinfra/facts/apk.py +2 -0
- pyinfra/facts/apt.py +2 -0
- pyinfra/facts/brew.py +2 -0
- pyinfra/facts/bsdinit.py +2 -0
- pyinfra/facts/cargo.py +2 -0
- pyinfra/facts/choco.py +2 -0
- pyinfra/facts/deb.py +7 -2
- pyinfra/facts/dnf.py +2 -0
- pyinfra/facts/docker.py +19 -0
- pyinfra/facts/files.py +47 -32
- pyinfra/facts/gem.py +2 -0
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +3 -1
- pyinfra/facts/hardware.py +34 -24
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/launchd.py +2 -0
- pyinfra/facts/lxd.py +2 -0
- pyinfra/facts/mysql.py +13 -6
- pyinfra/facts/npm.py +1 -0
- pyinfra/facts/openrc.py +2 -0
- pyinfra/facts/pacman.py +6 -2
- pyinfra/facts/pip.py +2 -0
- pyinfra/facts/pkg.py +2 -0
- pyinfra/facts/pkgin.py +2 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +6 -160
- pyinfra/facts/rpm.py +12 -9
- pyinfra/facts/runit.py +68 -0
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +80 -36
- pyinfra/facts/snap.py +2 -0
- pyinfra/facts/systemd.py +31 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/upstart.py +2 -0
- pyinfra/facts/util/packaging.py +7 -4
- pyinfra/facts/vzctl.py +2 -0
- pyinfra/facts/xbps.py +2 -0
- pyinfra/facts/yum.py +2 -0
- pyinfra/facts/zypper.py +2 -0
- pyinfra/local.py +4 -5
- pyinfra/operations/apk.py +6 -4
- pyinfra/operations/apt.py +46 -65
- pyinfra/operations/brew.py +17 -22
- pyinfra/operations/bsdinit.py +9 -7
- pyinfra/operations/cargo.py +4 -2
- pyinfra/operations/choco.py +4 -2
- pyinfra/operations/dnf.py +19 -23
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +188 -386
- pyinfra/operations/gem.py +4 -2
- pyinfra/operations/git.py +24 -53
- pyinfra/operations/iptables.py +29 -35
- pyinfra/operations/launchd.py +6 -7
- pyinfra/operations/lxd.py +8 -13
- pyinfra/operations/mysql.py +62 -81
- pyinfra/operations/npm.py +9 -2
- pyinfra/operations/openrc.py +6 -4
- pyinfra/operations/pacman.py +7 -8
- pyinfra/operations/pip.py +25 -24
- pyinfra/operations/pkg.py +4 -2
- pyinfra/operations/pkgin.py +6 -4
- pyinfra/operations/postgres.py +349 -0
- pyinfra/operations/postgresql.py +18 -379
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +8 -19
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +47 -44
- pyinfra/operations/server.py +111 -127
- pyinfra/operations/snap.py +4 -4
- pyinfra/operations/ssh.py +20 -33
- pyinfra/operations/systemd.py +19 -15
- pyinfra/operations/sysvinit.py +9 -16
- pyinfra/operations/upstart.py +9 -7
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +177 -0
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +55 -57
- pyinfra/operations/util/service.py +39 -51
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +6 -4
- pyinfra/operations/yum.py +18 -22
- pyinfra/operations/zypper.py +12 -13
- pyinfra/version.py +5 -2
- {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
- pyinfra-3.0.dist-info/RECORD +167 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
- pyinfra-3.0.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +4 -3
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +78 -42
- pyinfra_cli/inventory.py +40 -6
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +95 -127
- pyinfra_cli/util.py +62 -29
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +13 -13
- tests/test_api/test_api_deploys.py +28 -29
- tests/test_api/test_api_facts.py +60 -98
- tests/test_api/test_api_operations.py +101 -201
- tests/test_cli/test_cli.py +18 -49
- tests/test_cli/test_cli_deploy.py +11 -37
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_chroot.py +6 -6
- tests/test_connectors/test_docker.py +4 -4
- tests/test_connectors/test_dockerssh.py +38 -50
- tests/test_connectors/test_local.py +11 -12
- tests/test_connectors/test_ssh.py +105 -93
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +7 -7
- pyinfra/api/operation.pyi +0 -117
- pyinfra/connectors/ansible.py +0 -171
- pyinfra/connectors/mech.py +0 -186
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -320
- pyinfra/facts/windows.py +0 -366
- pyinfra/facts/windows_files.py +0 -90
- pyinfra/operations/windows.py +0 -59
- pyinfra/operations/windows_files.py +0 -551
- pyinfra-2.9.2.dist-info/RECORD +0 -170
- pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- tests/test_connectors/test_winrm.py +0 -76
- {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
pyinfra/connectors/mech.py
DELETED
|
@@ -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
|
pyinfra/connectors/winrm.py
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
.. warning::
|
|
3
|
-
This connector is in alpha and may change in future releases.
|
|
4
|
-
|
|
5
|
-
Some Windows facts and Windows operations work but this is to be considered
|
|
6
|
-
experimental. For now, only ``winrm_username`` and ``winrm_password`` is
|
|
7
|
-
being used. There are other methods for authentication, but they have not yet
|
|
8
|
-
been added/experimented with.
|
|
9
|
-
|
|
10
|
-
The ``@winrm`` connector can be used to communicate with Windows instances that have WinRM enabled.
|
|
11
|
-
|
|
12
|
-
Examples using ``@winrm``:
|
|
13
|
-
|
|
14
|
-
.. code:: python
|
|
15
|
-
|
|
16
|
-
# Get the windows_home fact
|
|
17
|
-
pyinfra @winrm/192.168.3.232 --winrm-username vagrant \\
|
|
18
|
-
--winrm-password vagrant --winrm-port 5985 -vv --debug fact windows_home
|
|
19
|
-
|
|
20
|
-
# Create a directory
|
|
21
|
-
pyinfra @winrm/192.168.3.232 --winrm-username vagrant \\
|
|
22
|
-
--winrm-password vagrant --winrm-port 5985 windows_files.windows_directory 'c:\temp'
|
|
23
|
-
|
|
24
|
-
# Run a powershell command ('ps' is the default shell-executable for the winrm connector)
|
|
25
|
-
pyinfra @winrm/192.168.3.232 --winrm-username vagrant \\
|
|
26
|
-
--winrm-password vagrant --winrm-port 5985 exec -- write-host hello
|
|
27
|
-
|
|
28
|
-
# Run a command using the command prompt:
|
|
29
|
-
pyinfra @winrm/192.168.3.232 --winrm-username vagrant \\
|
|
30
|
-
--winrm-password vagrant --winrm-port 5985 --shell-executable cmd exec -- date /T
|
|
31
|
-
|
|
32
|
-
# Run a command using the winrm ntlm transport
|
|
33
|
-
pyinfra @winrm/192.168.3.232 --winrm-username vagrant \\
|
|
34
|
-
--winrm-password vagrant --winrm-port 5985 --winrm-transport ntlm exec -- hostname
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
import base64
|
|
38
|
-
import ntpath
|
|
39
|
-
|
|
40
|
-
import click
|
|
41
|
-
|
|
42
|
-
from pyinfra import logger
|
|
43
|
-
from pyinfra.api.connectors import BaseConnectorMeta
|
|
44
|
-
from pyinfra.api.exceptions import ConnectError, PyinfraError
|
|
45
|
-
from pyinfra.api.util import get_file_io, memoize, sha1_hash
|
|
46
|
-
|
|
47
|
-
from .pyinfrawinrmsession import PyinfraWinrmSession
|
|
48
|
-
from .util import make_win_command
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class Meta(BaseConnectorMeta):
|
|
52
|
-
handles_execution = True
|
|
53
|
-
keys_prefix = "winrm"
|
|
54
|
-
|
|
55
|
-
class DataKeys:
|
|
56
|
-
hostname = "WinRM hostname to connect to"
|
|
57
|
-
port = "WinRM port to connect to"
|
|
58
|
-
user = "WinRM username"
|
|
59
|
-
password = "WinRM password"
|
|
60
|
-
transport = "WinRM transport (default: ``plaintext``)"
|
|
61
|
-
read_timeout_sec = "Read timeout in seconds (default: ``30``)"
|
|
62
|
-
operation_timeout_sec = "Operation timeout in seconds (default: ``20``)"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
DATA_KEYS = Meta.keys()
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _raise_connect_error(host, message, data):
|
|
69
|
-
message = "{0} ({1})".format(message, data)
|
|
70
|
-
raise ConnectError(message)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@memoize
|
|
74
|
-
def show_warning():
|
|
75
|
-
logger.warning("The @winrm connector is alpha!")
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _make_winrm_kwargs(state, host):
|
|
79
|
-
kwargs = {}
|
|
80
|
-
|
|
81
|
-
for key, value in (
|
|
82
|
-
("username", host.data.get(DATA_KEYS.user)),
|
|
83
|
-
("password", host.data.get(DATA_KEYS.password)),
|
|
84
|
-
("winrm_port", int(host.data.get(DATA_KEYS.port, 0))),
|
|
85
|
-
("winrm_transport", host.data.get(DATA_KEYS.transport, "plaintext")),
|
|
86
|
-
(
|
|
87
|
-
"winrm_read_timeout_sec",
|
|
88
|
-
host.data.get(DATA_KEYS.read_timeout_sec, 30),
|
|
89
|
-
),
|
|
90
|
-
(
|
|
91
|
-
"winrm_operation_timeout_sec",
|
|
92
|
-
host.data.get(DATA_KEYS.operation_timeout_sec, 20),
|
|
93
|
-
),
|
|
94
|
-
):
|
|
95
|
-
if value:
|
|
96
|
-
kwargs[key] = value
|
|
97
|
-
|
|
98
|
-
# FUTURE: add more auth
|
|
99
|
-
# pywinrm supports: basic, certificate, ntlm, kerberos, plaintext, ssl, credssp
|
|
100
|
-
# see https://github.com/diyan/pywinrm/blob/master/winrm/__init__.py#L12
|
|
101
|
-
|
|
102
|
-
return kwargs
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def make_names_data(hostname):
|
|
106
|
-
|
|
107
|
-
show_warning()
|
|
108
|
-
|
|
109
|
-
yield "@winrm/{0}".format(hostname), {"winrm_hostname": hostname}, []
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def connect(state, host):
|
|
113
|
-
"""
|
|
114
|
-
Connect to a single host. Returns the winrm Session if successful.
|
|
115
|
-
"""
|
|
116
|
-
|
|
117
|
-
kwargs = _make_winrm_kwargs(state, host)
|
|
118
|
-
logger.debug("Connecting to: %s (%s)", host.name, kwargs)
|
|
119
|
-
|
|
120
|
-
# Hostname can be provided via winrm config (alias), data, or the hosts name
|
|
121
|
-
hostname = kwargs.pop(
|
|
122
|
-
"hostname",
|
|
123
|
-
host.data.get(DATA_KEYS.hostname, host.name),
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
try:
|
|
127
|
-
# Create new session
|
|
128
|
-
host_and_port = "{}:{}".format(hostname, host.data.get(DATA_KEYS.port))
|
|
129
|
-
logger.debug("host_and_port: %s", host_and_port)
|
|
130
|
-
|
|
131
|
-
session = PyinfraWinrmSession(
|
|
132
|
-
host_and_port,
|
|
133
|
-
auth=(
|
|
134
|
-
kwargs["username"],
|
|
135
|
-
kwargs["password"],
|
|
136
|
-
),
|
|
137
|
-
transport=kwargs["winrm_transport"],
|
|
138
|
-
read_timeout_sec=kwargs["winrm_read_timeout_sec"],
|
|
139
|
-
operation_timeout_sec=kwargs["winrm_operation_timeout_sec"],
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
return session
|
|
143
|
-
|
|
144
|
-
# TODO: add exceptions here
|
|
145
|
-
except Exception as e:
|
|
146
|
-
auth_kwargs = {}
|
|
147
|
-
|
|
148
|
-
for key, value in kwargs.items():
|
|
149
|
-
if key in ("username", "password"):
|
|
150
|
-
auth_kwargs[key] = value
|
|
151
|
-
|
|
152
|
-
auth_args = ", ".join("{0}={1}".format(key, value) for key, value in auth_kwargs.items())
|
|
153
|
-
logger.debug("%s", e)
|
|
154
|
-
_raise_connect_error(host, "Authentication error", auth_args)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def run_shell_command(
|
|
158
|
-
state,
|
|
159
|
-
host,
|
|
160
|
-
command,
|
|
161
|
-
env=None,
|
|
162
|
-
success_exit_codes=None,
|
|
163
|
-
print_output=False,
|
|
164
|
-
print_input=False,
|
|
165
|
-
return_combined_output=False,
|
|
166
|
-
shell_executable=None,
|
|
167
|
-
**ignored_command_kwargs,
|
|
168
|
-
):
|
|
169
|
-
"""
|
|
170
|
-
Execute a command on the specified host.
|
|
171
|
-
|
|
172
|
-
Args:
|
|
173
|
-
state (``pyinfra.api.State`` obj): state object for this command
|
|
174
|
-
hostname (string): hostname of the target
|
|
175
|
-
command (string): actual command to execute
|
|
176
|
-
success_exit_codes (list): all values in the list that will return success
|
|
177
|
-
print_output (boolean): print the output
|
|
178
|
-
print_intput (boolean): print the input
|
|
179
|
-
return_combined_output (boolean): combine the stdout and stderr lists
|
|
180
|
-
shell_executable (string): shell to use - 'cmd'=cmd, 'ps'=powershell(default)
|
|
181
|
-
env (dict): environment variables to set
|
|
182
|
-
|
|
183
|
-
Returns:
|
|
184
|
-
tuple: (exit_code, stdout, stderr)
|
|
185
|
-
stdout and stderr are both lists of strings from each buffer.
|
|
186
|
-
"""
|
|
187
|
-
|
|
188
|
-
command = make_win_command(command)
|
|
189
|
-
|
|
190
|
-
logger.debug("Running command on %s: %s", host.name, command)
|
|
191
|
-
|
|
192
|
-
if print_input:
|
|
193
|
-
click.echo("{0}>>> {1}".format(host.print_prefix, command), err=True)
|
|
194
|
-
|
|
195
|
-
# get rid of leading/trailing quote
|
|
196
|
-
tmp_command = command.strip("'")
|
|
197
|
-
|
|
198
|
-
if print_output:
|
|
199
|
-
click.echo(
|
|
200
|
-
"{0}>>> {1}".format(host.print_prefix, command),
|
|
201
|
-
err=True,
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
if not shell_executable:
|
|
205
|
-
shell_executable = "ps"
|
|
206
|
-
logger.debug("shell_executable:%s", shell_executable)
|
|
207
|
-
|
|
208
|
-
# we use our own subclassed session that allows for env setting from open_shell.
|
|
209
|
-
if shell_executable in ["cmd"]:
|
|
210
|
-
response = host.connection.run_cmd(tmp_command, env=env)
|
|
211
|
-
else:
|
|
212
|
-
response = host.connection.run_ps(tmp_command, env=env)
|
|
213
|
-
|
|
214
|
-
return_code = response.status_code
|
|
215
|
-
logger.debug("response:%s", response)
|
|
216
|
-
|
|
217
|
-
std_out_str = response.std_out.decode("utf-8")
|
|
218
|
-
std_err_str = response.std_err.decode("utf-8")
|
|
219
|
-
|
|
220
|
-
# split on '\r\n' (windows newlines)
|
|
221
|
-
std_out = std_out_str.split("\r\n")
|
|
222
|
-
std_err = std_err_str.split("\r\n")
|
|
223
|
-
|
|
224
|
-
logger.debug("std_out:%s", std_out)
|
|
225
|
-
logger.debug("std_err:%s", std_err)
|
|
226
|
-
|
|
227
|
-
if print_output:
|
|
228
|
-
click.echo(
|
|
229
|
-
"{0}>>> {1}".format(host.print_prefix, "\n".join(std_out)),
|
|
230
|
-
err=True,
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
if success_exit_codes:
|
|
234
|
-
status = return_code in success_exit_codes
|
|
235
|
-
else:
|
|
236
|
-
status = return_code == 0
|
|
237
|
-
|
|
238
|
-
logger.debug("Command exit status: %s", status)
|
|
239
|
-
|
|
240
|
-
if return_combined_output:
|
|
241
|
-
std_out = [("stdout", line) for line in std_out]
|
|
242
|
-
std_err = [("stderr", line) for line in std_err]
|
|
243
|
-
return status, std_out + std_err
|
|
244
|
-
|
|
245
|
-
return status, std_out, std_err
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def get_file(
|
|
249
|
-
state, host, remote_filename, filename_or_io, remote_temp_filename=None, **command_kwargs
|
|
250
|
-
):
|
|
251
|
-
raise PyinfraError("Not implemented")
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def _put_file(state, host, filename_or_io, remote_location, chunk_size=2048):
|
|
255
|
-
# this should work fine on smallish files, but there will be perf issues
|
|
256
|
-
# on larger files both due to the full read, the base64 encoding, and
|
|
257
|
-
# the latency when sending chunks
|
|
258
|
-
with get_file_io(filename_or_io) as file_io:
|
|
259
|
-
data = file_io.read()
|
|
260
|
-
for i in range(0, len(data), chunk_size):
|
|
261
|
-
chunk = data[i : i + chunk_size]
|
|
262
|
-
ps = (
|
|
263
|
-
'$data = [System.Convert]::FromBase64String("{0}"); '
|
|
264
|
-
'{1} -Value $data -Encoding byte -Path "{2}"'
|
|
265
|
-
).format(
|
|
266
|
-
base64.b64encode(chunk).decode("utf-8"),
|
|
267
|
-
"Set-Content" if i == 0 else "Add-Content",
|
|
268
|
-
remote_location,
|
|
269
|
-
)
|
|
270
|
-
status, _stdout, stderr = run_shell_command(state, host, ps)
|
|
271
|
-
if status is False:
|
|
272
|
-
logger.error("File upload error: {0}".format("\n".join(stderr)))
|
|
273
|
-
return False
|
|
274
|
-
|
|
275
|
-
return True
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def put_file(
|
|
279
|
-
state,
|
|
280
|
-
host,
|
|
281
|
-
filename_or_io,
|
|
282
|
-
remote_filename,
|
|
283
|
-
print_output=False,
|
|
284
|
-
print_input=False,
|
|
285
|
-
remote_temp_filename=None, # ignored
|
|
286
|
-
**command_kwargs,
|
|
287
|
-
):
|
|
288
|
-
"""
|
|
289
|
-
Upload file by chunking and sending base64 encoded via winrm
|
|
290
|
-
"""
|
|
291
|
-
|
|
292
|
-
# TODO: fix this? Workaround for circular import
|
|
293
|
-
from pyinfra.facts.windows_files import TempDir
|
|
294
|
-
|
|
295
|
-
# Always use temp file here in case of failure
|
|
296
|
-
temp_file = ntpath.join(
|
|
297
|
-
host.get_fact(TempDir),
|
|
298
|
-
"pyinfra-{0}".format(sha1_hash(remote_filename)),
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
if not _put_file(state, host, filename_or_io, temp_file):
|
|
302
|
-
return False
|
|
303
|
-
|
|
304
|
-
# Execute run_shell_command w/sudo and/or su_user
|
|
305
|
-
command = "Move-Item -Path {0} -Destination {1} -Force".format(temp_file, remote_filename)
|
|
306
|
-
status, _, stderr = run_shell_command(
|
|
307
|
-
state, host, command, print_output=print_output, print_input=print_input, **command_kwargs
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
if status is False:
|
|
311
|
-
logger.error("File upload error: {0}".format("\n".join(stderr)))
|
|
312
|
-
return False
|
|
313
|
-
|
|
314
|
-
if print_output:
|
|
315
|
-
click.echo(
|
|
316
|
-
"{0}file uploaded: {1}".format(host.print_prefix, remote_filename),
|
|
317
|
-
err=True,
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
return True
|