FOR projects with changes DO
Runs the ops nested underneath it once per project that has changesets in the current job. Before each pass it sets the project context and loads that project's variables into the stash, so the ops inside can be written once and work for every project the job touches.
This is the outer loop of almost every deployment rule. Use APPLY PROJECT instead when you already know which single project you mean and only want its variables.
Configuration¶
There is nothing to configure. The Config tab holds the op's Name and an empty data editor, and
every decision the op makes comes from the job. The only settings that affect it are the generic
ones on the Options tab, which behave here as they do on any other op.
Which projects it visits¶
The list comes from the job's changesets, grouped by the project each changeset belongs to. That grouping is built by Load Job Items into Stash, so that op has to run before this one.
If it has not run, or the job has no changesets, the loop records a warning saying no project changes were detected and the body never runs. The rule carries on to the next op. This is a warning and not a failure: the job finishes, carrying the warning marker the monitor shows on the job, so a rule that quietly does nothing at all is the symptom to look for.
The visiting order is undefined. The changesets are collected into a set keyed by project and the loop walks that set, so the order is neither the changeset order nor alphabetical, and the same job re-run can take the projects in a different order. Write the body so it does not care, and put any step that has to happen in a fixed sequence outside the loop.
The environment filter¶
A project can be restricted to a set of environments. When the job's environment is not one of them, the pass is abandoned before the nested ops run and the job log records the project as skipped for that environment.
A project with no environments at all is never filtered out. The filter only starts applying once the project lists one environment, and from that point the job's environment has to appear in the list. Adding the first environment to a project that was deploying everywhere therefore stops it deploying anywhere else.
What the loop puts in the stash¶
Five values change at the start of every pass.
| Variable | Holds |
|---|---|
project |
the project name |
project_mid |
the project's resource identifier |
project_lc |
the project name in lower case |
project_uc |
the project name in upper case |
current_project |
the project resource itself, for ops that take a resource |
Those five are written before the environment filter runs, so a project the filter skips still leaves its name, its identifier and its resource behind in the stash.
On top of those, the project's variables for the job environment are merged in under their own names. Variables defined for all environments come first, and a variable defined for this specific environment overrides the general one.
Read them in a variable field as ${myvariable}, or in a code block from the stash under the same
name.
After the merge, every value in the stash is expanded once more, so a project variable whose value
contains ${another_var} resolves against what is in the stash at that moment rather than staying
literal.
Variables defined by end users¶
Variables created under a project's Deploy area arrive separately, grouped under CUSTOM_VARS:
${CUSTOM_VARS.myvariable}
In a code block, read them as $$stash{CUSTOM_VARS}{myvariable}.
They are held apart because they are user-managed. Each one is typed either Text or Secret, and
nothing else. They do not appear in resource drop-downs or other assisted variable pickers. Copy one
into a plain variable with SET VAR when an op needs to pick it from a
list.
They carry their own environment list, separate from the project's. A variable with no environments set arrives in every job; one that lists environments arrives only when the job runs in one of them.
Values outlive the pass¶
None of what the loop writes is scoped to the pass. When the loop finishes, project and the rest
still hold the last project's values, and every project variable merged along the way is still in
the stash.
Two consequences worth planning around:
- Ops placed after the loop that reference
${project}get whichever project happened to go last, silently. That is rarely what the author meant, and with an undefined order it is not even stable between runs. - A variable defined by the first project and not by the second is still visible during the second
project's pass, holding the first project's value. Define the variable on every project, or test
it with
IS EMPTYbefore use, rather than assuming a missing definition reads as blank.
What you see in the job log¶
One line per project that runs, naming the project and the environment, with the project variables
attached to the entry so you can open it and see exactly what was loaded. The user-managed variables
under CUSTOM_VARS are not part of that attachment. One line per project skipped by the environment
filter. Neither message is configurable.
A failure in any nested op ends the whole loop, not only the current project. The projects that had not been reached yet are never visited. Wrap the body in TRY statement with CATCH statement when one project failing should not stop the rest.
Combining with other ops¶
Load Job Items into Stash goes above it. Nothing works without that.
Load Nature Items also goes above it, and the nature blocks that depend on it go inside, one level deeper, since they read the project context this loop sets.
FOREACH file/item and
Load files/items into stash go inside, where ${project} and the project
variables are available to build paths.
Branch per project with IF var condition THEN on project, or
per environment with IF ANY bl THEN.
For the variables themselves, see Variables.
Examples¶
Deploy each changed project into a path built from its own name and its own variables.
FOR projects with changes DO
-> LOG Message deploying ${project} to ${bl}
-> Ship File Remotely from ${job_dir}/${project_lc}
to ${CUSTOM_VARS.target_dir}
-> Run a Remote Script restart ${project_lc}
Handle one project differently without splitting the rule.
FOR projects with changes DO
-> IF var condition THEN
When: All
project EQUALS myapp (Ignore case)
-> Run a Remote Script extra step for myapp
-> Run a Remote Script the step every project needs