// Package build builds an image from a Dockerfile (RFC 0006): the // base image is resolved through the store, one writable work tree is // created over it, the steps run against that tree, the metadata // instructions go into image.json through store.ApplyChange, and the // result is an ordinary flat werkdock image. // // Everything that depends on the bwrap version lives behind Backend. // The parser, the instructions, COPY, the uid levers, the CLI, and the // commit are the same behind either implementation. package build import ( "werkdock/internal/engine" ) // Backend opens the writable work tree a build's steps run against. // // It is the ONLY part of the build that depends on what bwrap can do. // Two implementations are foreseen (RFC 0006): // // - CopyBackend, the one implemented here, works on the bwrap 0.8 the // Hostsharing webspaces run today: a real full copy of the base // rootfs, bound read-write, committed by renaming the staging // directory. No per-step cache. // - An overlay backend, possible from bwrap 0.9 (Debian 13 brings // 0.11) when bwrap is not installed setuid: `--overlay-src` on the // base, stacked over the upper directories of the steps before it, // `--overlay` on this step's own. Measured to work unprivileged, // and the upper directory is already a layer diff — deletions // appear as character devices 0:0, which is what OCI writes as // `.wh.NAME`. That makes a per-step cache nearly free, which is // why the cache waits for it: without a union mount every cached // step would cost a whole tree copy. // // The copy backend stays after that upgrade — as the fallback for a // bwrap that is too old or setuid, and for single-shot builds, whose // commit is a rename rather than a walk over upper directories. type Backend interface { // Name identifies the backend in build output and in doctor. Name() string // CachesSteps reports whether a step's result can be kept and // reused by a later build. Only an overlay backend can. CachesSteps() bool // Open prepares a work tree over base — an image's rootfs — below // dir, which the caller owns and removes. Open(base, dir string) (Tree, error) } // Tree is one build's writable rootfs. type Tree interface { // Step prepares step n (0-based, in build order). Step(n int) (Step, error) // Commit returns the host path of the finished flat rootfs, which // lies at /rootfs — where the store's staging expects it. // The caller owns it afterwards and renames it into the store. Commit() (rootfs string, err error) // Close releases what the tree holds — mounts, overlay work // directories, staging it did not hand over. It is safe to call // after Commit. Close() error } // Step is how one build step reaches the tree. // // Nothing outside a backend may set a RunSpec's root fields, and // Writable is a host directory rather than a tree: that is what keeps // the boundary where it belongs. An overlay backend returns its own // step's upper directory here, which is where a COPY of a whole file // has to land anyway. It would not be where a deletion lands — in an // upper directory that is a character device 0:0, and mknod is EPERM // in the sandbox (CapEff is 0, measured) — but no instruction of // stages 1 to 3 deletes, and an overlay commit reads whiteouts on the // host, where creating one is not needed. type Step interface { // Root sets the root-of-sandbox fields of a RUN step's spec: the // copy backend sets RootFS and WritableRoot, an overlay backend // the overlay sources and this step's upper directory. Root(spec *engine.RunSpec) // Writable is the host directory this step's own file writes land // in, with the image's paths below it. Writable() string } // Select returns the backend to build with on this host, and the // reason. Today that is always the copy backend; the overlay backend // plugs in here, behind a probe of bwrap's version and of whether it // is installed setuid (bwrap refuses overlay then, per its NEWS). func Select() (Backend, string) { return CopyBackend{}, "copy backend: a full copy of the base rootfs, bound read-write; no per-step cache" }