The provisioner's client (ADR 0349 §4) fills a module's templates and needs the node-engine to write them: it must not hold the right to write the machine's files itself. A second local socket accepts a write only from the account the declaration names as the provisioner client's (SO_PEERCRED, same namespaces), only for a clean path the declaration names as filled, under the apply's own locks, and writes it through the apply as the declared file would be, firing restart-on, reload-on and container reads. Issue 668. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
2.8 KiB
Go
75 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"errors"
|
|
"path/filepath"
|
|
|
|
"github.com/novox/mesh-host/internal/apply"
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
"github.com/novox/mesh-host/internal/filling"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
"github.com/novox/mesh-host/internal/system"
|
|
)
|
|
|
|
// serveFilling writes filled files on the provisioner client's request for as long as this engine serves (novox/hq
|
|
// ADR 0349 §4). A socket that cannot be served is said once, and this engine goes on applying.
|
|
func serveFilling(ctx context.Context, opts options, signer ed25519.PublicKey, say func(string)) {
|
|
if err := filling.Serve(ctx, filling.Socket, fillingEngine(opts, signer, say), say); err != nil {
|
|
say("filled files are not written on request on this machine: " + err.Error())
|
|
}
|
|
}
|
|
|
|
// fillingEngine is the write of a filled file as this engine does it: the declaration it keeps, verified, read under
|
|
// the apply's lock — the same two locks every apply takes, the process's and the one beside the state — and the file
|
|
// written through the apply (apply.WriteFilled), its outcome recorded in the node's state as an apply's is.
|
|
func fillingEngine(opts options, signer ed25519.PublicKey, say func(string)) *filling.Engine {
|
|
return &filling.Engine{
|
|
Declared: func() (*declaration.Declaration, error) {
|
|
raw, err := store.LoadDeclared(store.DeclaredPath(opts.state), signer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return declaration.Parse(raw)
|
|
},
|
|
UIDOf: filling.UIDOf,
|
|
Lock: func() (func(), error) {
|
|
applying.Lock()
|
|
unlock, err := store.Lock(opts.state, nil)
|
|
if err != nil {
|
|
applying.Unlock()
|
|
return nil, err
|
|
}
|
|
return func() { unlock(); applying.Unlock() }, nil
|
|
},
|
|
Place: func(ctx context.Context, d *declaration.Declaration, path string, content []byte) (string, string, error) {
|
|
built, err := system.For(builtFor)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
known, err := store.Load(opts.state)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
report, updated, writeErr := apply.WriteFilled(ctx, built, d, known, path, content, apply.ExecRunner,
|
|
announceOr(say), apply.KeepIn(filepath.Dir(opts.state)), apply.WindowsIn(filepath.Dir(opts.state)))
|
|
// Saved whichever way it went, as an apply's: what was written is on the machine either way.
|
|
if err := store.Save(opts.state, updated); err != nil {
|
|
return "", "", errors.Join(writeErr, errors.New("written, and the node's state could not be saved: "+
|
|
err.Error()))
|
|
}
|
|
action, digest := "", ""
|
|
for _, o := range report.Outcomes {
|
|
if o.Target == path && o.Type == string(declaration.TypeFile) {
|
|
action = o.Action
|
|
}
|
|
}
|
|
if rec, ok := updated.At(string(declaration.TypeFile), path); ok {
|
|
digest = rec.Wrote
|
|
}
|
|
return action, digest, writeErr
|
|
},
|
|
}
|
|
}
|