diff --git a/doc/source/admin/dependency_resolvers.rst b/doc/source/admin/dependency_resolvers.rst index 6b196a0f4c4..2c601f5acdb 100644 --- a/doc/source/admin/dependency_resolvers.rst +++ b/doc/source/admin/dependency_resolvers.rst @@ -236,3 +236,7 @@ copy_dependencies linking them when creating per job environments. This should be considered somewhat deprecated because Conda will do this as needed for newer versions of Conda - such as the version targeted with Galaxy 17.01+. + +read_only + If ``True``, Galaxy will not attempt to install or uninstall requirement sets into + this environment. diff --git a/lib/galaxy/tool_util/deps/container_resolvers/__init__.py b/lib/galaxy/tool_util/deps/container_resolvers/__init__.py index 395deffe5ef..ed609dfc15a 100644 --- a/lib/galaxy/tool_util/deps/container_resolvers/__init__.py +++ b/lib/galaxy/tool_util/deps/container_resolvers/__init__.py @@ -25,6 +25,7 @@ class ContainerResolver(Dictifiable, metaclass=ABCMeta): dict_collection_visible_keys = ['resolver_type', 'can_uninstall_dependencies', 'builds_on_resolution'] can_uninstall_dependencies = False builds_on_resolution = False + read_only = True # not used for containers, but set for when they are used like dependency resolvers def __init__(self, app_info=None, **kwds): """Default initializer for ``ContainerResolver`` subclasses.""" diff --git a/lib/galaxy/tool_util/deps/resolvers/__init__.py b/lib/galaxy/tool_util/deps/resolvers/__init__.py index be782f2b8cf..69d9960583f 100644 --- a/lib/galaxy/tool_util/deps/resolvers/__init__.py +++ b/lib/galaxy/tool_util/deps/resolvers/__init__.py @@ -19,7 +19,7 @@ class DependencyResolver(Dictifiable, metaclass=ABCMeta): """Abstract description of a technique for resolving container images for tool execution.""" # Keys for dictification. - dict_collection_visible_keys = ['resolver_type', 'resolves_simple_dependencies', 'can_uninstall_dependencies'] + dict_collection_visible_keys = ['resolver_type', 'resolves_simple_dependencies', 'can_uninstall_dependencies', 'read_only'] # A "simple" dependency is one that does not depend on the the tool # resolving the dependency. Classic tool shed dependencies are non-simple # because the repository install context is used in dependency resolution @@ -29,6 +29,7 @@ class DependencyResolver(Dictifiable, metaclass=ABCMeta): resolves_simple_dependencies = True can_uninstall_dependencies = False config_options: Dict[str, Any] = {} + read_only = True @abstractmethod def resolve(self, requirement, **kwds): @@ -241,9 +242,16 @@ class InstallableDependencyResolver(metaclass=ABCMeta): """ Mix this into a ``DependencyResolver`` and implement to indicate the dependency resolver can attempt to install new dependencies. """ + read_only = False + + def install_dependency(self, name, version, type, **kwds): + if self.read_only: + return False + else: + return self._install_dependency(name, version, type, **kwds) @abstractmethod - def install_dependency(self, name, version, type, **kwds): + def _install_dependency(self, name, version, type, **kwds): """ Attempt to install this dependency if a recipe to do so has been registered in some way. """ diff --git a/lib/galaxy/tool_util/deps/resolvers/conda.py b/lib/galaxy/tool_util/deps/resolvers/conda.py index 48877ff2ab9..9585cd4243f 100644 --- a/lib/galaxy/tool_util/deps/resolvers/conda.py +++ b/lib/galaxy/tool_util/deps/resolvers/conda.py @@ -80,7 +80,9 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li _specification_pattern = re.compile(r"https\:\/\/anaconda.org\/\w+\/\w+") def __init__(self, dependency_manager, **kwds): - self.can_uninstall_dependencies = True + read_only = _string_as_bool(kwds.get('read_only', 'false')) + self.read_only = read_only + self.can_uninstall_dependencies = not read_only self._setup_mapping(dependency_manager, **kwds) self.versionless = _string_as_bool(kwds.get('versionless', 'false')) self.dependency_manager = dependency_manager @@ -167,6 +169,9 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li return final_return_code def install_all(self, conda_targets): + if self.read_only: + return False + env = self.merged_environment_name(conda_targets) return_code = install_conda_targets(conda_targets, conda_context=self.conda_context, env_name=env) if return_code != 0: @@ -335,8 +340,11 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li version = install_target.version yield self._to_requirement(name, version) - def install_dependency(self, name, version, type, **kwds): + def _install_dependency(self, name, version, type, **kwds): "Returns True on (seemingly) successfull installation" + # should be checked before called + assert not self.read_only + if type != "package": log.warning("Cannot install dependencies of type '%s'" % type) return False diff --git a/test/unit/tool_util/test_conda_resolution.py b/test/unit/tool_util/test_conda_resolution.py index f445f86812b..18fdca874ea 100644 --- a/test/unit/tool_util/test_conda_resolution.py +++ b/test/unit/tool_util/test_conda_resolution.py @@ -23,6 +23,12 @@ def test_conda_resolution(): auto_install=True, use_path_exec=False, # For the test ensure this is always a clean install ) + resolver.read_only = True + req = ToolRequirement(name="samtools", version=None, type="package") + dependency = resolver.resolve(req, job_directory=job_dir) + assert dependency.shell_commands() is None + + resolver.read_only = False req = ToolRequirement(name="samtools", version=None, type="package") dependency = resolver.resolve(req, job_directory=job_dir) assert dependency.shell_commands() is not None