Skip to content

Commit 3cfc92f

Browse files
committed
fix: Allow an environment to define only onExit
An environment script that defined onExit without onEnter was rejected at template decode with "onEnter is required." This blocked cleanup-only environments, including queue environments whose only work is tearing down state established elsewhere. Restore the one-of rule this validator applied before the strict check was added: an ordinary environment script must define onEnter or onExit, and either action alone is sufficient. An actions object that defines neither is still rejected, and the WRAP_ACTIONS extension gating, the all-or-nothing wrap-hook rule, and the wrapped-variable scope rules are unchanged. The removed check was also the only reason the validator inspected the parsing context on the ordinary-action path, so the behavior no longer differs between template decode and job instantiation. Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>
1 parent 5dfed5b commit 3cfc92f

4 files changed

Lines changed: 55 additions & 18 deletions

File tree

src/openjd/model/v2023_09/_model.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -758,15 +758,6 @@ def _requires_oneof(cls, values: dict[str, Any], info: ValidationInfo) -> dict[s
758758

759759
on_enter = values.get("onEnter")
760760
on_exit = values.get("onExit")
761-
# Base 2023-09 (§3.5) requires onEnter whenever a script is present;
762-
# RFC 0008 relaxes this to "at least one action" when the
763-
# WRAP_ACTIONS extension is declared. The strict base rule is only
764-
# applied at template decode (context present) — job-instantiation
765-
# re-validation has no parsing context, matching the other extension
766-
# gates in this module.
767-
if context is not None and "WRAP_ACTIONS" not in extensions:
768-
if on_enter is None:
769-
raise ValueError("onEnter is required.")
770761
if on_enter is None and on_exit is None:
771762
raise ValueError("Must define one of: onEnter or onExit")
772763
return values

test/openjd/model_v0/v2023_09/test_action.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ class TestEnvironmentActions:
164164
"data",
165165
(
166166
pytest.param({"onEnter": {"command": "foo"}}, id="has onEnter"),
167+
pytest.param({"onExit": {"command": "foo"}}, id="has onExit"),
167168
# For making sure our pre-validator logic is correct
168169
pytest.param(
169170
{
@@ -187,26 +188,35 @@ def test_parse_success(self, data: dict[str, Any]) -> None:
187188
# THEN
188189
# no exception was raised.
189190

190-
def test_parse_onexit_only_with_wrap_actions_extension(self) -> None:
191-
# RFC 0008: with the WRAP_ACTIONS extension declared, an environment
192-
# may define any single action without a standalone onEnter.
191+
@pytest.mark.parametrize(
192+
"supported_extensions",
193+
(
194+
pytest.param([], id="no extensions"),
195+
pytest.param(["EXPR", "WRAP_ACTIONS"], id="EXPR and WRAP_ACTIONS"),
196+
),
197+
)
198+
def test_parse_onexit_only(self, supported_extensions: list[str]) -> None:
199+
# An environment may define onExit without onEnter; either ordinary
200+
# action alone satisfies the one-of requirement, with or without an
201+
# extension being declared.
193202

194203
# GIVEN
195-
context = ModelParsingContext(supported_extensions=["EXPR", "WRAP_ACTIONS"])
204+
context = ModelParsingContext(supported_extensions=supported_extensions)
196205

197206
# WHEN
198-
_parse_model(model=EnvironmentActions, obj={"onExit": {"command": "foo"}}, context=context)
207+
actions = _parse_model(
208+
model=EnvironmentActions, obj={"onExit": {"command": "foo"}}, context=context
209+
)
199210

200211
# THEN
201-
# no exception was raised.
212+
assert actions.onEnter is None
213+
assert actions.onExit is not None
214+
assert actions.onExit.command == "foo"
202215

203216
@pytest.mark.parametrize(
204217
"data",
205218
(
206219
pytest.param({}, id="empty object"),
207-
# §3.5: base 2023-09 requires onEnter whenever a script is
208-
# present (the WRAP_ACTIONS extension relaxes this).
209-
pytest.param({"onExit": {"command": "foo"}}, id="onExit only"),
210220
pytest.param({"onEnter": {"command": "foo"}, "onUnknown": "blah"}, id="unknown field"),
211221
),
212222
)
@@ -219,3 +229,15 @@ def test_parse_fails(self, data: dict[str, Any]) -> None:
219229

220230
# THEN
221231
assert len(excinfo.value.errors()) > 0
232+
233+
def test_parse_fails_no_actions_message(self) -> None:
234+
# An actions object with neither ordinary action must be rejected with
235+
# the one-of message; this is the only remaining constraint on which
236+
# ordinary actions an environment defines.
237+
238+
# WHEN
239+
with pytest.raises(ValidationError) as excinfo:
240+
_parse_model(model=EnvironmentActions, obj={})
241+
242+
# THEN
243+
assert "Must define one of: onEnter or onExit" in str(excinfo.value)

test/openjd/model_v0/v2023_09/test_environment_template.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ class TestEnvironmentTemplate:
2121
{"specificationVersion": "environment-2023-09", "environment": ENVIRONMENT},
2222
id="minimum required",
2323
),
24+
pytest.param(
25+
{
26+
"specificationVersion": "environment-2023-09",
27+
"environment": {
28+
"name": "Foo",
29+
"script": {"actions": {"onExit": {"command": "foo"}}},
30+
},
31+
},
32+
id="script with onExit only",
33+
),
2434
pytest.param(
2535
{
2636
"specificationVersion": "environment-2023-09",

test/openjd/model_v0/v2023_09/test_job_template.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ class TestJobTemplate:
7373
},
7474
id="with least environments",
7575
),
76+
pytest.param(
77+
{
78+
"specificationVersion": "jobtemplate-2023-09",
79+
"name": "Foo",
80+
"steps": [STEP_TEMPLATE],
81+
"jobEnvironments": [
82+
{
83+
"name": "Foo",
84+
"script": {"actions": {"onExit": {"command": "foo"}}},
85+
}
86+
],
87+
},
88+
id="with environment defining onExit only",
89+
),
7690
pytest.param(
7791
{
7892
"specificationVersion": "jobtemplate-2023-09",

0 commit comments

Comments
 (0)