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
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ from io import IOBase
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Any,
8
+ Iterable,
9
+ Iterator,
10
+ Optional,
11
+ Type,
12
+ TypeVar,
13
+ Union,
14
+ cast,
15
+ get_type_hints,
16
+ )
17
+
18
+ from typing_extensions import TypedDict, Unpack
19
+
20
+ from pyinfra.api.exceptions import ConnectorDataTypeError
21
+ from pyinfra.api.util import raise_if_bad_type
22
+
23
+ if TYPE_CHECKING:
24
+ from pyinfra.api.arguments import ConnectorArguments
25
+ from pyinfra.api.command import StringCommand
26
+ from pyinfra.api.host import Host, HostData
27
+ from pyinfra.api.state import State
28
+
29
+ from .util import CommandOutput
30
+
31
+
32
+ T = TypeVar("T")
33
+ default_sentinel = object()
34
+
35
+
36
+ def host_to_connector_data(
37
+ connector_data: Type[T],
38
+ connector_data_meta: dict[str, DataMeta],
39
+ host_data: "HostData",
40
+ ) -> T:
41
+ data: T = cast(T, {})
42
+ for key, type_ in get_type_hints(connector_data).items():
43
+ value = host_data.get(key, default_sentinel)
44
+ if value is default_sentinel:
45
+ value = connector_data_meta[key].default
46
+ else:
47
+ raise_if_bad_type(
48
+ value,
49
+ type_,
50
+ ConnectorDataTypeError,
51
+ f"Invalid connector data `{key}`:",
52
+ )
53
+
54
+ data[key] = value # type: ignore
55
+ return data
56
+
57
+
58
+ class DataMeta:
59
+ description: str
60
+ default: Any
61
+
62
+ def __init__(self, description, default=None) -> None:
63
+ self.description = description
64
+ self.default = default
65
+
66
+
67
+ class ConnectorData(TypedDict, total=False):
68
+ pass
69
+
70
+
71
+ class BaseConnector(abc.ABC):
72
+ state: "State"
73
+ host: "Host"
74
+
75
+ handles_execution = False
76
+
77
+ data_cls: Type = ConnectorData
78
+ data_meta: dict[str, DataMeta] = {}
79
+
80
+ def __init__(self, state: "State", host: "Host"):
81
+ self.state = state
82
+ self.host = host
83
+ self.data = host_to_connector_data(self.data_cls, self.data_meta, host.data)
84
+
85
+ @staticmethod
86
+ @abc.abstractmethod
87
+ def make_names_data(name: str) -> Iterator[tuple[str, dict, list[str]]]:
88
+ """
89
+ Generates hosts/data/groups information for inventory. This allows a
90
+ single connector reference to generate multiple target hosts.
91
+ """
92
+ ...
93
+
94
+ def connect(self) -> None:
95
+ """
96
+ Connect this connector instance.
97
+ """
98
+
99
+ def disconnect(self) -> None:
100
+ """
101
+ Disconnect this connector instance.
102
+ """
103
+
104
+ @abc.abstractmethod
105
+ def run_shell_command(
106
+ self,
107
+ command: "StringCommand",
108
+ print_output: bool,
109
+ print_input: bool,
110
+ **arguments: Unpack["ConnectorArguments"],
111
+ ) -> tuple[bool, "CommandOutput"]: ...
112
+
113
+ @abc.abstractmethod
114
+ def put_file(
115
+ self,
116
+ filename_or_io: Union[str, IOBase],
117
+ remote_filename: str,
118
+ remote_temp_filename: Optional[str] = None,
119
+ print_output: bool = False,
120
+ print_input: bool = False,
121
+ **arguments: Unpack["ConnectorArguments"],
122
+ ) -> bool: ...
123
+
124
+ @abc.abstractmethod
125
+ def get_file(
126
+ self,
127
+ remote_filename: str,
128
+ filename_or_io: Union[str, IOBase],
129
+ remote_temp_filename: Optional[str] = None,
130
+ print_output: bool = False,
131
+ print_input: bool = False,
132
+ **arguments: Unpack["ConnectorArguments"],
133
+ ) -> bool: ...
134
+
135
+ def check_can_rsync(self):
136
+ raise NotImplementedError("This connector does not support rsync")
137
+
138
+ def rsync(
139
+ self,
140
+ src: str,
141
+ dest: str,
142
+ flags: Iterable[str],
143
+ print_output: bool = False,
144
+ print_input: bool = False,
145
+ **arguments: Unpack["ConnectorArguments"],
146
+ ) -> bool:
147
+ raise NotImplementedError("This connector does not support rsync")
@@ -3,210 +3,201 @@ from tempfile import mkstemp
3
3
  from typing import TYPE_CHECKING, Optional
4
4
 
5
5
  import click
6
+ from typing_extensions import Unpack
6
7
 
7
8
  from pyinfra import local, logger
8
9
  from pyinfra.api import QuoteString, StringCommand
9
- from pyinfra.api.connectors import BaseConnectorMeta
10
10
  from pyinfra.api.exceptions import ConnectError, InventoryError, PyinfraError
11
11
  from pyinfra.api.util import get_file_io, memoize
12
12
  from pyinfra.progress import progress_spinner
13
13
 
14
- from .local import run_shell_command as run_local_shell_command
15
- from .util import make_unix_command_for_host
14
+ from .base import BaseConnector
15
+ from .local import LocalConnector
16
+ from .util import extract_control_arguments, make_unix_command_for_host
16
17
 
17
18
  if TYPE_CHECKING:
19
+ from pyinfra.api.arguments import ConnectorArguments
18
20
  from pyinfra.api.host import Host
19
21
  from pyinfra.api.state import State
20
22
 
21
23
 
22
- class Meta(BaseConnectorMeta):
23
- handles_execution = True
24
-
25
-
26
24
  @memoize
27
25
  def show_warning():
28
26
  logger.warning("The @chroot connector is in beta!")
29
27
 
30
28
 
31
- def make_names_data(directory: Optional[str] = None):
32
- if not directory:
33
- raise InventoryError("No directory provided!")
29
+ class ChrootConnector(BaseConnector):
30
+ """
31
+ The chroot connector allows you to execute operations within another root.
32
+ """
34
33
 
35
- show_warning()
34
+ handles_execution = True
36
35
 
37
- yield "@chroot/{0}".format(directory), {
38
- "chroot_directory": "/{0}".format(directory.lstrip("/")),
39
- }, ["@chroot"]
36
+ local: LocalConnector
40
37
 
38
+ def __init__(self, state: "State", host: "Host"):
39
+ super().__init__(state, host)
40
+ self.local = LocalConnector(state, host)
41
41
 
42
- def connect(state: "State", host: "Host"):
43
- chroot_directory = host.data.chroot_directory
42
+ @staticmethod
43
+ def make_names_data(name: Optional[str] = None):
44
+ if not name:
45
+ raise InventoryError("No directory provided!")
44
46
 
45
- try:
46
- with progress_spinner({"chroot run"}):
47
- local.shell(
48
- "chroot {0} ls".format(chroot_directory),
49
- splitlines=True,
50
- )
51
- except PyinfraError as e:
52
- raise ConnectError(e.args[0])
53
-
54
- host.connector_data["chroot_directory"] = chroot_directory
55
- return True
56
-
57
-
58
- def run_shell_command(
59
- state: "State",
60
- host: "Host",
61
- command,
62
- get_pty: bool = False,
63
- timeout=None,
64
- stdin=None,
65
- success_exit_codes=None,
66
- print_output: bool = False,
67
- print_input: bool = False,
68
- return_combined_output: bool = False,
69
- **command_kwargs,
70
- ):
71
- chroot_directory = host.connector_data["chroot_directory"]
72
-
73
- command = make_unix_command_for_host(state, host, command, **command_kwargs)
74
- command = QuoteString(command)
75
-
76
- logger.debug("--> Running chroot command on (%s): %s", chroot_directory, command)
77
-
78
- chroot_command = StringCommand(
79
- "chroot",
80
- chroot_directory,
81
- "sh",
82
- "-c",
83
- command,
84
- )
85
-
86
- return run_local_shell_command(
87
- state,
88
- host,
89
- chroot_command,
90
- timeout=timeout,
91
- stdin=stdin,
92
- success_exit_codes=success_exit_codes,
93
- print_output=print_output,
94
- print_input=print_input,
95
- return_combined_output=return_combined_output,
96
- )
97
-
98
-
99
- def put_file(
100
- state: "State",
101
- host: "Host",
102
- filename_or_io,
103
- remote_filename,
104
- remote_temp_filename=None, # ignored
105
- print_output: bool = False,
106
- print_input: bool = False,
107
- **kwargs, # ignored (sudo/etc)
108
- ):
109
-
110
- _, temp_filename = mkstemp()
111
-
112
- try:
113
- # Load our file or IO object and write it to the temporary file
114
- with get_file_io(filename_or_io) as file_io:
115
- with open(temp_filename, "wb") as temp_f:
116
- data = file_io.read()
117
-
118
- if isinstance(data, str):
119
- data = data.encode()
120
-
121
- temp_f.write(data)
122
-
123
- chroot_directory = host.connector_data["chroot_directory"]
124
-
125
- chroot_command = "cp {0} {1}/{2}".format(
126
- temp_filename,
127
- chroot_directory,
128
- remote_filename,
129
- )
47
+ show_warning()
130
48
 
131
- status, _, stderr = run_local_shell_command(
132
- state,
133
- host,
134
- chroot_command,
135
- print_output=print_output,
136
- print_input=print_input,
137
- )
138
- finally:
139
- os.remove(temp_filename)
140
-
141
- if not status:
142
- raise IOError("\n".join(stderr))
143
-
144
- if print_output:
145
- click.echo(
146
- "{0}file uploaded to chroot: {1}".format(
147
- host.print_prefix,
148
- remote_filename,
149
- ),
150
- err=True,
151
- )
49
+ yield "@chroot/{0}".format(name), {
50
+ "chroot_directory": "/{0}".format(name.lstrip("/")),
51
+ }, ["@chroot"]
52
+
53
+ def connect(self) -> None:
54
+ self.local.connect()
152
55
 
153
- return status
56
+ chroot_directory = self.host.data.chroot_directory
154
57
 
58
+ try:
59
+ with progress_spinner({"chroot run"}):
60
+ local.shell(
61
+ "chroot {0} ls".format(chroot_directory),
62
+ splitlines=True,
63
+ )
64
+ except PyinfraError as e:
65
+ raise ConnectError(e.args[0])
155
66
 
156
- def get_file(
157
- state: "State",
158
- host: "Host",
159
- remote_filename,
160
- filename_or_io,
161
- remote_temp_filename=None, # ignored
162
- print_output: bool = False,
163
- print_input: bool = False,
164
- **kwargs, # ignored (sudo/etc)
165
- ):
67
+ self.host.connector_data["chroot_directory"] = chroot_directory
166
68
 
167
- _, temp_filename = mkstemp()
69
+ def run_shell_command(
70
+ self,
71
+ command,
72
+ print_output: bool = False,
73
+ print_input: bool = False,
74
+ **command_arguments: Unpack["ConnectorArguments"],
75
+ ):
76
+ local_arguments = extract_control_arguments(command_arguments)
168
77
 
169
- try:
170
- chroot_directory = host.connector_data["chroot_directory"]
171
- chroot_command = "cp {0}/{1} {2}".format(
78
+ chroot_directory = self.host.connector_data["chroot_directory"]
79
+
80
+ command = make_unix_command_for_host(self.state, self.host, command, **command_arguments)
81
+ command = QuoteString(command)
82
+
83
+ logger.debug("--> Running chroot command on (%s): %s", chroot_directory, command)
84
+
85
+ chroot_command = StringCommand(
86
+ "chroot",
172
87
  chroot_directory,
173
- remote_filename,
174
- temp_filename,
88
+ "sh",
89
+ "-c",
90
+ command,
175
91
  )
176
92
 
177
- status, _, stderr = run_local_shell_command(
178
- state,
179
- host,
93
+ return self.local.run_shell_command(
180
94
  chroot_command,
181
95
  print_output=print_output,
182
96
  print_input=print_input,
97
+ **local_arguments,
183
98
  )
184
99
 
185
- # Load the temporary file and write it to our file or IO object
186
- with open(temp_filename, encoding="utf-8") as temp_f:
187
- with get_file_io(filename_or_io, "wb") as file_io:
188
- data = temp_f.read()
189
- data_bytes: bytes
190
-
191
- if isinstance(data, str):
192
- data_bytes = data.encode()
193
- else:
194
- data_bytes = data
195
-
196
- file_io.write(data_bytes)
197
- finally:
198
- os.remove(temp_filename)
199
-
200
- if not status:
201
- raise IOError("\n".join(stderr))
202
-
203
- if print_output:
204
- click.echo(
205
- "{0}file downloaded from chroot: {1}".format(
206
- host.print_prefix,
207
- remote_filename,
208
- ),
209
- err=True,
210
- )
100
+ def put_file(
101
+ self,
102
+ filename_or_io,
103
+ remote_filename,
104
+ remote_temp_filename=None, # ignored
105
+ print_output: bool = False,
106
+ print_input: bool = False,
107
+ **kwargs, # ignored (sudo/etc)
108
+ ):
109
+ _, temp_filename = mkstemp()
110
+
111
+ try:
112
+ # Load our file or IO object and write it to the temporary file
113
+ with get_file_io(filename_or_io) as file_io:
114
+ with open(temp_filename, "wb") as temp_f:
115
+ data = file_io.read()
116
+
117
+ if isinstance(data, str):
118
+ data = data.encode()
119
+
120
+ temp_f.write(data)
121
+
122
+ chroot_directory = self.host.connector_data["chroot_directory"]
123
+ chroot_command = StringCommand(
124
+ "cp",
125
+ temp_filename,
126
+ f"{chroot_directory}/{remote_filename}",
127
+ )
128
+
129
+ status, output = self.local.run_shell_command(
130
+ chroot_command,
131
+ print_output=print_output,
132
+ print_input=print_input,
133
+ )
134
+ finally:
135
+ os.remove(temp_filename)
136
+
137
+ if not status:
138
+ raise IOError(output.stderr)
139
+
140
+ if print_output:
141
+ click.echo(
142
+ "{0}file uploaded to chroot: {1}".format(
143
+ self.host.print_prefix,
144
+ remote_filename,
145
+ ),
146
+ err=True,
147
+ )
148
+
149
+ return status
150
+
151
+ def get_file(
152
+ self,
153
+ remote_filename,
154
+ filename_or_io,
155
+ remote_temp_filename=None, # ignored
156
+ print_output: bool = False,
157
+ print_input: bool = False,
158
+ **kwargs, # ignored (sudo/etc)
159
+ ):
160
+ _, temp_filename = mkstemp()
161
+
162
+ try:
163
+ chroot_directory = self.host.connector_data["chroot_directory"]
164
+ chroot_command = StringCommand(
165
+ "cp",
166
+ f"{chroot_directory}/{remote_filename}",
167
+ temp_filename,
168
+ )
169
+
170
+ status, output = self.local.run_shell_command(
171
+ chroot_command,
172
+ print_output=print_output,
173
+ print_input=print_input,
174
+ )
175
+
176
+ # Load the temporary file and write it to our file or IO object
177
+ with open(temp_filename, encoding="utf-8") as temp_f:
178
+ with get_file_io(filename_or_io, "wb") as file_io:
179
+ data = temp_f.read()
180
+ data_bytes: bytes
181
+
182
+ if isinstance(data, str):
183
+ data_bytes = data.encode()
184
+ else:
185
+ data_bytes = data
186
+
187
+ file_io.write(data_bytes)
188
+ finally:
189
+ os.remove(temp_filename)
190
+
191
+ if not status:
192
+ raise IOError(output.stderr)
193
+
194
+ if print_output:
195
+ click.echo(
196
+ "{0}file downloaded from chroot: {1}".format(
197
+ self.host.print_prefix,
198
+ remote_filename,
199
+ ),
200
+ err=True,
201
+ )
211
202
 
212
- return status
203
+ return status