Help us shape the future of Go! Take the 2026 Go Developer Survey

maintenance

package
v0.5.3 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package maintenance controls admission while existing work retains its normal resource ownership and cancellation rules. It owns no servers or goroutines.

Index

Constants

View Source
const (
	// MaxRules bounds configured plus shared exemption rules separately.
	MaxRules = 64
	// MaxAllowedPrefixes bounds configured plus shared network allow lists separately.
	MaxAllowedPrefixes = 64
	// MaxMessageBytes bounds the operator message rendered in maintenance responses.
	MaxMessageBytes = 512
	// MaxRetryAfter bounds the advertised Retry-After interval.
	MaxRetryAfter = 24 * time.Hour
	// DefaultBypassTTL is the lifetime of a secret bypass cookie.
	DefaultBypassTTL = 12 * time.Hour
	// MaxBypassTTL bounds configured bypass cookie lifetimes.
	MaxBypassTTL = 7 * 24 * time.Hour
	// BypassCookie names the signed cookie that admits a browser holding the secret.
	BypassCookie = "foundry_maintenance"
)
View Source
const (
	// DefaultPollInterval is how often an instance reloads the shared state.
	DefaultPollInterval = 5 * time.Second
	// MinPollInterval and MaxPollInterval bound shared-state polling.
	MinPollInterval = 100 * time.Millisecond
	MaxPollInterval = 10 * time.Minute
)

Variables

View Source
var ErrDraining = safeerror.New("application is draining")
View Source
var ErrMaintenance = safeerror.New("application is in maintenance mode")

Functions

func NewSecret

func NewSecret() (string, error)

NewSecret returns a random URL-safe bypass secret for an operator.

func Publish

func Publish(ctx context.Context, gate *Gate, store Store, state State) error

Publish saves shared state and then applies it to the local gate, so the issuing process observes the same record as polling instances.

func Refresh

func Refresh(ctx context.Context, gate *Gate, store Store) error

Refresh loads the shared record once and applies it. An absent record leaves the local gate unchanged, so configured initial maintenance survives until an operator first saves shared state. Draining gates ignore the record.

func Watch

func Watch(ctx context.Context, gate *Gate, store Store, interval time.Duration, report func(error)) error

Watch refreshes the gate every interval until ctx ends and then returns nil. A failed load retains the last applied state; report receives the first failure after each success (it may be nil) so a store outage is visible without logging every poll. Transient failures never stop the application.

func WithContext

func WithContext(ctx context.Context, gate *Gate) context.Context

WithContext carries the application's admission gate. Kernels derive request, job and command contexts from the application runtime, so every admission check shares one gate even when observability is disabled. A nil gate clears an inherited gate and represents normal serving.

Types

type Gate

type Gate struct {
	// contains filtered or unexported fields
}

Gate is application-owned and concurrency-safe. Pausing admission is reversible; Drain is terminal. A nil optional gate represents normal serving. Admission checks do not cancel existing work or revoke acquired resources. Do not copy a Gate after first use. The zero value is ready to use and has an empty Policy; New validates and snapshots configured exemptions.

func FromContext

func FromContext(ctx context.Context) *Gate

FromContext returns the carried gate, or nil (normal serving) when absent.

func New

func New(policy Policy) (*Gate, error)

New constructs a serving gate retaining a validated Policy snapshot.

func (*Gate) Admit

func (g *Gate) Admit() error

Admit returns the current maintenance or draining error, or nil when serving.

func (*Gate) Apply

func (g *Gate) Apply(state State) error

Apply replaces the operator state, pausing or resuming admission. A shared store applies the same record on every instance. Drain remains terminal.

func (*Gate) Bypass

func (g *Gate) Bypass(ctx context.Context, cookie string, now time.Time) bool

Bypass reports whether a paused gate admits the holder of a signed cookie.

func (*Gate) Drain

func (g *Gate) Drain()

Drain permanently stops admission and wakes waiting workers. It is idempotent.

func (*Gate) Exempts

func (g *Gate) Exempts(method, path string, ip netip.Addr) bool

Exempts reports whether a paused gate admits this request through a configured or shared rule or allowed network. It is false unless the gate is paused, so exemptions never reopen a draining application.

func (*Gate) IssueBypass

func (g *Gate) IssueBypass(ctx context.Context, candidate string, now time.Time) (string, time.Time, bool)

IssueBypass signs a bypass cookie when a paused gate's shared secret matches candidate. The cookie lifetime comes from the configured Policy.

func (*Gate) Mode

func (g *Gate) Mode() Mode

Mode returns the synchronized serving mode; a nil gate is serving.

func (*Gate) Policy

func (g *Gate) Policy() Policy

Policy returns an owned snapshot of the configured exemptions.

func (*Gate) Set

func (g *Gate) Set(paused bool) error

Set pauses or resumes admission. Existing work continues under its original lifetime; application shutdown must still drain the owning kernels/resources.

func (*Gate) State

func (g *Gate) State() State

State returns an owned snapshot of the applied operator state. Down reports whether admission is currently paused; a draining gate reports Down true.

func (*Gate) Wait

func (g *Gate) Wait(ctx context.Context) error

Wait holds new worker admission while paused. It owns no goroutine and wakes on resume, terminal drain or caller cancellation. HTTP admission should use Admit and respond promptly instead of holding a request until maintenance ends.

type Mode

type Mode string

Mode identifies the maintenance serving mode.

const (
	Serving  Mode = "serving"
	Paused   Mode = "maintenance"
	Draining Mode = "draining"
)

type Policy

type Policy struct {
	Exempt    []Rule
	Allow     []netip.Prefix
	BypassTTL time.Duration
	// Keys seal bypass cookies so every instance sharing them accepts one;
	// typically the application key ring. Without keys a cookie is valid only
	// on the instance that issued it.
	Keys *encryption.Keyring
}

Policy is deployment configuration retained by one Gate. It never changes after construction; shared operator State adds its own rules and networks.

func (Policy) Validate

func (p Policy) Validate() error

Validate validates exemption rules, allowed addresses, optional keys, and bypass lifetime.

type Rule

type Rule struct {
	Method string `json:"method,omitempty"`
	Path   string `json:"path"`
	Prefix bool   `json:"prefix,omitempty"`
}

Rule exempts matching requests from a paused gate. Method is an uppercase HTTP method or empty for every method. Path is a canonical absolute path; Prefix also matches every descendant segment ("/status" matches "/status/db", never "/statuses"). Exemptions never bypass Draining.

func ParseRule

func ParseRule(text string) (Rule, error)

ParseRule reads the operator text form "[METHOD ]/path[/*]". A trailing "/*" selects prefix matching ("/*" alone exempts every path).

func (Rule) Matches

func (r Rule) Matches(method, path string) bool

Matches compares an unescaped request path; callers reject escaped paths.

func (Rule) String

func (r Rule) String() string

String returns the ParseRule text form.

func (Rule) Validate

func (r Rule) Validate() error

Validate validates the optional uppercase method and canonical absolute path.

type SecretDigest

type SecretDigest [32]byte

SecretDigest is the SHA-256 digest of a bypass secret. Stores retain only the digest; the plaintext secret is shown once to the operator who created it.

func DigestSecret

func DigestSecret(secret string) (SecretDigest, error)

DigestSecret validates a bypass secret: 16 to 128 URL-safe characters (letters, digits, '-' and '_'), usable as one request path segment.

func (SecretDigest) IsZero

func (d SecretDigest) IsZero() bool

IsZero reports whether the digest is all zero bytes.

func (SecretDigest) LogValue

func (SecretDigest) LogValue() slog.Value

LogValue returns a fixed log label without exposing the digest.

func (SecretDigest) MarshalText

func (d SecretDigest) MarshalText() ([]byte, error)

MarshalText encodes the digest as lowercase hexadecimal, or empty text for the zero value.

func (*SecretDigest) UnmarshalText

func (d *SecretDigest) UnmarshalText(text []byte) error

UnmarshalText decodes lowercase hexadecimal, treating empty text as the zero digest.

type State

type State struct {
	Down       bool
	RetryAfter time.Duration
	Message    string
	Secret     SecretDigest
	Allow      []netip.Prefix
	Exempt     []Rule
	Since      time.Time
}

State is the operator-controlled maintenance record shared by a fleet. Down pauses admission. RetryAfter is advertised in whole seconds; Message is an operator-supplied public text. Allow and Exempt add to the configured Policy.

func (State) MarshalJSON

func (s State) MarshalJSON() ([]byte, error)

MarshalJSON is the versioned shared-store representation. It contains the secret digest, never the secret itself.

func (State) MatchesSecret

func (s State) MatchesSecret(candidate string) bool

MatchesSecret compares a candidate secret with the stored digest in constant time.

func (*State) UnmarshalJSON

func (s *State) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a bounded versioned maintenance record and replaces the receiver only after validation.

func (State) Validate

func (s State) Validate() error

Validate checks retry duration, message text, rules, and the absence of maintenance options when serving.

type Store

type Store interface {
	// Load loads maintenance state and reports whether a record exists.
	Load(context.Context) (State, bool, error)
	// Save persists the supplied maintenance state.
	Save(context.Context, State) error
}

Store shares one operator State across a fleet, for example through a Redis or PostgreSQL cache store. Load reports found=false before any State was saved. Implementations must honor context cancellation and bound their I/O. Admission never reads the store: each instance applies polled records to its local Gate, which is the cache consulted on every request, job and command.

Directories

Path Synopsis
Package cachestore shares maintenance state through an existing configured cache store.
Package cachestore shares maintenance state through an existing configured cache store.

Jump to

Keyboard shortcuts

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