Skip to content

Commit 1604e3f

Browse files
authored
Fix credential reloading by default + gracefully handle missing files (#6632)
* Use the default (refreshing) profileFile supplier + fix invalid state when file does not exist * Fix test and add changelogs * Fix checkstyle * Removeunused imports * Fix flaky test * Make tests more resiliant * remove flaky test
1 parent 2a8f873 commit 1604e3f

File tree

12 files changed

+107
-17
lines changed

12 files changed

+107
-17
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Fix credential reloading in defaults when shared credential/config files are modified."
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Gracefully handle missing file in ProfileFileSupplier.reloadWhenModified."
6+
}

core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) {
105105
this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled;
106106
this.asyncThreadName = builder.asyncThreadName;
107107
this.profileFile = Optional.ofNullable(builder.profileFile)
108-
.orElseGet(() -> ProfileFileSupplier.fixedProfileFile(ProfileFile.defaultProfileFile()));
108+
.orElseGet(ProfileFileSupplier::defaultSupplier);
109109
this.profileName = Optional.ofNullable(builder.profileName)
110110
.orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow);
111111
this.sourceChain = builder.sourceChain;

core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private ProfileCredentialsProvider(BuilderImpl builder) {
7373
.orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow);
7474
selectedProfileSupplier =
7575
Optional.ofNullable(builder.profileFile)
76-
.orElseGet(() -> ProfileFileSupplier.fixedProfileFile(builder.defaultProfileFileLoader.get()));
76+
.orElse(defaultProfileFileLoader);
7777

7878
} catch (RuntimeException e) {
7979
// If we couldn't load the credentials provider for some reason, save an exception describing why. This exception
@@ -216,7 +216,7 @@ public interface Builder extends CopyableBuilder<Builder, ProfileCredentialsProv
216216
static final class BuilderImpl implements Builder {
217217
private Supplier<ProfileFile> profileFile;
218218
private String profileName;
219-
private Supplier<ProfileFile> defaultProfileFileLoader = ProfileFile::defaultProfileFile;
219+
private Supplier<ProfileFile> defaultProfileFileLoader = ProfileFileSupplier.defaultSupplier();
220220

221221
BuilderImpl() {
222222
}

core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProfileCredentialsProviderTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.nio.file.Files;
2626
import java.nio.file.Path;
2727
import java.util.Optional;
28+
import java.util.concurrent.atomic.AtomicBoolean;
2829
import java.util.function.Supplier;
2930
import org.junit.jupiter.api.AfterAll;
3031
import org.junit.jupiter.api.BeforeAll;
@@ -33,6 +34,7 @@
3334
import software.amazon.awssdk.profiles.ProfileFile;
3435
import software.amazon.awssdk.profiles.ProfileFileSupplier;
3536
import software.amazon.awssdk.profiles.ProfileProperty;
37+
import software.amazon.awssdk.testutils.Waiter;
3638
import software.amazon.awssdk.utils.StringInputStream;
3739

3840
/**
@@ -243,6 +245,32 @@ void resolveCredentials_presentSupplierProfileFile_returnsCredentials() {
243245
});
244246
}
245247

248+
@Test
249+
void resolveCredentials_defaultProfileFileSupplier_refreshesCredentials() {
250+
AtomicBoolean firstCall = new AtomicBoolean(true);
251+
ProfileFile file1 = profileFile("[default]\naws_access_key_id = akid1\n"
252+
+ "aws_secret_access_key = sak1\n");
253+
ProfileFile file2 = profileFile("[default]\naws_access_key_id = akid2\n"
254+
+ "aws_secret_access_key = sak2\n");
255+
Supplier<ProfileFile> refreshingSupplier = () -> firstCall.getAndSet(false) ? file1 : file2;
256+
257+
ProfileCredentialsProvider provider = new ProfileCredentialsProvider
258+
.BuilderImpl()
259+
.defaultProfileFileLoader(refreshingSupplier)
260+
.profileName("default")
261+
.build();
262+
263+
assertThat(provider.resolveCredentials()).satisfies(credentials -> {
264+
assertThat(credentials.accessKeyId()).isEqualTo("akid1");
265+
assertThat(credentials.secretAccessKey()).isEqualTo("sak1");
266+
});
267+
268+
assertThat(provider.resolveCredentials()).satisfies(credentials -> {
269+
assertThat(credentials.accessKeyId()).isEqualTo("akid2");
270+
assertThat(credentials.secretAccessKey()).isEqualTo("sak2");
271+
});
272+
}
273+
246274
@Test
247275
void create_noProfileName_returnsProfileCredentialsProviderToResolveWithDefaults() {
248276
ProfileCredentialsProvider provider = ProfileCredentialsProvider.create();

core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplier.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package software.amazon.awssdk.profiles;
1717

18+
import java.nio.file.Files;
1819
import java.nio.file.Path;
1920
import java.util.Collections;
2021
import java.util.LinkedHashMap;
@@ -38,7 +39,7 @@ public interface ProfileFileSupplier extends Supplier<ProfileFile> {
3839

3940
/**
4041
* Creates a {@link ProfileFileSupplier} capable of producing multiple profile objects by aggregating the default
41-
* credentials and configuration files as determined by {@link ProfileFileLocation#credentialsFileLocation()} abd
42+
* credentials and configuration files as determined by {@link ProfileFileLocation#credentialsFileLocation()} and
4243
* {@link ProfileFileLocation#configurationFileLocation()}. This supplier will return a new ProfileFile instance only once
4344
* either disk file has been modified. Multiple calls to the supplier while both disk files are unchanged will return the
4445
* same object.
@@ -81,13 +82,17 @@ static ProfileFileSupplier defaultSupplier() {
8182
*/
8283
static ProfileFileSupplier reloadWhenModified(Path path, ProfileFile.Type type) {
8384
return new ProfileFileSupplier() {
84-
85-
final ProfileFile.Builder builder = ProfileFile.builder()
86-
.content(path)
87-
.type(type);
85+
Supplier<ProfileFile> profileFileSupplier = () -> {
86+
if (Files.isRegularFile(path) && Files.isReadable(path)) {
87+
return ProfileFile.builder()
88+
.content(path)
89+
.type(type).build();
90+
}
91+
return ProfileFile.empty();
92+
};
8893

8994
final ProfileFileRefresher refresher = ProfileFileRefresher.builder()
90-
.profileFile(builder::build)
95+
.profileFile(profileFileSupplier)
9196
.profileFilePath(path)
9297
.build();
9398

core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileSupplierBuilder.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package software.amazon.awssdk.profiles;
1717

18+
import java.nio.file.Files;
1819
import java.nio.file.Path;
1920
import java.time.Clock;
2021
import java.util.Objects;
@@ -33,10 +34,14 @@ final class ProfileFileSupplierBuilder {
3334
private Consumer<ProfileFile> onProfileFileLoad;
3435

3536
public ProfileFileSupplierBuilder reloadWhenModified(Path path, ProfileFile.Type type) {
36-
ProfileFile.Builder builder = ProfileFile.builder()
37-
.content(path)
38-
.type(type);
39-
this.profileFile = builder::build;
37+
this.profileFile = () -> {
38+
if (Files.isRegularFile(path) && Files.isReadable(path)) {
39+
return ProfileFile.builder()
40+
.content(path)
41+
.type(type).build();
42+
}
43+
return ProfileFile.empty();
44+
};
4045
this.profileFilePath = path;
4146
this.reloadingSupplier = true;
4247
return this;

core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileFileRefresher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private boolean isNewProfileFile(ProfileFile profileFile) {
122122
}
123123

124124
private boolean canReloadProfileFile() {
125-
if (Objects.isNull(profileFilePath)) {
125+
if (Objects.isNull(profileFilePath) || !Files.exists(profileFilePath)) {
126126
return false;
127127
}
128128

core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,28 @@ public void defaultSupplier_noCredentialsFiles_returnsEmptyProvider() {
592592
});
593593
}
594594

595+
@Test
596+
public void reloadWhenModified_noCredentialsFiles_returnsEmptyProvider_andRefreshes() throws IOException {
597+
Path credentialsFilePath = getTestCredentialsFilePath();
598+
Files.deleteIfExists(credentialsFilePath);
599+
600+
AdjustableClock clock = new AdjustableClock();
601+
ProfileFileSupplier supplier = builderWithClock(clock)
602+
.reloadWhenModified(credentialsFilePath, ProfileFile.Type.CREDENTIALS)
603+
.build();
604+
605+
assertThat(supplier.get().profiles()).isEmpty();
606+
607+
generateTestCredentialsFile("modifiedAccessKey", "modifiedSecretAccessKey", "modifiedAccountId");
608+
updateModificationTime(credentialsFilePath, clock.instant().plusMillis(1));
609+
610+
clock.tickForward(Duration.ofSeconds(10));
611+
612+
// supplied ProfileFile should refreshed and now have data under the `default` profile
613+
Optional<Profile> fileOptional = supplier.get().profile("default");
614+
assertThat(fileOptional).isPresent();
615+
}
616+
595617
private Path writeTestFile(String contents, Path path) {
596618
try {
597619
Files.createDirectories(testDirectory);

core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@
105105
import software.amazon.awssdk.identity.spi.IdentityProviders;
106106
import software.amazon.awssdk.metrics.MetricPublisher;
107107
import software.amazon.awssdk.profiles.ProfileFile;
108+
import software.amazon.awssdk.profiles.ProfileFileSupplier;
108109
import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
109110
import software.amazon.awssdk.profiles.ProfileProperty;
110111
import software.amazon.awssdk.retries.api.RetryStrategy;
111112
import software.amazon.awssdk.utils.AttributeMap;
112113
import software.amazon.awssdk.utils.AttributeMap.LazyValueSource;
113114
import software.amazon.awssdk.utils.Either;
114-
import software.amazon.awssdk.utils.Lazy;
115115
import software.amazon.awssdk.utils.OptionalUtils;
116116
import software.amazon.awssdk.utils.StringUtils;
117117
import software.amazon.awssdk.utils.ThreadFactoryBuilder;
@@ -280,7 +280,7 @@ protected SdkClientConfiguration mergeChildDefaults(SdkClientConfiguration confi
280280
* Apply global default configuration
281281
*/
282282
private SdkClientConfiguration mergeGlobalDefaults(SdkClientConfiguration configuration) {
283-
Supplier<ProfileFile> defaultProfileFileSupplier = new Lazy<>(ProfileFile::defaultProfileFile)::getValue;
283+
Supplier<ProfileFile> defaultProfileFileSupplier = ProfileFileSupplier.defaultSupplier();
284284

285285
configuration = configuration.merge(c -> c.option(EXECUTION_INTERCEPTORS, new ArrayList<>())
286286
.option(METRIC_PUBLISHERS, new ArrayList<>())

0 commit comments

Comments
 (0)