Initial commit
This commit is contained in:
commit
2ee9e93bf6
15 changed files with 1047 additions and 0 deletions
46
checker/collect.go
Normal file
46
checker/collect.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand/v2"
|
||||
"time"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// Collect gathers observation data. This is called by happyDomain (or the
|
||||
// /collect HTTP endpoint) every time a check runs.
|
||||
//
|
||||
// In a real checker, this is where you would perform the actual monitoring
|
||||
// work: sending network requests, querying APIs, measuring latency, etc.
|
||||
//
|
||||
// This dummy implementation simply reads options and generates a random score
|
||||
// so you can focus on the structure rather than external dependencies.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: a context for cancellation/timeout - always honour it.
|
||||
// - opts: the merged checker options (admin + user + domain + service + run).
|
||||
//
|
||||
// Return:
|
||||
// - any: the observation data (will be JSON-serialised by the SDK).
|
||||
// - error: non-nil if collection failed entirely.
|
||||
func (p *dummyProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any, error) {
|
||||
// Read user-configurable options using the SDK helpers.
|
||||
// These helpers handle type coercion gracefully - the value may come as
|
||||
// a native Go type (in-process plugin) or as a JSON-decoded float64/string
|
||||
// (external HTTP mode). The helpers normalise both cases.
|
||||
message := "Hello from the dummy checker!"
|
||||
if v, ok := sdk.GetOption[string](opts, "message"); ok && v != "" {
|
||||
message = v
|
||||
}
|
||||
|
||||
// Generate a random score between 0 and 100 to simulate a measurement.
|
||||
// In your real checker, replace this with actual monitoring logic.
|
||||
score := rand.Float64() * 100
|
||||
|
||||
return &DummyData{
|
||||
Message: message,
|
||||
Score: score,
|
||||
CollectedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
116
checker/definition.go
Normal file
116
checker/definition.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// Version is the checker version reported in CheckerDefinition.Version.
|
||||
//
|
||||
// It defaults to "built-in", which is appropriate when the checker package is
|
||||
// imported directly (built-in or plugin mode). Standalone binaries (like
|
||||
// main.go) should override this from their own Version variable at the start
|
||||
// of main(), which makes it easy for CI to inject a version with a single
|
||||
// -ldflags "-X main.Version=..." flag instead of targeting the nested
|
||||
// package path.
|
||||
var Version = "built-in"
|
||||
|
||||
// Definition returns the CheckerDefinition for the dummy checker.
|
||||
//
|
||||
// A CheckerDefinition tells happyDomain everything it needs to know about
|
||||
// your checker: its identity, where it can be applied, what options it
|
||||
// accepts, what rules it provides, and how often it should run.
|
||||
func Definition() *sdk.CheckerDefinition {
|
||||
return &sdk.CheckerDefinition{
|
||||
// ID is a unique, stable identifier for this checker. It is stored in
|
||||
// the database, so never change it after release.
|
||||
ID: "dummy",
|
||||
|
||||
// Name is the human-readable label shown in the happyDomain UI.
|
||||
Name: "Dummy (example)",
|
||||
|
||||
// Version is an optional version string for this checker. It is
|
||||
// surfaced in the UI/API and is useful to track which iteration of
|
||||
// your checker produced a given observation. The value is injected
|
||||
// at build time via -ldflags "-X .../checker.Version=...".
|
||||
Version: Version,
|
||||
|
||||
// Availability controls where this checker appears in the UI.
|
||||
// A checker can apply at the domain level, zone level, or service
|
||||
// level. You can also restrict it to specific service types.
|
||||
//
|
||||
// Here we apply it at the domain level, which means users will see
|
||||
// this checker in the "Domain checks" section and it does not require
|
||||
// a specific service to be present.
|
||||
Availability: sdk.CheckerAvailability{
|
||||
ApplyToDomain: true,
|
||||
},
|
||||
|
||||
// ObservationKeys lists the keys this checker produces. This ties
|
||||
// the definition to the provider(s) that generate the data.
|
||||
ObservationKeys: []sdk.ObservationKey{ObservationKeyDummy},
|
||||
|
||||
// Options documents what configuration the checker accepts. Options
|
||||
// are grouped by audience (admin, user, domain, service, run):
|
||||
//
|
||||
// - AdminOpts: set once by the happyDomain administrator
|
||||
// - UserOpts: editable by end-users in the checker settings UI
|
||||
// - DomainOpts: auto-filled per domain (domain_name, etc.)
|
||||
// - ServiceOpts: auto-filled per service (the service payload)
|
||||
// - RunOpts: set at collect-time only (e.g., overrides)
|
||||
//
|
||||
// Each option has an Id (used as the key in CheckerOptions), a Type
|
||||
// for the UI widget, a Label, and optionally a Default value.
|
||||
Options: sdk.CheckerOptionsDocumentation{
|
||||
UserOpts: []sdk.CheckerOptionDocumentation{
|
||||
{
|
||||
Id: "message",
|
||||
Type: "string",
|
||||
Label: "Custom message",
|
||||
Description: "A message that will be included in the observation data.",
|
||||
Default: "Hello from the dummy checker!",
|
||||
},
|
||||
{
|
||||
Id: "warningThreshold",
|
||||
Type: "number",
|
||||
Label: "Warning threshold (score)",
|
||||
Description: "If the score drops below this value, the check status becomes Warning.",
|
||||
Default: float64(50),
|
||||
},
|
||||
{
|
||||
Id: "criticalThreshold",
|
||||
Type: "number",
|
||||
Label: "Critical threshold (score)",
|
||||
Description: "If the score drops below this value, the check status becomes Critical.",
|
||||
Default: float64(20),
|
||||
},
|
||||
},
|
||||
DomainOpts: []sdk.CheckerOptionDocumentation{
|
||||
{
|
||||
Id: "domain_name",
|
||||
Label: "Domain name",
|
||||
AutoFill: sdk.AutoFillDomainName,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Rules lists the evaluation rules provided by this checker. Each
|
||||
// rule will appear in the UI, and users can enable/disable them
|
||||
// individually.
|
||||
Rules: []sdk.CheckRule{
|
||||
Rule(),
|
||||
},
|
||||
|
||||
// Interval specifies how often the check should run.
|
||||
Interval: &sdk.CheckIntervalSpec{
|
||||
Min: 1 * time.Minute,
|
||||
Max: 1 * time.Hour,
|
||||
Default: 5 * time.Minute,
|
||||
},
|
||||
|
||||
// HasMetrics indicates that this checker can produce time-series
|
||||
// metrics (because our provider implements CheckerMetricsReporter).
|
||||
HasMetrics: true,
|
||||
}
|
||||
}
|
||||
61
checker/provider.go
Normal file
61
checker/provider.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// Provider returns a new dummy observation provider.
|
||||
//
|
||||
// The provider is the central object of a checker. It implements the
|
||||
// ObservationProvider interface (required) and can optionally implement
|
||||
// additional interfaces to unlock more features:
|
||||
//
|
||||
// - CheckerDefinitionProvider → exposes /definition and /evaluate endpoints
|
||||
// - CheckerMetricsReporter → exposes /report (JSON metrics) endpoint
|
||||
// - CheckerHTMLReporter → exposes /report (HTML) endpoint
|
||||
//
|
||||
// In this example, the provider implements all three optional interfaces
|
||||
// so you can see how each one works.
|
||||
func Provider() sdk.ObservationProvider {
|
||||
return &dummyProvider{}
|
||||
}
|
||||
|
||||
// dummyProvider is the concrete type that satisfies the ObservationProvider
|
||||
// interface and the optional reporter interfaces.
|
||||
type dummyProvider struct{}
|
||||
|
||||
// Key returns the observation key for this provider. This must match the key
|
||||
// used in your CheckerDefinition's ObservationKeys list so happyDomain knows
|
||||
// which provider produces which data.
|
||||
func (p *dummyProvider) Key() sdk.ObservationKey {
|
||||
return ObservationKeyDummy
|
||||
}
|
||||
|
||||
// Definition implements sdk.CheckerDefinitionProvider.
|
||||
// Returning a definition enables the /definition and /evaluate HTTP endpoints
|
||||
// in the SDK server, and lets happyDomain discover this checker's metadata.
|
||||
func (p *dummyProvider) Definition() *sdk.CheckerDefinition {
|
||||
return Definition()
|
||||
}
|
||||
|
||||
// ExtractMetrics implements sdk.CheckerMetricsReporter.
|
||||
// This is called when happyDomain (or the /report endpoint) needs to turn
|
||||
// raw observation data into time-series metrics for graphing.
|
||||
func (p *dummyProvider) ExtractMetrics(raw json.RawMessage, collectedAt time.Time) ([]sdk.CheckMetric, error) {
|
||||
var data DummyData
|
||||
if err := json.Unmarshal(raw, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []sdk.CheckMetric{
|
||||
{
|
||||
Name: "dummy_score",
|
||||
Value: data.Score,
|
||||
Unit: "points",
|
||||
Timestamp: collectedAt,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
96
checker/rule.go
Normal file
96
checker/rule.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// Rule returns a new dummy check rule.
|
||||
//
|
||||
// A rule evaluates collected observation data and returns a status (OK,
|
||||
// Warning, Critical, Error). Each checker can define multiple rules that
|
||||
// inspect the same data from different angles.
|
||||
func Rule() sdk.CheckRule {
|
||||
return &dummyRule{}
|
||||
}
|
||||
|
||||
// dummyRule implements the sdk.CheckRule interface.
|
||||
type dummyRule struct{}
|
||||
|
||||
// Name returns a unique, stable identifier for this rule. It is used as the
|
||||
// "code" field in check results and stored in the database.
|
||||
func (r *dummyRule) Name() string { return "dummy_score_check" }
|
||||
|
||||
// Description returns a human-readable summary of what this rule checks.
|
||||
func (r *dummyRule) Description() string {
|
||||
return "Checks whether the dummy score is above the configured thresholds"
|
||||
}
|
||||
|
||||
// ValidateOptions is called before evaluation to verify that the options are
|
||||
// well-formed. Return an error to reject invalid configuration early, before
|
||||
// any data collection happens.
|
||||
func (r *dummyRule) ValidateOptions(opts sdk.CheckerOptions) error {
|
||||
warning := sdk.GetFloatOption(opts, "warningThreshold", 50)
|
||||
critical := sdk.GetFloatOption(opts, "criticalThreshold", 20)
|
||||
|
||||
if warning < 0 || warning > 100 {
|
||||
return fmt.Errorf("warningThreshold must be between 0 and 100")
|
||||
}
|
||||
if critical < 0 || critical > 100 {
|
||||
return fmt.Errorf("criticalThreshold must be between 0 and 100")
|
||||
}
|
||||
if critical >= warning {
|
||||
return fmt.Errorf("criticalThreshold (%v) must be less than warningThreshold (%v)", critical, warning)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate inspects the collected observation data and returns a CheckState.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: context for cancellation.
|
||||
// - obs: an ObservationGetter to retrieve collected data by key.
|
||||
// - opts: the merged checker options.
|
||||
//
|
||||
// The ObservationGetter.Get method deserialises the stored JSON into your data
|
||||
// struct. Always check the error: the observation may not be available if
|
||||
// collection failed.
|
||||
func (r *dummyRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) sdk.CheckState {
|
||||
// Retrieve the observation data by key.
|
||||
var data DummyData
|
||||
if err := obs.Get(ctx, ObservationKeyDummy, &data); err != nil {
|
||||
return sdk.CheckState{
|
||||
Status: sdk.StatusError,
|
||||
Message: fmt.Sprintf("Failed to get dummy data: %v", err),
|
||||
Code: "dummy_error",
|
||||
}
|
||||
}
|
||||
|
||||
// Read thresholds from options.
|
||||
warningThreshold := sdk.GetFloatOption(opts, "warningThreshold", 50)
|
||||
criticalThreshold := sdk.GetFloatOption(opts, "criticalThreshold", 20)
|
||||
|
||||
// Determine the status based on the score and thresholds.
|
||||
var status sdk.Status
|
||||
switch {
|
||||
case data.Score < criticalThreshold:
|
||||
status = sdk.StatusCrit
|
||||
case data.Score < warningThreshold:
|
||||
status = sdk.StatusWarn
|
||||
default:
|
||||
status = sdk.StatusOK
|
||||
}
|
||||
|
||||
return sdk.CheckState{
|
||||
Status: status,
|
||||
Message: fmt.Sprintf("Score: %.1f - %s", data.Score, data.Message),
|
||||
Code: "dummy_score_check",
|
||||
Meta: map[string]any{
|
||||
"score": data.Score,
|
||||
"message": data.Message,
|
||||
},
|
||||
}
|
||||
}
|
||||
33
checker/types.go
Normal file
33
checker/types.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Package checker implements a dummy checker for happyDomain.
|
||||
//
|
||||
// This is an educational example that demonstrates all the building blocks
|
||||
// needed to create a happyDomain checker. It performs no real monitoring;
|
||||
// instead, it returns a configurable message and a random score, so you can
|
||||
// focus on the structure without worrying about external dependencies.
|
||||
package checker
|
||||
|
||||
import "time"
|
||||
|
||||
// ObservationKeyDummy is the unique key that identifies observations
|
||||
// produced by this checker. Every checker must define at least one key so
|
||||
// happyDomain can store and retrieve its data.
|
||||
const ObservationKeyDummy = "dummy"
|
||||
|
||||
// DummyData is the data structure returned by Collect.
|
||||
//
|
||||
// When happyDomain collects an observation, it serialises this struct to JSON
|
||||
// and stores it. Later, during evaluation, the same JSON is deserialised back
|
||||
// into this struct. Design this type to hold everything your rules will need
|
||||
// to decide OK / Warning / Critical.
|
||||
type DummyData struct {
|
||||
// Message is an arbitrary string returned as part of the observation.
|
||||
Message string `json:"message"`
|
||||
|
||||
// Score is a number between 0 and 100. The evaluation rules compare it
|
||||
// against user-defined thresholds to determine the check status.
|
||||
Score float64 `json:"score"`
|
||||
|
||||
// CollectedAt records when the observation was taken. It is used by the
|
||||
// metrics reporter to timestamp the extracted metrics.
|
||||
CollectedAt time.Time `json:"collected_at"`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue