checker-tls/checker/collect.go

63 lines
1.8 KiB
Go

package checker
import (
"context"
"fmt"
"log"
"sync"
"time"
sdk "git.happydns.org/checker-sdk-go/checker"
"git.happydns.org/checker-tls/contract"
)
func (p *tlsProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any, error) {
raw, ok := sdk.GetOption[[]sdk.DiscoveryEntry](opts, OptionEndpoints)
if !ok {
return nil, fmt.Errorf("no discovery entries in options: did the host wire AutoFillDiscoveryEntries?")
}
timeoutMs := sdk.GetIntOption(opts, OptionProbeTimeoutMs, DefaultProbeTimeoutMs)
if timeoutMs <= 0 {
timeoutMs = DefaultProbeTimeoutMs
}
timeout := time.Duration(timeoutMs) * time.Millisecond
entries, warnings := contract.ParseEntries(raw)
for _, w := range warnings {
log.Printf("checker-tls: discarding malformed entry: %v", w)
}
// An empty entry set is not an error: it is the steady state on any
// target where no producer has published yet, and the first run after
// a fresh publication when the producer hasn't finished its own cycle.
// The rule surfaces this as StatusUnknown rather than StatusError so a
// freshly-enrolled domain doesn't flap red.
if len(entries) == 0 {
return &TLSData{Probes: map[string]TLSProbe{}, CollectedAt: time.Now()}, nil
}
probes := make(map[string]TLSProbe, len(entries))
var mu sync.Mutex
var wg sync.WaitGroup
sem := make(chan struct{}, MaxConcurrentProbes)
for _, e := range entries {
wg.Add(1)
sem <- struct{}{}
go func() {
defer wg.Done()
defer func() { <-sem }()
pr := probe(ctx, e.Endpoint, timeout)
log.Printf("checker-tls: %s %s:%d → tls=%s handshake_ok=%t elapsed=%dms err=%q",
pr.Type, pr.Host, pr.Port, pr.TLSVersion, pr.TLSHandshakeOK, pr.ElapsedMS, pr.Error)
mu.Lock()
probes[e.Ref] = pr
mu.Unlock()
}()
}
wg.Wait()
return &TLSData{
Probes: probes,
CollectedAt: time.Now(),
}, nil
}