Documentation
¶
Overview ¶
Package ownernotify delivers owner notifications from the server a StackKit is installed on to the channels the owner declared in the StackSpec notification intent.
The ResolvedPlan carries only a secret-free projection of each channel (name, type, destination hash, secret references). Delivery reads the destination from the canonical StackSpec on the same node, recomputes the projection and refuses a channel whose destination no longer matches the verified plan. Credentials resolve through local custody at send time and never enter a receipt, a log line or an error message.
Index ¶
- Constants
- Variables
- func ChannelDeliveryBound(channelType string) bool
- func IsLifecycleEvent(event string) bool
- func SpecChannels(canonicalStackSpec []byte) (map[string]SpecChannel, error)
- func WriteReceipt(workspace string, receipt Receipt) error
- type BackupDigestInput
- type ChannelStatus
- type LifecycleInput
- type Message
- type PlanChannel
- type PlanIntent
- type Receipt
- type SecretResolver
- type Sender
- type SpecChannel
- type Status
Constants ¶
const ( // EventBackupFailed reports a backup that cannot currently protect data. EventBackupFailed = "backup-failed" // EventRestoreVerificationOverdue reports a missing or too old staged // restore proof while snapshots are otherwise healthy. EventRestoreVerificationOverdue = "backup-restore-verification-overdue" // EventBackupDigest is the opt-in "all is well" backup summary. EventBackupDigest = "backup-digest" )
const ( // EventApplyFailed reports an Apply that stopped before the runtime converged. EventApplyFailed = "apply-failed" // EventUpgradeAvailable reports a newer StackKit release the owner can adopt. EventUpgradeAvailable = "upgrade-available" // EventDriftDetected reports a runtime that no longer matches the applied plan. EventDriftDetected = "drift-detected" // EventCertificateExpiring reports a certificate that will expire soon. EventCertificateExpiring = "certificate-expiring" )
const ( // StatusAPIVersion identifies the `notifications` projection of status. StatusAPIVersion = "stackkit.owner-notification-status/v1" // ReceiptAPIVersion identifies one delivery receipt under .stackkit/runs. ReceiptAPIVersion = "stackkit.owner-notification-receipt/v1" StateNotConfigured = "not-configured" StateConfigured = "configured" // EventVerify is the delivery check a Verify or an explicit test sends; it // is always admitted because it proves the channel rather than reporting a // lifecycle event. EventVerify = "delivery-check" )
Variables ¶
var ErrNotConfigured = errors.New("owner notifications: not configured; declare spec.notifications with at least one owner channel (ntfy, email, gotify, webhook, slack, discord or telegram)")
ErrNotConfigured reports that the verified plan declares no owner channel.
var LifecycleEvents = []string{EventApplyFailed, EventUpgradeAvailable, EventDriftDetected, EventCertificateExpiring}
LifecycleEvents are the events ComposeLifecycleEvent can write.
Functions ¶
func ChannelDeliveryBound ¶
ChannelDeliveryBound reports whether a channel type has a local delivery adapter in this package.
func IsLifecycleEvent ¶
IsLifecycleEvent reports whether event is one ComposeLifecycleEvent writes.
func SpecChannels ¶
func SpecChannels(canonicalStackSpec []byte) (map[string]SpecChannel, error)
SpecChannels returns the declared channels of a canonical StackSpec keyed by name, each paired with its recomputed plan projection.
func WriteReceipt ¶
WriteReceipt stores one owner-only receipt under .stackkit/runs.
Types ¶
type BackupDigestInput ¶
type BackupDigestInput struct {
// Configuration and Schedule are the binding states `stackkit status`
// reports (not-configured, bound, enabled, stale, unavailable, ...).
Configuration string
Schedule string
// RepositoryReady is false when the repository status could not be read
// or reports not ready; RepositoryProblem names why without a location.
RepositoryReady bool
RepositoryProblem string
// Snapshot and StagedRestore are evidence states (recorded, unverified,
// clock-skew) with their age when recorded.
SnapshotState string
SnapshotAgeSeconds *int64
StagedRestoreState string
StagedRestoreAge *int64
MaxRestoreEvidenceAge time.Duration
// ObjectivesBreached and ObjectivesUnverified count recovery objectives
// whose data-loss check is breached or unverified.
ObjectivesBreached int
ObjectivesUnverified int
// Attention carries the exact remedy commands status names.
Attention []string
}
BackupDigestInput carries the facts the backup digest reports. It holds states and ages only: no repository location, path or credential.
type ChannelStatus ¶
type ChannelStatus struct {
Name string `json:"name"`
Type string `json:"type"`
Delivery string `json:"delivery"`
}
ChannelStatus is the status view of one channel; it names no destination.
type LifecycleInput ¶
type LifecycleInput struct {
Event string
// Subject names what the event is about (a service, version or certificate).
Subject string
// Facts are the short evidence lines of the message.
Facts []string
// Remedy is the exact command the owner can run next.
Remedy string
}
LifecycleInput carries the facts of one lifecycle event. Facts are short states, counts, versions or subject names the caller owns; they are cleaned to one line and never carry a path, location or credential.
type Message ¶
Message is one owner notification. Body text must not carry secrets; callers build it from evidence summaries only.
func ComposeBackupDigest ¶
func ComposeBackupDigest(input BackupDigestInput) Message
ComposeBackupDigest selects the event and writes the owner message for the backup digest recipe. A failure outranks an overdue restore proof, which outranks the plain digest, so the owner's event allowlist decides whether a healthy week produces a message at all.
func ComposeLifecycleEvent ¶
func ComposeLifecycleEvent(input LifecycleInput) (Message, bool)
ComposeLifecycleEvent writes the owner message of a lifecycle event. It reports ok=false for an event it does not know.
type PlanChannel ¶
type PlanChannel struct {
Name string `json:"name"`
Type string `json:"type"`
DestinationHash string `json:"destinationHash"`
SecretRefs []string `json:"secretRefs"`
Delivery string `json:"delivery"`
}
PlanChannel is the secret-free plan projection of one channel.
type PlanIntent ¶
type PlanIntent struct {
APIVersion string `json:"apiVersion"`
Channels []PlanChannel `json:"channels"`
Events []string `json:"events"`
}
PlanIntent is the plan projection of the owner's notification intent.
func PlanIntentFromPlan ¶
func PlanIntentFromPlan(plan resolvedplan.ResolvedPlan) (*PlanIntent, error)
PlanIntentFromPlan decodes the notification projection of a verified plan. It returns nil when the plan declares no channel.
type Receipt ¶
type Receipt struct {
APIVersion string `json:"apiVersion"`
Event string `json:"event"`
Channel string `json:"channel"`
Type string `json:"type"`
DestinationHash string `json:"destinationHash"`
PlanHash string `json:"planHash"`
Result string `json:"result"`
Reason string `json:"reason,omitempty"`
AttemptedAt string `json:"attemptedAt"`
}
Receipt records one delivery attempt. It never carries the destination, the credential or the message body.
func LatestReceipt ¶
LatestReceipt returns the newest readable receipt, or nil.
type SecretResolver ¶
SecretResolver returns the material behind one secret reference from local custody.
type Sender ¶
type Sender struct {
HTTPClient *http.Client
// SendMail overrides SMTP delivery in tests.
SendMail func(addr string, auth smtp.Auth, from string, to []string, msg []byte) error
Now func() time.Time
}
Sender delivers notifications. The zero value uses net/http and net/smtp.
func (Sender) Deliver ¶
func (s Sender) Deliver( ctx context.Context, workspace, planHash string, intent *PlanIntent, specChannels map[string]SpecChannel, resolve SecretResolver, message Message, channelFilter string, ) ([]Receipt, error)
Deliver sends message to every plan channel that admits its event and writes one receipt per attempt. channelFilter, when set, limits delivery to that channel name. It returns the receipts and an error when any admitted channel failed.
type SpecChannel ¶
type SpecChannel struct {
Name string
Type string
DestinationHash string
Destination map[string]any
}
SpecChannel is one declared channel with its destination as authored.
type Status ¶
type Status struct {
APIVersion string `json:"apiVersion"`
State string `json:"state"`
Channels []ChannelStatus `json:"channels,omitempty"`
Events []string `json:"events,omitempty"`
// LastDelivery is the newest local delivery receipt, when one exists.
LastDelivery *Receipt `json:"lastDelivery,omitempty"`
Attention []string `json:"attention,omitempty"`
}
Status is the additive `notifications` projection of `stackkit status`.
func BuildStatus ¶
func BuildStatus(intent *PlanIntent, workspace string) *Status
BuildStatus renders the status projection. It never fails: an unreadable receipt directory only omits the last delivery.