hub v0.132.0: the System page (versions + OS updates with the ring/switch/approve buttons, R-852), the Hosts Proxmox/kernel column, the operator-approved Docker engine release (2 healthy ring-0 nights), the crash-guard events (R-851); evidence audits/os-docker-crash-2026-10-04
gates / gates (push) Successful in 30s
gates / gates (push) Successful in 30s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package osupdates
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var engineSet = []Package{{Name: "docker-ce", Version: "5:29.8.2-1~debian.13~trixie", Origin: "Docker"},
|
||||
{Name: "containerd.io", Version: "2.3.6-1~debian.13~trixie", Origin: "Docker"}}
|
||||
|
||||
func (f *fix) dockerNight(t *testing.T, host string, healthy bool) {
|
||||
t.Helper()
|
||||
out := "nothing"
|
||||
if !healthy {
|
||||
out = "health_failed"
|
||||
}
|
||||
f.ingest(t, host, Report{Layer: LayerDocker, Trigger: "night", Mode: "apply", Outcome: out, Healthy: healthy,
|
||||
Installed: append([]Package{pk("libc6", "x")}, engineSet...)})
|
||||
}
|
||||
|
||||
// The Docker set is NEVER approved automatically (`11` §5.8: the operator approves it). Red-proof: add LayerDocker to
|
||||
// Layers (the auto-approved list) and this fails.
|
||||
func TestDocker_NeverAutoApproved(t *testing.T) {
|
||||
f := newFix(t)
|
||||
for i := 0; i < 3; i++ {
|
||||
f.dockerNight(t, "hp", true)
|
||||
f.dockerNight(t, "n100", true)
|
||||
f.now = f.now.Add(25 * time.Hour)
|
||||
f.s.Evaluate()
|
||||
}
|
||||
if rel, _ := f.s.Store.LatestOSRelease(LayerDocker); rel != nil {
|
||||
t.Fatalf("a Docker set was auto-approved: %+v", rel)
|
||||
}
|
||||
}
|
||||
|
||||
// The operator's button works only after every ring-0 box ran the set 2 healthy nights; an unhealthy step blocks it.
|
||||
// The candidate is the six engine packages only. Red-proof: compare nights against 1 instead of DockerNights and the
|
||||
// one-night step approves.
|
||||
func TestDocker_ApproveNeedsTwoHealthyNightsOnEveryRing0Box(t *testing.T) {
|
||||
f := newFix(t)
|
||||
f.dockerNight(t, "hp", true)
|
||||
f.dockerNight(t, "n100", true)
|
||||
if _, err := f.s.ApproveDocker(); err == nil || !strings.Contains(err.Error(), "1 of 2") {
|
||||
t.Fatalf("approved after one night: %v", err)
|
||||
}
|
||||
f.now = f.now.Add(24 * time.Hour)
|
||||
f.dockerNight(t, "hp", true)
|
||||
if _, err := f.s.ApproveDocker(); err == nil {
|
||||
t.Fatal("approved while n100 had only one night")
|
||||
}
|
||||
f.dockerNight(t, "n100", true)
|
||||
id, err := f.s.ApproveDocker()
|
||||
if err != nil || !strings.HasPrefix(id, "os-docker-") {
|
||||
t.Fatalf("%q %v", id, err)
|
||||
}
|
||||
rel, _ := f.s.Store.LatestOSRelease(LayerDocker)
|
||||
if rel.ApprovedBy != "operator" || !strings.Contains(rel.PackagesJSON, "docker-ce") || strings.Contains(rel.PackagesJSON, "libc6") {
|
||||
t.Fatalf("release %+v", rel)
|
||||
}
|
||||
if len(f.bumps) != 0 {
|
||||
t.Fatalf("a Docker approval must nudge no box (ring 1 takes it by a signed job): %v", f.bumps)
|
||||
}
|
||||
if b := f.s.DesiredBlock("cust1"); b.Release != nil || b.HostRelease != nil {
|
||||
t.Fatalf("the Docker set leaked into the desired block: %+v", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocker_UnhealthyStepBlocksTheButton(t *testing.T) {
|
||||
f := newFix(t)
|
||||
f.dockerNight(t, "hp", true)
|
||||
f.dockerNight(t, "n100", true)
|
||||
f.now = f.now.Add(24 * time.Hour)
|
||||
f.dockerNight(t, "hp", false)
|
||||
f.dockerNight(t, "n100", true)
|
||||
if _, err := f.s.ApproveDocker(); err == nil || !strings.Contains(err.Error(), "health_failed") {
|
||||
t.Fatalf("an unhealthy Docker step did not block: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user