apistockdocs
v0.4 GitHub apistock.dev
Technical/Module packages

modules/releases

import "apistock.dev/modules/releases"Source on GitHub

Package releases records which build every instance of an app runs and answers which releases are running (ADR-0040).

A Tracker is an app.Runner: it records the instance's build when it starts, sends a heartbeat while it runs, and marks the instance stopped when its context ends. A Store queries the records for operator APIs:

tracker, err := releases.NewTracker(pool, buildinfo.Read())
store, err := releases.NewStore(pool)
current, err := store.Current(ctx)

An instance is running while it hasn't stopped and its last heartbeat is recent, so instances that crash or lose their network stop counting after three missed heartbeats. Tracking never stops an app from starting or serving: failed writes are logged and retried at the next heartbeat.

The release_instances table comes from Migrations; apply them first.

Stability: pre-1.0 (ADR-0015).

Constants#

const DefaultHeartbeat, …#

go
const (
	DefaultHeartbeat = 30 * time.Second
	DefaultRetention = 90 * 24 * time.Hour
)

Defaults.

Variables#

var ErrInvalidCursor#

go
var ErrInvalidCursor = errors.New("releases: invalid cursor")

ErrInvalidCursor reports a cursor that wasn't returned by a Store list.

var Migrations#

go
var Migrations fs.FS = mustSub(migrationFiles, "migrations")

Migrations holds the module's goose migrations: the release_instances table. Apps copy them into db/migrations; tests can apply them directly with pgtest.

Types#

type CurrentRelease#

go
type CurrentRelease struct {
	Version   string
	Commit    string
	Instances []Instance
}

CurrentRelease is a release with the instances running it now.

type Instance#

go
type Instance struct {
	ID int64
	// InstanceID is random per process start.
	InstanceID string
	Version    string
	Commit     string
	// BuildTime is nil when the build didn't record one.
	BuildTime *time.Time
	// Modified reports a build from a tree with uncommitted changes.
	Modified   bool
	GoVersion  string
	Host       string
	StartedAt  time.Time
	LastSeenAt time.Time
	// StoppedAt is set when the instance shut down cleanly.
	StoppedAt *time.Time
	// Running reports that the instance hasn't stopped and sent a heartbeat
	// recently.
	Running bool
}

Instance is one start of an app instance.

type InstanceFilter#

go
type InstanceFilter struct {
	Version string
	Commit  string
	// RunningOnly keeps the instances running now.
	RunningOnly bool
	// Limit is clamped to 1–100; 0 means 50.
	Limit int
	// Cursor is InstancePage.NextCursor from the previous page.
	Cursor string
}

InstanceFilter selects instance starts. Empty fields match everything.

type InstancePage#

go
type InstancePage struct {
	Instances []Instance
	// NextCursor fetches the next page; empty on the last page.
	NextCursor string
}

InstancePage is a page of instance starts, newest first.

type Option#

go
type Option interface {
	// contains filtered or unexported methods
}

An Option configures NewTracker and NewStore.

func WithClock#

go
func WithClock(now func() time.Time) Option

WithClock sets the clock, for tests.

func WithHeartbeat#

go
func WithHeartbeat(d time.Duration) Option

WithHeartbeat sets how often a tracker updates its instance, from 5 seconds to 5 minutes. Give the store the same value: it counts an instance as running for three heartbeats after it was last seen. Default: DefaultHeartbeat.

func WithHost#

go
func WithHost(host string) Option

WithHost sets the host a tracker records, such as a pod name. Default: the operating system's host name.

func WithLogger#

go
func WithLogger(logger *slog.Logger) Option

WithLogger sets the logger for failed writes. Default: discard.

func WithRetention#

go
func WithRetention(d time.Duration) Option

WithRetention sets how long instances are kept after they were last seen, from 1 day to 3 years. A tracker deletes older instances when it starts. Default: DefaultRetention.

func WithRetentionFunc#

go
func WithRetentionFunc(fn func(context.Context) time.Duration) Option

WithRetentionFunc reads the retention each time a tracker starts, so a runtime setting such as releases.instance_retention applies without a redeploy (ADR-0051). Values outside 1 day to 3 years are clamped. It overrides WithRetention.

type Release#

go
type Release struct {
	Version        string
	Commit         string
	FirstStartedAt time.Time
	LastSeenAt     time.Time
	// Running counts the instances running now.
	Running int
	// Starts counts every recorded start, including stopped instances.
	Starts int
	// Modified reports that an instance ran a build with uncommitted changes.
	Modified bool
}

Release is one build: every instance start with the same version and commit.

type ReleaseFilter#

go
type ReleaseFilter struct {
	// Limit is clamped to 1–100; 0 means 50.
	Limit int
	// Cursor is ReleasePage.NextCursor from the previous page.
	Cursor string
}

ReleaseFilter pages through releases.

type ReleasePage#

go
type ReleasePage struct {
	Releases []Release
	// NextCursor fetches the next page; empty on the last page.
	NextCursor string
}

ReleasePage is a page of releases, newest first.

type Store#

go
type Store struct {
	// contains filtered or unexported fields
}

Store queries recorded instances and the releases they ran. It is safe for concurrent use.

func NewStore#

go
func NewStore(pool *pgxpool.Pool, opts ...Option) (*Store, error)

NewStore returns a store on pool. Give it the trackers' heartbeat, if not the default.

func (*Store) Current#

go
func (s *Store) Current(ctx context.Context) ([]CurrentRelease, error)

Current returns the releases running now, newest start first, each with its running instances. During a rolling deploy it returns more than one; when no instance sent a recent heartbeat, none.

func (*Store) Instances#

go
func (s *Store) Instances(ctx context.Context, f InstanceFilter) (InstancePage, error)

Instances returns instance starts matching f, newest first. It returns ErrInvalidCursor for a cursor it didn't return.

func (*Store) Releases#

go
func (s *Store) Releases(ctx context.Context, f ReleaseFilter) (ReleasePage, error)

Releases returns releases, newest first by their first start. It returns ErrInvalidCursor for a cursor it didn't return.

type Tracker#

go
type Tracker struct {
	// contains filtered or unexported fields
}

Tracker records one app instance: its build when it starts, a heartbeat while it runs and the time it stops. Run it once, as an app.Runner.

func NewTracker#

go
func NewTracker(pool *pgxpool.Pool, info buildinfo.Info, opts ...Option) (*Tracker, error)

NewTracker returns a tracker for an instance running the build info describes, usually buildinfo.Read. An empty version is recorded as dev.

func (*Tracker) InstanceID#

go
func (t *Tracker) InstanceID() string

InstanceID returns the random ID this instance is recorded under, as listed by the release log's instances.

func (*Tracker) Run#

go
func (t *Tracker) Run(ctx context.Context) error

Run records the instance, sends heartbeats until ctx ends, then marks the instance stopped. It always returns nil: failed writes are logged and retried at the next heartbeat, so tracking never stops the app.

v0.4
esc
↑↓ move↵ openesc close