Files
mesh-catalog/modules/mesh-build-image/cmd/mesh-build-image/main.go
T
jschoubben 357967308f BLD11: the three build modules take the version from the ask as MESH_VERSION; version.numbered is no longer heard
The Go compile, the Node tsc/esbuild/npm and the image's buildah bud get the
build argument MESH_VERSION from the version the build ask carries. The late
publish on version.numbered goes (code, tests, the consumes of the Go and
image manifests). The image's label and tag and a program's stamp are open
(question to the lead): the design names no key or symbol.

Builds: design 47 G §349, §370, §413

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 11:00:52 +01:00

115 lines
4.2 KiB
Go

// mesh-build-image: the image build module (novox/hq ADR 0487, ADR 0466 §1, ADR 0469; design 47 R4c-9, issue 1703). A
// tools bundle the node's runtime launches as the module's own account (runs-as). It builds a module's `image`
// artifact from the Dockerfile in the module's own directory with buildah, rootless, in a user namespace of its
// account and with no container runtime socket, and pushes a merged trunk commit's image to the registry from its
// own process once the build has ended. It refuses a step only when buildah cannot be run or the account has no
// subordinate ranges, and its health is then unhealthy. What it says, it says on stderr; stdout is the MCP channel.
package main
import (
"context"
"fmt"
"os"
"os/user"
"strings"
"time"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// The directories the module's manifest places outside its home (ADR 0466 §1): the workspaces, and buildah's image
// store, so nothing of the home is in a build.
const (
WorkDir = "/var/lib/mesh-build-image-work"
StoreDir = "/var/lib/mesh-build-image-store"
)
func logf(format string, a ...any) { fmt.Fprintf(os.Stderr, "[mesh-build-image] "+format+"\n", a...) }
func main() {
ctx := context.Background()
m := &Module{
Node: strings.TrimSpace(os.Getenv("MESH_NODE")),
Steps: stdio.State(seat.StateSteps).Put,
ReadSteps: readOf(stdio.State(seat.StateSteps)),
Pauses: stdio.State(sdkstep.PausesState).Put,
ReadPauses: readOf(stdio.State(sdkstep.PausesState)),
ForgetPause: stdio.State(sdkstep.PausesState).Delete,
WatchPauses: stdio.State(sdkstep.PausesState).Watch,
Ctx: ctx,
Say: logf,
Work: WorkDir,
Store: StoreDir,
}
m.Floor = floorOf(m.Node)
if u, err := user.Current(); err == nil {
m.Home, m.Account, m.UID = u.HomeDir, u.Username, u.Uid
}
waits := stdio.State(MirrorWaitsState)
m.Outcomes = &Outcomes{Waits: stateWaits{waits}, Node: m.Node}
m.ReadWaits = readOf(waits)
m.Settings = m.Home + "/settings.json"
m.Run = StepRunner(m.Home)
// Outside a launch by the runtime — a build, a check — there is no node and no state to write.
if m.Node != "" {
go m.Start(ctx)
}
go startKeeper(ctx)
// Every state it keeps, said on the bus before it serves (ADR 0495 §2; issue 1790).
if err := keepStates(); err != nil {
logf("announcing the states it keeps: %v", err)
os.Exit(1)
}
steps := m.StepTools()
booted := time.Now()
puller := m.Puller(steps, nil)
m.Held = puller
if m.Node != "" {
// A step a previous process left running is ended (issue 1890): one that names another owner, or none and
// started before booted.
go m.endLeftRunning(ctx, m.stepScan(), booted, logf)
}
if m.Node != "" {
// The image seat's outcomes, which a copy waits on (the manifest's consumes), and the copies' waits in
// mirror-waits, where another instance writes an outcome it heard.
go hearOutcomes(ctx, m.Outcomes)
go waits.Follow("", m.Outcomes.Changed)
}
if m.Node != "" {
// The seat's asks, pulled once Serve has the runtime's channel; a pull before it is said and tried again.
go func() { _ = puller.Pull(ctx) }()
}
if err := stdio.Serve("", append(m.Tools(steps), m.SeatTools(puller)...)); err != nil {
logf("%v", err)
os.Exit(1)
}
}
// OutcomeEvents are the image seat's events a copy's end is said by, as the manifest consumes them.
var OutcomeEvents = []string{reg.EventPublished, reg.EventRefused}
// hearOutcomes subscribes to the image seat's published and refused, asking again until the runtime takes each.
func hearOutcomes(ctx context.Context, o *Outcomes) {
for _, event := range OutcomeEvents {
pattern := reg.Seat + "." + event
go func() {
for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) {
err := stdio.Subscribe(pattern, func(e stdio.Envelope) error { return o.Heard(event, e.Body) })
if err == nil {
logf("listening for %s", pattern)
return
}
logf("not hearing %s yet (%v); asking again in %s", pattern, err, wait)
select {
case <-ctx.Done():
return
case <-time.After(wait):
}
}
}()
}
}