Skip to content

Commit 5d17bd4

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 78eaf38 commit 5d17bd4

File tree

5 files changed

+42
-58
lines changed

5 files changed

+42
-58
lines changed

doc/source/Subprocess.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ API: Subprocess
4040

4141
.. versionchanged:: 1.1.0 release lock on exit
4242

43+
.. py:method:: chroot(path)
44+
45+
Context manager for changing chroot rules.
46+
47+
:param path: chroot path or none for working without chroot.
48+
:type path: ``typing.Optional[typing.Union[str, typing.Text]``
49+
:return: context manager with selected chroot state inside
50+
:rtype: typing.ContextManager
51+
52+
.. Note:: Enter and exit main context manager is produced as well.
53+
4354
.. py:method:: execute_async(command, stdin=None, open_stdout=True, open_stderr=True, verbose=False, log_mask_re=None, **kwargs)
4455
4556
Execute command in async mode and return Popen with IO objects.

exec_helpers/_ssh_client_base.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,7 @@ def __call__( # type: ignore
119119
password=None, # type: typing.Optional[typing.Union[str, typing.Text]]
120120
private_keys=None, # type: typing.Optional[typing.Iterable[paramiko.RSAKey]]
121121
auth=None, # type: typing.Optional[ssh_auth.SSHAuth]
122-
verbose=True, # type: bool
123-
chroot_path=None, # type: typing.Optional[typing.Union[str, typing.Text]]
122+
verbose=True # type: bool
124123
): # type: (...) -> SSHClientBase
125124
"""Main memorize method: check for cached instance and return it.
126125
@@ -138,12 +137,10 @@ def __call__( # type: ignore
138137
:type auth: typing.Optional[ssh_auth.SSHAuth]
139138
:param verbose: show additional error/warning messages
140139
:type verbose: bool
141-
:param chroot_path: chroot path (use chroot if set)
142-
:type chroot_path: typing.Optional[typing.Union[str, typing.Text]]
143140
:return: SSH client instance
144141
:rtype: SSHClientBase
145142
"""
146-
if (host, port) in cls.__cache and not chroot_path: # chrooted connections are not memorized
143+
if (host, port) in cls.__cache:
147144
key = host, port
148145
if auth is None:
149146
auth = ssh_auth.SSHAuth(username=username, password=password, keys=private_keys)
@@ -171,10 +168,8 @@ def __call__( # type: ignore
171168
private_keys=private_keys,
172169
auth=auth,
173170
verbose=verbose,
174-
chroot_path=chroot_path,
175171
)
176-
if not chroot_path:
177-
cls.__cache[(ssh.hostname, ssh.port)] = ssh
172+
cls.__cache[(ssh.hostname, ssh.port)] = ssh
178173
return ssh
179174

180175
@classmethod
@@ -272,7 +267,6 @@ def __init__(
272267
private_keys=None, # type: typing.Optional[typing.Iterable[paramiko.RSAKey]]
273268
auth=None, # type: typing.Optional[ssh_auth.SSHAuth]
274269
verbose=True, # type: bool
275-
chroot_path=None, # type: typing.Optional[typing.Union[str, typing.Text]]
276270
): # type: (...) -> None
277271
"""Main SSH Client helper.
278272
@@ -290,14 +284,11 @@ def __init__(
290284
:type auth: typing.Optional[ssh_auth.SSHAuth]
291285
:param verbose: show additional error/warning messages
292286
:type verbose: bool
293-
:param chroot_path: chroot path (use chroot if set)
294-
:type chroot_path: typing.Optional[typing.Union[str, typing.Text]]
295287
296288
.. note:: auth has priority over username/password/private_keys
297289
"""
298290
super(SSHClientBase, self).__init__(
299-
logger=logging.getLogger(self.__class__.__name__).getChild("{host}:{port}".format(host=host, port=port)),
300-
chroot_path=chroot_path
291+
logger=logging.getLogger(self.__class__.__name__).getChild("{host}:{port}".format(host=host, port=port))
301292
)
302293

303294
self.__hostname = host
@@ -367,7 +358,7 @@ def __unicode__(self): # type: () -> typing.Text # pragma: no cover
367358
cls=self.__class__.__name__, self=self, username=self.auth.username
368359
)
369360

370-
def __str__(self): # type: () -> bytes # pragma: no cover
361+
def __str__(self): # type: ignore # pragma: no cover
371362
"""Representation for debug purposes."""
372363
return self.__unicode__().encode("utf-8")
373364

exec_helpers/api.py

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,39 @@
5050
)
5151

5252

53-
class _ChRootContext(object):
53+
# noinspection PyProtectedMember
54+
class _ChRootContext(object): # pylint: disable=protected-access
5455
"""Context manager for call commands with chroot.
5556
5657
.. versionadded:: 1.12.0
5758
"""
5859

59-
__slots__ = ("__conn", "__chroot_status", "__path")
60+
__slots__ = ("_conn", "_chroot_status", "_path")
6061

6162
def __init__(
6263
self,
6364
conn, # type: ExecHelper
6465
path=None, # type: typing.Optional[typing.Union[str, typing.Text]]
65-
): # type: (...) -> None
66+
): # type: (...) -> None # pylint: disable=protected-access
6667
"""Context manager for call commands with sudo.
6768
6869
:param conn: connection instance
6970
:type conn: ExecHelper
7071
:param path: chroot path or None for no chroot
7172
:type path: typing.Optional[str]
7273
"""
73-
self.__conn = conn # type: ExecHelper
74-
self.__chroot_status = conn.chroot_path # type: typing.Optional[typing.Union[str, typing.Text]]
75-
self.__path = path # type: typing.Optional[typing.Union[str, typing.Text]]
74+
self._conn = conn # type: ExecHelper
75+
self._chroot_status = conn._chroot_path # type: typing.Optional[typing.Union[str, typing.Text]]
76+
self._path = path # type: typing.Optional[typing.Union[str, typing.Text]]
7677

7778
def __enter__(self): # type: () -> None
78-
self.__conn.__enter__()
79-
self.__chroot_status = self.__conn.chroot_path
80-
self.__conn.chroot_path = self.__path
79+
self._conn.__enter__()
80+
self._chroot_status = self._conn._chroot_path # pylint: disable=protected-access
81+
self._conn._chroot_path = self._path # pylint: disable=protected-access
8182

8283
def __exit__(self, exc_type, exc_val, exc_tb): # type: (typing.Any, typing.Any, typing.Any) -> None
83-
self.__conn.chroot_path = self.__chroot_status
84-
self.__conn.__exit__(exc_type=exc_type, exc_val=exc_val, exc_tb=exc_tb) # type: ignore
84+
self._conn._chroot_path = self._chroot_status # pylint: disable=protected-access
85+
self._conn.__exit__(exc_type=exc_type, exc_val=exc_val, exc_tb=exc_tb) # type: ignore
8586

8687

8788
class ExecHelper(six.with_metaclass(abc.ABCMeta, object)):
@@ -92,17 +93,14 @@ class ExecHelper(six.with_metaclass(abc.ABCMeta, object)):
9293
def __init__(
9394
self,
9495
logger, # type: logging.Logger
95-
log_mask_re=None, # type: typing.Optional[typing.Text]
96-
chroot_path=None # type: typing.Optional[typing.Union[str, typing.Text]]
96+
log_mask_re=None # type: typing.Optional[typing.Text]
9797
): # type: (...) -> None
9898
"""Global ExecHelper API.
9999
100100
:param logger: logger instance to use
101101
:type logger: logging.Logger
102102
:param log_mask_re: regex lookup rule to mask command for logger.
103103
all MATCHED groups will be replaced by '<*masked*>'
104-
:param chroot_path: chroot path (use chroot if set)
105-
:type chroot_path: typing.Optional[str]
106104
:type log_mask_re: typing.Optional[str]
107105
108106
.. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd
@@ -112,24 +110,24 @@ def __init__(
112110
self.__lock = threading.RLock()
113111
self.__logger = logger
114112
self.log_mask_re = log_mask_re # type: typing.Optional[typing.Text]
115-
self.__chroot_path = chroot_path # type: typing.Optional[typing.Union[str, typing.Text]]
113+
self.__chroot_path = None # type: typing.Optional[typing.Union[str, typing.Text]]
116114

117115
@property
118116
def logger(self): # type: () -> logging.Logger
119117
"""Instance logger access."""
120118
return self.__logger
121119

122120
@property
123-
def chroot_path(self): # type: () -> typing.Optional[typing.Union[str, typing.Text]]
121+
def _chroot_path(self): # type: () -> typing.Optional[typing.Union[str, typing.Text]]
124122
"""Path for chroot if set.
125123
126124
:rtype: typing.Optional[typing.Text]
127125
.. versionadded:: 1.12.0
128126
"""
129127
return self.__chroot_path
130128

131-
@chroot_path.setter
132-
def chroot_path(self, new_state): # type: (typing.Optional[typing.Union[str, typing.Text]]) -> None
129+
@_chroot_path.setter
130+
def _chroot_path(self, new_state): # type: (typing.Optional[typing.Union[str, typing.Text]]) -> None
133131
"""Path for chroot if set.
134132
135133
:param new_state: new path
@@ -138,8 +136,8 @@ def chroot_path(self, new_state): # type: (typing.Optional[typing.Union[str, ty
138136
"""
139137
self.__chroot_path = new_state
140138

141-
@chroot_path.deleter
142-
def chroot_path(self): # type: () -> None
139+
@_chroot_path.deleter
140+
def _chroot_path(self): # type: () -> None
143141
"""Remove Path for chroot.
144142
145143
.. versionadded:: 1.12.0
@@ -231,9 +229,9 @@ def _prepare_command(
231229
chroot_path=None # type: typing.Optional[typing.Union[str, typing.Text]]
232230
): # type: (...) -> typing.Text
233231
"""Prepare command: cower chroot and other cases."""
234-
if any((chroot_path, self.chroot_path)):
232+
if any((chroot_path, self._chroot_path)):
235233
return "chroot {chroot_path} {cmd}".format(
236-
chroot_path=chroot_path if chroot_path else self.chroot_path,
234+
chroot_path=chroot_path if chroot_path else self._chroot_path,
237235
cmd=cmd
238236
)
239237
return cmd

exec_helpers/subprocess_runner.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ class Subprocess(six.with_metaclass(metaclasses.SingleLock, api.ExecHelper)):
5959

6060
def __init__(
6161
self,
62-
log_mask_re=None, # type: typing.Optional[typing.Text]
63-
chroot_path=None, # type: typing.Optional[typing.Union[str, typing.Text]]
62+
log_mask_re=None # type: typing.Optional[typing.Text]
6463
): # type: (...) -> None
6564
"""Subprocess helper with timeouts and lock-free FIFO.
6665
@@ -69,8 +68,6 @@ def __init__(
6968
:param log_mask_re: regex lookup rule to mask command for logger.
7069
all MATCHED groups will be replaced by '<*masked*>'
7170
:type log_mask_re: typing.Optional[str]
72-
:param chroot_path: chroot path (use chroot if set)
73-
:type chroot_path: typing.Optional[str]
7471
7572
.. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd
7673
.. versionchanged:: 1.9.0 Not singleton anymore. Only lock is shared between all instances.

test/test_ssh_client_execute_async_special.py

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

262262

263-
def test_011_execute_async_chroot(ssh, ssh_transport_channel):
264-
"""Global chroot path."""
265-
ssh.chroot_path = "/"
266-
267-
ssh.execute_async(command)
268-
ssh_transport_channel.assert_has_calls(
269-
(
270-
mock.call.makefile_stderr("rb"),
271-
mock.call.exec_command('chroot {ssh.chroot_path} {command}\n'.format(ssh=ssh, command=command)),
272-
)
273-
)
274-
275-
276-
def test_012_execute_async_chroot_cmd(ssh, ssh_transport_channel):
263+
def test_011_execute_async_chroot_cmd(ssh, ssh_transport_channel):
277264
"""Command-only chroot path."""
278265
ssh.execute_async(command, chroot_path='/')
279266
ssh_transport_channel.assert_has_calls(
@@ -284,7 +271,7 @@ def test_012_execute_async_chroot_cmd(ssh, ssh_transport_channel):
284271
)
285272

286273

287-
def test_013_execute_async_chroot_context(ssh, ssh_transport_channel):
274+
def test_012_execute_async_chroot_context(ssh, ssh_transport_channel):
288275
"""Context-managed chroot path."""
289276
with ssh.chroot('/'):
290277
ssh.execute_async(command)
@@ -296,9 +283,9 @@ def test_013_execute_async_chroot_context(ssh, ssh_transport_channel):
296283
)
297284

298285

299-
def test_014_execute_async_no_chroot_context(ssh, ssh_transport_channel):
286+
def test_013_execute_async_no_chroot_context(ssh, ssh_transport_channel):
300287
"""Context-managed chroot path override."""
301-
ssh.chroot_path = "/"
288+
ssh._chroot_path = "/"
302289

303290
with ssh.chroot(None):
304291
ssh.execute_async(command)

0 commit comments

Comments
 (0)