Files
ionscale/internal/dns/plugin.go
T
2025-05-24 11:01:05 +02:00

125 lines
2.7 KiB
Go

package dns
import (
"context"
"encoding/json"
"fmt"
"github.com/hashicorp/go-plugin"
"github.com/jsiebens/ionscale/internal/util"
dnsplugin "github.com/jsiebens/libdns-plugin"
"github.com/libdns/libdns"
"go.uber.org/zap"
"os/exec"
"sync"
"time"
)
// pluginManager handles plugin lifecycle and resilience
type pluginManager struct {
pluginPath string
client *plugin.Client
instance dnsplugin.Provider
lock sync.RWMutex
logger *zap.Logger
zone string
config json.RawMessage
}
// NewPluginManager creates a new plugin manager
func newPluginManager(pluginPath string, zone string, config json.RawMessage) (*pluginManager, error) {
logger := zap.L().Named("dns").With(zap.String("plugin_path", pluginPath))
p := &pluginManager{
pluginPath: pluginPath,
logger: logger,
zone: zone,
config: config,
}
if err := p.ensureRunning(true); err != nil {
return nil, err
}
return p, nil
}
// ensureRunning makes sure the plugin is running
func (pm *pluginManager) ensureRunning(start bool) error {
pm.lock.RLock()
running := pm.client != nil && !pm.client.Exited()
instance := pm.instance
pm.lock.RUnlock()
if running && instance != nil {
return nil
}
// Need to restart
pm.lock.Lock()
defer pm.lock.Unlock()
if !start {
pm.logger.Info("Restarting DNS plugin")
}
if pm.client != nil {
pm.client.Kill()
}
// Create a new client
cmd := exec.Command(pm.pluginPath)
pm.client = plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: dnsplugin.Handshake,
Plugins: dnsplugin.PluginMap,
Cmd: cmd,
AllowedProtocols: []plugin.Protocol{
plugin.ProtocolNetRPC,
plugin.ProtocolGRPC,
},
Managed: true,
Logger: util.NewZapAdapter(pm.logger, "dns"),
})
// Connect via RPC
rpcClient, err := pm.client.Client()
if err != nil {
return fmt.Errorf("error creating plugin client: %w", err)
}
// Request the plugin
raw, err := rpcClient.Dispense(dnsplugin.ProviderPluginName)
if err != nil {
return fmt.Errorf("error dispensing plugin: %w", err)
}
// Convert to the interface
pm.instance = raw.(dnsplugin.Provider)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
if err := pm.instance.Configure(ctx, pm.config); err != nil {
return fmt.Errorf("error configuring plugin: %w", err)
}
pm.logger.Info("DNS plugin started")
return nil
}
func (pm *pluginManager) SetRecord(ctx context.Context, recordType, recordName, value string) error {
if err := pm.ensureRunning(false); err != nil {
return err
}
_, err := pm.instance.SetRecords(ctx, pm.zone, []libdns.Record{{
Type: recordType,
Name: libdns.RelativeName(recordName, pm.zone),
Value: value,
TTL: 1 * time.Minute,
}})
return err
}