Fix is_symlink method when executed from py3.12
Description
Python3.12 changes structure of os.path module. If target host is using older python version, os.path.islink
points to non-existing function.
failing test stacktrace
self = <inmanta_plugins.mitogen.Proxy object at 0x7f10928878c0>
function = <function islink at 0x7f109c7584a0>, args = ('/tmp/test-file',)
kwargs = {}
logger_kwargs = {'args': ['/tmp/test-file'], 'context_name': 'ssh.127.0.0.1:32778.sudo.root', 'exception': '"builtins.AttributeError: module \'genericpath<...>', 'function': 'genericpath.islink', ...}
def remote_call(
self,
function: collections.abc.Callable[..., object],
*args: object,
**kwargs: object,
) -> object:
"""
Perform a remote call and returns the result. When a remote exception occurs the exception and stacktrace
is packed in RemoteException
"""
logger_kwargs = dict(
function=function.__module__ + "." + function.__qualname__,
context_name=self.context.name,
args=[truncate_logged_value(arg) for arg in args],
kwargs={key: truncate_logged_value(value) for key, value in kwargs.items()},
)
try:
> result = self.context.call(function, *args, **kwargs)
inmanta_plugins/mitogen/__init__.py:519:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
env/lib64/python3.12/site-packages/mitogen/parent.py:2007: in call
return self.default_call_chain.call(fn, *args, **kwargs)
env/lib64/python3.12/site-packages/mitogen/parent.py:1964: in call
return receiver.get().unpickle(throw_dead=False)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = Message(0, 2, 2, 1012, 0, b'\x80\x02cmitogen.core\n_unpickle_call_error\nq\x00X\xa5\x00\x00\x00builti'..218)
throw = True, throw_dead = False
def unpickle(self, throw=True, throw_dead=True):
"""
Unpickle :attr:`data`, optionally raising any exceptions present.
:param bool throw_dead:
If :data:`True`, raise exceptions, otherwise it is the caller's
responsibility.
:raises CallError:
The serialized data contained CallError exception.
:raises ChannelError:
The `is_dead` field was set.
"""
_vv and IOLOG.debug('%r.unpickle()', self)
if throw_dead and self.is_dead:
self._throw_dead()
obj = self._unpickled
if obj is Message._unpickled:
fp = BytesIO(self.data)
unpickler = _Unpickler(fp, **self.UNPICKLER_KWARGS)
unpickler.find_global = self._find_global
try:
# Must occur off the broker thread.
try:
obj = unpickler.load()
except:
LOG.error('raw pickle was: %r', self.data)
raise
self._unpickled = obj
except (TypeError, ValueError):
e = sys.exc_info()[1]
raise StreamError('invalid message: %s', e)
if throw:
if isinstance(obj, CallError):
> raise obj
E mitogen.core.CallError: builtins.AttributeError: module 'genericpath' has no attribute 'islink'
E File "<stdin>", line 3856, in _dispatch_one
E File "<stdin>", line 3846, in _parse_request
env/lib64/python3.12/site-packages/mitogen/core.py:1005: CallError
During handling of the above exception, another exception occurred:
remote_ssh_container = <ssh_container.SshContainer object at 0x7f10925c2270>
def test_remote_ssh_sudo(remote_ssh_container: ssh_container.SshContainer) -> None:
export_port = remote_ssh_container.get_exposed_port(remote_ssh_container.port)
with inmanta_plugins.mitogen.Proxy(
{
"method_name": "sudo",
"via": {
"method_name": "ssh",
"hostname": remote_ssh_container.get_container_host_ip(),
"port": export_port,
"username": "user",
"python_path": ["/usr/libexec/platform-python"],
"identity_file": str(remote_ssh_container.private_key_file),
"check_host_keys": "ignore",
},
},
) as io:
assert io.run("whoami") == ("root", "", 0)
path = "/tmp/test-file"
content = "this is a test"
io.put(path, content.encode())
assert io.read(path) == content
assert io.read_binary(path) == content.encode()
assert io.file_stat(path) == {
"group": "root",
"owner": "root",
"permissions": 644,
}
assert io.file_exists(path)
io.chown(path, "user", "user")
assert io.file_stat(path) == {
"group": "user",
"owner": "user",
"permissions": 644,
}
io.chmod(path, "755")
assert io.file_stat(path) == {
"group": "user",
"owner": "user",
"permissions": 755,
}
sha1sum = hashlib.sha1()
sha1sum.update(content.encode())
assert io.hash_file(path) == sha1sum.hexdigest()
> assert not io.is_symlink(path)
tests/test_basics.py:78:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
inmanta_plugins/mitogen/__init__.py:629: in is_symlink
return bool(self.remote_call(os.path.islink, path))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <inmanta_plugins.mitogen.Proxy object at 0x7f10928878c0>
function = <function islink at 0x7f109c7584a0>, args = ('/tmp/test-file',)
kwargs = {}
logger_kwargs = {'args': ['/tmp/test-file'], 'context_name': 'ssh.127.0.0.1:32778.sudo.root', 'exception': '"builtins.AttributeError: module \'genericpath<...>', 'function': 'genericpath.islink', ...}
def remote_call(
self,
function: collections.abc.Callable[..., object],
*args: object,
**kwargs: object,
) -> object:
"""
Perform a remote call and returns the result. When a remote exception occurs the exception and stacktrace
is packed in RemoteException
"""
logger_kwargs = dict(
function=function.__module__ + "." + function.__qualname__,
context_name=self.context.name,
args=[truncate_logged_value(arg) for arg in args],
kwargs={key: truncate_logged_value(value) for key, value in kwargs.items()},
)
try:
result = self.context.call(function, *args, **kwargs)
logger_kwargs["result"] = truncate_logged_value(result)
self.logger.debug(
"Calling function %(function)s in context %(context_name)s: %(result)s",
**logger_kwargs,
)
return result
except mitogen.core.CallError as e:
# Translate to our remote exception to not expose mitogen into the caller code
logger_kwargs["exception"] = truncate_logged_value(str(e))
self.logger.debug(
"Calling function %(function)s in context %(context_name)s: %(exception)s",
**logger_kwargs,
)
> raise RemoteException(e)
E inmanta_plugins.mitogen.RemoteException: builtins.AttributeError: module 'genericpath' has no attribute 'islink'
E File "<stdin>", line 3856, in _dispatch_one
E File "<stdin>", line 3846, in _parse_request
inmanta_plugins/mitogen/__init__.py:533: RemoteException
Closes Add ticket reference here
Reviewers list
-
reviewer 1
Self Check:
Strike through any lines that are not applicable (~~line~~
) then check the box
-
Changelog entry and version bump inmanta module release --dev [--major|--minor|--patch] [--changelog-message "<your_changelog_message>"]
-
Code is clear and sufficiently documented (classes, methods and function docs, document important assumptions, link to reference materials) -
Sufficient test cases (reproduces the bug/tests the requested feature) -
Correct, in line with design -
Type annotations -
Issue is in the review column -
MR is assigned to first reviewer
Edited by Marcin Chron