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 }