Shared Go library module for the homerun microservice family.
| Module | Description |
|---|---|
| Message | Core Message struct with NewMessage constructor, JSON serialization and Redis JSON retrieval |
| Pitcher | Enqueue messages into Redis Streams with Redis JSON storage |
| Send | HTTP POST client for sending messages to homerun endpoints + template rendering |
| RediSearch | Full-text search indexing of messages via RediSearch (deprecated, see migration guide) |
| Startup wait | WaitForRedis / WaitForReady: retry a dependency with backoff instead of crashlooping |
| Table rendering utilities (go-pretty) | |
| Routing | routing package: what a message would trigger in which catcher, without publishing it |
| Helpers | UUID generation, random selection, environment variable utilities |
go get github.com/stuttgart-things/homerun-library/v4Upgrading from v3? The module path,
Message.Url,SendToHomerun's return type and the RediSearch schema changed. See docs/migration-v4.md — the RediSearch change needs the index recreated.
package main
import (
"fmt"
"time"
homerun "github.com/stuttgart-things/homerun-library/v4"
)
func main() {
msg := homerun.Message{
Title: "Deployment Complete",
Message: "Service xyz deployed to production",
Severity: "success",
Author: "ci-pipeline",
Timestamp: time.Now().Format(time.RFC3339),
System: "production",
Tags: "deployment,production",
}
rendered, err := homerun.RenderBody(homerun.HomeRunBodyData, msg)
if err != nil {
panic(err)
}
resp, err := homerun.SendToHomerun(
"https://homerun.example.com/generic", "my-token", []byte(rendered), false,
)
if err != nil {
panic(err)
}
if !resp.OK() {
panic("homerun rejected the message: " + resp.Status)
}
fmt.Printf("Status: %s\nBody: %s\n", resp.Status, string(resp.Body))
}objectID, streamID, err := homerun.EnqueueMessageInRedisStreams(
homerun.Message{
Title: "Build Finished",
Message: "Build #42 completed",
System: "ci",
},
homerun.RedisConfig{
Addr: "localhost",
Port: "6379",
Password: "",
Stream: "notifications",
},
)EnqueueMessageInRedisStreams opens a Redis connection and closes it again per
call. A service that publishes continuously should own one connection instead:
pitcher := homerun.NewPitcher(rc)
defer pitcher.Close()
objectID, streamID, err := pitcher.Enqueue(ctx, msg)Every function that talks to the network has a context-taking variant. The
context-free forms are wrappers passing context.Background(), so nothing
breaks if you do not need one:
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
_, err := homerun.SendToHomerunContext(ctx, dest, token, body, false)
_, _, err = homerun.EnqueueMessageInRedisStreamsContext(ctx, msg, rc)Even without a context, HTTP sends are bounded by homerun.DefaultHTTPTimeout
(30s) and Redis calls by connection deadlines. To use your own transport,
proxy, instrumentation or retry wrapper:
homerun.SetHTTPClient(&http.Client{Timeout: 5 * time.Second})A service that dials Redis exactly once at startup crashloops when Redis is
seconds away from ready - a freshly installed redis-stack took ~70s to answer.
Wait for it first, bounded by REDIS_STARTUP_TIMEOUT (a Go duration, default
120s):
timeout, err := homerun.LoadRedisStartupTimeout()
if err != nil {
log.Fatal(err) // an unparsable or non-positive value is an error, not a fallback
}
if err := homerun.WaitForRedis(rc, timeout); err != nil {
log.Fatal(err)
}WaitForRedisContext also stops on a cancelled context (e.g. SIGTERM), and
WaitForReady retries any probe with the same backoff (1s, 2s, 4s, 8s, capped at
16s). The library never exits the process; a failed wait is returned.
Add a
startupProbe. A service that waits before opening its HTTP port needs astartupProbein its Deployment coveringREDIS_STARTUP_TIMEOUT. Otherwise the liveness probe restarts it in the middle of the wait - with 10s initial delay, 10s period and 3 failures, about 40s in:startupProbe: httpGet: { path: /healthz, port: http } periodSeconds: 5 failureThreshold: 30 # 150s > REDIS_STARTUP_TIMEOUT of 120s
The library is silent by default. Install a logger to see what it is doing:
import "log/slog"
homerun.SetLogger(slog.Default())The routing package evaluates the catcher profiles (light-catcher effects,
led-catcher displayRules, notification-catcher outputs) with the catchers' own
matching rules, and the streams and consumer groups each component runs with:
import "github.com/stuttgart-things/homerun-library/v4/routing"
light, err := routing.ParseLightProfile(profileYAML)
// ...
components := []routing.Component{
{Name: "demo-pitcher", Role: routing.RolePitcher, Streams: []string{"homerun"}},
{Name: "light-catcher", Role: routing.RoleCatcher, Streams: []string{"messages"},
ConsumerGroup: "homerun2-light-catcher", Profile: light},
}
deliveries := routing.DryRun(components, "messages", msg) // one message
matrix := routing.BuildMatrix(components, "messages", nil) // severity × system
findings := routing.Check(components, []string{"error", "critical"})It reads nothing itself - no Kubernetes API, no Redis. See docs/routing.md.
id := homerun.GenerateUUID()
item := homerun.GetRandomObject([]string{"a", "b", "c"})
addr := homerun.GetEnv("REDIS_ADDR", "localhost")# Unit tests, no Redis required
go test ./...
# Integration tests, needs a running Redis (see docs/development.md)
go test -tags=integration ./...
# Full Dagger suite with a JSON report; starts Redis automatically
task test-allRun task -l for the authoritative list.
task test-all # Full Dagger test suite, fails on a failing test
task ci-run-static-checks # Lint + tests via Dagger, fails on a failing test
task govulncheck # Reachable Go vulnerabilities, fails if any are found
task lint # golangci-lint
task check # pre-commit hooks
task release # Semantic release
task tag # Commit, push & tag the moduleFull documentation is available via MkDocs:
pip install mkdocs-material
mkdocs servePatrick Hermann, stuttgart-things 10/2024
Sina Schlatter, stuttgart-things 12/2024
Licensed under the Apache License, Version 2.0. See LICENSE for details.