CALL rule
Runs another rule from inside this one, at the point where the op sits, and then carries on with the next op. The called rule is not a copy and not a sandbox: it works on the same stash as the caller, so everything it writes is waiting for you when it returns.
This is how a set of pipelines share one piece of logic. Put the common part in its own rule, call it
from each pipeline, and fix it in one place. The alternative op, INCLUDE rule, grafts the other
rule's ops into this rule when the rule is built, which flattens both into a single unit. Prefer
CALL when the shared rule is maintained on its own schedule, and INCLUDE when you want the ops
themselves to show up as part of this tree.
Fields¶
Rule To Be Invoked¶
The rule to run, picked from a searchable list. Type to filter by name; the list pages ten at a time and is sorted by name. One rule per op, and the field is required: the designer refuses to apply the op while it is empty.
The list offers active rules, with one exception: type a rule id exactly and an inactive rule with that id shows up too. That exception matters more than it sounds, because the active flag is never checked again afterwards. A rule you deactivate later keeps running for every op that already points at it. Deactivating a rule hides it from the list and stops it being chosen as a pipeline; it does not stop it being called.
Any rule type can be selected. Calling a pipeline rule from outside a job gives it a stash with no job in it, and the first op that expects one fails.
What the form stores is the rule id, but the value is expanded against the stash before the lookup
and the lookup accepts an id or an exact rule name. That only shows up on rules you edited through
the YAML view or imported, where ${target_rule} or a plain rule name in this field both work.
Naming a rule that no longer exists stops the rule with a not-found message.
Name¶
The op's name on its Config tab, not part of this form. It is the label the tree and the job log show
for the call, and it defaults to CALL rule, which tells a reader nothing. Name it after what the
called rule does. The name is expanded against the stash before it reaches the log, so
Call ${check_scope} checks prints the resolved text.
What the called rule sees¶
It sees your stash, the same one, not a copy. There is no parameter list: set the variables it needs before the op, with SET VAR or a code block, and read its results out of the stash afterwards.
One thing changes in the stash at the moment of the call. Global variables are
merged in, and only under names the stash is not already carrying, so a value you computed into
deploy_user survives and a deploy_user the caller never set arrives at its global default. The
environment used is the bl in the stash, or the job's environment when the stash carries no bl,
or the all-environments set when there is neither. Keys added this way stay in the stash after the
call returns, so the caller can read them too.
What comes back¶
Whatever the called rule left in the stash. That is the entire return channel.
Return Key on the op's Options tab does nothing here. You can fill it in, save, and never see a
value appear under that name. Read results out of the stash instead.
Each op of the called rule appears in the job log under its own rule name, so the log tells you which rule produced which line. The Rule Trace panel in the log viewer is a different matter: it draws the job's own rule, and this op holds no children, so nothing appears under the call there. Where the called rule does show up nested is rule profiling, which expands the call into the called rule's ops one level deeper and subtracts their time from the call's own. An op left in a parallel mode is not expanded.
Failure, versions and depth¶
If the called rule fails, the failure travels back out and stops the calling rule with a message that
quotes the id of the rule that failed, not its name, followed by the original error. Trap it like any
other op, with Error Handling on the Options tab or by wrapping the op in
TRY statement and CATCH statement.
The called rule is resolved when the op runs, so it is always the current version that runs. A job pinned to a specific version of its pipeline does not pin the versions of the rules that pipeline calls. If a call has to be reproducible, treat the called rule as part of the pipeline's release surface and change it with the same care.
Nothing stops a rule calling itself, directly or through a chain. There is no depth limit and no loop detection; a cycle keeps calling until the process dies. Use a stash counter and IF var condition THEN if you need recursion at all.
Combining with other ops¶
Set up the inputs with SET VAR immediately above the call, so the contract is visible in the tree rather than buried in the called rule.
Guard the call with IF var condition THEN when the shared rule only applies to some environments or some topic statuses, and keep that decision in the caller. A called rule that starts with its own guard is harder to follow from either side.
Inside a JOB STEP block, a call runs as part of that step, and the called rule's ops take part in the same rollback bookkeeping as anything else in the step.
Examples¶
A shared pre-flight rule used by several pipelines. The caller states what to check, the called rule decides how.
SET VAR check_scope = changesets
SET VAR check_strict = 1
CALL rule Rule To Be Invoked: Pre-flight checks
IF var condition THEN
When: Any
check_errors NOT EMPTY
FAIL Pre-flight failed: ${check_errors}
A notification rule reused from a workflow rule and a pipeline alike. Everything it needs travels in the stash, and nothing comes back.
SET VAR notify_subject = Deployment of ${job_name} finished
SET VAR notify_group = release-managers
CALL rule Rule To Be Invoked: Send release notification