Compare commits

...

No commits in common. "v0.1.0" and "master" have entirely different histories.

12 changed files with 219 additions and 233 deletions

View file

@ -10,5 +10,8 @@ RUN CGO_ENABLED=0 go build -ldflags "-X main.Version=${CHECKER_VERSION}" -o /che
FROM scratch
COPY --from=builder /checker-dummy /checker-dummy
USER 65534:65534
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/checker-dummy", "-healthcheck"]
ENTRYPOINT ["/checker-dummy"]

View file

@ -6,7 +6,7 @@ CHECKER_SOURCES := main.go $(wildcard checker/*.go)
GO_LDFLAGS := -X main.Version=$(CHECKER_VERSION)
.PHONY: all plugin docker clean
.PHONY: all plugin docker test clean
all: $(CHECKER_NAME)
@ -21,5 +21,8 @@ $(CHECKER_NAME).so: $(CHECKER_SOURCES) $(wildcard plugin/*.go)
docker:
docker build --build-arg CHECKER_VERSION=$(CHECKER_VERSION) -t $(CHECKER_IMAGE) .
test:
go test -tags standalone ./...
clean:
rm -f $(CHECKER_NAME) $(CHECKER_NAME).so

26
NOTICE Normal file
View file

@ -0,0 +1,26 @@
checker-dummy
Copyright (c) 2026 The happyDomain Authors
This product is licensed under the MIT License (see LICENSE).
-------------------------------------------------------------------------------
Third-party notices
-------------------------------------------------------------------------------
This product includes software developed as part of the checker-sdk-go
project (https://git.happydns.org/happyDomain/checker-sdk-go), licensed
under the Apache License, Version 2.0:
checker-sdk-go
Copyright 2020-2026 The happyDomain Authors
This product includes software developed as part of the happyDomain
project (https://happydomain.org).
Portions of this code were originally written for the happyDomain
server (licensed under AGPL-3.0 and a commercial license) and are
made available there under the Apache License, Version 2.0 to enable
a permissively licensed ecosystem of checker plugins.
You may obtain a copy of the Apache License 2.0 at:
http://www.apache.org/licenses/LICENSE-2.0

187
README.md
View file

@ -19,11 +19,12 @@ Use this as a template when you create your own checker.
- [Step 5: Write Evaluation Rules](#step-5-write-evaluation-rules)
- [Step 6: Wire It Up (main.go)](#step-6-wire-it-up-maingo)
- [Step 7: Create the Plugin Entrypoint](#step-7-create-the-plugin-entrypoint)
5. [Running the Checker](#running-the-checker)
6. [Testing with curl](#testing-with-curl)
7. [Deploying to happyDomain](#deploying-to-happydomain)
8. [License & happyDomain compatibility](#license--happydomain-compatibility)
9. [Going Further](#going-further)
5. [Optional: Standalone Human UI (`CheckerInteractive`)](#optional-standalone-human-ui-checkerinteractive)
6. [Running the Checker](#running-the-checker)
7. [Testing with curl](#testing-with-curl)
8. [Deploying to happyDomain](#deploying-to-happydomain)
9. [License & happyDomain compatibility](#license--happydomain-compatibility)
10. [Going Further](#going-further)
---
@ -40,18 +41,30 @@ Every checker does three things:
## Architecture Overview
A checker can run in two modes:
A checker can run in three modes:
### Standalone HTTP Server (External Checker)
### Built-in Checker
The checker runs as its own process and exposes an HTTP API. happyDomain communicates with it over the network. This is the most flexible option: you can write your checker in any language, deploy it independently, and scale it separately.
The checker package can be imported directly into the happyDomain server and registered at init time: no plugin loading, no separate process. This avoids the operational burden of Go's plugin system (matching toolchain versions, CGO, `.so` distribution) entirely.
```go
// happydomain/checkers/ping.go
package checkers
import (
ping "git.happydns.org/checker-ping/checker"
"git.happydns.org/internal/checker"
)
func init() {
checker.RegisterObservationProvider(ping.Provider())
checker.RegisterExternalizableChecker(ping.Definition())
}
```
┌─────────────┐ HTTP ┌─────────────────┐
│ happyDomain │ ──────────► │ checker-dummy │
│ server │ ◄────────── │ (this program) │
└─────────────┘ └─────────────────┘
```
This mode is reserved for checkers maintained as part of the happyDomain project itself. Or if you compile yourself your own version of happyDomain.
**Both standalone, plugin and built-in modes use the same checker code; only the entry point differs.**
### In-Process Plugin
@ -70,28 +83,16 @@ The checker is compiled as a Go plugin (`.so` file) and loaded directly into the
└──────────────────────────────────────┘
```
### Built-in Checker
### Standalone HTTP Server (External Checker)
The checker package can be imported directly into the happyDomain server and registered at init time: no plugin loading, no separate process. This avoids the operational burden of Go's plugin system (matching toolchain versions, CGO, `.so` distribution) entirely.
The checker runs as its own process and exposes an HTTP API. happyDomain communicates with it over the network. This is the most flexible option: you can write your checker in any language, deploy it independently, and scale it separately.
```go
// happydomain/checkers/ping.go
package checkers
import (
ping "git.happydns.org/happyDomain/checker-ping/checker"
"git.happydns.org/happyDomain/internal/checker"
)
func init() {
checker.RegisterObservationProvider(ping.Provider())
checker.RegisterExternalizableChecker(ping.Definition())
}
```
This mode is reserved for checkers maintained as part of the happyDomain project itself. Or if you compile yourself your own version of happyDomain.
**Both standalone, plugin and built-in modes use the same checker code; only the entry point differs.**
┌─────────────┐ HTTP ┌─────────────────┐
│ happyDomain │ ──────────► │ checker-dummy │
│ server │ ◄────────── │ (this program) │
└─────────────┘ └─────────────────┘
```
## Repository Structure
@ -160,6 +161,7 @@ You can also implement optional interfaces to unlock additional features:
| `CheckerDefinitionProvider` | `/definition` and `/evaluate` endpoints |
| `CheckerMetricsReporter` | `/report` endpoint (JSON metrics) |
| `CheckerHTMLReporter` | `/report` endpoint (HTML) |
| `CheckerInteractive` | `GET`/`POST /check` human-friendly HTML UI |
In this example, we implement all three optional interfaces:
@ -291,19 +293,19 @@ A rule implements the `CheckRule` interface:
type CheckRule interface {
Name() string
Description() string
Evaluate(ctx context.Context, obs ObservationGetter, opts CheckerOptions) CheckState
Evaluate(ctx context.Context, obs ObservationGetter, opts CheckerOptions) []CheckState
}
```
Optionally, your rule can also implement `ValidateOptions(opts) error` for early validation.
The `Evaluate` method receives an `ObservationGetter` to retrieve the collected data:
The `Evaluate` method receives an `ObservationGetter` to retrieve the collected data and returns a **slice** of `CheckState`, one entry per element being evaluated:
```go
func (r *dummyRule) Evaluate(ctx context.Context, obs ObservationGetter, opts CheckerOptions) CheckState {
func (r *dummyRule) Evaluate(ctx context.Context, obs ObservationGetter, opts CheckerOptions) []CheckState {
var data DummyData
if err := obs.Get(ctx, ObservationKeyDummy, &data); err != nil {
return CheckState{Status: StatusError, Message: "..."}
return []CheckState{{Status: StatusError, Message: "..."}}
}
warningThreshold := sdk.GetFloatOption(opts, "warningThreshold", 50)
@ -311,16 +313,52 @@ func (r *dummyRule) Evaluate(ctx context.Context, obs ObservationGetter, opts Ch
switch {
case data.Score < criticalThreshold:
return CheckState{Status: StatusCrit, ...}
return []CheckState{{Status: StatusCrit, ...}}
case data.Score < warningThreshold:
return CheckState{Status: StatusWarn, ...}
return []CheckState{{Status: StatusWarn, ...}}
default:
return CheckState{Status: StatusOK, ...}
return []CheckState{{Status: StatusOK, ...}}
}
}
```
**Status values**: `StatusOK`, `StatusWarn`, `StatusCrit`, `StatusError`, `StatusUnknown`.
**Contract**: `Evaluate` must return at least one state. If a rule has nothing to evaluate, return a single `CheckState` describing that fact (typically `StatusInfo` or `StatusOK`). The SDK server injects a `StatusUnknown` placeholder if a rule returns an empty or nil slice.
**The `CheckState` struct**:
```go
type CheckState struct {
Status Status
Message string
RuleName string // set automatically by the server, do not set yourself
Code string // optional, use to distinguish kinds of finding within one rule
Subject string // opaque per-element identifier (hostname, cert serial, …)
Meta map[string]any
}
```
- **`Subject`** identifies the element a state refers to (a hostname, a certificate serial, a nameserver FQDN, …). Leave empty for rules that produce a single global result. Do **not** repeat the subject inside `Message`, the UI renders it separately.
- **`RuleName`** is stamped automatically by the server with `rule.Name()` on every returned state. UIs should use `RuleName` (not `Code`) to group, filter, or offer "disable this rule" controls.
- **`Code`** is left untouched by the server. Set it only when your rule emits several kinds of finding (e.g. `too_many_lookups` vs `syntax_error`).
**One state per subject**: a rule that iterates over N elements should emit N states (one per `Subject`) instead of concatenating them into a single `Message`:
```go
func (r *CertExpiryRule) Evaluate(...) []CheckState {
out := make([]CheckState, 0, len(certs))
for _, cert := range certs {
s := evalCert(cert)
s.Subject = cert.Host
out = append(out, s)
}
if len(out) == 0 {
return []CheckState{{Status: StatusInfo, Message: "no certificate to evaluate"}}
}
return out
}
```
**Status values**: `StatusOK`, `StatusWarn`, `StatusCrit`, `StatusError`, `StatusUnknown`, `StatusInfo`.
You can define **multiple rules** per checker. Each rule evaluates the same collected data from a different angle. Users can enable/disable rules individually in the UI.
@ -351,6 +389,7 @@ func main() {
| `GET /definition` | - | `CheckerDefinitionProvider` |
| `POST /evaluate` | - | `CheckerDefinitionProvider` |
| `POST /report` | - | `CheckerMetricsReporter` or `CheckerHTMLReporter` |
| `GET`/`POST /check` | - | `CheckerInteractive` |
### Step 7: Create the Plugin Entrypoint
@ -362,13 +401,13 @@ For in-process plugin mode, the entrypoint must be a `package main` that exposes
package main
import (
dummy "git.happydns.org/happyDomain/checker-dummy/checker"
"git.happydns.org/happyDomain/model"
sdk "git.happydns.org/checker-sdk-go/checker"
dummy "git.happydns.org/checker-dummy/checker"
)
var Version = "custom-build"
func NewCheckerPlugin() (*happydns.CheckerDefinition, happydns.ObservationProvider, error) {
func NewCheckerPlugin() (*sdk.CheckerDefinition, sdk.ObservationProvider, error) {
// Propagate the plugin's version to the checker package.
dummy.Version = Version
return dummy.Definition(), dummy.Provider(), nil
@ -386,6 +425,53 @@ Then drop the resulting `checker-dummy.so` into one of happyDomain's configured
---
## Optional: Standalone Human UI (`CheckerInteractive`)
The SDK provides an optional `CheckerInteractive` interface that exposes a browser-friendly `/check` route, letting your checker be used as a standalone DNS-probing tool without a happyDomain instance in front of it.
```go
type CheckerInteractive interface {
RenderForm() []CheckerOptionField
ParseForm(r *http.Request) (CheckerOptions, error)
}
```
When a provider implements it, `NewServer` automatically registers:
- `GET /check`, renders an HTML form derived from `RenderForm()`.
- `POST /check`, calls `ParseForm`, runs the standard `Collect``Evaluate``GetHTMLReport` / `ExtractMetrics` pipeline, and returns a consolidated HTML page (states table, metrics table, sandboxed iframe around the HTML report).
### Why it exists
Over the HTTP `/evaluate` endpoint, happyDomain fills `AutoFill*`-backed options (zone records, service payload, …) from its execution context. A human hitting `/check` has no such host, `ParseForm` is where the checker does whatever lookups are needed (typically direct DNS queries) to turn a minimal human input (e.g. a domain name) into the full `CheckerOptions` that `Collect` expects.
### When to implement it
- You want the checker to be usable as a standalone DNS-probing tool (debug, demo, one-off runs) without a happyDomain instance.
- You are fine doing the auto-fill work yourself from the user's inputs. Checkers whose `Collect` intrinsically requires data only happyDomain can provide (e.g. a full zone diff) should skip this.
### Minimal implementation
```go
func (p *dummyProvider) RenderForm() []sdk.CheckerOptionField {
return []sdk.CheckerOptionField{
{Id: "message", Type: "string", Label: "Custom message",
Placeholder: "Hello!", Required: false},
}
}
func (p *dummyProvider) ParseForm(r *http.Request) (sdk.CheckerOptions, error) {
return sdk.CheckerOptions{
"message": strings.TrimSpace(r.FormValue("message")),
}, nil
}
```
Returning an error from `ParseForm` re-renders the form with the error message displayed so the user can correct and resubmit.
---
## Running the Checker
### Build and run locally
@ -467,12 +553,15 @@ Response (score 42.5 is below the warning threshold of 50):
{
"status": 3,
"message": "Score: 42.5 - test",
"rule_name": "dummy_score_check",
"code": "dummy_score_check"
}
]
}
```
Each entry in `states` carries a `rule_name` (server-stamped) and may include a `subject` field when the rule evaluates multiple elements.
Status codes: `1` = OK, `3` = Warning, `4` = Critical.
### Extract metrics
@ -511,13 +600,15 @@ curl -X POST http://localhost:8080/report \
This template is released under the **MIT License**, so you're free to use it as a starting point for any checker, including proprietary ones.
**Important caveat about the deployment mode you choose:**
The types and helpers your checker depends on live in [`checker-sdk-go`](https://git.happydns.org/checker-sdk-go), a separate module released under the **Apache License 2.0**. happyDomain itself depends on the same SDK, so plugins and the host share a common, permissively licensed contract instead of linking against AGPL code.
**What this means for the deployment mode you choose:**
- **Standalone HTTP checker:** your checker is a separate process communicating with happyDomain over the network. It is *not* a derivative work of happyDomain and you can license it however you want (proprietary, MIT, GPL, anything).
- **In-process plugin (`.so`):** your checker is loaded into the happyDomain process via `plugin.Open` and links against happyDomain's types at runtime. The FSF considers this a derivative work, so your plugin must be licensed under a license compatible with happyDomain's **AGPL-3.0** (typically AGPL-3.0 itself, or GPL-3.0).
- **Built-in checker** (imported directly into the happyDomain source tree): same constraint: AGPL-3.0.
- **In-process plugin (`.so`):** your checker is loaded into the happyDomain process via `plugin.Open`, but it only links against the Apache-licensed SDK, not against any AGPL code. You are free to license your plugin however you want.
- **Built-in checker** (imported directly into the happyDomain source tree): same as above on the linking side. Built-in checkers maintained inside the happyDomain repository are conventionally distributed under AGPL-3.0 to stay consistent with the rest of the project, but this is a project policy, not a legal requirement coming from the SDK.
If in doubt, pick the standalone mode: it gives you the most licensing freedom and avoids Go's plugin toolchain headaches.
If your checker imports anything *else* from the happyDomain repository (for example service abstractions like `happydns.ServiceMessage`), then that code *is* AGPL-licensed and the AGPL constraint comes back. The SDK alone is safe; the rest of happyDomain is not.
---
@ -531,7 +622,7 @@ Now that you understand the structure, here are ideas for your own checker:
- **HTTP checker:** send an HTTP request to a domain's web server and check the status code, response time, ...
- **Business logic:** probe your own application from the outside and verify it behaves as expected, e.g. log into your SaaS with a synthetic account and check that the dashboard loads, place a test order and confirm it reaches the order pipeline, hit an internal health endpoint that aggregates queue depth / worker lag / replication status, or check that a license server still hands out valid tokens. This turns happyDomain into a lightweight synthetic-monitoring dashboard for your own services.
For a real-world example, look at [checker-ping](https://git.happydns.org/happyDomain/checker-ping), which implements ICMP ping monitoring with multiple targets, packet loss detection, and RTT metrics.
For a real-world example, look at [checker-ping](https://git.happydns.org/checker-ping), which implements ICMP ping monitoring with multiple targets, packet loss detection, and RTT metrics.
### Tips

View file

@ -5,8 +5,7 @@ import (
"math/rand/v2"
"time"
"git.happydns.org/happyDomain/model"
sdk "git.happydns.org/happyDomain/sdk/checker"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// Collect gathers observation data. This is called by happyDomain (or the
@ -25,7 +24,7 @@ import (
// 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 happydns.CheckerOptions) (any, error) {
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

View file

@ -3,7 +3,7 @@ package checker
import (
"time"
"git.happydns.org/happyDomain/model"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// Version is the checker version reported in CheckerDefinition.Version.
@ -21,8 +21,8 @@ var Version = "built-in"
// 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() *happydns.CheckerDefinition {
return &happydns.CheckerDefinition{
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",
@ -43,13 +43,13 @@ func Definition() *happydns.CheckerDefinition {
// 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: happydns.CheckerAvailability{
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: []happydns.ObservationKey{ObservationKeyDummy},
ObservationKeys: []sdk.ObservationKey{ObservationKeyDummy},
// Options documents what configuration the checker accepts. Options
// are grouped by audience (admin, user, domain, service, run):
@ -62,8 +62,8 @@ func Definition() *happydns.CheckerDefinition {
//
// 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: happydns.CheckerOptionsDocumentation{
UserOpts: []happydns.CheckerOptionDocumentation{
Options: sdk.CheckerOptionsDocumentation{
UserOpts: []sdk.CheckerOptionDocumentation{
{
Id: "message",
Type: "string",
@ -86,11 +86,11 @@ func Definition() *happydns.CheckerDefinition {
Default: float64(20),
},
},
DomainOpts: []happydns.CheckerOptionDocumentation{
DomainOpts: []sdk.CheckerOptionDocumentation{
{
Id: "domain_name",
Label: "Domain name",
AutoFill: happydns.AutoFillDomainName,
AutoFill: sdk.AutoFillDomainName,
},
},
},
@ -98,12 +98,12 @@ func Definition() *happydns.CheckerDefinition {
// Rules lists the evaluation rules provided by this checker. Each
// rule will appear in the UI, and users can enable/disable them
// individually.
Rules: []happydns.CheckRule{
Rules: []sdk.CheckRule{
Rule(),
},
// Interval specifies how often the check should run.
Interval: &happydns.CheckIntervalSpec{
Interval: &sdk.CheckIntervalSpec{
Min: 1 * time.Minute,
Max: 1 * time.Hour,
Default: 5 * time.Minute,

View file

@ -4,7 +4,7 @@ import (
"encoding/json"
"time"
"git.happydns.org/happyDomain/model"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// Provider returns a new dummy observation provider.
@ -19,7 +19,7 @@ import (
//
// In this example, the provider implements all three optional interfaces
// so you can see how each one works.
func Provider() happydns.ObservationProvider {
func Provider() sdk.ObservationProvider {
return &dummyProvider{}
}
@ -30,27 +30,27 @@ 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() happydns.ObservationKey {
func (p *dummyProvider) Key() sdk.ObservationKey {
return ObservationKeyDummy
}
// Definition implements happydns.CheckerDefinitionProvider.
// 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() *happydns.CheckerDefinition {
func (p *dummyProvider) Definition() *sdk.CheckerDefinition {
return Definition()
}
// ExtractMetrics implements happydns.CheckerMetricsReporter.
// 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) ([]happydns.CheckMetric, error) {
func (p *dummyProvider) ExtractMetrics(ctx sdk.ReportContext, collectedAt time.Time) ([]sdk.CheckMetric, error) {
var data DummyData
if err := json.Unmarshal(raw, &data); err != nil {
if err := json.Unmarshal(ctx.Data(), &data); err != nil {
return nil, err
}
return []happydns.CheckMetric{
return []sdk.CheckMetric{
{
Name: "dummy_score",
Value: data.Score,

View file

@ -4,8 +4,7 @@ import (
"context"
"fmt"
"git.happydns.org/happyDomain/model"
sdk "git.happydns.org/happyDomain/sdk/checker"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// Rule returns a new dummy check rule.
@ -13,11 +12,11 @@ import (
// 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() happydns.CheckRule {
func Rule() sdk.CheckRule {
return &dummyRule{}
}
// dummyRule implements the happydns.CheckRule interface.
// dummyRule implements the sdk.CheckRule interface.
type dummyRule struct{}
// Name returns a unique, stable identifier for this rule. It is used as the
@ -32,7 +31,7 @@ func (r *dummyRule) Description() string {
// 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 happydns.CheckerOptions) error {
func (r *dummyRule) ValidateOptions(opts sdk.CheckerOptions) error {
warning := sdk.GetFloatOption(opts, "warningThreshold", 50)
critical := sdk.GetFloatOption(opts, "criticalThreshold", 20)
@ -59,15 +58,15 @@ func (r *dummyRule) ValidateOptions(opts happydns.CheckerOptions) error {
// 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 happydns.ObservationGetter, opts happydns.CheckerOptions) happydns.CheckState {
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 happydns.CheckState{
Status: happydns.StatusError,
return []sdk.CheckState{{
Status: sdk.StatusError,
Message: fmt.Sprintf("Failed to get dummy data: %v", err),
Code: "dummy_error",
}
}}
}
// Read thresholds from options.
@ -75,17 +74,17 @@ func (r *dummyRule) Evaluate(ctx context.Context, obs happydns.ObservationGetter
criticalThreshold := sdk.GetFloatOption(opts, "criticalThreshold", 20)
// Determine the status based on the score and thresholds.
var status happydns.Status
var status sdk.Status
switch {
case data.Score < criticalThreshold:
status = happydns.StatusCrit
status = sdk.StatusCrit
case data.Score < warningThreshold:
status = happydns.StatusWarn
status = sdk.StatusWarn
default:
status = happydns.StatusOK
status = sdk.StatusOK
}
return happydns.CheckState{
return []sdk.CheckState{{
Status: status,
Message: fmt.Sprintf("Score: %.1f - %s", data.Score, data.Message),
Code: "dummy_score_check",
@ -93,5 +92,5 @@ func (r *dummyRule) Evaluate(ctx context.Context, obs happydns.ObservationGetter
"score": data.Score,
"message": data.Message,
},
}
}}
}

43
go.mod
View file

@ -1,44 +1,5 @@
module git.happydns.org/happyDomain/checker-dummy
module git.happydns.org/checker-dummy
go 1.25.0
require git.happydns.org/happyDomain v0.6.0
require (
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/gin-gonic/gin v1.12.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
golang.org/x/arch v0.24.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/tools v0.42.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
replace git.happydns.org/happyDomain => ../happydomain3
require git.happydns.org/checker-sdk-go v1.5.0

100
go.sum
View file

@ -1,98 +1,2 @@
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
git.happydns.org/checker-sdk-go v1.5.0 h1:5uD5Cm6xJ+lwnhbJ09iCXGHbYS9zRh+Yh0NeBHkAPBY=
git.happydns.org/checker-sdk-go v1.5.0/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI=

View file

@ -4,8 +4,8 @@ import (
"flag"
"log"
dummy "git.happydns.org/happyDomain/checker-dummy/checker"
sdk "git.happydns.org/happyDomain/sdk/checker"
dummy "git.happydns.org/checker-dummy/checker"
"git.happydns.org/checker-sdk-go/checker/server"
)
// Version is the standalone binary's version. It defaults to "custom-build"
@ -23,8 +23,8 @@ func main() {
// CheckerDefinition.Version.
dummy.Version = Version
server := sdk.NewServer(dummy.Provider())
if err := server.ListenAndServe(*listenAddr); err != nil {
srv := server.New(dummy.Provider())
if err := srv.ListenAndServe(*listenAddr); err != nil {
log.Fatalf("server error: %v", err)
}
}

View file

@ -5,8 +5,8 @@
package main
import (
dummy "git.happydns.org/happyDomain/checker-dummy/checker"
"git.happydns.org/happyDomain/model"
dummy "git.happydns.org/checker-dummy/checker"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// Version is the plugin's version. It defaults to "custom-build" and is
@ -18,7 +18,7 @@ var Version = "custom-build"
// NewCheckerPlugin is the symbol resolved by happyDomain when loading the
// .so file. It returns the checker definition and the observation provider
// that the host will register in its global registries.
func NewCheckerPlugin() (*happydns.CheckerDefinition, happydns.ObservationProvider, error) {
func NewCheckerPlugin() (*sdk.CheckerDefinition, sdk.ObservationProvider, error) {
// Propagate the plugin's version to the checker package so it shows up
// in CheckerDefinition.Version.
dummy.Version = Version