mesh/merge-gate head b53b0e7e2cc4 merged into trunk 48dd41db91a6 — pass: builds mesh-build-image → ace, shanks; no bus step; every machine composes with th…
mesh/repo-check head b53b0e7e2cc4 merged into trunk 48dd41db91a6 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
mesh/check failed: unit: modules/gitea.test: ✔ the same merge, head or close announced twice has the same id, and another has another (7.92618ms) | b5…
The look made <store>/xdg only after `buildah --version`, which refuses first when its XDG_RUNTIME_DIR is missing: the directory was never made on ace.
148 lines
5.6 KiB
Go
148 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
|
"git.novox.be/novox/mesh-sdk/go/health"
|
|
)
|
|
|
|
// CheckRound is how often the module looks at itself again, beside the look before every step and at every health look.
|
|
const CheckRound = 10 * time.Minute
|
|
|
|
// Module is the image build module's one running state.
|
|
type Module struct {
|
|
Node string
|
|
// Account and UID are the account the module runs as; Home is its home.
|
|
Account, UID, Home string
|
|
// Steps is the Put of the steps state; ReadSteps reads it back for the read tools.
|
|
Steps buildstep.Put
|
|
ReadSteps StateRead
|
|
// Ctx is the process's context, which a step runs under (the tool call's is cut at 30 s, the step's is not).
|
|
Ctx context.Context
|
|
Say func(format string, a ...any)
|
|
// Run runs the commands of a step (StepRunner); Work holds the workspaces and Store buildah's image store, both
|
|
// outside the account's home (ADR 0466 §1).
|
|
Run forgekey.Runner
|
|
Work, Store string
|
|
// SubUIDFile and SubGIDFile are the subordinate id files; /etc/subuid and /etc/subgid when empty.
|
|
SubUIDFile, SubGIDFile string
|
|
// RegistryFile holds the registry's address, host:port, which the manifest binds from the artifact-store provision.
|
|
RegistryFile string
|
|
// RemoteBase is where repositories are fetched from, "ssh://mesh-forge/" when empty; FetchOptions are the options of
|
|
// the SDK's fetch and merge. A test points them at a local repository (buildstep.AllowFileRemote).
|
|
RemoteBase string
|
|
FetchOptions []buildstep.Option
|
|
// HTTP talks to the registry; a client of its own when nil. RegistryWait and RegistryPause bound the wait for a
|
|
// registry held still (RegistryWait and RegistryFirstPause when zero).
|
|
HTTP *http.Client
|
|
RegistryWait time.Duration
|
|
RegistryPause time.Duration
|
|
// Now is the clock; time.Now when nil.
|
|
Now func() time.Time
|
|
}
|
|
|
|
// Look is what the module found out about its machine.
|
|
type Look struct {
|
|
Buildah string
|
|
UIDs, GIDs int
|
|
Account string
|
|
}
|
|
|
|
// look proves the machine: buildah runs, the account holds ranges, a user namespace can be made. The first thing
|
|
// missing is a *buildstep.Refusal naming it, so a step ends refused and the health says the same.
|
|
func (m *Module) look(ctx context.Context) (Look, error) {
|
|
l := Look{Account: m.Account}
|
|
// The runtime directory comes first: buildah refuses before anything else when its XDG_RUNTIME_DIR is not there
|
|
// (issue 1884), so a look that made it only after `buildah --version` never made it at all.
|
|
if err := m.ensureRuntimeDir(); err != nil {
|
|
return l, &buildstep.Refusal{Text: err.Error()}
|
|
}
|
|
var err error
|
|
if l.Buildah, err = m.buildahVersion(ctx); err != nil {
|
|
return l, &buildstep.Refusal{Text: err.Error()}
|
|
}
|
|
if l.UIDs, l.GIDs, err = m.ranges(); err != nil {
|
|
return l, &buildstep.Refusal{Text: err.Error(), Have: l.Buildah}
|
|
}
|
|
if err := m.userNamespace(ctx); err != nil {
|
|
return l, &buildstep.Refusal{Text: err.Error(), Have: l.Buildah}
|
|
}
|
|
return l, nil
|
|
}
|
|
|
|
// Check is the guard: nil when the machine can build, else the refusal.
|
|
func (m *Module) Check(ctx context.Context) error {
|
|
_, err := m.look(ctx)
|
|
return err
|
|
}
|
|
|
|
// Start looks once, as the module starts, and then every CheckRound until ctx ends; it also removes old workspaces.
|
|
func (m *Module) Start(ctx context.Context) {
|
|
for {
|
|
if err := m.Check(ctx); err != nil && m.Say != nil {
|
|
m.Say("this machine cannot build images: %v", err)
|
|
}
|
|
m.CleanWorkspaces(m.clock())
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(CheckRound):
|
|
}
|
|
}
|
|
}
|
|
|
|
// Health is the module's health (the runtime-health tool, ADR 0366): unhealthy while it cannot build, saying why,
|
|
// healthy it names the buildah installed and the account's ranges (ADR 0487 §2: the ranges are proven here, not declared).
|
|
func (m *Module) Health(ctx context.Context) health.Answer {
|
|
l, err := m.look(ctx)
|
|
if err != nil {
|
|
return health.Unhealthy(err.Error())
|
|
}
|
|
return health.Healthy(fmt.Sprintf("buildah %s installed; %s holds %d subordinate uids and %d gids; a user namespace can be made", l.Buildah, l.Account, l.UIDs, l.GIDs))
|
|
}
|
|
|
|
// Status is what the module reports of itself.
|
|
func (m *Module) Status(ctx context.Context) map[string]any {
|
|
l, err := m.look(ctx)
|
|
out := map[string]any{"node": m.Node, "account": m.Account, "buildah": l.Buildah, "subuids": l.UIDs, "subgids": l.GIDs, "refusing": err != nil}
|
|
if err != nil {
|
|
out["why"] = err.Error()
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Long is a tool that runs a long step (ADR 0469 §2): it answers at once with the step's id, and the step ends on its
|
|
// key in the steps state. The guard runs inside the step, before run, so a machine that cannot build ends every step
|
|
// refused with the cause named.
|
|
func (m *Module) Long(tool string, run func(ctx context.Context, args map[string]any) (any, error)) func(map[string]any) (any, error) {
|
|
return func(args map[string]any) (any, error) {
|
|
id, err := buildstep.Start(m.Ctx, m.Steps, m.Node, tool, func(ctx context.Context) (any, error) {
|
|
if err := m.Check(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return run(ctx, args)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
key, _ := delivery.StepKey(m.Node, id)
|
|
return map[string]any{"step": id, "key": key, "state": delivery.StepRunning}, nil
|
|
}
|
|
}
|
|
|
|
// Short is a tool that answers in its own call; the guard runs before it, and a refusal is the call's error.
|
|
func (m *Module) Short(run func(ctx context.Context, args map[string]any) (any, error)) func(map[string]any) (any, error) {
|
|
return func(args map[string]any) (any, error) {
|
|
if err := m.Check(m.Ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return run(m.Ctx, args)
|
|
}
|
|
}
|