Documentation
¶
Overview ¶
Package dispatch is the controller's persistent record of in-flight dispatches. Every offer, claim, and terminal report appends an event to a JSONL write-ahead log, so a controller restart knows which dispatches were outstanding instead of losing all state (the v0.1 failure mode).
Lifecycle: offered -> claimed -> [started] -> resolved (done/failed/lost/expired). Offered records do not survive a restart: no credential was minted, the job is still queued on GitHub, and the poller will re-emit it. Claimed records do survive: a runner may exist, so the server's sweeps decide their fate.
Index ¶
- Constants
- Variables
- func DefaultPath() string
- type Ledger
- func (l *Ledger) Claim(id string, at time.Time, wantOrgs []string) (Record, bool, error)
- func (l *Ledger) Close() error
- func (l *Ledger) ExpireOffer(id string) bool
- func (l *Ledger) Offer(r Record) error
- func (l *Ledger) Resolve(id, status string, wantOrgs []string) (Record, bool, error)
- func (l *Ledger) Snapshot() []Record
- func (l *Ledger) Start(id, agent string, wantOrgs []string) (Record, bool, error)
- type Record
- type Store
Constants ¶
const ( StateOffered = "offered" StateClaimed = "claimed" // StateStarted is a claimed dispatch whose runner has taken a job. GitHub // gives a JIT runner any queued job with matching labels, so the offered // job may still be queued: a started record holds its slot but no longer // holds that job. StateStarted = "started" )
States a live record can be in.
Variables ¶
var ErrDuplicate = errors.New("dispatch already live")
ErrDuplicate is returned by Offer when the job already has a live dispatch. Callers must distinguish it from WAL I/O errors: a duplicate means the offer was rejected, any other error means the record is live but unpersisted.
Functions ¶
func DefaultPath ¶
func DefaultPath() string
DefaultPath is where the WAL lives when config doesn't override it.
Types ¶
type Ledger ¶
type Ledger struct {
// contains filtered or unexported fields
}
Ledger tracks live dispatch records, optionally backed by a JSONL WAL.
func Open ¶
Open loads the ledger at path, replaying the WAL and compacting it down to the surviving records. Offered records are dropped during replay (see the package comment). An empty path yields a purely in-memory ledger.
func (*Ledger) Claim ¶
Claim transitions an offered record to claimed, returning it. ok is false if the record is unknown, not in the offered state (double claim), or owned by another tenant (wantOrg set and mismatched).
func (*Ledger) ExpireOffer ¶
ExpireOffer removes id only if it is still offered (see Store.ExpireOffer).
func (*Ledger) Offer ¶
Offer records a new offered dispatch. Returns ErrDuplicate if the ID is taken or the job already has an unstarted dispatch under another ID. IDs embed the agent name (poll source) or are random (scaleset), so a re-emitted job offered to a different agent would otherwise slip past an ID-only check and double-provision.
func (*Ledger) Resolve ¶
Resolve removes a record in any live state, returning it. ok is false if the record is unknown (already resolved, or a report for a pre-restart offered record) or owned by another tenant (wantOrg set and mismatched).
type Record ¶
type Record struct {
ID string `json:"id"`
Agent string `json:"agent"`
State string `json:"state"`
Pending domain.Job `json:"pending"`
OfferedAt time.Time `json:"offered_at"`
ClaimedAt time.Time `json:"claimed_at,omitzero"`
}
Record is one in-flight dispatch. ID is the runner name (fixed at offer time, before any mint) and doubles as the agent's slot handle.
type Store ¶
type Store interface {
Offer(r Record) error
// Claim and Resolve take wantOrgs to scope the operation to one tenant: a
// non-empty wantOrgs only matches a record whose org is one of them (case-
// insensitively), so an enrolled agent can't claim or resolve another
// tenant's dispatch by its (guessable) id. Empty wantOrgs skips the check
// (OSS single-host / sweeps).
Claim(id string, at time.Time, wantOrgs []string) (Record, bool, error)
// Start marks a claimed dispatch's runner as working, which frees its
// offered job to be offered again (see StateStarted). Only the agent that
// holds the dispatch may start it: another agent of the same tenant would
// otherwise free a booting runner's job for a second dispatch.
Start(id, agent string, wantOrgs []string) (Record, bool, error)
Resolve(id, status string, wantOrgs []string) (Record, bool, error)
Snapshot() []Record
// ExpireOffer removes a record only if it is still in the offered state,
// returning true if it did. State-guarded so a claim landing between a
// sweep's snapshot and its expiry call can't delete a live claimed record.
ExpireOffer(id string) bool
}
Store is the dispatch persistence the control plane depends on. *Ledger is the in-memory + JSONL implementation used by the OSS single-host controller; the hosted control plane swaps in a Postgres-backed implementation with the same contract, so the server never knows which is behind it.