Design 47's last milestones: the keeper, self-check, healers, hand-acts, provisioner, vault, artifact sweep, disk readings, credential forms (issues 774, 1952, 1953, 1954, 1955)
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
One commit with exactly the tree of the reviewed head35b4509c0a, on main12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
This commit is contained in:
@@ -120,8 +120,8 @@ func TestProvisionerCreatesTheDatabaseThenItsExtensions(t *testing.T) {
|
||||
if err := a.Retire(ctx, "mesh_ace_letta", nil, "test", time.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
noDrop(t, f.statements())
|
||||
if !has(f.statements(), `NOLOGIN`) {
|
||||
// On the operator's yes the login goes and the database stays (ADR 0570 clause 5).
|
||||
if !has(f.statements(), `^DROP ROLE "mesh_ace_letta"$`) || has(f.statements(), `DROP DATABASE`) {
|
||||
t.Fatal(f.statements())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,14 @@ func TestLive(t *testing.T) {
|
||||
if err != nil || len(r.Rows) != 1 || cell(r.Rows[0], 0) != "1" {
|
||||
t.Fatal("retiring lost the data:", r, err)
|
||||
}
|
||||
// The login is removed, its database kept by its keeper (ADR 0570 clause 5).
|
||||
if has, _ := c.exists(ctx, "SELECT 1 FROM pg_roles WHERE rolname = "+Literal(p.As)); has {
|
||||
t.Fatal("the retired login is still there")
|
||||
}
|
||||
if has, _ := c.exists(ctx, "SELECT 1 FROM pg_database d JOIN pg_roles r ON r.oid = d.datdba WHERE d.datname = "+
|
||||
Literal(p.As)+" AND r.rolname = "+Literal(p.As+"~retired")); !has {
|
||||
t.Fatal("the database is not its keeper's")
|
||||
}
|
||||
inv, err := a.Inventory(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -35,10 +35,14 @@ func (a provisioner) Create(ctx context.Context, p harness.Provision) error {
|
||||
if err := a.pg.CreateDatabaseAndRole(ctx, database, p.As, p.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
// A login retired and asked for again takes back what its keeper kept (retire_pg.go).
|
||||
if err := a.pg.Reclaim(ctx, database, p.As); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.pg.EnsureExtensions(ctx, database, extensions); err != nil {
|
||||
return err
|
||||
}
|
||||
// The active mark: a retired consumer asked for again loses its mark to delete here, its LOGIN
|
||||
// The active mark: a login locked before ADR 0570 and asked for again loses its mark to delete here, its LOGIN
|
||||
// already set again by the role statement above (novox/hq ADR 0230).
|
||||
if err := a.pg.MarkActive(ctx, p.As, p.Consumer); err != nil {
|
||||
return err
|
||||
@@ -47,11 +51,10 @@ func (a provisioner) Create(ctx context.Context, p harness.Provision) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retire locks the login, never drops (novox/hq issue 241, ADR 0230): the database is kept under its
|
||||
// own name, the role marked retired with when and why (retire_pg.go). On 2026-10-04 a misread
|
||||
// contributions file withdrew every consumer at once, and dropping made that a loss of seven databases.
|
||||
// Deleting is `cleanup delete`, a person's act; taking a database out of service by hand is
|
||||
// postgres_retire_database, which renames rather than drops.
|
||||
// Retire removes the login on the operator's yes (novox/hq ADR 0570 clause 5, ADR 0114 §2): what it owns is handed
|
||||
// to its keeper, its sessions end and the login is dropped; the database and its data are kept (retire_pg.go).
|
||||
// Deleting is `cleanup delete`, a person's act; taking a database out of service by hand is postgres_retire_database,
|
||||
// which renames rather than drops.
|
||||
func (a provisioner) Retire(ctx context.Context, as string, _ map[string]any, why string, at time.Time) error {
|
||||
if err := a.pg.RetireRole(ctx, as, why, at); err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
package main
|
||||
|
||||
// What retired means in postgres (novox/hq ADR 0230).
|
||||
// What retired means in postgres (novox/hq ADR 0570 clause 5, ADR 0114 §2 and §9; ADR 0230 for the delete).
|
||||
//
|
||||
// **Retired is the login locked, the database kept, and the role marked.** `ALTER ROLE … NOLOGIN` —
|
||||
// the consumer's login can no longer connect, as itself, to anything — and its open sessions are
|
||||
// ended. The database, its owner, its grants and every byte in it stay exactly as they were: CONNECT is
|
||||
// not revoked and the database still allows connections, because the backup contribution dumps every
|
||||
// database and a retired one is still worth backing up, and because re-enabling must give it back as it
|
||||
// was. The mark is the role's comment, a JSON object the mesh owns:
|
||||
// **Retired is the login removed, the database kept under a keeper role, and the keeper marked.** Retiring happens
|
||||
// only on the operator's yes to a retiring provision (the SDK's moved path, Gone). What the login owns, its database
|
||||
// first, is handed to its keeper, a role of its own that cannot log in (`<login>~retired`, KeeperOf), its open
|
||||
// sessions are ended, and the login is dropped: nothing can connect as it any more, and no credential of it is left.
|
||||
// The database, its grants to others and every byte in it stay: the backup contribution dumps every database and a
|
||||
// retired one is still worth backing up. The mark is the keeper's comment, a JSON object the mesh owns:
|
||||
//
|
||||
// {"mesh":"consumer","node":"ace"} active
|
||||
// {"mesh":"consumer","node":"ace","retired":"2026-10-06T…Z","why":"…"} retired
|
||||
// {"mesh":"consumer","node":"ace"} an active login
|
||||
// {"mesh":"consumer","node":"ace","retired":"2026-10-06T…Z","why":"…","login":"…"} a keeper
|
||||
//
|
||||
// Asked for again, the ordinary create sets LOGIN and the password again and writes the active mark.
|
||||
// Deleted — only through `cleanup delete` — the database is dropped, then the role, unless it still owns
|
||||
// another database (a set-aside copy), which is said and kept.
|
||||
// Asked for again, the ordinary create makes the login again and its keeper hands everything back (Reclaim) and
|
||||
// goes. Deleted — only through `cleanup delete` — the database is dropped, then the keeper.
|
||||
//
|
||||
// **A login retired before this record** was only locked (`NOLOGIN`, its own role marked retired); it is listed and
|
||||
// deleted as before, and create sets its LOGIN again.
|
||||
//
|
||||
// **What the mesh made is recognised by its mark, or by its shape before the mark existed**: a role
|
||||
// valid until 'infinity' (only the mesh's role statement says that) that owns a database of its own
|
||||
@@ -41,6 +43,17 @@ type roleMark struct {
|
||||
Node string `json:"node,omitempty"`
|
||||
Retired string `json:"retired,omitempty"`
|
||||
Why string `json:"why,omitempty"`
|
||||
// Login is, on a keeper, the login it keeps for.
|
||||
Login string `json:"login,omitempty"`
|
||||
}
|
||||
|
||||
// KeeperOf is the role a retired login's database and objects are handed to: the login's name with `~retired`.
|
||||
func KeeperOf(login string) (string, error) {
|
||||
k := login + "~retired"
|
||||
if len(k) > 63 {
|
||||
return "", fmt.Errorf("%s is too long a login for a keeper role (%s is over postgres's 63 bytes)", login, k)
|
||||
}
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func readMark(comment string) (roleMark, bool) {
|
||||
@@ -64,8 +77,9 @@ func (c *Client) MarkActive(ctx context.Context, role, node string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// RetireRole locks the login, ends its sessions and marks it retired with when and why. Its database
|
||||
// is not touched. A role that does not exist has nothing to retire.
|
||||
// RetireRole removes a login on the operator's yes (ADR 0570 clause 5): what it owns is handed to its keeper, marked
|
||||
// with when and why, its sessions end and the login is dropped. The database and its data are kept. A login that does
|
||||
// not exist has nothing to retire.
|
||||
func (c *Client) RetireRole(ctx context.Context, role, why string, at time.Time) error {
|
||||
r, err := c.Query(ctx, "", "SELECT coalesce(shobj_description(oid, 'pg_authid'), '') FROM pg_roles WHERE rolname = "+
|
||||
Literal(role))
|
||||
@@ -76,10 +90,66 @@ func (c *Client) RetireRole(ctx context.Context, role, why string, at time.Time)
|
||||
return nil
|
||||
}
|
||||
prev, _ := readMark(cell(r.Rows[0], 0))
|
||||
keeper, err := KeeperOf(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
has, err := c.exists(ctx, "SELECT 1 FROM pg_roles WHERE rolname = "+Literal(keeper))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
if _, err := c.Query(ctx, "", fmt.Sprintf("CREATE ROLE %s NOLOGIN", Ident(keeper))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := c.Query(ctx, "", MarkStatement(keeper, roleMark{Node: prev.Node, Retired: at.UTC().Format(time.RFC3339),
|
||||
Why: why, Login: role})); err != nil {
|
||||
return err
|
||||
}
|
||||
// No new session, and none left open.
|
||||
if err := c.LockRole(ctx, role); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.Query(ctx, "", MarkStatement(role, roleMark{Node: prev.Node, Retired: at.UTC().Format(time.RFC3339), Why: why}))
|
||||
// In its own database, where its objects are; the database itself is a shared object, handed with them.
|
||||
in := ""
|
||||
if own, err := c.exists(ctx, "SELECT 1 FROM pg_database WHERE datname = "+Literal(role)); err != nil {
|
||||
return err
|
||||
} else if own {
|
||||
in = role
|
||||
}
|
||||
for _, sql := range []string{
|
||||
fmt.Sprintf("REASSIGN OWNED BY %s TO %s", Ident(role), Ident(keeper)),
|
||||
fmt.Sprintf("DROP OWNED BY %s", Ident(role)),
|
||||
} {
|
||||
if _, err := c.Query(ctx, in, sql); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = c.Query(ctx, "", fmt.Sprintf("DROP ROLE %s", Ident(role)))
|
||||
return err
|
||||
}
|
||||
|
||||
// Reclaim gives a login made again what its keeper kept (ADR 0570 clause 5: a provision needed again): the keeper's
|
||||
// database and objects are handed back, and the keeper goes. A login with no keeper has nothing to reclaim.
|
||||
func (c *Client) Reclaim(ctx context.Context, database, role string) error {
|
||||
keeper, err := KeeperOf(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
has, err := c.exists(ctx, "SELECT 1 FROM pg_roles WHERE rolname = "+Literal(keeper))
|
||||
if err != nil || !has {
|
||||
return err
|
||||
}
|
||||
for _, sql := range []string{
|
||||
fmt.Sprintf("REASSIGN OWNED BY %s TO %s", Ident(keeper), Ident(role)),
|
||||
fmt.Sprintf("DROP OWNED BY %s", Ident(keeper)),
|
||||
} {
|
||||
if _, err := c.Query(ctx, database, sql); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = c.Query(ctx, "", fmt.Sprintf("DROP ROLE %s", Ident(keeper)))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -89,7 +159,8 @@ var setAside = regexp.MustCompile(`_deleted_(\d{8})$`)
|
||||
// of its own database.
|
||||
const InventoryStatement = `SELECT r.rolname, r.rolcanlogin, coalesce(shobj_description(r.oid, 'pg_authid'), '') AS mark,
|
||||
coalesce(r.rolvaliduntil = 'infinity', false) AS mesh_shaped,
|
||||
(SELECT pg_database_size(d.datname) FROM pg_database d WHERE d.datname = r.rolname AND d.datdba = r.oid) AS size
|
||||
(SELECT pg_database_size(d.datname) FROM pg_database d WHERE d.datdba = r.oid
|
||||
AND (d.datname = r.rolname OR d.datname || '~retired' = r.rolname)) AS size
|
||||
FROM pg_roles r
|
||||
WHERE r.rolname !~ '^pg_' AND NOT r.rolsuper AND r.rolname <> ` + "'" + Reader + "'" + `
|
||||
ORDER BY r.rolname`
|
||||
@@ -118,6 +189,9 @@ func (c *Client) Inventory(ctx context.Context) (harness.Inventory, error) {
|
||||
continue
|
||||
}
|
||||
at, _ := time.Parse(time.RFC3339, m.Retired)
|
||||
if m.Login != "" {
|
||||
name = m.Login // a keeper: listed under the login it keeps for
|
||||
}
|
||||
inv.Retired = append(inv.Retired, harness.Retired{Consumer: name, Node: m.Node, RetiredAt: at, Why: m.Why,
|
||||
SizeBytes: sizeOf(size), Kind: harness.KindConsumer})
|
||||
}
|
||||
@@ -160,7 +234,19 @@ func (c *Client) DeleteRetired(ctx context.Context, r harness.Retired) (int64, e
|
||||
return freed, err
|
||||
}
|
||||
if len(row.Rows) == 0 {
|
||||
return freed, nil
|
||||
// Retired by removing the login: its keeper goes with the database it kept.
|
||||
keeper, err := KeeperOf(r.Consumer)
|
||||
if err != nil {
|
||||
return freed, err
|
||||
}
|
||||
if has, err := c.exists(ctx, "SELECT 1 FROM pg_roles WHERE rolname = "+Literal(keeper)); err != nil || !has {
|
||||
return freed, err
|
||||
}
|
||||
if _, err := c.Query(ctx, "", fmt.Sprintf("DROP OWNED BY %s", Ident(keeper))); err != nil {
|
||||
return freed, err
|
||||
}
|
||||
_, err = c.Query(ctx, "", fmt.Sprintf("DROP ROLE %s", Ident(keeper)))
|
||||
return freed, err
|
||||
}
|
||||
owns, err := c.Query(ctx, "", "SELECT datname FROM pg_database WHERE datdba = (SELECT oid FROM pg_roles WHERE rolname = "+
|
||||
Literal(r.Consumer)+")")
|
||||
|
||||
@@ -10,31 +10,74 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRetiringLocksMarksAndDropsNothing(t *testing.T) {
|
||||
// ADR 0570 clause 5, on the operator's yes: the login's database and objects go to its keeper, marked; its sessions
|
||||
// end; the login is dropped; the database is not.
|
||||
func TestRetiringHandsTheDatabaseToAKeeperAndRemovesTheLogin(t *testing.T) {
|
||||
f, c := newFake(admin)
|
||||
f.answer(`shobj_description\(oid`, []string{"c"}, []string{`{"mesh":"consumer","node":"ace"}`})
|
||||
f.answer(`SELECT 1 FROM pg_roles`, []string{"?column?"}, []string{"1"})
|
||||
f.answer(`pg_roles WHERE rolname = 'mesh_ace_letta'$`, []string{"?column?"}, []string{"1"})
|
||||
f.answer(`pg_database WHERE datname = 'mesh_ace_letta'$`, []string{"?column?"}, []string{"1"})
|
||||
at := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
|
||||
if err := c.RetireRole(ctx, "mesh_ace_letta", "the mesh stopped asking for it", at); err != nil {
|
||||
if err := c.RetireRole(ctx, "mesh_ace_letta", "the operator approved its retirement", at); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sql := f.statements()
|
||||
if !has(sql, `ALTER ROLE "mesh_ace_letta" NOLOGIN`) || !has(sql, `pg_terminate_backend.*usename = 'mesh_ace_letta'`) ||
|
||||
!has(sql, `COMMENT ON ROLE "mesh_ace_letta" IS '\{"mesh":"consumer","node":"ace","retired":"2026-10-06T12:00:00Z","why":"the mesh stopped asking for it"\}'`) {
|
||||
t.Fatal(strings.Join(sql, "\n"))
|
||||
var order []string
|
||||
for _, call := range f.calls {
|
||||
switch {
|
||||
case strings.HasPrefix(call.SQL, "SELECT"):
|
||||
case strings.HasPrefix(call.SQL, "REASSIGN"), strings.HasPrefix(call.SQL, "DROP OWNED"):
|
||||
order = append(order, call.Database+": "+call.SQL)
|
||||
default:
|
||||
order = append(order, call.SQL)
|
||||
}
|
||||
}
|
||||
noDrop(t, sql)
|
||||
if has(sql, `REVOKE|ALLOW_CONNECTIONS|RENAME`) {
|
||||
t.Fatal("retiring touched the database:", strings.Join(sql, "\n"))
|
||||
if !has(f.statements(), `pg_terminate_backend.*usename = 'mesh_ace_letta'`) {
|
||||
t.Fatal(strings.Join(f.statements(), "\n"))
|
||||
}
|
||||
want := []string{
|
||||
`CREATE ROLE "mesh_ace_letta~retired" NOLOGIN`,
|
||||
`COMMENT ON ROLE "mesh_ace_letta~retired" IS '{"mesh":"consumer","node":"ace","retired":"2026-10-06T12:00:00Z","why":"the operator approved its retirement","login":"mesh_ace_letta"}'`,
|
||||
`ALTER ROLE "mesh_ace_letta" NOLOGIN`,
|
||||
`mesh_ace_letta: REASSIGN OWNED BY "mesh_ace_letta" TO "mesh_ace_letta~retired"`,
|
||||
`mesh_ace_letta: DROP OWNED BY "mesh_ace_letta"`,
|
||||
`DROP ROLE "mesh_ace_letta"`,
|
||||
}
|
||||
if strings.Join(order, "\n") != strings.Join(want, "\n") {
|
||||
t.Fatalf("sent:\n%s\nwant:\n%s", strings.Join(order, "\n"), strings.Join(want, "\n"))
|
||||
}
|
||||
if has(f.statements(), `DROP DATABASE|REVOKE|ALLOW_CONNECTIONS|RENAME`) {
|
||||
t.Fatal("retiring touched the database:", strings.Join(f.statements(), "\n"))
|
||||
}
|
||||
|
||||
// No such role: nothing to retire, nothing done.
|
||||
f, c = newFake(admin)
|
||||
if err := c.RetireRole(ctx, "gone", "x", at); err != nil || has(f.statements(), `ALTER|COMMENT`) {
|
||||
if err := c.RetireRole(ctx, "gone", "x", at); err != nil || has(f.statements(), `ALTER|COMMENT|CREATE|DROP`) {
|
||||
t.Fatal(f.statements(), err)
|
||||
}
|
||||
}
|
||||
|
||||
// A login made again takes back what its keeper kept, and the keeper goes; with no keeper, nothing is sent.
|
||||
func TestALoginMadeAgainReclaimsWhatItsKeeperKept(t *testing.T) {
|
||||
f, c := newFake(admin)
|
||||
if err := c.Reclaim(ctx, "mesh_ace_letta", "mesh_ace_letta"); err != nil || has(f.statements(), `REASSIGN|DROP`) {
|
||||
t.Fatal(f.statements(), err)
|
||||
}
|
||||
f, c = newFake(admin)
|
||||
f.answer(`pg_roles WHERE rolname = 'mesh_ace_letta~retired'$`, []string{"?column?"}, []string{"1"})
|
||||
if err := c.Reclaim(ctx, "mesh_ace_letta", "mesh_ace_letta"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got []string
|
||||
for _, call := range f.calls[1:] {
|
||||
got = append(got, call.Database+": "+call.SQL)
|
||||
}
|
||||
want := `mesh_ace_letta: REASSIGN OWNED BY "mesh_ace_letta~retired" TO "mesh_ace_letta"` + "\n" +
|
||||
`mesh_ace_letta: DROP OWNED BY "mesh_ace_letta~retired"` + "\n" + `postgres: DROP ROLE "mesh_ace_letta~retired"`
|
||||
if strings.Join(got, "\n") != want {
|
||||
t.Fatal(strings.Join(got, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheInventoryIsWhatTheMeshMadeByMarkOrShape(t *testing.T) {
|
||||
f, c := newFake(admin)
|
||||
f.answer(`FROM pg_roles r`, []string{"rolname", "rolcanlogin", "mark", "mesh_shaped", "size"},
|
||||
@@ -44,6 +87,7 @@ func TestTheInventoryIsWhatTheMeshMadeByMarkOrShape(t *testing.T) {
|
||||
[]string{"locked_before", "f", "", "t", "400"},
|
||||
[]string{"hal_registry", "t", "", "f", "500"},
|
||||
[]string{"jochens", "t", "", "t", ""},
|
||||
[]string{"mesh_ace_umami~retired", "f", `{"mesh":"consumer","node":"ace","retired":"2026-10-29T12:00:00Z","why":"approved","login":"mesh_ace_umami"}`, "f", "600"},
|
||||
)
|
||||
f.answer(`_deleted_`, []string{"datname", "size"}, []string{"mesh_novox_gitea_deleted_20261005", "7771827"})
|
||||
inv, err := c.Inventory(ctx)
|
||||
@@ -53,7 +97,7 @@ func TestTheInventoryIsWhatTheMeshMadeByMarkOrShape(t *testing.T) {
|
||||
if strings.Join(inv.Active, ",") != "active_marked,active_shaped" {
|
||||
t.Fatalf("active: %v", inv.Active)
|
||||
}
|
||||
if len(inv.Retired) != 3 {
|
||||
if len(inv.Retired) != 4 {
|
||||
t.Fatalf("%+v", inv.Retired)
|
||||
}
|
||||
r := inv.Retired[0]
|
||||
@@ -64,7 +108,10 @@ func TestTheInventoryIsWhatTheMeshMadeByMarkOrShape(t *testing.T) {
|
||||
if r := inv.Retired[1]; r.Consumer != "locked_before" || !r.RetiredAt.IsZero() || r.Kind != harness.KindConsumer {
|
||||
t.Fatalf("found locked without a mark: %+v", r)
|
||||
}
|
||||
if r := inv.Retired[2]; r.Kind != KindSetAside || r.RetiredAt.Format("20060102") != "20261005" || r.SizeBytes != 7771827 {
|
||||
if r := inv.Retired[2]; r.Consumer != "mesh_ace_umami" || r.SizeBytes != 600 || r.Why != "approved" {
|
||||
t.Fatalf("a keeper is listed under the login it keeps for: %+v", r)
|
||||
}
|
||||
if r := inv.Retired[3]; r.Kind != KindSetAside || r.RetiredAt.Format("20060102") != "20261005" || r.SizeBytes != 7771827 {
|
||||
t.Fatalf("set aside: %+v", r)
|
||||
}
|
||||
}
|
||||
@@ -97,6 +144,18 @@ func TestDeletingDropsTheDatabaseThenTheRole(t *testing.T) {
|
||||
if db < 0 || role < db {
|
||||
t.Fatal(sql)
|
||||
}
|
||||
// A login removed on retiring: the database, then its keeper.
|
||||
f, c = newFake(admin)
|
||||
f.answer(`SELECT pg_database_size`, []string{"size"}, []string{"9000"})
|
||||
f.answer(`pg_roles WHERE rolname = 'mesh_ace_umami~retired'$`, []string{"?column?"}, []string{"1"})
|
||||
if _, err := c.DeleteRetired(ctx, harness.Retired{Consumer: "mesh_ace_umami", Kind: harness.KindConsumer}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sql = strings.Join(f.statements(), "\n")
|
||||
db, keeper := strings.Index(sql, `DROP DATABASE "mesh_ace_umami"`), strings.Index(sql, `DROP ROLE "mesh_ace_umami~retired"`)
|
||||
if db < 0 || keeper < db {
|
||||
t.Fatal(sql)
|
||||
}
|
||||
// A role still owning a set-aside database is kept, and said.
|
||||
f, c = newFake(admin)
|
||||
f.answer(`SELECT rolcanlogin`, []string{"rolcanlogin"}, []string{"f"})
|
||||
|
||||
Reference in New Issue
Block a user