Compare commits

...

4 commits

Author SHA1 Message Date
ae8efc2f64 Validate from_address option via net/mail at the host
All checks were successful
continuous-integration/drone/push Build is passing
Reject malformed addresses up-front instead of letting them surface as
a runtime SMTP failure during Collect.
2026-05-21 18:52:56 +08:00
7fe4e4b3a4 Fix TestDefinitionShape to assert ApplyToService
The checker no longer applies to whole domains (see 62af174 "Apply only
on opt-in services"); align the test with that availability.
2026-05-21 18:52:55 +08:00
10a65aed02 Move from_address to domain options with no-reply default
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2026-05-21 18:35:48 +08:00
1588131e90 Override is no more needed 2026-05-21 18:17:57 +08:00
4 changed files with 37 additions and 23 deletions

View file

@ -158,7 +158,11 @@ func loadConfig(opts sdk.CheckerOptions) (*runConfig, error) {
return nil, fmt.Errorf("smtp_host is required") return nil, fmt.Errorf("smtp_host is required")
} }
if cfg.FromAddress == "" { if cfg.FromAddress == "" {
return nil, fmt.Errorf("from_address is required") domain := strings.TrimSpace(stringOpt(opts, "domain_name"))
if domain == "" {
return nil, fmt.Errorf("from_address is required when domain_name is not set")
}
cfg.FromAddress = "no-reply@" + strings.TrimSuffix(domain, ".")
} }
parsedFrom, err := mail.ParseAddress(cfg.FromAddress) parsedFrom, err := mail.ParseAddress(cfg.FromAddress)
if err != nil { if err != nil {

View file

@ -35,19 +35,6 @@ func Definition() *sdk.CheckerDefinition {
}, },
}, },
UserOpts: []sdk.CheckerOptionDocumentation{ UserOpts: []sdk.CheckerOptionDocumentation{
{
Id: "happydeliver_url",
Type: "string",
Label: "happyDeliver instance URL (override)",
Description: "Override the operator-provided happyDeliver URL.",
},
{
Id: "happydeliver_token",
Type: "string",
Label: "happyDeliver API token (override)",
Description: "Override the operator-provided happyDeliver token.",
Secret: true,
},
{ {
Id: "smtp_host", Id: "smtp_host",
Type: "string", Type: "string",
@ -84,13 +71,6 @@ func Definition() *sdk.CheckerDefinition {
Choices: []string{"starttls", "tls", "none"}, Choices: []string{"starttls", "tls", "none"},
Default: "starttls", Default: "starttls",
}, },
{
Id: "from_address",
Type: "string",
Label: "From address",
Description: "Address used in the From header of the test email. Must be in the tested domain.",
Required: true,
},
{ {
Id: "subject_override", Id: "subject_override",
Type: "string", Type: "string",
@ -132,6 +112,12 @@ func Definition() *sdk.CheckerDefinition {
Label: "Domain name", Label: "Domain name",
AutoFill: sdk.AutoFillDomainName, AutoFill: sdk.AutoFillDomainName,
}, },
{
Id: "from_address",
Type: "string",
Label: "From address",
Description: "Address used in the From header of the test email. Must be in the tested domain. Defaults to no-reply@<domain>.",
},
}, },
}, },
Rules: []sdk.CheckRule{ Rules: []sdk.CheckRule{

View file

@ -2,6 +2,9 @@ package checker
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/mail"
"strings"
"time" "time"
sdk "git.happydns.org/checker-sdk-go/checker" sdk "git.happydns.org/checker-sdk-go/checker"
@ -21,6 +24,27 @@ func (p *happyDeliverProvider) Definition() *sdk.CheckerDefinition {
return Definition() return Definition()
} }
// ValidateOptions runs on the host before Collect so bad options are rejected
// up-front rather than surfacing as a runtime SMTP failure.
func (p *happyDeliverProvider) ValidateOptions(opts sdk.CheckerOptions) error {
raw, ok := opts["from_address"]
if !ok {
return nil
}
s, ok := raw.(string)
if !ok {
return fmt.Errorf("from_address must be a string")
}
s = strings.TrimSpace(s)
if s == "" {
return nil
}
if _, err := mail.ParseAddress(s); err != nil {
return fmt.Errorf("invalid from_address: %w", err)
}
return nil
}
func (p *happyDeliverProvider) ExtractMetrics(ctx sdk.ReportContext, collectedAt time.Time) ([]sdk.CheckMetric, error) { func (p *happyDeliverProvider) ExtractMetrics(ctx sdk.ReportContext, collectedAt time.Time) ([]sdk.CheckMetric, error) {
var data HappyDeliverData var data HappyDeliverData
if err := json.Unmarshal(ctx.Data(), &data); err != nil { if err := json.Unmarshal(ctx.Data(), &data); err != nil {

View file

@ -86,8 +86,8 @@ func TestDefinitionShape(t *testing.T) {
if def.ID != "happydeliver" { if def.ID != "happydeliver" {
t.Errorf("ID = %q", def.ID) t.Errorf("ID = %q", def.ID)
} }
if !def.Availability.ApplyToDomain { if !def.Availability.ApplyToService {
t.Error("should apply to domain") t.Error("should apply to service")
} }
if !def.HasMetrics { if !def.HasMetrics {
t.Error("HasMetrics should be true") t.Error("HasMetrics should be true")