Skip to content

Record the units a layer carries, so a child can subtract them - #8

Merged
marcos-mendez merged 1 commit into
19.xfrom
feat/layer-units
Sep 27, 2026
Merged

marcos-mendez merged 1 commit into
19.xfrom
feat/layer-units

Conversation

@marcos-mendez

Copy link
Copy Markdown
Collaborator

Prerequisite of Keel decision 0010: nothing migrates out of the shared tree until bt-layer records units in the layer manifest.

A component handed to fab as a unit (a directory under UNIT_DIRS with a plan, an overlay, a conf script and a removelist) appeared in no manifest field. A child layer computes what to apply by subtracting what the parent recorded, so it could not subtract the component and would re-apply the overlay and re-run the conf script. conf/mysql does ln -s /etc/init.d/mysql under bash -e and fails with "File exists" the second time, so the second layer of the stack did not build at all. That is the LAMP case.

The two fields

Key Meaning
units every component the finished layer carries as a unit, name@version sorted by name, or none
build_units the units this build applied: default without a parent, else the difference, or none

units is cumulative, the parent's record merged with this build's own. The common_* fields are cumulative too, but only because a child recipe includes its parent's makefile fragments; here it is kept so deliberately, because a child that carries no database content of its own still sits on the database and its own child has to know that.

The subtraction rule

build_units is the recipe's own units minus the parent's, matched by name. Same name and same version means the parent already applied it, so it is skipped. Same name at a different version fails the build: a conf script runs once, so there is no move from one version of a component to another inside a child layer, and the parent is the thing to rebuild.

Versions

The first of three sources that answers: a version file (the pin an assembly step writes); the unit's own git HEAD, but only when the directory is the top of that work tree, so the commit of an enclosing product repository is never passed off as the component's; otherwise a digest of the directory content, so an assembled or scratch unit still has an identity.

Compatibility

The applied set reaches fab as UNITS, added in Keel-Linux/fab#4. A parent manifest written before these fields existed reads as none through the new layer_manifest_opt. keel validates a manifest by its required keys and round-trips the rest, so both fields pass keel verify and keel pull untouched, and manifests without them still validate; checked against keel/layers/manifest.py on this host.

Test plan

  • tests/layer: ten new functions and six builds (a rootfs layer with a unit, a child composing the same unit, a child adding one, a child with none of its own, a version conflict, a conf fab would skip, and a parent manifest older than the fields).
  • tests/coverage.sh 99: bin/layer-lib 331 of 332, bt-layer 110 of 111, both 99 percent; the other four files 100 percent. Gate 99.
  • keel loads, validates and byte-identically round-trips a manifest carrying both fields, and still validates one without them.
  • A real mariadb component built as a unit-composed layer with a child on top, reported in the decision note.

A component can be handed to fab as a unit: a directory under the product's
UNIT_DIRS with a plan, an overlay, a conf script and a removelist. fab
resolves and applies it, and a layer built that way is the same layer, which
the Keel decision 0010 experiment measured. bt-layer did not know units
exist, and that is what stopped a component from moving out of the shared
tree.

A child layer works out what it has to do by subtracting what the parent
recorded. A unit appeared in no manifest field, so a child could not subtract
a component its parent had already applied, and would apply the overlay again
and re-run the conf script. conf/mysql does ln -s /etc/init.d/mysql under
bash -e and fails with "File exists" the second time, so the second layer of
a stack simply did not build. That is the LAMP case, not a distant one.

Two fields, alongside the two pairs that are already there:

  units         every component the finished layer carries as a unit, as
                name@version sorted by name, or none
  build_units   the units this build applied: default without a parent, else
                the difference, or none

units is cumulative: the parent's record merged with this build's own. The
common_* fields are cumulative too, but only because a child recipe includes
its parent's makefile fragments; here bt-layer keeps it so on purpose,
because a child that carries no database content of its own still sits on the
database and its own child has to know that.

A version comes from the first of three sources that answers: a version file,
the pin an assembly step writes; the unit's own git HEAD, but only when the
directory is the top of that work tree, so the commit of an enclosing product
repository is never passed off as the component's; otherwise a digest of the
directory's content, so a scratch or assembled unit still has an identity and
two different contents never record the same version.

A unit the parent applied at another version fails the build instead of being
applied. A conf script runs once, so there is no move from one version of a
component to another inside a child layer, and the parent is the thing to
rebuild. bt-layer also refuses a unit directory carrying none of the four
inputs, and one whose conf is not executable, which fab skips without a word.

The applied set is passed to fab as UNITS, the variable keel-linux/fab pull
request 4 added. A parent manifest written before these fields existed reads
as none through the new layer_manifest_opt, so older layers stay usable as
parents; keel reads a manifest by its required keys and round-trips the rest,
so the two fields need no change there.

tests/layer covers the ten new functions and six builds: a rootfs layer with
a unit, a child that composes the same unit, a child that adds one, a child
with none of its own, a version conflict and a conf fab would skip. Coverage
99 percent (layer-lib 331 of 332, bt-layer 110 of 111), gate 99.
@marcos-mendez
marcos-mendez merged commit dd0dcb5 into 19.x Sep 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant