From 8c05c1b1a709ed306c9dec68d36c783d52b2ff62 Mon Sep 17 00:00:00 2001 From: Yuxiao Zeng Date: Fri, 29 May 2026 21:14:05 +0900 Subject: [PATCH] Improve file provider behavior regarding dangling symlinks --- pkg/provider/file/file.go | 24 +++++++++++++++++++----- pkg/provider/file/file_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index 04395878e..557429a3a 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -71,9 +71,16 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. // ignore sub-dir continue } + if !isFileSupported(entry.Name()) { + // ignore unsupported file extension + continue + } watchItems = append(watchItems, path.Join(p.Directory, entry.Name())) } case len(p.Filename) > 0: + if !isFileSupported(p.Filename) { + return fmt.Errorf("unsupported file extension for file %s", p.Filename) + } watchItems = append(watchItems, filepath.Dir(p.Filename), p.Filename) default: return errors.New("error using file configuration provider, neither filename nor directory is defined") @@ -168,7 +175,7 @@ func (p *Provider) addWatcher(pool *safe.Pool, items []string, configurationChan log.Debug().Msgf("add watcher on: %s", item) err = watcher.Add(item) if err != nil { - return fmt.Errorf("error adding file watcher: %w", err) + return fmt.Errorf("error adding file watcher for %s: %w", item, err) } } @@ -420,10 +427,8 @@ func (p *Provider) loadFileConfigFromDirectory(ctx context.Context, directory st continue } - switch strings.ToLower(filepath.Ext(item.Name())) { - case ".toml", ".yaml", ".yml": - // noop - default: + if !isFileSupported(item.Name()) { + logger.Debug().Msg("Skipping file, unsupported extension") continue } @@ -627,3 +632,12 @@ func readFile(filename string) (string, error) { } return "", fmt.Errorf("invalid filename: %s", filename) } + +func isFileSupported(filename string) bool { + switch strings.ToLower(filepath.Ext(filename)) { + case ".toml", ".yaml", ".yml": + return true + default: + return false + } +} diff --git a/pkg/provider/file/file_test.go b/pkg/provider/file/file_test.go index 3aebe1908..e89e504e3 100644 --- a/pkg/provider/file/file_test.go +++ b/pkg/provider/file/file_test.go @@ -197,6 +197,38 @@ func TestProvideWithWatch(t *testing.T) { } } +func TestProvideWatchWithNonConfigDanglingSymlink(t *testing.T) { + tempDir := t.TempDir() + + err := copyFile("./fixtures/yaml/simple_file_01.yml", filepath.Join(tempDir, "simple_file_01.yml")) + require.NoError(t, err) + + err = os.Symlink(filepath.Join(tempDir, "non_existent_file.txt"), filepath.Join(tempDir, "dangling_symlink.txt")) + require.NoError(t, err) + + provider := &Provider{ + Directory: tempDir, + Watch: true, + } + configChan := make(chan dynamic.Message) + go func() { + err := provider.Provide(configChan, safe.NewPool(t.Context())) + assert.NoError(t, err) + }() + + timeout := time.After(time.Second) + select { + case conf := <-configChan: + require.NotNil(t, conf.Configuration.HTTP) + numServices := len(conf.Configuration.HTTP.Services) + len(conf.Configuration.TCP.Services) + len(conf.Configuration.UDP.Services) + numRouters := len(conf.Configuration.HTTP.Routers) + len(conf.Configuration.TCP.Routers) + len(conf.Configuration.UDP.Routers) + assert.Equal(t, 6, numServices) + assert.Equal(t, 3, numRouters) + case <-timeout: + t.Errorf("timeout while waiting for config") + } +} + func getTestCases() []ProvideTestCase { return []ProvideTestCase{ {