Skip to content

feat: add support for loading multiple config files and staleness check#908

Open
Elia-Renzoni wants to merge 2 commits into
trickstercache:mainfrom
Elia-Renzoni:config-493
Open

feat: add support for loading multiple config files and staleness check#908
Elia-Renzoni wants to merge 2 commits into
trickstercache:mainfrom
Elia-Renzoni:config-493

Conversation

@Elia-Renzoni

@Elia-Renzoni Elia-Renzoni commented Dec 29, 2025

Copy link
Copy Markdown
Contributor

@jranson @crandles
This PR improves Trickster’s configuration system by extending support from a single configuration file to multiple YAML configuration files loaded from a directory.

The implementation assumes that users who want to use multiple configuration files will provide only the directory path as a flag
(e.g., /etc/trickster) and the code will automatically look for all YAML files in that directory and load them in order, merging the configurations.

To support this feature, some methods were refactored, any feedback on these changes would be appreciated.

Related Issue: #493

Signed-off-by: Elia Renzoni <elia.renzoni03@gmail.com>
@Elia-Renzoni Elia-Renzoni requested a review from a team as a code owner December 29, 2025 15:39
@coveralls

coveralls commented Dec 29, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 20577480854

Details

  • 65 of 87 (74.71%) changed or added relevant lines in 2 files are covered.
  • 8 unchanged lines in 3 files lost coverage.
  • Overall coverage decreased (-0.04%) to 72.087%

Changes Missing Coverage Covered Lines Changed/Added Lines %
pkg/config/config.go 64 86 74.42%
Files with Coverage Reduction New Missed Lines %
pkg/proxy/engines/cache_read.go 2 81.82%
pkg/proxy/engines/progressive_collapse_forwarder.go 2 96.52%
pkg/config/config.go 4 73.72%
Totals Coverage Status
Change from base Build 20349547424: -0.04%
Covered Lines: 14958
Relevant Lines: 20750

💛 - Coveralls

Signed-off-by: Elia Renzoni <elia.renzoni03@gmail.com>
@jranson

jranson commented Feb 21, 2026

Copy link
Copy Markdown
Member

@Elia-Renzoni thanks so much for this, we haven't forgotten about it. We were already feature-locked and in the release candidate phase for v2.0.0 when this PR was opened. We are targeting this for v2.1.0. I'll review and test it out this week. Thanks again!

@jranson jranson added the 2.1 release feature slated for 2.1 release label Jul 5, 2026

@houyuwushang houyuwushang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rebased these two commits onto current main and traced the directory path through daemon.Hup. The basic case with two existing files still works, but I found several cases that can miss configuration changes or break existing callers. I left the reproductions and affected behavior inline. The main structural issue is that staleness is represented as parallel slices for only the files present at startup, rather than as a snapshot of the directory contents.

Comment thread pkg/config/config.go
if len(c.Main.configFilesPath) > 0 {
for index, file := range c.Main.configFilesPath {
t := c.CheckFileLastModified(file)
if t.IsZero() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only walks configFilesPath captured during the initial load. A newly added YAML file is never examined, while a removed file returns a zero timestamp and is skipped here. daemon.Hup calls this method before loading again, so both changes make an explicit reload report "not stale" and leave the running config unchanged. I reproduced both cases after rebasing onto current main. Could this compare a fresh directory snapshot (names and modification times) with the loaded snapshot instead?

Comment thread pkg/config/config.go
if c.Main == nil ||
(len(c.Main.configFilesPath) == 0 &&
(c.Main.configFilePath == "" ||
time.Now().Before(c.Main.configRateLimitTime))) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For directory-backed configs, len(c.Main.configFilesPath) == 0 is false, so the rate-limit check is never evaluated. After an existing file changes, IsStale() scans the files and returns true on every call during the rate-limit window. The rate-limit condition should apply to both single-file and directory modes.

Comment thread pkg/config/config.go
// CheckFileLastModified returns the last modified date of the running config file, if present
func (c *Config) CheckFileLastModified() time.Time {
if c.Main == nil || c.Main.configFilePath == "" {
func (c *Config) CheckFileLastModified(confFile string) time.Time {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes an exported v2 method from CheckFileLastModified() to CheckFileLastModified(string), which breaks external callers at compile time. Could the path-taking helper stay private while retaining the existing no-argument method for compatibility?

Comment thread pkg/config/config.go
if len(c.Main.configFilesPath) == 0 {
return c.Main.configFilePath
}
return c.Flags.ConfigPath

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clone() copies configFilesPath but does not copy Flags, so cfg.Clone().ConfigFilePath() panics for every directory-backed config at this dereference. I reproduced that on current main. Storing the directory path in MainConfig and deep-copying the directory snapshot would avoid making this accessor depend on runtime flags.

Comment thread pkg/config/config.go

// loadAndMergeFiles loads application configuration from multiple YAML files
func (c *Config) loadAndMergeFiles(flags *Flags) error {
files, err := fs.Glob(os.DirFS(flags.ConfigPath), "*.yaml")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only matches .yaml. A directory containing trickster.yml loads no files and eventually returns no valid backends configured, although the PR describes loading all YAML files. Is .yml intentionally unsupported? If not, this should enumerate both extensions and return a specific error when no matching files are present.

Comment thread pkg/config/config.go
return err
}

if err = c.loadYAMLConfig(string(data)); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated unmarshalling keeps distinct map keys, but a later file replaces an entire same-named backend. In a local case where the second fragment only overrode origin_url, the loaded backend lost its provider. Since the PR says files are merged in order, this needs an explicit contract: deep-merge fields, reject duplicate resource names, or document that a repeated resource must be complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2.1 release feature slated for 2.1 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants