92 lines
3.5 KiB
Go
92 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/osupdate"
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/selfupdate"
|
|
)
|
|
|
|
// R-1285, in the REAL order (measured 2026-10-26 on demo-hp): the box gets agent_update and agent_config_update in one
|
|
// pass; the OLD process installs the bundle (no policy pass in it), then restarts into the new agent, which dwells and
|
|
// commits. The consequence asserted: the NEW agent runs the bundle policy pass exactly once, AFTER its commit — so a
|
|
// BYO host loses our drop-in in the same update. No pass without a commit (no marker, a mismatched marker).
|
|
// RED-PROOF: drop the wireBundlePolicyAfterCommit call in main → no bundle-policy plan after the commit.
|
|
|
|
// orderRunner records, in order, the self-update wrapper's verbs and the os-apply plans' modes.
|
|
type orderRunner struct {
|
|
mu sync.Mutex
|
|
order []string
|
|
}
|
|
|
|
func (r *orderRunner) Run(_ context.Context, name string, args ...string) ([]byte, []byte, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if name == osupdate.WrapperPath && len(args) == 2 && args[0] == "--plan" {
|
|
var p map[string]any
|
|
b, _ := os.ReadFile(args[1])
|
|
_ = json.Unmarshal(b, &p)
|
|
mode, _ := p["mode"].(string)
|
|
r.order = append(r.order, "os-apply:"+mode)
|
|
return []byte(`OSAPPLY-REPORT {"mode":"bundle-policy","bundle_policy":{"install_mode":"byo","removed":["/etc/ssh/sshd_config.d/10-felhom-root-key-only.conf"]}}` + "\n"), nil, nil
|
|
}
|
|
if len(args) >= 1 {
|
|
r.order = append(r.order, filepath.Base(name)+":"+args[0])
|
|
}
|
|
return []byte("ok"), nil, nil
|
|
}
|
|
|
|
func (r *orderRunner) RunStdin(ctx context.Context, _ io.Reader, name string, args ...string) ([]byte, []byte, error) {
|
|
return r.Run(ctx, name, args...)
|
|
}
|
|
|
|
func r1285Start(t *testing.T, marker *selfupdate.PendingMarker, running string) []string {
|
|
t.Helper()
|
|
stateDir := t.TempDir()
|
|
if marker != nil {
|
|
dir := filepath.Join(stateDir, "selfupdate")
|
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, _ := json.Marshal(marker)
|
|
if err := os.WriteFile(filepath.Join(dir, "pending.json"), b, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
r := &orderRunner{}
|
|
lg := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
mgr := selfupdate.NewManager(selfupdate.ManagerConfig{StateDir: stateDir, RunningVersion: running, Dwell: time.Millisecond, Runner: r, Logger: lg})
|
|
leg := &osupdate.Leg{Runner: r, Logger: lg, PlanDir: t.TempDir(), StatePath: filepath.Join(t.TempDir(), "last")}
|
|
exec := osupdate.ConfigUpdateExecutor{Leg: leg}
|
|
wireBundlePolicyAfterCommit(mgr, exec, lg, running)
|
|
mgr.MaybeCommit(context.Background())
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return append([]string(nil), r.order...)
|
|
}
|
|
|
|
func TestR1285_TheNewAgentRunsThePolicyPassAfterItsCommit(t *testing.T) {
|
|
got := r1285Start(t, &selfupdate.PendingMarker{OldVersion: "0.166.0", NewVersion: "0.167.0", SHA256: "ab", AppliedAt: "t"}, "0.167.0")
|
|
if len(got) != 2 || got[0] != "felhom-selfupdate-guarded:commit" || got[1] != "os-apply:bundle-policy" {
|
|
t.Fatalf("the new agent must commit, then run ONE bundle policy pass: %v", got)
|
|
}
|
|
}
|
|
|
|
func TestR1285_NoCommitNoPolicyPass(t *testing.T) {
|
|
if got := r1285Start(t, nil, "0.167.0"); len(got) != 0 {
|
|
t.Fatalf("an ordinary start (no pending update) must run nothing: %v", got)
|
|
}
|
|
got := r1285Start(t, &selfupdate.PendingMarker{OldVersion: "0.166.0", NewVersion: "0.168.0", SHA256: "ab", AppliedAt: "t"}, "0.167.0")
|
|
if len(got) != 0 {
|
|
t.Fatalf("a mismatched marker is not committed, so no pass: %v", got)
|
|
}
|
|
}
|