package host import ( "context" "crypto/ed25519" "os" "path/filepath" "testing" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rollout" ) // Settings spec ยง4.3 through updatesPassOption: the option is built ONCE, // as fastPassOpts builds it, and still reads swap-failures.json at the start // of every pass. A build is failed for a host when the book blocks it or // holds the host down on it; a served build is exempt from Blocks only. func TestUpdatesPassOptionReadsTheBookEveryPass(t *testing.T) { t.Parallel() ctx := context.Background() opt, data := t.TempDir(), t.TempDir() store := release.NewDirStore(t.TempDir()) seed, pubB64, _ := release.GenerateKey() priv, _ := release.ParseSigningKey(seed, "t") pub, _ := release.ParsePublicKey(pubB64, "t") for _, v := range []string{"v1", "v2"} { m := release.Manifest{Schema: 1, App: "km", Version: v, Kind: "binary", Artifacts: map[string]release.Artifact{runtimePlatform(): {}}} if err := release.Ship(ctx, store, "bab", m, map[string][]byte{runtimePlatform(): []byte(v)}); err != nil { t.Fatal(err) } if err := release.Promote(ctx, store, priv, "bab", "km", "canary/x", v); err != nil { t.Fatal(err) } } reg, err := registry.Open(filepath.Join(t.TempDir(), "r.db")) if err != nil { t.Fatal(err) } pinned := release.UpdateSettings{Updates: release.UpdatesAuto, Channel: "canary/x", Pin: "v1"}.Column() add := func(host string, good string) { t.Helper() if err := reg.Upsert(registry.Route{Host: host, Kind: "instance", App: "km", Account: "bab", Channel: "canary/x"}); err != nil { t.Fatal(err) } if err := reg.SetUpdateSettings(host, pinned); err != nil { t.Fatal(err) } if err := reg.SetBuildState(host, release.BuildStateCopy{Known: true}.Column()); err != nil { t.Fatal(err) } if good != "" { if err := reg.SetGoodBuild(host, registry.GoodBuild{}, registry.GoodBuild{Version: good}); err != nil { t.Fatal(err) } } } opts := []release.PassOption{updatesPassOption(data, true, func(context.Context, release.Store, ed25519.PublicKey) (*release.RootUpdateInputs, error) { return nil, nil })} // built once pass := func() { t.Helper() if err := release.PassFilter(ctx, reg, store, pub, opt, nil, nil, opts...); err != nil { t.Fatal(err) } } live := func(host string) string { v, _ := release.LiveVersion(opt, host) return v } add("first.example.com", "") pass() // no book yet: the pin installs if v := live("first.example.com"); v != "v1" { t.Fatalf("first live %q, want v1 with no failure records", v) } // The agent records v1 failed after the option was built. book := `{"schema":1,"records":[{"account":"bab","app":"km","build":"v1","host":"first.example.com", "cause":"health","mode":"canary","reason":"did not come up", "held_down":["held.example.com"],"retried":["retried.example.com"]}]}` if err := os.WriteFile(filepath.Join(data, rollout.FailureFile), []byte(book), 0o644); err != nil { t.Fatal(err) } add("blocked.example.com", "") // Blocks: not served, not retried add("served.example.com", "v1") // served: Blocks exempts, not held down add("held.example.com", "v1") // served, but held down on v1: HeldDown add("retried.example.com", "") // a host retry cleared it pass() for host, want := range map[string]string{ "blocked.example.com": "", "served.example.com": "v1", "held.example.com": "", "retried.example.com": "v1", } { if v := live(host); v != want { t.Fatalf("%s live %q, want %q", host, v, want) } } for _, host := range []string{"blocked.example.com", "held.example.com"} { c, err := release.ReadConsumed(opt, host) if err != nil || c == nil || c.Reason != string(release.ReasonFailed) || c.Detail != "v1" { t.Fatalf("%s consumed = %+v, %v; want reason failed naming v1", host, c, err) } } }