From 4faf983fb60a23a4bae8a17beebe94c738f2ab03 Mon Sep 17 00:00:00 2001 From: houyuwushang Date: Mon, 29 Jun 2026 15:59:23 +0800 Subject: [PATCH] plugin/auto: warn on duplicate zone file origins (#8191) Signed-off-by: houyuwushang --- plugin/auto/README.md | 3 ++- plugin/auto/walk.go | 10 ++++++-- plugin/auto/walk_test.go | 51 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/plugin/auto/README.md b/plugin/auto/README.md index 4f4b895db..6b407f2ec 100644 --- a/plugin/auto/README.md +++ b/plugin/auto/README.md @@ -28,7 +28,8 @@ are used. like `{}` are replaced with the respective matches in the file name, e.g. `{1}` is the first match, `{2}` is the second. The default is: `db\.(.*) {1}` i.e. from a file with the name `db.example.com`, the extracted origin will be `example.com`. **REGEXP** must not be longer - than 10000 characters. + than 10000 characters. **REGEXP** is unanchored; use `^` and `$` to require matching the full + file name, for example `^(.*)\.zone$` to avoid matching backup files like `example.org.zone.bak`. * `reload` interval to perform reloads of zones if SOA version changes and zonefiles. It specifies how often CoreDNS should scan the directory to watch for file removal and addition. Default is one minute. Value of `0` means to not scan for changes and reload. eg. `30s` checks zonefile every 30 seconds and reloads zone when serial changes. diff --git a/plugin/auto/walk.go b/plugin/auto/walk.go index 1aa1b1b2c..4967fcff0 100644 --- a/plugin/auto/walk.go +++ b/plugin/auto/walk.go @@ -26,6 +26,7 @@ func (a Auto) Walk() error { for _, n := range a.Names() { toDelete[n] = true } + seen := make(map[string]string) filepath.Walk(dir, func(path string, info os.FileInfo, e error) error { if e != nil { @@ -40,14 +41,19 @@ func (a Auto) Walk() error { return nil } + cleanPath := filepath.Clean(path) + if previous, ok := seen[origin]; ok && previous != cleanPath { + log.Warningf("Multiple zone files match origin %q: using %q instead of %q", origin, path, previous) + } + seen[origin] = cleanPath + if z, ok := a.Z[origin]; ok { - // we already have this zone toDelete[origin] = false z.SetFile(path) return nil } - reader, err := os.Open(filepath.Clean(path)) //nolint:gosec // G122: path is from filepath.Walk rooted in a.directory; symlinks must be followed for configmap-style mounts + reader, err := os.Open(cleanPath) //nolint:gosec // G122: path is from filepath.Walk rooted in a.directory; symlinks must be followed for configmap-style mounts if err != nil { log.Warningf("Opening %s failed: %s", path, err) return nil diff --git a/plugin/auto/walk_test.go b/plugin/auto/walk_test.go index 7c62eab92..5331429e3 100644 --- a/plugin/auto/walk_test.go +++ b/plugin/auto/walk_test.go @@ -1,9 +1,13 @@ package auto import ( + "bytes" + "io" + golog "log" "os" "path/filepath" "regexp" + "strings" "testing" ) @@ -98,6 +102,53 @@ func TestWalkNonExistent(t *testing.T) { a.Walk() } +func TestWalkWarnsForDuplicateOrigin(t *testing.T) { + dir := t.TempDir() + zone := filepath.Join(dir, "example.org.zone") + backup := filepath.Join(dir, "example.org.zone.bak-20260528") + + for _, path := range []string{zone, backup} { + if err := os.WriteFile(path, []byte(zoneContent), 0644); err != nil { + t.Fatal(err) + } + } + + var logBuf bytes.Buffer + golog.SetOutput(&logBuf) + defer golog.SetOutput(io.Discard) + + a := Auto{ + loader: loader{ + directory: dir, + re: regexp.MustCompile(`(.*)\.zone`), + template: `${1}`, + }, + Zones: &Zones{}, + } + + a.Walk() + + got := logBuf.String() + if count := strings.Count(got, `[WARNING] plugin/auto: Multiple zone files match origin "example.org."`); count != 1 { + t.Fatalf("Expected one duplicate origin warning, got %d in %q", count, got) + } + for _, want := range []string{ + `[WARNING] plugin/auto: Multiple zone files match origin "example.org."`, + "example.org.zone", + "example.org.zone.bak-20260528", + } { + if !strings.Contains(got, want) { + t.Fatalf("Expected log to contain %q, got %q", want, got) + } + } + + logBuf.Reset() + a.Walk() + if count := strings.Count(logBuf.String(), `[WARNING] plugin/auto: Multiple zone files match origin "example.org."`); count != 1 { + t.Fatalf("Expected one duplicate origin warning after reload, got %d in %q", count, logBuf.String()) + } +} + func createFiles(t *testing.T) (string, error) { t.Helper() dir := t.TempDir()