package host import ( "crypto/ed25519" "encoding/base64" "encoding/json" "os" "path/filepath" "strings" "testing" "github.com/carlosframework/platform/internal/hostfloor" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootstate" ) func TestPlan14NamedUnitsWithoutDeploymentRecord(t *testing.T) { if !plan14RootFixture(t) { return } for _, scenario := range []string{"console bootstrap", "console bootstrap invalid capture", "console update", "console crash", "operator update", "forged unit", "changed list", "untrusted list", "missing dropin", "captured target changed"} { t.Run(scenario, func(t *testing.T) { e, rt, u, c, pub := plan14AdoptionFixture(t) if err := e.store.Delete(t.Context(), provision.RecordKey(rt.Account, rt.App, rt.Host)); err != nil { t.Fatal(err) } rt.Account = "" rt.Addr = "127.0.0.1:8090" rt.Health = `{"path":"/healthz","timeout":"2s","interval":"30s","grace":"10s","failures":3}` if err := e.root.reg.Upsert(rt); err != nil { t.Fatal(err) } if err := os.MkdirAll("/var/lib", 0755); err != nil { t.Fatal(err) } if err := rootstate.EnsurePublicDir(release.RootHealthyDir); err != nil { t.Fatal(err) } oldHealth := release.ReadHealthy(e.bake, "bab", "console") for _, b := range oldHealth { if strings.HasPrefix(scenario, "console bootstrap") { continue } if err := release.RecordHealthy(release.RootHealthyDir, "", "console", b); err != nil { t.Fatal(err) } } t.Cleanup(func() { if err := os.RemoveAll(release.RootHealthyDir); err != nil { t.Error(err) } }) e.bake = release.RootHealthyDir s := productionProofBindingSource(e.store, u, u.opt) s.namedPath = filepath.Join(t.TempDir(), "named-units.json") s.unitDirs = []string{filepath.Dir(u.props.FragmentPath)} s.now = c.now c.attempts = productionRootRestartChecks(u, u.opt).attempts c.resolve, c.host, c.rollback, c.prepareRollback, c.dispatch, c.targetFile = s.resolve, s.forHost, s.rollback, s.prepareRollback, s.needsRestart, rootstate.OpenTrustedFile e.root.obs.resolve = s.resolve put := func(path string, body []byte) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, body, 0644); err != nil { t.Fatal(err) } makeProofAncestorsPublic(t, path) } if scenario == "operator update" || scenario == "changed list" || scenario == "untrusted list" { u.props.Name = "operator.service" rt.Unit = u.props.Name raw, err := json.Marshal(struct { Schema int `json:"schema"` Units []namedUnit `json:"units"` }{1, []namedUnit{{Host: rt.Host, App: rt.App, Unit: rt.Unit, Addr: rt.Addr, Health: &provision.Health{Path: "/healthz"}}}}) if err != nil { t.Fatal(err) } put(s.namedPath, raw) } else if scenario != "forged unit" { marker := filepath.Join(u.opt, "system", "guard", "console-ready.json") put(marker, []byte(release.LiveLink(u.opt, rt.Host))) dropin := filepath.Join(s.unitDirs[0], "console.service.d", "90-host-floor.conf") put(dropin, []byte(hostfloor.ConsoleGuardDropIn(release.LiveLink(u.opt, rt.Host)))) u.props.DropInPaths = []string{dropin} u.props.ExecPath = "/opt/carlos/system/guard/carlos" put(u.props.ExecPath, []byte("root guard")) } if strings.HasPrefix(scenario, "console bootstrap") { public, key, err := ed25519.GenerateKey(nil) if err != nil { t.Fatal(err) } t.Setenv("CARLOS_RELEASE_PUBKEY", base64.StdEncoding.EncodeToString(public)) rel := release.SystemRelease{Version: "v1", ReleaseTag: "v1.0.0", MovesProtocol: 1, Artifacts: map[string]release.SystemArtifact{release.AdoptPlatform(): {Key: release.SystemArtifactKey("v1", release.AdoptPlatform()), SHA256: strings.Repeat("a", 64)}}, ConsoleArtifacts: map[string]release.SystemArtifact{release.AdoptPlatform(): {Key: "bootstrap/console/v1/carlos-" + release.AdoptPlatform(), SHA256: oldHealth[0].SHA256}}} if _, err := release.PromoteSystem(t.Context(), e.store, release.RootSigner(key), "router", "canary", rel, release.SystemPromoteOptions{}); err != nil { t.Fatal(err) } wire, _, err := e.store.Get(t.Context(), release.SystemPointerKey("router", "canary")) if err != nil { t.Fatal(err) } put(filepath.Join(u.opt, "system", "guard", "identities", "v1.0.0.json"), wire) } makeProofAncestorsPublic(t, u.props.FragmentPath) makeProofAncestorsPublic(t, u.exe) makeProofAncestorsPublic(t, s.namedPath) if scenario != "forged unit" { if _, err := s.resolve(t.Context(), rt); err != nil { t.Fatalf("baseline: %v", err) } } if scenario == "console bootstrap invalid capture" { put(filepath.Join(u.opt, "system", "guard", "identities", "v1.0.0.json"), []byte(`{"signature":"forged"}`)) if _, err := s.resolve(t.Context(), rt); err != nil { t.Fatal("root live pointer must remain authoritative without installation history", err) } if len(u.effects) != 0 { t.Fatal("invalid capture restarted console") } return } if strings.HasPrefix(scenario, "console bootstrap") { if err := e.root.obs.recordSettled(e.bake)(rt); err != nil { t.Fatal("initial health", err) } } switch scenario { case "console crash": u.crashNew = true case "untrusted list": if err := os.Chmod(s.namedPath, 0666); err != nil { t.Fatal(err) } case "missing dropin": u.props.DropInPaths = nil case "changed list": u.crashNew = true u.afterEffect = func() { raw, err := os.ReadFile(s.namedPath) if err != nil { t.Fatal(err) } put(s.namedPath, append(raw, ' ')) } case "captured target changed": u.afterRestart = func() { put(release.VersionPath(u.opt, "", "v2", "console"), []byte("changed after capture")) } } // Registry metadata cannot select a unit or the readiness inputs. rt.Unit = "forged.service" rt.Addr = "127.0.0.1:1" rt.Health = "" if err := e.root.reg.Upsert(rt); err != nil { t.Fatal(err) } c.ready = func(got registry.Route) func() error { if got.Unit == rt.Unit || got.Addr != "127.0.0.1:8090" || got.Health == "" { return func() error { return os.ErrPermission } } return func() error { return nil } } e.root.obs.ready = c.ready forward := rootRestartRouteWith(e.root.reg, u, u.opt, func(string) string { return "5s" }, e.root.obs.recordSettled(e.bake), c) rollback := rollbackUnitRestart(e.root.reg, u, u.opt, func(string) string { return "5s" }, c) var rollbackErr error err := release.Pass(t.Context(), e.root.reg, e.store, pub, u.opt, forward, release.BakeDir(e.bake), release.WithRestartFailurePropagation(), release.WithRollbackRestart(func(host string) error { rollbackErr = rollback(host); return rollbackErr })) switch scenario { case "console bootstrap", "console update", "operator update": if err != nil { t.Fatal(err) } if len(u.effects) != 1 || u.exe != release.VersionPath(u.opt, "", "v2", "console") { t.Fatal(u.effects, u.exe) } if got := release.ReadHealthy(e.bake, "", "console"); len(got) != 2 { t.Fatal("missing health", got) } if err := forward(rt.Host); err != nil { t.Fatal("explicit named-unit restart", err) } if len(u.effects) != 2 { t.Fatal("missing explicit restart", u.effects) } if got := release.ReadHealthy(e.bake, "", "console"); len(got) != 2 { t.Fatal("explicit restart changed digest history", got) } case "console crash": if err == nil || rollbackErr != nil || len(u.effects) != 3 || u.exe != release.VersionPath(u.opt, "", "v1", "console") { t.Fatal(err, rollbackErr, u.effects, u.exe) } if got := release.ReadHealthy(e.bake, "", "console"); len(got) != 1 { t.Fatal("crash recorded healthy", got) } case "forged unit", "untrusted list", "missing dropin": if err == nil || len(u.effects) != 0 { t.Fatal("forged authority restarted", err, u.effects) } default: if err == nil { t.Fatal("mutation succeeded") } } }) } }