Bulk Runs
A single loom run executes one module with one set of parameters. But operational work rarely comes one at a time: you onboard ten services, roll a change across every environment, or wire up a whole team's repositories in one sitting.
There are three ways to run the same module in bulk. All of the examples below reuse this child module:
onboard-service/
├── loom.yaml # declares serviceName + namespace params
└── templates/...1. YAML Wrapper with Child Modules
Module composition already gives you bulk for free: a wrapper module lists the same module as a child once per item, each entry with its own params. Children execute in declaration order.
# bulk-onboard/loom.yaml
apiVersion: loom.rickliujh.github.io/v1beta1
kind: Loom
metadata:
name: bulk-onboard
spec:
modules:
- name: onboard-payments
source: ../onboard-service
params: { serviceName: payments, namespace: fintech }
- name: onboard-billing
source: ../onboard-service
params: { serviceName: billing, namespace: fintech }
- name: onboard-auth
source: ../onboard-service
params: { serviceName: auth, namespace: platform }loom run ./bulk-onboardBest for: small, stable batches where the item list itself should be reviewable in Git. The wrapper is plain declarative YAML — anyone can read exactly what will run.
Trade-off: N items means N hand-written entries. When the list grows or changes often, move to Jsonnet.
2. Jsonnet Wrapper
The same wrapper written as loom.jsonnet generates the child entries from a list, so the item list is data and the entry shape is written once:
// bulk-onboard/loom.jsonnet
local services = [
{ name: 'payments', namespace: 'fintech' },
{ name: 'billing', namespace: 'fintech' },
{ name: 'auth', namespace: 'platform' },
];
{
apiVersion: 'loom.rickliujh.github.io/v1beta1',
kind: 'Loom',
metadata: { name: 'bulk-onboard' },
spec: {
modules: [
{
name: 'onboard-' + svc.name,
source: '../onboard-service',
params: { serviceName: svc.name, namespace: svc.namespace },
}
for svc in services
],
},
}loom run ./bulk-onboardBest for: batches with more than a handful of items, or where the list changes regularly. Adding an item is a one-line diff. Shared logic can live in .libsonnet files next to the config.
You don't have to write the wrapper by hand — loom bulk scaffolds it from the module's declared parameters:
loom bulk ../onboard-service -o ./bulk-onboard --items items.yaml --name-param serviceNameTrade-off: the config is evaluated before parameter resolution, so Jsonnet cannot see CLI params — the item list lives in the file (or an imported one), not on the command line.
3. Shell Loop
When the item list is dynamic — produced by a script, a service catalog query, a CSV export — loop over loom run itself:
for svc in payments billing auth; do
loom run ./onboard-service -p serviceName="$svc" -p namespace=fintech
doneOr keep one params file per item and iterate:
for f in params/*.yaml; do
loom run ./onboard-service --params-file "$f"
doneIndependent runs can be parallelized:
printf '%s\n' payments billing auth |
xargs -P 4 -I {} loom run ./onboard-service -p serviceName={}Best for: ad-hoc batches, dynamic item lists, and parallel execution.
Trade-off: each iteration is an independent process — there is no shared clone, so this always produces one branch/PR per item, and failure handling is up to the shell (a failed item doesn't stop the others under xargs).
One PR per Item, or One PR for the Batch?
With the wrapper approaches (1 and 2), the child module's spec.target decides how the batch ships:
- Child has its own
spec.target→ every entry clones, branches, and opens a pull request independently. Ten items, ten PRs. Pass--summarytoloom runto get all their URLs as a list at the end of the run. - Child has no
spec.target→ children fall back to the parent's target directory. Puttarget,commitPush, andpron the wrapper, and the whole batch lands in one clone and ships as a single commit and PR:
# bulk-onboard/loom.yaml — single PR for the whole batch
spec:
target:
url: "https://github.com/myorg/gitops-repo.git"
featureBranch: "loom/bulk-onboard"
modules:
- name: onboard-payments
source: ../onboard-service # child has no target spec
params: { serviceName: payments, namespace: fintech }
# ...more entries...
operations:
- name: commit
commitPush:
message: "feat: onboard payments, billing, auth"
- name: open-pr
pr:
provider: github
title: "Bulk onboard services"
tokenEnv: GITHUB_TOKENA shell loop (3) cannot do this — each run is isolated, so batching into one PR requires a wrapper module.
Previewing a Batch
All three approaches work with the usual preview modes:
loom run ./bulk-onboard --dry-run # log what every entry would do
loom diff ./bulk-onboard --quick # show rendered file diffs (no execution)
loom diff ./bulk-onboard # full diff of every target's real cloneIn --local-run mode, each child with its own target clones into a numbered subdirectory (00-, 01-, …) under --target-path, in execution order — see Local Run.