controller v0.269.0: whole restore from the second drive; crash loops stopped; exact image digests; steps judged by their own .felhom.yml (decisions 26-28, R-661 R-666 R-667 R-668 R-664 R-665 R-662, 09 6.4 part 6)
gates / gates (push) Successful in 27s
gates / gates (push) Successful in 27s
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,80 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var dA = "sha256:" + strings.Repeat("a", 64)
|
||||
var dB = "sha256:" + strings.Repeat("b", 64)
|
||||
|
||||
func TestDigest_RenderAndStrip(t *testing.T) {
|
||||
in := "# header\nservices:\n web:\n image: redis:7-alpine # pinned\n restart: unless-stopped\n db:\n image: \"mariadb:11.8\"\nvolumes:\n x:\n"
|
||||
out := string(renderDigests([]byte(in), map[string]string{"web": dA, "db": "not-a-digest"}))
|
||||
if !strings.Contains(out, " image: redis:7-alpine@"+dA+" # pinned") {
|
||||
t.Fatalf("web not rendered:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, " image: \"mariadb:11.8\"") {
|
||||
t.Fatalf("an invalid digest must leave the line alone:\n%s", out)
|
||||
}
|
||||
again := string(renderDigests([]byte(out), map[string]string{"web": dB}))
|
||||
if strings.Count(again, "@sha256:") != 1 || !strings.Contains(again, "redis:7-alpine@"+dB) {
|
||||
t.Fatalf("re-render must replace, not stack, the digest:\n%s", again)
|
||||
}
|
||||
imgs, err := parseComposeImagesBytes([]byte(again))
|
||||
if err != nil || imgs["web"] != "redis:7-alpine" {
|
||||
t.Fatalf("the parsed ref must be digest-free: %v %v", imgs, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDigest_FloatingTagBehindOnlyForANewerTestedDigest — the badge rule, every arm.
|
||||
//
|
||||
// COMPANION RED-PROOF (REPORT): make digestBehind return false — the "newer test" case then reads
|
||||
// Current and this test fails.
|
||||
func TestDigest_FloatingTagBehindOnlyForANewerTestedDigest(t *testing.T) {
|
||||
installAt := time.Date(2026, 9, 20, 0, 0, 0, 0, time.UTC)
|
||||
mk := func(instDigest, instAt string, cat map[string]string, testedAt time.Time) Stack {
|
||||
return Stack{Deployed: true, CatalogImages: map[string]string{"web": "redis:7-alpine"},
|
||||
CatalogDigests: cat, CatalogTestedAt: testedAt,
|
||||
AppConfig: &AppConfig{InstalledImages: map[string]InstalledImage{"web": {Ref: "redis:7-alpine", Digest: instDigest, At: instAt}}}}
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
s Stack
|
||||
want UpdateOrder
|
||||
}{
|
||||
{"same digest", mk(dA, installAt.Format(time.RFC3339), map[string]string{"web": dA}, installAt.Add(time.Hour)), UpdateOrderCurrent},
|
||||
{"newer tested digest", mk(dA, installAt.Format(time.RFC3339), map[string]string{"web": dB}, installAt.Add(time.Hour)), UpdateOrderBehind},
|
||||
{"test older than the install", mk(dA, installAt.Format(time.RFC3339), map[string]string{"web": dB}, installAt.Add(-time.Hour)), UpdateOrderCurrent},
|
||||
{"install time unknown", mk(dA, "", map[string]string{"web": dB}, installAt.Add(time.Hour)), UpdateOrderCurrent},
|
||||
{"installed digest unknown", mk("", installAt.Format(time.RFC3339), map[string]string{"web": dB}, installAt.Add(time.Hour)), UpdateOrderCurrent},
|
||||
{"no tested digest", mk(dA, installAt.Format(time.RFC3339), nil, time.Time{}), UpdateOrderCurrent},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := CatalogOrder(c.s); got != c.want {
|
||||
t.Errorf("%s: %v, want %v", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The update runs EXACTLY the tested image: the pinned step's compose carries the entry's digest, while
|
||||
// the pin itself stays a plain ref.
|
||||
func TestDigest_TheUpdateRendersTheStepsTestedDigest(t *testing.T) {
|
||||
m, dir, _, _, _ := ladderManager(t, true)
|
||||
catDir := filepath.Dir(m.CatalogTemplatePath("nextcloud", "docker-compose.yml"))
|
||||
// the ladder fixture's digests are sha256:aaaa…
|
||||
if err := m.StartGuardedUpdate("nextcloud"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
waitUpdateDone(t, m, "nextcloud")
|
||||
body := fileBody(t, ComposePathIn(dir))
|
||||
if !strings.Contains(body, "image: "+ladderB+"@"+dA) {
|
||||
t.Fatalf("the live compose does not pin the tested digest:\n%s", body)
|
||||
}
|
||||
if pinOf(t, dir) != ladderB {
|
||||
t.Fatalf("the pin must stay a plain ref, got %q", pinOf(t, dir))
|
||||
}
|
||||
_ = catDir
|
||||
}
|
||||
Reference in New Issue
Block a user