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
- Variables
- func CheckURL(ctx context.Context, raw string, allowPrivate bool) (*url.URL, error)
- func Client(allowPrivate bool, timeout time.Duration) *http.Client
- func IsInternal(a netip.Addr) bool
- func Payload(kind string, ev Event) ([]byte, error)
- func Sign(secret, body []byte) string
- func Verify(secret, body []byte, signature string) bool
- type Attempt
- type Channel
- type Drift
- type Event
- type Run
- type Sender
- type Summary
Constants ¶
const ( KindWebhook = "webhook" KindSlack = "slack" KindDiscord = "discord" )
Channel kinds.
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.
const ( HeaderSignature = "X-Stampede-Signature" HeaderEvent = "X-Stampede-Event" HeaderDelivery = "X-Stampede-Delivery" HeaderTimestamp = "X-Stampede-Timestamp" )
Signature headers of the generic webhook.
Variables ¶
var DefaultBackoff = []time.Duration{2 * time.Second, 10 * time.Second, 30 * time.Second}
DefaultBackoff is the default retry schedule.
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.
var Events = []string{EventRunFinished, EventRunTargetFailed, EventRunKilled, EventDriftDetected}
Events lists the event types a channel can subscribe to.
var Kinds = []string{KindWebhook, KindSlack, KindDiscord}
Kinds lists the channel kinds.
Functions ¶
func CheckURL ¶
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 ¶
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 ¶
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.
Types ¶
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.