package cluster import ( "io/fs" "path/filepath" "darvaza.org/resolver" "darvaza.org/slog" "darvaza.org/slog/handlers/discard" "github.com/hack-pad/hackpadfs/os" ) // A ScanOption pre-configures the Zones before scanning type ScanOption func(*Cluster, *ScanOptions) error // ScanOptions contains flags used by the initial scan type ScanOptions struct { // DontResolvePublicAddresses indicates we shouldn't // pre-populate Machine.PublicAddresses during the // initial scan DontResolvePublicAddresses bool // Logger specifies the logger to be used. otherwise // the scanner will be mute slog.Logger } // ResolvePublicAddresses instructs the scanner to use // the DNS resolver to get PublicAddresses of nodes. // Default is true func ResolvePublicAddresses(resolve bool) ScanOption { return func(m *Cluster, opt *ScanOptions) error { opt.DontResolvePublicAddresses = !resolve return nil } } // WithLookuper specifies what resolver.Lookuper to use to // find public addresses func WithLookuper(h resolver.Lookuper) ScanOption { return func(m *Cluster, opt *ScanOptions) error { if h == nil { return fs.ErrInvalid } m.resolver = resolver.NewResolver(h) return nil } } // WithResolver specifies what resolver to use to find // public addresses. if nil is passed, the [net.Resolver] will be used. // The default is using Cloudflare's 1.1.1.1. func WithResolver(h resolver.Resolver) ScanOption { return func(m *Cluster, opt *ScanOptions) error { if h == nil { h = resolver.SystemResolver(true) } m.resolver = h return nil } } // WithLogger specifies what to use for logging func WithLogger(log slog.Logger) ScanOption { return func(m *Cluster, opt *ScanOptions) error { if log == nil { log = discard.New() } opt.Logger = log m.log = log return nil } } func (m *Cluster) setDefaults(opt *ScanOptions) error { if m.resolver == nil { h := resolver.NewCloudflareLookuper() if err := WithLookuper(h)(m, opt); err != nil { return err } } if opt.Logger == nil { if err := WithLogger(nil)(m, opt); err != nil { return err } } return nil } // NewFS builds a [Cluster] tree using the given directory func NewFS(dir fs.FS, domain string, opts ...ScanOption) (*Cluster, error) { var scanOptions ScanOptions z := &Cluster{ dir: dir, domain: domain, } for _, opt := range opts { if err := opt(z, &scanOptions); err != nil { return nil, err } } if err := z.setDefaults(&scanOptions); err != nil { return nil, err } if err := z.scan(&scanOptions); err != nil { return nil, err } return z, nil } // New builds a [Cluster] tree using the given directory func New(dir, domain string, opts ...ScanOption) (*Cluster, error) { dir, err := filepath.Abs(dir) if err != nil { return nil, err } base, err := os.NewFS().Sub(dir[1:]) if err != nil { return nil, err } return NewFS(base, domain, opts...) }