scheduledeploy

package
v0.9.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package scheduledeploy checks, on an interval, which apps have a due cron schedule (store.AppSchedule, migrations/0250_app_schedules.sql) and redeploys the latest commit on their configured branch.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Executor

type Executor interface {
	ExecuteScheduledDeploy(ctx context.Context, d store.ScheduledDeploy) (Outcome, error)
}

Executor starts a due scheduled deploy through the normal deploy path.

type Notifier

type Notifier interface {
	NotifyScheduledDeploy(ctx context.Context, d store.ScheduledDeploy, status, reason string)
}

Notifier tells operators a scheduled deploy did not run as planned.

type OneShotRunner

type OneShotRunner struct {
	Store    OneShotStore
	Freeze   deploy.FreezeStore
	Exec     Executor
	Notify   Notifier
	Logger   *slog.Logger
	Now      func() time.Time
	Grace    time.Duration
	StaleRun time.Duration
	// contains filtered or unexported fields
}

OneShotRunner is the level-triggered scanner for one-shot scheduled deploys. Each tick re-reads the due rows, so a restart loses nothing: rows within Grace of run_at still run, older ones are marked missed and notified. A row is claimed (pending to running) before executing, so it never fires twice; a claim left behind by a crash is closed as failed, never retried.

func (*OneShotRunner) Run

func (o *OneShotRunner) Run(ctx context.Context, interval time.Duration) error

Run ticks on interval until ctx is done.

func (*OneShotRunner) Tick

func (o *OneShotRunner) Tick(ctx context.Context) error

Tick processes every due scheduled deploy once.

type OneShotStore

type OneShotStore interface {
	ListDueScheduledDeploys(ctx context.Context, now, staleBefore time.Time) ([]store.ScheduledDeploy, error)
	ClaimScheduledDeploy(ctx context.Context, id string, at time.Time) error
	FinishScheduledDeploy(ctx context.Context, id, status, reason, approvalID string, at time.Time) error
}

OneShotStore is the store surface the one-shot runner needs.

type Outcome

type Outcome struct {
	// ApprovalID is set when the deploy became a pending approval instead of running.
	ApprovalID string
	Message    string
}

Outcome is what an Executor reports for a deploy it started.

type ScheduleStore

type ScheduleStore interface {
	ListEnabledAppSchedules(ctx context.Context) ([]store.AppSchedule, error)
	ArmAppScheduleNextRun(ctx context.Context, serviceName string, next time.Time) error
	RecordAppScheduleHistory(ctx context.Context, e store.AppScheduleHistoryEntry) error
	CompleteAppSchedule(ctx context.Context, serviceName string, at time.Time) error
}

ScheduleStore is the narrow store surface Scheduler needs, re-derived fresh every tick, the same shape internal/scheduledtask.ScheduleStore's own doc comment establishes for its sibling. *store.DB satisfies this structurally.

type Scheduler

type Scheduler struct {
	Store   ScheduleStore
	Freeze  deploy.FreezeStore
	Trigger Trigger
	Logger  *slog.Logger
	// Now returns the current time; nil falls back to time.Now, the same
	// testable-clock convention every other scheduler in this codebase
	// establishes.
	Now func() time.Time
	// OneShotCatchUp bounds how late a one-time schedule may still fire
	// after its run_at (for example after downtime); later than that it is
	// recorded as skipped. Zero or negative means no bound.
	OneShotCatchUp time.Duration
	// contains filtered or unexported fields
}

Scheduler periodically checks which app schedules are due and fires them through Trigger.

Catch-up semantics: every enabled schedule persists its own next fire time (store.AppSchedule.NextFireAt) rather than keeping it only in an in-memory position. A schedule seen for the first time (NextFireAt nil) is armed to its next real occurrence and does not fire immediately, the same "no fire on sight" rule internal/scheduledtask. Scheduler and internal/backup.Scheduler already apply to a freshly observed schedule. Once armed, Tick re-arms NextFireAt to the next occurrence strictly after now *before* calling Trigger: if the process is killed between that arm and Trigger returning, the missed firing is not retried on the next tick, and it is never fired twice either.

Because the armed time is persisted rather than kept in memory, a schedule that missed one or more occurrences entirely while the control plane was down still fires exactly once the next time it is checked, for the most recent due occurrence, never once per missed occurrence, then resumes its normal cadence from now. This is a deliberate difference from its sibling schedulers (internal/scheduledtask, internal/backup), whose in-memory position is lost on restart and which therefore never catch up a missed occurrence at all: a missed scheduled deploy is worth catching up once, a missed backup or task run is not, matching how this codebase already treats each.

A schedule due during an active deploy freeze window is skipped, not forced: it is recorded in history with a reason and re-armed for its next natural occurrence, never queued to run once the freeze lifts.

func NewScheduler

func NewScheduler(store ScheduleStore, freeze deploy.FreezeStore, trigger Trigger, logger *slog.Logger) *Scheduler

NewScheduler builds a Scheduler ready to Tick or Run. logger defaults to slog.Default() if nil.

func (*Scheduler) Run

func (s *Scheduler) Run(ctx context.Context, interval time.Duration) error

Run calls Tick on interval until ctx is done, matching the shape of every other periodic loop in this codebase (backup.Scheduler.Run, scheduledtask.Scheduler.Run, alerting.Engine.Run).

func (*Scheduler) Tick

func (s *Scheduler) Tick(ctx context.Context) error

Tick evaluates every currently enabled app schedule once. One schedule's error (an invalid cron string, Trigger itself failing) is collected and joined, never stopping evaluation of the rest, the same "one broken resource must not block the rest" principle every other scheduler in this codebase already follows.

type Trigger

type Trigger interface {
	TriggerScheduledDeploy(ctx context.Context, serviceName, branch string) (result string, err error)
}

Trigger fires a scheduled redeploy of serviceName's latest commit on branch, returning a short human-readable result recorded as the history entry's reason. *api.Router (TriggerScheduledDeploy) satisfies this; redeclared here rather than depended on directly, the same "substitutable without a real Router" reasoning internal/scheduledtask.TaskRunner's own doc comment gives for its sibling.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL