package inventory import ( "context" "errors" "time" "github.com/jackc/pgx/v5" ) // BusStep is one planned bus upgrade (novox/hq to-be 45 ยง8, ADR 0235). type BusStep struct { ID int64 Module string Machines []string From, To string Snapshot string Reversible bool By, Why string Started time.Time Ended *time.Time Outcome string Found string } // StartBusStep records a bus upgrade starting. Refused while another runs. func (i *Inventory) StartBusStep(ctx context.Context, s BusStep) (BusStep, error) { if _, err := i.actingEpoch(ctx); err != nil { return s, err } if open, found, err := i.LatestBusStep(ctx); err != nil { return s, err } else if found && open.Ended == nil { return s, ErrBusStepRunning } if s.Machines == nil { s.Machines = []string{} } err := i.store.Pool().QueryRow(ctx, `insert into bus_step (module, machines, from_build, to_build, snapshot, reversible, by_whom, why) values ($1, $2, $3, $4, $5, $6, $7, $8) returning id, started`, s.Module, s.Machines, s.From, s.To, s.Snapshot, s.Reversible, s.By, s.Why).Scan(&s.ID, &s.Started) return s, err } // ErrBusStepRunning is a bus upgrade asked while one runs. var ErrBusStepRunning = errors.New("a bus upgrade is already running") // EndBusStep records how a bus upgrade ended: done or failed, with what was found. func (i *Inventory) EndBusStep(ctx context.Context, id int64, outcome, found string) error { if _, err := i.actingEpoch(ctx); err != nil { return err } _, err := i.store.Pool().Exec(ctx, `update bus_step set ended = now(), outcome = $2, found = $3 where id = $1 and ended is null`, id, outcome, found) return err } // LatestBusStep is the newest bus upgrade, and whether there is any. func (i *Inventory) LatestBusStep(ctx context.Context) (BusStep, bool, error) { var s BusStep err := i.store.Pool().QueryRow(ctx, `select id, module, machines, from_build, to_build, snapshot, reversible, by_whom, why, started, ended, outcome, found from bus_step order by id desc limit 1`). Scan(&s.ID, &s.Module, &s.Machines, &s.From, &s.To, &s.Snapshot, &s.Reversible, &s.By, &s.Why, &s.Started, &s.Ended, &s.Outcome, &s.Found) if errors.Is(err, pgx.ErrNoRows) { return s, false, nil } return s, err == nil, err }