Skip to content

Add support for credential_injector#6785

Merged
jrhee17 merged 5 commits into
line:mainfrom
jrhee17:feat/credential-injector
Jun 4, 2026
Merged

Add support for credential_injector#6785
jrhee17 merged 5 commits into
line:mainfrom
jrhee17:feat/credential-injector

Conversation

@jrhee17
Copy link
Copy Markdown
Contributor

@jrhee17 jrhee17 commented May 27, 2026

This PR should be reviewed after #6784
This PR is a subset of #6781

Motivation:

xDS extension factories (e.g. HttpFilterFactory) currently receive only an XdsResourceValidator, which is insufficient for filters that depend on external xDS resources like SDS secrets. The CredentialInjectorFilterFactory (envoy.filters.http.credential_injector) needs to reactively subscribe to generic secrets via SDS and inject credentials into outgoing requests, requiring access to the event loop, metrics, and secret streams at construction time.

Modifications:

  • Added FactoryContext interface providing runtime infrastructure to extension factories (eventLoop(), meterRegistry(), meterIdPrefix(), validator(), genericSecretStream()).
  • Changed HttpFilterFactory.create() to accept FactoryContext instead of XdsResourceValidator.
  • Added HttpFilterFactory.createStream() that returns a SnapshotStream<XdsHttpFilter> for reactive filter lifecycle management.
  • Added XdsHttpFilter.NOOP static constant for no-op filter instances.
  • Added GenericSecretSnapshot and GenericSecretStream for resolving generic secrets via SDS.
  • Made the filter pipeline reactive: FilterUtil now returns SnapshotStream<ClientPreprocessors> / SnapshotStream<ClientDecoration>, and RouteStream composes cluster, downstream, and upstream filter streams via combineLatest.
  • Simplified RouteEntry to accept pre-built filter configs instead of building them internally.
  • Updated ListenerResourceParser / ListenerXdsResource to parse and carry the Router HTTP filter for upstream filter extraction.
  • Made SubscriptionContext extend FactoryContext for internal use.
  • Added CredentialInjectorFilterFactory implementing envoy.filters.http.credential_injector, registered via service loader.
  • Annotated TypedExtensionConfig, CredentialInjector, Generic, and Secret.generic_secret proto fields as supported.
  • Updated RouterFilterFactory and IstioFilterFactories to use FactoryContext.

Result:

  • xDS extension factories can now build reactive filter pipelines that depend on external resources like SDS secrets.
  • The CredentialInjectorFilterFactory injects credentials from SDS-backed generic secrets into outgoing HTTP requests, supporting the envoy.filters.http.credential_injector filter type.

@jrhee17 jrhee17 added this to the 1.40.0 milestone May 27, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c54f0ab9-c10f-4722-b2ba-cd06a2ec52f1

📥 Commits

Reviewing files that changed from the base of the PR and between 8ed868a and 1b2f161.

📒 Files selected for processing (3)
  • xds/src/main/java/com/linecorp/armeria/xds/filter/CredentialInjectorFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/RescheduleSubscription.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/SnapshotStream.java
💤 Files with no reviewable changes (1)
  • xds/src/main/java/com/linecorp/armeria/xds/stream/SnapshotStream.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • xds/src/main/java/com/linecorp/armeria/xds/stream/RescheduleSubscription.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/CredentialInjectorFilterFactory.java

📝 Walkthrough

Walkthrough

Adds Envoy credential injector support with reactive SDS/file/bootstrap secret resolution, FactoryContext API, SnapshotStream event-loop wrappers and RescheduleSubscription, reactive filter composition via FilterUtil, explicit Router extraction and route composition updates, plus integration tests covering injection scenarios.

Changes

Credential Injector Filter Implementation

Layer / File(s) Summary
Proto metadata annotations
xds-api/src/main/proto/...
Proto files annotated with Armeria XDS supported-field options for typed extensions, credential injector fields, generic injected-credentials, and generic secret/oneof mapping.
SnapshotStream event-loop wrappers & RescheduleSubscription
xds/src/main/java/com/linecorp/armeria/xds/stream/SnapshotStream.java, xds/src/main/java/com/linecorp/armeria/xds/stream/RescheduleSubscription.java
Adds checkSubscribeOn(EventExecutor) and rescheduleEventsOn(EventExecutor) wrappers; implements RescheduleSubscription to schedule onUpdate deliveries on a given EventExecutor.
FactoryContext API and HttpFilterFactory updates
xds/src/main/java/com/linecorp/armeria/xds/filter/FactoryContext.java, xds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactory.java, xds/src/main/java/com/linecorp/armeria/xds/filter/XdsHttpFilter.java
Introduces FactoryContext (event loop, metrics, validator, genericSecretStream); HttpFilterFactory#create now takes FactoryContext and gains default createStream(); adds XdsHttpFilter.noop().
Generic secret snapshot & stream and SubscriptionContext wiring
xds/src/main/java/com/linecorp/armeria/xds/GenericSecretSnapshot.java, xds/src/main/java/com/linecorp/armeria/xds/GenericSecretStream.java, xds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.java
Adds GenericSecretSnapshot and GenericSecretStream to resolve credentials from GenericSecret (SDS/DataSource); SubscriptionContext extends FactoryContext and provides genericSecretStream().
Credential injector filter factory and implementation
xds/src/main/java/com/linecorp/armeria/xds/filter/CredentialInjectorFilterFactory.java, xds/src/main/resources/META-INF/services/com.linecorp.armeria.xds.filter.HttpFilterFactory
Adds CredentialInjectorFilterFactory.createStream() that subscribes to GenericSecretSnapshot, validates Generic credential type, determines header (default authorization), and emits filters that inject credentials, enforce allow_request_without_credential, and honor overwrite/header-existence checks. Service loader entry added.
Reactive filter composition in FilterUtil
xds/src/main/java/com/linecorp/armeria/xds/FilterUtil.java
FilterUtil now resolves filters to SnapshotStream, combines streams with combineNLatest to produce SnapshotStream/SnapshotStream, and centralizes decoration building.
Listener router extraction and route composition
xds/src/main/java/com/linecorp/armeria/xds/ListenerResourceParser.java, xds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.java, xds/src/main/java/com/linecorp/armeria/xds/RouteEntry.java, xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java
ListenerResourceParser extracts Router explicitly from HCM terminal filter; ListenerXdsResource stores Router; RouteEntry accepts precomputed filterConfigs and downstream/upstream streams; RouteStream merges per-filter-configs and composes reactive filter streams with cluster stream.
Factory implementations and imports updated
it/xds-istio/src/test/.../IstioFilterFactories.java, xds/src/main/java/com/linecorp/armeria/xds/client/endpoint/RouterFilterFactory.java, xds/src/main/java/com/linecorp/armeria/xds/ListenerManager.java
IstioFilterFactories and RouterFilterFactory updated to use FactoryContext in create(); ListenerManager formatting/import adjustment for Subscription.
Integration tests
it/xds-client/src/test/java/com/linecorp/armeria/xds/it/CredentialInjectorFilterTest.java
Integration tests boot xDS control plane + echo server and exercise inline/static/file/SDS secret injection, allow_request_without_credential behavior, overwrite=false preservation, and custom-header injection.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • line/armeria#6784: SnapshotStream/reactive snapshot infra publication that this change builds upon.
  • line/armeria#6723: Related xDS extension-resolution and factory/Any unpacking changes relied upon by the new factories.
  • line/armeria#6710: Core refactor of filter config handling and factory interfaces connected to these changes.

Suggested labels

breaking change

Suggested reviewers

  • trustin
  • ikhoon
  • minwoox

Poem

"I hop on event loops, ears aflutter,
Secrets tucked in bytes, no need to stutter.
Headers bloom where tokens fall,
Streams reschedule, answering call.
A nimble rabbit cheers the xDS hall! 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main feature being added: support for the credential_injector filter.
Description check ✅ Passed The PR description comprehensively explains the motivation, modifications, and results. It clearly relates to the changeset and provides sufficient context for reviewers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@xds/src/main/java/com/linecorp/armeria/xds/filter/CredentialInjectorFilterFactory.java`:
- Around line 57-76: The new public methods in CredentialInjectorFilterFactory
(name(), typeUrls(), create(HttpFilter, Any, FactoryContext),
createStream(HttpFilter, Any, FactoryContext)) lack method-level Javadoc; add
concise Javadoc to each public/protected method in the class (or use
{`@inheritDoc`} where it simply implements/overrides behavior) so the public API
is documented per guidelines—ensure the Javadoc on create(...) explains it
throws UnsupportedOperationException and that createStream(...) returns a
SnapshotStream for reactive secret subscription, and include brief descriptions
for name() and typeUrls().
- Around line 69-76: The public methods create(...) and createStream(...) in
CredentialInjectorFilterFactory must validate their parameters; add explicit
null checks using Objects.requireNonNull for each user-facing parameter
(httpFilter, config, and context) at the start of both methods (e.g.,
Objects.requireNonNull(httpFilter, "httpFilter"); Objects.requireNonNull(config,
"config"); Objects.requireNonNull(context, "context")). Ensure these checks are
placed before any other logic or thrown exceptions so callers get a clear NPE
with the parameter name.

In `@xds/src/main/java/com/linecorp/armeria/xds/stream/SnapshotStream.java`:
- Around line 72-171: Add explicit null checks using Objects.requireNonNull for
all public API parameters: in SnapshotStream.map validate "mapper"; in
switchMapEager validate "mapper"; in combineNLatest validate "streams"; in both
combineLatest overloads validate "a", "b", and "combiner" (and "c" for the 3-arg
overload); and in error validate "error". Use Objects.requireNonNull(param,
"paramName") at the start of each method before constructing the corresponding
stream implementation (MapStream, SwitchMapEagerStream, CombineNLatestStream,
CombineLatest2Stream, CombineLatest3Stream, StaticSnapshotStream) so invalid
null inputs fail fast with clear messages.

In `@xds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.java`:
- Around line 33-37: SubscriptionContext#genericSecretStream currently forwards
a potentially null sdsSecretConfig into new SecretStream(...) without
validation; add a null check at the start of the default method
(genericSecretStream) that throws a clear NPE (or IllegalArgumentException) if
sdsSecretConfig is null, then proceed to construct new
SecretStream(sdsSecretConfig, null, this). Keep the rest of the chain
(.switchMapEager(... new GenericSecretStream(this, resource))
.checkSubscribeOn(eventLoop())) unchanged so behavior and call sites remain the
same.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 67323ba0-838a-4222-8d0c-b2503814f361

📥 Commits

Reviewing files that changed from the base of the PR and between fc75bbb and 6ad3afd.

📒 Files selected for processing (54)
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/it/CredentialInjectorFilterTest.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/xds/filter/IstioFilterFactories.java
  • xds-api/src/main/proto/envoy/config/core/v3/extension.proto
  • xds-api/src/main/proto/envoy/extensions/filters/http/credential_injector/v3/credential_injector.proto
  • xds-api/src/main/proto/envoy/extensions/http/injected_credentials/generic/v3/generic.proto
  • xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto
  • xds/src/main/java/com/linecorp/armeria/xds/CertificateValidationContextStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/ClusterRoot.java
  • xds/src/main/java/com/linecorp/armeria/xds/ClusterStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/DataSourceStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/EndpointStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/FilterUtil.java
  • xds/src/main/java/com/linecorp/armeria/xds/GenericSecretSnapshot.java
  • xds/src/main/java/com/linecorp/armeria/xds/GenericSecretStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerManager.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerResourceParser.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerRoot.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.java
  • xds/src/main/java/com/linecorp/armeria/xds/RawBufferTransportSocketFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeAdapter.java
  • xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeMeterBinderFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/RouteEntry.java
  • xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/SecretStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/SnapshotStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/SnapshotWatcher.java
  • xds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.java
  • xds/src/main/java/com/linecorp/armeria/xds/TlsCertificateStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/TransportSocketFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/TransportSocketStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/UpstreamTlsTransportSocketFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/XdsClusterManager.java
  • xds/src/main/java/com/linecorp/armeria/xds/client/endpoint/RouterFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/CredentialInjectorFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/FactoryContext.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/XdsHttpFilter.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/CombineLatest2Stream.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/CombineLatest3Stream.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/CombineNLatestStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/MapStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/RefCountedStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/RescheduleSubscription.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/SnapshotStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/StaticSnapshotStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/Subscription.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/SwitchMapEagerStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/TriFunction.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/package-info.java
  • xds/src/main/resources/META-INF/services/com.linecorp.armeria.xds.filter.HttpFilterFactory
  • xds/src/test/java/com/linecorp/armeria/xds/stream/CombineNLatestStreamTest.java
  • xds/src/test/java/com/linecorp/armeria/xds/stream/RefCountedStreamTest.java
  • xds/src/test/java/com/linecorp/armeria/xds/stream/StreamSwitchMapEagerTest.java
💤 Files with no reviewable changes (1)
  • xds/src/main/java/com/linecorp/armeria/xds/SnapshotStream.java

Comment on lines +69 to +76
public XdsHttpFilter create(HttpFilter httpFilter, Any config, FactoryContext context) {
throw new UnsupportedOperationException(
"credential_injector requires reactive secret subscription; use createStream()");
}

@Override
public SnapshotStream<XdsHttpFilter> createStream(HttpFilter httpFilter, Any config,
FactoryContext context) {
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate public API inputs explicitly with Objects.requireNonNull.

Line 69 and Line 75 expose public entry points but do not perform required null validation on parameters.

Suggested fix
 import java.util.List;
+import java.util.Objects;
@@
     `@Nullable`
     public XdsHttpFilter create(HttpFilter httpFilter, Any config, FactoryContext context) {
+        Objects.requireNonNull(httpFilter, "httpFilter");
+        Objects.requireNonNull(config, "config");
+        Objects.requireNonNull(context, "context");
         throw new UnsupportedOperationException(
                 "credential_injector requires reactive secret subscription; use createStream()");
     }
@@
     public SnapshotStream<XdsHttpFilter> createStream(HttpFilter httpFilter, Any config,
                                                       FactoryContext context) {
+        Objects.requireNonNull(httpFilter, "httpFilter");
+        Objects.requireNonNull(config, "config");
+        Objects.requireNonNull(context, "context");
         final CredentialInjector injectorConfig = context.validator().unpack(config,
                                                                              CredentialInjector.class);
As per coding guidelines "do explicit `null` validation on user-facing public method parameters using `Objects.requireNonNull(x, \"name\")`".
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public XdsHttpFilter create(HttpFilter httpFilter, Any config, FactoryContext context) {
throw new UnsupportedOperationException(
"credential_injector requires reactive secret subscription; use createStream()");
}
@Override
public SnapshotStream<XdsHttpFilter> createStream(HttpFilter httpFilter, Any config,
FactoryContext context) {
`@Nullable`
public XdsHttpFilter create(HttpFilter httpFilter, Any config, FactoryContext context) {
Objects.requireNonNull(httpFilter, "httpFilter");
Objects.requireNonNull(config, "config");
Objects.requireNonNull(context, "context");
throw new UnsupportedOperationException(
"credential_injector requires reactive secret subscription; use createStream()");
}
`@Override`
public SnapshotStream<XdsHttpFilter> createStream(HttpFilter httpFilter, Any config,
FactoryContext context) {
Objects.requireNonNull(httpFilter, "httpFilter");
Objects.requireNonNull(config, "config");
Objects.requireNonNull(context, "context");
final CredentialInjector injectorConfig = context.validator().unpack(config,
CredentialInjector.class);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/armeria/xds/filter/CredentialInjectorFilterFactory.java`
around lines 69 - 76, The public methods create(...) and createStream(...) in
CredentialInjectorFilterFactory must validate their parameters; add explicit
null checks using Objects.requireNonNull for each user-facing parameter
(httpFilter, config, and context) at the start of both methods (e.g.,
Objects.requireNonNull(httpFilter, "httpFilter"); Objects.requireNonNull(config,
"config"); Objects.requireNonNull(context, "context")). Ensure these checks are
placed before any other logic or thrown exceptions so callers get a clear NPE
with the parameter name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is called by internal APIs

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.

Understood — since create() and createStream() are invoked only by internal framework code (FilterUtil.resolveInstance) that controls the inputs, the null validation guard is unnecessary here. Withdrawing the suggestion.


✏️ Learnings added
Learnt from: jrhee17
Repo: line/armeria PR: 6785
File: xds/src/main/java/com/linecorp/armeria/xds/filter/CredentialInjectorFilterFactory.java:69-76
Timestamp: 2026-06-01T07:24:42.277Z
Learning: In the Armeria XDS module, `HttpFilterFactory.create()` and `HttpFilterFactory.createStream()` (and their overrides, e.g. in `CredentialInjectorFilterFactory`) are called exclusively by internal framework code (`FilterUtil.resolveInstance`). They are NOT directly user-facing entry points, so `Objects.requireNonNull` parameter validation is not required on their parameters (`httpFilter`, `config`, `context`).

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: jrhee17
Repo: line/armeria PR: 6628
File: xds/src/main/java/com/linecorp/armeria/xds/TransportSocketSnapshot.java:48-48
Timestamp: 2026-02-19T06:03:54.715Z
Learning: In the Armeria XDS module (package com.linecorp.armeria.xds), code paths that run on the event loop (e.g., TransportSocketSnapshot and related XDS paths) are single-threaded. Do not use volatile or AtomicBoolean to guard static fields that are accessed only within these execution paths. If a field must be thread-safe, restrict access to code outside the event loop or document the event-loop assumption; otherwise, keep such fields as plain statics initialized once and mutably used only within the event loop context.

Comment thread xds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.java
@jrhee17 jrhee17 force-pushed the feat/credential-injector branch from 6ad3afd to 4b1330b Compare June 1, 2026 06:56
@jrhee17 jrhee17 marked this pull request as ready for review June 1, 2026 07:30
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

❌ Patch coverage is 68.24645% with 67 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.00%. Comparing base (8150425) to head (1b2f161).
⚠️ Report is 478 commits behind head on main.

Files with missing lines Patch % Lines
...ia/xds/filter/CredentialInjectorFilterFactory.java 60.86% 14 Missing and 4 partials ⚠️
...main/java/com/linecorp/armeria/xds/FilterUtil.java 57.57% 10 Missing and 4 partials ⚠️
...om/linecorp/armeria/xds/GenericSecretSnapshot.java 27.77% 13 Missing ⚠️
.../com/linecorp/armeria/xds/GenericSecretStream.java 71.42% 4 Missing and 2 partials ⚠️
...ain/java/com/linecorp/armeria/xds/RouteStream.java 81.48% 3 Missing and 2 partials ⚠️
...m/linecorp/armeria/xds/ListenerResourceParser.java 71.42% 2 Missing and 2 partials ⚠️
...linecorp/armeria/xds/filter/HttpFilterFactory.java 50.00% 1 Missing and 1 partial ⚠️
...linecorp/armeria/xds/filter/NoopXdsHttpFilter.java 0.00% 2 Missing ⚠️
...orp/armeria/xds/stream/RescheduleSubscription.java 88.23% 0 Missing and 2 partials ⚠️
...com/linecorp/armeria/xds/filter/XdsHttpFilter.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6785      +/-   ##
============================================
+ Coverage     74.46%   75.00%   +0.54%     
- Complexity    22234    24915    +2681     
============================================
  Files          1963     2215     +252     
  Lines         82437    92677   +10240     
  Branches      10764    12105    +1341     
============================================
+ Hits          61385    69512    +8127     
- Misses        15918    17372    +1454     
- Partials       5134     5793     +659     

☔ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
xds/src/main/java/com/linecorp/armeria/xds/stream/SnapshotStream.java (1)

194-224: ⚡ Quick win

Validate watcher in the wrapper subscriptions.

Both lambdas implement a public subscribe(...) path, but neither checks watcher for null before delegating. Add requireNonNull(watcher, "watcher") at the top of each lambda so failures stay consistent with the rest of this API.

Proposed fix
     default SnapshotStream<T> checkSubscribeOn(EventExecutor eventLoop) {
         requireNonNull(eventLoop, "eventLoop");
         final SnapshotStream<T> self = this;
         return watcher -> {
+            requireNonNull(watcher, "watcher");
             checkState(eventLoop.inEventLoop(),
                        "subscribe must be called from the event loop: %s", eventLoop);
             final Subscription sub = self.subscribe(watcher);
             return () -> {
@@
     default SnapshotStream<T> rescheduleEventsOn(EventExecutor eventLoop) {
         requireNonNull(eventLoop, "eventLoop");
         final SnapshotStream<T> self = this;
         return watcher -> {
+            requireNonNull(watcher, "watcher");
             final RescheduleSubscription<T> sub = new RescheduleSubscription<>(watcher, eventLoop);
             sub.setUpstream(self.subscribe(sub));
             return sub;
         };
     }

As per coding guidelines, "Null/validation: do explicit parameter null-checks for user-facing public methods using Objects.requireNonNull(obj, "name")."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xds/src/main/java/com/linecorp/armeria/xds/stream/SnapshotStream.java` around
lines 194 - 224, The two wrapper lambdas returned by
SnapshotStream.checkSubscribeOn(EventExecutor) and
SnapshotStream.rescheduleEventsOn(EventExecutor) do not validate the watcher
parameter; add requireNonNull(watcher, "watcher") as the first statement inside
each lambda (the subscribe(...) implementations) so the public subscribe path
fails fast and consistently with the API; update the lambdas in checkSubscribeOn
and rescheduleEventsOn to call Objects.requireNonNull(watcher, "watcher") (or
requireNonNull if statically imported) before any other logic such as
checkState, self.subscribe, or constructing RescheduleSubscription.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@xds/src/main/java/com/linecorp/armeria/xds/stream/RescheduleSubscription.java`:
- Around line 42-55: The RescheduleSubscription.close() must preserve event-loop
affinity and visibility: schedule the close logic onto eventLoop (like
eventLoop.execute(() -> { closed = true; if (upstream != null) upstream.close();
})) instead of calling upstream.close() on the caller thread so upstream.close()
runs on the same event loop as onUpdate handlers, and also mark the closed field
as volatile to ensure visibility across threads; update the close() method and
the closed field accordingly (references: class RescheduleSubscription, methods
onUpdate and close, fields eventLoop, upstream, closed).

---

Nitpick comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/stream/SnapshotStream.java`:
- Around line 194-224: The two wrapper lambdas returned by
SnapshotStream.checkSubscribeOn(EventExecutor) and
SnapshotStream.rescheduleEventsOn(EventExecutor) do not validate the watcher
parameter; add requireNonNull(watcher, "watcher") as the first statement inside
each lambda (the subscribe(...) implementations) so the public subscribe path
fails fast and consistently with the API; update the lambdas in checkSubscribeOn
and rescheduleEventsOn to call Objects.requireNonNull(watcher, "watcher") (or
requireNonNull if statically imported) before any other logic such as
checkState, self.subscribe, or constructing RescheduleSubscription.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8aad84cd-aabd-4b72-b9a4-5f663c42cedd

📥 Commits

Reviewing files that changed from the base of the PR and between 6ad3afd and 4b1330b.

📒 Files selected for processing (23)
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/it/CredentialInjectorFilterTest.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/xds/filter/IstioFilterFactories.java
  • xds-api/src/main/proto/envoy/config/core/v3/extension.proto
  • xds-api/src/main/proto/envoy/extensions/filters/http/credential_injector/v3/credential_injector.proto
  • xds-api/src/main/proto/envoy/extensions/http/injected_credentials/generic/v3/generic.proto
  • xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto
  • xds/src/main/java/com/linecorp/armeria/xds/FilterUtil.java
  • xds/src/main/java/com/linecorp/armeria/xds/GenericSecretSnapshot.java
  • xds/src/main/java/com/linecorp/armeria/xds/GenericSecretStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerManager.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerResourceParser.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.java
  • xds/src/main/java/com/linecorp/armeria/xds/RouteEntry.java
  • xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.java
  • xds/src/main/java/com/linecorp/armeria/xds/client/endpoint/RouterFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/CredentialInjectorFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/FactoryContext.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/XdsHttpFilter.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/RescheduleSubscription.java
  • xds/src/main/java/com/linecorp/armeria/xds/stream/SnapshotStream.java
  • xds/src/main/resources/META-INF/services/com.linecorp.armeria.xds.filter.HttpFilterFactory
✅ Files skipped from review due to trivial changes (2)
  • xds-api/src/main/proto/envoy/extensions/filters/http/credential_injector/v3/credential_injector.proto
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerManager.java
🚧 Files skipped from review as they are similar to previous changes (19)
  • xds/src/main/resources/META-INF/services/com.linecorp.armeria.xds.filter.HttpFilterFactory
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/xds/filter/IstioFilterFactories.java
  • xds-api/src/main/proto/envoy/config/core/v3/extension.proto
  • xds-api/src/main/proto/envoy/extensions/http/injected_credentials/generic/v3/generic.proto
  • xds/src/main/java/com/linecorp/armeria/xds/filter/XdsHttpFilter.java
  • xds/src/main/java/com/linecorp/armeria/xds/client/endpoint/RouterFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/GenericSecretSnapshot.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/FactoryContext.java
  • xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerResourceParser.java
  • xds/src/main/java/com/linecorp/armeria/xds/RouteEntry.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/CredentialInjectorFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/GenericSecretStream.java
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/it/CredentialInjectorFilterTest.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.java
  • xds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.java
  • xds/src/main/java/com/linecorp/armeria/xds/FilterUtil.java

Comment on lines +42 to +55
public void onUpdate(@Nullable T value, @Nullable Throwable error) {
eventLoop.execute(() -> {
if (!closed) {
downstream.onUpdate(value, error);
}
});
}

@Override
public void close() {
closed = true;
if (upstream != null) {
upstream.close();
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jun 1, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve event-loop affinity when closing the rescheduled subscription.

onUpdate() always hops onto eventLoop, but close() forwards upstream.close() on the caller thread. That breaks composition with checkSubscribeOn(eventLoop) and also shares closed across threads without a visibility guarantee. Close should either hop to eventLoop or explicitly enforce same-thread close semantics.

Proposed fix
 final class RescheduleSubscription<T> implements SnapshotWatcher<T>, Subscription {
@@
-    private boolean closed;
+    private volatile boolean closed;
@@
     `@Override`
     public void close() {
+        if (closed) {
+            return;
+        }
         closed = true;
-        if (upstream != null) {
-            upstream.close();
+        final Subscription upstream = this.upstream;
+        if (upstream != null) {
+            if (eventLoop.inEventLoop()) {
+                upstream.close();
+            } else {
+                eventLoop.execute(upstream::close);
+            }
         }
     }
 }

Based on learnings, "If a field must be thread-safe, restrict access to code outside the event loop or document the event-loop assumption."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void onUpdate(@Nullable T value, @Nullable Throwable error) {
eventLoop.execute(() -> {
if (!closed) {
downstream.onUpdate(value, error);
}
});
}
@Override
public void close() {
closed = true;
if (upstream != null) {
upstream.close();
}
public void onUpdate(`@Nullable` T value, `@Nullable` Throwable error) {
eventLoop.execute(() -> {
if (!closed) {
downstream.onUpdate(value, error);
}
});
}
`@Override`
public void close() {
if (closed) {
return;
}
closed = true;
final Subscription upstream = this.upstream;
if (upstream != null) {
if (eventLoop.inEventLoop()) {
upstream.close();
} else {
eventLoop.execute(upstream::close);
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/armeria/xds/stream/RescheduleSubscription.java`
around lines 42 - 55, The RescheduleSubscription.close() must preserve
event-loop affinity and visibility: schedule the close logic onto eventLoop
(like eventLoop.execute(() -> { closed = true; if (upstream != null)
upstream.close(); })) instead of calling upstream.close() on the caller thread
so upstream.close() runs on the same event loop as onUpdate handlers, and also
mark the closed field as volatile to ensure visibility across threads; update
the close() method and the closed field accordingly (references: class
RescheduleSubscription, methods onUpdate and close, fields eventLoop, upstream,
closed).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because close is called by downstream, which is from the event loop

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.

@jrhee17, thanks for the clarification! That makes sense — since both the close() call and the onUpdate lambda execute on the same event loop thread, closed is always accessed from a single thread, so no volatile or cross-thread forwarding is needed. My concern doesn't apply here.


✏️ Learnings added
Learnt from: jrhee17
Repo: line/armeria PR: 6785
File: xds/src/main/java/com/linecorp/armeria/xds/stream/RescheduleSubscription.java:42-55
Timestamp: 2026-06-01T08:09:06.657Z
Learning: In `xds/src/main/java/com/linecorp/armeria/xds/stream/RescheduleSubscription.java`, `close()` is always called by downstream from the event loop thread. Because both `close()` and the lambda inside `onUpdate()` run on the same event loop, the `closed` boolean field does not need to be `volatile`, and `upstream.close()` does not need to be rescheduled onto the event loop. The event-loop single-threading contract is upheld by the caller.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: jrhee17
Repo: line/armeria PR: 6628
File: xds/src/main/java/com/linecorp/armeria/xds/TransportSocketSnapshot.java:48-48
Timestamp: 2026-02-19T06:03:54.715Z
Learning: In the Armeria XDS module (package com.linecorp.armeria.xds), code paths that run on the event loop (e.g., TransportSocketSnapshot and related XDS paths) are single-threaded. Do not use volatile or AtomicBoolean to guard static fields that are accessed only within these execution paths. If a field must be thread-safe, restrict access to code outside the event loop or document the event-loop assumption; otherwise, keep such fields as plain statics initialized once and mutably used only within the event loop context.

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.

How about adding
assert eventLoop.inEventLoop();

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

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

👍 👍

/**
* A no-op filter that passes through all operations unchanged.
*/
XdsHttpFilter NOOP = new XdsHttpFilter() {};
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.

nit) Should we use a static method instead? Conventionally, we don't set a field in the public interface.

Copy link
Copy Markdown
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

👍 👍

private static final String GENERIC_TYPE_URL =
"type.googleapis.com/envoy.extensions.http.injected_credentials.generic.v3.Generic";
private static final List<String> TYPE_URLS = ImmutableList.of(TYPE_URL);
private static final String DEFAULT_HEADER = "authorization";
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.

HttpHeadersNames.AUTHORIZATION?

*
* @param eventLoop the event loop that subscribe and close must be called from
*/
@UnstableApi
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.

Don't need to add

Suggested change
@UnstableApi

*
* @param eventLoop the event loop to deliver emissions on
*/
@UnstableApi
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.

Suggested change
@UnstableApi

Comment on lines +42 to +55
public void onUpdate(@Nullable T value, @Nullable Throwable error) {
eventLoop.execute(() -> {
if (!closed) {
downstream.onUpdate(value, error);
}
});
}

@Override
public void close() {
closed = true;
if (upstream != null) {
upstream.close();
}
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.

How about adding
assert eventLoop.inEventLoop();

@jrhee17 jrhee17 merged commit e174785 into line:main Jun 4, 2026
16 of 19 checks passed
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.

3 participants