-
Notifications
You must be signed in to change notification settings - Fork 20
feat: RFC 0007/0008 — step-level let bindings, wrap-hook step names, template extensions #230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: mainline
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,6 +102,8 @@ def __init__( | |
| self._openjd_session = Session( | ||
| session_id=self.session_id, | ||
| job_parameter_values=job_parameter_values, | ||
| # RFC 0007 §7.3.1 (EXPR): seeds the Job.Name template variable. | ||
| job_name=str(job.name), | ||
| path_mapping_rules=self._path_mapping_rules, | ||
| callback=self._action_callback, | ||
| retain_working_dir=retain_working_dir, | ||
|
|
@@ -197,7 +199,14 @@ def _sigint_handler(self, signum: int, frame: Optional[FrameType]) -> None: | |
| LOG.info("Interruption signal recieved.") | ||
| self.cancel() | ||
|
|
||
| def run_environment_enters(self, environments: Optional[list[Any]], type: EnvironmentType): | ||
| def run_environment_enters( | ||
| self, | ||
| environments: Optional[list[Any]], | ||
| type: EnvironmentType, | ||
| *, | ||
| extra_let_bindings: Optional[list[str]] = None, | ||
| step_name: Optional[str] = None, | ||
| ): | ||
| """Enter one or more environments in the session.""" | ||
| if environments is None: | ||
| return | ||
|
|
@@ -211,10 +220,43 @@ def run_environment_enters(self, environments: Optional[list[Any]], type: Enviro | |
| env_id = f"{type.name} - {env.name}" | ||
| self._action_ended.clear() | ||
| self._current_action = EnterEnvironmentAction( | ||
| session=self._openjd_session, environment=env, env_id=env_id | ||
| session=self._openjd_session, | ||
| environment=env, | ||
| env_id=env_id, | ||
| # RFC 0007: a step's environments see the step-level `let` | ||
| # bindings. | ||
| extra_let_bindings=extra_let_bindings, | ||
| # RFC 0007 §7.3.1 (EXPR): a step's environments see Step.Name. | ||
| # Only step-environment enters carry a step name. | ||
| step_name=step_name, | ||
| ) | ||
| self._environments_entered.append((type, env_id)) | ||
| self._current_action.run() | ||
| try: | ||
| self._current_action.run() | ||
| except (RuntimeError, ValueError) as exc: | ||
| # Session.enter_environment raises (rather than reporting | ||
| # through the action-status callback) when it rejects the | ||
| # environment up front — e.g. the RFC 0008 "at most one wrap | ||
| # environment" RuntimeError, or a ValueError from the extra | ||
| # `let` bindings — but it can also raise *after* registering | ||
| # the environment (e.g. an environment `variables` expression | ||
| # that fails to evaluate). Only when the session did NOT | ||
| # register the environment may we drop it from our entered | ||
| # list (cleanup must not try to exit it). If the session did | ||
| # register it, it must stay in our list so cleanup exits it — | ||
| # popping it here would skip its onExit and desynchronize us | ||
| # from the session's LIFO exit ordering check, masking the | ||
| # original error with "Must exit Environment X first". | ||
| if env_id not in self._openjd_session.environments_entered: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unguarded This block is otherwise carefully defensive about version skew — The consequence is worse than usual because this is inside the Please confirm |
||
| self._environments_entered.pop() | ||
| LOG.info( | ||
| msg=f"Open Job Description CLI: ERROR entering environment '{env.name}': {exc}", | ||
| extra={"session_id": self.session_id}, | ||
| ) | ||
| self.failed = True | ||
| self._failed_action = self._current_action | ||
| self._current_action = None | ||
| raise LocalSessionFailed(self._failed_action) from exc | ||
| self._action_ended.wait() | ||
| if self.failed: | ||
| self._failed_action = self._current_action | ||
|
|
@@ -348,8 +390,22 @@ def run_step( | |
| if task_parameters is None: | ||
| task_parameters = StepParameterSpaceIterator(space=step.parameterSpace) | ||
|
|
||
| # Enter all the step environments | ||
| self.run_environment_enters(step.stepEnvironments, EnvironmentType.STEP) | ||
| # Enter all the step environments. When the step defines step-level | ||
| # `let` bindings (RFC 0007), its environments are entered with them so | ||
| # their variables and actions can reference them. | ||
| # getattr guard: requires an openjd-model with Step.let on the | ||
| # instantiated Job (openjd-model PR #318+); collapse to plain | ||
| # `step.let` once the version pin floor guarantees it. | ||
| step_let_bindings = getattr(step, "let", None) | ||
| self.run_environment_enters( | ||
| step.stepEnvironments, | ||
| EnvironmentType.STEP, | ||
| extra_let_bindings=step_let_bindings or None, | ||
| # RFC 0007 §7.3.1 (EXPR): Step.Name is available to a step's | ||
| # environments (openjd-rs threads the step's resolved symbol | ||
| # table into enter_environment; this is the CLI counterpart). | ||
| step_name=step.name, | ||
| ) | ||
|
|
||
| try: | ||
| # Run the tasks | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -338,6 +338,7 @@ def _run_local_session( | |
| Creates a Session object and listens for log messages to synchronously end the session. | ||
| """ | ||
|
|
||
| error_message = "Session ended with errors; see Task logs for details" | ||
| try: | ||
| start_seconds = time.perf_counter() | ||
|
|
||
|
|
@@ -367,6 +368,14 @@ def _run_local_session( | |
| except LocalSessionFailed: | ||
| duration = time.perf_counter() - start_seconds | ||
| session = None | ||
| except (RuntimeError, ValueError) as exc: | ||
| # Exceptions raised by openjd.sessions from within session actions | ||
| # (e.g. the RFC 0008 "at most one wrap environment" RuntimeError) | ||
| # rather than reported through the action-status callback. Report | ||
| # them as a clean error result instead of a raw traceback. | ||
| duration = time.perf_counter() - start_seconds | ||
| session = None | ||
| error_message = f"Session ended with errors: {exc}" | ||
|
|
||
| preserved_message: str = "" | ||
| if retain_working_dir and session is not None: | ||
|
|
@@ -377,7 +386,7 @@ def _run_local_session( | |
| if session is None or session.failed: | ||
| return OpenJDRunResult( | ||
| status="error", | ||
| message="Session ended with errors; see Task logs for details" + preserved_message, | ||
| message=error_message + preserved_message, | ||
| job_name=job.name, | ||
| step_name=step_name, | ||
| duration=duration, | ||
|
|
@@ -547,9 +556,17 @@ def do_run(args: Namespace) -> OpenJDCliResult: | |
| return OpenJDCliResult(status="error", message=str(rte)) | ||
|
|
||
| # Create a RevisionExtensions object with the default specification version and enabled extensions | ||
| # We use the default v2023_09 since that's what we're currently supporting | ||
| # We use the default v2023_09 since that's what we're currently supporting. | ||
| # Extension behaviors activate only when declared by the templates being | ||
| # run (the job template and any external environment templates) — the | ||
| # CLI's --extensions list is what the CLI *accepts*, not what is enabled. | ||
| declared_extensions: list[str] = list(the_job.extensions or []) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Direct
Note the asymmetry with |
||
| for env_template in environments or []: | ||
| for ext_name in env_template.extensions or []: | ||
| if ext_name not in declared_extensions: | ||
| declared_extensions.append(ext_name) | ||
| revision_extensions = RevisionExtensions( | ||
| spec_rev=the_job.revision, supported_extensions=extensions | ||
| spec_rev=the_job.revision, supported_extensions=declared_extensions | ||
| ) | ||
|
|
||
| return _run_local_session( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # RFC 0007 §7.3.1 (EXPR): Step.Name is available to a step's environments. | ||
| # The step-level `let` binding references Step.Name, and the step's | ||
| # environment is entered with the binding so its onEnter action can echo it. | ||
| specificationVersion: jobtemplate-2023-09 | ||
| extensions: | ||
| - EXPR | ||
| name: StepNameEnvJob | ||
| steps: | ||
| - name: EchoStepName | ||
| let: | ||
| - bound_name = Step.Name | ||
| stepEnvironments: | ||
| - name: NameEcho | ||
| script: | ||
| actions: | ||
| onEnter: | ||
| command: python | ||
| args: | ||
| - -c | ||
| - print('EnvSaw={{ bound_name }}') | ||
| script: | ||
| actions: | ||
| onRun: | ||
| command: python | ||
| args: | ||
| - -c | ||
| - print('TaskRan') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent backwards-compatibility handling for new
openjd-sessionskeywords.This PR carefully guards
extra_let_bindings— the comment atenter_environment(in_actions.py) states that olderopenjd-sessionsreleases do not accept that keyword, so it is only forwarded when step-levelletbindings exist. Butjob_namehere (andstep_namein_actions.py:55, passed torun_task) is passed unconditionally, andpyproject.tomlstill pinsopenjd-sessions >= 0.10.7, < 0.11(unchanged by this PR).If the RFC-0007/0008 keywords (
job_name,step_name) are not present in the minimum supported sessions version — which is exactly the premise behind guardingextra_let_bindings— thenSession(...)andrun_task(...)will raiseTypeError: unexpected keyword argumentfor any user on that version, breaking everyopenjd runinvocation regardless of whether the template uses these extensions.Either raise the
openjd-sessionslower bound to the first release that acceptsjob_name/step_name, or guard these the same wayextra_let_bindingsis guarded. Whichever is correct, the three new keywords should be handled consistently.