Skip to content

Fix is_symlink method when executed from py3.12

Marcin Chron requested to merge chron/fix-ci into master

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

Merge request reports

Loading