Skip to content

Commit f2a39f2

Browse files
author
Michael Buchar
committed
feat(task): pass scoped user secret names (#319)
Forward optional secret allowlists and step-local REANA hints from CWL workflows so job submissions preserve workflow defaults, step overrides, and the documented omitted-versus-empty semantics. Closes reanahub/reana#978
1 parent 5e8a9dd commit f2a39f2

4 files changed

Lines changed: 79 additions & 1 deletion

File tree

reana_workflow_engine_cwl/cwl_reana.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@
3333
from reana_commons.api_client import JobControllerAPIClient as rjc_api_client
3434
from reana_commons.config import REANA_WORKFLOW_UMASK
3535

36+
try:
37+
from reana_commons.k8s.secrets import resolve_secret_names
38+
except ModuleNotFoundError:
39+
def resolve_secret_names(scoped_secret_names, workflow_resources=None):
40+
"""Resolve a step-local allowlist against the workflow-global default."""
41+
if scoped_secret_names is not None:
42+
return scoped_secret_names
43+
workflow_resources = workflow_resources or {}
44+
return workflow_resources.get("secret_names")
45+
3646
from reana_workflow_engine_cwl.config import LOGGING_MODULE, MOUNT_CVMFS
3747
from reana_workflow_engine_cwl.pipeline import Pipeline
3848
from reana_workflow_engine_cwl.poll import PollThread
@@ -51,6 +61,7 @@ def __init__(self, **kwargs):
5161
self.service = kwargs.get(
5262
"rjc_api_client", rjc_api_client("reana-job-controller")
5363
)
64+
self.workflow_resources = kwargs.get("workflow_resources") or {}
5465
if kwargs.get("basedir") is not None:
5566
self.basedir = kwargs.get("basedir")
5667
else:
@@ -280,6 +291,9 @@ def shouldquote(x):
280291
c4p_cpu_cores = self._get_hint("c4p_cpu_cores")
281292
c4p_memory_limit = self._get_hint("c4p_memory_limit")
282293
c4p_additional_requirements = self._get_hint("c4p_additional_requirements")
294+
secret_names = resolve_secret_names(
295+
self._get_hint("secret_names"), getattr(self, "workflow_resources", {})
296+
)
283297
create_body = {
284298
"image": container,
285299
"cmd": wrapped_cmd,
@@ -293,6 +307,7 @@ def shouldquote(x):
293307
"unpacked_img": unpacked_img,
294308
"voms_proxy": voms_proxy,
295309
"rucio": rucio,
310+
"secret_names": secret_names,
296311
"htcondor_max_runtime": htcondor_max_runtime,
297312
"htcondor_accounting_group": htcondor_accounting_group,
298313
"htcondor_request_cpus": htcondor_request_cpus,
@@ -332,6 +347,7 @@ def _get_hint(self, hint_name):
332347
def run(self, runtimeContext): # noqa: C901
333348
"""Run a job."""
334349
self._setup(runtimeContext)
350+
self.workflow_resources = runtimeContext.pipeline.workflow_resources
335351

336352
env = self.environment
337353
if not os.path.exists(self.tmpdir):

reana_workflow_engine_cwl/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def main(
4747
rjc_api_client,
4848
workflow_uuid,
4949
workflow_spec,
50+
workflow_resources,
5051
workflow_inputs,
5152
operational_options,
5253
working_dir,
@@ -127,7 +128,9 @@ def main(
127128
if parsed_args.debug:
128129
log.setLevel(logging.DEBUG)
129130

130-
pipeline = ReanaPipeline(rjc_api_client=rjc_api_client)
131+
pipeline = ReanaPipeline(
132+
rjc_api_client=rjc_api_client, workflow_resources=workflow_resources
133+
)
131134
log.info("starting the run..")
132135
db_log_writer = SQLiteHandler(workflow_uuid, publisher)
133136

reana_workflow_engine_cwl/tasks.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def run_cwl_workflow_engine_adapter(
3232
workflow_uuid=None,
3333
workflow_workspace=None,
3434
workflow_json=None,
35+
workflow_resources=None,
3536
workflow_parameters=None,
3637
operational_options={},
3738
**kwargs,
@@ -43,6 +44,7 @@ def run_cwl_workflow_engine_adapter(
4344
rjc_api_client,
4445
workflow_uuid,
4546
workflow_json,
47+
workflow_resources,
4648
workflow_parameters,
4749
operational_options,
4850
workflow_workspace,

tests/test_cwl_reana.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# This file is part of REANA.
4+
# Copyright (C) 2026 CERN.
5+
#
6+
# REANA is free software; you can redistribute it and/or modify it
7+
# under the terms of the MIT License; see LICENSE file for more details.
8+
9+
"""REANA-Workflow-Engine-CWL tests."""
10+
11+
from types import SimpleNamespace
12+
13+
import pytest
14+
15+
from reana_workflow_engine_cwl.cwl_reana import ReanaPipelineJob
16+
17+
18+
@pytest.mark.parametrize(
19+
"step_secret_names,workflow_secret_names,expected_secret_names",
20+
[
21+
(None, None, None),
22+
(None, ["global"], ["global"]),
23+
([], ["global"], []),
24+
(["local"], ["global"], ["local"]),
25+
],
26+
)
27+
def test_create_task_msg_secret_names_resolution(
28+
step_secret_names, workflow_secret_names, expected_secret_names
29+
):
30+
"""Step hints should override or inherit workflow-global secret_names."""
31+
job = ReanaPipelineJob.__new__(ReanaPipelineJob)
32+
job.name = "fit"
33+
job.environment = {"HOME": "/tmp/outdir"}
34+
job.volumes = []
35+
job.command_line = ["echo", "hello"]
36+
job.stdin = None
37+
job.stdout = None
38+
job.stderr = None
39+
job.outdir = "/tmp/outdir"
40+
job.builder = SimpleNamespace(outdir="/tmp/outdir", bindings=[])
41+
job.workflow_resources = (
42+
{"secret_names": workflow_secret_names}
43+
if workflow_secret_names is not None
44+
else {}
45+
)
46+
job.hints = []
47+
if step_secret_names is not None:
48+
job.hints.append({"secret_names": step_secret_names})
49+
job.get_requirement = lambda name: (
50+
({"dockerPull": "docker.io/library/busybox"}, None)
51+
if name == "DockerRequirement"
52+
else (None, None)
53+
)
54+
55+
create_body = job.create_task_msg("/tmp/workspace", "workflow-uuid")
56+
57+
assert create_body["secret_names"] == expected_secret_names

0 commit comments

Comments
 (0)