heartbeat

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: 20 Imported by: 0

Documentation

Overview

Package heartbeat sends an outbound dead-man ping so an external monitor alerts when the control plane stops, which no in-box alert can do.

Index

Constants

View Source
const (
	EnvURL          = "APP_HEARTBEAT_URL"
	EnvFailURL      = "APP_HEARTBEAT_FAIL_URL"
	EnvInterval     = "APP_HEARTBEAT_INTERVAL"
	EnvTimeout      = "APP_HEARTBEAT_TIMEOUT"
	EnvBody         = "APP_HEARTBEAT_BODY"
	EnvMethod       = "APP_HEARTBEAT_METHOD"
	EnvAllowPrivate = "APP_HEARTBEAT_ALLOW_PRIVATE"
	EnvReconcileMul = "APP_HEARTBEAT_RECONCILE_WINDOWS"
)

Environment variables that configure and lock the heartbeat settings.

View Source
const (
	EnvJobMaxAttempts = "APP_HEARTBEAT_JOB_MAX_ATTEMPTS"
	EnvJobBackoffBase = "APP_HEARTBEAT_JOB_BACKOFF_BASE"
	EnvJobBackoffMax  = "APP_HEARTBEAT_JOB_BACKOFF_MAX"
	EnvJobQueue       = "APP_HEARTBEAT_JOB_QUEUE"
)

Environment variables for per-job ping-out retries.

View Source
const (
	ResultOK         = "ok"
	ResultFailSent   = "fail_reported"
	ResultUnhealthy  = "unhealthy"
	ResultSendFailed = "error"
)

Result words recorded after each tick.

View Source
const (
	SourceEnv       = "env"
	SourceDashboard = "dashboard"
)

Source says where the active ping URL comes from.

Variables

View Source
var (
	// ErrNoSecrets means no master key is configured, so a URL cannot be stored.
	ErrNoSecrets = errors.New("heartbeat: no secrets store configured")
	// ErrEnvManaged means APP_HEARTBEAT_URL is set and owns the configuration.
	ErrEnvManaged = errors.New("heartbeat: configured by " + EnvURL)
)

Functions

func DeriveFailURL

func DeriveFailURL(pingURL, failURL string) string

DeriveFailURL returns the explicit fail URL, else the ping URL with /fail appended to its path and the query kept.

func HostOf

func HostOf(raw string) string

HostOf returns only the host of raw, the one part of a heartbeat URL safe to show or log.

func NewClient

func NewClient(allowPrivate bool, timeout time.Duration) *http.Client

NewClient is the guarded HTTP client for off-host pings: no proxy, no private destinations unless allowed, same-host redirects only.

func Scrub

func Scrub(err error) error

Scrub drops the URL net/http wraps into its errors because it holds the monitor's secret key.

func ValidateURL

func ValidateURL(raw string, allowPrivate bool) (*url.URL, error)

ValidateURL accepts an absolute http or https URL. Errors never echo the URL because it holds a secret key.

Types

type Config

type Config struct {
	URL          string
	FailURL      string
	Interval     time.Duration
	Timeout      time.Duration
	Method       string
	BodyOff      bool
	AllowPrivate bool
	// ReconcileWindows is how many reconcile intervals may pass without a pass before the loop counts as stalled.
	ReconcileWindows int
}

Config is the env-derived heartbeat configuration.

func ConfigFromEnv

func ConfigFromEnv(lookup func(string) (string, bool)) (Config, error)

ConfigFromEnv reads and validates the heartbeat env; an invalid URL is a boot error.

type Health

type Health func(ctx context.Context) (ok bool, reason string)

Health reports whether the control plane is fit to claim it is alive, with a short reason word when not.

type JobPingConfig

type JobPingConfig struct {
	Timeout      time.Duration
	AllowPrivate bool
	MaxAttempts  int
	BackoffBase  time.Duration
	BackoffMax   time.Duration
	Queue        int
}

JobPingConfig is the env-derived configuration of the per-job sender.

func JobPingConfigFromEnv

func JobPingConfigFromEnv(lookup func(string) (string, bool)) (JobPingConfig, error)

JobPingConfigFromEnv reads the shared heartbeat timeout and private switch plus the job retry knobs.

type JobPinger

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

JobPinger sends a per-job ping after a scheduled run settles. It never affects the run result.

func NewJobPinger

func NewJobPinger(cfg JobPingConfig, secrets Secrets, logger *slog.Logger) *JobPinger

NewJobPinger builds a sender; secrets must be non-nil.

func (*JobPinger) Clear

func (p *JobPinger) Clear(ctx context.Context, taskID string) error

Clear removes both URLs of a task.

func (*JobPinger) Host

func (p *JobPinger) Host(ctx context.Context, taskID string) (string, bool)

Host returns the ping URL host, the only part safe to show, and whether one is set.

func (*JobPinger) Notify

func (p *JobPinger) Notify(taskID, runID, status string)

Notify queues a ping for a settled run; a full queue drops it so the job is never delayed.

func (*JobPinger) Run

func (p *JobPinger) Run(ctx context.Context)

Run drains the queue until ctx ends.

func (*JobPinger) Set

func (p *JobPinger) Set(ctx context.Context, taskID, pingURL, failURL string) error

Set stores the ping URL, and the fail URL when given, encrypted. An empty pingURL clears both.

func (*JobPinger) SetTuning

func (p *JobPinger) SetTuning(f func(context.Context) JobTuning)

SetTuning makes the retry and queue limits follow a live source, such as runtime settings.

func (*JobPinger) Start

func (p *JobPinger) Start(ctx context.Context)

Start records the lifetime context and defers the drain goroutine to the first queued ping, so an idle box with no scheduled-job pings runs no worker.

type JobTuning

type JobTuning struct {
	MaxAttempts int
	BackoffBase time.Duration
	BackoffMax  time.Duration
	Queue       int
}

JobTuning is the live retry and queue limits; zero fields fall back to the boot config.

type Options

type Options struct {
	Config    Config
	Secrets   Secrets
	State     StateStore
	Health    Health
	Logger    *slog.Logger
	Version   string
	UserAgent string
}

Options carries the service's collaborators.

type Secrets

type Secrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteValue(ctx context.Context, serviceName, envKey string) error
}

Secrets is the slice of the secrets manager the heartbeat needs.

type Service

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

Service pings the configured URL while the control plane is healthy.

func New

func New(o Options) *Service

New builds a Service. Secrets may be nil, which limits configuration to the env.

func (*Service) AgentSilenceURL

func (s *Service) AgentSilenceURL(ctx context.Context) string

AgentSilenceURL returns the stored agent silence URL, or "" when none is set.

func (*Service) Apply

func (s *Service) Apply(ctx context.Context, u Update) error

Apply validates and stores an Update, then pings at once so the monitor sees the new URL.

func (*Service) Run

func (s *Service) Run(ctx context.Context)

Run pings every interval until ctx ends. It re-reads the target each tick so dashboard edits apply without a restart.

func (*Service) Status

func (s *Service) Status(ctx context.Context) (Status, error)

Status reports the current configuration and last outcome.

func (*Service) Tick

func (s *Service) Tick(ctx context.Context)

Tick runs one health check and, when a target exists, one ping.

type StateStore

type StateStore interface {
	GetHeartbeatState(ctx context.Context) (store.HeartbeatState, error)
	SaveHeartbeatState(ctx context.Context, s store.HeartbeatState) error
}

StateStore persists the last ping outcome and the body toggle.

type Status

type Status struct {
	Configured   bool      `json:"configured"`
	Source       string    `json:"source,omitempty"`
	Host         string    `json:"host,omitempty"`
	FailHost     string    `json:"fail_host,omitempty"`
	IntervalSecs int       `json:"interval_seconds"`
	BodyEnabled  bool      `json:"body_enabled"`
	CanStoreURL  bool      `json:"can_store_url"`
	LastPingAt   time.Time `json:"last_ping_at,omitzero"`
	LastResult   string    `json:"last_result,omitempty"`
	LastError    string    `json:"last_error,omitempty"`
	// AgentSilenceHost is the host agents ping when the control plane goes quiet; empty when unset.
	AgentSilenceHost string `json:"agent_silence_host,omitempty"`
}

Status is the API view of the heartbeat; it never carries a URL, only hosts.

type Update

type Update struct {
	URL     *string
	FailURL *string
	Body    *bool
	// AgentSilenceURL is the off-host URL every agent pings when it loses the control plane.
	AgentSilenceURL *string
}

Update is a partial change: nil fields stay as they are, an empty URL string clears that URL.

Jump to

Keyboard shortcuts

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