diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index 0120d44cc16..870e2b28b86 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -2455,6 +2455,10 @@ class ComputeEnvironment(object): def tmp_directory(self): """Temp directory of target job - none if HOME should not be set.""" + @abstractmethod + def galaxy_url(self): + """URL to access Galaxy API from for this compute environment.""" + class SimpleComputeEnvironment(object): @@ -2525,6 +2529,9 @@ class SharedComputeEnvironment(SimpleComputeEnvironment): def tmp_directory(self): return self.job_wrapper.tmp_directory() + def galaxy_url(self): + return self.job_wrapper.get_destination_configuration("galaxy_infrastructure_url") + class NoopQueue(object): """ diff --git a/lib/galaxy/jobs/runners/condor.py b/lib/galaxy/jobs/runners/condor.py index 2ddb4030bc3..2a887bd87ab 100644 --- a/lib/galaxy/jobs/runners/condor.py +++ b/lib/galaxy/jobs/runners/condor.py @@ -176,7 +176,7 @@ class CondorJobRunner(AsynchronousJobRunner): job_id = cjs.job_id galaxy_id_tag = cjs.job_wrapper.get_id_tag() try: - if os.stat(cjs.user_log).st_size == cjs.user_log_size: + if cjs.job_wrapper.tool.tool_type != 'interactive' and os.stat(cjs.user_log).st_size == cjs.user_log_size: new_watched.append(cjs) continue s1, s4, s7, s5, s9, log_size = summarize_condor_log(cjs.user_log, job_id) diff --git a/lib/galaxy/jobs/runners/pulsar.py b/lib/galaxy/jobs/runners/pulsar.py index fa73cdcdc05..3e208bc9b53 100644 --- a/lib/galaxy/jobs/runners/pulsar.py +++ b/lib/galaxy/jobs/runners/pulsar.py @@ -1025,6 +1025,9 @@ class PulsarComputeEnvironment(ComputeEnvironment): # meantime. return None + def galaxy_url(self): + return self.job_wrapper.get_destination_configuration("galaxy_infrastructure_url") + class UnsupportedPulsarException(Exception): diff --git a/lib/galaxy/managers/library_datasets.py b/lib/galaxy/managers/library_datasets.py index 2f844e57c15..3b52b6a6227 100644 --- a/lib/galaxy/managers/library_datasets.py +++ b/lib/galaxy/managers/library_datasets.py @@ -139,7 +139,7 @@ class LibraryDatasetsManager(datasets.DatasetAssociationManager): val = validation.validate_and_sanitize_basestring(key, val) validated_payload[key] = val if key in ('tags'): - val = validation.validate_and_sanitize_basestring_list(key, val) + val = validation.validate_and_sanitize_basestring_list(key, util.listify(val)) validated_payload[key] = val return validated_payload diff --git a/lib/galaxy/tool_util/parser/xml.py b/lib/galaxy/tool_util/parser/xml.py index ecfaf88cce1..b453f1c82ca 100644 --- a/lib/galaxy/tool_util/parser/xml.py +++ b/lib/galaxy/tool_util/parser/xml.py @@ -138,9 +138,17 @@ class XmlToolSource(ToolSource): environment_variables = [] for environment_variable_el in environment_variables_el.findall("environment_variable"): + template = environment_variable_el.text + inject = environment_variable_el.get("inject") + if inject: + assert not template, "Cannot specify inject and environment variable template." + assert inject in ["api_key"] + if template: + assert not inject, "Cannot specify inject and environment variable template." definition = { "name": environment_variable_el.get("name"), - "template": environment_variable_el.text, + "template": template, + "inject": inject, "strip": string_as_bool(environment_variable_el.get("strip", False)), } environment_variables.append( diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index 8946797a945..05f7355b97b 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -4676,6 +4676,21 @@ variable instead of shell variable. ``` +### inject + +The Galaxy user's API key can be injected into an environment variable by setting ``inject`` +attribute to ``api_key`` (e.g. ``inject="api_key"``). + +```xml + + + +``` + +The framework allows setting this via environment variable and not via templating variables +in order to discourage setting the actual values of these keys as command line arguments. +On shared systems this provides some security by preventing a simple process listing command +from exposing keys. ]]> @@ -4686,15 +4701,27 @@ variable instead of shell variable. define. + + + Special variable to inject into the environment variable. Currently 'api_key' is the only option and will cause the user's API key to be injected via this environment variable. + + - - Whether to strip leading and trailing whitespace from the calculated value before exporting the environment variable. - + + Whether to strip leading and trailing whitespace from the calculated value before exporting the environment variable. + - + + + + + + + + ) which may not be the same value # as self.tool_data_path, we'll parse the path to get the filename and see if it is # in self.tool_data_path. file_path, file_name = os.path.split(filename) - if file_path and file_path != self.tool_data_path: + if file_path != self.tool_data_path: corrected_filename = os.path.join(self.tool_data_path, file_name) if self.tool_data_path_files.exists(corrected_filename): filename = corrected_filename found = True + elif not from_shed_config and self.tool_data_path_files.exists("%s.sample" % corrected_filename): + log.info("Could not find tool data %s, reading sample" % corrected_filename) + filename = "%s.sample" % corrected_filename + found = True errors = [] if found: diff --git a/lib/galaxy/tools/evaluation.py b/lib/galaxy/tools/evaluation.py index 04b0876c698..cd03a0fee80 100644 --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -69,7 +69,7 @@ class ToolEvaluator(object): incoming = self.tool.params_from_strings(incoming, self.app) # Full parameter validation - request_context = WorkRequestContext(app=self.app, user=job.history and job.history.user, history=job.history) + request_context = WorkRequestContext(app=self.app, user=self._user, history=self._history) def validate_inputs(input, value, context, **kwargs): value = input.from_json(value, request_context, context) @@ -135,7 +135,9 @@ class ToolEvaluator(object): param_dict["input"] = input param_dict['__datatypes_config__'] = param_dict['GALAXY_DATATYPES_CONF_FILE'] = os.path.join(job_working_directory, 'registry.xml') - + if self._history: + param_dict['__history_id__'] = self.app.security.encode_id(self._history.id) + param_dict['__galaxy_url__'] = self.compute_environment.galaxy_url() param_dict.update(self.tool.template_macro_params) # All parameters go into the param_dict param_dict.update(incoming) @@ -536,9 +538,19 @@ class ToolEvaluator(object): directory = self.local_working_directory environment_variable = environment_variable_def.copy() environment_variable_template = environment_variable_def["template"] + inject = environment_variable_def.get("inject") + if inject == "api_key": + if self._user: + from galaxy.managers import api_keys + environment_variable_template = api_keys.ApiKeyManager(self.app).get_or_create_api_key(self._user) + else: + environment_variable_template = "" + is_template = False + else: + is_template = True fd, config_filename = tempfile.mkstemp(dir=directory) os.close(fd) - self.__write_workdir_file(config_filename, environment_variable_template, param_dict, strip=environment_variable_def.get("strip", False)) + self.__write_workdir_file(config_filename, environment_variable_template, param_dict, is_template=is_template, strip=environment_variable_def.get("strip", False)) config_file_basename = os.path.basename(config_filename) # environment setup in job file template happens before `cd $working_directory` environment_variable["value"] = '`cat "$_GALAXY_JOB_DIR/%s"`' % config_file_basename @@ -624,3 +636,12 @@ class ToolEvaluator(object): compat. """ return self.compute_environment.sep().join(args) + + @property + def _history(self): + return self.job.history + + @property + def _user(self): + history = self._history + return history and history.user diff --git a/lib/galaxy/webapps/galaxy/controllers/user.py b/lib/galaxy/webapps/galaxy/controllers/user.py index 8e204ff1c24..56394bcf968 100644 --- a/lib/galaxy/webapps/galaxy/controllers/user.py +++ b/lib/galaxy/webapps/galaxy/controllers/user.py @@ -227,7 +227,7 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesApiKeysMixin): # while sometimes, so we don't want to block on logout. send_local_control_task(trans.app, "recalculate_user_disk_usage", - {"user_id": trans.security.encode_id(trans.user.id)}) + kwargs={"user_id": trans.security.encode_id(trans.user.id)}) # Since logging an event requires a session, we'll log prior to ending the session trans.log_event("User logged out") trans.handle_user_logout(logout_all=logout_all) diff --git a/test/functional/tools/environment_variables_inject.xml b/test/functional/tools/environment_variables_inject.xml new file mode 100644 index 00000000000..7b8cb5b6955 --- /dev/null +++ b/test/functional/tools/environment_variables_inject.xml @@ -0,0 +1,42 @@ + + + + + $__history_id__ + $__galaxy_url__ + + + echo "\$GX_API" > '$out_file_api_key'; + echo "\$GX_URL" > '$out_file_galaxy_url'; + echo "\$GX_HISTORY_ID" > '$out_file_history_id' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/interactivetool_simple.xml b/test/functional/tools/interactivetool_simple.xml index 54820286ef4..63e4c4b410e 100644 --- a/test/functional/tools/interactivetool_simple.xml +++ b/test/functional/tools/interactivetool_simple.xml @@ -1,4 +1,4 @@ - + galaxy/test-http-example:0.1 diff --git a/test/functional/tools/interactivetool_two_entry_points.xml b/test/functional/tools/interactivetool_two_entry_points.xml index 8693360840a..ffb59974df8 100644 --- a/test/functional/tools/interactivetool_two_entry_points.xml +++ b/test/functional/tools/interactivetool_two_entry_points.xml @@ -1,4 +1,4 @@ - + galaxy/test-http-example:0.1 diff --git a/test/functional/tools/samples_tool_conf.xml b/test/functional/tools/samples_tool_conf.xml index a12e997bce9..a867645e74f 100644 --- a/test/functional/tools/samples_tool_conf.xml +++ b/test/functional/tools/samples_tool_conf.xml @@ -8,6 +8,7 @@ + diff --git a/test/unit/tool_util/test_parsing.py b/test/unit/tool_util/test_parsing.py index 434e631ecf4..da646d19894 100644 --- a/test/unit/tool_util/test_parsing.py +++ b/test/unit/tool_util/test_parsing.py @@ -618,6 +618,16 @@ class CollectionOutputYamlTestCase(BaseLoaderTestCase): assert len(output_collections) == 1 +class EnvironmentVariablesTestCase(BaseLoaderTestCase): + source_file_name = os.path.join(galaxy_directory(), "test/functional/tools/environment_variables.xml") + source_contents = None + + def test_tests(self): + tests_dict = self._tool_source.parse_tests_to_dict() + tests = tests_dict["tests"] + assert len(tests) == 1 + + class ExpectationsTestCase(BaseLoaderTestCase): source_file_name = os.path.join(galaxy_directory(), "test/functional/tools/detect_errors.xml") source_contents = None diff --git a/test/unit/tools/test_evaluation.py b/test/unit/tools/test_evaluation.py index db94baf4cca..fc06b2782dd 100644 --- a/test/unit/tools/test_evaluation.py +++ b/test/unit/tools/test_evaluation.py @@ -33,6 +33,7 @@ from ..tools_support import UsesApp # To Test: # - param_file handling. TEST_TOOL_DIRECTORY = "/path/to/the/tool" +TEST_GALAXY_URL = "http://mycool.galaxyproject.org:8456" class ToolEvaluatorTestCase(TestCase, UsesApp): @@ -42,6 +43,7 @@ class ToolEvaluatorTestCase(TestCase, UsesApp): self.tool = MockTool(self.app) self.job = Job() self.job.history = History() + self.job.history.id = 42 self.job.parameters = [JobParameter(name="thresh", value="4")] self.evaluator = ToolEvaluator(self.app, self.tool, self.job, self.test_directory) @@ -65,6 +67,18 @@ class ToolEvaluatorTestCase(TestCase, UsesApp): command_line, extra_filenames, _ = self.evaluator.build() self.assertEqual(command_line, "prog1 4 5") + def test_eval_galaxy_url(self): + self.tool._command_line = "prog1 $__galaxy_url__" + self._set_compute_environment() + command_line, extra_filenames, _ = self.evaluator.build() + self.assertEqual(command_line, "prog1 %s" % TEST_GALAXY_URL) + + def test_eval_history_id(self): + self.tool._command_line = "prog1 '$__history_id__'" + self._set_compute_environment() + command_line, extra_filenames, _ = self.evaluator.build() + self.assertEqual(command_line, "prog1 '%s'" % self.app.security.encode_id(42)) + def test_conditional_evaluation(self): select_xml = XML('''''') parameter = SelectToolParameter(self.tool, select_xml) @@ -264,6 +278,9 @@ class TestComputeEnvironment(SimpleComputeEnvironment): def tool_directory(self): return TEST_TOOL_DIRECTORY + def galaxy_url(self): + return TEST_GALAXY_URL + class MockTool(object): diff --git a/tools/interactive/interactivetool_askomics.xml b/tools/interactive/interactivetool_askomics.xml index c1ce9d3bd11..b5f0b4d56a4 100644 --- a/tools/interactive/interactivetool_askomics.xml +++ b/tools/interactive/interactivetool_askomics.xml @@ -10,15 +10,8 @@ - ${__app__.config.galaxy_infrastructure_url} - - #if $__user__: - #for $api_key in $__user__.api_keys: - ${api_key.key} - #break - #end for - #end if - + $__galaxy_url__ + - ${__app__.security.encode_id($jupyter_notebook.history_id)} - ${__app__.config.galaxy_infrastructure_url} + $__history_id__ + $__galaxy_url__ 8080 - ${__app__.config.galaxy_infrastructure_url} - - #if $__user__: - #for $api_key in $__user__.api_keys: - ${api_key.key} - #break - #end for - #end if - + $__galaxy_url__ +