maintenance

package
v0.9.0 Latest Latest
Warning

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

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

Documentation

Overview

Package maintenance holds one-off scheduled maintenance windows: their validation, state, and the lead-time announcement through alert channels.

Index

Constants

View Source
const (
	EndManual        = "manual"
	EndUntil         = "until"
	EndMaxDuration   = "max_duration"
	EndDomainRemoved = "domain_removed"
)

Why domain maintenance ended, recorded on the end audit row.

View Source
const (
	AuditActionStarted = "domain_maintenance.started"
	AuditActionEnded   = store.AuditActionDomainMaintenanceEnded
)

Audit actions for the maintenance lifecycle rows.

View Source
const (
	MaxDomainTitle      = 120
	MaxDomainMessage    = 2000
	MaxBypassEntries    = 32
	MaxRetryAfterSecond = 86400
)

Bounds on a domain's maintenance settings.

View Source
const (
	StateScheduled = "scheduled"
	StateActive    = "active"
	StateEnded     = "ended"
)

Window states relative to one instant.

View Source
const (
	MaxDuration    = 30 * 24 * time.Hour
	MaxLeadMinutes = 7 * 24 * 60
)

Bounds on user input.

View Source
const DefaultInterval = time.Minute

DefaultInterval is how often the announcer looks for windows entering their lead time.

View Source
const SilenceCreatorPrefix = "maintenance:"

SilenceCreatorPrefix marks a silence owned by domain maintenance; the rest of CreatedBy is the domain. Users cannot forge it: manual silences carry a user name.

Variables

View Source
var SystemActor = Actor{Type: "system", ID: "maintenance-expiry", Name: "Scheduled maintenance", ClientKind: "system"}

SystemActor is the actor for automatic ends.

Functions

func AnnounceDue

func AnnounceDue(w store.MaintenanceWindow, now time.Time) bool

AnnounceDue reports whether the lead-time notice should go out now.

func DesiredMatcher

func DesiredMatcher(app string) alerting.SilenceMatcher

DesiredMatcher is the matcher every maintenance silence for app must carry.

func DomainWindow

func DomainWindow(m store.DomainMaintenance, app string, now, until time.Time) store.MaintenanceWindow

DomainWindow is the scheduled window that carries a domain's end time, so the banner, status page and announcer see it without a second state.

func Elapsed

func Elapsed(startedAt, now time.Time) time.Duration

Elapsed is how long m has been on at now, never negative so a clock that stepped back cannot show a future start.

func EndReasonFor

func EndReasonFor(m store.DomainMaintenance, now time.Time) string

EndReasonFor says which limit ended m on its own at now.

func KeepStart

func KeepStart(prev store.DomainMaintenance, wasOn bool, now time.Time) time.Time

KeepStart carries the original start across a re-save, so toggling the settings twice does not reset how long the site has been down.

func NewAuditEntry

func NewAuditEntry(action string, d AuditDetail, a Actor, now time.Time) (store.AuditEntry, error)

NewAuditEntry builds the lifecycle audit row for action with detail.

func NormalizeBypass

func NormalizeBypass(entries []string) ([]string, error)

NormalizeBypass turns IP addresses and CIDRs into deduplicated, masked CIDRs. A catch-all range is refused: that is ending maintenance.

func NoticeText

func NoticeText(w store.MaintenanceWindow) string

NoticeText is the plain-text announcement for w.

func Overdue

func Overdue(active []store.DomainMaintenance, now time.Time, threshold time.Duration) []store.DomainMaintenance

Overdue returns the maintenance rows that have been on longer than threshold. A zero threshold turns the check off.

func ResolveAutoOff

func ResolveAutoOff(now time.Time, until *time.Time, requested *time.Duration, def time.Duration) (time.Time, error)

ResolveAutoOff returns when open ended maintenance switches itself off. A requested duration of nil takes def; zero means no limit. A dated end (until set) needs no cut-off, its window already ends it.

func SilenceDomain

func SilenceDomain(createdBy string) (string, bool)

SilenceDomain returns the domain a maintenance silence belongs to.

func SilenceEnd

func SilenceEnd(m store.DomainMaintenance, now time.Time) time.Time

SilenceEnd is when the silence for m stops: its end time, its auto-off, or a renewable default.

func StateAt

func StateAt(w store.MaintenanceWindow, now time.Time) string

StateAt reports where w is relative to now; the end instant is exclusive.

Types

type Actor

type Actor struct {
	Type, ID, Name, ClientKind, RemoteAddr string
}

Actor is who caused a maintenance change.

type AnnounceStore

type AnnounceStore interface {
	ListMaintenanceWindows(ctx context.Context, since time.Time) ([]store.MaintenanceWindow, error)
	MarkMaintenanceAnnounced(ctx context.Context, id string, at time.Time) error
}

AnnounceStore is the store surface the announcer needs.

type Announcer

type Announcer struct {
	Store    AnnounceStore
	Channels Channels
	Notifier Notifier
	Logger   *slog.Logger
	Now      func() time.Time
}

Announcer sends each window's lead-time notice once.

func (*Announcer) Run

func (a *Announcer) Run(ctx context.Context, interval time.Duration) error

Run ticks until ctx is done.

func (*Announcer) Tick

func (a *Announcer) Tick(ctx context.Context) error

Tick announces every window whose lead time has begun. A window stays unmarked when no channel accepted the notice, so the next tick retries.

type AuditDetail

type AuditDetail struct {
	Domain          string `json:"domain"`
	App             string `json:"app,omitempty"`
	StartedAt       string `json:"started_at,omitempty"`
	DurationSeconds int64  `json:"duration_seconds"`
	EndReason       string `json:"end_reason,omitempty"`
	Until           string `json:"until,omitempty"`
	AutoOffAt       string `json:"auto_off_at,omitempty"`
	Actor           string `json:"actor"`
}

AuditDetail is the JSON detail on a maintenance lifecycle audit row.

func EndedDetail

func EndedDetail(m store.DomainMaintenance, app, reason string, a Actor, now time.Time) AuditDetail

EndedDetail describes maintenance that has just ended for reason.

func StartedDetail

func StartedDetail(m store.DomainMaintenance, app string, a Actor) AuditDetail

StartedDetail describes maintenance that has just begun.

type Channels

type Channels interface {
	ListNotificationChannels(ctx context.Context) ([]alerting.NotificationChannel, error)
}

Channels lists notification channels. *alerting.DB satisfies it.

type DomainExpirer

type DomainExpirer struct {
	Store    DomainExpiryStore
	Silencer *Silencer
	Nudge    func()
	Logger   *slog.Logger
	Now      func() time.Time
}

DomainExpirer ends a domain's maintenance once its linked window ends, its auto-off passes or its domain is removed. The ingress already stops serving the page at that instant; this removes the row, audits it, lifts the alert silence and asks for a prompt reconcile.

func (*DomainExpirer) Run

func (x *DomainExpirer) Run(ctx context.Context, interval time.Duration) error

Run ticks until ctx is done.

func (*DomainExpirer) Tick

func (x *DomainExpirer) Tick(ctx context.Context) error

Tick ends every expired or orphaned domain maintenance, then syncs silences.

type DomainExpiryStore

type DomainExpiryStore interface {
	ListExpiredDomainMaintenance(ctx context.Context, now time.Time) ([]store.DomainMaintenance, error)
	DeleteDomainMaintenance(ctx context.Context, domain string) error
	SaveAuditEntry(ctx context.Context, e store.AuditEntry) error
}

DomainExpiryStore is what ending timed domain maintenance needs.

type DomainInput

type DomainInput struct {
	Title             string
	Message           string
	RetryAfterSeconds int
	// Until, when set, ends maintenance automatically through a linked window.
	Until             *time.Time
	Bypass            []string
	BypassHealthCheck bool
}

DomainInput is a request to put one domain into maintenance.

func (DomainInput) Validate

func (in DomainInput) Validate(domain string, now time.Time) (store.DomainMaintenance, error)

Validate checks in and returns the settings to store, without a window id.

type DomainOwnerStore

type DomainOwnerStore interface {
	ListActiveDomainMaintenance(ctx context.Context, now time.Time) ([]store.DomainMaintenance, error)
	ListServiceDomains(ctx context.Context) ([]store.ServiceDomain, error)
}

DomainOwnerStore is optional: with it the expirer ends maintenance on a domain that left its app and keeps alert silences in step.

type Input

type Input struct {
	Title            string
	Message          string
	Scope            string
	Target           string
	StartsAt         time.Time
	EndsAt           time.Time
	Freeze           bool
	AnnounceLeadMins int
}

Input is a request to create a window.

func (Input) Validate

func (in Input) Validate(now time.Time) (store.MaintenanceWindow, error)

Validate checks the input against now and returns the window to store.

type Notifier

type Notifier interface {
	SendNotice(ctx context.Context, c alerting.NotificationChannel, text string) error
}

Notifier sends one plain-text notice. *alerting.DeployDispatcher satisfies it.

type OngoingWindowStore

type OngoingWindowStore interface {
	EndOngoingMaintenanceWindow(ctx context.Context, id string, at time.Time) error
}

OngoingWindowStore is optional: it resolves the status page entry of open ended maintenance.

type SilenceStore

type SilenceStore interface {
	CreateSilence(ctx context.Context, s alerting.Silence) error
	ListSilences(ctx context.Context, now time.Time, includeExpired bool, limit int) ([]alerting.Silence, error)
	ExpireSilence(ctx context.Context, id string, now time.Time) (*alerting.Silence, error)
}

SilenceStore is the part of *alerting.DB the coupling needs.

type Silencer

type Silencer struct {
	Store SilenceStore
}

Silencer keeps one alert silence per domain in maintenance.

func (*Silencer) Active

func (s *Silencer) Active(ctx context.Context, domain string, now time.Time) (*alerting.Silence, error)

Active returns the active silence for domain, if any.

func (*Silencer) Discard

func (s *Silencer) Discard(ctx context.Context, id string, now time.Time) error

Discard expires one silence by id, for undoing an Open that was not committed.

func (*Silencer) Open

func (s *Silencer) Open(ctx context.Context, m store.DomainMaintenance, app string, now time.Time) (string, error)

Open creates the silence for m on app and returns its id. It never touches older silences: callers Settle once the maintenance row is saved.

func (*Silencer) Settle

func (s *Silencer) Settle(ctx context.Context, domain, keep string, now time.Time) error

Settle expires every active maintenance silence of domain except keep. An empty keep expires them all.

func (*Silencer) Sync

func (s *Silencer) Sync(ctx context.Context, active []store.DomainMaintenance, apps map[string]string, now time.Time) error

Sync makes silences match active: one fresh silence per row, none for a domain that is no longer in maintenance. It heals a crash between the two writes and an expiry that happened while the control plane was down. apps maps domain to its owning app; a row with no owner is left to the caller.

Jump to

Keyboard shortcuts

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