notify

package
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package notify delivers run events to notification channels: a generic webhook signed with HMAC-SHA256, a Slack incoming webhook or a Discord webhook. Deliveries are retried with backoff, every attempt is reported for the delivery log, and destinations on internal addresses are refused unless a channel explicitly allows them.

Index

Constants

View Source
const (
	KindWebhook = "webhook"
	KindSlack   = "slack"
	KindDiscord = "discord"
)

Channel kinds.

View Source
const (
	EventRunFinished     = "run.finished"
	EventRunTargetFailed = "run.target_failed"
	EventRunKilled       = "run.killed"
	// EventDriftDetected is sent when a scheduled drift check finds broken
	// journeys.
	EventDriftDetected = "drift.detected"
	// EventTest is sent by "send test" and ignores subscriptions.
	EventTest = "test"
)

Event types.

View Source
const (
	HeaderSignature = "X-Stampede-Signature"
	HeaderEvent     = "X-Stampede-Event"
	HeaderDelivery  = "X-Stampede-Delivery"
	HeaderTimestamp = "X-Stampede-Timestamp"
)

Signature headers of the generic webhook.

Variables

View Source
var DefaultBackoff = []time.Duration{2 * time.Second, 10 * time.Second, 30 * time.Second}

DefaultBackoff is the default retry schedule.

View Source
var ErrPrivateDestination = errors.New("destination is a private, loopback or link-local address")

ErrPrivateDestination is returned for a destination on a private, loopback, link-local or otherwise internal address when the channel does not allow private destinations.

Events lists the event types a channel can subscribe to.

Kinds lists the channel kinds.

Functions

func CheckURL

func CheckURL(ctx context.Context, raw string, allowPrivate bool) (*url.URL, error)

CheckURL validates a destination URL: http or https, a host, no credentials, and, unless allowPrivate, a host that does not resolve to an internal address. The check is repeated on every connection (see Client), so a DNS change cannot bypass it.

func Client

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

Client returns an HTTP client for deliveries. Unless allowPrivate, every connection is checked after DNS resolution, against the address actually dialled, so neither DNS rebinding nor a redirect can reach an internal address. Proxies from the environment are ignored for the same reason, and redirects are not followed.

func IsInternal

func IsInternal(a netip.Addr) bool

IsInternal reports whether a is an address webhooks must not reach without an explicit opt-in: loopback, private, link-local (including cloud metadata at 169.254.169.254), unspecified, multicast and reserved ranges, also when wrapped in IPv4-mapped, NAT64 or 6to4 IPv6 addresses.

func Payload

func Payload(kind string, ev Event) ([]byte, error)

Payload renders the request body for a channel kind.

func Sign

func Sign(secret, body []byte) string

Sign returns the X-Stampede-Signature value for body: "sha256=" and the hex HMAC-SHA256 of the body with the channel's secret.

func Verify

func Verify(secret, body []byte, signature string) bool

Verify checks a signature made by Sign in constant time.

Types

type Attempt

type Attempt struct {
	N        int
	OK       bool
	Status   int
	Err      string
	Duration time.Duration
	At       time.Time
}

Attempt is the outcome of one delivery attempt.

type Channel

type Channel struct {
	ID   string
	Name string
	Kind string
	URL  string
	// Secret signs generic webhook bodies.
	Secret       string
	AllowPrivate bool
}

Channel is a destination with its secrets decrypted.

type Drift

type Drift struct {
	ID       string `json:"id"`
	Project  string `json:"project,omitempty"`
	Schedule string `json:"schedule,omitempty"`
	Scenario string `json:"scenario"`
	Target   string `json:"target,omitempty"`
	// Broken lists the journeys that failed their dry run or call
	// endpoints the API no longer has.
	Broken []string `json:"broken"`
	// URL opens the result's API resource (when the server knows its
	// public URL).
	URL string `json:"url,omitempty"`
}

Drift describes a drift check that found broken journeys.

type Event

type Event struct {
	// ID identifies the delivery; retries reuse it so receivers can
	// ignore duplicates.
	ID   string    `json:"id"`
	Type string    `json:"type"`
	At   time.Time `json:"at"`
	Org  string    `json:"organisation,omitempty"`
	Run  *Run      `json:"run,omitempty"`
	// Drift is set for drift.detected.
	Drift *Drift `json:"drift,omitempty"`
	// Message is a one-line human summary.
	Message string `json:"message"`
}

Event is what happened, as sent to a generic webhook.

type Run

type Run struct {
	ID         string `json:"id"`
	Project    string `json:"project,omitempty"`
	Scenario   string `json:"scenario"`
	Target     string `json:"target,omitempty"`
	Status     string `json:"status"`
	Verdict    string `json:"verdict,omitempty"`
	StopReason string `json:"stopReason,omitempty"`
	// URL opens the run in the web UI (when the server knows its public URL).
	URL           string   `json:"url,omitempty"`
	Summary       *Summary `json:"summary,omitempty"`
	FailedTargets []string `json:"failedTargets,omitempty"`
	// KilledBy is set for run.killed.
	KilledBy string `json:"killedBy,omitempty"`
}

Run describes the run an event is about.

type Sender

type Sender struct {
	// Client returns the HTTP client for a channel; the default is Client
	// with a 15 second timeout.
	Client func(allowPrivate bool) *http.Client
	// Backoff lists the waits before each retry; its length is the number
	// of retries. The default is 2s, 10s, 30s.
	Backoff []time.Duration
	// MaxRetryAfter caps a Retry-After header (default 60s).
	MaxRetryAfter time.Duration
}

Sender delivers events.

func (*Sender) Send

func (s *Sender) Send(ctx context.Context, ch Channel, ev Event, record func(Attempt)) error

Send delivers ev to ch, retrying transport errors, 408, 425, 429 and 5xx responses with backoff. record is called after every attempt. The error is that of the last attempt.

type Summary

type Summary struct {
	Requests  uint64  `json:"requests"`
	ErrorRate float64 `json:"errorRate"`
	RPS       float64 `json:"rps"`
	P95       float64 `json:"p95"`
	P99       float64 `json:"p99"`
}

Summary holds the headline numbers of a finished run.

Jump to

Keyboard shortcuts

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