Skip to content

For Each

What it does

Applies a nested list of ordinary conversion rules to each element of a hub array and the corresponding spoke array — the same rule vocabulary, just scoped to one array element at a time, with paths in the nested rules relative to a single element rather than the whole object.

When to use it

Both sides have an array of objects at the same conceptual position, but the shape of each element differs between versions (a field renamed inside each array element, for instance) — forEach lets you describe that per-element mapping once instead of hand-writing index-specific rules.

Depends entirely on the nested rules

forEach itself introduces no lossiness — the losslessness of a forEach rule is exactly the combined losslessness of its nested rules, applied per element. A forEach wrapping only fieldRename rules (as below) is fully lossless in both directions.

Example

Each element of the hub's volumes array renames sizeGB/label to the v1 spoke's size/name:

spec:
  properties:
    volumes:
      type: array
      items:
        type: object
        properties:
          sizeGB:
            type: string
          label:
            type: string
spec:
  properties:
    volumes:
      type: array
      items:
        type: object
        properties:
          size:
            type: string
          name:
            type: string

Rule

- strategy: ForEach
  forEach:
    hubItemsPath: spec.volumes
    spokeItemsPath: spec.volumes
    rules:
      - strategy: FieldRename
        fieldRename:
          hubPath: sizeGB      # relative to one array element
          spokePath: size
      - strategy: FieldRename
        fieldRename:
          hubPath: label
          spokePath: name

Note that the nested rules' hubPath/spokePath (sizeGB, size, label, name) are relative to a single array element — not prefixed with spec.volumes[].

A second forEach is allowed inside the first for arrays-of-arrays. Paths in the inner list are relative to the inner element (a disk, below), not the outer volume:

- strategy: ForEach
  forEach:
    hubItemsPath: spec.volumes
    spokeItemsPath: spec.volumes
    rules:
      - strategy: ForEach
        forEach:
          hubItemsPath: disks
          spokeItemsPath: disks
          rules:
            - strategy: FieldRename
              fieldRename:
                hubPath: sizeGB
                spokePath: size

Objects

spec:
  volumes:
    - sizeGB: "100"
      label: "data"
spec:
  volumes:
    - size: "100"
      name: "data"

Constraints

  • Nesting is capped at depth 2. A forEach may contain another forEach (arrays-of-arrays, common in XR specs). A third nested forEach is a compile/admission error, not a silent truncation — see Limitations. Deeper nesting isn't supported yet because coverage analysis and the CRD's nested-rule list need a recursion bound, and two array levels covers the real migrations we've seen.
  • Strict positional correspondence is required at runtime. The hub and spoke arrays must have the same length and element order. If both item paths are present on the input as arrays of unequal length, conversion fails with a hard runtime error — not a best-effort merge or truncation to the source length. If the destination path is absent, the output is built from the source array alone.