Skip to content

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.

projects with changesets foo bar job environment allowed? yes load the project variables run the nested ops no logged as skipped next project

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 EMPTY before 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