The seat set was a Go slice compiled into the controller and referenced by name everywhere, so changing it meant a rebuild and a freeze-prone deploy. It is now a table: catalogue keeps the shipped set as defaultSeats (the seed and the fallback) and a loadable working set; inventory adds the seat table (migration 0034), Seats to read it, and SeedSeats to fill it idempotently without overwriting an operator's edit; migrate seeds it; openInventory loads it, and an empty or unreadable table leaves the compiled defaults in force so it can never brick the control plane's boot. Behaviour-neutral: the seeded table equals the defaults. Phase 2 (reference by a stable id so a rename touches no manifest or code, and the builder reads the set from the mesh) follows.
57 lines
2.0 KiB
Go
57 lines
2.0 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// The seats the mesh has, as data (novox/hq ADR 0122).
|
|
//
|
|
// The set the control plane reads is a table here, not a slice compiled into it. It is seeded from
|
|
// the binary's defaults the first time the mesh comes up (SeedSeats), and thereafter it is the live
|
|
// copy: a rename or an added seat is a write here, and the control plane loads it at startup rather
|
|
// than being rebuilt for it.
|
|
|
|
// Seats is every seat the mesh defines, read from the store.
|
|
func (i *Inventory) Seats(ctx context.Context) ([]catalogue.Seat, error) {
|
|
rows, err := i.store.Pool().Query(ctx,
|
|
`select name, scope, delivers, decided from seat order by name`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var seats []catalogue.Seat
|
|
for rows.Next() {
|
|
var s catalogue.Seat
|
|
if err := rows.Scan(&s.Name, &s.Scope, &s.Delivers, &s.Decision); err != nil {
|
|
return nil, err
|
|
}
|
|
seats = append(seats, s)
|
|
}
|
|
return seats, rows.Err()
|
|
}
|
|
|
|
// SeedSeats writes the mesh's default set into the table where it is not already present.
|
|
//
|
|
// **Idempotent, and never overwriting.** Run every time the control plane migrates, it fills an
|
|
// empty table on first boot and adds a seat a new release ships — but it leaves a row already there
|
|
// exactly as it is, so an operator's rename in the table is not undone by the next deploy putting
|
|
// the old name back. What a release removes from the defaults is not deleted here either; retiring a
|
|
// seat is its own decision, not a silent consequence of it dropping out of the binary.
|
|
func (i *Inventory) SeedSeats(ctx context.Context, defaults []catalogue.Seat) (int, error) {
|
|
var added int
|
|
for _, s := range defaults {
|
|
tag, err := i.store.Pool().Exec(ctx,
|
|
`insert into seat (name, scope, delivers, decided) values ($1, $2, $3, $4)
|
|
on conflict (name) do nothing`,
|
|
s.Name, s.Scope, s.Delivers, s.Decision)
|
|
if err != nil {
|
|
return added, err
|
|
}
|
|
added += int(tag.RowsAffected())
|
|
}
|
|
return added, nil
|
|
}
|