Skip to content

Support Git-style searchPaths with wildcards in AWS S3 buckets (#2812)#2958

Open
tomy8964 wants to merge 10 commits into
spring-cloud:mainfrom
tomy8964:feature/gh-2812-aws-s3-searchpaths
Open

Support Git-style searchPaths with wildcards in AWS S3 buckets (#2812)#2958
tomy8964 wants to merge 10 commits into
spring-cloud:mainfrom
tomy8964:feature/gh-2812-aws-s3-searchpaths

Conversation

@tomy8964

@tomy8964 tomy8964 commented Jul 6, 2025

Copy link
Copy Markdown

Summary

Add full Git-style searchPaths support (placeholders + wildcards) to the AWS S3 backend so that users can migrate existing Git-based configurations without renaming to application.*.

  • Implements literal, dot-wildcard (.*), single (?) and double (**) wildcard matching
  • Retains the lookup order .properties → .json → .yml → .yaml when expanding literals or .* patterns
  • Scans “directory” patterns for nested files
  • Deduplicates identical keys across multiple patterns

This issue (#2812)
resolves #2812


Changes

  1. Core Logic

    • Extended getS3ConfigFileWithSearchPaths(...) to treat {application} and {profile} placeholders identically to the Git backend

    • Added support for:

      • Literal + auto-ext: stops at first existing extension
      • Dot-wildcard (.*): expands against supported extensions in priority order
      • Single-character wildcard (?)
      • Multi-level wildcard (**)
      • Directory scan for literal prefixes ending with /
    • Ensured seenKeys set to avoid duplicate property sources

  2. Tests
    Added new JUnit 5 tests in AwsS3EnvironmentRepositoryTests to cover all scenarios:

    • searchPaths_placeholderOnly_shouldResolveExactFile
    • searchPaths_wildcardOnly_shouldResolveAllProperties
    • searchPaths_placeholderAndWildcard_shouldResolveMatchingKeys
    • searchPaths_orderMatters_forPropertySourceOrder
    • searchPath_extensionPreserved (dynamic tests for each extension)
    • searchPaths_applicationAsDirectory_shouldStillHonorSearchPaths
    • multiDocumentYaml_withSearchPaths_shouldNotSplitDocuments
    • getLocations_returnsCorrect
    • searchPaths_deduplication_shouldOnlyAddOnce
    • searchPaths_singleCharacterWildcard_shouldMatchExactlyOneChar
    • searchPaths_withEmptyLabel_shouldUseDefaultLabel
    • searchPaths_multipleLabels_shouldApplyForEachLabelInReverseOrder
    • searchPaths_literalNotFound_shouldReturnEmpty

    …plus the additional edge cases for literal-stop, dot-wildcard priority, and nested directory patterns.

  3. Example Usage

spring:
  cloud:
    config:
      server:
        awss3:
          bucket: my-config-bucket
          search-paths:
            - "{label}/{application}"          # literal + auto-ext
            - "{label}/{application}.*"        # dot-wildcard expansion
            - "{label}/common/*.json"          # JSON only in common/
            - "{label}/{application}.yml"      # explicit YML

Additional Notes

Backward Compatibility

This update does not break any existing AWS S3 config setups. The default lookup behavior is unchanged if no placeholders or wildcards are used in searchPaths.

Migration

Existing users migrating from Git-backed config servers can now use their current searchPaths (including wildcards and placeholders) on S3 with no renaming or convention change required.

Documentation

Documentation and usage examples for the enhanced searchPaths will be updated in the relevant documentation files after the merge.
If there are specific locations that require documentation updates, please let me know—I will be happy to update them.

Performance and Cost

Using wildcards (such as * or **) in searchPaths may result in additional AWS S3 API calls (e.g., ListObjects), especially for large buckets or deeply nested directory patterns.
This could lead to increased latency and higher AWS costs.
Users should consider the structure and size of their buckets when designing searchPaths and monitor AWS S3 usage accordingly.


…g-cloud#2812)

Fixes spring-cloud#2812

- ListObjectsV2 + AntPathMatcher based matching for *, **, ?, dot-wildcard
- auto-ext lookup order (.properties → .json → .yml/.yaml)
- directory scan, deduplication, prefix extraction
- only active when searchPaths non-empty

Signed-off-by: Geonwook Ham <tomy8964@naver.com>
Signed-off-by: ham <tomy8964@naver.com>
Signed-off-by: Geonwook Ham <tomy8964@naver.com>
@tomy8964

Copy link
Copy Markdown
Author

Hi @ryanjbaxter!
I have updated the branch to resolve the merge conflicts with the latest main. Could you please take a look when you have some time? Thank you!

Copilot AI 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.

Pull request overview

This PR extends the AWS S3 environment repository to support Git-style searchPaths (placeholders + wildcard matching), enabling S3-backed config layouts to match existing Git-backed repository conventions.

Changes:

  • Add searchPaths configuration to the AWS S3 backend and wire it through the factory.
  • Implement placeholder expansion and wildcard-based S3 key discovery (including directory scans) with deduplication.
  • Add an extensive JUnit 5 test suite covering placeholder/wildcard resolution scenarios.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 10 comments.

File Description
spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepository.java Adds searchPaths support, wildcard matching via AntPathMatcher, S3 list/head probing, and key→property source wrapping.
spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepositoryFactory.java Passes searchPaths from properties into the repository constructor.
spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentProperties.java Introduces searchPaths as a bindable configuration property.
spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepositoryTests.java Adds coverage for placeholder/wildcard behavior, ordering, and special cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Fixes spring-cloud#2812

Rework AwsS3EnvironmentRepository path resolution and profile handling. Reverse the app list before checking/appending the default application and only append the default when it is missing. Simplify property-source iteration and invoke negated-profile property source logic only when searchPaths is empty to avoid duplicate sources. Normalize search path patterns by resolving null label/profile vars, collapsing duplicate slashes and trimming leading '/', and optimize extension probing so existing extensions aren't re-probed.

Signed-off-by: Geonwook Ham <tomy8964@naver.com>
@tomy8964 tomy8964 force-pushed the feature/gh-2812-aws-s3-searchpaths branch from ad8fc5a to daa088a Compare June 2, 2026 12:36
tomy8964 added 2 commits June 2, 2026 21:41
Fixes spring-cloud#2812

In AwsS3EnvironmentRepositoryTests, avoid mutating a shared 'server' instance by creating a new ConfigServerProperties (serverWithDefaultLabel) and setting its defaultLabel before constructing AwsS3EnvironmentRepository. This prevents side-effects on shared test state and ensures the repository gets the intended default label for the test.

Signed-off-by: Geonwook Ham <tomy8964@naver.com>
@tomy8964

tomy8964 commented Jun 2, 2026

Copy link
Copy Markdown
Author

Hi @ryanjbaxter!

I have successfully addressed all the feedback from the Copilot review:

  1. Fixed property source precedence and default application fallback in findOne().
  2. Restored the native Spring Cloud Config profile activation lifecycle (including negated profiles) by refactoring addPropertySources().
  3. Added path normalization (trimming leading slashes and duplicate //) and optimized literal extension probing to avoid redundant S3 calls.
  4. Isolated the test state in AwsS3EnvironmentRepositoryTests by using a fresh ConfigServerProperties instance.

The checks are passing, and it's ready for your review. Could you please take another look when you have some time? Thank you!

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

tomy8964 and others added 2 commits June 2, 2026 23:04
The for loop header and the first statement are on the same line (for (...) {String resolvedLabel = ...), which is likely to fail formatting checks (and is hard to read). Split the loop body onto its own lines.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Geonwook Ham <tomy8964@naver.com>
Fixes spring-cloud#2812

Generalize getS3ConfigFileWithSearchPaths by adding a Function keyWrapper to handle different key-wrapping strategies (profile-specific and negated-profile cases). Implement wrapKeyWithNegatedConfigFiles to support YAML documents with negated spring.config.activate.on-profile expressions and wire it into property source discovery. Clean up formatting, streamline S3 client calls, and simplify create/wrapper methods. Also remove explicit setOrder in AwsS3EnvironmentRepositoryFactory (return repository directly) and update tests for minor formatting/usage changes.

Signed-off-by: Geonwook Ham <tomy8964@naver.com>
@tomy8964

tomy8964 commented Jun 2, 2026

Copy link
Copy Markdown
Author

Hi @ryanjbaxter!

I have successfully addressed the second round of feedback from the Copilot review:

  • Generalized S3 Search Logic: Refactored getS3ConfigFileWithSearchPaths by introducing a functional Function (keyWrapper) parameter, eliminating duplicate S3 object crawling and pagination logic between profile-specific and negated-profile cases.
  • Restored Negated Profiles: Implemented wrapKeyWithNegatedConfigFiles to fully support multi-document YAML files with negated profile expressions under the searchPaths architecture.
  • Formatting & Factory Cleanup: Streamlined the method chains, wrapped arguments to comply with project formatting conventions, and removed the redundant setOrder call in AwsS3EnvironmentRepositoryFactory.
  • Test Syntax Fixes: Corrected minor YAML formatting issues (missing spaces after colons) within AwsS3EnvironmentRepositoryTests.

The checks are passing again, and the codebase is now much cleaner and more reusable. Could you please take another look when you have some time? Thank you!

@ryanjbaxter ryanjbaxter 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.

Could you also add documentation?

tomy8964 added 3 commits June 4, 2026 21:48
…dant callReadImmediately flags

Signed-off-by: Geonwook Ham <tomy8964@naver.com>
Signed-off-by: Geonwook Ham <tomy8964@naver.com>
@tomy8964

tomy8964 commented Jun 6, 2026

Copy link
Copy Markdown
Author

Hi @ryanjbaxter!
We have successfully addressed all of your feedback and suggestions. Here is a summary of the changes:

Summary of Changes

  1. searchPaths Optimization & Safety
    • Initialized searchPaths using Collections.emptyList() by default in both AwsS3EnvironmentProperties and AwsS3EnvironmentRepository.
    • Updated the repository constructor to guard against null and pass Collections.emptyList() instead.
    • Cleaned up the factory builder by introducing a constructor that accepts AwsS3EnvironmentProperties directly, removing the redundant setOrder call.
  2. Cleanups & Constant Extraction
    • Extracted S3 file extension arrays into class-level private static final constants (SUPPORTED_EXTENSIONS and EMPTY_EXTENSION) and reused them.
    • Refactored isSimpleProfileName to be a package-private static helper method and reused it in wrapKeyWithNegatedConfigFiles.
    • Removed numbered comments in the test cases (AwsS3EnvironmentRepositoryTests).
  3. Subclass Refactoring
    • Consolidated the redundant key-based S3ConfigFile subclasses (PropertyConfigFileFromKey, YamlConfigFileFromKey, and JsonConfigFileFromKey) into a single S3ConfigFileFromKey class, completely removing the callReadImmediately flag.
  4. S3 File Probe & Scanning Improvements
    • Corrected a bug by removing the break statement in the literal file probing loop, ensuring all matching files with different extensions (e.g., both .properties and .yml at the same path) are properly loaded.
    • Added an INFO level log statement when checking S3 object keys before rethrowing unexpected S3Exceptions to assist troubleshooting.
    • Refactored getS3ConfigFileWithSearchPaths into smaller, self-documenting helper methods (probeLiteralPattern, scanDirectoryPattern, probeDotWildcardPattern, scanWildcardPattern) to improve readability and maintainability.
  5. Documentation
    • Added comprehensive documentation for the S3 search-paths feature in aws-s3-backend.adoc, explaining literal/directory paths, wildcards, placeholders, and providing a YAML configuration example.
      All tests have been run and verified. Please let us know if there is anything else that needs to be addressed. Thank you! 🙏

@tomy8964 tomy8964 requested a review from ryanjbaxter June 6, 2026 03:37
private Optional<S3ConfigFile> createConfigFileFromKey(String key, String application, String profile,
String label) {
String ext = key.substring(key.lastIndexOf('.') + 1);
if ("properties".equalsIgnoreCase(ext)) {

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.

The return for these 3 conditions looks identical to me

return this.order;
}

public void setOrder(int order) {

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.

We cannot remove public methods

putFiles("v1/foo/foo.json", jsonContent);

Environment env = repo.findOne("foo", "", "v1");
assertThat(env.getPropertySources()).hasSize(2);

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.

What about validating the 2nd property source

private String extractPrefix(String pattern) {
int idx = pattern.indexOf('*');
int q = pattern.indexOf('?');
if (q != -1 && (idx == -1 || q < idx)) {

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 think there is a bug here if the pattern begins with *.

If q == -1 and idx == 0 (wildcard at the very start), the else if is never entered because the outer if was false (q == -1), so idx stays 0 and the method falls through to the lastIndexOf logic rather than returning ""

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AwsS3EnvironmentRepository does not comply with Git

4 participants