diff --git a/lib/galaxy/tool_util/deps/requirements.py b/lib/galaxy/tool_util/deps/requirements.py index 7865bdb59435..b77566cc3cd9 100644 --- a/lib/galaxy/tool_util/deps/requirements.py +++ b/lib/galaxy/tool_util/deps/requirements.py @@ -306,28 +306,64 @@ def resource_requirements_from_list(requirements: Iterable[Dict[str, Any]]) -> L return rr -class SecretOrVariable: +class Secret: + def __init__( + self, + name: str, + inject_as_env: str, + label: str = "", + description: str = "", + ) -> None: + self.name = name + self.inject_as_env = inject_as_env + self.label = label + self.description = description + if not self.inject_as_env: + raise ValueError("Missing inject_as_env") + + def to_dict(self) -> Dict[str, Any]: + return { + "name": self.name, + "inject_as_env": self.inject_as_env, + "label": self.label, + "description": self.description, + } + + @classmethod + def from_element(cls, elem) -> "Secret": + return cls( + name=elem.get("name"), + inject_as_env=elem.get("inject_as_env"), + label=elem.get("label", ""), + description=elem.get("description", ""), + ) + + @classmethod + def from_dict(cls, dict: Dict[str, Any]) -> "Secret": + name = dict["name"] + inject_as_env = dict["inject_as_env"] + label = dict.get("label", "") + description = dict.get("description", "") + return cls(name=name, inject_as_env=inject_as_env, label=label, description=description) + + +class Variable: def __init__( self, - type: str, name: str, inject_as_env: str, label: str = "", description: str = "", ) -> None: - self.type = type self.name = name self.inject_as_env = inject_as_env self.label = label self.description = description - if self.type not in {"secret", "variable"}: - raise ValueError(f"Invalid credential type '{self.type}'") if not self.inject_as_env: raise ValueError("Missing inject_as_env") def to_dict(self) -> Dict[str, Any]: return { - "type": self.type, "name": self.name, "inject_as_env": self.inject_as_env, "label": self.label, @@ -335,9 +371,8 @@ def to_dict(self) -> Dict[str, Any]: } @classmethod - def from_element(cls, elem) -> "SecretOrVariable": + def from_element(cls, elem) -> "Variable": return cls( - type=elem.tag, name=elem.get("name"), inject_as_env=elem.get("inject_as_env"), label=elem.get("label", ""), @@ -345,13 +380,12 @@ def from_element(cls, elem) -> "SecretOrVariable": ) @classmethod - def from_dict(cls, dict: Dict[str, Any]) -> "SecretOrVariable": - type = dict["type"] + def from_dict(cls, dict: Dict[str, Any]) -> "Variable": name = dict["name"] inject_as_env = dict["inject_as_env"] label = dict.get("label", "") description = dict.get("description", "") - return cls(type=type, name=name, inject_as_env=inject_as_env, label=label, description=description) + return cls(name=name, inject_as_env=inject_as_env, label=label, description=description) class CredentialsRequirement: @@ -359,17 +393,21 @@ def __init__( self, name: str, reference: str, - required: bool = False, + optional: bool = True, + multiple: bool = False, label: str = "", description: str = "", - secrets_and_variables: Optional[List[SecretOrVariable]] = None, + secrets: Optional[List[Secret]] = None, + variables: Optional[List[Variable]] = None, ) -> None: self.name = name self.reference = reference - self.required = required + self.optional = optional + self.multiple = multiple self.label = label self.description = description - self.secrets_and_variables = secrets_and_variables if secrets_and_variables is not None else [] + self.secrets = secrets if secrets is not None else [] + self.variables = variables if variables is not None else [] if not self.reference: raise ValueError("Missing reference") @@ -378,27 +416,33 @@ def to_dict(self) -> Dict[str, Any]: return { "name": self.name, "reference": self.reference, - "required": self.required, + "optional": self.optional, + "multiple": self.multiple, "label": self.label, "description": self.description, - "secrets_and_variables": [s.to_dict() for s in self.secrets_and_variables], + "secrets": [s.to_dict() for s in self.secrets], + "variables": [v.to_dict() for v in self.variables], } @classmethod def from_dict(cls, dict: Dict[str, Any]) -> "CredentialsRequirement": name = dict["name"] reference = dict["reference"] - required = dict.get("required", False) + optional = dict.get("optional", True) + multiple = dict.get("multiple", False) label = dict.get("label", "") description = dict.get("description", "") - secrets_and_variables = [SecretOrVariable.from_dict(s) for s in dict.get("secrets_and_variables", [])] + secrets = [Secret.from_dict(s) for s in dict.get("secrets", [])] + variables = [Variable.from_dict(v) for v in dict.get("variables", [])] return cls( name=name, reference=reference, - required=required, + optional=optional, + multiple=multiple, label=label, description=description, - secrets_and_variables=secrets_and_variables, + secrets=secrets, + variables=variables, ) @@ -490,15 +534,19 @@ def container_from_element(container_elem) -> ContainerDescription: def credentials_from_element(credentials_elem) -> CredentialsRequirement: name = credentials_elem.get("name") reference = credentials_elem.get("reference") - required = string_as_bool(credentials_elem.get("required", "false")) + optional = string_as_bool(credentials_elem.get("optional", "true")) + multiple = string_as_bool(credentials_elem.get("multiple", "false")) label = credentials_elem.get("label", "") description = credentials_elem.get("description", "") - secrets_and_variables = [SecretOrVariable.from_element(elem) for elem in credentials_elem.findall("*")] + secrets = [Secret.from_element(elem) for elem in credentials_elem.findall("secret")] + variables = [Variable.from_element(elem) for elem in credentials_elem.findall("variable")] return CredentialsRequirement( name=name, reference=reference, - required=required, + optional=optional, + multiple=multiple, label=label, description=description, - secrets_and_variables=secrets_and_variables, + secrets=secrets, + variables=variables, ) diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index c688fc4f8692..6700dbd18a22 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -739,11 +739,11 @@ It can contain multiple ``variable`` and ``secret`` tags. ```xml - - - - - + + + + + ``` ]]> @@ -772,9 +772,14 @@ It can contain multiple ``variable`` and ``secret`` tags. The description of the credential set. - + - Whether the credentials are required for the tool to run. + Whether the credentials are optional for the tool to run. + + + + + Indicates multiple sets of credentials can be provided. diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 286283e4e0b4..77d23113d6c7 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -2667,6 +2667,27 @@ def to_json(self, trans, kwd=None, job=None, workflow_building_mode=False, histo state_inputs_json: ToolStateDumpedToJsonT = params_to_json(self.inputs, state_inputs, self.app) + credentials = [] + for credential in self.credentials: + credential_dict = credential.to_dict() + credential_dict["variables"] = [ + { + "name": variable.name, + "label": variable.label, + "description": variable.description, + } + for variable in credential.variables + ] + credential_dict["secrets"] = [ + { + "name": secret.name, + "label": secret.label, + "description": secret.description, + } + for secret in credential.secrets + ] + credentials.append(credential_dict) + # update tool model tool_model.update( { @@ -2679,6 +2700,7 @@ def to_json(self, trans, kwd=None, job=None, workflow_building_mode=False, histo "warnings": tool_warnings, "versions": self.tool_versions, "requirements": [{"name": r.name, "version": r.version} for r in self.requirements], + "credentials": credentials, "errors": state_errors, "tool_errors": self.tool_errors, "state_inputs": state_inputs_json, diff --git a/lib/galaxy/tools/evaluation.py b/lib/galaxy/tools/evaluation.py index bbb72308cc1e..12d81a55a0be 100644 --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -195,19 +195,12 @@ def set_compute_environment(self, compute_environment: ComputeEnvironment, get_s # user_vault = UserVaultWrapper(app.vault, self._user) for credentials in self.tool.credentials: reference = credentials.reference - for secret_or_variable in credentials.secrets_and_variables: - if secret_or_variable.type == "variable": - # variable_value = self.param_dict.get(f"{reference}/{secret_or_variable.name}") - variable_value = f"A variable: {reference}/{secret_or_variable.name}" - self.environment_variables.append( - {"name": secret_or_variable.inject_as_env, "value": variable_value} - ) - elif secret_or_variable.type == "secret": - # secret_value = user_vault.read_secret(f"{reference}/{secret_or_variable.name}") - secret_value = f"A secret: {reference}/{secret_or_variable.name}" - self.environment_variables.append( - {"name": secret_or_variable.inject_as_env, "value": secret_value} - ) + for secret in credentials.secret: + secret_value = f"{reference}/{secret.name}" + self.environment_variables.append({"name": secret.inject_as_env, "value": secret_value}) + for variable in credentials.variable: + variable_value = f"{reference}/{variable.name}" + self.environment_variables.append({"name": variable.inject_as_env, "value": variable_value}) def execute_tool_hooks(self, inp_data, out_data, incoming): # Certain tools require tasks to be completed prior to job execution diff --git a/test/functional/tools/secret_tool.xml b/test/functional/tools/secret_tool.xml index bbcf9b7f91a3..998bf14dcb1a 100644 --- a/test/functional/tools/secret_tool.xml +++ b/test/functional/tools/secret_tool.xml @@ -1,15 +1,15 @@ - + + + + + '$output' +echo \$service1_url > '$output' && echo \$service1_user >> '$output' && echo \$service1_pass >> '$output' ]]> - - - - diff --git a/test/integration/test_vault_extra_prefs.py b/test/integration/test_vault_extra_prefs.py index 2fde51a0201a..d9166a553b8a 100644 --- a/test/integration/test_vault_extra_prefs.py +++ b/test/integration/test_vault_extra_prefs.py @@ -11,11 +11,12 @@ ) from galaxy.model.db.user import get_user_by_email -from galaxy_test.api.test_tools import TestsTools -from galaxy_test.base.populators import ( - DatasetPopulator, - skip_without_tool, -) + +# from galaxy_test.api.test_tools import TestsTools +# from galaxy_test.base.populators import ( +# DatasetPopulator, +# skip_without_tool, +# ) from galaxy_test.driver import integration_util TEST_USER_EMAIL = "vault_test_user@bx.psu.edu" diff --git a/test/unit/tool_util/test_parsing.py b/test/unit/tool_util/test_parsing.py index 169d69ff8759..ea4ddb1f334f 100644 --- a/test/unit/tool_util/test_parsing.py +++ b/test/unit/tool_util/test_parsing.py @@ -50,10 +50,10 @@ 1 2 67108864 - - - - + + + + @@ -368,11 +368,9 @@ def test_credentials(self): *_, credentials = self._tool_source.parse_requirements_and_containers() assert credentials[0].name == "Apollo" assert credentials[0].reference == "gmod.org/apollo" - assert credentials[0].required - assert len(credentials[0].secrets_and_variables) == 3 - assert credentials[0].secrets_and_variables[0].type == "variable" - assert credentials[0].secrets_and_variables[1].type == "secret" - assert credentials[0].secrets_and_variables[2].type == "secret" + assert credentials[0].optional + assert len(credentials[0].secrets) == 2 + assert len(credentials[0].variables) == 1 def test_outputs(self): outputs, output_collections = self._tool_source.parse_outputs(object())