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 72c825c9fdcf..97a42582b2de 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 @@ -99,7 +99,7 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) { this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; this.asyncThreadName = builder.asyncThreadName; this.profileFile = Optional.ofNullable(builder.profileFile) - .orElseGet(() -> ProfileFileSupplier.fixedProfileFile(ProfileFile.defaultProfileFile())); + .orElseGet(ProfileFileSupplier::defaultSupplier); this.profileName = Optional.ofNullable(builder.profileName) .orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow); @@ -311,7 +311,7 @@ public interface Builder extends HttpCredentialsProvider.BuilderBy default, {@link ProfileFile#defaultProfileFile()} is used. - * + * * @see #profileFile(Supplier) */ Builder profileFile(ProfileFile profileFile); 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 95d6bb88afb4..c638e91c7b70 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 @@ -73,7 +73,7 @@ private ProfileCredentialsProvider(BuilderImpl builder) { .orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow); selectedProfileSupplier = Optional.ofNullable(builder.profileFile) - .orElseGet(() -> ProfileFileSupplier.fixedProfileFile(builder.defaultProfileFileLoader.get())); + .orElseGet(() -> builder.defaultProfileFileLoader); } catch (RuntimeException e) { // If we couldn't load the credentials provider for some reason, save an exception describing why. This exception @@ -196,7 +196,7 @@ public interface Builder extends CopyableBuilder profileFileSupplier); @@ -216,7 +216,7 @@ public interface Builder extends CopyableBuilder profileFile; private String profileName; - private Supplier defaultProfileFileLoader = ProfileFile::defaultProfileFile; + private Supplier defaultProfileFileLoader = ProfileFileSupplier.defaultSupplier(); BuilderImpl() { } 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 1c1c44bd140c..01e7a1f30312 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 @@ -16,17 +16,28 @@ package software.amazon.awssdk.auth.credentials; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileFileSupplier; +import software.amazon.awssdk.profiles.ProfileFileSystemSetting; import software.amazon.awssdk.utils.StringInputStream; +import software.amazon.awssdk.utils.internal.SystemSettingUtilsTestBackdoor; class DefaultCredentialsProviderTest { + @TempDir + private Path testDirectory; + @Test void resolveCredentials_ProfileCredentialsProviderWithProfileFile_returnsCredentials() { DefaultCredentialsProvider provider = DefaultCredentialsProvider @@ -97,6 +108,56 @@ void resolveCredentials_ProfileCredentialsProviderWithSupplierProfileFile_return }); } + @Test + void resolveCredentials_DefaultCredentialProviderWithReloadWhenModified() throws Exception { + Path credentialsFilePath = generateTestCredentialsFile("customAccess", "customSecret"); + SystemSettingUtilsTestBackdoor.addEnvironmentVariableOverride(ProfileFileSystemSetting.AWS_SHARED_CREDENTIALS_FILE.environmentVariable(), + credentialsFilePath.toString()); + DefaultCredentialsProvider provider = DefaultCredentialsProvider.create(); + Thread.sleep(2000); + try { + assertThat(provider.resolveCredentials()).satisfies(awsCredentials -> { + assertThat(awsCredentials.accessKeyId()).isEqualTo("customAccess"); + assertThat(awsCredentials.secretAccessKey()).isEqualTo("customSecret"); + }); + } catch (AssertionError e) { + Thread.sleep(2000); + assertTrue(assertReloadCredentials(provider)); + } + + generateTestCredentialsFile("modifiedAccess", "modifiedSecret"); + + // without sleep the assertion fails, as there is delay in config reload + Thread.sleep(1000); + assertThat(provider.resolveCredentials()).satisfies(awsCredentials -> { + assertThat(awsCredentials.accessKeyId()).isEqualTo("modifiedAccess"); + assertThat(awsCredentials.secretAccessKey()).isEqualTo("modifiedSecret"); + }); + SystemSettingUtilsTestBackdoor.clearEnvironmentVariableOverrides(); + } + + private boolean assertReloadCredentials(AwsCredentialsProvider provider) { + assertThat(provider.resolveCredentials()).satisfies(awsCredentials -> { + assertThat(awsCredentials.accessKeyId()).isEqualTo("customAccess"); + assertThat(awsCredentials.secretAccessKey()).isEqualTo("customSecret"); + }); + } + + 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 ProfileFile credentialFile(String credentialFile) { return ProfileFile.builder() .content(new StringInputStream(credentialFile)) 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 4dec2883c814..385c828bb5d1 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 @@ -55,13 +55,15 @@ static ProfileFileSupplier defaultSupplier() { = ProfileFileLocation.configurationFileLocation() .map(path -> reloadWhenModified(path, ProfileFile.Type.CONFIGURATION)); - ProfileFileSupplier supplier = () -> ProfileFile.builder().build(); + 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(); + } else { + supplier = fixedProfileFile(ProfileFile.aggregator().build()); } return supplier; 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 f24b9b996a19..3e91f790e3c5 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 @@ -96,6 +96,7 @@ import software.amazon.awssdk.identity.spi.IdentityProviders; 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.profiles.ProfileProperty; import software.amazon.awssdk.retries.AdaptiveRetryStrategy; @@ -273,7 +274,8 @@ protected SdkClientConfiguration mergeChildDefaults(SdkClientConfiguration confi * Apply global default configuration */ private SdkClientConfiguration mergeGlobalDefaults(SdkClientConfiguration configuration) { - Supplier defaultProfileFileSupplier = new Lazy<>(ProfileFile::defaultProfileFile)::getValue; + Supplier profileFileSupplierProvider = new Lazy<>(ProfileFileSupplier::defaultSupplier)::getValue; + Supplier defaultProfileFileSupplier = profileFileSupplierProvider.get(); configuration = configuration.merge(c -> c.option(EXECUTION_INTERCEPTORS, new ArrayList<>()) .option(METRIC_PUBLISHERS, new ArrayList<>()) 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 6175e2004247..680dd501b8b4 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 @@ -120,7 +120,7 @@ private S3Utilities(Builder builder) { this.region = Validate.paramNotNull(builder.region, "Region"); this.endpoint = builder.endpoint; this.profileFile = Optional.ofNullable(builder.profileFile) - .orElseGet(() -> ProfileFileSupplier.fixedProfileFile(ProfileFile.defaultProfileFile())); + .orElseGet(ProfileFileSupplier::defaultSupplier); this.profileName = builder.profileName; if (builder.s3Configuration == null) {