package app import ( "context" "crypto/sha256" "fmt" "os" "path/filepath" "sort" "time" ) // watchLocalZones reloads projected ConfigMap files on every replica. A // Kubernetes ConfigMap volume swaps its ..data symlink atomically when the UI // edits a zone; polling the contents keeps both pods in sync without routing // DNS changes through a single pod. func (a *App) watchLocalZones(ctx context.Context) { if a.zones == nil { return } last, _ := a.localZoneFingerprint() ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: next, err := a.localZoneFingerprint() if err != nil { a.log.Warn("local zone change check failed", "error", err) continue } if next == last { continue } a.reloadLocalZones() if a.cache != nil { a.cache.Flush() } last = next } } } func (a *App) localZoneFingerprint() (string, error) { paths := make([]string, 0) for _, configured := range a.cfg.Get().Block.LocalZones { path := a.cfg.Get().AbsPath(configured) if matches, err := filepath.Glob(path); err == nil && len(matches) > 0 { paths = append(paths, matches...) continue } if info, err := os.Stat(path); err == nil && info.IsDir() { matches, err := filepath.Glob(filepath.Join(path, "*.zone")) if err != nil { return "", err } paths = append(paths, matches...) continue } } sort.Strings(paths) h := sha256.New() for _, path := range paths { b, err := os.ReadFile(path) if err != nil { return "", fmt.Errorf("read %s: %w", path, err) } _, _ = h.Write([]byte(path)) _, _ = h.Write([]byte{0}) _, _ = h.Write(b) _, _ = h.Write([]byte{0}) } return fmt.Sprintf("%x", h.Sum(nil)), nil }