Routing: what would this message trigger?¶
Which catcher reacts to a message, and how, is decided in two places that are both normally invisible:
- Which catcher sees it at all. The streams (
REDIS_STREAMS/REDIS_STREAM) and theCONSUMER_GROUPeach component runs with. A pitcher on a stream no catcher reads still reports success. - What the catcher does with it. Its profile, in a schema of its own per catcher.
The routing package evaluates both without publishing anything. It reads
nothing itself — no Kubernetes API, no Redis. A caller, such as a config viewer,
collects the Deployments and ConfigMaps and passes Components in.
Profiles¶
| Catcher | Source | Parser | Matching |
|---|---|---|---|
| light-catcher | profile.yaml in ConfigMap homerun2-light-catcher-profile |
ParseLightProfile |
first effect in document order; systems exact or *; severity case-insensitive; every tags entry a whole element of the message tags |
| led-catcher | profile.yaml in ConfigMap homerun2-led-catcher-profile |
ParseLEDProfile |
first rule in document order; systems case-insensitive or *; severity case-insensitive, empty matches any; missing system reads as unknown, missing severity as info |
| notification-catcher | config.yaml |
ParseNotificationConfig |
every output whose filters all match: severity_min by rank, match exact case-insensitive, tags_contain / message_contains any substring |
| core-catcher, scout | — | Profile: nil |
every message |
A parser rejects what the catcher cannot load. For a catcher whose profile does
not parse, use routing.InvalidProfile{Err: err}: it reports the error on every
message instead of pretending the catcher does nothing.
Each profile is a Profile:
type Profile interface {
Evaluate(msg homerun.Message) []Reaction
Systems() []string
}
type Reaction struct {
Rule string // effect, display rule or output
Summary string // one line
Problem string // why it will not happen as configured
Details any // LightEffect, LEDDisplay or NotificationOutput
}
Problem catches rules that match but cannot act:
- light-catcher: unknown
colororfx, or noendpoint— nothing is sent - led-catcher: a severity without a color renders white, the "unknown severity"
color (led-catcher's defaults have no
critical)
A notification-catcher output's URL and headers are not kept. Knowing where
a message goes does not need the secret, and ${VAR} references are not
interpolated.
Components¶
type Component struct {
Name string // e.g. the Deployment name
Role Role // RolePitcher or RoleCatcher
Streams []string
ConsumerGroup string
Profile Profile // catchers only; nil acts on every message
}
Resolve streams from a Deployment's environment with the component's own default, which is not the same everywhere:
streams := routing.ParseStreams(env["REDIS_STREAMS"], env["REDIS_STREAM"], "alerts") // notification-catcher
Dry run¶
msg := homerun.Message{Title: "Build failed", Severity: "error", System: "github"}
for _, d := range routing.DryRun(components, "messages", msg) {
if !d.Receives {
fmt.Printf("%s: does not read the stream\n", d.Component)
continue
}
for _, r := range d.Reactions {
fmt.Printf("%s: %s %s\n", d.Component, r.Summary, r.Problem)
}
}
Every catcher appears, including those that do not read the stream, so a
message that reaches nobody is visible as such. Delivery.SharedWith lists
other catchers in the same consumer group on that stream: Redis hands each
message to only one of them.
For led-catcher, plain {{ variable }} text templates are rendered. Anything
else — filters, expressions, statements — is returned as the raw template with
TextRendered: false rather than half-rendered.
Static matrix¶
m := routing.BuildMatrix(components, "messages", nil) // nil = routing.Severities
cell, _ := m.Cell(routing.OtherSystem, "critical")
One cell per system × severity. Systems are those any rule names, plus
routing.OtherSystem for everything else. A cell evaluates a message carrying
only that system and severity, so rules that also require tags or message text
do not match in it.
Check¶
for _, f := range routing.Check(components, []string{"error", "critical"}) {
fmt.Println(f.Kind, f.Message)
}
| Kind | Meaning |
|---|---|
unread-stream |
a pitcher publishes to a stream no catcher reads |
shared-consumer-group |
different catchers read a stream in one consumer group |
uncovered-severity |
a catcher with rules does not react to one of the given severities from a system its rules do not name |
broken-rule |
a rule cannot act as configured — found whether or not a message reaches it |
Keeping in step with the catchers¶
Each evaluator mirrors a catcher's matching code. A change to how a catcher matches must be made here as well, or the dry run describes behaviour the catcher no longer has. The package's tests are ported from the catchers' own tests; the Go catchers can import the evaluators instead of keeping a copy.