Skip to content

feat(config): ConfigWatcher tracks HOCON include files#610

Open
dsudomoin wants to merge 11 commits intokora-projects:1.0from
dsudomoin:feature/config-watcher-includes
Open

feat(config): ConfigWatcher tracks HOCON include files#610
dsudomoin wants to merge 11 commits intokora-projects:1.0from
dsudomoin:feature/config-watcher-includes

Conversation

@dsudomoin
Copy link
Copy Markdown

@dsudomoin dsudomoin commented Mar 17, 2026

Summary

  • ConfigWatcher now watches not only the main config file but also all files included via HOCON include directives
  • Uses custom TrackingConfigIncluder (ConfigIncluder + ConfigIncluderFile) to intercept include calls during parsing and record file paths
  • Handles both include file("path") and heuristic include "name" (resolved via ConfigIncludeContext.relativeTo())
  • Switched from ConfigFactory.parseReader() to ConfigFactory.parseFile()/parseURL() for correct include resolution
  • ConfigWatcher recalculates watched file list after each refresh (adds new files, removes stale ones)

Motivation

When application.conf includes external files (e.g. include file("/opt/app/config/override.conf")), changes to those files were invisible to ConfigWatcher. This is a common pattern in Kubernetes where application.conf stays in resources and per-environment overrides are mounted separately.

Changes

  • ContainerConfigOrigin — added List<ConfigOrigin> constructor with nested container flattening
  • TrackingConfigIncluder — new class implementing ConfigIncluder + ConfigIncluderFile, intercepts include calls and records file paths
  • HoconConfigFactory — added parseAndEnrichOrigin() and enrichOriginWithIncludes() for building enriched ConfigOrigin with all discovered include files
  • HoconConfigModuleapplicationConfigOrigin() uses HoconConfigFactory.parseAndEnrichOrigin(); applicationUnresolved() uses parseFile()/parseURL()
  • ConfigWatcher — recalculates watched files after each refresh

Test plan

  • Unit test: include file("...") — included file path recorded
  • Unit test: nested include file() (main → level1 → level2) — all paths recorded
  • Unit test: heuristic include "name" — resolved file path recorded
  • Unit test: optional non-existent include — path still recorded (ConfigWatcher handles gracefully)
  • Integration test: ConfigWatcher detects changes in an included file and triggers refresh
  • All existing ConfigWatcher tests pass (symlink, file change, new data dir)

🤖 Generated with Claude Code

dsudomoin and others added 10 commits March 17, 2026 12:32
… paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ContainerConfigOrigin(List) now flattens nested containers (consistency with 2-arg constructor)
- ConfigWatcher removes stale files from watch list after refresh
- Add comment explaining "merge of" filter origin

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ConfigFactory

Move extractIncludedFiles, collectFileOrigins, enrichOriginWithIncludes
from the interface to the factory class where they belong.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace config tree walking approach with TrackingConfigIncluder that
intercepts include file() calls during HOCON parsing. This is more
correct because:
- Catches all includes, even empty files or overridden values
- No fragile "merge of" string filtering
- Uses the proper Typesafe Config extension point

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… HoconConfigFactory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…luder

Handle include "filename" (not just include file("...")) by resolving
the path via ConfigIncludeContext.relativeTo() and recording it if it
points to a file on disk.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@GoodforGod GoodforGod requested a review from Squiry March 24, 2026 12:26
@GoodforGod GoodforGod added this to the v1.2.13 milestone Mar 25, 2026
@GoodforGod GoodforGod added improvement Improvements to existing features module: config Related to Config module labels Mar 25, 2026
closeable.close();
return ConfigFactory.parseFile(file.path().toFile());
} else if (origin instanceof ContainerConfigOrigin container) {
for (var o : container.origins()) {
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.

classpath отвалится.
Это надо сделать так: ContainerConfigOrigin сделать интерфейсом и брать у него в вотчере список.
Для хокона сделать ConfigOrigin, который реализует этот интерфейс и имеет какой-то openInputStream или что-то такое, чтобы считать то, что является источником.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Поправил: ContainerConfigOrigin теперь интерфейс с origins(). Для HOCON сделал HoconConfigOrigin, который реализует его и имеет openInputStream() — работает и с файлами, и с classpath. applicationUnresolved() упрощён, больше нет pattern-matching по типам origin. TrackingConfigIncluder теперь фильтрует не-файлы через Files.isRegularFile().

* <p>
* Delegates actual include resolution to the fallback (default) includer.
*/
final class TrackingConfigIncluder implements ConfigIncluder, ConfigIncluderFile {
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.

Тут кажется надо фильтрануть не файлы.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Добавил проверку Files.isRegularFile(path) в обоих методах include() и includeFile() — теперь classpath ресурсы, URL и несуществующие пути отсеиваются.

@GoodforGod GoodforGod removed this from the v1.2.13 milestone Mar 27, 2026
…, HoconConfigOrigin with openInputStream

- Convert ContainerConfigOrigin from class to interface with origins() method
- Add SimpleContainerConfigOrigin for general use (MergeConfigFactory)
- Add HoconConfigOrigin implementing ContainerConfigOrigin with openInputStream()
  to support both file and classpath sources polymorphically
- Simplify HoconConfigModule.applicationUnresolved() — no more pattern-matching
  on origin types, just uses HoconConfigOrigin.openInputStream()
- Filter non-file includes in TrackingConfigIncluder (Files.isRegularFile check)
  to ignore classpath resources and non-existent paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improvements to existing features module: config Related to Config module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants