From 33d4012d73ec4a5265c41aa54c0b52ab96511d9f Mon Sep 17 00:00:00 2001 From: David Negrete Date: Mon, 21 Nov 2022 08:00:56 -0700 Subject: [PATCH 01/16] ProfileCredentialsProvider can now reload credentials when profile files change (#3487) * ProfileFile can update if disk changed, reload as new instance * ProfileCredentialsProvider reloads credentials if profile file has changes * Created class ProfileFileRefresher * Created ReloadingProfileCredentialsProvider; moved new logic in ProfileFile to ProfileFileRefresher * Fix ReloadingProfileCredentialsBehavior when missing ProfileFile Supplier or Predicate, and dealing with defaults * Consolidated ReloadingProfileCredentialsProvider functionality into ProfileCredentialsProvider * Fix behavior when dealing with defaults * Created ProfileFileSupplier interface; refactored * Misc fixes * Created package-private ProfileFileSupplierBuilder; ProfileFileSupplier now extends Supplier; Fixed Javadoc * Fixed unit tests for default credentials file * Removed ProfileFileSupplier.Builder interface * Code cleanup --- .../feature-AWSSDKforJavav2-688d30e.json | 6 + core/auth/pom.xml | 6 + .../credentials/AwsCredentialsProvider.java | 2 +- .../ProfileCredentialsProvider.java | 123 ++++-- .../ProfileCredentialsProviderTest.java | 217 +++++++++- core/profiles/pom.xml | 6 + .../amazon/awssdk/profiles/ProfileFile.java | 2 +- .../awssdk/profiles/ProfileFileSupplier.java | 108 +++++ .../profiles/ProfileFileSupplierBuilder.java | 109 +++++ .../internal/ProfileFileRefresher.java | 267 ++++++++++++ .../profiles/ProfileFileSupplierTest.java | 389 ++++++++++++++++++ .../internal/ProfileFileRefresherTest.java | 348 ++++++++++++++++ 12 files changed, 1526 insertions(+), 57 deletions(-) create mode 100644 .changes/next-release/feature-AWSSDKforJavav2-688d30e.json create mode 100644 core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java create mode 100644 core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java create mode 100644 core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java create mode 100644 core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java create mode 100644 core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java diff --git a/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json b/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json new file mode 100644 index 000000000000..7c53e085c72c --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json @@ -0,0 +1,6 @@ +{ + "category": "AWS SDK for Java v2", + "contributor": "", + "type": "feature", + "description": "Created ReloadingProfileCredentialsProvider to reload credentials due to disk changes" +} diff --git a/core/auth/pom.xml b/core/auth/pom.xml index 8273a9bf5ac7..e5e15fdee807 100644 --- a/core/auth/pom.xml +++ b/core/auth/pom.xml @@ -152,6 +152,12 @@ commons-lang3 test + + com.google.jimfs + jimfs + ${jimfs.version} + test + diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProvider.java index 0e8ddb4aad3b..3c797cf9905c 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProvider.java @@ -30,7 +30,7 @@ public interface AwsCredentialsProvider { /** * Returns {@link AwsCredentials} that can be used to authorize an AWS request. Each implementation of AWSCredentialsProvider - * can chose its own strategy for loading credentials. For example, an implementation might load credentials from an existing + * can choose its own strategy for loading credentials. For example, an implementation might load credentials from an existing * key management system, or load new credentials when credentials are rotated. * *

If an error occurs during the loading of credentials or credentials could not be found, a runtime exception will be diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java index 37ccb8a636bf..28c4b74ce3ee 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.auth.credentials; +import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; @@ -23,6 +24,7 @@ import software.amazon.awssdk.auth.credentials.internal.ProfileCredentialsUtils; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.SdkAutoCloseable; @@ -46,55 +48,42 @@ public final class ProfileCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable, ToCopyableBuilder { - private final AwsCredentialsProvider credentialsProvider; - private final RuntimeException loadException; - private final ProfileFile profileFile; + private AwsCredentialsProvider credentialsProvider; + private final RuntimeException loadException; + private final ProfileFileSupplier profileFileSupplier; + private volatile ProfileFile currentProfileFile; private final String profileName; - private final Supplier defaultProfileFileLoader; /** * @see #builder() */ private ProfileCredentialsProvider(BuilderImpl builder) { - AwsCredentialsProvider credentialsProvider = null; - RuntimeException loadException = null; - ProfileFile profileFile = null; - String profileName = null; + this.defaultProfileFileLoader = builder.defaultProfileFileLoader; + + RuntimeException thrownException = null; + String selectedProfileName = null; + ProfileFileSupplier selectedProfileSupplier = null; try { - profileName = builder.profileName != null ? builder.profileName - : ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow(); - - // Load the profiles file - profileFile = Optional.ofNullable(builder.profileFile) - .orElseGet(builder.defaultProfileFileLoader); - - // Load the profile and credentials provider - String finalProfileName = profileName; - ProfileFile finalProfileFile = profileFile; - credentialsProvider = - profileFile.profile(profileName) - .flatMap(p -> new ProfileCredentialsUtils(finalProfileFile, p, finalProfileFile::profile) - .credentialsProvider()) - .orElseThrow(() -> { - String errorMessage = String.format("Profile file contained no credentials for " + - "profile '%s': %s", finalProfileName, finalProfileFile); - return SdkClientException.builder().message(errorMessage).build(); - }); + selectedProfileName = Optional.ofNullable(builder.profileName) + .orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow); + + selectedProfileSupplier = Optional.ofNullable(builder.profileFileSupplier) + .orElseGet(() -> ProfileFileSupplier + .fixedProfileFile(builder.defaultProfileFileLoader.get())); + } catch (RuntimeException e) { // If we couldn't load the credentials provider for some reason, save an exception describing why. This exception - // will only be raised on calls to getCredentials. We don't want to raise an exception here because it may be + // will only be raised on calls to resolveCredentials. We don't want to raise an exception here because it may be // expected (eg. in the default credential chain). - loadException = e; + thrownException = e; } - this.loadException = loadException; - this.credentialsProvider = credentialsProvider; - this.profileFile = profileFile; - this.profileName = profileName; - this.defaultProfileFileLoader = builder.defaultProfileFileLoader; + this.loadException = thrownException; + this.profileName = selectedProfileName; + this.profileFileSupplier = selectedProfileSupplier; } /** @@ -127,19 +116,39 @@ public AwsCredentials resolveCredentials() { if (loadException != null) { throw loadException; } + + ProfileFile cachedOrRefreshedProfileFile = refreshProfileFile(); + if (isNewProfileFile(cachedOrRefreshedProfileFile)) { + currentProfileFile = cachedOrRefreshedProfileFile; + handleProfileFileReload(cachedOrRefreshedProfileFile); + } + return credentialsProvider.resolveCredentials(); } + private void handleProfileFileReload(ProfileFile profileFile) { + credentialsProvider = createCredentialsProvider(profileFile, profileName); + } + + private ProfileFile refreshProfileFile() { + return profileFileSupplier.get(); + } + + private boolean isNewProfileFile(ProfileFile profileFile) { + return !Objects.equals(currentProfileFile, profileFile); + } + @Override public String toString() { return ToString.builder("ProfileCredentialsProvider") .add("profileName", profileName) - .add("profileFile", profileFile) + .add("profileFile", currentProfileFile) .build(); } @Override public void close() { + profileFileSupplier.close(); // The delegate credentials provider may be closeable (eg. if it's an STS credentials provider). In this case, we should // clean it up when this credentials provider is closed. IoUtils.closeIfCloseable(credentialsProvider, null); @@ -150,6 +159,17 @@ public Builder toBuilder() { return new BuilderImpl(this); } + private AwsCredentialsProvider createCredentialsProvider(ProfileFile profileFile, String profileName) { + // Load the profile and credentials provider + return profileFile.profile(profileName) + .flatMap(p -> new ProfileCredentialsUtils(profileFile, p, profileFile::profile).credentialsProvider()) + .orElseThrow(() -> { + String errorMessage = String.format("Profile file contained no credentials for " + + "profile '%s': %s", profileName, profileFile); + return SdkClientException.builder().message(errorMessage).build(); + }); + } + /** * A builder for creating a custom {@link ProfileCredentialsProvider}. */ @@ -158,6 +178,7 @@ public interface Builder extends CopyableBuilder profileFile); + /** + * Define the mechanism for loading profile files. + * + * @param profileFileSupplier Supplier interface for generating a ProfileFile instance. + * @see #profileFile(ProfileFile) + */ + Builder profileFile(ProfileFileSupplier profileFileSupplier); + /** * Define the name of the profile that should be used by this credentials provider. By default, the value in * {@link ProfileFileSystemSetting#AWS_PROFILE} is used. @@ -176,11 +205,12 @@ public interface Builder extends CopyableBuilder defaultProfileFileLoader = ProfileFile::defaultProfileFile; @@ -188,15 +218,14 @@ static final class BuilderImpl implements Builder { } BuilderImpl(ProfileCredentialsProvider provider) { - this.profileFile = provider.profileFile; this.profileName = provider.profileName; this.defaultProfileFileLoader = provider.defaultProfileFileLoader; + this.profileFileSupplier = provider.profileFileSupplier; } @Override public Builder profileFile(ProfileFile profileFile) { - this.profileFile = profileFile; - return this; + return profileFile(ProfileFileSupplier.wrapIntoNullableSupplier(profileFile)); } public void setProfileFile(ProfileFile profileFile) { @@ -208,6 +237,16 @@ public Builder profileFile(Consumer profileFile) { return profileFile(ProfileFile.builder().applyMutation(profileFile).build()); } + @Override + public Builder profileFile(ProfileFileSupplier profileFileSupplier) { + this.profileFileSupplier = profileFileSupplier; + return this; + } + + public void setProfileFile(ProfileFileSupplier supplier) { + profileFile(supplier); + } + @Override public Builder profileName(String profileName) { this.profileName = profileName; @@ -225,8 +264,9 @@ public ProfileCredentialsProvider build() { /** * Override the default configuration file to be used when the customer does not explicitly set - * profileName(profileName); - * {@link #profileFile(ProfileFile)}. Use of this method is only useful for testing the default behavior. + * profileFile(ProfileFile) or profileFileSupplier(supplier); + * {@link #profileFile(ProfileFile)}. Use of this method is + * only useful for testing the default behavior. */ @SdkTestInternalApi Builder defaultProfileFileLoader(Supplier defaultProfileFileLoader) { @@ -234,4 +274,5 @@ Builder defaultProfileFileLoader(Supplier defaultProfileFileLoader) return this; } } + } diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java index d6f261779206..7895e520f02e 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java @@ -18,9 +18,18 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.google.common.jimfs.Jimfs; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; import software.amazon.awssdk.profiles.ProfileProperty; import software.amazon.awssdk.utils.StringInputStream; @@ -28,18 +37,37 @@ * Verify functionality of {@link ProfileCredentialsProvider}. */ public class ProfileCredentialsProviderTest { + + private static FileSystem jimfs; + private static Path testDirectory; + + @BeforeAll + public static void setup() { + jimfs = Jimfs.newFileSystem(); + testDirectory = jimfs.getPath("test"); + } + + @AfterAll + public static void tearDown() { + try { + jimfs.close(); + } catch (IOException e) { + // no-op + } + } + @Test - public void missingCredentialsFileThrowsExceptionInGetCredentials() { + void missingCredentialsFileThrowsExceptionInResolveCredentials() { ProfileCredentialsProvider provider = - new ProfileCredentialsProvider.BuilderImpl() - .defaultProfileFileLoader(() -> { throw new IllegalStateException(); }) - .build(); + new ProfileCredentialsProvider.BuilderImpl() + .defaultProfileFileLoader(() -> { throw new IllegalStateException(); }) + .build(); assertThatThrownBy(provider::resolveCredentials).isInstanceOf(IllegalStateException.class); } @Test - public void missingProfileFileThrowsExceptionInGetCredentials() { + void missingProfileFileThrowsExceptionInResolveCredentials() { ProfileCredentialsProvider provider = new ProfileCredentialsProvider.BuilderImpl() .defaultProfileFileLoader(() -> ProfileFile.builder() @@ -52,35 +80,35 @@ public void missingProfileFileThrowsExceptionInGetCredentials() { } @Test - public void missingProfileThrowsExceptionInGetCredentials() { + void missingProfileThrowsExceptionInResolveCredentials() { ProfileFile file = profileFile("[default]\n" - + "aws_access_key_id = defaultAccessKey\n" - + "aws_secret_access_key = defaultSecretAccessKey"); + + "aws_access_key_id = defaultAccessKey\n" + + "aws_secret_access_key = defaultSecretAccessKey"); ProfileCredentialsProvider provider = - ProfileCredentialsProvider.builder().profileFile(file).profileName("foo").build(); + ProfileCredentialsProvider.builder().profileFile(file).profileName("foo").build(); assertThatThrownBy(provider::resolveCredentials).isInstanceOf(SdkClientException.class); } @Test - public void profileWithoutCredentialsThrowsExceptionInGetCredentials() { + void profileWithoutCredentialsThrowsExceptionInResolveCredentials() { ProfileFile file = profileFile("[default]"); ProfileCredentialsProvider provider = - ProfileCredentialsProvider.builder().profileFile(file).profileName("default").build(); + ProfileCredentialsProvider.builder().profileFile(file).profileName("default").build(); assertThatThrownBy(provider::resolveCredentials).isInstanceOf(SdkClientException.class); } @Test - public void presentProfileReturnsCredentials() { + void presentProfileReturnsCredentials() { ProfileFile file = profileFile("[default]\n" + "aws_access_key_id = defaultAccessKey\n" + "aws_secret_access_key = defaultSecretAccessKey"); ProfileCredentialsProvider provider = - ProfileCredentialsProvider.builder().profileFile(file).profileName("default").build(); + ProfileCredentialsProvider.builder().profileFile(file).profileName("default").build(); assertThat(provider.resolveCredentials()).satisfies(credentials -> { assertThat(credentials.accessKeyId()).isEqualTo("defaultAccessKey"); @@ -89,7 +117,7 @@ public void presentProfileReturnsCredentials() { } @Test - public void profileWithWebIdentityToken() { + void profileWithWebIdentityToken() { String token = "/User/home/test"; ProfileFile file = profileFile("[default]\n" @@ -100,7 +128,168 @@ public void profileWithWebIdentityToken() { assertThat(file.profile("default").get().property(ProfileProperty.WEB_IDENTITY_TOKEN_FILE).get()).isEqualTo(token); } + @Test + void resolveCredentials_missingProfileFileCausesExceptionInMethod_throwsException() { + ProfileCredentialsProvider.BuilderImpl builder = new ProfileCredentialsProvider.BuilderImpl(); + builder.defaultProfileFileLoader(() -> ProfileFile.builder() + .content(new StringInputStream("")) + .type(ProfileFile.Type.CONFIGURATION) + .build()); + ProfileCredentialsProvider provider = builder.build(); + + assertThatThrownBy(provider::resolveCredentials).isInstanceOf(SdkClientException.class); + } + + @Test + void resolveCredentials_missingProfile_throwsException() { + ProfileFile file = profileFile("[default]\n" + + "aws_access_key_id = defaultAccessKey\n" + + "aws_secret_access_key = defaultSecretAccessKey"); + + try (ProfileCredentialsProvider provider = + ProfileCredentialsProvider.builder() + .profileFile(() -> file) + .profileName("foo") + .build()) { + + assertThatThrownBy(provider::resolveCredentials).isInstanceOf(SdkClientException.class); + } + } + + @Test + void resolveCredentials_profileWithoutCredentials_throwsException() { + ProfileFile file = profileFile("[default]"); + + ProfileCredentialsProvider provider = + ProfileCredentialsProvider.builder() + .profileFile(() -> file) + .profileName("default") + .build(); + + assertThatThrownBy(provider::resolveCredentials).isInstanceOf(SdkClientException.class); + } + + @Test + void resolveCredentials_presentProfile_returnsCredentials() { + ProfileFile file = profileFile("[default]\n" + + "aws_access_key_id = defaultAccessKey\n" + + "aws_secret_access_key = defaultSecretAccessKey"); + + ProfileCredentialsProvider provider = + ProfileCredentialsProvider.builder() + .profileFile(() -> file) + .profileName("default") + .build(); + + assertThat(provider.resolveCredentials()).satisfies(credentials -> { + assertThat(credentials.accessKeyId()).isEqualTo("defaultAccessKey"); + assertThat(credentials.secretAccessKey()).isEqualTo("defaultSecretAccessKey"); + }); + } + + @Test + void resolveCredentials_presentProfileFileSupplier_returnsCredentials() { + Path path = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + ProfileCredentialsProvider provider = + ProfileCredentialsProvider.builder() + .profileFile(ProfileFileSupplier.reloadWhenModified(path)) + .profileName("default") + .build(); + + assertThat(provider.resolveCredentials()).satisfies(credentials -> { + assertThat(credentials.accessKeyId()).isEqualTo("defaultAccessKey"); + assertThat(credentials.secretAccessKey()).isEqualTo("defaultSecretAccessKey"); + }); + } + + @Test + void create_noProfileName_returnsProfileCredentialsProviderToResolveWithDefaults() { + ProfileCredentialsProvider provider = ProfileCredentialsProvider.create(); + String toString = provider.toString(); + + assertThat(toString).satisfies(s -> assertThat(s).contains("profileName=default")); + } + + @Test + void create_givenProfileName_returnsProfileCredentialsProviderToResolveForGivenName() { + ProfileCredentialsProvider provider = ProfileCredentialsProvider.create("override"); + String toString = provider.toString(); + + assertThat(toString).satisfies(s -> assertThat(s).contains("profileName=override")); + } + + @Test + void toString_anyProfileCredentialsProviderAfterResolvingCredentialsFileDoesExists_returnsProfileFile() { + ProfileCredentialsProvider provider = new ProfileCredentialsProvider.BuilderImpl() + .defaultProfileFileLoader(() -> profileFile("[default]\naws_access_key_id = %s\naws_secret_access_key = %s\n")) + .build(); + provider.resolveCredentials(); + String toString = provider.toString(); + + assertThat(toString).satisfies(s -> { + assertThat(s).contains("profileName=default"); + assertThat(s).contains("profileFile="); + }); + } + + @Test + void toString_anyProfileCredentialsProviderAfterResolvingCredentialsFileDoesNotExist_throwsException() { + ProfileCredentialsProvider provider = new ProfileCredentialsProvider.BuilderImpl() + .defaultProfileFileLoader(() -> ProfileFile.builder() + .content(new StringInputStream("")) + .type(ProfileFile.Type.CONFIGURATION) + .build()) + .build(); + + assertThatThrownBy(provider::resolveCredentials).isInstanceOf(SdkClientException.class); + } + + @Test + void toString_anyProfileCredentialsProviderBeforeResolvingCredentials_doesNotReturnProfileFile() { + ProfileCredentialsProvider provider = + new ProfileCredentialsProvider.BuilderImpl() + .defaultProfileFileLoader(() -> ProfileFile.builder() + .content(new StringInputStream("")) + .type(ProfileFile.Type.CONFIGURATION) + .build()) + .build(); + + String toString = provider.toString(); + + assertThat(toString).satisfies(s -> { + assertThat(s).contains("profileName"); + assertThat(s).doesNotContain("profileFile"); + }); + } + + @Test + void toBuilder_fromCredentialsProvider_returnsBuilderCapableOfProducingSimilarProvider() { + ProfileCredentialsProvider provider1 = ProfileCredentialsProvider.create("override"); + ProfileCredentialsProvider provider2 = provider1.toBuilder().build(); + + String provider1ToString = provider1.toString(); + String provider2ToString = provider2.toString(); + assertThat(provider1ToString).isEqualTo(provider2ToString); + } + private ProfileFile profileFile(String string) { return ProfileFile.builder().content(new StringInputStream(string)).type(ProfileFile.Type.CONFIGURATION).build(); } + + private Path generateTestFile(String contents, String filename) { + try { + Files.createDirectories(testDirectory); + return Files.write(testDirectory.resolve(filename), contents.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Path generateTestCredentialsFile(String accessKeyId, String secretAccessKey) { + String contents = String.format("[default]\naws_access_key_id = %s\naws_secret_access_key = %s\n", + accessKeyId, secretAccessKey); + return generateTestFile(contents, "credentials.txt"); + } + } diff --git a/core/profiles/pom.xml b/core/profiles/pom.xml index 6d904037e880..e77e31fbd594 100644 --- a/core/profiles/pom.xml +++ b/core/profiles/pom.xml @@ -55,6 +55,12 @@ assertj-core test + + com.google.jimfs + jimfs + ${jimfs.version} + test + diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFile.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFile.java index 064397c90c6c..b12370b1cebd 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFile.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFile.java @@ -273,7 +273,7 @@ public void setContent(InputStream contentStream) { @Override public Builder content(Path contentLocation) { Validate.paramNotNull(contentLocation, "profileLocation"); - Validate.validState(contentLocation.toFile().exists(), "Profile file '%s' does not exist.", contentLocation); + Validate.validState(Files.exists(contentLocation), "Profile file '%s' does not exist.", contentLocation); this.content = null; this.contentLocation = contentLocation; diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java new file mode 100644 index 000000000000..ea8e2ffbd8de --- /dev/null +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java @@ -0,0 +1,108 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.profiles; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.function.Supplier; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.profiles.internal.ProfileFileRefresher; +import software.amazon.awssdk.utils.SdkAutoCloseable; + +/** + * Encapsulates the logic for supplying either a single or multiple ProfileFile instances. + *

+ * Each call to the {@link #get()} method will result in either a new or previously supplied profile based on the + * implementation's rules. + */ +@SdkPublicApi +@FunctionalInterface +public interface ProfileFileSupplier extends Supplier, SdkAutoCloseable { + + @Override + default void close() { + } + + /** + * Creates a {@link ProfileFileSupplier} capable of producing multiple profile objects from a file. This supplier will + * return a new ProfileFile instance only once the disk file has been modified. Multiple calls to the supplier while the + * disk file is unchanged will return the same object. + * + * @param path Path to the file to read from. + * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a new profile when the file + * has been modified. + */ + static ProfileFileSupplier reloadWhenModified(Path path) { + return new ProfileFileSupplier() { + + final ProfileFile.Builder builder = ProfileFile.builder() + .content(path) + .type(ProfileFile.Type.CREDENTIALS); + + final ProfileFileRefresher refresher = ProfileFileRefresher.builder() + .profileFile(builder::build) + .profileFilePath(path) + .build(); + + @Override + public ProfileFile get() { + return refresher.refreshIfStale(); + } + + @Override + public void close() { + refresher.close(); + } + }; + } + + /** + * Creates a {@link ProfileFileSupplier} capable of producing a single profile object from a file. + * + * @param path Path to the file to read from. + * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a single profile. + */ + static ProfileFileSupplier fixedProfileFile(Path path) { + ProfileFile profileFile = ProfileFile.builder() + .content(path) + .type(ProfileFile.Type.CREDENTIALS) + .build(); + + return () -> profileFile; + } + + /** + * Creates a {@link ProfileFileSupplier} that produces an existing profile. + * + * @param profileFile Profile object to supply. + * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a single profile. + */ + static ProfileFileSupplier fixedProfileFile(ProfileFile profileFile) { + return () -> profileFile; + } + + /** + * creates a {@link ProfileFileSupplier} that produces an existing non-null profile. If the given profile + * is null, then the created supplier will also be null. + * + * @param profileFile Profile object to supply. + * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a single profile. + */ + static ProfileFileSupplier wrapIntoNullableSupplier(ProfileFile profileFile) { + return Objects.nonNull(profileFile) ? () -> profileFile : null; + } + +} diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java new file mode 100644 index 000000000000..6bc55cc33130 --- /dev/null +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java @@ -0,0 +1,109 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.profiles; + +import java.nio.file.Path; +import java.time.Clock; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.profiles.internal.ProfileFileRefresher; + +@SdkInternalApi +final class ProfileFileSupplierBuilder { + + private boolean reloadingSupplier = false; + private Supplier profileFile; + private Path profileFilePath; + private Clock clock; + private Consumer onProfileFileLoad; + + public ProfileFileSupplierBuilder reloadWhenModified(Path path) { + ProfileFile.Builder builder = ProfileFile.builder() + .content(path) + .type(ProfileFile.Type.CREDENTIALS); + this.profileFile = builder::build; + this.profileFilePath = path; + this.reloadingSupplier = true; + return this; + } + + public ProfileFileSupplierBuilder fixedProfileFile(Path path) { + return fixedProfileFile(ProfileFile.builder() + .content(path) + .type(ProfileFile.Type.CREDENTIALS) + .build()); + } + + public ProfileFileSupplierBuilder fixedProfileFile(ProfileFile profileFile) { + this.profileFile = () -> profileFile; + this.profileFilePath = null; + this.reloadingSupplier = false; + return this; + } + + public ProfileFileSupplierBuilder onProfileFileLoad(Consumer action) { + this.onProfileFileLoad = action; + return this; + } + + public ProfileFileSupplierBuilder clock(Clock clock) { + this.clock = clock; + return this; + } + + public ProfileFileSupplier build() { + return fromBuilder(this); + } + + /** + * Completes {@link ProfileFileSupplier} build. + * @param builder Object to complete build. + * @return Implementation of {@link ProfileFileSupplier}. + */ + static ProfileFileSupplier fromBuilder(ProfileFileSupplierBuilder builder) { + if (builder.reloadingSupplier) { + + ProfileFileRefresher.Builder refresherBuilder = ProfileFileRefresher.builder() + .profileFile(builder.profileFile) + .profileFilePath(builder.profileFilePath); + + if (Objects.nonNull(builder.clock)) { + refresherBuilder.clock(builder.clock); + } + if (Objects.nonNull(builder.onProfileFileLoad)) { + refresherBuilder.onProfileFileReload(builder.onProfileFileLoad); + } + + ProfileFileRefresher refresher = refresherBuilder.build(); + + return new ProfileFileSupplier() { + @Override + public ProfileFile get() { + return refresher.refreshIfStale(); + } + + @Override + public void close() { + refresher.close(); + } + }; + } + + return builder.profileFile::get; + } +} diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java new file mode 100644 index 000000000000..4740a36577c3 --- /dev/null +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java @@ -0,0 +1,267 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.profiles.internal; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Clock; +import java.time.Instant; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.annotations.SdkTestInternalApi; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.utils.SdkAutoCloseable; +import software.amazon.awssdk.utils.cache.CachedSupplier; +import software.amazon.awssdk.utils.cache.RefreshResult; + +/** + * Class used for caching and reloading ProfileFile objects from a Supplier. + */ +@SdkInternalApi +public final class ProfileFileRefresher implements SdkAutoCloseable { + + private static final ProfileFileRefreshRecord EMPTY_REFRESH_RECORD = ProfileFileRefreshRecord.builder() + .refreshTime(Instant.MIN) + .build(); + private final CachedSupplier profileFileCache; + private volatile ProfileFileRefreshRecord currentRefreshRecord; + private final Supplier profileFile; + private final Path profileFilePath; + private final Function exceptionHandler; + private final Consumer onProfileFileReload; + private final Clock clock; + + private ProfileFileRefresher(Builder builder) { + this.exceptionHandler = builder.exceptionHandler; + this.clock = builder.clock; + this.profileFile = builder.profileFile; + this.profileFilePath = builder.profileFilePath; + this.onProfileFileReload = builder.onProfileFileReload; + this.profileFileCache = CachedSupplier.builder(this::refreshResult) + .clock(this.clock) + .build(); + this.currentRefreshRecord = EMPTY_REFRESH_RECORD; + } + + /** + * Builder method to construct instance of ProfileFileRefresher. + */ + public static ProfileFileRefresher.Builder builder() { + return new ProfileFileRefresher.Builder(); + } + + /** + * Retrieves the cache value or refreshes it if stale. + */ + public ProfileFile refreshIfStale() { + ProfileFileRefreshRecord cachedOrRefreshedRecord = profileFileCache.get(); + ProfileFile cachedOrRefreshedProfileFile = cachedOrRefreshedRecord.profileFile; + if (isNewProfileFile(cachedOrRefreshedProfileFile)) { + currentRefreshRecord = cachedOrRefreshedRecord; + } + + return cachedOrRefreshedProfileFile; + } + + @Override + public void close() { + profileFileCache.close(); + } + + private RefreshResult refreshResult() { + try { + return reloadAsRefreshResultIfStale(); + } catch (RuntimeException exception) { + Instant now = Instant.now(); + Instant staleTime = now; + ProfileFile exceptionProfileFile = exceptionHandler.apply(exception); + ProfileFileRefreshRecord refreshRecord = ProfileFileRefreshRecord.builder() + .profileFile(exceptionProfileFile) + .refreshTime(now) + .build(); + + return wrapIntoRefreshResult(refreshRecord, staleTime); + } + } + + private RefreshResult reloadAsRefreshResultIfStale() { + Instant now = clock.instant(); + Instant staleTime = now; + ProfileFileRefreshRecord refreshRecord; + + if (canReloadProfileFile() || hasNotBeenPreviouslyLoaded()) { + ProfileFile reloadedProfileFile = reload(profileFile, onProfileFileReload); + refreshRecord = ProfileFileRefreshRecord.builder() + .profileFile(reloadedProfileFile) + .refreshTime(now) + .build(); + } else { + refreshRecord = currentRefreshRecord; + } + + return wrapIntoRefreshResult(refreshRecord, staleTime); + } + + private RefreshResult wrapIntoRefreshResult(T value, Instant staleTime) { + return RefreshResult.builder(value) + .staleTime(staleTime) + .build(); + } + + private static ProfileFile reload(Supplier supplier) { + return supplier.get(); + } + + private static ProfileFile reload(Supplier supplier, Consumer consumer) { + ProfileFile reloadedProfileFile = reload(supplier); + consumer.accept(reloadedProfileFile); + + return reloadedProfileFile; + } + + private boolean isNewProfileFile(ProfileFile profileFile) { + return !Objects.equals(currentRefreshRecord.profileFile, profileFile); + } + + private boolean canReloadProfileFile() { + if (Objects.isNull(profileFilePath)) { + return false; + } + + try { + Instant lastModifiedInstant = Files.getLastModifiedTime(profileFilePath).toInstant(); + return currentRefreshRecord.refreshTime.isBefore(lastModifiedInstant); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private boolean hasNotBeenPreviouslyLoaded() { + return currentRefreshRecord == EMPTY_REFRESH_RECORD; + } + + + public static final class Builder { + + private Supplier profileFile; + private Path profileFilePath; + private Consumer onProfileFileReload = p -> { }; + private Function exceptionHandler; + private Clock clock = Clock.systemUTC(); + + private Builder() { + } + + public Builder profileFile(Supplier profileFile) { + this.profileFile = profileFile; + return this; + } + + public Builder profileFilePath(Path profileFilePath) { + this.profileFilePath = profileFilePath; + return this; + } + + /** + * Sets a clock for managing stale and prefetch durations. + */ + @SdkTestInternalApi + public Builder clock(Clock clock) { + this.clock = clock; + return this; + } + + /** + * @param exceptionHandler Handler which takes action when a Runtime exception occurs while loading a profile file. + * Handler can return a previously stored profile file or throw back the exception. + */ + public Builder exceptionHandler(Function exceptionHandler) { + this.exceptionHandler = exceptionHandler; + return this; + } + + /** + * Sets a custom action to perform when a profile file is reloaded. This action is executed when both the cache is stale + * and the disk file associated with the profile file has been modified since the last load. + * + * @param consumer The action to perform. + */ + public Builder onProfileFileReload(Consumer consumer) { + this.onProfileFileReload = consumer; + return this; + } + + public ProfileFileRefresher build() { + return new ProfileFileRefresher(this); + } + } + + /** + * Class used to encapsulate additional refresh information. + */ + public static final class ProfileFileRefreshRecord { + private final Instant refreshTime; + private final ProfileFile profileFile; + + private ProfileFileRefreshRecord(Builder builder) { + this.profileFile = builder.profileFile; + this.refreshTime = builder.refreshTime; + } + + /** + * The refreshed ProfileFile instance. + */ + public ProfileFile profileFile() { + return profileFile; + } + + /** + * The time at which the RefreshResult was created. + */ + public Instant refreshTime() { + return refreshTime; + } + + static Builder builder() { + return new Builder(); + } + + private static final class Builder { + private Instant refreshTime; + private ProfileFile profileFile; + + Builder refreshTime(Instant refreshTime) { + this.refreshTime = refreshTime; + return this; + } + + Builder profileFile(ProfileFile profileFile) { + this.profileFile = profileFile; + return this; + } + + ProfileFileRefreshRecord build() { + return new ProfileFileRefreshRecord(this); + } + } + } + +} diff --git a/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java b/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java new file mode 100644 index 000000000000..370ddb955a80 --- /dev/null +++ b/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java @@ -0,0 +1,389 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.profiles; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.jimfs.Jimfs; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.TemporalAmount; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.profiles.ProfileFileSupplierBuilder; + +class ProfileFileSupplierTest { + + private static FileSystem jimfs; + private static Path testDirectory; + + @BeforeAll + public static void setup() { + jimfs = Jimfs.newFileSystem(); + testDirectory = jimfs.getPath("test"); + } + + @AfterAll + public static void tearDown() { + try { + jimfs.close(); + } catch (IOException e) { + // no-op + } + } + + @Test + void get_profileFileFixed_doesNotReloadProfileFile() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + ProfileFileSupplier supplier = builder() + .fixedProfileFile(credentialsFilePath) + .build(); + + ProfileFile file1 = supplier.get(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + + ProfileFile file2 = supplier.get(); + + assertThat(file2).isSameAs(file1); + } + + @Test + void get_profileModifiedWithinJitterPeriod_doesNotReloadCredentials() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + Duration durationWithinJitter = Duration.ofMillis(10); + ProfileFileSupplier supplier = builderWithClock(clock) + .reloadWhenModified(credentialsFilePath) + .build(); + + ProfileFile file1 = supplier.get(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plus(durationWithinJitter)); + + clock.tickForward(durationWithinJitter); + ProfileFile file2 = supplier.get(); + + assertThat(file2).isSameAs(file1); + } + + @Test + void get_profileModifiedOutsideJitterPeriod_reloadsCredentials() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + + ProfileFileSupplier supplier = builderWithClock(clock) + .reloadWhenModified(credentialsFilePath) + .build(); + + Duration durationOutsideJitter = Duration.ofSeconds(1); + + supplier.get(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plus(durationOutsideJitter)); + + clock.tickForward(durationOutsideJitter); + + Optional fileOptional = supplier.get().profile("default"); + assertThat(fileOptional).isPresent(); + + assertThat(fileOptional.get()).satisfies(profile -> { + Optional awsAccessKeyIdOptional = profile.property("aws_access_key_id"); + assertThat(awsAccessKeyIdOptional).isPresent(); + String awsAccessKeyId = awsAccessKeyIdOptional.get(); + assertThat(awsAccessKeyId).isEqualTo("modifiedAccessKey"); + + Optional awsSecretAccessKeyOptional = profile.property("aws_secret_access_key"); + assertThat(awsSecretAccessKeyOptional).isPresent(); + String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); + assertThat(awsSecretAccessKey).isEqualTo("modifiedSecretAccessKey"); + }); + } + + @Test + void get_profileModified_reloadsProfileFile() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + ProfileFileSupplier supplier = builderWithClock(clock) + .reloadWhenModified(credentialsFilePath) + .build(); + + Duration duration = Duration.ofSeconds(10); + ProfileFile file1 = supplier.get(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(duration); + ProfileFile file2 = supplier.get(); + + assertThat(file2).isNotSameAs(file1); + } + + @Test + void get_profileModifiedOnceButRefreshedMultipleTimes_reloadsProfileFileOnce() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + ProfileFileSupplier supplier = builderWithClock(clock) + .reloadWhenModified(credentialsFilePath) + .build(); + ProfileFile file1 = supplier.get(); + + clock.tickForward(Duration.ofSeconds(5)); + ProfileFile file2 = supplier.get(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(Duration.ofSeconds(5)); + ProfileFile file3 = supplier.get(); + + assertThat(file2).isSameAs(file1); + assertThat(file3).isNotSameAs(file2); + } + + @Test + void get_profileModifiedMultipleTimes_reloadsProfileFileOncePerChange() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + ProfileFileSupplier supplier = builderWithClock(clock) + .reloadWhenModified(credentialsFilePath) + .build(); + Duration duration = Duration.ofSeconds(5); + + ProfileFile file1 = supplier.get(); + + clock.tickForward(duration); + ProfileFile file2 = supplier.get(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(duration); + ProfileFile file3 = supplier.get(); + + generateTestCredentialsFile("updatedAccessKey", "updatedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(duration); + ProfileFile file4 = supplier.get(); + + clock.tickForward(duration); + ProfileFile file5 = supplier.get(); + + assertThat(file2).isSameAs(file1); + assertThat(file3).isNotSameAs(file2); + assertThat(file4).isNotSameAs(file3); + assertThat(file5).isSameAs(file4); + } + + @Test + void get_supplierBuiltByReloadWhenModified_loadsProfileFile() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + ProfileFileSupplier supplier = ProfileFileSupplier.reloadWhenModified(credentialsFilePath); + ProfileFile file = supplier.get(); + + Optional profileOptional = file.profile("default"); + assertThat(profileOptional).isPresent(); + + assertThat(profileOptional.get()).satisfies(profile -> { + Optional awsAccessKeyIdOptional = profile.property("aws_access_key_id"); + assertThat(awsAccessKeyIdOptional).isPresent(); + String awsAccessKeyId = awsAccessKeyIdOptional.get(); + assertThat(awsAccessKeyId).isEqualTo("defaultAccessKey"); + + Optional awsSecretAccessKeyOptional = profile.property("aws_secret_access_key"); + assertThat(awsSecretAccessKeyOptional).isPresent(); + String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); + assertThat(awsSecretAccessKey).isEqualTo("defaultSecretAccessKey"); + }); + } + + @Test + void get_supplierBuiltByFixedProfileFilePath_loadsProfileFile() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + ProfileFileSupplier supplier = ProfileFileSupplier.fixedProfileFile(credentialsFilePath); + ProfileFile file = supplier.get(); + + Optional profileOptional = file.profile("default"); + assertThat(profileOptional).isPresent(); + + assertThat(profileOptional.get()).satisfies(profile -> { + Optional awsAccessKeyIdOptional = profile.property("aws_access_key_id"); + assertThat(awsAccessKeyIdOptional).isPresent(); + String awsAccessKeyId = awsAccessKeyIdOptional.get(); + assertThat(awsAccessKeyId).isEqualTo("defaultAccessKey"); + + Optional awsSecretAccessKeyOptional = profile.property("aws_secret_access_key"); + assertThat(awsSecretAccessKeyOptional).isPresent(); + String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); + assertThat(awsSecretAccessKey).isEqualTo("defaultSecretAccessKey"); + }); + } + + @Test + void get_supplierBuiltByFixedProfileFileObject_returnsProfileFileInstance() { + ProfileFile file = ProfileFile.defaultProfileFile(); + ProfileFileSupplier supplier = ProfileFileSupplier.fixedProfileFile(file); + + assertThat(supplier.get()).isSameAs(file); + } + + @Test + void wrapIntoNullableSupplier_nonNullProfileFile_returnsNonNullSupplier() { + ProfileFile file = ProfileFile.defaultProfileFile(); + ProfileFileSupplier supplier = ProfileFileSupplier.wrapIntoNullableSupplier(file); + + assertThat(supplier).isNotNull(); + } + + @Test + void wrapIntoNullableSupplier_nullProfileFile_returnsNullSupplier() { + ProfileFile file = null; + ProfileFileSupplier supplier = ProfileFileSupplier.wrapIntoNullableSupplier(file); + + assertThat(supplier).isNull(); + } + + @Test + void fixedProfileFile_nullProfileFile_returnsNonNullSupplier() { + ProfileFile file = null; + ProfileFileSupplier supplier = ProfileFileSupplier.fixedProfileFile(file); + + assertThat(supplier).isNotNull(); + } + + @Test + void get_givenOnLoadAction_callsActionOncePerNewProfileFile() { + int actualProfilesCount = 3; + AtomicInteger blockCount = new AtomicInteger(); + + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + ProfileFileSupplier supplier = builderWithClock(clock) + .reloadWhenModified(credentialsFilePath) + .onProfileFileLoad(f -> blockCount.incrementAndGet()) + .build(); + Duration duration = Duration.ofSeconds(5); + + supplier.get(); + + clock.tickForward(duration); + supplier.get(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(duration); + supplier.get(); + + generateTestCredentialsFile("updatedAccessKey", "updatedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(duration); + supplier.get(); + + clock.tickForward(duration); + supplier.get(); + + assertThat(blockCount.get()).isEqualTo(actualProfilesCount); + } + + private Path generateTestFile(String contents, String filename) { + try { + Files.createDirectories(testDirectory); + return Files.write(testDirectory.resolve(filename), contents.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Path generateTestCredentialsFile(String accessKeyId, String secretAccessKey) { + String contents = String.format("[default]\naws_access_key_id = %s\naws_secret_access_key = %s\n", + accessKeyId, secretAccessKey); + return generateTestFile(contents, "credentials.txt"); + } + + private void updateModificationTime(Path path, Instant instant) { + try { + Files.setLastModifiedTime(path, FileTime.from(instant)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private ProfileFileSupplierBuilder builder() { + return new ProfileFileSupplierBuilder(); + } + + private ProfileFileSupplierBuilder builderWithClock(Clock clock) { + return new ProfileFileSupplierBuilder().clock(clock); + } + + private static final class AdjustableClock extends Clock { + private Instant time; + + private AdjustableClock() { + this.time = Instant.now(); + } + + @Override + public ZoneId getZone() { + return ZoneOffset.UTC; + } + + @Override + public Clock withZone(ZoneId zone) { + throw new UnsupportedOperationException(); + } + + @Override + public Instant instant() { + return time; + } + + public void tickForward(TemporalAmount amount) { + time = time.plus(amount); + } + + public void tickBackward(TemporalAmount amount) { + time = time.minus(amount); + } + } +} \ No newline at end of file diff --git a/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java b/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java new file mode 100644 index 000000000000..eda74dfc3bb2 --- /dev/null +++ b/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java @@ -0,0 +1,348 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.profiles.internal; + +import com.google.common.jimfs.Jimfs; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.TemporalAmount; +import java.util.concurrent.atomic.AtomicInteger; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.utils.StringInputStream; + +public class ProfileFileRefresherTest { + + private static FileSystem jimfs; + private static Path testDirectory; + + @BeforeAll + public static void setup() { + jimfs = Jimfs.newFileSystem(); + testDirectory = jimfs.getPath("test"); + } + + @AfterAll + public static void tearDown() { + try { + jimfs.close(); + } catch (IOException e) { + // no-op + } + } + + @Test + void refreshIfStale_profileModifiedNoPathSpecified_doesNotReloadProfileFile() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + try (ProfileFileRefresher refresher = refresherWithClock(clock) + .profileFile(() -> profileFile(credentialsFilePath)) + .build()) { + Duration intervalWithinJitter = Duration.ofMillis(100); + + ProfileFile file1 = refresher.refreshIfStale(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(intervalWithinJitter); + ProfileFile file2 = refresher.refreshIfStale(); + + Assertions.assertThat(file2).isSameAs(file1); + } + } + + @Test + void refreshIfStale_profileModifiedWithinJitterPeriod_doesNotReloadProfileFile() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + try (ProfileFileRefresher refresher = refresherWithClock(clock) + .profileFile(() -> profileFile(credentialsFilePath)) + .profileFilePath(credentialsFilePath) + .build()) { + Duration intervalWithinJitter = Duration.ofMillis(100); + + ProfileFile file1 = refresher.refreshIfStale(); + + clock.tickForward(intervalWithinJitter); + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant()); + + ProfileFile file2 = refresher.refreshIfStale(); + + Assertions.assertThat(file2).isSameAs(file1); + } + } + + @Test + void refreshIfStale_profileModifiedOutsideJitterPeriod_reloadsProfileFile() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + try (ProfileFileRefresher refresher = refresherWithClock(clock) + .profileFile(() -> profileFile(credentialsFilePath)) + .profileFilePath(credentialsFilePath) + .build()) { + Duration intervalOutsideJitter = Duration.ofMillis(1_000); + + ProfileFile file1 = refresher.refreshIfStale(); + + clock.tickForward(intervalOutsideJitter); + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant()); + + ProfileFile file2 = refresher.refreshIfStale(); + + Assertions.assertThat(file2).isNotSameAs(file1); + } + } + + @Test + void refreshIfStale_profileModified_reloadsProfileFile() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + try (ProfileFileRefresher refresher = refresherWithClock(clock) + .profileFile(() -> profileFile(credentialsFilePath)) + .profileFilePath(credentialsFilePath) + .build()) { + + Duration refreshInterval = Duration.ofSeconds(15); + + ProfileFile file1 = refresher.refreshIfStale(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(refreshInterval.plusSeconds(10)); + ProfileFile file2 = refresher.refreshIfStale(); + + Assertions.assertThat(file2).isNotSameAs(file1); + } + } + + @Test + void refreshIfStale_profileModifiedOnceButRefreshedMultipleTimes_reloadsProfileFileOnce() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + try (ProfileFileRefresher refresher = refresherWithClock(clock) + .profileFile(() -> profileFile(credentialsFilePath)) + .profileFilePath(credentialsFilePath) + .build()) { + ProfileFile file1 = refresher.refreshIfStale(); + + clock.tickForward(Duration.ofSeconds(5)); + ProfileFile file2 = refresher.refreshIfStale(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(Duration.ofSeconds(5)); + ProfileFile file3 = refresher.refreshIfStale(); + + Assertions.assertThat(file2).isSameAs(file1); + Assertions.assertThat(file3).isNotSameAs(file2); + } + } + + @Test + void refreshIfStale_profileModifiedMultipleTimes_reloadsProfileFileOncePerChange() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + try (ProfileFileRefresher refresher = refresherWithClock(clock) + .profileFile(() -> profileFile(credentialsFilePath)) + .profileFilePath(credentialsFilePath) + .build()) { + Duration duration = Duration.ofSeconds(5); + + ProfileFile file1 = refresher.refreshIfStale(); + + clock.tickForward(duration); + ProfileFile file2 = refresher.refreshIfStale(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(duration); + ProfileFile file3 = refresher.refreshIfStale(); + + generateTestCredentialsFile("updatedAccessKey", "updatedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(duration); + ProfileFile file4 = refresher.refreshIfStale(); + + clock.tickForward(duration); + ProfileFile file5 = refresher.refreshIfStale(); + + Assertions.assertThat(file2).isSameAs(file1); + Assertions.assertThat(file3).isNotSameAs(file2); + Assertions.assertThat(file4).isNotSameAs(file3); + Assertions.assertThat(file5).isSameAs(file4); + } + } + + @Test + void refreshIfStale_givenOnReloadConsumer_callsConsumerOncePerChange() { + int actualRefreshOperations = 3; + AtomicInteger refreshOperationsCounter = new AtomicInteger(); + + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + + AdjustableClock clock = new AdjustableClock(); + try (ProfileFileRefresher refresher = refresherWithClock(clock) + .profileFile(() -> profileFile(credentialsFilePath)) + .profileFilePath(credentialsFilePath) + .onProfileFileReload(f -> refreshOperationsCounter.incrementAndGet()) + .build()) { + Duration duration = Duration.ofSeconds(5); + + ProfileFile file1 = refresher.refreshIfStale(); + + clock.tickForward(duration); + ProfileFile file2 = refresher.refreshIfStale(); + + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(duration); + ProfileFile file3 = refresher.refreshIfStale(); + + generateTestCredentialsFile("updatedAccessKey", "updatedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + + clock.tickForward(duration); + ProfileFile file4 = refresher.refreshIfStale(); + + clock.tickForward(duration); + ProfileFile file5 = refresher.refreshIfStale(); + + Assertions.assertThat(file2).isSameAs(file1); + Assertions.assertThat(file3).isNotSameAs(file2); + Assertions.assertThat(file4).isNotSameAs(file3); + Assertions.assertThat(file5).isSameAs(file4); + } + + Assertions.assertThat(refreshOperationsCounter.get()).isEqualTo(actualRefreshOperations); + } + + @Test + void refreshIfStale_profileDeleted_returnsProfileFileFromExceptionHandler() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + ProfileFile fallbackProfile = credentialFile("[test]\nx = y"); + + AdjustableClock clock = new AdjustableClock(); + try (ProfileFileRefresher refresher = refresherWithClock(clock) + .profileFile(() -> profileFile(credentialsFilePath)) + .profileFilePath(credentialsFilePath) + .exceptionHandler(e -> fallbackProfile) + .build()) { + + Files.deleteIfExists(credentialsFilePath); + ProfileFile file1 = refresher.refreshIfStale(); + + Assertions.assertThat(file1).isSameAs(fallbackProfile); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private ProfileFile credentialFile(String credentialFile) { + return ProfileFile.builder() + .content(new StringInputStream(credentialFile)) + .type(ProfileFile.Type.CREDENTIALS) + .build(); + } + + private Path generateTestFile(String contents, String filename) { + try { + Files.createDirectories(testDirectory); + return Files.write(testDirectory.resolve(filename), contents.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Path generateTestCredentialsFile(String accessKeyId, String secretAccessKey) { + String contents = String.format("[default]\naws_access_key_id = %s\naws_secret_access_key = %s\n", + accessKeyId, secretAccessKey); + return generateTestFile(contents, "credentials.txt"); + } + + private void updateModificationTime(Path path, Instant instant) { + try { + Files.setLastModifiedTime(path, FileTime.from(instant)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private ProfileFile profileFile(Path path) { + return ProfileFile.builder().content(path).type(ProfileFile.Type.CREDENTIALS).build(); + } + + private ProfileFileRefresher.Builder refresherWithClock(Clock clock) { + return ProfileFileRefresher.builder() + .clock(clock); + } + + private static final class AdjustableClock extends Clock { + private Instant time; + + private AdjustableClock() { + this.time = Instant.now(); + } + + @Override + public ZoneId getZone() { + return ZoneOffset.UTC; + } + + @Override + public Clock withZone(ZoneId zone) { + throw new UnsupportedOperationException(); + } + + @Override + public Instant instant() { + return time; + } + + public void tickForward(TemporalAmount amount) { + time = time.plus(amount); + } + + public void tickBackward(TemporalAmount amount) { + time = time.minus(amount); + } + } +} From 6b186a29105b82b70b05417a7c83182a68ccc192 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Thu, 1 Dec 2022 07:21:50 -0700 Subject: [PATCH 02/16] ProfileFileSupplier API changes, aggregating (#3558) * Added methods for aggregating ProfileFile objects * Removed redundant logic, changelog entry * Removed redundant methods * Use compare and set to make thread safe --- .../ProfileCredentialsProvider.java | 4 +- .../ProfileCredentialsProviderTest.java | 2 +- .../awssdk/profiles/ProfileFileSupplier.java | 98 +++++-- .../profiles/ProfileFileSupplierBuilder.java | 8 +- .../profiles/ProfileFileSupplierTest.java | 258 ++++++++++++++++-- 5 files changed, 318 insertions(+), 52 deletions(-) diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java index 28c4b74ce3ee..34c0e2352a7b 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java @@ -225,7 +225,9 @@ static final class BuilderImpl implements Builder { @Override public Builder profileFile(ProfileFile profileFile) { - return profileFile(ProfileFileSupplier.wrapIntoNullableSupplier(profileFile)); + return profileFile(Optional.ofNullable(profileFile) + .map(ProfileFileSupplier::fixedProfileFile) + .orElse(null)); } public void setProfileFile(ProfileFile profileFile) { diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java index 7895e520f02e..b7eea2daff67 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java @@ -193,7 +193,7 @@ void resolveCredentials_presentProfileFileSupplier_returnsCredentials() { ProfileCredentialsProvider provider = ProfileCredentialsProvider.builder() - .profileFile(ProfileFileSupplier.reloadWhenModified(path)) + .profileFile(ProfileFileSupplier.reloadWhenModified(path, ProfileFile.Type.CREDENTIALS)) .profileName("default") .build(); diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java index ea8e2ffbd8de..79e8881e69af 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java @@ -16,7 +16,10 @@ package software.amazon.awssdk.profiles; import java.nio.file.Path; +import java.util.Arrays; import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.profiles.internal.ProfileFileRefresher; @@ -36,21 +39,53 @@ public interface ProfileFileSupplier extends Supplier, SdkAutoClose default void close() { } + /** + * Creates a {@link ProfileFileSupplier} capable of producing multiple profile objects by aggregating the default + * credentials and configuration files as determined by {@link ProfileFileLocation#credentialsFileLocation()} abd + * {@link ProfileFileLocation#configurationFileLocation()}. This supplier will return a new ProfileFile instance only once + * either disk file has been modified. Multiple calls to the supplier while both disk files are unchanged will return the + * same object. + * + * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a new aggregate profile when either file + * has been modified. + */ + static ProfileFileSupplier defaultSupplier() { + Optional credentialsSupplierOptional + = ProfileFileLocation.credentialsFileLocation() + .map(path -> reloadWhenModified(path, ProfileFile.Type.CREDENTIALS)); + + Optional configurationSupplierOptional + = ProfileFileLocation.configurationFileLocation() + .map(path -> reloadWhenModified(path, ProfileFile.Type.CONFIGURATION)); + + ProfileFileSupplier supplier = () -> null; + if (credentialsSupplierOptional.isPresent() && configurationSupplierOptional.isPresent()) { + supplier = aggregate(credentialsSupplierOptional.get(), configurationSupplierOptional.get()); + } else if (credentialsSupplierOptional.isPresent()) { + supplier = credentialsSupplierOptional.get(); + } else if (configurationSupplierOptional.isPresent()) { + supplier = configurationSupplierOptional.get(); + } + + return supplier; + } + /** * Creates a {@link ProfileFileSupplier} capable of producing multiple profile objects from a file. This supplier will * return a new ProfileFile instance only once the disk file has been modified. Multiple calls to the supplier while the * disk file is unchanged will return the same object. * * @param path Path to the file to read from. + * @param type The type of file. See {@link ProfileFile.Type} for possible values. * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a new profile when the file * has been modified. */ - static ProfileFileSupplier reloadWhenModified(Path path) { + static ProfileFileSupplier reloadWhenModified(Path path, ProfileFile.Type type) { return new ProfileFileSupplier() { final ProfileFile.Builder builder = ProfileFile.builder() .content(path) - .type(ProfileFile.Type.CREDENTIALS); + .type(type); final ProfileFileRefresher refresher = ProfileFileRefresher.builder() .profileFile(builder::build) @@ -69,21 +104,6 @@ public void close() { }; } - /** - * Creates a {@link ProfileFileSupplier} capable of producing a single profile object from a file. - * - * @param path Path to the file to read from. - * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a single profile. - */ - static ProfileFileSupplier fixedProfileFile(Path path) { - ProfileFile profileFile = ProfileFile.builder() - .content(path) - .type(ProfileFile.Type.CREDENTIALS) - .build(); - - return () -> profileFile; - } - /** * Creates a {@link ProfileFileSupplier} that produces an existing profile. * @@ -95,14 +115,46 @@ static ProfileFileSupplier fixedProfileFile(ProfileFile profileFile) { } /** - * creates a {@link ProfileFileSupplier} that produces an existing non-null profile. If the given profile - * is null, then the created supplier will also be null. + * Creates a {@link ProfileFileSupplier} by combining the {@link ProfileFile} objects from two {@code ProfileFileSupplier}s. + * Objects are passed into {@link ProfileFile.Aggregator}. * - * @param profileFile Profile object to supply. - * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a single profile. + * @param suppliers Array of {@code ProfileFileSupplier} objects. {@code ProfileFile} objects are passed to + * {@link ProfileFile.Aggregator#addFile(ProfileFile)} in the same argument order as the supplier that + * generated it. + * @return Implementation of {@link ProfileFileSupplier} aggregating results from the supplier objects. */ - static ProfileFileSupplier wrapIntoNullableSupplier(ProfileFile profileFile) { - return Objects.nonNull(profileFile) ? () -> profileFile : null; + static ProfileFileSupplier aggregate(ProfileFileSupplier... suppliers) { + + return new ProfileFileSupplier() { + + final AtomicReference currentAggregateProfileFile = new AtomicReference<>(); + + @Override + public ProfileFile get() { + ProfileFile.Aggregator aggregator = ProfileFile.aggregator(); + for (ProfileFileSupplier supplier : suppliers) { + aggregator.addFile(supplier.get()); + } + + return refreshAndGetCurrentAggregate(aggregator); + } + + @Override + public void close() { + Arrays.stream(suppliers).forEach(ProfileFileSupplier::close); + } + + private ProfileFile refreshAndGetCurrentAggregate(ProfileFile.Aggregator aggregator) { + ProfileFile current = currentAggregateProfileFile.get(); + ProfileFile next = aggregator.build(); + if (!Objects.equals(current, next)) { + currentAggregateProfileFile.compareAndSet(current, next); + } + + return currentAggregateProfileFile.get(); + } + + }; } } diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java index 6bc55cc33130..3df150da4277 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java @@ -32,20 +32,20 @@ final class ProfileFileSupplierBuilder { private Clock clock; private Consumer onProfileFileLoad; - public ProfileFileSupplierBuilder reloadWhenModified(Path path) { + public ProfileFileSupplierBuilder reloadWhenModified(Path path, ProfileFile.Type type) { ProfileFile.Builder builder = ProfileFile.builder() .content(path) - .type(ProfileFile.Type.CREDENTIALS); + .type(type); this.profileFile = builder::build; this.profileFilePath = path; this.reloadingSupplier = true; return this; } - public ProfileFileSupplierBuilder fixedProfileFile(Path path) { + public ProfileFileSupplierBuilder fixedProfileFile(Path path, ProfileFile.Type type) { return fixedProfileFile(ProfileFile.builder() .content(path) - .type(ProfileFile.Type.CREDENTIALS) + .type(type) .build()); } diff --git a/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java b/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java index 370ddb955a80..7c098e2c5b72 100644 --- a/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java +++ b/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java @@ -30,12 +30,21 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.temporal.TemporalAmount; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import software.amazon.awssdk.profiles.ProfileFileSupplierBuilder; +import software.amazon.awssdk.utils.Pair; +import software.amazon.awssdk.utils.StringInputStream; class ProfileFileSupplierTest { @@ -62,7 +71,7 @@ void get_profileFileFixed_doesNotReloadProfileFile() { Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); ProfileFileSupplier supplier = builder() - .fixedProfileFile(credentialsFilePath) + .fixedProfileFile(credentialsFilePath, ProfileFile.Type.CREDENTIALS) .build(); ProfileFile file1 = supplier.get(); @@ -72,6 +81,8 @@ void get_profileFileFixed_doesNotReloadProfileFile() { ProfileFile file2 = supplier.get(); assertThat(file2).isSameAs(file1); + + supplier.close(); } @Test @@ -81,7 +92,7 @@ void get_profileModifiedWithinJitterPeriod_doesNotReloadCredentials() { AdjustableClock clock = new AdjustableClock(); Duration durationWithinJitter = Duration.ofMillis(10); ProfileFileSupplier supplier = builderWithClock(clock) - .reloadWhenModified(credentialsFilePath) + .reloadWhenModified(credentialsFilePath, ProfileFile.Type.CREDENTIALS) .build(); ProfileFile file1 = supplier.get(); @@ -93,6 +104,8 @@ void get_profileModifiedWithinJitterPeriod_doesNotReloadCredentials() { ProfileFile file2 = supplier.get(); assertThat(file2).isSameAs(file1); + + supplier.close(); } @Test @@ -102,7 +115,7 @@ void get_profileModifiedOutsideJitterPeriod_reloadsCredentials() { AdjustableClock clock = new AdjustableClock(); ProfileFileSupplier supplier = builderWithClock(clock) - .reloadWhenModified(credentialsFilePath) + .reloadWhenModified(credentialsFilePath, ProfileFile.Type.CREDENTIALS) .build(); Duration durationOutsideJitter = Duration.ofSeconds(1); @@ -128,6 +141,8 @@ void get_profileModifiedOutsideJitterPeriod_reloadsCredentials() { String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); assertThat(awsSecretAccessKey).isEqualTo("modifiedSecretAccessKey"); }); + + supplier.close(); } @Test @@ -136,7 +151,7 @@ void get_profileModified_reloadsProfileFile() { AdjustableClock clock = new AdjustableClock(); ProfileFileSupplier supplier = builderWithClock(clock) - .reloadWhenModified(credentialsFilePath) + .reloadWhenModified(credentialsFilePath, ProfileFile.Type.CREDENTIALS) .build(); Duration duration = Duration.ofSeconds(10); @@ -149,6 +164,8 @@ void get_profileModified_reloadsProfileFile() { ProfileFile file2 = supplier.get(); assertThat(file2).isNotSameAs(file1); + + supplier.close(); } @Test @@ -157,7 +174,7 @@ void get_profileModifiedOnceButRefreshedMultipleTimes_reloadsProfileFileOnce() { AdjustableClock clock = new AdjustableClock(); ProfileFileSupplier supplier = builderWithClock(clock) - .reloadWhenModified(credentialsFilePath) + .reloadWhenModified(credentialsFilePath, ProfileFile.Type.CREDENTIALS) .build(); ProfileFile file1 = supplier.get(); @@ -172,6 +189,8 @@ void get_profileModifiedOnceButRefreshedMultipleTimes_reloadsProfileFileOnce() { assertThat(file2).isSameAs(file1); assertThat(file3).isNotSameAs(file2); + + supplier.close(); } @Test @@ -180,7 +199,7 @@ void get_profileModifiedMultipleTimes_reloadsProfileFileOncePerChange() { AdjustableClock clock = new AdjustableClock(); ProfileFileSupplier supplier = builderWithClock(clock) - .reloadWhenModified(credentialsFilePath) + .reloadWhenModified(credentialsFilePath, ProfileFile.Type.CREDENTIALS) .build(); Duration duration = Duration.ofSeconds(5); @@ -208,13 +227,15 @@ void get_profileModifiedMultipleTimes_reloadsProfileFileOncePerChange() { assertThat(file3).isNotSameAs(file2); assertThat(file4).isNotSameAs(file3); assertThat(file5).isSameAs(file4); + + supplier.close(); } @Test void get_supplierBuiltByReloadWhenModified_loadsProfileFile() { Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); - ProfileFileSupplier supplier = ProfileFileSupplier.reloadWhenModified(credentialsFilePath); + ProfileFileSupplier supplier = ProfileFileSupplier.reloadWhenModified(credentialsFilePath, ProfileFile.Type.CREDENTIALS); ProfileFile file = supplier.get(); Optional profileOptional = file.profile("default"); @@ -231,13 +252,18 @@ void get_supplierBuiltByReloadWhenModified_loadsProfileFile() { String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); assertThat(awsSecretAccessKey).isEqualTo("defaultSecretAccessKey"); }); + + supplier.close(); } @Test - void get_supplierBuiltByFixedProfileFilePath_loadsProfileFile() { + void get_supplierBuiltByFixedProfileFile_returnsProfileFile() { Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); - ProfileFileSupplier supplier = ProfileFileSupplier.fixedProfileFile(credentialsFilePath); + ProfileFileSupplier supplier = ProfileFileSupplier.fixedProfileFile(ProfileFile.builder() + .content(credentialsFilePath) + .type(ProfileFile.Type.CREDENTIALS) + .build()); ProfileFile file = supplier.get(); Optional profileOptional = file.profile("default"); @@ -254,30 +280,158 @@ void get_supplierBuiltByFixedProfileFilePath_loadsProfileFile() { String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); assertThat(awsSecretAccessKey).isEqualTo("defaultSecretAccessKey"); }); + + supplier.close(); } @Test - void get_supplierBuiltByFixedProfileFileObject_returnsProfileFileInstance() { - ProfileFile file = ProfileFile.defaultProfileFile(); - ProfileFileSupplier supplier = ProfileFileSupplier.fixedProfileFile(file); + void get_supplierBuiltByReloadWhenModifiedAggregate_reloadsCredentials() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + Path configFilePath = generateTestConfigFile(Pair.of("region", "us-west-2")); + + ProfileFileSupplier credentialsProfileFileSupplier = ProfileFileSupplier.reloadWhenModified(credentialsFilePath, + ProfileFile.Type.CREDENTIALS); + ProfileFileSupplier configProfileFileSupplier = ProfileFileSupplier.reloadWhenModified(configFilePath, + ProfileFile.Type.CONFIGURATION); + ProfileFileSupplier supplier = ProfileFileSupplier.aggregate(credentialsProfileFileSupplier, configProfileFileSupplier); + + Optional fileOptional = supplier.get().profile("default"); + assertThat(fileOptional).isPresent(); + + assertThat(fileOptional.get()).satisfies(profile -> { + Optional awsAccessKeyIdOptional = profile.property("aws_access_key_id"); + assertThat(awsAccessKeyIdOptional).isPresent(); + String awsAccessKeyId = awsAccessKeyIdOptional.get(); + assertThat(awsAccessKeyId).isEqualTo("defaultAccessKey"); + + Optional awsSecretAccessKeyOptional = profile.property("aws_secret_access_key"); + assertThat(awsSecretAccessKeyOptional).isPresent(); + String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); + assertThat(awsSecretAccessKey).isEqualTo("defaultSecretAccessKey"); + + Optional regionOptional = profile.property("region"); + assertThat(regionOptional).isPresent(); + String region = regionOptional.get(); + assertThat(region).isEqualTo("us-west-2"); + }); - assertThat(supplier.get()).isSameAs(file); + supplier.close(); } @Test - void wrapIntoNullableSupplier_nonNullProfileFile_returnsNonNullSupplier() { - ProfileFile file = ProfileFile.defaultProfileFile(); - ProfileFileSupplier supplier = ProfileFileSupplier.wrapIntoNullableSupplier(file); + void get_supplierBuiltByFixedProfileFileAggregate_returnsAggregateProfileFileInstance() { + Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); + Path configFilePath = generateTestConfigFile(Pair.of("region", "us-west-2")); + + ProfileFileSupplier credentialsProfileFileSupplier + = ProfileFileSupplier.fixedProfileFile(ProfileFile.builder() + .content(credentialsFilePath) + .type(ProfileFile.Type.CREDENTIALS) + .build()); + ProfileFileSupplier configProfileFileSupplier + = ProfileFileSupplier.fixedProfileFile(ProfileFile.builder() + .content(configFilePath) + .type(ProfileFile.Type.CONFIGURATION) + .build()); + ProfileFileSupplier supplier = ProfileFileSupplier.aggregate(credentialsProfileFileSupplier, configProfileFileSupplier); + ProfileFile file = supplier.get(); - assertThat(supplier).isNotNull(); + Optional profileOptional = file.profile("default"); + assertThat(profileOptional).isPresent(); + + assertThat(profileOptional.get()).satisfies(profile -> { + Optional awsAccessKeyIdOptional = profile.property("aws_access_key_id"); + assertThat(awsAccessKeyIdOptional).isPresent(); + String awsAccessKeyId = awsAccessKeyIdOptional.get(); + assertThat(awsAccessKeyId).isEqualTo("defaultAccessKey"); + + Optional awsSecretAccessKeyOptional = profile.property("aws_secret_access_key"); + assertThat(awsSecretAccessKeyOptional).isPresent(); + String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); + assertThat(awsSecretAccessKey).isEqualTo("defaultSecretAccessKey"); + + Optional regionOptional = profile.property("region"); + assertThat(regionOptional).isPresent(); + String region = regionOptional.get(); + assertThat(region).isEqualTo("us-west-2"); + }); + + supplier.close(); } @Test - void wrapIntoNullableSupplier_nullProfileFile_returnsNullSupplier() { - ProfileFile file = null; - ProfileFileSupplier supplier = ProfileFileSupplier.wrapIntoNullableSupplier(file); + void aggregate_supplierReturnsSameInstanceMultipleTimesAggregatingProfileFile_aggregatesOnlyDistinctInstances() { + ProfileFile credentialFile1 = credentialFile("test1", "key1", "secret1"); + ProfileFile credentialFile2 = credentialFile("test2", "key2", "secret2"); + ProfileFile credentialFile3 = credentialFile("test3", "key3", "secret3"); + ProfileFile credentialFile4 = credentialFile("test4", "key4", "secret4"); + ProfileFile configFile = configFile("profile test", Pair.of("region", "us-west-2")); + + List orderedCredentialsFiles + = Arrays.asList(credentialFile1, credentialFile1, credentialFile2, credentialFile3, credentialFile3, credentialFile4, + credentialFile4, credentialFile4); - assertThat(supplier).isNull(); + ProfileFile aggregateFile1 = ProfileFile.aggregator().addFile(credentialFile1).addFile(configFile).build(); + ProfileFile aggregateFile2 = ProfileFile.aggregator().addFile(credentialFile2).addFile(configFile).build(); + ProfileFile aggregateFile3 = ProfileFile.aggregator().addFile(credentialFile3).addFile(configFile).build(); + ProfileFile aggregateFile4 = ProfileFile.aggregator().addFile(credentialFile4).addFile(configFile).build(); + + List distinctAggregateFiles = Arrays.asList(aggregateFile1, aggregateFile2, aggregateFile3, aggregateFile4); + + ProfileFileSupplier supplier = ProfileFileSupplier.aggregate(supply(orderedCredentialsFiles), () -> configFile); + + List suppliedProfileFiles = Stream.generate(supplier) + .limit(orderedCredentialsFiles.size()) + .filter(uniqueInstances()) + .collect(Collectors.toList()); + + assertThat(suppliedProfileFiles).isEqualTo(distinctAggregateFiles); + + supplier.close(); + } + + @Test + void aggregate_supplierReturnsSameInstanceMultipleTimesAggregatingProfileFileSupplier_aggregatesOnlyDistinctInstances() { + ProfileFile credentialFile1 = credentialFile("test1", "key1", "secret1"); + ProfileFile credentialFile2 = credentialFile("test2", "key2", "secret2"); + ProfileFile credentialFile3 = credentialFile("test3", "key3", "secret3"); + ProfileFile credentialFile4 = credentialFile("test4", "key4", "secret4"); + ProfileFile configFile1 = configFile("profile test", Pair.of("region", "us-west-1")); + ProfileFile configFile2 = configFile("profile test", Pair.of("region", "us-west-2")); + ProfileFile configFile3 = configFile("profile test", Pair.of("region", "us-west-3")); + + List orderedCredentialsFiles + = Arrays.asList(credentialFile1, credentialFile1, credentialFile2, credentialFile2, credentialFile3, + credentialFile4, credentialFile4, credentialFile4); + + List orderedConfigFiles + = Arrays.asList(configFile1, configFile1, configFile1, configFile2, configFile3, configFile3, configFile3, + configFile3); + + ProfileFile aggregateFile11 = ProfileFile.aggregator().addFile(credentialFile1).addFile(configFile1).build(); + ProfileFile aggregateFile21 = ProfileFile.aggregator().addFile(credentialFile2).addFile(configFile1).build(); + ProfileFile aggregateFile22 = ProfileFile.aggregator().addFile(credentialFile2).addFile(configFile2).build(); + ProfileFile aggregateFile33 = ProfileFile.aggregator().addFile(credentialFile3).addFile(configFile3).build(); + ProfileFile aggregateFile43 = ProfileFile.aggregator().addFile(credentialFile4).addFile(configFile3).build(); + + List aggregateProfileFiles + = Arrays.asList(aggregateFile11, aggregateFile11, aggregateFile21, aggregateFile22, aggregateFile33, + aggregateFile43, aggregateFile43, aggregateFile43); + + List distinctAggregateProfileFiles + = Arrays.asList(aggregateFile11, aggregateFile21, aggregateFile22, aggregateFile33, aggregateFile43); + + ProfileFileSupplier supplier = ProfileFileSupplier.aggregate(supply(orderedCredentialsFiles), supply(orderedConfigFiles)); + + List suppliedProfileFiles = Stream.generate(supplier) + .filter(Objects::nonNull) + .limit(aggregateProfileFiles.size()) + .filter(uniqueInstances()) + .collect(Collectors.toList()); + + assertThat(suppliedProfileFiles).isEqualTo(distinctAggregateProfileFiles); + + supplier.close(); } @Test @@ -286,6 +440,8 @@ void fixedProfileFile_nullProfileFile_returnsNonNullSupplier() { ProfileFileSupplier supplier = ProfileFileSupplier.fixedProfileFile(file); assertThat(supplier).isNotNull(); + + supplier.close(); } @Test @@ -297,7 +453,7 @@ void get_givenOnLoadAction_callsActionOncePerNewProfileFile() { AdjustableClock clock = new AdjustableClock(); ProfileFileSupplier supplier = builderWithClock(clock) - .reloadWhenModified(credentialsFilePath) + .reloadWhenModified(credentialsFilePath, ProfileFile.Type.CREDENTIALS) .onProfileFileLoad(f -> blockCount.incrementAndGet()) .build(); Duration duration = Duration.ofSeconds(5); @@ -323,6 +479,8 @@ void get_givenOnLoadAction_callsActionOncePerNewProfileFile() { supplier.get(); assertThat(blockCount.get()).isEqualTo(actualProfilesCount); + + supplier.close(); } private Path generateTestFile(String contents, String filename) { @@ -340,6 +498,15 @@ private Path generateTestCredentialsFile(String accessKeyId, String secretAccess return generateTestFile(contents, "credentials.txt"); } + private Path generateTestConfigFile(Pair... pairs) { + String values = Arrays.stream(pairs) + .map(pair -> String.format("%s=%s", pair.left(), pair.right())) + .collect(Collectors.joining(System.lineSeparator())); + String contents = String.format("[default]\n%s", values); + + return generateTestFile(contents, "config.txt"); + } + private void updateModificationTime(Path path, Instant instant) { try { Files.setLastModifiedTime(path, FileTime.from(instant)); @@ -348,6 +515,51 @@ private void updateModificationTime(Path path, Instant instant) { } } + private ProfileFile credentialFile(String credentialFile) { + return ProfileFile.builder() + .content(new StringInputStream(credentialFile)) + .type(ProfileFile.Type.CREDENTIALS) + .build(); + } + + private ProfileFile credentialFile(String name, String accessKeyId, String secretAccessKey) { + String contents = String.format("[%s]\naws_access_key_id = %s\naws_secret_access_key = %s\n", + name, accessKeyId, secretAccessKey); + return credentialFile(contents); + } + + private ProfileFile configFile(String credentialFile) { + return ProfileFile.builder() + .content(new StringInputStream(credentialFile)) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + } + + private ProfileFile configFile(String name, Pair... pairs) { + String values = Arrays.stream(pairs) + .map(pair -> String.format("%s=%s", pair.left(), pair.right())) + .collect(Collectors.joining(System.lineSeparator())); + String contents = String.format("[%s]\n%s", name, values); + + return configFile(contents); + } + + private static Predicate uniqueInstances() { + Set seen = ConcurrentHashMap.newKeySet(); + return e -> { + boolean unique = seen.stream().noneMatch(o -> o == e); + if (unique) { + seen.add(e); + } + + return unique; + }; + } + + private static ProfileFileSupplier supply(Iterable iterable) { + return iterable.iterator()::next; + } + private ProfileFileSupplierBuilder builder() { return new ProfileFileSupplierBuilder(); } From c3e743b6393a159d50bfd450f565072dee3f4783 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Thu, 1 Dec 2022 15:03:28 -0700 Subject: [PATCH 03/16] DefaultCredentialsProvider reload profile (#3580) * Misc fixes * Reload credentials by DefaultCredentialsProvider; pass supplier to InstanceProfileCredentialsProvider * Fix code alignment --- .../DefaultCredentialsProvider.java | 53 +++++++----- .../InstanceProfileCredentialsProvider.java | 40 +++++++-- .../DefaultCredentialsProviderTest.java | 83 ++++++++++++++++++ ...nstanceProfileCredentialsProviderTest.java | 86 ++++++++++++++++++- 4 files changed, 231 insertions(+), 31 deletions(-) create mode 100644 core/auth/src/test/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProviderTest.java diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.java index 2486723c3b86..4887deb2fac0 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.java @@ -15,9 +15,11 @@ package software.amazon.awssdk.auth.credentials; +import java.util.Optional; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.internal.LazyAwsCredentialsProvider; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; import software.amazon.awssdk.utils.SdkAutoCloseable; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.builder.CopyableBuilder; @@ -26,8 +28,8 @@ /** * AWS credentials provider chain that looks for credentials in this order: *
    - *
  1. Java System Properties - aws.accessKeyId and aws.secretAccessKey
  2. - *
  3. Environment Variables - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
  4. + *
  5. Java System Properties - {@code aws.accessKeyId} and {@code aws.secretAccessKey}
  6. + *
  7. Environment Variables - {@code AWS_ACCESS_KEY_ID} and {@code AWS_SECRET_ACCESS_KEY}
  8. *
  9. Web Identity Token credentials from system properties or environment variables
  10. *
  11. Credential profiles file at the default location (~/.aws/credentials) shared by all AWS SDKs and the AWS CLI
  12. *
  13. Credentials delivered through the Amazon EC2 container service if AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" environment @@ -51,7 +53,7 @@ public final class DefaultCredentialsProvider private final LazyAwsCredentialsProvider providerChain; - private final ProfileFile profileFile; + private final ProfileFileSupplier profileFileSupplier; private final String profileName; @@ -63,7 +65,7 @@ public final class DefaultCredentialsProvider * @see #builder() */ private DefaultCredentialsProvider(Builder builder) { - this.profileFile = builder.profileFile; + this.profileFileSupplier = builder.profileFileSupplier; this.profileName = builder.profileName; this.reuseLastProviderEnabled = builder.reuseLastProviderEnabled; this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; @@ -87,21 +89,21 @@ private static LazyAwsCredentialsProvider createChain(Builder builder) { return LazyAwsCredentialsProvider.create(() -> { AwsCredentialsProvider[] credentialsProviders = new AwsCredentialsProvider[] { - SystemPropertyCredentialsProvider.create(), - EnvironmentVariableCredentialsProvider.create(), - WebIdentityTokenFileCredentialsProvider.create(), - ProfileCredentialsProvider.builder() - .profileFile(builder.profileFile) - .profileName(builder.profileName) - .build(), - ContainerCredentialsProvider.builder() - .asyncCredentialUpdateEnabled(asyncCredentialUpdateEnabled) - .build(), - InstanceProfileCredentialsProvider.builder() - .asyncCredentialUpdateEnabled(asyncCredentialUpdateEnabled) - .profileFile(builder.profileFile) - .profileName(builder.profileName) - .build() + SystemPropertyCredentialsProvider.create(), + EnvironmentVariableCredentialsProvider.create(), + WebIdentityTokenFileCredentialsProvider.create(), + ProfileCredentialsProvider.builder() + .profileFile(builder.profileFileSupplier) + .profileName(builder.profileName) + .build(), + ContainerCredentialsProvider.builder() + .asyncCredentialUpdateEnabled(asyncCredentialUpdateEnabled) + .build(), + InstanceProfileCredentialsProvider.builder() + .asyncCredentialUpdateEnabled(asyncCredentialUpdateEnabled) + .profileFile(builder.profileFileSupplier) + .profileName(builder.profileName) + .build() }; return AwsCredentialsProviderChain.builder() @@ -144,7 +146,7 @@ public Builder toBuilder() { * Configuration that defines the {@link DefaultCredentialsProvider}'s behavior. */ public static final class Builder implements CopyableBuilder { - private ProfileFile profileFile; + private ProfileFileSupplier profileFileSupplier; private String profileName; private Boolean reuseLastProviderEnabled = true; private Boolean asyncCredentialUpdateEnabled = false; @@ -156,14 +158,20 @@ private Builder() { } private Builder(DefaultCredentialsProvider credentialsProvider) { - this.profileFile = credentialsProvider.profileFile; + this.profileFileSupplier = credentialsProvider.profileFileSupplier; this.profileName = credentialsProvider.profileName; this.reuseLastProviderEnabled = credentialsProvider.reuseLastProviderEnabled; this.asyncCredentialUpdateEnabled = credentialsProvider.asyncCredentialUpdateEnabled; } public Builder profileFile(ProfileFile profileFile) { - this.profileFile = profileFile; + return profileFile(Optional.ofNullable(profileFile) + .map(ProfileFileSupplier::fixedProfileFile) + .orElse(null)); + } + + public Builder profileFile(ProfileFileSupplier profileFileSupplier) { + this.profileFileSupplier = profileFileSupplier; return this; } @@ -198,6 +206,7 @@ public Builder asyncCredentialUpdateEnabled(Boolean asyncCredentialUpdateEnabled /** * Create a {@link DefaultCredentialsProvider} using the configuration defined in this builder. */ + @Override public DefaultCredentialsProvider build() { return new DefaultCredentialsProvider(this); } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java index 04abbbc56569..eb7e0f97b7bf 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java @@ -26,6 +26,7 @@ import java.time.Instant; import java.util.Collections; import java.util.Map; +import java.util.Optional; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; import software.amazon.awssdk.auth.credentials.internal.Ec2MetadataConfigProvider; @@ -36,6 +37,7 @@ import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.exception.SdkServiceException; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; import software.amazon.awssdk.regions.util.HttpResourcesUtils; import software.amazon.awssdk.regions.util.ResourcesEndpointProvider; @@ -77,7 +79,7 @@ public final class InstanceProfileCredentialsProvider private final String asyncThreadName; - private final ProfileFile profileFile; + private final ProfileFileSupplier profileFileSupplier; private final String profileName; @@ -89,14 +91,14 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) { this.endpoint = builder.endpoint; this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; this.asyncThreadName = builder.asyncThreadName; - this.profileFile = builder.profileFile; + this.profileFileSupplier = builder.profileFileSupplier; this.profileName = builder.profileName; this.httpCredentialsLoader = HttpCredentialsLoader.create(); this.configProvider = Ec2MetadataConfigProvider.builder() - .profileFile(builder.profileFile == null ? null : () -> builder.profileFile) - .profileName(builder.profileName == null ? null : builder.profileName) + .profileFile(builder.profileFileSupplier) + .profileName(builder.profileName) .build(); if (Boolean.TRUE.equals(builder.asyncCredentialUpdateEnabled)) { @@ -279,9 +281,19 @@ public interface Builder extends HttpCredentialsProvider.BuilderBy default, {@link ProfileFile#defaultProfileFile()} is used. + * + * @see #profileFile(ProfileFileSupplier) */ Builder profileFile(ProfileFile profileFile); + /** + * Define the mechanism for loading profile files. + * + * @param profileFileSupplier Supplier interface for generating a ProfileFile instance. + * @see #profileFile(ProfileFile) + */ + Builder profileFile(ProfileFileSupplier profileFileSupplier); + /** * Configure the profile name used for loading IMDS-related configuration, like the endpoint mode (IPv4 vs IPv6). * @@ -292,6 +304,7 @@ public interface Builder extends HttpCredentialsProvider.Builder { + assertThat(awsCredentials.accessKeyId()).isEqualTo("access"); + assertThat(awsCredentials.secretAccessKey()).isEqualTo("secret"); + }); + } + + @Test + void resolveCredentials_requestFallsIntoProfileCredentialsProviderWithProfileFileSupplier_returnsCredentials() { + List profileFileList = Arrays.asList(credentialFile("test", "access", "secret"), + credentialFile("test", "modified", "update")); + ProfileFileSupplier profileFileSupplier = supply(profileFileList); + + DefaultCredentialsProvider provider = DefaultCredentialsProvider + .builder() + .profileFile(profileFileSupplier) + .profileName("test") + .build(); + + assertThat(provider.resolveCredentials()).satisfies(awsCredentials -> { + assertThat(awsCredentials.accessKeyId()).isEqualTo("access"); + assertThat(awsCredentials.secretAccessKey()).isEqualTo("secret"); + }); + + assertThat(provider.resolveCredentials()).satisfies(awsCredentials -> { + assertThat(awsCredentials.accessKeyId()).isEqualTo("modified"); + assertThat(awsCredentials.secretAccessKey()).isEqualTo("update"); + }); + } + + private ProfileFile credentialFile(String credentialFile) { + return ProfileFile.builder() + .content(new StringInputStream(credentialFile)) + .type(ProfileFile.Type.CREDENTIALS) + .build(); + } + + private ProfileFile credentialFile(String name, String accessKeyId, String secretAccessKey) { + String contents = String.format("[%s]\naws_access_key_id = %s\naws_secret_access_key = %s\n", + name, accessKeyId, secretAccessKey); + return credentialFile(contents); + } + + private static ProfileFileSupplier supply(Iterable iterable) { + return iterable.iterator()::next; + } + +} diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java index 3a9d3ea4c505..fe6c7952fa30 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java @@ -41,7 +41,9 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; @@ -50,7 +52,12 @@ import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.util.SdkUserAgent; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; +import software.amazon.awssdk.profiles.ProfileProperty; import software.amazon.awssdk.utils.DateUtils; +import software.amazon.awssdk.utils.Pair; +import software.amazon.awssdk.utils.StringInputStream; public class InstanceProfileCredentialsProviderTest { private static final String TOKEN_RESOURCE_PATH = "/latest/api/token"; @@ -255,6 +262,49 @@ public void resolveCredentials_customProfileFileAndName_usesCorrectEndpoint() { } } + @Test + public void resolveCredentials_customProfileFileSupplierAndNameSettingEndpointOverride_usesCorrectEndpointFromSupplier() { + System.clearProperty(SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT.property()); + WireMockServer mockMetadataEndpoint_2 = new WireMockServer(WireMockConfiguration.options().dynamicPort()); + mockMetadataEndpoint_2.start(); + try { + String stubToken = "some-token"; + mockMetadataEndpoint_2.stubFor(put(urlPathEqualTo(TOKEN_RESOURCE_PATH)).willReturn(aResponse().withBody(stubToken))); + mockMetadataEndpoint_2.stubFor(get(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH)).willReturn(aResponse().withBody("some-profile"))); + mockMetadataEndpoint_2.stubFor(get(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH + "some-profile")).willReturn(aResponse().withBody(STUB_CREDENTIALS))); + + String mockServer2Endpoint = "http://localhost:" + mockMetadataEndpoint_2.port(); + + ProfileFile config = configFile("profile test", + Pair.of(ProfileProperty.EC2_METADATA_SERVICE_ENDPOINT, mockServer2Endpoint)); + + List profileFileList = Arrays.asList(credentialFile("test", "key1", "secret1"), + credentialFile("test", "key2", "secret2"), + credentialFile("test", "key3", "secret3")); + + InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider + .builder() + .profileFile(ProfileFileSupplier.aggregate(supply(profileFileList), () -> config)) + .profileName("test") + .build(); + + AwsCredentials awsCredentials1 = provider.resolveCredentials(); + + assertThat(awsCredentials1).isNotNull(); + + String userAgentHeader = "User-Agent"; + String userAgent = SdkUserAgent.create().userAgent(); + mockMetadataEndpoint_2.verify(putRequestedFor(urlPathEqualTo(TOKEN_RESOURCE_PATH)).withHeader(userAgentHeader, equalTo(userAgent))); + mockMetadataEndpoint_2.verify(getRequestedFor(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH)).withHeader(userAgentHeader, equalTo(userAgent))); + mockMetadataEndpoint_2.verify(getRequestedFor(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH + "some-profile")).withHeader(userAgentHeader, equalTo(userAgent))); + + // all requests should have gone to the second server, and none to the other one + mockMetadataEndpoint.verify(0, RequestPatternBuilder.allRequests()); + } finally { + mockMetadataEndpoint_2.stop(); + } + } + @Test public void resolveCredentials_doesNotFailIfImdsReturnsExpiredCredentials() { String credentialsResponse = @@ -448,4 +498,38 @@ public Instant instant() { return time; } } + + private static ProfileFileSupplier supply(Iterable iterable) { + return iterable.iterator()::next; + } + + private ProfileFile credentialFile(String credentialFile) { + return ProfileFile.builder() + .content(new StringInputStream(credentialFile)) + .type(ProfileFile.Type.CREDENTIALS) + .build(); + } + + private ProfileFile configFile(String credentialFile) { + return ProfileFile.builder() + .content(new StringInputStream(credentialFile)) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + } + + private ProfileFile credentialFile(String name, String accessKeyId, String secretAccessKey) { + String contents = String.format("[%s]\naws_access_key_id = %s\naws_secret_access_key = %s\n", + name, accessKeyId, secretAccessKey); + return credentialFile(contents); + } + + private ProfileFile configFile(String name, Pair... pairs) { + String values = Arrays.stream(pairs) + .map(pair -> String.format("%s=%s", pair.left(), pair.right())) + .collect(Collectors.joining(System.lineSeparator())); + String contents = String.format("[%s]\n%s", name, values); + + return configFile(contents); + } + } From 2f8f819f01fdcc0de6718b21a6525cfb3687eb95 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Mon, 12 Dec 2022 12:50:56 -0700 Subject: [PATCH 04/16] Update public APIs to Supplier instead of ProfileFileSupplier (#3607) --- .../DefaultCredentialsProvider.java | 17 ++++---- .../InstanceProfileCredentialsProvider.java | 21 +++++----- .../ProfileCredentialsProvider.java | 31 +++++++------- .../DefaultCredentialsProviderTest.java | 38 ++++++++++++++++- ...nstanceProfileCredentialsProviderTest.java | 42 +++++++++++++++++++ .../ProfileCredentialsProviderTest.java | 21 +++++++++- 6 files changed, 134 insertions(+), 36 deletions(-) diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.java index 4887deb2fac0..248a130c5864 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.auth.credentials; import java.util.Optional; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.internal.LazyAwsCredentialsProvider; import software.amazon.awssdk.profiles.ProfileFile; @@ -53,7 +54,7 @@ public final class DefaultCredentialsProvider private final LazyAwsCredentialsProvider providerChain; - private final ProfileFileSupplier profileFileSupplier; + private final Supplier profileFile; private final String profileName; @@ -65,7 +66,7 @@ public final class DefaultCredentialsProvider * @see #builder() */ private DefaultCredentialsProvider(Builder builder) { - this.profileFileSupplier = builder.profileFileSupplier; + this.profileFile = builder.profileFile; this.profileName = builder.profileName; this.reuseLastProviderEnabled = builder.reuseLastProviderEnabled; this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; @@ -93,7 +94,7 @@ private static LazyAwsCredentialsProvider createChain(Builder builder) { EnvironmentVariableCredentialsProvider.create(), WebIdentityTokenFileCredentialsProvider.create(), ProfileCredentialsProvider.builder() - .profileFile(builder.profileFileSupplier) + .profileFile(builder.profileFile) .profileName(builder.profileName) .build(), ContainerCredentialsProvider.builder() @@ -101,7 +102,7 @@ private static LazyAwsCredentialsProvider createChain(Builder builder) { .build(), InstanceProfileCredentialsProvider.builder() .asyncCredentialUpdateEnabled(asyncCredentialUpdateEnabled) - .profileFile(builder.profileFileSupplier) + .profileFile(builder.profileFile) .profileName(builder.profileName) .build() }; @@ -146,7 +147,7 @@ public Builder toBuilder() { * Configuration that defines the {@link DefaultCredentialsProvider}'s behavior. */ public static final class Builder implements CopyableBuilder { - private ProfileFileSupplier profileFileSupplier; + private Supplier profileFile; private String profileName; private Boolean reuseLastProviderEnabled = true; private Boolean asyncCredentialUpdateEnabled = false; @@ -158,7 +159,7 @@ private Builder() { } private Builder(DefaultCredentialsProvider credentialsProvider) { - this.profileFileSupplier = credentialsProvider.profileFileSupplier; + this.profileFile = credentialsProvider.profileFile; this.profileName = credentialsProvider.profileName; this.reuseLastProviderEnabled = credentialsProvider.reuseLastProviderEnabled; this.asyncCredentialUpdateEnabled = credentialsProvider.asyncCredentialUpdateEnabled; @@ -170,8 +171,8 @@ public Builder profileFile(ProfileFile profileFile) { .orElse(null)); } - public Builder profileFile(ProfileFileSupplier profileFileSupplier) { - this.profileFileSupplier = profileFileSupplier; + public Builder profileFile(Supplier profileFileSupplier) { + this.profileFile = profileFileSupplier; return this; } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java index eb7e0f97b7bf..3fd3e7368c19 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java @@ -27,6 +27,7 @@ import java.util.Collections; import java.util.Map; import java.util.Optional; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; import software.amazon.awssdk.auth.credentials.internal.Ec2MetadataConfigProvider; @@ -79,7 +80,7 @@ public final class InstanceProfileCredentialsProvider private final String asyncThreadName; - private final ProfileFileSupplier profileFileSupplier; + private final Supplier profileFile; private final String profileName; @@ -91,13 +92,13 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) { this.endpoint = builder.endpoint; this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; this.asyncThreadName = builder.asyncThreadName; - this.profileFileSupplier = builder.profileFileSupplier; + this.profileFile = builder.profileFile; this.profileName = builder.profileName; this.httpCredentialsLoader = HttpCredentialsLoader.create(); this.configProvider = Ec2MetadataConfigProvider.builder() - .profileFile(builder.profileFileSupplier) + .profileFile(builder.profileFile) .profileName(builder.profileName) .build(); @@ -282,7 +283,7 @@ public interface Builder extends HttpCredentialsProvider.BuilderBy default, {@link ProfileFile#defaultProfileFile()} is used. * - * @see #profileFile(ProfileFileSupplier) + * @see #profileFile(Supplier) */ Builder profileFile(ProfileFile profileFile); @@ -292,7 +293,7 @@ public interface Builder extends HttpCredentialsProvider.Builder profileFileSupplier); /** * Configure the profile name used for loading IMDS-related configuration, like the endpoint mode (IPv4 vs IPv6). @@ -314,7 +315,7 @@ static final class BuilderImpl implements Builder { private String endpoint; private Boolean asyncCredentialUpdateEnabled; private String asyncThreadName; - private ProfileFileSupplier profileFileSupplier; + private Supplier profileFile; private String profileName; private BuilderImpl() { @@ -326,7 +327,7 @@ private BuilderImpl(InstanceProfileCredentialsProvider provider) { this.endpoint = provider.endpoint; this.asyncCredentialUpdateEnabled = provider.asyncCredentialUpdateEnabled; this.asyncThreadName = provider.asyncThreadName; - this.profileFileSupplier = provider.profileFileSupplier; + this.profileFile = provider.profileFile; this.profileName = provider.profileName; } @@ -377,12 +378,12 @@ public void setProfileFile(ProfileFile profileFile) { } @Override - public Builder profileFile(ProfileFileSupplier profileFileSupplier) { - this.profileFileSupplier = profileFileSupplier; + public Builder profileFile(Supplier profileFileSupplier) { + this.profileFile = profileFileSupplier; return this; } - public void setProfileFile(ProfileFileSupplier profileFileSupplier) { + public void setProfileFile(Supplier profileFileSupplier) { profileFile(profileFileSupplier); } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java index 34c0e2352a7b..b870b4f2b814 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java @@ -51,7 +51,7 @@ public final class ProfileCredentialsProvider private AwsCredentialsProvider credentialsProvider; private final RuntimeException loadException; - private final ProfileFileSupplier profileFileSupplier; + private final Supplier profileFile; private volatile ProfileFile currentProfileFile; private final String profileName; private final Supplier defaultProfileFileLoader; @@ -64,15 +64,14 @@ private ProfileCredentialsProvider(BuilderImpl builder) { RuntimeException thrownException = null; String selectedProfileName = null; - ProfileFileSupplier selectedProfileSupplier = null; + Supplier selectedProfileSupplier = null; try { selectedProfileName = Optional.ofNullable(builder.profileName) .orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow); - selectedProfileSupplier = Optional.ofNullable(builder.profileFileSupplier) - .orElseGet(() -> ProfileFileSupplier - .fixedProfileFile(builder.defaultProfileFileLoader.get())); + selectedProfileSupplier = Optional.ofNullable(builder.profileFile) + .orElseGet(() -> builder.defaultProfileFileLoader); } catch (RuntimeException e) { // If we couldn't load the credentials provider for some reason, save an exception describing why. This exception @@ -83,7 +82,7 @@ private ProfileCredentialsProvider(BuilderImpl builder) { this.loadException = thrownException; this.profileName = selectedProfileName; - this.profileFileSupplier = selectedProfileSupplier; + this.profileFile = selectedProfileSupplier; } /** @@ -131,7 +130,7 @@ private void handleProfileFileReload(ProfileFile profileFile) { } private ProfileFile refreshProfileFile() { - return profileFileSupplier.get(); + return profileFile.get(); } private boolean isNewProfileFile(ProfileFile profileFile) { @@ -148,7 +147,9 @@ public String toString() { @Override public void close() { - profileFileSupplier.close(); + if (profileFile instanceof SdkAutoCloseable) { + ((SdkAutoCloseable) profileFile).close(); + } // The delegate credentials provider may be closeable (eg. if it's an STS credentials provider). In this case, we should // clean it up when this credentials provider is closed. IoUtils.closeIfCloseable(credentialsProvider, null); @@ -178,7 +179,7 @@ public interface Builder extends CopyableBuilder profileFileSupplier); /** * Define the name of the profile that should be used by this credentials provider. By default, the value in @@ -210,7 +211,7 @@ public interface Builder extends CopyableBuilder profileFile; private String profileName; private Supplier defaultProfileFileLoader = ProfileFile::defaultProfileFile; @@ -220,7 +221,7 @@ static final class BuilderImpl implements Builder { BuilderImpl(ProfileCredentialsProvider provider) { this.profileName = provider.profileName; this.defaultProfileFileLoader = provider.defaultProfileFileLoader; - this.profileFileSupplier = provider.profileFileSupplier; + this.profileFile = provider.profileFile; } @Override @@ -240,12 +241,12 @@ public Builder profileFile(Consumer profileFile) { } @Override - public Builder profileFile(ProfileFileSupplier profileFileSupplier) { - this.profileFileSupplier = profileFileSupplier; + public Builder profileFile(Supplier profileFileSupplier) { + this.profileFile = profileFileSupplier; return this; } - public void setProfileFile(ProfileFileSupplier supplier) { + public void setProfileFile(Supplier supplier) { profileFile(supplier); } diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProviderTest.java index a3f12a605c45..1c1c44bd140c 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/DefaultCredentialsProviderTest.java @@ -19,6 +19,7 @@ import java.util.Arrays; import java.util.List; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileFileSupplier; @@ -27,7 +28,7 @@ class DefaultCredentialsProviderTest { @Test - void resolveCredentials_requestFallsIntoProfileCredentialsProviderWithProfileFile_returnsCredentials() { + void resolveCredentials_ProfileCredentialsProviderWithProfileFile_returnsCredentials() { DefaultCredentialsProvider provider = DefaultCredentialsProvider .builder() .profileFile(credentialFile("test", "access", "secret")) @@ -41,7 +42,7 @@ void resolveCredentials_requestFallsIntoProfileCredentialsProviderWithProfileFil } @Test - void resolveCredentials_requestFallsIntoProfileCredentialsProviderWithProfileFileSupplier_returnsCredentials() { + void resolveCredentials_ProfileCredentialsProviderWithProfileFileSupplier_resolvesCredentialsPerCall() { List profileFileList = Arrays.asList(credentialFile("test", "access", "secret"), credentialFile("test", "modified", "update")); ProfileFileSupplier profileFileSupplier = supply(profileFileList); @@ -63,6 +64,39 @@ void resolveCredentials_requestFallsIntoProfileCredentialsProviderWithProfileFil }); } + @Test + void resolveCredentials_ProfileCredentialsProviderWithProfileFileSupplier_returnsCredentials() { + ProfileFile profileFile = credentialFile("test", "access", "secret"); + ProfileFileSupplier profileFileSupplier = ProfileFileSupplier.fixedProfileFile(profileFile); + + DefaultCredentialsProvider provider = DefaultCredentialsProvider + .builder() + .profileFile(profileFileSupplier) + .profileName("test") + .build(); + + assertThat(provider.resolveCredentials()).satisfies(awsCredentials -> { + assertThat(awsCredentials.accessKeyId()).isEqualTo("access"); + assertThat(awsCredentials.secretAccessKey()).isEqualTo("secret"); + }); + } + + @Test + void resolveCredentials_ProfileCredentialsProviderWithSupplierProfileFile_returnsCredentials() { + Supplier supplier = () -> credentialFile("test", "access", "secret"); + + DefaultCredentialsProvider provider = DefaultCredentialsProvider + .builder() + .profileFile(supplier) + .profileName("test") + .build(); + + assertThat(provider.resolveCredentials()).satisfies(awsCredentials -> { + assertThat(awsCredentials.accessKeyId()).isEqualTo("access"); + assertThat(awsCredentials.secretAccessKey()).isEqualTo("secret"); + }); + } + private ProfileFile credentialFile(String credentialFile) { return ProfileFile.builder() .content(new StringInputStream(credentialFile)) diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java index fe6c7952fa30..0f32ed3c17bd 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java @@ -43,6 +43,7 @@ import java.time.ZoneOffset; import java.util.Arrays; import java.util.List; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.junit.AfterClass; import org.junit.Before; @@ -305,6 +306,47 @@ public void resolveCredentials_customProfileFileSupplierAndNameSettingEndpointOv } } + @Test + public void resolveCredentials_customSupplierProfileFileAndNameSettingEndpointOverride_usesCorrectEndpointFromSupplier() { + System.clearProperty(SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT.property()); + WireMockServer mockMetadataEndpoint_2 = new WireMockServer(WireMockConfiguration.options().dynamicPort()); + mockMetadataEndpoint_2.start(); + try { + String stubToken = "some-token"; + mockMetadataEndpoint_2.stubFor(put(urlPathEqualTo(TOKEN_RESOURCE_PATH)).willReturn(aResponse().withBody(stubToken))); + mockMetadataEndpoint_2.stubFor(get(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH)).willReturn(aResponse().withBody("some-profile"))); + mockMetadataEndpoint_2.stubFor(get(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH + "some-profile")).willReturn(aResponse().withBody(STUB_CREDENTIALS))); + + String mockServer2Endpoint = "http://localhost:" + mockMetadataEndpoint_2.port(); + + ProfileFile config = configFile("profile test", + Pair.of(ProfileProperty.EC2_METADATA_SERVICE_ENDPOINT, mockServer2Endpoint)); + + Supplier supplier = () -> config; + + InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider + .builder() + .profileFile(supplier) + .profileName("test") + .build(); + + AwsCredentials awsCredentials1 = provider.resolveCredentials(); + + assertThat(awsCredentials1).isNotNull(); + + String userAgentHeader = "User-Agent"; + String userAgent = SdkUserAgent.create().userAgent(); + mockMetadataEndpoint_2.verify(putRequestedFor(urlPathEqualTo(TOKEN_RESOURCE_PATH)).withHeader(userAgentHeader, equalTo(userAgent))); + mockMetadataEndpoint_2.verify(getRequestedFor(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH)).withHeader(userAgentHeader, equalTo(userAgent))); + mockMetadataEndpoint_2.verify(getRequestedFor(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH + "some-profile")).withHeader(userAgentHeader, equalTo(userAgent))); + + // all requests should have gone to the second server, and none to the other one + mockMetadataEndpoint.verify(0, RequestPatternBuilder.allRequests()); + } finally { + mockMetadataEndpoint_2.stop(); + } + } + @Test public void resolveCredentials_doesNotFailIfImdsReturnsExpiredCredentials() { String credentialsResponse = diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java index b7eea2daff67..17b9b24ae092 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java @@ -24,6 +24,7 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.util.function.Supplier; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -203,6 +204,23 @@ void resolveCredentials_presentProfileFileSupplier_returnsCredentials() { }); } + @Test + void resolveCredentials_presentSupplierProfileFile_returnsCredentials() { + Supplier supplier = () -> profileFile("[default]\naws_access_key_id = defaultAccessKey\n" + + "aws_secret_access_key = defaultSecretAccessKey\n"); + + ProfileCredentialsProvider provider = + ProfileCredentialsProvider.builder() + .profileFile(supplier) + .profileName("default") + .build(); + + assertThat(provider.resolveCredentials()).satisfies(credentials -> { + assertThat(credentials.accessKeyId()).isEqualTo("defaultAccessKey"); + assertThat(credentials.secretAccessKey()).isEqualTo("defaultSecretAccessKey"); + }); + } + @Test void create_noProfileName_returnsProfileCredentialsProviderToResolveWithDefaults() { ProfileCredentialsProvider provider = ProfileCredentialsProvider.create(); @@ -222,7 +240,8 @@ void create_givenProfileName_returnsProfileCredentialsProviderToResolveForGivenN @Test void toString_anyProfileCredentialsProviderAfterResolvingCredentialsFileDoesExists_returnsProfileFile() { ProfileCredentialsProvider provider = new ProfileCredentialsProvider.BuilderImpl() - .defaultProfileFileLoader(() -> profileFile("[default]\naws_access_key_id = %s\naws_secret_access_key = %s\n")) + .defaultProfileFileLoader(() -> profileFile("[default]\naws_access_key_id = not-set\n" + + "aws_secret_access_key = not-set\n")) .build(); provider.resolveCredentials(); String toString = provider.toString(); From 68ff756c2814b44227805127be05f334ef1df9c4 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Tue, 20 Dec 2022 07:29:13 -0700 Subject: [PATCH 05/16] ProfileTokenProvider Reload ProfileFile (#3608) * Updated ProfileTokenProvider * Updated tests, do not explicitly swallow exceptions * ProfileTokenProviderLoader can use Supplier internally * Simplified ProfileTokenProviderLoader API; implemented synchronized block --- .../credentials/ProfileTokenProvider.java | 53 ++++++------ .../internal/ProfileTokenProviderLoader.java | 80 ++++++++++++++----- .../credentials/ProfileTokenProviderTest.java | 35 ++++++-- .../common/SsoOidcTokenRefreshTestBase.java | 9 +-- .../ProfileTokenProviderLoaderTest.java | 38 +++++---- 5 files changed, 141 insertions(+), 74 deletions(-) diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProvider.java index 698955eb84e5..bd15f28ff2e6 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProvider.java @@ -40,53 +40,41 @@ public final class ProfileTokenProvider implements SdkTokenProvider, SdkAutoClos private final SdkTokenProvider tokenProvider; private final RuntimeException loadException; - private final ProfileFile profileFile; private final String profileName; /** * @see #builder() */ private ProfileTokenProvider(BuilderImpl builder) { - SdkTokenProvider tokenProvider = null; - RuntimeException loadException = null; - ProfileFile profileFile = null; - String profileName = null; + SdkTokenProvider sdkTokenProvider = null; + RuntimeException thrownException = null; + Supplier selectedProfileFile = null; + String selectedProfileName = null; try { - profileName = builder.profileName != null ? builder.profileName - : ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow(); + selectedProfileName = Optional.ofNullable(builder.profileName) + .orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow); // Load the profiles file - profileFile = Optional.ofNullable(builder.profileFile) - .orElse(builder.defaultProfileFileLoader) - .get(); + selectedProfileFile = Optional.ofNullable(builder.profileFile) + .orElse(builder.defaultProfileFileLoader); // Load the profile and token provider - String finalProfileName = profileName; - ProfileFile finalProfileFile = profileFile; - tokenProvider = - profileFile.profile(profileName) - .flatMap(p -> new ProfileTokenProviderLoader(finalProfileFile, p).tokenProvider()) - .orElseThrow(() -> { - String errorMessage = String.format("Profile file contained no information for " + - "profile '%s': %s", finalProfileName, finalProfileFile); - return SdkClientException.builder().message(errorMessage).build(); - }); + sdkTokenProvider = createTokenProvider(selectedProfileFile, selectedProfileName); + } catch (RuntimeException e) { // If we couldn't load the provider for some reason, save an exception describing why. - loadException = e; + thrownException = e; } - if (loadException != null) { - this.loadException = loadException; + if (thrownException != null) { + this.loadException = thrownException; this.tokenProvider = null; - this.profileFile = null; this.profileName = null; } else { this.loadException = null; - this.tokenProvider = tokenProvider; - this.profileFile = profileFile; - this.profileName = profileName; + this.tokenProvider = sdkTokenProvider; + this.profileName = selectedProfileName; } } @@ -127,7 +115,6 @@ public SdkToken resolveToken() { public String toString() { return ToString.builder("ProfileTokenProvider") .add("profileName", profileName) - .add("profileFile", profileFile) .build(); } @@ -137,6 +124,16 @@ public void close() { IoUtils.closeIfCloseable(tokenProvider, null); } + private SdkTokenProvider createTokenProvider(Supplier profileFile, String profileName) { + return new ProfileTokenProviderLoader(profileFile, profileName) + .tokenProvider() + .orElseThrow(() -> { + String errorMessage = String.format("Profile file contained no information for " + + "profile '%s'", profileName); + return SdkClientException.builder().message(errorMessage).build(); + }); + } + /** * A builder for creating a custom {@link ProfileTokenProvider}. */ diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/token/internal/ProfileTokenProviderLoader.java b/core/auth/src/main/java/software/amazon/awssdk/auth/token/internal/ProfileTokenProviderLoader.java index 87e00046947e..4545477d9fce 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/token/internal/ProfileTokenProviderLoader.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/token/internal/ProfileTokenProviderLoader.java @@ -17,15 +17,19 @@ import java.lang.reflect.InvocationTargetException; import java.util.Arrays; +import java.util.Objects; import java.util.Optional; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.token.credentials.ChildProfileTokenProviderFactory; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.internal.util.ClassLoaderHelper; import software.amazon.awssdk.profiles.Profile; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileProperty; import software.amazon.awssdk.profiles.internal.ProfileSection; +import software.amazon.awssdk.utils.Lazy; import software.amazon.awssdk.utils.Validate; /** @@ -36,12 +40,17 @@ public final class ProfileTokenProviderLoader { private static final String SSO_OIDC_TOKEN_PROVIDER_FACTORY = "software.amazon.awssdk.services.ssooidc.SsoOidcProfileTokenProviderFactory"; - private final Profile profile; - private final ProfileFile profileFile; + private final Supplier profileFileSupplier; + private final String profileName; + private volatile ProfileFile currentProfileFile; + private volatile SdkTokenProvider currentTokenProvider; - public ProfileTokenProviderLoader(ProfileFile profileFile, Profile profile) { - this.profile = Validate.paramNotNull(profile, "profile"); - this.profileFile = Validate.paramNotNull(profileFile, "profileFile"); + private final Lazy factory; + + public ProfileTokenProviderLoader(Supplier profileFile, String profileName) { + this.profileFileSupplier = Validate.paramNotNull(profileFile, "profileFile"); + this.profileName = Validate.paramNotNull(profileName, "profileName"); + this.factory = new Lazy<>(this::ssoTokenProviderFactory); } /** @@ -55,19 +64,53 @@ public Optional tokenProvider() { * Create the SSO credentials provider based on the related profile properties. */ private SdkTokenProvider ssoProfileCredentialsProvider() { + return () -> ssoProfileCredentialsProvider(profileFileSupplier, profileName).resolveToken(); + } + + private SdkTokenProvider ssoProfileCredentialsProvider(ProfileFile profileFile, Profile profile) { + String profileSsoSectionName = profileSsoSectionName(profile); + Profile ssoProfile = ssoProfile(profileFile, profileSsoSectionName); + + validateRequiredProperties(ssoProfile, ProfileProperty.SSO_REGION, ProfileProperty.SSO_START_URL); + + return factory.getValue().create(profileFile, profile); + } - String profileSsoSectionName = profile.property(ProfileSection.SSO_SESSION.getPropertyKeyName()) - .orElseThrow(() -> new IllegalArgumentException( - "Profile " + profile.name() + " does not have sso_session property")); + private SdkTokenProvider ssoProfileCredentialsProvider(Supplier profileFile, String profileName) { + ProfileFile profileFileInstance = profileFile.get(); + if (!Objects.equals(profileFileInstance, currentProfileFile)) { + synchronized (this) { + if (!Objects.equals(profileFileInstance, currentProfileFile)) { + Profile profileInstance = resolveProfile(profileFileInstance, profileName); + currentProfileFile = profileFileInstance; + currentTokenProvider = ssoProfileCredentialsProvider(profileFileInstance, profileInstance); + } + } + } + + return currentTokenProvider; + } - Profile ssoProfile = profileFile.getSection(ProfileSection.SSO_SESSION.getSectionTitle(), profileSsoSectionName) - .orElseThrow(() -> new IllegalArgumentException( - "Sso-session section not found with sso-session title " + profileSsoSectionName)); + private Profile resolveProfile(ProfileFile profileFile, String profileName) { + return profileFile.profile(profileName) + .orElseThrow(() -> { + String errorMessage = String.format("Profile file contained no information for profile '%s': %s", + profileName, profileFile); + return SdkClientException.builder().message(errorMessage).build(); + }); + } + + private String profileSsoSectionName(Profile profile) { + return Optional.ofNullable(profile) + .flatMap(p -> p.property(ProfileSection.SSO_SESSION.getPropertyKeyName())) + .orElseThrow(() -> new IllegalArgumentException( + "Profile " + profileName + " does not have sso_session property")); + } - validateRequiredProperties(ssoProfile, - ProfileProperty.SSO_REGION, - ProfileProperty.SSO_START_URL); - return ssoTokenProviderFactory().create(profileFile, profile); + private Profile ssoProfile(ProfileFile profileFile, String profileSsoSectionName) { + return profileFile.getSection(ProfileSection.SSO_SESSION.getSectionTitle(), profileSsoSectionName) + .orElseThrow(() -> new IllegalArgumentException( + "Sso-session section not found with sso-session title " + profileSsoSectionName)); } /** @@ -76,7 +119,8 @@ private SdkTokenProvider ssoProfileCredentialsProvider() { private void validateRequiredProperties(Profile ssoProfile, String... requiredProperties) { Arrays.stream(requiredProperties) .forEach(p -> Validate.isTrue(ssoProfile.properties().containsKey(p), - "Property '%s' was not configured for profile '%s'.", p, this.profile.name())); + "Property '%s' was not configured for profile '%s'.", + p, profileName)); } /** @@ -88,10 +132,10 @@ private ChildProfileTokenProviderFactory ssoTokenProviderFactory() { getClass()); return (ChildProfileTokenProviderFactory) ssoOidcTokenProviderFactory.getConstructor().newInstance(); } catch (ClassNotFoundException e) { - throw new IllegalStateException("To use SSO OIDC related properties in the '" + profile.name() + "' profile, " + throw new IllegalStateException("To use SSO OIDC related properties in the '" + profileName + "' profile, " + "the 'ssooidc' service module must be on the class path.", e); } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { - throw new IllegalStateException("Failed to create the '" + profile.name() + "' token provider factory.", e); + throw new IllegalStateException("Failed to create the '%s" + profileName + "' token provider factory.", e); } } } diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProviderTest.java index a03cd87d7cf4..f5324dd43771 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProviderTest.java @@ -17,16 +17,17 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; -import software.amazon.awssdk.auth.token.credentials.ProfileTokenProvider; +import org.mockito.Mockito; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.utils.StringInputStream; -public class ProfileTokenProviderTest { +class ProfileTokenProviderTest { @Test - public void missingProfileFile_throwsException() { + void missingProfileFile_throwsException() { ProfileTokenProvider provider = new ProfileTokenProvider.BuilderImpl() .defaultProfileFileLoader(() -> ProfileFile.builder() @@ -39,7 +40,7 @@ public void missingProfileFile_throwsException() { } @Test - public void emptyProfileFile_throwsException() { + void emptyProfileFile_throwsException() { ProfileTokenProvider provider = new ProfileTokenProvider.BuilderImpl() .defaultProfileFileLoader(() -> ProfileFile.builder() @@ -52,7 +53,7 @@ public void emptyProfileFile_throwsException() { } @Test - public void missingProfile_throwsException() { + void missingProfile_throwsException() { ProfileFile file = profileFile("[default]\n" + "aws_access_key_id = defaultAccessKey\n" + "aws_secret_access_key = defaultSecretAccessKey"); @@ -64,7 +65,7 @@ public void missingProfile_throwsException() { } @Test - public void compatibleProfileSettings_callsLoader() { + void compatibleProfileSettings_callsLoader() { ProfileFile file = profileFile("[default]"); ProfileTokenProvider provider = @@ -73,6 +74,28 @@ public void compatibleProfileSettings_callsLoader() { assertThatThrownBy(provider::resolveToken).hasMessageContaining("does not have sso_session property"); } + @Test + void resolveToken_profileFileSupplier_suppliesObjectPerCall() { + ProfileFile file1 = profileFile("[profile sso]\n" + + "aws_access_key_id = defaultAccessKey\n" + + "aws_secret_access_key = defaultSecretAccessKey\n" + + "sso_session = xyz"); + ProfileFile file2 = profileFile("[profile sso]\n" + + "aws_access_key_id = modifiedAccessKey\n" + + "aws_secret_access_key = modifiedSecretAccessKey\n" + + "sso_session = xyz"); + Supplier supplier = Mockito.mock(Supplier.class); + + ProfileTokenProvider provider = + ProfileTokenProvider.builder().profileFile(supplier).profileName("sso").build(); + + Mockito.when(supplier.get()).thenReturn(file1, file2); + assertThatThrownBy(provider::resolveToken).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(provider::resolveToken).isInstanceOf(IllegalArgumentException.class); + + Mockito.verify(supplier, Mockito.times(2)).get(); + } + private ProfileFile profileFile(String string) { return ProfileFile.builder() .content(new StringInputStream(string)) diff --git a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/common/SsoOidcTokenRefreshTestBase.java b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/common/SsoOidcTokenRefreshTestBase.java index d925d448b4f1..4d43bd154426 100644 --- a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/common/SsoOidcTokenRefreshTestBase.java +++ b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/common/SsoOidcTokenRefreshTestBase.java @@ -20,7 +20,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static software.amazon.awssdk.services.ssooidc.internal.SsoOidcTokenProviderTest.START_URL; import static software.amazon.awssdk.services.ssooidc.internal.SsoOidcTokenProviderTest.deriveCacheKey; import static software.amazon.awssdk.utils.UserHomeDirectoryUtils.userHomeDirectory; @@ -41,7 +40,6 @@ import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.auth.token.internal.ProfileTokenProviderLoader; import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.profiles.Profile; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.protocols.jsoncore.JsonNode; import software.amazon.awssdk.services.ssooidc.SsoOidcClient; @@ -59,7 +57,7 @@ public abstract class SsoOidcTokenRefreshTestBase { protected boolean shouldMockServiceClient; protected SsoOidcClient ssoOidcClient; protected String testStartUrl; - protected String testSessionName = "sso-prod";; + protected String testSessionName = "sso-prod"; protected String baseTokenResourceFile; protected ProfileTokenProviderLoader profileTokenProviderLoader; @@ -98,12 +96,11 @@ protected void initializeProfileProperties() { "sso_region=us-east-1\n" + "sso_start_url=" + testStartUrl + "\n"; - ProfileFile profiles = ProfileFile.builder() + ProfileFile profile = ProfileFile.builder() .content(new StringInputStream(profileContent)) .type(ProfileFile.Type.CONFIGURATION) .build(); - Optional profile = profiles.profile("sso-refresh"); - profileTokenProviderLoader = new ProfileTokenProviderLoader(profiles, profile.get()); + profileTokenProviderLoader = new ProfileTokenProviderLoader(() -> profile, "sso-refresh"); } @Test diff --git a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/ssooidc/ProfileTokenProviderLoaderTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/ssooidc/ProfileTokenProviderLoaderTest.java index 7a7540c8081e..be3e93eef82c 100644 --- a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/ssooidc/ProfileTokenProviderLoaderTest.java +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/ssooidc/ProfileTokenProviderLoaderTest.java @@ -17,10 +17,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.HashMap; import java.util.Optional; +import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -29,45 +30,50 @@ import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.auth.token.internal.ProfileTokenProviderLoader; import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.profiles.Profile; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.utils.StringInputStream; -public class ProfileTokenProviderLoaderTest { +class ProfileTokenProviderLoaderTest { @Test - public void noProfile_throwsException() { - assertThatThrownBy(() -> new ProfileTokenProviderLoader(ProfileFile.defaultProfileFile(), null)) - .hasMessageContaining("profile must not be null"); + void profileTokenProviderLoader_noProfileFileSupplier_throwsException() { + assertThatThrownBy(() -> new ProfileTokenProviderLoader(null, "sso'")) + .hasMessageContaining("profileFile must not be null"); } @Test - public void noProfileFile_throwsException() { - assertThatThrownBy(() -> new ProfileTokenProviderLoader(null, Profile.builder().name("sso").properties(new HashMap<>()).build())) - .hasMessageContaining("profileFile must not be null"); + void profileTokenProviderLoader_noProfileName_throwsException() { + assertThatThrownBy(() -> new ProfileTokenProviderLoader(ProfileFile::defaultProfileFile, null)) + .hasMessageContaining("profileName must not be null"); } @ParameterizedTest @MethodSource("ssoErrorValues") - public void incorrectSsoProperties_throwsException(String profileContent, String msg) { + void incorrectSsoProperties_supplier_delaysThrowingExceptionUntilResolvingToken(String profileContent, String msg) { ProfileFile profileFile = configFile(profileContent); + Supplier supplier = () -> profileFile; + + ProfileTokenProviderLoader providerLoader = new ProfileTokenProviderLoader(supplier, "sso"); + + assertThatNoException().isThrownBy(providerLoader::tokenProvider); - assertThat(profileFile.profile("sso")).hasValueSatisfying(profile -> { - ProfileTokenProviderLoader providerLoader = new ProfileTokenProviderLoader(profileFile, profile); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(providerLoader::tokenProvider).withMessageContaining(msg); + assertThat(providerLoader.tokenProvider()).satisfies(tokenProviderOptional -> { + assertThat(tokenProviderOptional).isPresent().get().satisfies(tokenProvider-> { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(tokenProvider::resolveToken) + .withMessageContaining(msg); + }); }); } @Test - public void correctSsoProperties_createsTokenProvider() { + void correctSsoProperties_createsTokenProvider() { String profileContent = "[profile sso]\n" + "sso_session=admin\n" + "[sso-session admin]\n" + "sso_region=us-east-1\n" + "sso_start_url=https://d-abc123.awsapps.com/start\n"; - Optional ssoProfile = configFile(profileContent).profile("sso"); - ProfileTokenProviderLoader providerLoader = new ProfileTokenProviderLoader(configFile(profileContent), ssoProfile.get()); + ProfileTokenProviderLoader providerLoader = new ProfileTokenProviderLoader(() -> configFile(profileContent), "sso"); Optional tokenProvider = providerLoader.tokenProvider(); assertThat(tokenProvider).isPresent(); assertThatThrownBy(() -> tokenProvider.get().resolveToken()) From 6f2bafdd88bcc1a074866972443da72df4a478df Mon Sep 17 00:00:00 2001 From: David Negrete Date: Wed, 21 Dec 2022 17:33:31 -0700 Subject: [PATCH 06/16] Use synchronized block (#3646) --- .../auth/credentials/ProfileCredentialsProvider.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java index b870b4f2b814..c2eb3424bf55 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java @@ -49,7 +49,7 @@ public final class ProfileCredentialsProvider SdkAutoCloseable, ToCopyableBuilder { - private AwsCredentialsProvider credentialsProvider; + private volatile AwsCredentialsProvider credentialsProvider; private final RuntimeException loadException; private final Supplier profileFile; private volatile ProfileFile currentProfileFile; @@ -118,8 +118,12 @@ public AwsCredentials resolveCredentials() { ProfileFile cachedOrRefreshedProfileFile = refreshProfileFile(); if (isNewProfileFile(cachedOrRefreshedProfileFile)) { - currentProfileFile = cachedOrRefreshedProfileFile; - handleProfileFileReload(cachedOrRefreshedProfileFile); + synchronized (this) { + if (isNewProfileFile(cachedOrRefreshedProfileFile)) { + currentProfileFile = cachedOrRefreshedProfileFile; + handleProfileFileReload(cachedOrRefreshedProfileFile); + } + } } return credentialsProvider.resolveCredentials(); From b88ea402934990dfcafcc31ceb21934554aeded2 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Fri, 6 Jan 2023 11:31:18 -0700 Subject: [PATCH 07/16] S3 support classes take Supplier (#3653) * S3 support classes take Supplier * Review comments --- .../awssdk/services/s3/S3Configuration.java | 56 +++++++++++++++---- .../DisableMultiRegionProviderChain.java | 9 ++- .../ProfileDisableMultiRegionProvider.java | 4 ++ .../ProfileUseArnRegionProvider.java | 6 +- .../UseArnRegionProviderChain.java | 8 ++- .../services/s3/S3ConfigurationTest.java | 28 ++++++++++ .../DisableMultiRegionProviderChainTest.java | 28 +++++++--- ...ProfileDisableMultiRegionProviderTest.java | 15 +++++ .../ProfileUseArnRegionProviderTest.java | 14 +++++ .../UseArnRegionProviderChainTest.java | 30 ++++++++-- 10 files changed, 170 insertions(+), 28 deletions(-) diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java index 74a2fd11523b..41ad17267030 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java @@ -15,12 +15,15 @@ package software.amazon.awssdk.services.s3; +import java.util.Optional; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.Immutable; import software.amazon.awssdk.annotations.NotThreadSafe; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.core.ServiceConfiguration; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; import software.amazon.awssdk.services.s3.internal.FieldWithDefault; import software.amazon.awssdk.services.s3.internal.settingproviders.DisableMultiRegionProviderChain; @@ -66,9 +69,9 @@ public final class S3Configuration implements ServiceConfiguration, ToCopyableBu private final FieldWithDefault dualstackEnabled; private final FieldWithDefault checksumValidationEnabled; private final FieldWithDefault chunkedEncodingEnabled; - private final FieldWithDefault useArnRegionEnabled; - private final FieldWithDefault multiRegionEnabled; - private final FieldWithDefault profileFile; + private final Boolean useArnRegionEnabled; + private final Boolean multiRegionEnabled; + private final FieldWithDefault> profileFile; private final FieldWithDefault profileName; private S3Configuration(DefaultS3ServiceConfigurationBuilder builder) { @@ -78,11 +81,11 @@ private S3Configuration(DefaultS3ServiceConfigurationBuilder builder) { this.checksumValidationEnabled = FieldWithDefault.create(builder.checksumValidationEnabled, DEFAULT_CHECKSUM_VALIDATION_ENABLED); this.chunkedEncodingEnabled = FieldWithDefault.create(builder.chunkedEncodingEnabled, DEFAULT_CHUNKED_ENCODING_ENABLED); - this.profileFile = FieldWithDefault.createLazy(builder.profileFile, ProfileFile::defaultProfileFile); + this.profileFile = FieldWithDefault.create(builder.profileFile, ProfileFile::defaultProfileFile); this.profileName = FieldWithDefault.create(builder.profileName, ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow()); - this.useArnRegionEnabled = FieldWithDefault.createLazy(builder.useArnRegionEnabled, this::resolveUseArnRegionEnabled); - this.multiRegionEnabled = FieldWithDefault.createLazy(builder.multiRegionEnabled, this::resolveMultiRegionEnabled); + this.useArnRegionEnabled = builder.useArnRegionEnabled; + this.multiRegionEnabled = builder.multiRegionEnabled; if (accelerateModeEnabled() && pathStyleAccessEnabled()) { throw new IllegalArgumentException("Accelerate mode cannot be used with path style addressing"); @@ -189,7 +192,8 @@ public boolean chunkedEncodingEnabled() { * @return True if a different region in the ARN can be used. */ public boolean useArnRegionEnabled() { - return useArnRegionEnabled.value(); + return Optional.ofNullable(useArnRegionEnabled) + .orElseGet(this::resolveUseArnRegionEnabled); } /** @@ -198,19 +202,20 @@ public boolean useArnRegionEnabled() { * @return True if multi-region ARNs is enabled. */ public boolean multiRegionEnabled() { - return multiRegionEnabled.value(); + return Optional.ofNullable(multiRegionEnabled) + .orElseGet(this::resolveMultiRegionEnabled); } @Override public Builder toBuilder() { return builder() .dualstackEnabled(dualstackEnabled.valueOrNullIfDefault()) - .multiRegionEnabled(multiRegionEnabled.valueOrNullIfDefault()) + .multiRegionEnabled(multiRegionEnabled) .accelerateModeEnabled(accelerateModeEnabled.valueOrNullIfDefault()) .pathStyleAccessEnabled(pathStyleAccessEnabled.valueOrNullIfDefault()) .checksumValidationEnabled(checksumValidationEnabled.valueOrNullIfDefault()) .chunkedEncodingEnabled(chunkedEncodingEnabled.valueOrNullIfDefault()) - .useArnRegionEnabled(useArnRegionEnabled.valueOrNullIfDefault()) + .useArnRegionEnabled(useArnRegionEnabled) .profileFile(profileFile.valueOrNullIfDefault()) .profileName(profileName.valueOrNullIfDefault()); } @@ -325,6 +330,19 @@ public interface Builder extends CopyableBuilder { */ Builder profileFile(ProfileFile profileFile); + Supplier profileFileSupplier(); + + /** + * The supplier of profile file instances that should be consulted to determine the default value of + * {@link #useArnRegionEnabled(Boolean)} or {@link #multiRegionEnabled(Boolean)}. + * This is not used, if those parameters are configured on the builder. + * + *

    + * By default, the {@link ProfileFile#defaultProfileFile()} is used. + *

    + */ + Builder profileFile(Supplier profileFile); + String profileName(); /** @@ -347,7 +365,7 @@ static final class DefaultS3ServiceConfigurationBuilder implements Builder { private Boolean chunkedEncodingEnabled; private Boolean useArnRegionEnabled; private Boolean multiRegionEnabled; - private ProfileFile profileFile; + private Supplier profileFile; private String profileName; @Override @@ -449,11 +467,25 @@ public Builder multiRegionEnabled(Boolean multiRegionEnabled) { @Override public ProfileFile profileFile() { - return profileFile; + return Optional.ofNullable(profileFile) + .map(Supplier::get) + .orElse(null); } @Override public Builder profileFile(ProfileFile profileFile) { + return profileFile(Optional.ofNullable(profileFile) + .map(ProfileFileSupplier::fixedProfileFile) + .orElse(null)); + } + + @Override + public Supplier profileFileSupplier() { + return profileFile; + } + + @Override + public Builder profileFile(Supplier profileFile) { this.profileFile = profileFile; return this; } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/DisableMultiRegionProviderChain.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/DisableMultiRegionProviderChain.java index 9b5fb603054e..cda136d92edd 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/DisableMultiRegionProviderChain.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/DisableMultiRegionProviderChain.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; @@ -50,7 +51,7 @@ private DisableMultiRegionProviderChain(List provide *
*/ public static DisableMultiRegionProviderChain create() { - return create(ProfileFile.defaultProfileFile(), + return create(ProfileFile::defaultProfileFile, ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow()); } @@ -60,6 +61,12 @@ public static DisableMultiRegionProviderChain create(ProfileFile profileFile, St ProfileDisableMultiRegionProvider.create(profileFile, profileName))); } + public static DisableMultiRegionProviderChain create(Supplier profileFile, String profileName) { + return new DisableMultiRegionProviderChain(Arrays.asList( + SystemsSettingsDisableMultiRegionProvider.create(), + ProfileDisableMultiRegionProvider.create(profileFile, profileName))); + } + @Override public Optional resolve() { for (DisableMultiRegionProvider provider : providers) { diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileDisableMultiRegionProvider.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileDisableMultiRegionProvider.java index 4316baebda93..77889e7330cf 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileDisableMultiRegionProvider.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileDisableMultiRegionProvider.java @@ -49,6 +49,10 @@ public static ProfileDisableMultiRegionProvider create(ProfileFile profileFile, return new ProfileDisableMultiRegionProvider(() -> profileFile, profileName); } + public static ProfileDisableMultiRegionProvider create(Supplier profileFile, String profileName) { + return new ProfileDisableMultiRegionProvider(profileFile, profileName); + } + @Override public Optional resolve() { return profileFile.get() diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileUseArnRegionProvider.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileUseArnRegionProvider.java index 23f54d98b018..8f35eebacf74 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileUseArnRegionProvider.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileUseArnRegionProvider.java @@ -28,7 +28,7 @@ @SdkInternalApi public final class ProfileUseArnRegionProvider implements UseArnRegionProvider { /** - * Property name for specifying whether or not use arn region should be enabled. + * Property name for specifying whether or not to use arn region should be enabled. */ private static final String AWS_USE_ARN_REGION = "s3_use_arn_region"; @@ -49,6 +49,10 @@ public static ProfileUseArnRegionProvider create(ProfileFile profileFile, String return new ProfileUseArnRegionProvider(() -> profileFile, profileName); } + public static ProfileUseArnRegionProvider create(Supplier profileFile, String profileName) { + return new ProfileUseArnRegionProvider(profileFile, profileName); + } + @Override public Optional resolveUseArnRegion() { return profileFile.get() diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/UseArnRegionProviderChain.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/UseArnRegionProviderChain.java index a64fc440c354..d418d496d17b 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/UseArnRegionProviderChain.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/settingproviders/UseArnRegionProviderChain.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; @@ -49,7 +50,7 @@ private UseArnRegionProviderChain(List providers) { * */ public static UseArnRegionProviderChain create() { - return create(ProfileFile.defaultProfileFile(), + return create(ProfileFile::defaultProfileFile, ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow()); } @@ -58,6 +59,11 @@ public static UseArnRegionProviderChain create(ProfileFile profileFile, String p ProfileUseArnRegionProvider.create(profileFile, profileName))); } + public static UseArnRegionProviderChain create(Supplier profileFile, String profileName) { + return new UseArnRegionProviderChain(Arrays.asList(SystemsSettingsUseArnRegionProvider.create(), + ProfileUseArnRegionProvider.create(profileFile, profileName))); + } + @Override public Optional resolveUseArnRegion() { for (UseArnRegionProvider provider : providers) { diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3ConfigurationTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3ConfigurationTest.java index 42f7e567f1c2..d9a7ed6da7e0 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3ConfigurationTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3ConfigurationTest.java @@ -87,4 +87,32 @@ public void multiRegionEnabled_notDisabledInProviders_shouldResolveToTrue() { assertThat(config.multiRegionEnabled()).isEqualTo(true); } + @Test + public void multiRegionEnabled_enabledInCProfile_shouldResolveToConfigCorrectlyOncePerCall() { + S3Configuration config = S3Configuration.builder().build(); + + String trueProfileConfig = getClass().getResource("internal/settingproviders/ProfileFile_true").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), trueProfileConfig); + assertThat(config.multiRegionEnabled()).isEqualTo(false); + + System.clearProperty(AWS_CONFIG_FILE.property()); + String falseProfileConfig = getClass().getResource("internal/settingproviders/ProfileFile_false").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), falseProfileConfig); + assertThat(config.multiRegionEnabled()).isEqualTo(true); + } + + @Test + public void useArnRegionEnabled_enabledInCProfile_shouldResolveToConfigCorrectlyOncePerCall() { + S3Configuration config = S3Configuration.builder().build(); + + String trueProfileConfig = getClass().getResource("internal/settingproviders/ProfileFile_true").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), trueProfileConfig); + assertThat(config.useArnRegionEnabled()).isEqualTo(true); + + System.clearProperty(AWS_CONFIG_FILE.property()); + String falseProfileConfig = getClass().getResource("internal/settingproviders/ProfileFile_false").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), falseProfileConfig); + assertThat(config.useArnRegionEnabled()).isEqualTo(false); + } + } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/DisableMultiRegionProviderChainTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/DisableMultiRegionProviderChainTest.java index f2cd9efb87ad..fb018228e5a1 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/DisableMultiRegionProviderChainTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/DisableMultiRegionProviderChainTest.java @@ -24,23 +24,23 @@ import org.junit.jupiter.api.Test; import software.amazon.awssdk.testutils.EnvironmentVariableHelper; -public class DisableMultiRegionProviderChainTest { +class DisableMultiRegionProviderChainTest { private final EnvironmentVariableHelper helper = new EnvironmentVariableHelper(); @AfterEach - public void clearSystemProperty() { + void clearSystemProperty() { System.clearProperty(AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS.property()); System.clearProperty(AWS_CONFIG_FILE.property()); helper.reset(); } @Test - public void notSpecified_shouldReturnEmptyOptional() { + void notSpecified_shouldReturnEmptyOptional() { assertThat(DisableMultiRegionProviderChain.create().resolve()).isEqualTo(Optional.empty()); } @Test - public void specifiedInBothProviders_systemPropertiesShouldTakePrecedence() { + void specifiedInBothProviders_systemPropertiesShouldTakePrecedence() { System.setProperty(AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS.property(), "false"); String configFile = getClass().getResource("ProfileFile_true").getFile(); System.setProperty(AWS_CONFIG_FILE.property(), configFile); @@ -49,7 +49,7 @@ public void specifiedInBothProviders_systemPropertiesShouldTakePrecedence() { } @Test - public void systemPropertiesThrowException_shouldUseConfigFile() { + void systemPropertiesThrowException_shouldUseConfigFile() { System.setProperty(AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS.property(), "foobar"); String configFile = getClass().getResource("ProfileFile_true").getFile(); System.setProperty(AWS_CONFIG_FILE.property(), configFile); @@ -58,7 +58,7 @@ public void systemPropertiesThrowException_shouldUseConfigFile() { } @Test - public void systemPropertiesNotSpecified_shouldUseConfigFile() { + void systemPropertiesNotSpecified_shouldUseConfigFile() { String configFile = getClass().getResource("ProfileFile_true").getFile(); System.setProperty(AWS_CONFIG_FILE.property(), configFile); @@ -66,7 +66,21 @@ public void systemPropertiesNotSpecified_shouldUseConfigFile() { } @Test - public void bothProvidersThrowException_shouldReturnEmpty() { + void resolve_systemPropertiesNotSpecified_shouldResolveOncePerCall() { + String trueConfigFile = getClass().getResource("ProfileFile_true").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), trueConfigFile); + + assertThat(DisableMultiRegionProviderChain.create().resolve()).isEqualTo(Optional.of(Boolean.TRUE)); + + System.clearProperty(AWS_CONFIG_FILE.property()); + String falseConfigFile = getClass().getResource("ProfileFile_false").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), falseConfigFile); + + assertThat(DisableMultiRegionProviderChain.create().resolve()).isEqualTo(Optional.of(Boolean.FALSE)); + } + + @Test + void bothProvidersThrowException_shouldReturnEmpty() { System.setProperty(AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS.property(), "foobar"); String configFile = getClass().getResource("ProfileFile_unsupportedValue").getFile(); System.setProperty(AWS_CONFIG_FILE.property(), configFile); diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileDisableMultiRegionProviderTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileDisableMultiRegionProviderTest.java index a7ca5fd7363c..b4948527e601 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileDisableMultiRegionProviderTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileDisableMultiRegionProviderTest.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.services.s3.internal.settingproviders; +import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -72,6 +73,20 @@ public void unsupportedValue_shouldThrowException() { assertThatThrownBy(() -> provider.resolve()).isInstanceOf(IllegalArgumentException.class); } + @Test + public void resolve_specifiedMultipleValuesInConfigFile_shouldResolveOncePerCall() { + String trueConfigFile = getClass().getResource("ProfileFile_true").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), trueConfigFile); + + assertThat(provider.resolve()).isEqualTo(Optional.of(TRUE)); + + System.clearProperty(AWS_CONFIG_FILE.property()); + String falseConfigFile = getClass().getResource("ProfileFile_false").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), falseConfigFile); + + assertThat(provider.resolve()).isEqualTo(Optional.of(FALSE)); + } + @Test public void specifiedInOverrideConfig_shouldUse() { ExecutionInterceptor interceptor = Mockito.spy(AbstractExecutionInterceptor.class); diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileUseArnRegionProviderTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileUseArnRegionProviderTest.java index 044142d23103..362953f90b4c 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileUseArnRegionProviderTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/ProfileUseArnRegionProviderTest.java @@ -81,6 +81,20 @@ public void commaNoSpace_shouldResolveCorrectly() { assertThat(provider.resolveUseArnRegion()).isEqualTo(Optional.of(FALSE)); } + @Test + public void resolveUseArnRegion_specifiedMultipleValuesInConfigFile_shouldResolveOncePerCall() { + String trueConfigFile = getClass().getResource("ProfileFile_true").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), trueConfigFile); + + assertThat(provider.resolveUseArnRegion()).isEqualTo(Optional.of(TRUE)); + + System.clearProperty(AWS_CONFIG_FILE.property()); + String falseConfigFile = getClass().getResource("ProfileFile_false").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), falseConfigFile); + + assertThat(provider.resolveUseArnRegion()).isEqualTo(Optional.of(FALSE)); + } + @Test public void specifiedInOverrideConfig_shouldUse() { ExecutionInterceptor interceptor = Mockito.spy(AbstractExecutionInterceptor.class); diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/UseArnRegionProviderChainTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/UseArnRegionProviderChainTest.java index 5f1e72b77492..43bede52b75a 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/UseArnRegionProviderChainTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/settingproviders/UseArnRegionProviderChainTest.java @@ -24,23 +24,23 @@ import org.junit.jupiter.api.Test; import software.amazon.awssdk.testutils.EnvironmentVariableHelper; -public class UseArnRegionProviderChainTest { +class UseArnRegionProviderChainTest { private final EnvironmentVariableHelper helper = new EnvironmentVariableHelper(); @AfterEach - public void clearSystemProperty() { + void clearSystemProperty() { System.clearProperty(AWS_S3_USE_ARN_REGION.property()); System.clearProperty(AWS_CONFIG_FILE.property()); helper.reset(); } @Test - public void notSpecified_shouldReturnEmptyOptional() { + void notSpecified_shouldReturnEmptyOptional() { assertThat(UseArnRegionProviderChain.create().resolveUseArnRegion()).isEqualTo(Optional.empty()); } @Test - public void specifiedInBothProviders_systemPropertiesShouldTakePrecedence() { + void specifiedInBothProviders_systemPropertiesShouldTakePrecedence() { System.setProperty(AWS_S3_USE_ARN_REGION.property(), "false"); String configFile = getClass().getResource("ProfileFile_true").getFile(); System.setProperty(AWS_CONFIG_FILE.property(), configFile); @@ -49,7 +49,7 @@ public void specifiedInBothProviders_systemPropertiesShouldTakePrecedence() { } @Test - public void systemPropertiesThrowException_shouldUseConfigFile() { + void systemPropertiesThrowException_shouldUseConfigFile() { System.setProperty(AWS_S3_USE_ARN_REGION.property(), "foobar"); String configFile = getClass().getResource("ProfileFile_true").getFile(); System.setProperty(AWS_CONFIG_FILE.property(), configFile); @@ -58,7 +58,25 @@ public void systemPropertiesThrowException_shouldUseConfigFile() { } @Test - public void bothProvidersThrowException_shouldReturnEmpty() { + void resolveUseArnRegion_systemPropertiesNotSpecifiedConfigFileValueTrue_resolvesOncePerCall() { + String trueConfigFile = getClass().getResource("ProfileFile_true").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), trueConfigFile); + + UseArnRegionProviderChain providerChain = UseArnRegionProviderChain.create(); + assertThat(providerChain.resolveUseArnRegion()).isEqualTo(Optional.of(Boolean.TRUE)); + } + + @Test + void resolveUseArnRegion_systemPropertiesNotSpecifiedConfigFileValueFalse_resolvesOncePerCall() { + String falseConfigFile = getClass().getResource("ProfileFile_false").getFile(); + System.setProperty(AWS_CONFIG_FILE.property(), falseConfigFile); + + UseArnRegionProviderChain providerChain = UseArnRegionProviderChain.create(); + assertThat(providerChain.resolveUseArnRegion()).isEqualTo(Optional.of(Boolean.FALSE)); + } + + @Test + void bothProvidersThrowException_shouldReturnEmpty() { System.setProperty(AWS_S3_USE_ARN_REGION.property(), "foobar"); String configFile = getClass().getResource("ProfileFile_unsupportedValue").getFile(); System.setProperty(AWS_CONFIG_FILE.property(), configFile); From 6ae05dc240785c2760fa9abc8a15fc97802c7958 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Wed, 11 Jan 2023 12:57:29 -0700 Subject: [PATCH 08/16] Presigners, other Support classes take Supplier (#3677) * Presigners, other Support classes take Supplier * Split new ProfileFile tests from existing parameterized tests * Improved tests readability --- .../core/client/config/SdkClientOption.java | 7 + .../presigner/DefaultPollyPresigner.java | 13 +- .../presigner/DefaultPollyPresignerTest.java | 27 ++- .../endpoints/UseGlobalEndpointResolver.java | 4 +- .../internal/signing/DefaultS3Presigner.java | 6 +- .../internal/signing/DefaultSdkPresigner.java | 15 +- ...GlobalEndpointResolverProfileFileTest.java | 183 ++++++++++++++++++ .../UseGlobalEndpointResolverTest.java | 8 +- 8 files changed, 236 insertions(+), 27 deletions(-) create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverProfileFileTest.java diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java index 8e056a7dabd5..1d2c33b98e64 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.core.ClientType; import software.amazon.awssdk.core.ServiceConfiguration; @@ -129,6 +130,12 @@ public final class SdkClientOption extends ClientOption { */ public static final SdkClientOption PROFILE_FILE = new SdkClientOption<>(ProfileFile.class); + /** + * The profile file supplier to use for this client. + */ + public static final SdkClientOption> PROFILE_FILE_SUPPLIER = + new SdkClientOption<>(new UnsafeValueType(Supplier.class)); + /** * The profile name to use for this client. */ diff --git a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java index 1dd6218801b1..d939bdbc389d 100644 --- a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java +++ b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Stream; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.credentials.AwsCredentials; @@ -68,7 +69,7 @@ public final class DefaultPollyPresigner implements PollyPresigner { private static final String SERVICE_NAME = "polly"; private static final Aws4Signer DEFAULT_SIGNER = Aws4Signer.create(); - private final ProfileFile profileFile; + private final Supplier profileFile; private final String profileName; private final Region region; private final AwsCredentialsProvider credentialsProvider; @@ -77,11 +78,11 @@ public final class DefaultPollyPresigner implements PollyPresigner { private final Boolean fipsEnabled; private DefaultPollyPresigner(BuilderImpl builder) { - this.profileFile = ProfileFile.defaultProfileFile(); + this.profileFile = ProfileFile::defaultProfileFile; this.profileName = ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow(); this.region = builder.region != null ? builder.region : DefaultAwsRegionProviderChain.builder() - .profileFile(() -> profileFile) + .profileFile(profileFile) .profileName(profileName) .build() .getRegion(); @@ -93,14 +94,14 @@ private DefaultPollyPresigner(BuilderImpl builder) { this.endpointOverride = builder.endpointOverride; this.dualstackEnabled = builder.dualstackEnabled != null ? builder.dualstackEnabled : DualstackEnabledProvider.builder() - .profileFile(() -> profileFile) + .profileFile(profileFile) .profileName(profileName) .build() .isDualstackEnabled() .orElse(false); this.fipsEnabled = builder.fipsEnabled != null ? builder.fipsEnabled : FipsEnabledProvider.builder() - .profileFile(() -> profileFile) + .profileFile(profileFile) .profileName(profileName) .build() .isFipsEnabled() @@ -245,7 +246,7 @@ private URI resolveEndpoint() { return new DefaultServiceEndpointBuilder(SERVICE_NAME, "https") .withRegion(region()) - .withProfileFile(() -> profileFile) + .withProfileFile(profileFile) .withProfileName(profileName) .withDualstackEnabled(dualstackEnabled) .withFipsEnabled(fipsEnabled) diff --git a/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java b/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java index f5a17a07b9bd..4d5cc4973ff3 100644 --- a/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java +++ b/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java @@ -41,7 +41,7 @@ /** * Tests for {@link DefaultPollyPresigner}. */ -public class DefaultPollyPresignerTest { +class DefaultPollyPresignerTest { private static final SynthesizeSpeechRequest BASIC_SYNTHESIZE_SPEECH_REQUEST = SynthesizeSpeechRequest.builder() .voiceId("Salli") .outputFormat(OutputFormat.PCM) @@ -56,7 +56,7 @@ public void methodSetup() { } @Test - public void presign_requestLevelCredentials_honored() { + void presign_requestLevelCredentials_honored() { AwsCredentials requestCredentials = AwsBasicCredentials.create("akid2", "skid2"); PollyPresigner presigner = DefaultPollyPresigner.builder() @@ -80,7 +80,7 @@ public void presign_requestLevelCredentials_honored() { } @Test - public void presign_requestLevelHeaders_included() { + void presign_requestLevelHeaders_included() { PollyPresigner presigner = DefaultPollyPresigner.builder() .region(Region.US_EAST_1) .credentialsProvider(credentialsProvider) @@ -104,7 +104,7 @@ public void presign_requestLevelHeaders_included() { } @Test - public void presign_includesRequestLevelHeaders_notBrowserCompatible() { + void presign_includesRequestLevelHeaders_notBrowserCompatible() { PollyPresigner presigner = DefaultPollyPresigner.builder() .region(Region.US_EAST_1) .credentialsProvider(credentialsProvider) @@ -128,7 +128,7 @@ public void presign_includesRequestLevelHeaders_notBrowserCompatible() { } @Test - public void presign_includesRequestLevelQueryParams_included() { + void presign_includesRequestLevelQueryParams_included() { PollyPresigner presigner = DefaultPollyPresigner.builder() .region(Region.US_EAST_1) .credentialsProvider(credentialsProvider) @@ -151,7 +151,7 @@ public void presign_includesRequestLevelQueryParams_included() { } @Test - public void presign_endpointOverriden() { + void presign_endpointOverriden() { PollyPresigner presigner = DefaultPollyPresigner.builder() .region(Region.US_EAST_1) .credentialsProvider(credentialsProvider) @@ -173,7 +173,7 @@ public void presign_endpointOverriden() { } @Test - public void close_closesCustomCloseableCredentialsProvider() throws IOException { + void close_closesCustomCloseableCredentialsProvider() throws IOException { TestCredentialsProvider mockCredentialsProvider = mock(TestCredentialsProvider.class); PollyPresigner presigner = DefaultPollyPresigner.builder() @@ -186,6 +186,19 @@ public void close_closesCustomCloseableCredentialsProvider() throws IOException verify(mockCredentialsProvider).close(); } + @Test + void presigner_credentialsProviderSetToNullByBuilder_createsDefaultCredentialsProvider() { + DefaultPollyPresigner presigner = (DefaultPollyPresigner) + DefaultPollyPresigner.builder() + .region(Region.US_EAST_1) + .credentialsProvider(null) + .build(); + + + AwsCredentialsProvider awsCredentialsProvider = presigner.credentialsProvider(); + assertThat(awsCredentialsProvider).isNotNull(); + } + private interface TestCredentialsProvider extends AwsCredentialsProvider, Closeable { } } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolver.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolver.java index 568b47c085cd..7f7b227f5e79 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolver.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolver.java @@ -44,7 +44,9 @@ public UseGlobalEndpointResolver(SdkClientConfiguration config) { String defaultS3UsEast1RegionalEndpointFromSmartDefaults = config.option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT); this.useUsEast1RegionalEndpoint = - new Lazy<>(() -> useUsEast1RegionalEndpoint(() -> config.option(SdkClientOption.PROFILE_FILE), + new Lazy<>(() -> useUsEast1RegionalEndpoint(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? + config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : + () -> config.option(SdkClientOption.PROFILE_FILE), () -> config.option(SdkClientOption.PROFILE_NAME), defaultS3UsEast1RegionalEndpointFromSmartDefaults)); } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java index 73356356bdef..ec79bb132ffe 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java @@ -129,7 +129,7 @@ private DefaultS3Presigner(Builder b) { S3Configuration serviceConfiguration = b.serviceConfiguration != null ? b.serviceConfiguration : S3Configuration.builder() - .profileFile(profileFile()) + .profileFile(profileFileSupplier()) .profileName(profileName()) .checksumValidationEnabled(false) .build(); @@ -214,7 +214,7 @@ private SdkClientConfiguration createClientConfiguration() { } else { URI defaultEndpoint = new DefaultServiceEndpointBuilder(SERVICE_NAME, "https") .withRegion(region()) - .withProfileFile(this::profileFile) + .withProfileFile(profileFileSupplier()) .withProfileName(profileName()) .withDualstackEnabled(serviceConfiguration.dualstackEnabled()) .withFipsEnabled(fipsEnabled()) @@ -533,7 +533,7 @@ private UseGlobalEndpointResolver createUseGlobalEndpointResolver() { SdkClientConfiguration config = clientConfiguration.toBuilder() .option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, legacyOption) - .option(SdkClientOption.PROFILE_FILE, profileFile()) + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, profileFileSupplier()) .option(SdkClientOption.PROFILE_NAME, profileName()) .build(); diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultSdkPresigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultSdkPresigner.java index 4b8a78ed44ed..b6df6e29fe7b 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultSdkPresigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultSdkPresigner.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.services.s3.internal.signing; import java.net.URI; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; @@ -30,14 +31,14 @@ /** * The base class implementing the {@link SdkPresigner} interface. - *

+ *

* TODO: This should get moved to aws-core (or split and moved to sdk-core and aws-core) when we support presigning from * multiple services. * TODO: After moving, this should get marked as an @SdkProtectedApi. */ @SdkInternalApi public abstract class DefaultSdkPresigner implements SdkPresigner { - private final ProfileFile profileFile; + private final Supplier profileFile; private final String profileName; private final Region region; private final URI endpointOverride; @@ -46,10 +47,10 @@ public abstract class DefaultSdkPresigner implements SdkPresigner { private final boolean fipsEnabled; protected DefaultSdkPresigner(Builder b) { - this.profileFile = ProfileFile.defaultProfileFile(); + this.profileFile = ProfileFile::defaultProfileFile; this.profileName = ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow(); this.region = b.region != null ? b.region : DefaultAwsRegionProviderChain.builder() - .profileFile(() -> profileFile) + .profileFile(profileFile) .profileName(profileName) .build() .getRegion(); @@ -61,21 +62,21 @@ protected DefaultSdkPresigner(Builder b) { this.endpointOverride = b.endpointOverride; this.dualstackEnabled = b.dualstackEnabled != null ? b.dualstackEnabled : DualstackEnabledProvider.builder() - .profileFile(() -> profileFile) + .profileFile(profileFile) .profileName(profileName) .build() .isDualstackEnabled() .orElse(null); this.fipsEnabled = b.fipsEnabled != null ? b.fipsEnabled : FipsEnabledProvider.builder() - .profileFile(() -> profileFile) + .profileFile(profileFile) .profileName(profileName) .build() .isFipsEnabled() .orElse(false); } - protected ProfileFile profileFile() { + protected Supplier profileFileSupplier() { return profileFile; } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverProfileFileTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverProfileFileTest.java new file mode 100644 index 000000000000..dd7ff6106ba4 --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverProfileFileTest.java @@ -0,0 +1,183 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3.internal.endpoints; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.utils.StringInputStream; + +class UseGlobalEndpointResolverProfileFileTest { + + @Test + void resolve_nonUsEast1_resolvesToFalse() { + SdkClientConfiguration config = SdkClientConfiguration.builder().build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.AF_SOUTH_1)).isEqualTo(false); + } + + @Test + void resolve_profileFileRegionalEndpointLegacy_resolvesFromPropertyAsTrue() { + ProfileFile file = configuration("[profile regional_s3_endpoint]\n" + + "s3_us_east_1_regional_endpoint = legacy"); + + SdkClientConfiguration config = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE, file) + .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") + .build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); + } + + @Test + void resolve_profileFileRegionalEndpointRegional_resolvesFromPropertyAsFalse() { + ProfileFile file = configuration("[profile regional_s3_endpoint]\n" + + "s3_us_east_1_regional_endpoint = regional"); + + SdkClientConfiguration config = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE, file) + .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") + .build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(false); + } + + @Test + void resolve_nullProfileFileSupplierAndNullProfileFileAndNullDefaultRegionalEndpoint_resolvesToTrue() { + SdkClientConfiguration config = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, null) + .option(SdkClientOption.PROFILE_FILE, null) + .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") + .build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); + } + + @Test + void resolve_nullProfileFileSupplierAndNullProfileFileAndDefaultRegionalEndPointLegacy_resolvesToTrue() { + SdkClientConfiguration config = SdkClientConfiguration + .builder() + .option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, "legacy") + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, null) + .option(SdkClientOption.PROFILE_FILE, null) + .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") + .build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); + } + + @Test + void resolve_nullProfileFileSupplierAndNullProfileFileAndDefaultRegionalEndPointRegional_resolvesToFalse() { + SdkClientConfiguration config = SdkClientConfiguration + .builder() + .option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, "regional") + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, null) + .option(SdkClientOption.PROFILE_FILE, null) + .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") + .build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(false); + } + + @Test + void resolve_nullProfileFileSupplierAndNonNullProfileFileEndpointLegacy_resolvesToTrue() { + Supplier supplier = null; + ProfileFile file = configuration("[profile regional_s3_endpoint]\n" + + "s3_us_east_1_regional_endpoint = legacy"); + + SdkClientConfiguration config = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, supplier) + .option(SdkClientOption.PROFILE_FILE, file) + .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") + .build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); + } + + @Test + void resolve_nullProfileFileSupplierAndNonNullProfileFileEndpointLegacy_resolvesToFalse() { + Supplier supplier = null; + ProfileFile file = configuration("[profile regional_s3_endpoint]\n" + + "s3_us_east_1_regional_endpoint = regional"); + + SdkClientConfiguration config = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, supplier) + .option(SdkClientOption.PROFILE_FILE, file) + .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") + .build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(false); + } + + @Test + void resolve_profileFileSupplierRegionalEndpointLegacy_resolvesToTrue() { + ProfileFile file = configuration("[profile regional_s3_endpoint]\n" + + "s3_us_east_1_regional_endpoint = legacy"); + Supplier supplier = () -> file; + + SdkClientConfiguration config = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, supplier) + .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") + .build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); + } + + @Test + void resolve_profileFileSupplierRegionalEndpointRegional_resolvesToFalse() { + ProfileFile file = configuration("[profile regional_s3_endpoint]\n" + + "s3_us_east_1_regional_endpoint = regional"); + Supplier supplier = () -> file; + + SdkClientConfiguration config = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, supplier) + .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") + .build(); + UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); + + assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(false); + } + + private ProfileFile configuration(String string) { + return ProfileFile.builder() + .content(new StringInputStream(string)) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + } + +} diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverTest.java index 3588c07f0dd2..3b293e951a42 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverTest.java @@ -17,11 +17,14 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.net.URI; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; +import java.util.function.Supplier; +import org.apache.hc.core5.http.Chars; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,10 +34,9 @@ import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.regions.ServiceMetadata; import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; -import software.amazon.awssdk.regions.servicemetadata.EnhancedS3ServiceMetadata; import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.utils.StringInputStream; import software.amazon.awssdk.utils.Validate; @RunWith(Parameterized.class) From 77e45cf2f38dbe1bbb5287a472920a7ab365d7bf Mon Sep 17 00:00:00 2001 From: David Negrete Date: Thu, 12 Jan 2023 16:36:34 -0700 Subject: [PATCH 09/16] ProfileFile updates to BaseClientBuilderClass, other S3 classes (#3685) --- .../poet/builder/BaseClientBuilderClass.java | 12 +++---- .../builder/test-client-builder-class.java | 9 ++++-- .../awssdk/services/s3/S3Configuration.java | 2 +- .../s3control/S3ControlConfiguration.java | 32 ++++++++++++++++--- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java index df7e6d3b2895..7fc8d198d551 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java @@ -365,15 +365,15 @@ private MethodSpec finalizeServiceConfigurationMethod() { private void mergeServiceConfiguration(MethodSpec.Builder builder, String clientConfigClassName) { ClassName clientConfigClass = ClassName.bestGuess(clientConfigClassName); - builder.addCode("$1T.Builder serviceConfigBuilder = (($1T) config.option($2T.SERVICE_CONFIGURATION)).toBuilder();" + - "serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFile() " - + "!= null ? serviceConfigBuilder.profileFile() : config.option($2T.PROFILE_FILE));" + - "serviceConfigBuilder.profileName(serviceConfigBuilder.profileName() " + builder.addCode("$1T.Builder serviceConfigBuilder = (($1T) config.option($2T.SERVICE_CONFIGURATION)).toBuilder();" + + "serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFileSupplier() != null ? " + + "serviceConfigBuilder.profileFileSupplier() : config.option($2T.PROFILE_FILE_SUPPLIER) != null ? " + + "config.option($2T.PROFILE_FILE_SUPPLIER) : serviceConfigBuilder.profileFile() != null ? " + + "() -> serviceConfigBuilder.profileFile() : () -> config.option($2T.PROFILE_FILE));" + + "serviceConfigBuilder.profileName(serviceConfigBuilder.profileName() " + "!= null ? serviceConfigBuilder.profileName() : config.option($2T.PROFILE_NAME));", clientConfigClass, SdkClientOption.class); - - if (model.getCustomizationConfig().getServiceConfig().hasDualstackProperty()) { builder.addCode("if (serviceConfigBuilder.dualstackEnabled() != null) {") .addCode(" $T.validState(config.option($T.DUALSTACK_ENDPOINT_ENABLED) == null, \"Dualstack has been " diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java index face91772800..0679d198f153 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java @@ -60,8 +60,13 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon interceptors = CollectionUtils.mergeLists(interceptors, config.option(SdkClientOption.EXECUTION_INTERCEPTORS)); ServiceConfiguration.Builder serviceConfigBuilder = ((ServiceConfiguration) config .option(SdkClientOption.SERVICE_CONFIGURATION)).toBuilder(); - serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFile() != null ? serviceConfigBuilder.profileFile() : config - .option(SdkClientOption.PROFILE_FILE)); + serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFileSupplier() != null ? + serviceConfigBuilder.profileFileSupplier() : + config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? + config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : + serviceConfigBuilder.profileFile() != null ? + () -> serviceConfigBuilder.profileFile() : + () -> config.option(SdkClientOption.PROFILE_FILE)); serviceConfigBuilder.profileName(serviceConfigBuilder.profileName() != null ? serviceConfigBuilder.profileName() : config .option(SdkClientOption.PROFILE_NAME)); if (serviceConfigBuilder.dualstackEnabled() != null) { diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java index 41ad17267030..4462b75add0a 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java @@ -78,7 +78,7 @@ private S3Configuration(DefaultS3ServiceConfigurationBuilder builder) { this.dualstackEnabled = FieldWithDefault.create(builder.dualstackEnabled, DEFAULT_DUALSTACK_ENABLED); this.accelerateModeEnabled = FieldWithDefault.create(builder.accelerateModeEnabled, DEFAULT_ACCELERATE_MODE_ENABLED); this.pathStyleAccessEnabled = FieldWithDefault.create(builder.pathStyleAccessEnabled, DEFAULT_PATH_STYLE_ACCESS_ENABLED); - this.checksumValidationEnabled = FieldWithDefault.create(builder.checksumValidationEnabled, + this.checksumValidationEnabled = FieldWithDefault.create(builder.checksumValidationEnabled, DEFAULT_CHECKSUM_VALIDATION_ENABLED); this.chunkedEncodingEnabled = FieldWithDefault.create(builder.chunkedEncodingEnabled, DEFAULT_CHUNKED_ENCODING_ENABLED); this.profileFile = FieldWithDefault.create(builder.profileFile, ProfileFile::defaultProfileFile); diff --git a/services/s3control/src/main/java/software/amazon/awssdk/services/s3control/S3ControlConfiguration.java b/services/s3control/src/main/java/software/amazon/awssdk/services/s3control/S3ControlConfiguration.java index c6d89635811e..00e0d3199ee9 100644 --- a/services/s3control/src/main/java/software/amazon/awssdk/services/s3control/S3ControlConfiguration.java +++ b/services/s3control/src/main/java/software/amazon/awssdk/services/s3control/S3ControlConfiguration.java @@ -15,17 +15,20 @@ package software.amazon.awssdk.services.s3control; +import java.util.Optional; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.Immutable; import software.amazon.awssdk.annotations.NotThreadSafe; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.core.ServiceConfiguration; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; import software.amazon.awssdk.utils.builder.CopyableBuilder; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; /** - * S3 Control specific configuration allowing customers to enabled FIPS or + * S3 Control specific configuration allowing customers to enable FIPS or * dualstack. */ @SdkPublicApi @@ -48,7 +51,7 @@ public final class S3ControlConfiguration implements ServiceConfiguration, private final Boolean fipsModeEnabled; private final Boolean dualstackEnabled; private final Boolean useArnRegionEnabled; - private final ProfileFile profileFile; + private final Supplier profileFile; private final String profileName; private S3ControlConfiguration(DefaultS3ServiceConfigurationBuilder builder) { @@ -175,6 +178,13 @@ public interface Builder extends CopyableBuilder profileFileSupplier(); + + /** + * The profile file supplier that should be consulted to determine the service-specific default configuration. + */ + Builder profileFile(Supplier profileFile); + String profileName(); /** @@ -188,7 +198,7 @@ private static final class DefaultS3ServiceConfigurationBuilder implements Build private Boolean dualstackEnabled; private Boolean fipsModeEnabled; - private ProfileFile profileFile; + private Supplier profileFile; private String profileName; private Boolean useArnRegionEnabled; @@ -239,11 +249,25 @@ public void setUseArnRegionEnabled(Boolean useArnRegionEnabled) { @Override public ProfileFile profileFile() { - return profileFile; + return Optional.ofNullable(profileFile) + .map(Supplier::get) + .orElse(null); } @Override public Builder profileFile(ProfileFile profileFile) { + return profileFile(Optional.ofNullable(profileFile) + .map(ProfileFileSupplier::fixedProfileFile) + .orElse(null)); + } + + @Override + public Supplier profileFileSupplier() { + return profileFile; + } + + @Override + public Builder profileFile(Supplier profileFile) { this.profileFile = profileFile; return this; } From 70fa31ad9964e6b69582944d8f6cbaccf242e0bb Mon Sep 17 00:00:00 2001 From: David Negrete Date: Tue, 17 Jan 2023 16:42:15 -0700 Subject: [PATCH 10/16] =?UTF-8?q?Leverage=20SdkClientOption=20and=20SdkExe?= =?UTF-8?q?cutionAttributes;=20fallback=20to=20simp=E2=80=A6=20(#3692)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Leverage SdkClientOption and SdkExecutionAttributes; fallback to simple ProfileFile * Addressed review comments --- .../ProfileProviderCredentialsContext.java | 2 +- .../builder/AwsDefaultClientBuilder.java | 30 ++++-- .../internal/AwsExecutionContextBuilder.java | 1 + .../AwsExecutionContextBuilderTest.java | 18 ++++ .../builder/SdkDefaultClientBuilder.java | 15 ++- ...DefaultEndpointDiscoveryProviderChain.java | 4 +- .../interceptor/SdkExecutionAttribute.java | 4 + .../internal/handler/BaseClientHandler.java | 2 + .../builder/DefaultClientBuilderTest.java | 5 +- .../docdb/internal/RdsPresignInterceptor.java | 4 +- .../internal/PresignRequestHandlerTest.java | 2 + .../dynamodb/DynamoDbRetryPolicy.java | 4 +- .../dynamodb/DynamoDbRetryPolicyTest.java | 94 ++++++++++++++++++- .../services/kinesis/KinesisRetryPolicy.java | 4 +- .../internal/RdsPresignInterceptor.java | 4 +- .../internal/PresignRequestHandlerTest.java | 2 + .../rds/internal/RdsPresignInterceptor.java | 4 +- .../internal/PresignRequestHandlerTest.java | 2 + .../awssdk/services/s3/S3Configuration.java | 2 +- .../awssdk/services/s3/S3Utilities.java | 25 +++-- .../SsoOidcProfileTokenProviderFactory.java | 61 +++++++++++- ...soOidcProfileTokenProviderFactoryTest.java | 44 +++++++-- 22 files changed, 289 insertions(+), 44 deletions(-) diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileProviderCredentialsContext.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileProviderCredentialsContext.java index 7f8a8efdb4d2..065f8b61da7c 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileProviderCredentialsContext.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileProviderCredentialsContext.java @@ -22,7 +22,7 @@ import software.amazon.awssdk.profiles.ProfileFile; /** - * Context class that defines the required properties for creation of a Credentials provider* + * Context class that defines the required properties for creation of a Credentials provider. */ @SdkProtectedApi public final class ProfileProviderCredentialsContext { diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java index 5b8a7c13287f..de1dec8578bd 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; @@ -245,7 +246,7 @@ private URI resolveEndpoint(SdkClientConfiguration config) { private URI endpointFromConfig(SdkClientConfiguration config) { return new DefaultServiceEndpointBuilder(serviceEndpointPrefix(), DEFAULT_ENDPOINT_PROTOCOL) .withRegion(config.option(AwsClientOption.AWS_REGION)) - .withProfileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) + .withProfileFile(profileFileSupplierOrFallbackToProfileFile(config)) .withProfileName(config.option(SdkClientOption.PROFILE_NAME)) .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, config.option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) @@ -272,10 +273,10 @@ private Region regionFromDefaultProvider(SdkClientConfiguration config) { throw new IllegalStateException("No region was configured, and use-region-provider-chain was disabled."); } - ProfileFile profileFile = config.option(SdkClientOption.PROFILE_FILE); + Supplier profileFile = profileFileSupplierOrFallbackToProfileFile(config); String profileName = config.option(SdkClientOption.PROFILE_NAME); return DefaultAwsRegionProviderChain.builder() - .profileFile(() -> profileFile) + .profileFile(profileFile) .profileName(profileName) .build() .getRegion(); @@ -286,7 +287,7 @@ private DefaultsMode resolveDefaultsMode(SdkClientConfiguration config) { config.option(AwsClientOption.DEFAULTS_MODE) != null ? config.option(AwsClientOption.DEFAULTS_MODE) : DefaultsModeResolver.create() - .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) + .profileFile(profileFileSupplierOrFallbackToProfileFile(config)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .resolve(); @@ -313,10 +314,10 @@ private Boolean resolveDualstackEndpointEnabled(SdkClientConfiguration config) { * Load the dualstack endpoint setting from the default provider logic. */ private Boolean resolveUseDualstackFromDefaultProvider(SdkClientConfiguration config) { - ProfileFile profileFile = config.option(SdkClientOption.PROFILE_FILE); + Supplier profileFile = profileFileSupplierOrFallbackToProfileFile(config); String profileName = config.option(SdkClientOption.PROFILE_NAME); return DualstackEnabledProvider.builder() - .profileFile(() -> profileFile) + .profileFile(profileFile) .profileName(profileName) .build() .isDualstackEnabled() @@ -336,10 +337,10 @@ private Boolean resolveFipsEndpointEnabled(SdkClientConfiguration config) { * Load the dualstack endpoint setting from the default provider logic. */ private Boolean resolveUseFipsFromDefaultProvider(SdkClientConfiguration config) { - ProfileFile profileFile = config.option(SdkClientOption.PROFILE_FILE); + Supplier profileFile = profileFileSupplierOrFallbackToProfileFile(config); String profileName = config.option(SdkClientOption.PROFILE_NAME); return FipsEnabledProvider.builder() - .profileFile(() -> profileFile) + .profileFile(profileFile) .profileName(profileName) .build() .isFipsEnabled() @@ -353,7 +354,7 @@ private AwsCredentialsProvider resolveCredentials(SdkClientConfiguration config) return config.option(AwsClientOption.CREDENTIALS_PROVIDER) != null ? config.option(AwsClientOption.CREDENTIALS_PROVIDER) : DefaultCredentialsProvider.builder() - .profileFile(config.option(SdkClientOption.PROFILE_FILE)) + .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .build(); } @@ -370,7 +371,7 @@ private RetryPolicy resolveAwsRetryPolicy(SdkClientConfiguration config) { } RetryMode retryMode = RetryMode.resolver() - .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) + .profileFile(profileFileSupplierOrFallbackToProfileFile(config)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); @@ -466,4 +467,13 @@ private static Pair> transformFipsPseudoRegionIfNecess return Pair.of(region, Optional.empty()); } + + private Supplier profileFileSupplierOrFallbackToProfileFile(SdkClientConfiguration config) { + Supplier profileFile = config.option(SdkClientOption.PROFILE_FILE_SUPPLIER); + if (profileFile != null) { + return profileFile; + } + + return () -> config.option(SdkClientOption.PROFILE_FILE); + } } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java index 6bcd1ec65b78..ecf6b3f5c8ac 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java @@ -83,6 +83,7 @@ private AwsExecutionContextBuilder() { .putAttribute(SdkExecutionAttribute.CLIENT_TYPE, clientConfig.option(SdkClientOption.CLIENT_TYPE)) .putAttribute(SdkExecutionAttribute.SERVICE_NAME, clientConfig.option(SdkClientOption.SERVICE_NAME)) .putAttribute(SdkExecutionAttribute.PROFILE_FILE, clientConfig.option(SdkClientOption.PROFILE_FILE)) + .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, clientConfig.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, clientConfig.option(SdkClientOption.PROFILE_NAME)) .putAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED, clientConfig.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java index 1234c05a90e7..67ef23a8426c 100644 --- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,6 +49,7 @@ import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; import software.amazon.awssdk.core.internal.util.HttpChecksumUtils; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.profiles.ProfileFile; @RunWith(MockitoJUnitRunner.class) public class AwsExecutionContextBuilderTest { @@ -178,6 +180,22 @@ public void invokeInterceptorsAndCreateExecutionContext_multipleExecutionContext assertThat(checksumSpecs1).isNotSameAs(checksumSpecs2); assertThat(checksumSpecs2).isSameAs(checksumSpecs3); } + + @Test + public void invokeInterceptorsAndCreateExecutionContext_profileFileSupplier_storesValueInExecutionAttributes() { + ClientExecutionParams executionParams = clientExecutionParams(); + Supplier profileFileSupplier = () -> null; + SdkClientConfiguration clientConfig = testClientConfiguration() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, profileFileSupplier) + .build(); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfig); + + ExecutionAttributes executionAttributes = executionContext.executionAttributes(); + + assertThat(profileFileSupplier).isSameAs(executionAttributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER)); + } private ClientExecutionParams clientExecutionParams() { return new ClientExecutionParams() diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java index 9b68e9f9f5dd..14bf7f7fb262 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java @@ -36,6 +36,7 @@ import static software.amazon.awssdk.core.client.config.SdkClientOption.INTERNAL_USER_AGENT; import static software.amazon.awssdk.core.client.config.SdkClientOption.METRIC_PUBLISHERS; import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_FILE; +import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_FILE_SUPPLIER; import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_NAME; import static software.amazon.awssdk.core.client.config.SdkClientOption.RETRY_POLICY; import static software.amazon.awssdk.core.client.config.SdkClientOption.SCHEDULED_EXECUTOR_SERVICE; @@ -86,6 +87,7 @@ import software.amazon.awssdk.http.async.SdkAsyncHttpClient; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.Either; @@ -231,6 +233,9 @@ private SdkClientConfiguration setOverrides(SdkClientConfiguration configuration builder.option(DISABLE_HOST_PREFIX_INJECTION, clientOverrideConfiguration.advancedOption(DISABLE_HOST_PREFIX_INJECTION).orElse(null)); builder.option(PROFILE_FILE, clientOverrideConfiguration.defaultProfileFile().orElse(null)); + builder.option(PROFILE_FILE_SUPPLIER, clientOverrideConfiguration.defaultProfileFile() + .map(ProfileFileSupplier::fixedProfileFile) + .orElse(null)); builder.option(PROFILE_NAME, clientOverrideConfiguration.defaultProfileName().orElse(null)); builder.option(METRIC_PUBLISHERS, clientOverrideConfiguration.metricPublishers()); builder.option(EXECUTION_ATTRIBUTES, clientOverrideConfiguration.executionAttributes()); @@ -261,11 +266,15 @@ protected SdkClientConfiguration mergeChildDefaults(SdkClientConfiguration confi private SdkClientConfiguration mergeGlobalDefaults(SdkClientConfiguration configuration) { // Don't load the default profile file if the customer already gave us one. ProfileFile configuredProfileFile = configuration.option(PROFILE_FILE); - ProfileFile profileFile = configuredProfileFile != null ? configuredProfileFile : ProfileFile.defaultProfileFile(); + ProfileFile profileFile = Optional.ofNullable(configuredProfileFile).orElse(ProfileFile.defaultProfileFile()); + + Supplier configuredProfileFileSupplier = configuration.option(PROFILE_FILE_SUPPLIER); + Supplier profileFileSupplier = Optional.ofNullable(configuredProfileFileSupplier).orElse(() -> profileFile); return configuration.merge(c -> c.option(EXECUTION_INTERCEPTORS, new ArrayList<>()) .option(ADDITIONAL_HTTP_HEADERS, new LinkedHashMap<>()) .option(PROFILE_FILE, profileFile) + .option(PROFILE_FILE_SUPPLIER, profileFileSupplier) .option(PROFILE_NAME, ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow()) .option(USER_AGENT_PREFIX, SdkUserAgent.create().userAgent()) .option(USER_AGENT_SUFFIX, "") @@ -330,7 +339,9 @@ private RetryPolicy resolveRetryPolicy(SdkClientConfiguration config) { } RetryMode retryMode = RetryMode.resolver() - .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) + .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? + config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : + () -> config.option(SdkClientOption.PROFILE_FILE)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/endpointdiscovery/providers/DefaultEndpointDiscoveryProviderChain.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/endpointdiscovery/providers/DefaultEndpointDiscoveryProviderChain.java index 8c814799998d..742d9fa28eda 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/endpointdiscovery/providers/DefaultEndpointDiscoveryProviderChain.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/endpointdiscovery/providers/DefaultEndpointDiscoveryProviderChain.java @@ -30,7 +30,9 @@ public DefaultEndpointDiscoveryProviderChain() { } public DefaultEndpointDiscoveryProviderChain(SdkClientConfiguration clientConfiguration) { - this(() -> clientConfiguration.option(SdkClientOption.PROFILE_FILE), + this(clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? + clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : + () -> clientConfiguration.option(SdkClientOption.PROFILE_FILE), clientConfiguration.option(SdkClientOption.PROFILE_NAME)); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java index 9d42d033f677..dfb79550a547 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.core.interceptor; import java.net.URI; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.ClientType; import software.amazon.awssdk.core.ServiceConfiguration; @@ -77,6 +78,9 @@ public class SdkExecutionAttribute { public static final ExecutionAttribute PROFILE_FILE = new ExecutionAttribute<>("ProfileFile"); + public static final ExecutionAttribute> PROFILE_FILE_SUPPLIER = + new ExecutionAttribute<>("ProfileFileSupplier"); + public static final ExecutionAttribute PROFILE_NAME = new ExecutionAttribute<>("ProfileName"); /** diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java index e6a493d69ae8..5f8a404b65ae 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java @@ -201,6 +201,8 @@ private static InterceptorContext runModifyHttpRequestAndHttpContentInterceptors clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION)) .putAttribute(SdkExecutionAttribute.SERVICE_NAME, clientConfiguration.option(SdkClientOption.SERVICE_NAME)) .putAttribute(SdkExecutionAttribute.PROFILE_FILE, clientConfiguration.option(SdkClientOption.PROFILE_FILE)) + .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, + clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, clientConfiguration.option(SdkClientOption.PROFILE_NAME)); ExecutionInterceptorChain interceptorChain = diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java index d8f8dcb0f921..8bb2455099ba 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java @@ -35,6 +35,7 @@ import static software.amazon.awssdk.core.client.config.SdkClientOption.EXECUTION_INTERCEPTORS; import static software.amazon.awssdk.core.client.config.SdkClientOption.METRIC_PUBLISHERS; import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_FILE; +import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_FILE_SUPPLIER; import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_NAME; import static software.amazon.awssdk.core.client.config.SdkClientOption.RETRY_POLICY; import static software.amazon.awssdk.core.internal.SdkInternalTestAdvancedClientOption.ENDPOINT_OVERRIDDEN_OVERRIDE; @@ -59,12 +60,11 @@ import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; -import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.interceptor.ExecutionAttribute; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; -import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.core.signer.NoOpSigner; import software.amazon.awssdk.core.signer.Signer; @@ -261,6 +261,7 @@ public void close() { assertThat(config.option(API_CALL_ATTEMPT_TIMEOUT)).isEqualTo(apiCallAttemptTimeout); assertThat(config.option(DISABLE_HOST_PREFIX_INJECTION)).isEqualTo(Boolean.TRUE); assertThat(config.option(PROFILE_FILE)).isEqualTo(profileFile); + assertThat(config.option(PROFILE_FILE_SUPPLIER).get()).isEqualTo(profileFile); assertThat(config.option(PROFILE_NAME)).isEqualTo(profileName); assertThat(config.option(METRIC_PUBLISHERS)).contains(metricPublisher); assertThat(config.option(EXECUTION_ATTRIBUTES).getAttribute(execAttribute)).isEqualTo("value"); diff --git a/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java b/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java index 24bb975020db..c496f811c758 100644 --- a/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java +++ b/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java @@ -162,7 +162,9 @@ private URI createEndpoint(String regionName, String serviceName, ExecutionAttri return new DefaultServiceEndpointBuilder(SERVICE_NAME, Protocol.HTTPS.toString()) .withRegion(region) - .withProfileFile(() -> attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE)) + .withProfileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) != null ? + attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) : + () -> attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE)) .withProfileName(attributes.getAttribute(SdkExecutionAttribute.PROFILE_NAME)) .withDualstackEnabled(attributes.getAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) .withFipsEnabled(attributes.getAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED)) diff --git a/services/docdb/src/test/java/software/amazon/awssdk/services/docdb/internal/PresignRequestHandlerTest.java b/services/docdb/src/test/java/software/amazon/awssdk/services/docdb/internal/PresignRequestHandlerTest.java index 4ffb5f70142f..00c446ed39ba 100644 --- a/services/docdb/src/test/java/software/amazon/awssdk/services/docdb/internal/PresignRequestHandlerTest.java +++ b/services/docdb/src/test/java/software/amazon/awssdk/services/docdb/internal/PresignRequestHandlerTest.java @@ -165,6 +165,8 @@ private ExecutionAttributes executionAttributes() { return new ExecutionAttributes().putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CREDENTIALS) .putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, DESTINATION_REGION) .putAttribute(SdkExecutionAttribute.PROFILE_FILE, ProfileFile.defaultProfileFile()) + .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, + ProfileFile::defaultProfileFile) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, "default"); } diff --git a/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java b/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java index b8ddfb4632d0..a0a2b6e55d2b 100644 --- a/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java +++ b/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java @@ -64,7 +64,9 @@ public static RetryPolicy resolveRetryPolicy(SdkClientConfiguration config) { } RetryMode retryMode = RetryMode.resolver() - .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) + .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? + config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : + () -> config.option(SdkClientOption.PROFILE_FILE)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); diff --git a/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java b/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java index c99de392ed52..6b4863c996d6 100644 --- a/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java +++ b/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java @@ -8,12 +8,16 @@ import org.junit.jupiter.api.Test; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.core.retry.backoff.BackoffStrategy; import software.amazon.awssdk.core.retry.backoff.FullJitterBackoffStrategy; +import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.utils.StringInputStream; -public class DynamoDbRetryPolicyTest { +class DynamoDbRetryPolicyTest { private EnvironmentVariableHelper environmentVariableHelper; @@ -28,7 +32,7 @@ public void reset() { } @Test - public void test_numRetries_with_standardRetryPolicy() { + void test_numRetries_with_standardRetryPolicy() { environmentVariableHelper.set(SdkSystemSetting.AWS_RETRY_MODE.environmentVariable(), "standard"); final SdkClientConfiguration sdkClientConfiguration = SdkClientConfiguration.builder().build(); final RetryPolicy retryPolicy = DynamoDbRetryPolicy.resolveRetryPolicy(sdkClientConfiguration); @@ -36,7 +40,7 @@ public void test_numRetries_with_standardRetryPolicy() { } @Test - public void test_numRetries_with_legacyRetryPolicy() { + void test_numRetries_with_legacyRetryPolicy() { environmentVariableHelper.set(SdkSystemSetting.AWS_RETRY_MODE.environmentVariable(), "legacy"); final SdkClientConfiguration sdkClientConfiguration = SdkClientConfiguration.builder().build(); final RetryPolicy retryPolicy = DynamoDbRetryPolicy.resolveRetryPolicy(sdkClientConfiguration); @@ -44,7 +48,7 @@ public void test_numRetries_with_legacyRetryPolicy() { } @Test - public void test_backoffBaseDelay_with_standardRetryPolicy() { + void test_backoffBaseDelay_with_standardRetryPolicy() { environmentVariableHelper.set(SdkSystemSetting.AWS_RETRY_MODE.environmentVariable(), "standard"); SdkClientConfiguration sdkClientConfiguration = SdkClientConfiguration.builder().build(); RetryPolicy retryPolicy = DynamoDbRetryPolicy.resolveRetryPolicy(sdkClientConfiguration); @@ -55,4 +59,86 @@ public void test_backoffBaseDelay_with_standardRetryPolicy() { }); } + @Test + void resolve_retryModeSetInEnv_doesNotCallSupplier() { + environmentVariableHelper.set(SdkSystemSetting.AWS_RETRY_MODE.environmentVariable(), "standard"); + SdkClientConfiguration sdkClientConfiguration = SdkClientConfiguration.builder().build(); + RetryPolicy retryPolicy = DynamoDbRetryPolicy.resolveRetryPolicy(sdkClientConfiguration); + RetryMode retryMode = retryPolicy.retryMode(); + + assertThat(retryMode).isEqualTo(RetryMode.STANDARD); + } + + @Test + void resolve_retryModeSetWithEnvAndSupplier_resolvesFromEnv() { + environmentVariableHelper.set(SdkSystemSetting.AWS_RETRY_MODE.environmentVariable(), "standard"); + ProfileFile profileFile = ProfileFile.builder() + .content(new StringInputStream("[profile default]\n" + + "retry_mode = adaptive")) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + SdkClientConfiguration sdkClientConfiguration = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, () -> profileFile) + .option(SdkClientOption.PROFILE_NAME, "default") + .build(); + RetryPolicy retryPolicy = DynamoDbRetryPolicy.resolveRetryPolicy(sdkClientConfiguration); + RetryMode retryMode = retryPolicy.retryMode(); + + assertThat(retryMode).isEqualTo(RetryMode.STANDARD); + } + + @Test + void resolve_retryModeSetWithSupplier_resolvesFromSupplier() { + ProfileFile profileFile = ProfileFile.builder() + .content(new StringInputStream("[profile default]\n" + + "retry_mode = adaptive")) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + SdkClientConfiguration sdkClientConfiguration = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, () -> profileFile) + .option(SdkClientOption.PROFILE_NAME, "default") + .build(); + RetryPolicy retryPolicy = DynamoDbRetryPolicy.resolveRetryPolicy(sdkClientConfiguration); + RetryMode retryMode = retryPolicy.retryMode(); + + assertThat(retryMode).isEqualTo(RetryMode.ADAPTIVE); + } + + @Test + void resolve_retryModeSetWithSdkClientOption_resolvesFromSdkClientOption() { + ProfileFile profileFile = ProfileFile.builder() + .content(new StringInputStream("[profile default]\n")) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + SdkClientConfiguration sdkClientConfiguration = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, () -> profileFile) + .option(SdkClientOption.PROFILE_NAME, "default") + .option(SdkClientOption.DEFAULT_RETRY_MODE, RetryMode.STANDARD) + .build(); + RetryPolicy retryPolicy = DynamoDbRetryPolicy.resolveRetryPolicy(sdkClientConfiguration); + RetryMode retryMode = retryPolicy.retryMode(); + + assertThat(retryMode).isEqualTo(RetryMode.STANDARD); + } + + @Test + void resolve_retryModeNotSetWithEnvNorSupplier_resolvesFromSdkDefault() { + ProfileFile profileFile = ProfileFile.builder() + .content(new StringInputStream("[profile default]\n")) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + SdkClientConfiguration sdkClientConfiguration = SdkClientConfiguration + .builder() + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, () -> profileFile) + .option(SdkClientOption.PROFILE_NAME, "default") + .build(); + RetryPolicy retryPolicy = DynamoDbRetryPolicy.resolveRetryPolicy(sdkClientConfiguration); + RetryMode retryMode = retryPolicy.retryMode(); + + assertThat(retryMode).isEqualTo(RetryMode.LEGACY); + } + } diff --git a/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java b/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java index 621993a91f4f..b2d48aae82ae 100644 --- a/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java +++ b/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java @@ -40,7 +40,9 @@ public static RetryPolicy resolveRetryPolicy(SdkClientConfiguration config) { } RetryMode retryMode = RetryMode.resolver() - .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) + .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? + config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : + () -> config.option(SdkClientOption.PROFILE_FILE)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); diff --git a/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java b/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java index d0b0e9faf558..b05955b3880f 100644 --- a/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java +++ b/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java @@ -164,7 +164,9 @@ private URI createEndpoint(String regionName, String serviceName, ExecutionAttri return new DefaultServiceEndpointBuilder(SERVICE_NAME, Protocol.HTTPS.toString()) .withRegion(region) - .withProfileFile(() -> attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE)) + .withProfileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) != null ? + attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) : + () -> attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE)) .withProfileName(attributes.getAttribute(SdkExecutionAttribute.PROFILE_NAME)) .withDualstackEnabled(attributes.getAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) .withFipsEnabled(attributes.getAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED)) diff --git a/services/neptune/src/test/java/software/amazon/awssdk/services/neptune/internal/PresignRequestHandlerTest.java b/services/neptune/src/test/java/software/amazon/awssdk/services/neptune/internal/PresignRequestHandlerTest.java index 9ed640a617f4..490d4671cd6d 100644 --- a/services/neptune/src/test/java/software/amazon/awssdk/services/neptune/internal/PresignRequestHandlerTest.java +++ b/services/neptune/src/test/java/software/amazon/awssdk/services/neptune/internal/PresignRequestHandlerTest.java @@ -165,6 +165,8 @@ private ExecutionAttributes executionAttributes() { return new ExecutionAttributes().putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CREDENTIALS) .putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, DESTINATION_REGION) .putAttribute(SdkExecutionAttribute.PROFILE_FILE, ProfileFile.defaultProfileFile()) + .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, + ProfileFile::defaultProfileFile) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, "default"); } diff --git a/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java b/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java index 6f4dc67954de..6bf7c6948bb3 100644 --- a/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java +++ b/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java @@ -162,7 +162,9 @@ private URI createEndpoint(String regionName, String serviceName, ExecutionAttri return new DefaultServiceEndpointBuilder(SERVICE_NAME, Protocol.HTTPS.toString()) .withRegion(region) - .withProfileFile(() -> attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE)) + .withProfileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) != null ? + attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) : + () -> attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE)) .withProfileName(attributes.getAttribute(SdkExecutionAttribute.PROFILE_NAME)) .withDualstackEnabled(attributes.getAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) .withFipsEnabled(attributes.getAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED)) diff --git a/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java b/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java index e79a81d0b003..4b82f1518fef 100644 --- a/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java +++ b/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java @@ -166,6 +166,8 @@ private ExecutionAttributes executionAttributes() { return new ExecutionAttributes().putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CREDENTIALS) .putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, DESTINATION_REGION) .putAttribute(SdkExecutionAttribute.PROFILE_FILE, ProfileFile.defaultProfileFile()) + .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, + ProfileFile::defaultProfileFile) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, "default"); } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java index 4462b75add0a..5dec8e93602f 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java @@ -242,7 +242,7 @@ public interface Builder extends CopyableBuilder { Boolean accelerateModeEnabled(); /** - * Option to enable using the accelerate enedpoint when accessing S3. Accelerate + * Option to enable using the accelerate endpoint when accessing S3. Accelerate * endpoints allow faster transfer of objects by using Amazon CloudFront's * globally distributed edge locations. * diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java index fd5aba1897ab..2e8233843968 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java @@ -21,6 +21,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; import software.amazon.awssdk.annotations.Immutable; @@ -48,6 +49,7 @@ import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; import software.amazon.awssdk.protocols.core.OperationInfo; import software.amazon.awssdk.protocols.core.PathMarshaller; import software.amazon.awssdk.protocols.core.ProtocolUtils; @@ -61,7 +63,6 @@ import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetUrlRequest; import software.amazon.awssdk.utils.AttributeMap; -import software.amazon.awssdk.utils.Lazy; import software.amazon.awssdk.utils.Validate; /** @@ -75,7 +76,6 @@ * GetUrlRequest request = GetUrlRequest.builder().bucket("foo-bucket").key("key-without-spaces").build(); * URL url = utilities.getUrl(request); * - *

* *

* 2) Using the low-level client {@link S3Client#utilities()} method. This is recommended as SDK will use the same @@ -87,7 +87,6 @@ * GetUrlRequest request = GetUrlRequest.builder().bucket("foo-bucket").key("key-without-spaces").build(); * URL url = utilities.getUrl(request); * - *

* * Note: This class does not make network calls. */ @@ -112,8 +111,8 @@ public final class S3Utilities { private S3Utilities(Builder builder) { this.region = Validate.paramNotNull(builder.region, "Region"); this.endpoint = builder.endpoint; - this.profileFile = builder.profileFile != null ? () -> builder.profileFile - : new Lazy<>(ProfileFile::defaultProfileFile)::getValue; + this.profileFile = Optional.ofNullable(builder.profileFile) + .orElse(ProfileFile::defaultProfileFile); this.profileName = builder.profileName; if (builder.s3Configuration == null) { @@ -172,7 +171,9 @@ static S3Utilities create(SdkClientConfiguration clientConfiguration) { S3Utilities.Builder builder = builder() .region(clientConfiguration.option(AwsClientOption.AWS_REGION)) .s3Configuration((S3Configuration) clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION)) - .profileFile(clientConfiguration.option(SdkClientOption.PROFILE_FILE)) + .profileFile(clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? + clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : + () -> clientConfiguration.option(SdkClientOption.PROFILE_FILE)) .profileName(clientConfiguration.option(SdkClientOption.PROFILE_NAME)); if (Boolean.TRUE.equals(clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { @@ -362,7 +363,7 @@ private UseGlobalEndpointResolver createUseGlobalEndpointResolver() { SdkClientConfiguration config = SdkClientConfiguration.builder() .option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, standardOption) - .option(SdkClientOption.PROFILE_FILE, profileFile.get()) + .option(SdkClientOption.PROFILE_FILE_SUPPLIER, profileFile) .option(SdkClientOption.PROFILE_NAME, profileName) .build(); @@ -377,7 +378,7 @@ public static final class Builder { private URI endpoint; private S3Configuration s3Configuration; - private ProfileFile profileFile; + private Supplier profileFile; private String profileName; private Boolean dualstackEnabled; private Boolean fipsEnabled; @@ -468,7 +469,13 @@ public Builder s3Configuration(S3Configuration s3Configuration) { * confusing to support the full {@link ClientOverrideConfiguration} object in the future. */ private Builder profileFile(ProfileFile profileFile) { - this.profileFile = profileFile; + return profileFile(Optional.ofNullable(profileFile) + .map(ProfileFileSupplier::fixedProfileFile) + .orElse(null)); + } + + private Builder profileFile(Supplier profileFileSupplier) { + this.profileFile = profileFileSupplier; return this; } diff --git a/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/SsoOidcProfileTokenProviderFactory.java b/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/SsoOidcProfileTokenProviderFactory.java index 2c39a58f5939..e5f1bd224d3e 100644 --- a/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/SsoOidcProfileTokenProviderFactory.java +++ b/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/SsoOidcProfileTokenProviderFactory.java @@ -15,11 +15,14 @@ package software.amazon.awssdk.services.ssooidc; +import java.util.Objects; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; import software.amazon.awssdk.auth.token.credentials.ChildProfileTokenProviderFactory; import software.amazon.awssdk.auth.token.credentials.SdkToken; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.profiles.Profile; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileProperty; @@ -39,7 +42,59 @@ public final class SsoOidcProfileTokenProviderFactory implements ChildProfileTok @Override public SdkTokenProvider create(ProfileFile profileFile, Profile profile) { - return new SsooidcProfileTokenProvider(profileFile, profile); + return new SsoOidcProfileTokenProvider(profileFile, profile); + } + + public SdkTokenProvider create(Supplier profileFile, String profileName) { + return new SsoOidcSuppliedProfileTokenProvider(profileFile, profileName); + } + + private static final class SsoOidcSuppliedProfileTokenProvider implements SdkTokenProvider, SdkAutoCloseable { + + private final Supplier profileFile; + private final String profileName; + private volatile ProfileFile currentProfileFile; + private volatile SsoOidcProfileTokenProvider currentTokenProvider; + + private SsoOidcSuppliedProfileTokenProvider(Supplier profileFile, String profileName) { + this.profileFile = profileFile; + this.profileName = profileName; + } + + @Override + public SdkToken resolveToken() { + return sdkTokenProvider().resolveToken(); + } + + private SdkTokenProvider sdkTokenProvider() { + ProfileFile profileFileInstance = profileFile.get(); + if (!Objects.equals(profileFileInstance, currentProfileFile)) { + synchronized (this) { + if (!Objects.equals(profileFileInstance, currentProfileFile)) { + Profile profileInstance = resolveProfile(profileFileInstance, profileName); + currentProfileFile = profileFileInstance; + currentTokenProvider = new SsoOidcProfileTokenProvider(profileFileInstance, profileInstance); + } + } + } + + return currentTokenProvider; + } + + private Profile resolveProfile(ProfileFile profileFile, String profileName) { + return profileFile.profile(profileName) + .orElseThrow(() -> { + String errorMessage = String.format("Profile file contained no information for profile" + + "'%s': %s", + profileName, profileFile); + return SdkClientException.builder().message(errorMessage).build(); + }); + } + + @Override + public void close() { + IoUtils.closeQuietly(currentTokenProvider, null); + } } @@ -47,10 +102,10 @@ public SdkTokenProvider create(ProfileFile profileFile, Profile profile) { * A wrapper for a {@link SdkTokenProvider} that is returned by this factory when {@link #create(ProfileFile,Profile)} is * invoked. This wrapper is important because it ensures the token provider is closed when it is no longer needed. */ - private static final class SsooidcProfileTokenProvider implements SdkTokenProvider, SdkAutoCloseable { + private static final class SsoOidcProfileTokenProvider implements SdkTokenProvider, SdkAutoCloseable { private final SsoOidcTokenProvider sdkTokenProvider; - private SsooidcProfileTokenProvider(ProfileFile profileFile, Profile profile) { + private SsoOidcProfileTokenProvider(ProfileFile profileFile, Profile profile) { String profileSsoSectionName = profile.property( ProfileSection.SSO_SESSION .getPropertyKeyName()).orElseThrow(() -> new IllegalStateException( diff --git a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcProfileTokenProviderFactoryTest.java b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcProfileTokenProviderFactoryTest.java index c7051474c6e9..0abb69f3bf4c 100644 --- a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcProfileTokenProviderFactoryTest.java +++ b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcProfileTokenProviderFactoryTest.java @@ -60,8 +60,8 @@ private static Stream ssoSessionMissingProperties() { void create_throwsExceptionIfRegionNotPassed() { String startUrl = "https://my-start-url.com"; Assertions.assertThatExceptionOfType(NullPointerException.class).isThrownBy( - () -> SdkTokenProviderFactoryProperties.builder(). - startUrl(startUrl) + () -> SdkTokenProviderFactoryProperties.builder() + .startUrl(startUrl) .build() ).withMessage("region must not be null."); } @@ -71,8 +71,8 @@ void create_throwsExceptionIfStartUrlNotPassed() { String region = "test-region"; Assertions.assertThatExceptionOfType(NullPointerException.class).isThrownBy( - () -> SdkTokenProviderFactoryProperties.builder(). - region(region) + () -> SdkTokenProviderFactoryProperties.builder() + .region(region) .build() ).withMessage("startUrl must not be null."); } @@ -95,7 +95,23 @@ void create_SsooidcTokenProvider_from_SsooidcSpecificProfile() { } @Test - void create_SsooidcTokenProvider_with_ssoAccountIdInProfile() { + void create_SsoOidcTokenProvider_from_SsooidcSpecificProfileSupplier() { + String profileContent = "[profile ssotoken]\n" + + "sso_session=admin\n" + + "[sso-session admin]\n" + + "sso_region=us-east-1\n" + + "sso_start_url= https://start-url\n"; + ProfileFile profiles = ProfileFile.builder() + .content(new StringInputStream(profileContent)) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + SdkTokenProvider sdkTokenProvider = new SsoOidcProfileTokenProviderFactory().create(() -> profiles, "ssotoken"); + Assertions.assertThat(sdkTokenProvider).isNotNull(); + + } + + @Test + void create_SsoOidcTokenProvider_with_ssoAccountIdInProfile() { String profileContent = "[profile sso]\n" + "sso_region=us-east-1\n" + "sso_account_id=1234567\n" + @@ -111,7 +127,7 @@ void create_SsooidcTokenProvider_with_ssoAccountIdInProfile() { } @Test - void create_SsooidcTokenProvider_with_ssoRoleNameInProfile() { + void create_SsoOidcTokenProvider_with_ssoRoleNameInProfile() { String profileContent = "[profile sso]\n" + "sso_region=us-east-1\n" + "sso_role_name=ssoSpecificRole\n" + @@ -126,6 +142,22 @@ void create_SsooidcTokenProvider_with_ssoRoleNameInProfile() { } + @Test + void create_SsoOidcTokenProvider_with_ssoRoleNameInProfileSupplier() { + String profileContent = "[profile sso]\n" + + "sso_region=us-east-1\n" + + "sso_role_name=ssoSpecificRole\n" + + "sso_start_url= https://start-url\n"; + ProfileFile profiles = ProfileFile.builder() + .content(new StringInputStream(profileContent)) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + + Assertions.assertThatNoException() + .isThrownBy(() -> new SsoOidcProfileTokenProviderFactory().create(() -> profiles, "sso")); + + } + @ParameterizedTest @MethodSource("ssoSessionMissingProperties") void incorrectSsoProperties_throwsException(String ssoProfileContent, String msg) { From b6e29f7a05ca865cfc59d40ef3c20df1f04865e5 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Tue, 24 Jan 2023 13:51:36 -0700 Subject: [PATCH 11/16] Updated changelog entry (#3699) --- .changes/next-release/feature-AWSSDKforJavav2-688d30e.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json b/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json index 7c53e085c72c..e94f61f9988b 100644 --- a/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json +++ b/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json @@ -2,5 +2,5 @@ "category": "AWS SDK for Java v2", "contributor": "", "type": "feature", - "description": "Created ReloadingProfileCredentialsProvider to reload credentials due to disk changes" + "description": "- ProfileCredentialsProvider and ProfileTokenProvider can reload credentials when disk profile changes\n- Updated DefaultCredentialsProvider chain for reloading credentials\n- Service support classes store ProfileFile as a Supplier interface\n- SdkClientOption and SdkExecutionAttributes retrieve value for PROFILE_FILE_SUPPLIER first and fallback to PROFILE_FILE when unavailable" } From 0890ecf4e070ded8518335621a6dabbae675c287 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Thu, 26 Jan 2023 11:07:45 -0700 Subject: [PATCH 12/16] Fixed review comments --- .../ProfileCredentialsProvider.java | 3 - .../awssdk/profiles/ProfileFileSupplier.java | 19 +- .../profiles/ProfileFileSupplierBuilder.java | 5 - .../internal/ProfileFileRefresher.java | 14 +- .../profiles/ProfileFileSupplierTest.java | 28 --- .../internal/ProfileFileRefresherTest.java | 185 +++++++++--------- 6 files changed, 94 insertions(+), 160 deletions(-) diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java index c2eb3424bf55..c35bc5ed40c7 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java @@ -151,9 +151,6 @@ public String toString() { @Override public void close() { - if (profileFile instanceof SdkAutoCloseable) { - ((SdkAutoCloseable) profileFile).close(); - } // The delegate credentials provider may be closeable (eg. if it's an STS credentials provider). In this case, we should // clean it up when this credentials provider is closed. IoUtils.closeIfCloseable(credentialsProvider, null); diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java index 79e8881e69af..de3be28c99f5 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java @@ -16,14 +16,12 @@ package software.amazon.awssdk.profiles; import java.nio.file.Path; -import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.profiles.internal.ProfileFileRefresher; -import software.amazon.awssdk.utils.SdkAutoCloseable; /** * Encapsulates the logic for supplying either a single or multiple ProfileFile instances. @@ -33,11 +31,7 @@ */ @SdkPublicApi @FunctionalInterface -public interface ProfileFileSupplier extends Supplier, SdkAutoCloseable { - - @Override - default void close() { - } +public interface ProfileFileSupplier extends Supplier { /** * Creates a {@link ProfileFileSupplier} capable of producing multiple profile objects by aggregating the default @@ -58,7 +52,7 @@ static ProfileFileSupplier defaultSupplier() { = ProfileFileLocation.configurationFileLocation() .map(path -> reloadWhenModified(path, ProfileFile.Type.CONFIGURATION)); - ProfileFileSupplier supplier = () -> null; + ProfileFileSupplier supplier = () -> ProfileFile.builder().build(); if (credentialsSupplierOptional.isPresent() && configurationSupplierOptional.isPresent()) { supplier = aggregate(credentialsSupplierOptional.get(), configurationSupplierOptional.get()); } else if (credentialsSupplierOptional.isPresent()) { @@ -97,10 +91,6 @@ public ProfileFile get() { return refresher.refreshIfStale(); } - @Override - public void close() { - refresher.close(); - } }; } @@ -139,11 +129,6 @@ public ProfileFile get() { return refreshAndGetCurrentAggregate(aggregator); } - @Override - public void close() { - Arrays.stream(suppliers).forEach(ProfileFileSupplier::close); - } - private ProfileFile refreshAndGetCurrentAggregate(ProfileFile.Aggregator aggregator) { ProfileFile current = currentAggregateProfileFile.get(); ProfileFile next = aggregator.build(); diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java index 3df150da4277..6e3aea7cf5bf 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java @@ -96,11 +96,6 @@ static ProfileFileSupplier fromBuilder(ProfileFileSupplierBuilder builder) { public ProfileFile get() { return refresher.refreshIfStale(); } - - @Override - public void close() { - refresher.close(); - } }; } diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java index 4740a36577c3..d1e8bdda9b57 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java @@ -28,7 +28,6 @@ import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; import software.amazon.awssdk.profiles.ProfileFile; -import software.amazon.awssdk.utils.SdkAutoCloseable; import software.amazon.awssdk.utils.cache.CachedSupplier; import software.amazon.awssdk.utils.cache.RefreshResult; @@ -36,7 +35,7 @@ * Class used for caching and reloading ProfileFile objects from a Supplier. */ @SdkInternalApi -public final class ProfileFileRefresher implements SdkAutoCloseable { +public final class ProfileFileRefresher { private static final ProfileFileRefreshRecord EMPTY_REFRESH_RECORD = ProfileFileRefreshRecord.builder() .refreshTime(Instant.MIN) @@ -81,30 +80,23 @@ public ProfileFile refreshIfStale() { return cachedOrRefreshedProfileFile; } - @Override - public void close() { - profileFileCache.close(); - } - private RefreshResult refreshResult() { try { return reloadAsRefreshResultIfStale(); } catch (RuntimeException exception) { Instant now = Instant.now(); - Instant staleTime = now; ProfileFile exceptionProfileFile = exceptionHandler.apply(exception); ProfileFileRefreshRecord refreshRecord = ProfileFileRefreshRecord.builder() .profileFile(exceptionProfileFile) .refreshTime(now) .build(); - return wrapIntoRefreshResult(refreshRecord, staleTime); + return wrapIntoRefreshResult(refreshRecord, now); } } private RefreshResult reloadAsRefreshResultIfStale() { Instant now = clock.instant(); - Instant staleTime = now; ProfileFileRefreshRecord refreshRecord; if (canReloadProfileFile() || hasNotBeenPreviouslyLoaded()) { @@ -117,7 +109,7 @@ private RefreshResult reloadAsRefreshResultIfStale() { refreshRecord = currentRefreshRecord; } - return wrapIntoRefreshResult(refreshRecord, staleTime); + return wrapIntoRefreshResult(refreshRecord, now); } private RefreshResult wrapIntoRefreshResult(T value, Instant staleTime) { diff --git a/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java b/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java index 7c098e2c5b72..e51bc53101f0 100644 --- a/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java +++ b/core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java @@ -81,8 +81,6 @@ void get_profileFileFixed_doesNotReloadProfileFile() { ProfileFile file2 = supplier.get(); assertThat(file2).isSameAs(file1); - - supplier.close(); } @Test @@ -104,8 +102,6 @@ void get_profileModifiedWithinJitterPeriod_doesNotReloadCredentials() { ProfileFile file2 = supplier.get(); assertThat(file2).isSameAs(file1); - - supplier.close(); } @Test @@ -141,8 +137,6 @@ void get_profileModifiedOutsideJitterPeriod_reloadsCredentials() { String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); assertThat(awsSecretAccessKey).isEqualTo("modifiedSecretAccessKey"); }); - - supplier.close(); } @Test @@ -164,8 +158,6 @@ void get_profileModified_reloadsProfileFile() { ProfileFile file2 = supplier.get(); assertThat(file2).isNotSameAs(file1); - - supplier.close(); } @Test @@ -189,8 +181,6 @@ void get_profileModifiedOnceButRefreshedMultipleTimes_reloadsProfileFileOnce() { assertThat(file2).isSameAs(file1); assertThat(file3).isNotSameAs(file2); - - supplier.close(); } @Test @@ -227,8 +217,6 @@ void get_profileModifiedMultipleTimes_reloadsProfileFileOncePerChange() { assertThat(file3).isNotSameAs(file2); assertThat(file4).isNotSameAs(file3); assertThat(file5).isSameAs(file4); - - supplier.close(); } @Test @@ -252,8 +240,6 @@ void get_supplierBuiltByReloadWhenModified_loadsProfileFile() { String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); assertThat(awsSecretAccessKey).isEqualTo("defaultSecretAccessKey"); }); - - supplier.close(); } @Test @@ -280,8 +266,6 @@ void get_supplierBuiltByFixedProfileFile_returnsProfileFile() { String awsSecretAccessKey = awsSecretAccessKeyOptional.get(); assertThat(awsSecretAccessKey).isEqualTo("defaultSecretAccessKey"); }); - - supplier.close(); } @Test @@ -314,8 +298,6 @@ void get_supplierBuiltByReloadWhenModifiedAggregate_reloadsCredentials() { String region = regionOptional.get(); assertThat(region).isEqualTo("us-west-2"); }); - - supplier.close(); } @Test @@ -355,8 +337,6 @@ void get_supplierBuiltByFixedProfileFileAggregate_returnsAggregateProfileFileIns String region = regionOptional.get(); assertThat(region).isEqualTo("us-west-2"); }); - - supplier.close(); } @Test @@ -386,8 +366,6 @@ void aggregate_supplierReturnsSameInstanceMultipleTimesAggregatingProfileFile_ag .collect(Collectors.toList()); assertThat(suppliedProfileFiles).isEqualTo(distinctAggregateFiles); - - supplier.close(); } @Test @@ -430,8 +408,6 @@ void aggregate_supplierReturnsSameInstanceMultipleTimesAggregatingProfileFileSup .collect(Collectors.toList()); assertThat(suppliedProfileFiles).isEqualTo(distinctAggregateProfileFiles); - - supplier.close(); } @Test @@ -440,8 +416,6 @@ void fixedProfileFile_nullProfileFile_returnsNonNullSupplier() { ProfileFileSupplier supplier = ProfileFileSupplier.fixedProfileFile(file); assertThat(supplier).isNotNull(); - - supplier.close(); } @Test @@ -479,8 +453,6 @@ void get_givenOnLoadAction_callsActionOncePerNewProfileFile() { supplier.get(); assertThat(blockCount.get()).isEqualTo(actualProfilesCount); - - supplier.close(); } private Path generateTestFile(String contents, String filename) { diff --git a/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java b/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java index eda74dfc3bb2..d5ed3cfebbb7 100644 --- a/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java +++ b/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java @@ -61,21 +61,20 @@ void refreshIfStale_profileModifiedNoPathSpecified_doesNotReloadProfileFile() { Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); AdjustableClock clock = new AdjustableClock(); - try (ProfileFileRefresher refresher = refresherWithClock(clock) + ProfileFileRefresher refresher = refresherWithClock(clock) .profileFile(() -> profileFile(credentialsFilePath)) - .build()) { - Duration intervalWithinJitter = Duration.ofMillis(100); + .build(); + Duration intervalWithinJitter = Duration.ofMillis(100); - ProfileFile file1 = refresher.refreshIfStale(); + ProfileFile file1 = refresher.refreshIfStale(); - generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); - updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); - clock.tickForward(intervalWithinJitter); - ProfileFile file2 = refresher.refreshIfStale(); + clock.tickForward(intervalWithinJitter); + ProfileFile file2 = refresher.refreshIfStale(); - Assertions.assertThat(file2).isSameAs(file1); - } + Assertions.assertThat(file2).isSameAs(file1); } @Test @@ -83,22 +82,21 @@ void refreshIfStale_profileModifiedWithinJitterPeriod_doesNotReloadProfileFile() Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); AdjustableClock clock = new AdjustableClock(); - try (ProfileFileRefresher refresher = refresherWithClock(clock) + ProfileFileRefresher refresher = refresherWithClock(clock) .profileFile(() -> profileFile(credentialsFilePath)) .profileFilePath(credentialsFilePath) - .build()) { - Duration intervalWithinJitter = Duration.ofMillis(100); + .build(); + Duration intervalWithinJitter = Duration.ofMillis(100); - ProfileFile file1 = refresher.refreshIfStale(); + ProfileFile file1 = refresher.refreshIfStale(); - clock.tickForward(intervalWithinJitter); - generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); - updateModificationTime(credentialsFilePath, clock.instant()); + clock.tickForward(intervalWithinJitter); + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant()); - ProfileFile file2 = refresher.refreshIfStale(); + ProfileFile file2 = refresher.refreshIfStale(); - Assertions.assertThat(file2).isSameAs(file1); - } + Assertions.assertThat(file2).isSameAs(file1); } @Test @@ -106,22 +104,21 @@ void refreshIfStale_profileModifiedOutsideJitterPeriod_reloadsProfileFile() { Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); AdjustableClock clock = new AdjustableClock(); - try (ProfileFileRefresher refresher = refresherWithClock(clock) + ProfileFileRefresher refresher = refresherWithClock(clock) .profileFile(() -> profileFile(credentialsFilePath)) .profileFilePath(credentialsFilePath) - .build()) { - Duration intervalOutsideJitter = Duration.ofMillis(1_000); + .build(); + Duration intervalOutsideJitter = Duration.ofMillis(1_000); - ProfileFile file1 = refresher.refreshIfStale(); + ProfileFile file1 = refresher.refreshIfStale(); - clock.tickForward(intervalOutsideJitter); - generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); - updateModificationTime(credentialsFilePath, clock.instant()); + clock.tickForward(intervalOutsideJitter); + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant()); - ProfileFile file2 = refresher.refreshIfStale(); + ProfileFile file2 = refresher.refreshIfStale(); - Assertions.assertThat(file2).isNotSameAs(file1); - } + Assertions.assertThat(file2).isNotSameAs(file1); } @Test @@ -129,23 +126,22 @@ void refreshIfStale_profileModified_reloadsProfileFile() { Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); AdjustableClock clock = new AdjustableClock(); - try (ProfileFileRefresher refresher = refresherWithClock(clock) + ProfileFileRefresher refresher = refresherWithClock(clock) .profileFile(() -> profileFile(credentialsFilePath)) .profileFilePath(credentialsFilePath) - .build()) { + .build(); - Duration refreshInterval = Duration.ofSeconds(15); + Duration refreshInterval = Duration.ofSeconds(15); - ProfileFile file1 = refresher.refreshIfStale(); + ProfileFile file1 = refresher.refreshIfStale(); - generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); - updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); - clock.tickForward(refreshInterval.plusSeconds(10)); - ProfileFile file2 = refresher.refreshIfStale(); + clock.tickForward(refreshInterval.plusSeconds(10)); + ProfileFile file2 = refresher.refreshIfStale(); - Assertions.assertThat(file2).isNotSameAs(file1); - } + Assertions.assertThat(file2).isNotSameAs(file1); } @Test @@ -153,24 +149,23 @@ void refreshIfStale_profileModifiedOnceButRefreshedMultipleTimes_reloadsProfileF Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); AdjustableClock clock = new AdjustableClock(); - try (ProfileFileRefresher refresher = refresherWithClock(clock) + ProfileFileRefresher refresher = refresherWithClock(clock) .profileFile(() -> profileFile(credentialsFilePath)) .profileFilePath(credentialsFilePath) - .build()) { - ProfileFile file1 = refresher.refreshIfStale(); + .build(); + ProfileFile file1 = refresher.refreshIfStale(); - clock.tickForward(Duration.ofSeconds(5)); - ProfileFile file2 = refresher.refreshIfStale(); + clock.tickForward(Duration.ofSeconds(5)); + ProfileFile file2 = refresher.refreshIfStale(); - generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); - updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); - clock.tickForward(Duration.ofSeconds(5)); - ProfileFile file3 = refresher.refreshIfStale(); + clock.tickForward(Duration.ofSeconds(5)); + ProfileFile file3 = refresher.refreshIfStale(); - Assertions.assertThat(file2).isSameAs(file1); - Assertions.assertThat(file3).isNotSameAs(file2); - } + Assertions.assertThat(file2).isSameAs(file1); + Assertions.assertThat(file3).isNotSameAs(file2); } @Test @@ -178,37 +173,35 @@ void refreshIfStale_profileModifiedMultipleTimes_reloadsProfileFileOncePerChange Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); AdjustableClock clock = new AdjustableClock(); - try (ProfileFileRefresher refresher = refresherWithClock(clock) + ProfileFileRefresher refresher = refresherWithClock(clock) .profileFile(() -> profileFile(credentialsFilePath)) .profileFilePath(credentialsFilePath) - .build()) { - Duration duration = Duration.ofSeconds(5); + .build(); + Duration duration = Duration.ofSeconds(5); - ProfileFile file1 = refresher.refreshIfStale(); + ProfileFile file1 = refresher.refreshIfStale(); - clock.tickForward(duration); - ProfileFile file2 = refresher.refreshIfStale(); + clock.tickForward(duration); + ProfileFile file2 = refresher.refreshIfStale(); - generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); - updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); - clock.tickForward(duration); - ProfileFile file3 = refresher.refreshIfStale(); + clock.tickForward(duration); + ProfileFile file3 = refresher.refreshIfStale(); - generateTestCredentialsFile("updatedAccessKey", "updatedSecretAccessKey"); - updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + generateTestCredentialsFile("updatedAccessKey", "updatedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); - clock.tickForward(duration); - ProfileFile file4 = refresher.refreshIfStale(); + clock.tickForward(duration); + ProfileFile file4 = refresher.refreshIfStale(); - clock.tickForward(duration); - ProfileFile file5 = refresher.refreshIfStale(); + clock.tickForward(duration); + ProfileFile file5 = refresher.refreshIfStale(); - Assertions.assertThat(file2).isSameAs(file1); - Assertions.assertThat(file3).isNotSameAs(file2); - Assertions.assertThat(file4).isNotSameAs(file3); - Assertions.assertThat(file5).isSameAs(file4); - } + Assertions.assertThat(file2).isSameAs(file1); + Assertions.assertThat(file3).isNotSameAs(file2); + Assertions.assertThat(file4).isNotSameAs(file3); } @Test @@ -219,38 +212,37 @@ void refreshIfStale_givenOnReloadConsumer_callsConsumerOncePerChange() { Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); AdjustableClock clock = new AdjustableClock(); - try (ProfileFileRefresher refresher = refresherWithClock(clock) + ProfileFileRefresher refresher = refresherWithClock(clock) .profileFile(() -> profileFile(credentialsFilePath)) .profileFilePath(credentialsFilePath) .onProfileFileReload(f -> refreshOperationsCounter.incrementAndGet()) - .build()) { - Duration duration = Duration.ofSeconds(5); + .build(); + Duration duration = Duration.ofSeconds(5); - ProfileFile file1 = refresher.refreshIfStale(); + ProfileFile file1 = refresher.refreshIfStale(); - clock.tickForward(duration); - ProfileFile file2 = refresher.refreshIfStale(); + clock.tickForward(duration); + ProfileFile file2 = refresher.refreshIfStale(); - generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); - updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); - clock.tickForward(duration); - ProfileFile file3 = refresher.refreshIfStale(); + clock.tickForward(duration); + ProfileFile file3 = refresher.refreshIfStale(); - generateTestCredentialsFile("updatedAccessKey", "updatedSecretAccessKey"); - updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); + generateTestCredentialsFile("updatedAccessKey", "updatedSecretAccessKey"); + updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1)); - clock.tickForward(duration); - ProfileFile file4 = refresher.refreshIfStale(); + clock.tickForward(duration); + ProfileFile file4 = refresher.refreshIfStale(); - clock.tickForward(duration); - ProfileFile file5 = refresher.refreshIfStale(); + clock.tickForward(duration); + ProfileFile file5 = refresher.refreshIfStale(); - Assertions.assertThat(file2).isSameAs(file1); - Assertions.assertThat(file3).isNotSameAs(file2); - Assertions.assertThat(file4).isNotSameAs(file3); - Assertions.assertThat(file5).isSameAs(file4); - } + Assertions.assertThat(file2).isSameAs(file1); + Assertions.assertThat(file3).isNotSameAs(file2); + Assertions.assertThat(file4).isNotSameAs(file3); + Assertions.assertThat(file5).isSameAs(file4); Assertions.assertThat(refreshOperationsCounter.get()).isEqualTo(actualRefreshOperations); } @@ -261,12 +253,13 @@ void refreshIfStale_profileDeleted_returnsProfileFileFromExceptionHandler() { ProfileFile fallbackProfile = credentialFile("[test]\nx = y"); AdjustableClock clock = new AdjustableClock(); - try (ProfileFileRefresher refresher = refresherWithClock(clock) + ProfileFileRefresher refresher = refresherWithClock(clock) .profileFile(() -> profileFile(credentialsFilePath)) .profileFilePath(credentialsFilePath) .exceptionHandler(e -> fallbackProfile) - .build()) { + .build(); + try { Files.deleteIfExists(credentialsFilePath); ProfileFile file1 = refresher.refreshIfStale(); From adac97448e17046219cd3dc4d16af7db98556149 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Fri, 27 Jan 2023 13:11:12 -0700 Subject: [PATCH 13/16] Removed unnecessary logic --- .../awssdk/profiles/ProfileFileSupplier.java | 31 ++++++++++++++----- .../internal/ProfileFileRefresher.java | 26 +--------------- .../internal/ProfileFileRefresherTest.java | 22 ------------- 3 files changed, 24 insertions(+), 55 deletions(-) diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java index de3be28c99f5..1521fdcfbc2d 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java @@ -18,6 +18,7 @@ import java.nio.file.Path; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; @@ -105,8 +106,8 @@ static ProfileFileSupplier fixedProfileFile(ProfileFile profileFile) { } /** - * Creates a {@link ProfileFileSupplier} by combining the {@link ProfileFile} objects from two {@code ProfileFileSupplier}s. - * Objects are passed into {@link ProfileFile.Aggregator}. + * Creates a {@link ProfileFileSupplier} by combining the {@link ProfileFile} objects from multiple {@code + * ProfileFileSupplier}s. Objects are passed into {@link ProfileFile.Aggregator}. * * @param suppliers Array of {@code ProfileFileSupplier} objects. {@code ProfileFile} objects are passed to * {@link ProfileFile.Aggregator#addFile(ProfileFile)} in the same argument order as the supplier that @@ -118,25 +119,39 @@ static ProfileFileSupplier aggregate(ProfileFileSupplier... suppliers) { return new ProfileFileSupplier() { final AtomicReference currentAggregateProfileFile = new AtomicReference<>(); + final ConcurrentHashMap, ProfileFile> currentValuesBySupplier = new ConcurrentHashMap<>(); @Override public ProfileFile get() { - ProfileFile.Aggregator aggregator = ProfileFile.aggregator(); + boolean refreshAggregate = false; for (ProfileFileSupplier supplier : suppliers) { - aggregator.addFile(supplier.get()); + if (didSuppliedValueChange(supplier)) { + refreshAggregate = true; + } + } + + if (refreshAggregate) { + refreshCurrentAggregate(); } - return refreshAndGetCurrentAggregate(aggregator); + return currentAggregateProfileFile.get(); + } + + private boolean didSuppliedValueChange(Supplier supplier) { + ProfileFile next = supplier.get(); + ProfileFile current = currentValuesBySupplier.put(supplier, next); + + return !Objects.equals(next, current); } - private ProfileFile refreshAndGetCurrentAggregate(ProfileFile.Aggregator aggregator) { + private void refreshCurrentAggregate() { + ProfileFile.Aggregator aggregator = ProfileFile.aggregator(); + currentValuesBySupplier.values().forEach(aggregator::addFile); ProfileFile current = currentAggregateProfileFile.get(); ProfileFile next = aggregator.build(); if (!Objects.equals(current, next)) { currentAggregateProfileFile.compareAndSet(current, next); } - - return currentAggregateProfileFile.get(); } }; diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java index d1e8bdda9b57..a55ea28415b2 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java @@ -23,7 +23,6 @@ import java.time.Instant; import java.util.Objects; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; @@ -44,12 +43,10 @@ public final class ProfileFileRefresher { private volatile ProfileFileRefreshRecord currentRefreshRecord; private final Supplier profileFile; private final Path profileFilePath; - private final Function exceptionHandler; private final Consumer onProfileFileReload; private final Clock clock; private ProfileFileRefresher(Builder builder) { - this.exceptionHandler = builder.exceptionHandler; this.clock = builder.clock; this.profileFile = builder.profileFile; this.profileFilePath = builder.profileFilePath; @@ -81,18 +78,7 @@ public ProfileFile refreshIfStale() { } private RefreshResult refreshResult() { - try { - return reloadAsRefreshResultIfStale(); - } catch (RuntimeException exception) { - Instant now = Instant.now(); - ProfileFile exceptionProfileFile = exceptionHandler.apply(exception); - ProfileFileRefreshRecord refreshRecord = ProfileFileRefreshRecord.builder() - .profileFile(exceptionProfileFile) - .refreshTime(now) - .build(); - - return wrapIntoRefreshResult(refreshRecord, now); - } + return reloadAsRefreshResultIfStale(); } private RefreshResult reloadAsRefreshResultIfStale() { @@ -156,7 +142,6 @@ public static final class Builder { private Supplier profileFile; private Path profileFilePath; private Consumer onProfileFileReload = p -> { }; - private Function exceptionHandler; private Clock clock = Clock.systemUTC(); private Builder() { @@ -181,15 +166,6 @@ public Builder clock(Clock clock) { return this; } - /** - * @param exceptionHandler Handler which takes action when a Runtime exception occurs while loading a profile file. - * Handler can return a previously stored profile file or throw back the exception. - */ - public Builder exceptionHandler(Function exceptionHandler) { - this.exceptionHandler = exceptionHandler; - return this; - } - /** * Sets a custom action to perform when a profile file is reloaded. This action is executed when both the cache is stale * and the disk file associated with the profile file has been modified since the last load. diff --git a/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java b/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java index d5ed3cfebbb7..24755e1cc9a7 100644 --- a/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java +++ b/core/profiles/src/test/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresherTest.java @@ -247,28 +247,6 @@ void refreshIfStale_givenOnReloadConsumer_callsConsumerOncePerChange() { Assertions.assertThat(refreshOperationsCounter.get()).isEqualTo(actualRefreshOperations); } - @Test - void refreshIfStale_profileDeleted_returnsProfileFileFromExceptionHandler() { - Path credentialsFilePath = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey"); - ProfileFile fallbackProfile = credentialFile("[test]\nx = y"); - - AdjustableClock clock = new AdjustableClock(); - ProfileFileRefresher refresher = refresherWithClock(clock) - .profileFile(() -> profileFile(credentialsFilePath)) - .profileFilePath(credentialsFilePath) - .exceptionHandler(e -> fallbackProfile) - .build(); - - try { - Files.deleteIfExists(credentialsFilePath); - ProfileFile file1 = refresher.refreshIfStale(); - - Assertions.assertThat(file1).isSameAs(fallbackProfile); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - private ProfileFile credentialFile(String credentialFile) { return ProfileFile.builder() .content(new StringInputStream(credentialFile)) From bcb7a4be8b1e0eae0dcbc285ea443cfcd6342af3 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Wed, 1 Feb 2023 14:30:21 -0700 Subject: [PATCH 14/16] Deprecated SdkClientOption PROFILE_FILE --- .../poet/builder/BaseClientBuilderClass.java | 4 +- .../builder/test-client-builder-class.java | 6 +- .../builder/AwsDefaultClientBuilder.java | 31 +++----- .../builder/SdkDefaultClientBuilder.java | 13 +--- .../core/client/config/SdkClientOption.java | 10 ++- ...DefaultEndpointDiscoveryProviderChain.java | 4 +- .../dynamodb/DynamoDbRetryPolicy.java | 4 +- services/kinesis/pom.xml | 5 -- .../services/kinesis/KinesisRetryPolicy.java | 4 +- .../awssdk/services/s3/S3Utilities.java | 4 +- .../endpoints/UseGlobalEndpointResolver.java | 4 +- ...GlobalEndpointResolverProfileFileTest.java | 75 +++---------------- .../UseGlobalEndpointResolverTest.java | 2 +- 13 files changed, 43 insertions(+), 123 deletions(-) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java index 7fc8d198d551..2bca22172f0c 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java @@ -367,9 +367,7 @@ private void mergeServiceConfiguration(MethodSpec.Builder builder, String client ClassName clientConfigClass = ClassName.bestGuess(clientConfigClassName); builder.addCode("$1T.Builder serviceConfigBuilder = (($1T) config.option($2T.SERVICE_CONFIGURATION)).toBuilder();" + "serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFileSupplier() != null ? " - + "serviceConfigBuilder.profileFileSupplier() : config.option($2T.PROFILE_FILE_SUPPLIER) != null ? " - + "config.option($2T.PROFILE_FILE_SUPPLIER) : serviceConfigBuilder.profileFile() != null ? " - + "() -> serviceConfigBuilder.profileFile() : () -> config.option($2T.PROFILE_FILE));" + + "serviceConfigBuilder.profileFileSupplier() : config.option($2T.PROFILE_FILE_SUPPLIER));" + "serviceConfigBuilder.profileName(serviceConfigBuilder.profileName() " + "!= null ? serviceConfigBuilder.profileName() : config.option($2T.PROFILE_NAME));", clientConfigClass, SdkClientOption.class); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java index 0679d198f153..7d0152632f34 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java @@ -62,11 +62,7 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon .option(SdkClientOption.SERVICE_CONFIGURATION)).toBuilder(); serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFileSupplier() != null ? serviceConfigBuilder.profileFileSupplier() : - config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? - config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : - serviceConfigBuilder.profileFile() != null ? - () -> serviceConfigBuilder.profileFile() : - () -> config.option(SdkClientOption.PROFILE_FILE)); + config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)); serviceConfigBuilder.profileName(serviceConfigBuilder.profileName() != null ? serviceConfigBuilder.profileName() : config .option(SdkClientOption.PROFILE_NAME)); if (serviceConfigBuilder.dualstackEnabled() != null) { diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java index de1dec8578bd..c41e604afccb 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java @@ -246,7 +246,7 @@ private URI resolveEndpoint(SdkClientConfiguration config) { private URI endpointFromConfig(SdkClientConfiguration config) { return new DefaultServiceEndpointBuilder(serviceEndpointPrefix(), DEFAULT_ENDPOINT_PROTOCOL) .withRegion(config.option(AwsClientOption.AWS_REGION)) - .withProfileFile(profileFileSupplierOrFallbackToProfileFile(config)) + .withProfileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .withProfileName(config.option(SdkClientOption.PROFILE_NAME)) .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, config.option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) @@ -273,7 +273,7 @@ private Region regionFromDefaultProvider(SdkClientConfiguration config) { throw new IllegalStateException("No region was configured, and use-region-provider-chain was disabled."); } - Supplier profileFile = profileFileSupplierOrFallbackToProfileFile(config); + Supplier profileFile = config.option(SdkClientOption.PROFILE_FILE_SUPPLIER); String profileName = config.option(SdkClientOption.PROFILE_NAME); return DefaultAwsRegionProviderChain.builder() .profileFile(profileFile) @@ -283,13 +283,12 @@ private Region regionFromDefaultProvider(SdkClientConfiguration config) { } private DefaultsMode resolveDefaultsMode(SdkClientConfiguration config) { - DefaultsMode defaultsMode = - config.option(AwsClientOption.DEFAULTS_MODE) != null ? - config.option(AwsClientOption.DEFAULTS_MODE) : - DefaultsModeResolver.create() - .profileFile(profileFileSupplierOrFallbackToProfileFile(config)) - .profileName(config.option(SdkClientOption.PROFILE_NAME)) - .resolve(); + DefaultsMode defaultsMode; + defaultsMode = config.option(AwsClientOption.DEFAULTS_MODE) != null ? config.option(AwsClientOption.DEFAULTS_MODE) : + DefaultsModeResolver.create() + .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(config.option(SdkClientOption.PROFILE_NAME)) + .resolve(); if (defaultsMode == DefaultsMode.AUTO) { defaultsMode = autoDefaultsModeDiscovery.discover(config.option(AwsClientOption.AWS_REGION)); @@ -314,7 +313,7 @@ private Boolean resolveDualstackEndpointEnabled(SdkClientConfiguration config) { * Load the dualstack endpoint setting from the default provider logic. */ private Boolean resolveUseDualstackFromDefaultProvider(SdkClientConfiguration config) { - Supplier profileFile = profileFileSupplierOrFallbackToProfileFile(config); + Supplier profileFile = config.option(SdkClientOption.PROFILE_FILE_SUPPLIER); String profileName = config.option(SdkClientOption.PROFILE_NAME); return DualstackEnabledProvider.builder() .profileFile(profileFile) @@ -337,7 +336,7 @@ private Boolean resolveFipsEndpointEnabled(SdkClientConfiguration config) { * Load the dualstack endpoint setting from the default provider logic. */ private Boolean resolveUseFipsFromDefaultProvider(SdkClientConfiguration config) { - Supplier profileFile = profileFileSupplierOrFallbackToProfileFile(config); + Supplier profileFile = config.option(SdkClientOption.PROFILE_FILE_SUPPLIER); String profileName = config.option(SdkClientOption.PROFILE_NAME); return FipsEnabledProvider.builder() .profileFile(profileFile) @@ -371,7 +370,7 @@ private RetryPolicy resolveAwsRetryPolicy(SdkClientConfiguration config) { } RetryMode retryMode = RetryMode.resolver() - .profileFile(profileFileSupplierOrFallbackToProfileFile(config)) + .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); @@ -468,12 +467,4 @@ private static Pair> transformFipsPseudoRegionIfNecess return Pair.of(region, Optional.empty()); } - private Supplier profileFileSupplierOrFallbackToProfileFile(SdkClientConfiguration config) { - Supplier profileFile = config.option(SdkClientOption.PROFILE_FILE_SUPPLIER); - if (profileFile != null) { - return profileFile; - } - - return () -> config.option(SdkClientOption.PROFILE_FILE); - } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java index 14bf7f7fb262..62fa6a368f5c 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java @@ -232,7 +232,6 @@ private SdkClientConfiguration setOverrides(SdkClientConfiguration configuration builder.option(API_CALL_ATTEMPT_TIMEOUT, clientOverrideConfiguration.apiCallAttemptTimeout().orElse(null)); builder.option(DISABLE_HOST_PREFIX_INJECTION, clientOverrideConfiguration.advancedOption(DISABLE_HOST_PREFIX_INJECTION).orElse(null)); - builder.option(PROFILE_FILE, clientOverrideConfiguration.defaultProfileFile().orElse(null)); builder.option(PROFILE_FILE_SUPPLIER, clientOverrideConfiguration.defaultProfileFile() .map(ProfileFileSupplier::fixedProfileFile) .orElse(null)); @@ -265,15 +264,13 @@ protected SdkClientConfiguration mergeChildDefaults(SdkClientConfiguration confi */ private SdkClientConfiguration mergeGlobalDefaults(SdkClientConfiguration configuration) { // Don't load the default profile file if the customer already gave us one. - ProfileFile configuredProfileFile = configuration.option(PROFILE_FILE); - ProfileFile profileFile = Optional.ofNullable(configuredProfileFile).orElse(ProfileFile.defaultProfileFile()); - Supplier configuredProfileFileSupplier = configuration.option(PROFILE_FILE_SUPPLIER); - Supplier profileFileSupplier = Optional.ofNullable(configuredProfileFileSupplier).orElse(() -> profileFile); + Supplier profileFileSupplier = Optional.ofNullable(configuredProfileFileSupplier) + .orElseGet(() -> ProfileFile::defaultProfileFile); return configuration.merge(c -> c.option(EXECUTION_INTERCEPTORS, new ArrayList<>()) .option(ADDITIONAL_HTTP_HEADERS, new LinkedHashMap<>()) - .option(PROFILE_FILE, profileFile) + .option(PROFILE_FILE, profileFileSupplier.get()) .option(PROFILE_FILE_SUPPLIER, profileFileSupplier) .option(PROFILE_NAME, ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow()) .option(USER_AGENT_PREFIX, SdkUserAgent.create().userAgent()) @@ -339,9 +336,7 @@ private RetryPolicy resolveRetryPolicy(SdkClientConfiguration config) { } RetryMode retryMode = RetryMode.resolver() - .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? - config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : - () -> config.option(SdkClientOption.PROFILE_FILE)) + .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java index 1d2c33b98e64..3af86f60fbf0 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java @@ -127,7 +127,15 @@ public final class SdkClientOption extends ClientOption { /** * The profile file to use for this client. - */ + * + * @deprecated This method was used: + * - To read configuration options in profile files in aws-core, sdk-core + * - Build service configuration objects from profile files in codegen, s3control + * - Build service configuration objects from profile files, set endpoint options in s3 + * - Set retry mode in dynamodb, kinesis + * This has been replaced with {@code PROFILE_FILE_SUPPLIER.get()}. + */ + @Deprecated public static final SdkClientOption PROFILE_FILE = new SdkClientOption<>(ProfileFile.class); /** diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/endpointdiscovery/providers/DefaultEndpointDiscoveryProviderChain.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/endpointdiscovery/providers/DefaultEndpointDiscoveryProviderChain.java index 742d9fa28eda..fd8e086e2f3d 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/endpointdiscovery/providers/DefaultEndpointDiscoveryProviderChain.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/endpointdiscovery/providers/DefaultEndpointDiscoveryProviderChain.java @@ -30,9 +30,7 @@ public DefaultEndpointDiscoveryProviderChain() { } public DefaultEndpointDiscoveryProviderChain(SdkClientConfiguration clientConfiguration) { - this(clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? - clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : - () -> clientConfiguration.option(SdkClientOption.PROFILE_FILE), + this(clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER), clientConfiguration.option(SdkClientOption.PROFILE_NAME)); } diff --git a/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java b/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java index a0a2b6e55d2b..3830c23cef5f 100644 --- a/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java +++ b/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java @@ -64,9 +64,7 @@ public static RetryPolicy resolveRetryPolicy(SdkClientConfiguration config) { } RetryMode retryMode = RetryMode.resolver() - .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? - config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : - () -> config.option(SdkClientOption.PROFILE_FILE)) + .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); diff --git a/services/kinesis/pom.xml b/services/kinesis/pom.xml index f3d712284e13..7158fe921be4 100644 --- a/services/kinesis/pom.xml +++ b/services/kinesis/pom.xml @@ -61,11 +61,6 @@ protocol-core ${awsjavasdk.version} - - software.amazon.awssdk - profiles - ${awsjavasdk.version} - software.amazon.eventstream eventstream diff --git a/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java b/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java index b2d48aae82ae..1538ec0dc67e 100644 --- a/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java +++ b/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java @@ -40,9 +40,7 @@ public static RetryPolicy resolveRetryPolicy(SdkClientConfiguration config) { } RetryMode retryMode = RetryMode.resolver() - .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? - config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : - () -> config.option(SdkClientOption.PROFILE_FILE)) + .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java index 2e8233843968..0a0bbc9cfc33 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java @@ -171,9 +171,7 @@ static S3Utilities create(SdkClientConfiguration clientConfiguration) { S3Utilities.Builder builder = builder() .region(clientConfiguration.option(AwsClientOption.AWS_REGION)) .s3Configuration((S3Configuration) clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION)) - .profileFile(clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? - clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : - () -> clientConfiguration.option(SdkClientOption.PROFILE_FILE)) + .profileFile(clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .profileName(clientConfiguration.option(SdkClientOption.PROFILE_NAME)); if (Boolean.TRUE.equals(clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolver.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolver.java index 7f7b227f5e79..412fd56d0c91 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolver.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolver.java @@ -44,9 +44,7 @@ public UseGlobalEndpointResolver(SdkClientConfiguration config) { String defaultS3UsEast1RegionalEndpointFromSmartDefaults = config.option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT); this.useUsEast1RegionalEndpoint = - new Lazy<>(() -> useUsEast1RegionalEndpoint(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? - config.option(SdkClientOption.PROFILE_FILE_SUPPLIER) : - () -> config.option(SdkClientOption.PROFILE_FILE), + new Lazy<>(() -> useUsEast1RegionalEndpoint(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER), () -> config.option(SdkClientOption.PROFILE_NAME), defaultS3UsEast1RegionalEndpointFromSmartDefaults)); } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverProfileFileTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverProfileFileTest.java index dd7ff6106ba4..4ad07d71f005 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverProfileFileTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverProfileFileTest.java @@ -33,112 +33,59 @@ void resolve_nonUsEast1_resolvesToFalse() { SdkClientConfiguration config = SdkClientConfiguration.builder().build(); UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - assertThat(resolver.resolve(Region.AF_SOUTH_1)).isEqualTo(false); + assertThat(resolver.resolve(Region.AF_SOUTH_1)).isFalse(); } @Test - void resolve_profileFileRegionalEndpointLegacy_resolvesFromPropertyAsTrue() { - ProfileFile file = configuration("[profile regional_s3_endpoint]\n" - + "s3_us_east_1_regional_endpoint = legacy"); - - SdkClientConfiguration config = SdkClientConfiguration - .builder() - .option(SdkClientOption.PROFILE_FILE, file) - .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") - .build(); - UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - - assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); - } - - @Test - void resolve_profileFileRegionalEndpointRegional_resolvesFromPropertyAsFalse() { - ProfileFile file = configuration("[profile regional_s3_endpoint]\n" - + "s3_us_east_1_regional_endpoint = regional"); - - SdkClientConfiguration config = SdkClientConfiguration - .builder() - .option(SdkClientOption.PROFILE_FILE, file) - .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") - .build(); - UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - - assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(false); - } - - @Test - void resolve_nullProfileFileSupplierAndNullProfileFileAndNullDefaultRegionalEndpoint_resolvesToTrue() { + void resolve_nullProfileFileSupplierAndNullDefaultRegionalEndpoint_resolvesToTrue() { SdkClientConfiguration config = SdkClientConfiguration .builder() .option(SdkClientOption.PROFILE_FILE_SUPPLIER, null) - .option(SdkClientOption.PROFILE_FILE, null) .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") .build(); UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); + assertThat(resolver.resolve(Region.US_EAST_1)).isTrue(); } @Test - void resolve_nullProfileFileSupplierAndNullProfileFileAndDefaultRegionalEndPointLegacy_resolvesToTrue() { + void resolve_nullProfileFileSupplierAndDefaultRegionalEndPointLegacy_resolvesToTrue() { SdkClientConfiguration config = SdkClientConfiguration .builder() .option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, "legacy") .option(SdkClientOption.PROFILE_FILE_SUPPLIER, null) - .option(SdkClientOption.PROFILE_FILE, null) .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") .build(); UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); + assertThat(resolver.resolve(Region.US_EAST_1)).isTrue(); } @Test - void resolve_nullProfileFileSupplierAndNullProfileFileAndDefaultRegionalEndPointRegional_resolvesToFalse() { + void resolve_nullProfileFileSupplierAndDefaultRegionalEndPointRegional_resolvesToFalse() { SdkClientConfiguration config = SdkClientConfiguration .builder() .option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, "regional") .option(SdkClientOption.PROFILE_FILE_SUPPLIER, null) - .option(SdkClientOption.PROFILE_FILE, null) - .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") - .build(); - UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - - assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(false); - } - - @Test - void resolve_nullProfileFileSupplierAndNonNullProfileFileEndpointLegacy_resolvesToTrue() { - Supplier supplier = null; - ProfileFile file = configuration("[profile regional_s3_endpoint]\n" - + "s3_us_east_1_regional_endpoint = legacy"); - - SdkClientConfiguration config = SdkClientConfiguration - .builder() - .option(SdkClientOption.PROFILE_FILE_SUPPLIER, supplier) - .option(SdkClientOption.PROFILE_FILE, file) .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") .build(); UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); + assertThat(resolver.resolve(Region.US_EAST_1)).isFalse(); } @Test - void resolve_nullProfileFileSupplierAndNonNullProfileFileEndpointLegacy_resolvesToFalse() { + void resolve_nullProfileFileSupplier_resolvesToTrue() { Supplier supplier = null; - ProfileFile file = configuration("[profile regional_s3_endpoint]\n" - + "s3_us_east_1_regional_endpoint = regional"); SdkClientConfiguration config = SdkClientConfiguration .builder() .option(SdkClientOption.PROFILE_FILE_SUPPLIER, supplier) - .option(SdkClientOption.PROFILE_FILE, file) .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint") .build(); UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(false); + assertThat(resolver.resolve(Region.US_EAST_1)).isTrue(); } @Test @@ -154,7 +101,7 @@ void resolve_profileFileSupplierRegionalEndpointLegacy_resolvesToTrue() { .build(); UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(true); + assertThat(resolver.resolve(Region.US_EAST_1)).isTrue(); } @Test @@ -170,7 +117,7 @@ void resolve_profileFileSupplierRegionalEndpointRegional_resolvesToFalse() { .build(); UseGlobalEndpointResolver resolver = new UseGlobalEndpointResolver(config); - assertThat(resolver.resolve(Region.US_EAST_1)).isEqualTo(false); + assertThat(resolver.resolve(Region.US_EAST_1)).isFalse(); } private ProfileFile configuration(String string) { diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverTest.java index 3b293e951a42..e1121fb3aa97 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/UseGlobalEndpointResolverTest.java @@ -112,7 +112,7 @@ public void differentCombinationOfConfigs_shouldResolveCorrectly() { .type(ProfileFile.Type.CONFIGURATION) .build(); - configBuilder.option(SdkClientOption.PROFILE_FILE, file) + configBuilder.option(SdkClientOption.PROFILE_FILE_SUPPLIER, () -> file) .option(SdkClientOption.PROFILE_NAME, "regional_s3_endpoint"); } From e8ba9fde57c229064ea1803a324ced35b0faee77 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Wed, 1 Feb 2023 17:23:02 -0700 Subject: [PATCH 15/16] Deprecated SdkExecutionAttribute PROFILE_FILE --- .../awssdk/awscore/internal/AwsExecutionContextBuilder.java | 4 +++- .../amazon/awssdk/core/client/config/SdkClientOption.java | 4 ++-- .../awssdk/core/interceptor/SdkExecutionAttribute.java | 6 ++++++ .../awssdk/core/internal/handler/BaseClientHandler.java | 4 +++- .../services/docdb/internal/RdsPresignInterceptor.java | 4 +--- .../services/docdb/internal/PresignRequestHandlerTest.java | 1 - .../services/neptune/internal/RdsPresignInterceptor.java | 4 +--- .../neptune/internal/PresignRequestHandlerTest.java | 1 - .../awssdk/services/rds/internal/RdsPresignInterceptor.java | 4 +--- .../services/rds/internal/PresignRequestHandlerTest.java | 1 - 10 files changed, 17 insertions(+), 16 deletions(-) diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java index ecf6b3f5c8ac..32daaba3f3fb 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java @@ -82,7 +82,9 @@ private AwsExecutionContextBuilder() { .putAttribute(SdkInternalExecutionAttribute.HAS_INITIAL_REQUEST_EVENT, executionParams.hasInitialRequestEvent()) .putAttribute(SdkExecutionAttribute.CLIENT_TYPE, clientConfig.option(SdkClientOption.CLIENT_TYPE)) .putAttribute(SdkExecutionAttribute.SERVICE_NAME, clientConfig.option(SdkClientOption.SERVICE_NAME)) - .putAttribute(SdkExecutionAttribute.PROFILE_FILE, clientConfig.option(SdkClientOption.PROFILE_FILE)) + .putAttribute(SdkExecutionAttribute.PROFILE_FILE, clientConfig.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? + clientConfig.option(SdkClientOption.PROFILE_FILE_SUPPLIER).get() : + null) .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, clientConfig.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, clientConfig.option(SdkClientOption.PROFILE_NAME)) .putAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED, diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java index 3af86f60fbf0..07361d75f23d 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java @@ -128,8 +128,8 @@ public final class SdkClientOption extends ClientOption { /** * The profile file to use for this client. * - * @deprecated This method was used: - * - To read configuration options in profile files in aws-core, sdk-core + * @deprecated This option was used to: + * - Read configuration options in profile files in aws-core, sdk-core * - Build service configuration objects from profile files in codegen, s3control * - Build service configuration objects from profile files, set endpoint options in s3 * - Set retry mode in dynamodb, kinesis diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java index dfb79550a547..94f78e64a0df 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java @@ -76,6 +76,12 @@ public class SdkExecutionAttribute { */ public static final ExecutionAttribute SIGNER_OVERRIDDEN = new ExecutionAttribute<>("SignerOverridden"); + /** + * @deprecated This attribute is used for: + * - Set profile file of service endpoint builder docdb, nepture, rds + * This has been replaced with {@code PROFILE_FILE_SUPPLIER.get()}. + */ + @Deprecated public static final ExecutionAttribute PROFILE_FILE = new ExecutionAttribute<>("ProfileFile"); public static final ExecutionAttribute> PROFILE_FILE_SUPPLIER = diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java index 5f8a404b65ae..21421eeb911a 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java @@ -200,7 +200,9 @@ private static InterceptorContext runModifyHttpRequestAndHttpContentInterceptors .putAttribute(SdkExecutionAttribute.SERVICE_CONFIG, clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION)) .putAttribute(SdkExecutionAttribute.SERVICE_NAME, clientConfiguration.option(SdkClientOption.SERVICE_NAME)) - .putAttribute(SdkExecutionAttribute.PROFILE_FILE, clientConfiguration.option(SdkClientOption.PROFILE_FILE)) + .putAttribute(SdkExecutionAttribute.PROFILE_FILE, + clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? + clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER).get() : null) .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, clientConfiguration.option(SdkClientOption.PROFILE_NAME)); diff --git a/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java b/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java index c496f811c758..5dd3248fe31f 100644 --- a/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java +++ b/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java @@ -162,9 +162,7 @@ private URI createEndpoint(String regionName, String serviceName, ExecutionAttri return new DefaultServiceEndpointBuilder(SERVICE_NAME, Protocol.HTTPS.toString()) .withRegion(region) - .withProfileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) != null ? - attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) : - () -> attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE)) + .withProfileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER)) .withProfileName(attributes.getAttribute(SdkExecutionAttribute.PROFILE_NAME)) .withDualstackEnabled(attributes.getAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) .withFipsEnabled(attributes.getAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED)) diff --git a/services/docdb/src/test/java/software/amazon/awssdk/services/docdb/internal/PresignRequestHandlerTest.java b/services/docdb/src/test/java/software/amazon/awssdk/services/docdb/internal/PresignRequestHandlerTest.java index 00c446ed39ba..1a261fc2ebdd 100644 --- a/services/docdb/src/test/java/software/amazon/awssdk/services/docdb/internal/PresignRequestHandlerTest.java +++ b/services/docdb/src/test/java/software/amazon/awssdk/services/docdb/internal/PresignRequestHandlerTest.java @@ -164,7 +164,6 @@ private SdkHttpFullRequest marshallRequest(CopyDbClusterSnapshotRequest request) private ExecutionAttributes executionAttributes() { return new ExecutionAttributes().putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CREDENTIALS) .putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, DESTINATION_REGION) - .putAttribute(SdkExecutionAttribute.PROFILE_FILE, ProfileFile.defaultProfileFile()) .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, ProfileFile::defaultProfileFile) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, "default"); diff --git a/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java b/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java index b05955b3880f..ba04154891a3 100644 --- a/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java +++ b/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java @@ -164,9 +164,7 @@ private URI createEndpoint(String regionName, String serviceName, ExecutionAttri return new DefaultServiceEndpointBuilder(SERVICE_NAME, Protocol.HTTPS.toString()) .withRegion(region) - .withProfileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) != null ? - attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) : - () -> attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE)) + .withProfileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER)) .withProfileName(attributes.getAttribute(SdkExecutionAttribute.PROFILE_NAME)) .withDualstackEnabled(attributes.getAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) .withFipsEnabled(attributes.getAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED)) diff --git a/services/neptune/src/test/java/software/amazon/awssdk/services/neptune/internal/PresignRequestHandlerTest.java b/services/neptune/src/test/java/software/amazon/awssdk/services/neptune/internal/PresignRequestHandlerTest.java index 490d4671cd6d..e0f083a4da88 100644 --- a/services/neptune/src/test/java/software/amazon/awssdk/services/neptune/internal/PresignRequestHandlerTest.java +++ b/services/neptune/src/test/java/software/amazon/awssdk/services/neptune/internal/PresignRequestHandlerTest.java @@ -164,7 +164,6 @@ private SdkHttpFullRequest marshallRequest(CopyDbClusterSnapshotRequest request) private ExecutionAttributes executionAttributes() { return new ExecutionAttributes().putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CREDENTIALS) .putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, DESTINATION_REGION) - .putAttribute(SdkExecutionAttribute.PROFILE_FILE, ProfileFile.defaultProfileFile()) .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, ProfileFile::defaultProfileFile) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, "default"); diff --git a/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java b/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java index 6bf7c6948bb3..cd2ad833f8db 100644 --- a/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java +++ b/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java @@ -162,9 +162,7 @@ private URI createEndpoint(String regionName, String serviceName, ExecutionAttri return new DefaultServiceEndpointBuilder(SERVICE_NAME, Protocol.HTTPS.toString()) .withRegion(region) - .withProfileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) != null ? - attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER) : - () -> attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE)) + .withProfileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER)) .withProfileName(attributes.getAttribute(SdkExecutionAttribute.PROFILE_NAME)) .withDualstackEnabled(attributes.getAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) .withFipsEnabled(attributes.getAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED)) diff --git a/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java b/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java index 4b82f1518fef..65a04d3a2f2a 100644 --- a/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java +++ b/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java @@ -165,7 +165,6 @@ private SdkHttpFullRequest marshallRequest(CopyDbSnapshotRequest request) { private ExecutionAttributes executionAttributes() { return new ExecutionAttributes().putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CREDENTIALS) .putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, DESTINATION_REGION) - .putAttribute(SdkExecutionAttribute.PROFILE_FILE, ProfileFile.defaultProfileFile()) .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, ProfileFile::defaultProfileFile) .putAttribute(SdkExecutionAttribute.PROFILE_NAME, "default"); From 627e5601349aa195dfae70c4ed8ddfcb2d05243c Mon Sep 17 00:00:00 2001 From: David Negrete Date: Mon, 6 Feb 2023 11:38:35 -0700 Subject: [PATCH 16/16] Updated changelog entry --- .changes/next-release/feature-AWSSDKforJavav2-688d30e.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json b/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json index e94f61f9988b..3fb53dd0ab26 100644 --- a/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json +++ b/.changes/next-release/feature-AWSSDKforJavav2-688d30e.json @@ -2,5 +2,5 @@ "category": "AWS SDK for Java v2", "contributor": "", "type": "feature", - "description": "- ProfileCredentialsProvider and ProfileTokenProvider can reload credentials when disk profile changes\n- Updated DefaultCredentialsProvider chain for reloading credentials\n- Service support classes store ProfileFile as a Supplier interface\n- SdkClientOption and SdkExecutionAttributes retrieve value for PROFILE_FILE_SUPPLIER first and fallback to PROFILE_FILE when unavailable" + "description": "- ProfileCredentialsProvider and ProfileTokenProvider can reload credentials when disk profile changes\n- Updated DefaultCredentialsProvider chain for reloading credentials\n- Service support classes store ProfileFile as a Supplier interface\n- SdkClientOption and SdkExecutionAttributes constants for PROFILE_FILE have been deprecated in favor of PROFILE_FILE_SUPPLIER" }