diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 94db66b6db..e604640fc8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -53,6 +53,10 @@ Fixed * StackStorm now explicitly decodes pack files as utf-8 instead of implicitly as ascii (bug fix) #5106, #5107 +* Kept functionality for implicit relative imports in Python actions (regression, bug fix) #5127 + + Identified by @amanda11 + Removed ~~~~~~~~ * Removed --python3 pack install option #5100 diff --git a/st2common/st2common/util/sandboxing.py b/st2common/st2common/util/sandboxing.py index 6b3c4e29b9..b6b629a72e 100644 --- a/st2common/st2common/util/sandboxing.py +++ b/st2common/st2common/util/sandboxing.py @@ -20,6 +20,7 @@ from __future__ import absolute_import +import fnmatch import os import sys from distutils.sysconfig import get_python_lib @@ -27,6 +28,7 @@ from oslo_config import cfg from st2common.constants.pack import SYSTEM_PACK_NAMES +from st2common.content.utils import get_pack_base_path __all__ = [ 'get_sandbox_python_binary_path', @@ -132,10 +134,67 @@ def get_sandbox_python_path_for_python_action(pack, inherit_from_parent=True, Same as get_sandbox_python_path() function, but it's intended to be used for Python runner actions. """ - return get_sandbox_python_path( + sandbox_python_path = get_sandbox_python_path( inherit_from_parent=inherit_from_parent, inherit_parent_virtualenv=inherit_parent_virtualenv) + virtualenv_path = get_sandbox_virtualenv_path(pack=pack) + + if virtualenv_path and os.path.isdir(virtualenv_path): + # Get the pack's virtualenv site-packages directory + # There should only be one, but we don't know what version of Python it + # will be using, so we use a glob and ensure it exists + virtualenv_lib_path = os.path.join(virtualenv_path, 'lib') + virtualenv_lib_directories = [ + dir_name for dir_name in os.listdir(virtualenv_lib_path) + if fnmatch.fnmatch(dir_name, 'python*') + ] + + if virtualenv_lib_directories: + pack_base_path = get_pack_base_path(pack_name=pack) + # This will be just the virtualenv's Python version - eg: python3.6 + virtualenv_lib_directory = virtualenv_lib_directories[0] + + # Get the pack's actions/lib directory + pack_actions_lib_paths = os.path.join(pack_base_path, 'actions', 'lib') + # Get the pack's virtualenv's site-packages directory + virtualenv_lib_directory = os.path.join(virtualenv_lib_path, virtualenv_lib_directory) + + # Work around to make sure we also add system lib dir to PYTHONPATH + # and not just virtualenv one (e.g. /usr/lib/python3.6) + # This code will pick the same sytem Python version as exists in + # the virtualenv, since virtualenv_lib_directory identifies that + # directory within the virtualenv and it should have the same name + # as the system Python executable. + + # NOTE: We can't simply use sys.prefix dir since it will be set to /opt/stackstorm/st2 + + # By default, Python libs are installed either in /usr/lib/python3.x or + # /usr/local/lib/python3.x + for system_prefix_dir in ['/usr/lib', '/usr/local/lib']: + system_lib_directory = os.path.join(system_prefix_dir, + virtualenv_lib_directory) + + if os.path.exists(system_lib_directory): + break + else: + # If no system library is found + system_lib_directory = None + + full_sandbox_python_path = [] + + # NOTE: Order here is very important for imports to function correctly + if system_lib_directory: + full_sandbox_python_path.append(system_lib_directory) + + full_sandbox_python_path.append(virtualenv_lib_directory) + full_sandbox_python_path.append(pack_actions_lib_paths) + full_sandbox_python_path.append(sandbox_python_path) + + sandbox_python_path = ':'.join(full_sandbox_python_path) + + return sandbox_python_path + def get_sandbox_virtualenv_path(pack): """ diff --git a/st2common/tests/unit/test_util_sandboxing.py b/st2common/tests/unit/test_util_sandboxing.py index af3c4328da..e1da46e257 100644 --- a/st2common/tests/unit/test_util_sandboxing.py +++ b/st2common/tests/unit/test_util_sandboxing.py @@ -111,6 +111,8 @@ def test_get_sandbox_python_path(self, mock_get_python_lib): @mock.patch('os.path.isdir', mock.Mock(return_value=True)) @mock.patch('os.listdir', mock.Mock(return_value=['python2.7'])) + @mock.patch('st2common.util.sandboxing.get_sandbox_virtualenv_path', + mock.Mock(return_value=None)) @mock.patch('st2common.util.sandboxing.get_python_lib') def test_get_sandbox_python_path_for_python_action_python2_used_for_venv(self, mock_get_python_lib):