Skip to content

[Java] Support dynamic secret provider registration via SecretRegistrar - #39940

Open
shunping wants to merge 1 commit into
apache:masterfrom
shunping:secret-manager-4
Open

[Java] Support dynamic secret provider registration via SecretRegistrar#39940
shunping wants to merge 1 commit into
apache:masterfrom
shunping:secret-manager-4

Conversation

@shunping

@shunping shunping commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Previously, Secret.java had hardcoded switch statements and factory calls for specific secret managers (such as Google Cloud Secret Manager and HSM-generated secrets). Supporting a new secret manager required directly modifying Secret.java.

This PR refactors Secret to follow Apache Beam's standard Service Provider Interface (SPI) pattern (similar to FileSystems / FileSystemRegistrar). It allows secret providers to register dynamically at runtime via ServiceLoader and @AutoService, decoupling the core Secret management from specific provider implementations.

@shunping shunping changed the title Refactor GCP related secret classes and tests to extensions/google-cloud-platform-core Move GCP related secret classes and tests to extensions/google-cloud-platform-core Aug 31, 2026
@shunping
shunping force-pushed the secret-manager-4 branch 3 times, most recently from 2f5bde7 to 47e95fd Compare September 1, 2026 16:05
@shunping shunping changed the title Move GCP related secret classes and tests to extensions/google-cloud-platform-core [Java] Support dynamic secret provider registration via SecretRegistrar Sep 1, 2026
@shunping
shunping marked this pull request as ready for review September 1, 2026 16:13
@shunping

shunping commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

r: @Abacn

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Stopping reviewer notifications for this pull request: review requested by someone other than the bot, ceding control. If you'd like to restart, comment assign set of reviewers

Follow the FileSystems registration pattern by introducing SecretRegistrar SPI
and auto-service discovery in Secret.java. This eliminates hardcoded secret provider
logic in Secret.java and allows modular extension for new secret managers.
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.38%. Comparing base (2c11a5f) to head (e2a5e59).
⚠️ Report is 21 commits behind head on master.

Files with missing lines Patch % Lines
...src/main/java/org/apache/beam/sdk/util/Secret.java 75.00% 6 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             master   #39940   +/-   ##
=========================================
  Coverage     58.37%   58.38%           
- Complexity    13491    13500    +9     
=========================================
  Files          2568     2571    +3     
  Lines        268743   268751    +8     
  Branches      11029    11032    +3     
=========================================
+ Hits         156888   156898   +10     
- Misses       105904   105905    +1     
+ Partials       5951     5948    -3     
Flag Coverage Δ
java 64.54% <77.77%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

smManager));
SecretRegistrar.SecretFactory factory = SECRET_FACTORIES.get(smManager.toLowerCase());
if (factory != null) {
return factory.createSecret(specMap != null ? specMap : Collections.emptyMap());

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.

Previously when specMap = null (jackson parser throws) it falls back to return GcpSecret.fromMap(specMap), now it becomes a factory.createSecret(Collections.emptyMap()) and The raw spec string is effectively dropped. Any concern here?

String.format(
"Duplicate SecretRegistrar for secret manager name '%s': %s and %s",
key,
factories.get(key).getClass().getName(),

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.

When using method references like GcpSecret::fromMap, their class names are compiler-generated lambdas (e.g., org.apache.beam.sdk.util.GcpSecret$$Lambda$142/0x000...) which is not helpful

Checked by test:

@Test
  public void testGcpHsmGeneratedSecretRegistrarServiceLoader() {
    for (SecretRegistrar registrar :
        Lists.newArrayList(ServiceLoader.load(SecretRegistrar.class).iterator())) {
      if (registrar instanceof GcpHsmGeneratedSecretRegistrar) {
        Map<String, SecretRegistrar.SecretFactory> factories = registrar.getSecretFactories();
        assertThat(
            factories.keySet(),
            hasItems("googlecloudhsmgeneratedsecretmanager", "gcphsmgeneratedsecret"));
        return;
      }
    }
    fail("Expected to find " + GcpHsmGeneratedSecretRegistrar.class);
  }

We should print actual SecretRegistrar implementations. A way to do this is to track the registering registrar in a separate map to report the actual conflicting SecretRegistrar classes

=========

If user shade Beam and relocated packages, there is still risk of duplicating classes. Not sure how should we handle them.

registrar.getSecretFactories().entrySet()) {
String key = entry.getKey().toLowerCase();
if (factories.containsKey(key)) {
throw new IllegalStateException(

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.

In general, AutoService scans all classes loaded to JVM and if there are malformed SecretRegistrar (e.g. from unit test, etc) leaked into class path, it will crash the whole loadSecretFactories. For example, if getSecretFactories() returns null or other situations.

Since here it dynamically loads class and execute codes, consider a fail safe handling here.

@Override
public Map<String, SecretFactory> getSecretFactories() {
return ImmutableMap.of(
"googlecloudsecretmanager", GcpSecret::fromMap,

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.

Error readability: previously, the error displayed the canonical PascalCase names:

https://github.com/apache/beam/pull/39940/changes#diff-48e6d847440ce6ec0da331ff4d7a2b63bba04d6d2e8ca6686a25d88560e869dcL110

now SECRET_FACTORIES.keySet() returns all lower cases.

consider retaining the original camel case?

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants