Files
keepalive/adapter/adapter.go
T
tiennm99 47afb85093 fix: close remaining secret leaks, timeout gaps and config blind spots
- Generated service names come only from a URL host, a key=value DSN's
  host=, or a MySQL tcp() address, so a password in a key=value DSN can no
  longer end up in the name printed on every log line.
- connect_timeout is added only to postgres:// and postgresql:// URLs
  (parsed, so it never lands after a fragment) or as a key=value token, never
  glued onto a key=value value containing "://".
- Driver parse errors that quote password fragments (mongo escape errors,
  lib/pq's missing "=" error) are replaced with generic hints.
- MongoDB keeps its client only after a successful connect, so a failed
  connect is not disconnected twice.
- Couchbase gets ready_timeout plus 1 minute to connect, so raising
  ready_timeout takes effect.
- Shutdown waits at most 7 seconds, inside Docker's 10-second grace period.
- Each adapter declares its config keys; unknown keys anywhere in the file,
  including under config, log a warning without blocking start.
2026-10-09 12:36:21 +07:00

104 lines
2.6 KiB
Go

// Package adapter defines the contract every database driver implements.
//
// An Adapter connects to a single backing store, increments a counter on every
// keepalive tick (the cheapest write that proves the cluster is live), and
// releases its resources on Close. New databases plug in by adding a file in
// this package and registering a factory in Registry.
package adapter
import (
"context"
"fmt"
"strconv"
"strings"
"time"
)
type Adapter interface {
Connect(ctx context.Context) error
Increment(ctx context.Context) (int64, error)
Close(ctx context.Context) error
}
type Config map[string]string
func (c Config) Required(name string) (string, error) {
v, ok := c[name]
if !ok || v == "" {
return "", fmt.Errorf("config %s is required", name)
}
return v, nil
}
func (c Config) Optional(name, def string) string {
if v, ok := c[name]; ok && v != "" {
return v
}
return def
}
func (c Config) OptionalDuration(name string, def time.Duration) (time.Duration, error) {
value := strings.TrimSpace(c[name])
if value == "" {
return def, nil
}
if d, err := time.ParseDuration(value); err == nil {
if d <= 0 {
return 0, fmt.Errorf("config %s must be greater than zero", name)
}
return d, nil
}
seconds, err := strconv.Atoi(value)
if err != nil {
return 0, fmt.Errorf("config %s must be a duration like 30s or an integer number of seconds", name)
}
d := time.Duration(seconds) * time.Second
if d <= 0 {
return 0, fmt.Errorf("config %s must be greater than zero", name)
}
return d, nil
}
func (c Config) OptionalUint64(name string, def uint64) (uint64, error) {
value := strings.TrimSpace(c[name])
if value == "" {
return def, nil
}
out, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return 0, fmt.Errorf("config %s must be an unsigned integer", name)
}
return out, nil
}
type Factory func(Config) (Adapter, error)
var Registry = map[string]Factory{}
// ConfigKeys lists the config keys each adapter reads, so the config loader
// can warn about misspelled or unsupported keys. counter_key is set by the
// loader and is not listed.
var ConfigKeys = map[string][]string{}
// ConnectTimeouter is implemented by adapters whose Connect may legitimately
// need longer than the runner's default connect timeout.
type ConnectTimeouter interface {
ConnectTimeout() time.Duration
}
func New(adapterType string, cfg Config) (Adapter, error) {
f, ok := Registry[adapterType]
if !ok {
return nil, fmt.Errorf("unknown adapter %q (known: %v)", adapterType, Known())
}
return f(cfg)
}
func Known() []string {
out := make([]string, 0, len(Registry))
for k := range Registry {
out = append(out, k)
}
return out
}