//go:build linux package host import ( "bytes" "context" "errors" "net" "os" "path/filepath" "runtime" "testing" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/release" ) func TestPlan14GenerationRelocationDefaultMaterializer(t *testing.T) { for _, mode := range []string{"fresh", "staged", "damaged", "already-live", "claims-off"} { t.Run(mode, func(t *testing.T) { f := newInstallFixture(t, nil) f.cfg.materialize = nil f.cfg.verifyLive = nil f.cfg.ReleasePub = hsPub f.cfg.Ratchet = hsRatchet(t) platform := runtime.GOOS + "-" + runtime.GOARCH artifact := []byte("signed relocation artifact") if err := release.Ship(t.Context(), f.claims, "acct", release.Manifest{App: "app", Version: "v1", Kind: "binary", Artifacts: map[string]release.Artifact{platform: {}}}, map[string][]byte{platform: artifact}); err != nil { t.Fatal(err) } if err := release.Promote(t.Context(), f.claims, hsPriv, "acct", "app", "canary/x", "v1"); err != nil { t.Fatal(err) } path := release.VersionPath(f.r.OptDir, "acct", "v1", "app") if err := os.Remove(path); err != nil { t.Fatal(err) } if mode == "staged" || mode == "damaged" || mode == "already-live" { // This positive control verifies the exact signed fixture independently // of the relocation install, whose default materializer is under test. rt, err := relocationRecord(t.Context(), f.r.DataDir, f.claims, f.read()) if err != nil { t.Fatal(err) } held := rt.Route held.HoldOp = f.read().Claim.Op.ID if _, err := release.StageHeldVerified(t.Context(), f.claims, hsPub, f.cfg.Ratchet, f.r.OptDir, held, "v1", offProofGate(t)); err != nil { t.Fatal(err) } } if mode == "already-live" { f.cfg.materialize = f.materialize if _, err := f.install(); err != nil { t.Fatal(err) } f.cfg.materialize = nil } if mode == "damaged" { if err := os.WriteFile(path, []byte("retained inode"), 0755); err != nil { t.Fatal(err) } } if mode == "claims-off" { t.Setenv("CARLOS_HOST_CLAIM", "") } var before []byte var identity os.FileInfo if mode != "fresh" && mode != "claims-off" { var err error before, err = os.ReadFile(path) if err != nil { t.Fatal(err) } identity, err = os.Stat(path) if err != nil { t.Fatal(err) } } receipt, err := f.install() if mode == "already-live" || mode == "claims-off" { if err != nil || receipt.JournalSHA256 == "" { t.Fatalf("verified legacy/live install: %+v %v", receipt, err) } got, err := os.ReadFile(path) if err != nil || !bytes.Equal(got, artifact) { t.Fatalf("installed artifact: %q %v", got, err) } return } if !errors.Is(err, errHeldReleaseUnavailable) { t.Errorf("unsupported root relocation must refuse before materializing: %+v %v", receipt, err) } if receipt.JournalSHA256 != "" { t.Error("unsupported relocation returned completion") } if _, err := os.Lstat(release.LiveLink(f.r.OptDir, buildHost)); !errors.Is(err, os.ErrNotExist) { t.Errorf("relocation published live: %v", err) } if mode == "fresh" { if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { t.Errorf("relocation staged without root pins: %v", err) } } else { got, err := os.ReadFile(path) after, statErr := os.Stat(path) if err != nil || statErr != nil || !bytes.Equal(before, got) || !os.SameFile(identity, after) { t.Errorf("retained artifact changed: %q %v %v", got, err, statErr) } } journal, pathErr := relocationInstallPath(f.r.DataDir, f.read().Claim.Op.ID) if pathErr != nil { t.Fatal(pathErr) } if _, err := os.Stat(journal); !errors.Is(err, os.ErrNotExist) { t.Errorf("refusal wrote completion journal: %v", err) } requests, err := filepath.Glob(filepath.Join(release.HeldStageDir(f.r.DataDir), "*")) if err != nil || len(requests) != 0 { t.Fatalf("unsupported operation queued legacy relay: %v %v", requests, err) } }) } } func TestPlan14GenerationRelocationRelayRefusal(t *testing.T) { for _, action := range []string{heldReleaseStage, heldReleasePublish} { for _, kind := range []string{hostclaim.KindRelocating, hostclaim.KindMoving, hostclaim.KindLanding, hostclaim.KindRestoring} { t.Run(action+"/"+kind, func(t *testing.T) { f := newHRFixture(t) f.set(func(f *hrFixture) { f.claim.Op.Kind = hostclaim.KindRelocating f.claim.Op.Phase = hostclaim.PhaseInstalled f.claim.Op.Journal = map[string]string{"install_stamp": "7", "install_release": "v7"} f.rec.Move = nil f.row.Status = "relocating" }) f.root.Authenticate = func(context.Context, net.Conn) (StopPeer, error) { return hrPeer, nil } client := newHeldReleaseRootClient(serveClaimRoot(t, f.root)) q := f.request(action) q.Kind = kind if _, err := client.HeldRelease(t.Context(), q); err == nil { t.Fatal("unsupported relocation granted through root relay") } if _, err := os.Stat(release.VersionPath(f.opt, "bab", "v7", "app")); !errors.Is(err, os.ErrNotExist) { t.Fatalf("root staged unsupported relocation: %v", err) } if _, err := os.Lstat(release.LiveLink(f.opt, p1Host)); !errors.Is(err, os.ErrNotExist) { t.Fatalf("root published unsupported relocation: %v", err) } entries, err := os.ReadDir(f.receipts) if err != nil || len(entries) != 0 { t.Fatalf("root receipted unsupported relocation: %v %v", entries, err) } f.sel.mu.Lock() defer f.sel.mu.Unlock() if len(f.sel.selected) != 0 { t.Fatalf("unsupported relocation selected work: %v", f.sel.selected) } }) } } }