Skip to content

Commit ad091ba

Browse files
committed
Drop chroot setting possibility in __init__ and public property
Design issue Signed-off-by: Aleksei Stepanov <penguinolog@gmail.com> (cherry picked from commit 52aa1b3) Signed-off-by: Aleksei Stepanov <penguinolog@gmail.com>
1 parent 3247549 commit ad091ba

File tree

6 files changed

+33
-81
lines changed

6 files changed

+33
-81
lines changed

doc/source/SSHClient.rst

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ API: SSHClient and SSHAuth.
1010
1111
SSHClient helper.
1212

13-
.. py:method:: __init__(host, port=22, username=None, password=None, private_keys=None, auth=None, *, chroot_path=None)
13+
.. py:method:: __init__(host, port=22, username=None, password=None, private_keys=None, auth=None)
1414
1515
:param host: remote hostname
1616
:type host: ``str``
@@ -26,8 +26,6 @@ API: SSHClient and SSHAuth.
2626
:type auth: typing.Optional[SSHAuth]
2727
:param verbose: show additional error/warning messages
2828
:type verbose: bool
29-
:param chroot_path: chroot path (use chroot if set)
30-
:type chroot_path: typing.Optional[str]
3129

3230
.. note:: auth has priority over username/password/private_keys
3331

@@ -68,11 +66,6 @@ API: SSHClient and SSHAuth.
6866
``bool``
6967
Paramiko status: ready to use|reconnect required
7068

71-
.. py:attribute:: chroot_path
72-
73-
``typing.Optional[str]``
74-
Path for chroot if set.
75-
7669
.. py:attribute:: sudo_mode
7770
7871
``bool``

doc/source/Subprocess.rst

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@ API: Subprocess
88
99
.. py:class:: Subprocess()
1010
11-
.. py:method:: __init__(logger, log_mask_re=None, *, logger=logging.getLogger("exec_helpers.subprocess_runner"), chroot_path=None)
11+
.. py:method:: __init__(logger, log_mask_re=None, *, logger=logging.getLogger("exec_helpers.subprocess_runner"))
1212
1313
ExecHelper global API.
1414

1515
:param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>'
1616
:type log_mask_re: typing.Optional[str]
1717
:param logger: logger instance to use
1818
:type logger: logging.Logger
19-
:param chroot_path: chroot path (use chroot if set)
20-
:type chroot_path: typing.Optional[str]
2119

2220
.. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd
2321
.. versionchanged:: 2.9.0 Not singleton anymore. Only lock is shared between all instances.
@@ -46,11 +44,6 @@ API: Subprocess
4644

4745
.. versionchanged:: 1.1.0 release lock on exit
4846

49-
.. py:attribute:: chroot_path
50-
51-
``typing.Optional[str]``
52-
Path for chroot if set.
53-
5447
.. py:method:: chroot(path)
5548
5649
Context manager for changing chroot rules.

exec_helpers/_ssh_client_base.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,7 @@ def __call__( # type: ignore
138138
password: typing.Optional[str] = None,
139139
private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]] = None,
140140
auth: typing.Optional[ssh_auth.SSHAuth] = None,
141-
verbose: bool = True,
142-
*,
143-
chroot_path: typing.Optional[str] = None
141+
verbose: bool = True
144142
) -> "SSHClientBase":
145143
"""Main memorize method: check for cached instance and return it. API follows target __init__.
146144
@@ -158,12 +156,10 @@ def __call__( # type: ignore
158156
:type auth: typing.Optional[ssh_auth.SSHAuth]
159157
:param verbose: show additional error/warning messages
160158
:type verbose: bool
161-
:param chroot_path: chroot path (use chroot if set)
162-
:type chroot_path: typing.Optional[str]
163159
:return: SSH client instance
164160
:rtype: SSHClientBase
165161
"""
166-
if (host, port) in cls.__cache and not chroot_path: # chrooted connections are not memorized
162+
if (host, port) in cls.__cache:
167163
key = host, port
168164
if auth is None:
169165
auth = ssh_auth.SSHAuth(username=username, password=password, keys=private_keys)
@@ -191,10 +187,8 @@ def __call__( # type: ignore
191187
private_keys=private_keys,
192188
auth=auth,
193189
verbose=verbose,
194-
chroot_path=chroot_path,
195190
)
196-
if not chroot_path:
197-
cls.__cache[(ssh.hostname, ssh.port)] = ssh
191+
cls.__cache[(ssh.hostname, ssh.port)] = ssh
198192
return ssh
199193

200194
@classmethod
@@ -292,9 +286,7 @@ def __init__(
292286
password: typing.Optional[str] = None,
293287
private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]] = None,
294288
auth: typing.Optional[ssh_auth.SSHAuth] = None,
295-
verbose: bool = True,
296-
*,
297-
chroot_path: typing.Optional[str] = None
289+
verbose: bool = True
298290
) -> None:
299291
"""Main SSH Client helper.
300292
@@ -312,14 +304,11 @@ def __init__(
312304
:type auth: typing.Optional[ssh_auth.SSHAuth]
313305
:param verbose: show additional error/warning messages
314306
:type verbose: bool
315-
:param chroot_path: chroot path (use chroot if set)
316-
:type chroot_path: typing.Optional[str]
317307
318308
.. note:: auth has priority over username/password/private_keys
319309
"""
320310
super(SSHClientBase, self).__init__(
321-
logger=logging.getLogger(self.__class__.__name__).getChild("{host}:{port}".format(host=host, port=port)),
322-
chroot_path=chroot_path
311+
logger=logging.getLogger(self.__class__.__name__).getChild("{host}:{port}".format(host=host, port=port))
323312
)
324313

325314
self.__hostname = host

exec_helpers/api.py

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@
4646
)
4747

4848

49-
class _ChRootContext:
49+
# noinspection PyProtectedMember
50+
class _ChRootContext: # pylint: disable=protected-access
5051
"""Context manager for call commands with chroot.
5152
5253
.. versionadded:: 4.1.0
5354
"""
5455

55-
__slots__ = ("__conn", "__chroot_status", "__path")
56+
__slots__ = ("_conn", "_chroot_status", "_path")
5657

5758
def __init__(self, conn: "ExecHelper", path: typing.Optional[str] = None) -> None:
5859
"""Context manager for call commands with sudo.
@@ -62,41 +63,33 @@ def __init__(self, conn: "ExecHelper", path: typing.Optional[str] = None) -> Non
6263
:param path: chroot path or None for no chroot
6364
:type path: typing.Optional[str]
6465
"""
65-
self.__conn = conn # type: ExecHelper
66-
self.__chroot_status = conn.chroot_path # type: typing.Optional[str]
67-
self.__path = path # type: typing.Optional[str]
66+
self._conn = conn # type: ExecHelper
67+
self._chroot_status = conn._chroot_path # type: typing.Optional[str]
68+
self._path = path # type: typing.Optional[str]
6869

6970
def __enter__(self) -> None:
70-
self.__conn.__enter__()
71-
self.__chroot_status = self.__conn.chroot_path
72-
self.__conn.chroot_path = self.__path
71+
self._conn.__enter__()
72+
self._chroot_status = self._conn._chroot_path
73+
self._conn._chroot_path = self._path
7374

7475
def __exit__(self, exc_type: typing.Any, exc_val: typing.Any, exc_tb: typing.Any) -> None:
75-
self.__conn.chroot_path = self.__chroot_status
76-
self.__conn.__exit__(exc_type=exc_type, exc_val=exc_val, exc_tb=exc_tb) # type: ignore
76+
self._conn._chroot_path = self._chroot_status
77+
self._conn.__exit__(exc_type=exc_type, exc_val=exc_val, exc_tb=exc_tb) # type: ignore
7778

7879

7980
class ExecHelper(metaclass=abc.ABCMeta):
8081
"""ExecHelper global API."""
8182

8283
__slots__ = ("__lock", "__logger", "log_mask_re", "__chroot_path")
8384

84-
def __init__(
85-
self,
86-
log_mask_re: typing.Optional[str] = None,
87-
*,
88-
logger: logging.Logger,
89-
chroot_path: typing.Optional[str] = None
90-
) -> None:
85+
def __init__(self, log_mask_re: typing.Optional[str] = None, *, logger: logging.Logger) -> None:
9186
"""Global ExecHelper API.
9287
9388
:param logger: logger instance to use
9489
:type logger: logging.Logger
9590
:param log_mask_re: regex lookup rule to mask command for logger.
9691
all MATCHED groups will be replaced by '<*masked*>'
9792
:type log_mask_re: typing.Optional[str]
98-
:param chroot_path: chroot path (use chroot if set)
99-
:type chroot_path: typing.Optional[str]
10093
10194
.. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd
10295
.. versionchanged:: 1.3.5 make API public to use as interface
@@ -105,7 +98,7 @@ def __init__(
10598
self.__lock = threading.RLock()
10699
self.__logger = logger
107100
self.log_mask_re = log_mask_re
108-
self.__chroot_path = chroot_path # type: typing.Optional[str]
101+
self.__chroot_path = None # type: typing.Optional[str]
109102

110103
@property
111104
def logger(self) -> logging.Logger:
@@ -121,16 +114,16 @@ def lock(self) -> threading.RLock:
121114
return self.__lock
122115

123116
@property
124-
def chroot_path(self) -> typing.Optional[str]:
117+
def _chroot_path(self) -> typing.Optional[str]:
125118
"""Path for chroot if set.
126119
127120
:rtype: typing.Optional[str]
128121
.. versionadded:: 3.5.3
129122
"""
130123
return self.__chroot_path
131124

132-
@chroot_path.setter
133-
def chroot_path(self, new_state: typing.Optional[str]) -> None:
125+
@_chroot_path.setter
126+
def _chroot_path(self, new_state: typing.Optional[str]) -> None:
134127
"""Path for chroot if set.
135128
136129
:param new_state: new path
@@ -139,8 +132,8 @@ def chroot_path(self, new_state: typing.Optional[str]) -> None:
139132
"""
140133
self.__chroot_path = new_state
141134

142-
@chroot_path.deleter
143-
def chroot_path(self) -> None:
135+
@_chroot_path.deleter
136+
def _chroot_path(self) -> None:
144137
"""Remove Path for chroot.
145138
146139
.. versionadded:: 3.5.3
@@ -218,9 +211,9 @@ def mask(text: str, rules: str) -> str:
218211

219212
def _prepare_command(self, cmd: str, chroot_path: typing.Optional[str] = None) -> str:
220213
"""Prepare command: cower chroot and other cases."""
221-
if any((chroot_path, self.chroot_path)):
214+
if any((chroot_path, self._chroot_path)):
222215
return "chroot {chroot_path} {cmd}".format(
223-
chroot_path=chroot_path if chroot_path else self.chroot_path,
216+
chroot_path=chroot_path if chroot_path else self._chroot_path,
224217
cmd=cmd
225218
)
226219
return cmd

exec_helpers/subprocess_runner.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ def __init__(
6767
self,
6868
log_mask_re: typing.Optional[str] = None,
6969
*,
70-
logger: logging.Logger = logging.getLogger(__name__), # noqa: B008
71-
chroot_path: typing.Optional[str] = None
70+
logger: logging.Logger = logging.getLogger(__name__) # noqa: B008
7271
) -> None:
7372
"""Subprocess helper with timeouts and lock-free FIFO.
7473
@@ -79,14 +78,12 @@ def __init__(
7978
:type log_mask_re: typing.Optional[str]
8079
:param logger: logger instance to use
8180
:type logger: logging.Logger
82-
:param chroot_path: chroot path (use chroot if set)
83-
:type chroot_path: typing.Optional[str]
8481
8582
.. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd
8683
.. versionchanged:: 2.9.3 Not singleton anymore. Only lock is shared between all instances.
8784
.. versionchanged:: 2.9.3 Logger can be enforced.
8885
"""
89-
super(Subprocess, self).__init__(logger=logger, log_mask_re=log_mask_re, chroot_path=chroot_path)
86+
super(Subprocess, self).__init__(logger=logger, log_mask_re=log_mask_re)
9087

9188
def _exec_command( # type: ignore
9289
self,

test/test_ssh_client_execute_async_special.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -255,20 +255,7 @@ def test_010_check_stdin_closed(paramiko_ssh_client, chan_makefile, auto_add_pol
255255
log.warning.assert_called_once_with("STDIN Send failed: closed channel")
256256

257257

258-
def test_011_execute_async_chroot(ssh, ssh_transport_channel):
259-
"""Global chroot path."""
260-
ssh.chroot_path = "/"
261-
262-
ssh.execute_async(command)
263-
ssh_transport_channel.assert_has_calls(
264-
(
265-
mock.call.makefile_stderr("rb"),
266-
mock.call.exec_command('chroot {ssh.chroot_path} {command}\n'.format(ssh=ssh, command=command)),
267-
)
268-
)
269-
270-
271-
def test_012_execute_async_chroot_cmd(ssh, ssh_transport_channel):
258+
def test_011_execute_async_chroot_cmd(ssh, ssh_transport_channel):
272259
"""Command-only chroot path."""
273260
ssh.execute_async(command, chroot_path='/')
274261
ssh_transport_channel.assert_has_calls(
@@ -279,7 +266,7 @@ def test_012_execute_async_chroot_cmd(ssh, ssh_transport_channel):
279266
)
280267

281268

282-
def test_013_execute_async_chroot_context(ssh, ssh_transport_channel):
269+
def test_012_execute_async_chroot_context(ssh, ssh_transport_channel):
283270
"""Context-managed chroot path."""
284271
with ssh.chroot('/'):
285272
ssh.execute_async(command)
@@ -291,9 +278,9 @@ def test_013_execute_async_chroot_context(ssh, ssh_transport_channel):
291278
)
292279

293280

294-
def test_014_execute_async_no_chroot_context(ssh, ssh_transport_channel):
281+
def test_013_execute_async_no_chroot_context(ssh, ssh_transport_channel):
295282
"""Context-managed chroot path override."""
296-
ssh.chroot_path = "/"
283+
ssh._chroot_path = "/"
297284

298285
with ssh.chroot(None):
299286
ssh.execute_async(command)

0 commit comments

Comments
 (0)