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.
Files changed (156) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +265 -253
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +68 -53
  5. pyinfra/api/config.py +139 -32
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +7 -26
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +102 -137
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +21 -25
  13. pyinfra/api/operation.py +240 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +79 -86
  17. pyinfra/connectors/base.py +147 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +220 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +196 -208
  22. pyinfra/connectors/ssh.py +530 -613
  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 +211 -137
  27. pyinfra/connectors/vagrant.py +60 -53
  28. pyinfra/context.py +4 -2
  29. pyinfra/facts/apk.py +2 -0
  30. pyinfra/facts/apt.py +2 -0
  31. pyinfra/facts/brew.py +2 -0
  32. pyinfra/facts/bsdinit.py +2 -0
  33. pyinfra/facts/cargo.py +2 -0
  34. pyinfra/facts/choco.py +2 -0
  35. pyinfra/facts/deb.py +7 -2
  36. pyinfra/facts/dnf.py +2 -0
  37. pyinfra/facts/docker.py +19 -0
  38. pyinfra/facts/files.py +47 -32
  39. pyinfra/facts/gem.py +2 -0
  40. pyinfra/facts/git.py +3 -1
  41. pyinfra/facts/gpg.py +3 -1
  42. pyinfra/facts/hardware.py +34 -24
  43. pyinfra/facts/iptables.py +5 -3
  44. pyinfra/facts/launchd.py +2 -0
  45. pyinfra/facts/lxd.py +2 -0
  46. pyinfra/facts/mysql.py +13 -6
  47. pyinfra/facts/npm.py +1 -0
  48. pyinfra/facts/openrc.py +2 -0
  49. pyinfra/facts/pacman.py +6 -2
  50. pyinfra/facts/pip.py +2 -0
  51. pyinfra/facts/pkg.py +2 -0
  52. pyinfra/facts/pkgin.py +2 -0
  53. pyinfra/facts/postgres.py +168 -0
  54. pyinfra/facts/postgresql.py +6 -160
  55. pyinfra/facts/rpm.py +12 -9
  56. pyinfra/facts/runit.py +68 -0
  57. pyinfra/facts/selinux.py +3 -1
  58. pyinfra/facts/server.py +80 -36
  59. pyinfra/facts/snap.py +2 -0
  60. pyinfra/facts/systemd.py +31 -12
  61. pyinfra/facts/sysvinit.py +10 -10
  62. pyinfra/facts/upstart.py +2 -0
  63. pyinfra/facts/util/packaging.py +7 -4
  64. pyinfra/facts/vzctl.py +2 -0
  65. pyinfra/facts/xbps.py +2 -0
  66. pyinfra/facts/yum.py +2 -0
  67. pyinfra/facts/zypper.py +2 -0
  68. pyinfra/local.py +4 -5
  69. pyinfra/operations/apk.py +6 -4
  70. pyinfra/operations/apt.py +46 -65
  71. pyinfra/operations/brew.py +17 -22
  72. pyinfra/operations/bsdinit.py +9 -7
  73. pyinfra/operations/cargo.py +4 -2
  74. pyinfra/operations/choco.py +4 -2
  75. pyinfra/operations/dnf.py +19 -23
  76. pyinfra/operations/docker.py +339 -0
  77. pyinfra/operations/files.py +188 -386
  78. pyinfra/operations/gem.py +4 -2
  79. pyinfra/operations/git.py +24 -53
  80. pyinfra/operations/iptables.py +29 -35
  81. pyinfra/operations/launchd.py +6 -7
  82. pyinfra/operations/lxd.py +8 -13
  83. pyinfra/operations/mysql.py +62 -81
  84. pyinfra/operations/npm.py +9 -2
  85. pyinfra/operations/openrc.py +6 -4
  86. pyinfra/operations/pacman.py +7 -8
  87. pyinfra/operations/pip.py +25 -24
  88. pyinfra/operations/pkg.py +4 -2
  89. pyinfra/operations/pkgin.py +6 -4
  90. pyinfra/operations/postgres.py +349 -0
  91. pyinfra/operations/postgresql.py +18 -379
  92. pyinfra/operations/puppet.py +3 -1
  93. pyinfra/operations/python.py +8 -19
  94. pyinfra/operations/runit.py +182 -0
  95. pyinfra/operations/selinux.py +47 -44
  96. pyinfra/operations/server.py +111 -127
  97. pyinfra/operations/snap.py +4 -4
  98. pyinfra/operations/ssh.py +20 -33
  99. pyinfra/operations/systemd.py +19 -15
  100. pyinfra/operations/sysvinit.py +9 -16
  101. pyinfra/operations/upstart.py +9 -7
  102. pyinfra/operations/util/__init__.py +12 -0
  103. pyinfra/operations/util/docker.py +177 -0
  104. pyinfra/operations/util/files.py +24 -16
  105. pyinfra/operations/util/packaging.py +55 -57
  106. pyinfra/operations/util/service.py +39 -51
  107. pyinfra/operations/vzctl.py +12 -10
  108. pyinfra/operations/xbps.py +6 -4
  109. pyinfra/operations/yum.py +18 -22
  110. pyinfra/operations/zypper.py +12 -13
  111. pyinfra/version.py +5 -2
  112. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
  113. pyinfra-3.0.dist-info/RECORD +167 -0
  114. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
  115. pyinfra-3.0.dist-info/entry_points.txt +11 -0
  116. pyinfra_cli/__main__.py +4 -3
  117. pyinfra_cli/commands.py +7 -2
  118. pyinfra_cli/exceptions.py +78 -42
  119. pyinfra_cli/inventory.py +40 -6
  120. pyinfra_cli/log.py +17 -3
  121. pyinfra_cli/main.py +133 -90
  122. pyinfra_cli/prints.py +95 -127
  123. pyinfra_cli/util.py +62 -29
  124. tests/test_api/test_api.py +2 -0
  125. tests/test_api/test_api_arguments.py +13 -13
  126. tests/test_api/test_api_deploys.py +28 -29
  127. tests/test_api/test_api_facts.py +60 -98
  128. tests/test_api/test_api_operations.py +101 -201
  129. tests/test_cli/test_cli.py +18 -49
  130. tests/test_cli/test_cli_deploy.py +11 -37
  131. tests/test_cli/test_cli_exceptions.py +50 -19
  132. tests/test_cli/util.py +1 -1
  133. tests/test_connectors/test_chroot.py +6 -6
  134. tests/test_connectors/test_docker.py +4 -4
  135. tests/test_connectors/test_dockerssh.py +38 -50
  136. tests/test_connectors/test_local.py +11 -12
  137. tests/test_connectors/test_ssh.py +105 -93
  138. tests/test_connectors/test_terraform.py +9 -15
  139. tests/test_connectors/test_util.py +24 -46
  140. tests/test_connectors/test_vagrant.py +7 -7
  141. pyinfra/api/operation.pyi +0 -117
  142. pyinfra/connectors/ansible.py +0 -171
  143. pyinfra/connectors/mech.py +0 -186
  144. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  145. pyinfra/connectors/winrm.py +0 -320
  146. pyinfra/facts/windows.py +0 -366
  147. pyinfra/facts/windows_files.py +0 -90
  148. pyinfra/operations/windows.py +0 -59
  149. pyinfra/operations/windows_files.py +0 -551
  150. pyinfra-2.9.2.dist-info/RECORD +0 -170
  151. pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
  152. tests/test_connectors/test_ansible.py +0 -64
  153. tests/test_connectors/test_mech.py +0 -126
  154. tests/test_connectors/test_winrm.py +0 -76
  155. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
  156. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
@@ -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
@@ -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