Skip to content

Commit 14f1c6a

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 234ab52 commit 14f1c6a

File tree

9 files changed

+77
-104
lines changed

9 files changed

+77
-104
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:: 3.1.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/async_api/api.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,47 @@
3131
from exec_helpers import proc_enums
3232

3333

34+
# noinspection PyProtectedMember
35+
class _ChRootContext(api._ChRootContext): # pylint: disable=protected-access
36+
"""Async extension for chroot."""
37+
38+
def __init__(self, conn: "ExecHelper", path: typing.Optional[str] = None) -> None:
39+
"""Context manager for call commands with sudo.
40+
41+
:param conn: connection instance
42+
:type conn: ExecHelper
43+
:param path: chroot path or None for no chroot
44+
:type path: typing.Optional[str]
45+
"""
46+
super(_ChRootContext, self).__init__(conn=conn, path=path)
47+
48+
async def __aenter__(self) -> None:
49+
await self._conn.__aenter__() # type: ignore
50+
self._chroot_status = self._conn._chroot_path # pylint: disable=protected-access
51+
self._conn._chroot_path = self._path # pylint: disable=protected-access
52+
53+
async def __aexit__( # pylint: disable=protected-access
54+
self, exc_type: typing.Any, exc_val: typing.Any, exc_tb: typing.Any
55+
) -> None:
56+
self._conn._chroot_path = self._chroot_status
57+
await self._conn.__aexit__(exc_type=exc_type, exc_val=exc_val, exc_tb=exc_tb) # type: ignore
58+
59+
3460
class ExecHelper(api.ExecHelper, metaclass=abc.ABCMeta):
3561
"""Subprocess helper with timeouts and lock-free FIFO."""
3662

3763
__slots__ = ("__alock",)
3864

39-
def __init__(
40-
self,
41-
log_mask_re: typing.Optional[str] = None,
42-
*,
43-
logger: logging.Logger,
44-
chroot_path: typing.Optional[str] = None
45-
) -> None:
65+
def __init__(self, log_mask_re: typing.Optional[str] = None, *, logger: logging.Logger) -> None:
4666
"""Subprocess helper with timeouts and lock-free FIFO.
4767
4868
:param logger: logger instance to use
4969
:type logger: logging.Logger
5070
:param log_mask_re: regex lookup rule to mask command for logger.
5171
all MATCHED groups will be replaced by '<*masked*>'
5272
:type log_mask_re: typing.Optional[str]
53-
:param chroot_path: chroot path (use chroot if set)
54-
:type chroot_path: typing.Optional[str]
5573
"""
56-
super(ExecHelper, self).__init__(logger=logger, log_mask_re=log_mask_re, chroot_path=chroot_path)
74+
super(ExecHelper, self).__init__(logger=logger, log_mask_re=log_mask_re)
5775
self.__alock = None # type: typing.Optional[asyncio.Lock]
5876

5977
async def __aenter__(self) -> "ExecHelper":
@@ -67,6 +85,19 @@ async def __aexit__(self, exc_type: typing.Any, exc_val: typing.Any, exc_tb: typ
6785
"""Async context manager."""
6886
self.__alock.release() # type: ignore
6987

88+
def chroot(self, path: typing.Union[str, None]) -> "typing.ContextManager[None]":
89+
"""Context manager for changing chroot rules.
90+
91+
:param path: chroot path or none for working without chroot.
92+
:type path: typing.Optional[str]
93+
:return: context manager with selected chroot state inside
94+
:rtype: typing.ContextManager
95+
96+
.. Note:: Enter and exit main context manager is produced as well.
97+
.. versionadded:: 4.1.0
98+
"""
99+
return _ChRootContext(conn=self, path=path)
100+
70101
@abc.abstractmethod
71102
async def _exec_command( # type: ignore
72103
self,

exec_helpers/async_api/subprocess_runner.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ def __init__(
7171
self,
7272
log_mask_re: typing.Optional[str] = None,
7373
*,
74-
logger: logging.Logger = logging.getLogger(__name__), # noqa: B008
75-
chroot_path: typing.Optional[str] = None
74+
logger: logging.Logger = logging.getLogger(__name__) # noqa: B008
7675
) -> None:
7776
"""Subprocess helper with timeouts and lock-free FIFO.
7877
@@ -81,13 +80,11 @@ def __init__(
8180
:type log_mask_re: typing.Optional[str]
8281
:param logger: logger instance to use
8382
:type logger: logging.Logger
84-
:param chroot_path: chroot path (use chroot if set)
85-
:type chroot_path: typing.Optional[str]
8683
8784
.. versionchanged:: 3.1.0 Not singleton anymore. Only lock is shared between all instances.
8885
.. versionchanged:: 3.2.0 Logger can be enforced.
8986
"""
90-
super(Subprocess, self).__init__(logger=logger, log_mask_re=log_mask_re, chroot_path=chroot_path)
87+
super(Subprocess, self).__init__(logger=logger, log_mask_re=log_mask_re)
9188

9289
async def _exec_command( # type: ignore
9390
self,

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:: 3.1.0 Not singleton anymore. Only lock is shared between all instances.
8784
.. versionchanged:: 3.2.0 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,

0 commit comments

Comments
 (0)