Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -263,14 +263,14 @@ private static void putAuthSchemeResolutionAttributes(ExecutionAttributes execut
SdkClientConfiguration clientConfig,
SdkRequest originalRequest) {

// TODO(sra-identity-and-auth): When request-level auth scheme provider is added, use the request-level auth scheme
// provider if the customer specified an override, otherwise fall back to the one on the client.
// TODO(request-override auth scheme feature): When request-level auth scheme provider is added, use the request-level
// auth scheme provider if the customer specified an override, otherwise fall back to the one on the client.
AuthSchemeProvider authSchemeProvider = clientConfig.option(SdkClientOption.AUTH_SCHEME_PROVIDER);

// Use auth schemes that the user specified at the request level with
// preference over those on the client.
// TODO(sra-identity-and-auth): The request level schemes should be "merged" with client level, with request preferred
// over client.
// TODO(request-override auth scheme feature): The request level schemes should be "merged" with client level, with
// request preferred over client.
Map<String, AuthScheme<?>> authSchemes = clientConfig.option(SdkClientOption.AUTH_SCHEMES);

IdentityProviders identityProviders = resolveIdentityProviders(originalRequest, clientConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@
@SdkInternalApi
public final class DefaultIdentityProviders implements IdentityProviders {
/**
* TODO(sra-identity-auth): Currently, some customers assume we won't interact with the identity providers when we create
* the client. This isn't true - we need to call identityType. To TEMPORARILY work around those customer's tests failing,
* this is marked lazy. Once we fully migrate over to the SRA as the default code path, we should remove this lazy and
* ticket everyone in live who is making those bad assumptions.
* Currently, some customers assume we won't interact with the identity providers when we create
* the client. This isn't true - we need to call identityType. To work around those customer's tests failing,
* this is marked lazy. See JAVA-8670 for more details.
*/
private final Lazy<Map<Class<?>, IdentityProvider<?>>> identityProviders;
private final List<IdentityProvider<?>> identityProvidersList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ public void copyBuilder_addIdentityProvider_works() {

@Test
public void identityProviders_notTouched_untilNeeded() {
// TODO(sra-identity-auth): This should be removed once everything is on useSraAuth = true
IdentityProvider<AwsCredentialsIdentity> awsCredentialsProvider = Mockito.mock(IdentityProvider.class);
IdentityProviders providers =
IdentityProviders.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,21 +489,43 @@ default B putRawQueryParameter(String name, String value) {
* @param plugins The list of plugins for this request.
* @return This object for method chaining.
*/
@SdkPreviewApi
B plugins(List<SdkPlugin> plugins);

/**
* Add a plugin used to update the configuration used by this request.
*
* @param plugin The plugin to add.
* Adds a plugin that will modify the configuration used by this specific request.
*
* <p>Request-level plugins are applied at request execution time. Multiple plugins can be added and
* are executed in the order they were added.
*
* <p><b>Note:</b> Request-level override configuration (e.g., {@link #apiCallTimeout(Duration)}) takes
* precedence over request-level plugin settings. This means if you set a value directly on the request
* override configuration and also set it via a plugin, the direct configuration value will be used.
*
* <p>Example:
* {@snippet :
* SdkPlugin highTimeoutPlugin = config -> {
* config.overrideConfiguration(c -> c.apiCallTimeout(Duration.ofMinutes(5)));
* };
*
* GetObjectResponse response = s3Client.getObject(
* GetObjectRequest.builder()
* .bucket("my-bucket")
* .key("large-file")
* .overrideConfiguration(c -> c.addPlugin(highTimeoutPlugin))
* .build(),
* ResponseTransformer.toBytes()
* );
* }
*
* @param plugin the plugin to add
* @return this builder for method chaining
* @see SdkPlugin
*/
@SdkPreviewApi
B addPlugin(SdkPlugin plugin);

/**
* Returns the list of registered plugins
*/
@SdkPreviewApi
List<SdkPlugin> plugins();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,115 @@

package software.amazon.awssdk.core;

import software.amazon.awssdk.annotations.SdkPreviewApi;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.utils.SdkAutoCloseable;

/**
* A plugin modifies a client's configuration when the client is created or at request execution
* time.
* A plugin that modifies SDK client configuration at client creation time or per-request execution time.
*
* <p>Plugins provide an extensibility mechanism for customizing SDK client behavior without modifying core SDK code.
* They can modify configuration such as retry policies, timeouts, execution interceptors, endpoints, and authentication
* schemes.
*
* <p>Plugins can be applied at two levels:
* <ul>
* <li><b>Client-level:</b> Applied once during client creation and affects all requests made by that client</li>
* <li><b>Request-level:</b> Applied per-request and can override client-level configuration for specific requests</li>
* </ul>
*
* <p><b>When to use plugins vs direct configuration:</b>
* <ul>
* <li>Use <b>direct configuration</b> for simple, one-time client setup specific to your application</li>
* <li>Use <b>plugins</b> when you need to:
* <ul>
* <li>Reuse the same configuration across multiple SDK clients</li>
* <li>Package configuration as a library or module for distribution</li>
* <li>Apply conditional or dynamic configuration logic</li>
* <li>Compose multiple configuration strategies together</li>
* </ul>
* </li>
* </ul>
*
* <p><b>Client-level plugin example:</b>
* {@snippet :
* // Create a reusable plugin with multiple configuration settings
* SdkPlugin standardPlugin = config -> {
* config.endpointOverride(URI.create("https://127.0.0.1"))
* .overrideConfiguration(c -> c
* .apiCallTimeout(Duration.ofSeconds(30))
* .addExecutionInterceptor(new LoggingInterceptor()));
* };
*
* // Apply to multiple clients
* S3Client s3Client = S3Client.builder()
* .addPlugin(standardPlugin)
* .build();
*
* DynamoDbClient dynamoClient = DynamoDbClient.builder()
* .addPlugin(standardPlugin)
* .build();
* }
*
* <p><b>Composing multiple plugins:</b>
* {@snippet :
* // Core plugin always applied
* SdkPlugin corePlugin = config -> {
* config.overrideConfiguration(c -> c.apiCallTimeout(Duration.ofSeconds(30)));
* };
*
* // Optional feature plugins
* SdkPlugin compressionPlugin = config -> {
* config.overrideConfiguration(c -> c.compressionConfiguration(
* CompressionConfiguration.builder().requestCompressionEnabled(true).build()));
* };
*
* SdkPlugin tracingPlugin = config -> {
* config.overrideConfiguration(c -> c.addExecutionInterceptor(new TracingInterceptor()));
* };
*
* // Conditionally compose plugins based on feature flags
* S3Client.Builder builder = S3Client.builder().addPlugin(corePlugin);
* if (compressionEnabled) {
* builder.addPlugin(compressionPlugin);
* }
* if (tracingEnabled) {
* builder.addPlugin(tracingPlugin);
* }
* S3Client client = builder.build();
* }
*
* <p>Plugins are invoked after default configuration is applied, allowing them to override SDK defaults.
* Multiple plugins can be registered and are executed in the order they were added.
*
* <p><b>Configuration precedence (highest to lowest):</b>
* <ol>
* <li>Plugin settings (applied last, highest precedence)</li>
* <li>Direct client builder settings (e.g., {@code .overrideConfiguration()})</li>
* <li>Service-specific defaults</li>
* <li>Global SDK defaults</li>
* </ol>
*
* <p><b>Note:</b> Request-level plugins have different precedence behavior. Request-level override configuration
* takes precedence over request-level plugin settings, meaning direct request configuration will override plugin
* settings for that request.
*
* @see software.amazon.awssdk.core.client.builder.SdkClientBuilder#addPlugin(SdkPlugin)
* @see software.amazon.awssdk.core.RequestOverrideConfiguration.Builder#addPlugin(SdkPlugin)
*/
@SdkPreviewApi
@SdkPublicApi
@ThreadSafe
@FunctionalInterface
public interface SdkPlugin extends SdkAutoCloseable {

/**
* Modify the provided client configuration.
* Modifies the provided client configuration.
*
* <p>This method is invoked by the SDK to allow the plugin to customize the client configuration.
* Implementations can modify any aspect of the configuration exposed through the builder, including
* override configuration, endpoints, and authentication schemes.
*
* @param config the configuration builder to modify
*/
void configureClient(SdkServiceClientConfiguration.Builder config);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.net.URI;
import java.util.List;
import java.util.function.Consumer;
import software.amazon.awssdk.annotations.SdkPreviewApi;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.core.SdkPlugin;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
Expand Down Expand Up @@ -80,18 +79,38 @@ default B putAuthScheme(AuthScheme<?> authScheme) {
}

/**
* Adds a plugin to the client builder. The plugins will be invoked when building the client to allow them to change the
* configuration of the built client.
* Adds a plugin to the client builder that will modify the client configuration when the client is built.
*
* <p>Plugins are invoked after all default configuration is applied, allowing them to override SDK defaults.
* Multiple plugins can be added and are executed in the order they were added. The configuration changes made
* by plugins apply to all requests made by the built client.
*
* <p><b>Important:</b> Plugin settings have the highest precedence and will override any configuration set directly
* on the client builder (e.g., via {@code overrideConfiguration()}).
*
* <p>Example:
* {@snippet :
* SdkPlugin customPlugin = config -> {
* config.endpointOverride(URI.create("https://localhost:8080"))
* .overrideConfiguration(c -> c.apiCallTimeout(Duration.ofSeconds(30)));
* };
*
* S3Client client = S3Client.builder()
* .addPlugin(customPlugin)
* .build();
* }
*
* @param plugin the plugin to add
* @return this builder for method chaining
* @see SdkPlugin
*/
@SdkPreviewApi
default B addPlugin(SdkPlugin plugin) {
throw new UnsupportedOperationException();
}

/**
* Returns the list of plugins configured on the client builder.
*/
@SdkPreviewApi
default List<SdkPlugin> plugins() {
throw new UnsupportedOperationException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ private Aws4PresignerParams getPresignerParams(ExecutionAttributes attributes, S
.build();
}

// TODO(sra-identity-and-auth): add test case for SELECTED_AUTH_SCHEME case
private AwsCredentials resolveCredentials(ExecutionAttributes attributes) {
return attributes.getOptionalAttribute(SELECTED_AUTH_SCHEME)
.map(selectedAuthScheme -> selectedAuthScheme.identity())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute.ENABLE_CHUNKED_ENCODING;
import static software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING;
import static software.amazon.awssdk.http.Header.CONTENT_LENGTH;
import static software.amazon.awssdk.http.Header.CONTENT_TYPE;

Expand Down Expand Up @@ -85,24 +83,12 @@ public void setupClient() {
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1)
.endpointOverride(URI.create("http://localhost:" + wireMock.port()))
.overrideConfiguration(
// TODO(sra-identity-and-auth): we should remove these
// overrides once we set up codegen to set chunk-encoding to true
// for requests that are streaming and checksum-enabled
o -> o.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, true)
.putExecutionAttribute(ENABLE_PAYLOAD_SIGNING, false))
.build();

asyncClient = ProtocolRestJsonAsyncClient.builder()
.credentialsProvider(AnonymousCredentialsProvider.create())
.region(Region.US_EAST_1)
.endpointOverride(URI.create("http://localhost:" + wireMock.port()))
.overrideConfiguration(
// TODO(sra-identity-and-auth): we should remove these
// overrides once we set up codegen to set chunk-encoding to true
// for requests that are streaming and checksum-enabled
o -> o.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, true)
.putExecutionAttribute(ENABLE_PAYLOAD_SIGNING, false))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mockito;
Expand All @@ -61,8 +60,6 @@
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
import software.amazon.awssdk.core.internal.retry.SdkDefaultRetryStrategy;
import software.amazon.awssdk.core.retry.RetryMode;
import software.amazon.awssdk.core.retry.RetryPolicy;
import software.amazon.awssdk.endpoints.Endpoint;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme;
Expand All @@ -77,7 +74,6 @@
import software.amazon.awssdk.profiles.ProfileFile;
import software.amazon.awssdk.profiles.ProfileProperty;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.retries.DefaultRetryStrategy;
import software.amazon.awssdk.retries.api.RetryStrategy;
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient;
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClientBuilder;
Expand Down Expand Up @@ -467,7 +463,6 @@ public <T> void requestPluginSeesCustomerClientConfiguredValue(TestCase<T> testC

@ParameterizedTest
@MethodSource("testCases")
@Disabled("Request-level values are currently higher-priority than plugin settings.") // TODO(sra-identity-auth)
public <T> void requestPluginSeesCustomerRequestConfiguredValue(TestCase<T> testCase) {
if (testCase.requestSetter == null) {
System.out.println("No request setting available.");
Expand All @@ -480,7 +475,11 @@ public <T> void requestPluginSeesCustomerRequestConfiguredValue(TestCase<T> test
SdkPlugin plugin = config -> {
ProtocolRestJsonServiceClientConfiguration.Builder conf =
(ProtocolRestJsonServiceClientConfiguration.Builder) config;
testCase.pluginValidator.accept(conf, testCase.nonDefaultValue);
// Request-level values are currently higher-priority than plugin settings.
// This is not the intended behavior and is inconsistent with client-level plugin.
// However, we can't fix it directly due to backwards compatibility. See
// JAVA-8671 for more details.
testCase.pluginValidator.accept(conf, testCase.defaultValue);
timesCalled.incrementAndGet();
};

Expand Down Expand Up @@ -570,8 +569,11 @@ public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttr
AwsRequestOverrideConfiguration.builder()
.addPlugin(plugin)
.applyMutation(c -> {
// TODO(sra-identity-auth): request-level plugins should override request-level
// configuration
// testCase.requestSetter is not applied here because request-level values are
// currently higher-priority than plugin settings.
// This is not the intended behavior and is inconsistent with client-level plugin.
// However, we can't fix it directly due to backwards compatibility. See
// JAVA-8671 for more details.
// if (testCase.requestSetter != null) {
// testCase.requestSetter.accept(c, testCase.defaultValue);
// }
Expand Down
Loading