Skip to content

Commit de45953

Browse files
committed
Use the default (refreshing) profileFile supplier + fix invalid state when file does not exist
1 parent 27a356b commit de45953

File tree

11 files changed

+133
-16
lines changed

11 files changed

+133
-16
lines changed

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/DefaultCredentialsProviderTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@
1717

1818
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
1919

20+
import com.google.common.jimfs.Jimfs;
21+
import java.io.IOException;
22+
import java.nio.file.FileSystem;
23+
import java.nio.file.Path;
2024
import java.util.Arrays;
2125
import java.util.List;
2226
import java.util.function.Supplier;
27+
import org.junit.jupiter.api.AfterAll;
28+
import org.junit.jupiter.api.BeforeAll;
2329
import org.junit.jupiter.api.Test;
2430
import software.amazon.awssdk.profiles.ProfileFile;
2531
import software.amazon.awssdk.profiles.ProfileFileSupplier;

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
@@ -24,6 +24,7 @@
2424
import java.nio.file.FileSystem;
2525
import java.nio.file.Files;
2626
import java.nio.file.Path;
27+
import java.time.Instant;
2728
import java.util.Optional;
2829
import java.util.function.Supplier;
2930
import org.junit.jupiter.api.AfterAll;
@@ -243,6 +244,33 @@ void resolveCredentials_presentSupplierProfileFile_returnsCredentials() {
243244
});
244245
}
245246

247+
@Test
248+
void resolveCredentials_defaultProfileFileSupplier_refreshesCredentials() throws InterruptedException {
249+
Path credentialsFile = generateTestCredentialsFile("defaultAccessKey", "defaultSecretAccessKey");
250+
251+
ProfileCredentialsProvider provider = new ProfileCredentialsProvider
252+
.BuilderImpl()
253+
.defaultProfileFileLoader(ProfileFileSupplier.reloadWhenModified(credentialsFile, ProfileFile.Type.CREDENTIALS))
254+
.profileName("default")
255+
.build();
256+
257+
assertThat(provider.resolveCredentials()).satisfies(credentials -> {
258+
assertThat(credentials.accessKeyId()).isEqualTo("defaultAccessKey");
259+
assertThat(credentials.secretAccessKey()).isEqualTo("defaultSecretAccessKey");
260+
});
261+
262+
// modify the file
263+
generateTestCredentialsFile("updatedAccessKey", "updatedSecretAccessKey");
264+
265+
// ProfileFileRefresher has a stale time of 1000 ms.
266+
Thread.sleep(1000);
267+
268+
assertThat(provider.resolveCredentials()).satisfies(credentials -> {
269+
assertThat(credentials.accessKeyId()).isEqualTo("updatedAccessKey");
270+
assertThat(credentials.secretAccessKey()).isEqualTo("updatedSecretAccessKey");
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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,27 @@ public void defaultSupplier_noCredentialsFiles_returnsEmptyProvider() {
592592
});
593593
}
594594

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

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
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;
@@ -280,7 +281,7 @@ protected SdkClientConfiguration mergeChildDefaults(SdkClientConfiguration confi
280281
* Apply global default configuration
281282
*/
282283
private SdkClientConfiguration mergeGlobalDefaults(SdkClientConfiguration configuration) {
283-
Supplier<ProfileFile> defaultProfileFileSupplier = new Lazy<>(ProfileFile::defaultProfileFile)::getValue;
284+
Supplier<ProfileFile> defaultProfileFileSupplier = ProfileFileSupplier.defaultSupplier();
284285

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

core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,13 @@
4444
import java.beans.BeanInfo;
4545
import java.beans.Introspector;
4646
import java.beans.PropertyDescriptor;
47+
import java.io.File;
48+
import java.io.IOException;
4749
import java.lang.reflect.Method;
4850
import java.net.URI;
51+
import java.nio.charset.StandardCharsets;
52+
import java.nio.file.Files;
53+
import java.nio.file.Path;
4954
import java.time.Duration;
5055
import java.util.ArrayList;
5156
import java.util.Arrays;
@@ -59,7 +64,9 @@
5964
import java.util.function.Supplier;
6065
import org.assertj.core.api.Assertions;
6166
import org.junit.Before;
67+
import org.junit.Rule;
6268
import org.junit.Test;
69+
import org.junit.rules.TemporaryFolder;
6370
import org.junit.runner.RunWith;
6471
import org.mockito.Mock;
6572
import org.mockito.Mockito;
@@ -81,6 +88,7 @@
8188
import software.amazon.awssdk.metrics.MetricCollection;
8289
import software.amazon.awssdk.metrics.MetricPublisher;
8390
import software.amazon.awssdk.profiles.ProfileFile;
91+
import software.amazon.awssdk.testutils.EnvironmentVariableHelper;
8492
import software.amazon.awssdk.utils.AttributeMap;
8593
import software.amazon.awssdk.utils.StringInputStream;
8694

@@ -100,6 +108,9 @@ public class DefaultClientBuilderTest {
100108
private static final URI ENDPOINT = URI.create("https://example.com");
101109
private static final NoOpSigner TEST_SIGNER = new NoOpSigner();
102110

111+
@Rule
112+
public TemporaryFolder tempFolder = new TemporaryFolder();
113+
103114
@Mock
104115
private SdkHttpClient.Builder defaultHttpClientFactory;
105116

@@ -419,6 +430,39 @@ public void defaultProfileFileSupplier_isStaticOrHasIdentityCaching() {
419430
assertThat(secondGet).isSameAs(firstGet);
420431
}
421432

433+
@Test
434+
public void defaultProfileFileSupplier_refreshesWhenFileModified() {
435+
EnvironmentVariableHelper.run(env -> {
436+
try {
437+
File credentialFile = tempFolder.newFile();
438+
writeTestCredentialsFile(credentialFile, "akid1", "sak");
439+
env.set("AWS_SHARED_CREDENTIALS_FILE", credentialFile.getPath());
440+
SdkClientConfiguration config =
441+
testClientBuilder().build().clientConfiguration;
442+
443+
Supplier<ProfileFile> defaultProfileFileSupplier = config.option(PROFILE_FILE_SUPPLIER);
444+
ProfileFile firstGet = defaultProfileFileSupplier.get();
445+
446+
writeTestCredentialsFile(credentialFile, "updatedAkid", "updatedSak");
447+
Thread.sleep(1000);
448+
ProfileFile secondGet = defaultProfileFileSupplier.get();
449+
450+
assertThat(secondGet).isNotSameAs(firstGet);
451+
assertThat(secondGet.profile("default")).isPresent();
452+
assertThat(secondGet.profile("default").get()).satisfies(profile -> {
453+
assertThat(profile.property("aws_access_key_id"))
454+
.isEqualTo(Optional.of("updatedAkid"));
455+
assertThat(profile.property("aws_secret_access_key"))
456+
.isEqualTo(Optional.of("updatedSak"));
457+
});
458+
459+
}
460+
catch (IOException | InterruptedException e) {
461+
throw new RuntimeException(e);
462+
}
463+
});
464+
}
465+
422466
private SdkDefaultClientBuilder<TestClientBuilder, TestClient> testClientBuilder() {
423467
ClientOverrideConfiguration overrideConfig =
424468
ClientOverrideConfiguration.builder()
@@ -437,6 +481,13 @@ private SdkDefaultClientBuilder<TestAsyncClientBuilder, TestAsyncClient> testAsy
437481
return new TestAsyncClientBuilder().overrideConfiguration(overrideConfig);
438482
}
439483

484+
private void writeTestCredentialsFile(File file, String accessKeyId, String secretAccessKey)
485+
throws IOException {
486+
String contents = String.format("[default]\naws_access_key_id = %s\naws_secret_access_key = %s\n",
487+
accessKeyId, secretAccessKey);
488+
Files.write(file.toPath(), contents.getBytes(StandardCharsets.UTF_8));
489+
}
490+
440491
private static class TestClient {
441492
private final SdkClientConfiguration clientConfiguration;
442493

0 commit comments

Comments
 (0)