package dns import ( "context" "errors" "io/fs" "net/netip" "strings" "darvaza.org/core" "darvaza.org/slog" "git.jpi.io/amery/jpictl/pkg/cluster" "golang.org/x/net/publicsuffix" ) // Manager is a DNS Manager instance type Manager struct { domain string suffix string zones map[string]*Zone regions map[string][]string p Provider l slog.Logger } // ManagerOption configures a Manager type ManagerOption func(*Manager) error func newErrorManagerOption(err error, hint string) ManagerOption { return func(*Manager) error { return core.Wrap(err, hint) } } // WithProvider attaches a libdns Provider to the Manager func WithProvider(p Provider) ManagerOption { var err error if p == nil { p, err = DefaultDNSProvider() } if err != nil { return newErrorManagerOption(err, "WithProvider") } return func(mgr *Manager) error { mgr.p = p return nil } } // WithLogger attaches a logger to the Manager func WithLogger(log slog.Logger) ManagerOption { if log == nil { log = cluster.DefaultLogger() } return func(mgr *Manager) error { mgr.l = log return nil } } func (mgr *Manager) setDefaults() error { var opts []ManagerOption if mgr.l == nil { opts = append(opts, WithLogger(nil)) } if mgr.domain == "" || mgr.suffix == "" { return errors.New("domain not specified") } for _, opt := range opts { if err := opt(mgr); err != nil { return err } } return nil } // WithDomain specifies where the manager operates func WithDomain(domain string) ManagerOption { base, err := publicsuffix.EffectiveTLDPlusOne(domain) if err != nil { return newErrorManagerOption(err, "publicsuffix") } suffix := strings.TrimSuffix(domain, base) if suffix != "" { suffix = "." + suffix[:len(suffix)-1] } return func(mgr *Manager) error { mgr.domain = base mgr.suffix = suffix return nil } } // NewManager creates a DNS manager with the func NewManager(opts ...ManagerOption) (*Manager, error) { mgr := &Manager{ zones: make(map[string]*Zone), regions: make(map[string][]string), } for _, opt := range opts { if err := opt(mgr); err != nil { return nil, err } } if err := mgr.setDefaults(); err != nil { return nil, err } return mgr, nil } // AddHost registers a host func (mgr *Manager) AddHost(_ context.Context, zone string, id int, active bool, addrs ...netip.Addr) error { // if zone == "" || id <= 0 { return fs.ErrInvalid } z, ok := mgr.zones[zone] if !ok { z = &Zone{ Name: zone, Hosts: make(map[int]*Host), } mgr.zones[zone] = z } p := &Host{ zone: z, ID: id, Active: active, Addrs: SortAddrSlice(addrs), } z.Hosts[id] = p if log, ok := mgr.l.Debug().WithEnabled(); ok { log.WithField("subsystem", "dns"). WithField("zone", zone). WithField("host", p.String()). WithField("active", active). Print() } return nil } // AddRegion specifies a new region and the zones it contains func (mgr *Manager) AddRegion(_ context.Context, region string, zones ...string) error { mgr.regions[region] = append(mgr.regions[region], zones...) if log, ok := mgr.l.Debug().WithEnabled(); ok { for _, zoneName := range zones { log.WithField("subsystem", "dns"). WithField("region", region). WithField("zone", zoneName).Print() } } return nil }