You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
atmos scaffold's spec.files[].when: can only gate a fixed, enumerable set of files declared up front, it can skip a file, but it can't multiply one. That leaves a gap: there's no way to generate one file per item in an unbounded or structured answer (e.g. "one stacks/<env>.yaml per environment the user typed," or "one file per region × environment in a nested answer").
This gap is already defined in the current PRD for scaffold 👉 see here.
I'd like to take a stab at this, so I put together a proposal for a spec.files[].for_each: construct that closes this gap, staying entirely inside the existing spec.files[] interface. What it would look like:
for_each iterates a list, a map, or (only when needed) a delimited free-text string — and it chains: a later step can iterate something nested inside an earlier step's bound item, so one entry can express a cross-product like "one file per environment within each region."
Didn't exactly knew where to put it, so I forked off and made some adjustments to the scaffold PRD to serve as a proposal:
FWIW, for truly complex shapes where you'd have a matrix of regions and environments (potentially not even a full matrix if you only want multi-region on some environments but not all), we are already piping through those shapes by just baking a .atmos/scaffold.yaml prior to kicking off the scaffold command. Not ideal, but works flawlessly.
I have a working version of the for_each implementation already to just verify if it was even at all feasible, but I would love thoughts before I sink more time into a final implementation, especially on:
whether spec.files[] is the right home for this vs. a separate top-level construct
the in: convention (answers.<path> for the first step, unprefixed for a chained step referencing an earlier binding)
matrix: maps an axis name to a list of values. Atmos iterates over every combination of the axes. Each combination binds to .matrix.<axis>. Users already know this pattern from workflows.
for_each adds new terms: as:, in:, split:, and chaining across steps. This looks like a loop. A loop invites more loop features: nested loops, conditions inside the loop, computed sources. We want to avoid this.
matrix: has a fixed shape. It iterates over a product of known axes. It has no named variable to chain. It has no place for an if. It cannot grow into a general loop.
Changes to the shipped matrix step
Axis values must come from user answers, not only fixed lists. We resolve this in the template string, with Sprig's splitList function. We do not add a split: key.
This generates stacks/linux.yaml only. It skips the darwin combination.
Dependent values: use when:, not chaining
Some cases need dependent values. Example: each region has a different list of valid environments.
Atmos does not build the full matrix as one list first. Atmos iterates over each combination one at a time. For each combination, when: runs in the context of that combination — the current region and env are already bound. If when: returns false, Atmos skips that file and moves to the next combination.
This needs one change to pkg/condition: add a Matrix field to condition.Context. Add a matrix variable to the CEL environment. Set this field fresh for each combination, not once for the whole run.
when: uses CEL, and we already use CEL
when: is not custom syntax. It is CEL (Common Expression Language). Atmos already uses CEL for hook events, custom command steps, and scaffold fields and files. Adding matrix.* to when: does not add a new language. It adds one variable to a system we already use.
CEL is a standard choice outside Atmos too. Kubernetes uses it for ValidatingAdmissionPolicy. Envoy uses it for RBAC and routing rules. Firebase uses it for security rules. CEL has no loops. It has no unbounded recursion. It has no custom functions. Every CEL expression ends.
We want the same limits for matrix:. A bounded expression language for conditions, plus a bounded iteration operation, keeps the config declarative. for_each breaks that pattern.
Implementation note
This design needs one change to pkg/condition: add a Matrix field to condition.Context, add a matrix variable to the CEL environment, and set it fresh per combination. Whoever picks up this work should make that change as part of it. celMentionsIdentifier already checks for identifier use in CEL expressions and can extend to require a matrix: block whenever when: uses matrix.*.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Hey folks 👋
atmos scaffold'sspec.files[].when:can only gate a fixed, enumerable set of files declared up front, it can skip a file, but it can't multiply one. That leaves a gap: there's no way to generate one file per item in an unbounded or structured answer (e.g. "onestacks/<env>.yamlper environment the user typed," or "one file per region × environment in a nested answer").This gap is already defined in the current PRD for scaffold 👉 see here.
I'd like to take a stab at this, so I put together a proposal for a
spec.files[].for_each:construct that closes this gap, staying entirely inside the existingspec.files[]interface. What it would look like:for_eachiterates a list, a map, or (only when needed) a delimited free-text string — and it chains: a later step can iterate something nested inside an earlier step's bound item, so one entry can express a cross-product like "one file per environment within each region."Didn't exactly knew where to put it, so I forked off and made some adjustments to the scaffold PRD to serve as a proposal:
📄
docs/prd/atmos-scaffold.md— see "Dynamic File Generation (for_each) — Proposal"🔀 Diff against
cloudposse/atmos:mainFWIW, for truly complex shapes where you'd have a matrix of regions and environments (potentially not even a full matrix if you only want multi-region on some environments but not all), we are already piping through those shapes by just baking a
.atmos/scaffold.yamlprior to kicking off thescaffoldcommand. Not ideal, but works flawlessly.I have a working version of the
for_eachimplementation already to just verify if it was even at all feasible, but I would love thoughts before I sink more time into a final implementation, especially on:spec.files[]is the right home for this vs. a separate top-level constructin:convention (answers.<path>for the first step, unprefixed for a chained step referencing an earlier binding)If the direction looks good, happy to work on it!
All reactions