Skip to content

Commit 33d4012

Browse files
authored
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
1 parent c77991e commit 33d4012

File tree

12 files changed

+1526
-57
lines changed

12 files changed

+1526
-57
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Created ReloadingProfileCredentialsProvider to reload credentials due to disk changes"
6+
}

core/auth/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@
152152
<artifactId>commons-lang3</artifactId>
153153
<scope>test</scope>
154154
</dependency>
155+
<dependency>
156+
<groupId>com.google.jimfs</groupId>
157+
<artifactId>jimfs</artifactId>
158+
<version>${jimfs.version}</version>
159+
<scope>test</scope>
160+
</dependency>
155161
</dependencies>
156162
<build>
157163
<plugins>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
public interface AwsCredentialsProvider {
3131
/**
3232
* Returns {@link AwsCredentials} that can be used to authorize an AWS request. Each implementation of AWSCredentialsProvider
33-
* can chose its own strategy for loading credentials. For example, an implementation might load credentials from an existing
33+
* can choose its own strategy for loading credentials. For example, an implementation might load credentials from an existing
3434
* key management system, or load new credentials when credentials are rotated.
3535
*
3636
* <p>If an error occurs during the loading of credentials or credentials could not be found, a runtime exception will be

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

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

1616
package software.amazon.awssdk.auth.credentials;
1717

18+
import java.util.Objects;
1819
import java.util.Optional;
1920
import java.util.function.Consumer;
2021
import java.util.function.Supplier;
@@ -23,6 +24,7 @@
2324
import software.amazon.awssdk.auth.credentials.internal.ProfileCredentialsUtils;
2425
import software.amazon.awssdk.core.exception.SdkClientException;
2526
import software.amazon.awssdk.profiles.ProfileFile;
27+
import software.amazon.awssdk.profiles.ProfileFileSupplier;
2628
import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
2729
import software.amazon.awssdk.utils.IoUtils;
2830
import software.amazon.awssdk.utils.SdkAutoCloseable;
@@ -46,55 +48,42 @@ public final class ProfileCredentialsProvider
4648
implements AwsCredentialsProvider,
4749
SdkAutoCloseable,
4850
ToCopyableBuilder<ProfileCredentialsProvider.Builder, ProfileCredentialsProvider> {
49-
private final AwsCredentialsProvider credentialsProvider;
50-
private final RuntimeException loadException;
5151

52-
private final ProfileFile profileFile;
52+
private AwsCredentialsProvider credentialsProvider;
53+
private final RuntimeException loadException;
54+
private final ProfileFileSupplier profileFileSupplier;
55+
private volatile ProfileFile currentProfileFile;
5356
private final String profileName;
54-
5557
private final Supplier<ProfileFile> defaultProfileFileLoader;
5658

5759
/**
5860
* @see #builder()
5961
*/
6062
private ProfileCredentialsProvider(BuilderImpl builder) {
61-
AwsCredentialsProvider credentialsProvider = null;
62-
RuntimeException loadException = null;
63-
ProfileFile profileFile = null;
64-
String profileName = null;
63+
this.defaultProfileFileLoader = builder.defaultProfileFileLoader;
64+
65+
RuntimeException thrownException = null;
66+
String selectedProfileName = null;
67+
ProfileFileSupplier selectedProfileSupplier = null;
6568

6669
try {
67-
profileName = builder.profileName != null ? builder.profileName
68-
: ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
69-
70-
// Load the profiles file
71-
profileFile = Optional.ofNullable(builder.profileFile)
72-
.orElseGet(builder.defaultProfileFileLoader);
73-
74-
// Load the profile and credentials provider
75-
String finalProfileName = profileName;
76-
ProfileFile finalProfileFile = profileFile;
77-
credentialsProvider =
78-
profileFile.profile(profileName)
79-
.flatMap(p -> new ProfileCredentialsUtils(finalProfileFile, p, finalProfileFile::profile)
80-
.credentialsProvider())
81-
.orElseThrow(() -> {
82-
String errorMessage = String.format("Profile file contained no credentials for " +
83-
"profile '%s': %s", finalProfileName, finalProfileFile);
84-
return SdkClientException.builder().message(errorMessage).build();
85-
});
70+
selectedProfileName = Optional.ofNullable(builder.profileName)
71+
.orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow);
72+
73+
selectedProfileSupplier = Optional.ofNullable(builder.profileFileSupplier)
74+
.orElseGet(() -> ProfileFileSupplier
75+
.fixedProfileFile(builder.defaultProfileFileLoader.get()));
76+
8677
} catch (RuntimeException e) {
8778
// If we couldn't load the credentials provider for some reason, save an exception describing why. This exception
88-
// will only be raised on calls to getCredentials. We don't want to raise an exception here because it may be
79+
// will only be raised on calls to resolveCredentials. We don't want to raise an exception here because it may be
8980
// expected (eg. in the default credential chain).
90-
loadException = e;
81+
thrownException = e;
9182
}
9283

93-
this.loadException = loadException;
94-
this.credentialsProvider = credentialsProvider;
95-
this.profileFile = profileFile;
96-
this.profileName = profileName;
97-
this.defaultProfileFileLoader = builder.defaultProfileFileLoader;
84+
this.loadException = thrownException;
85+
this.profileName = selectedProfileName;
86+
this.profileFileSupplier = selectedProfileSupplier;
9887
}
9988

10089
/**
@@ -127,19 +116,39 @@ public AwsCredentials resolveCredentials() {
127116
if (loadException != null) {
128117
throw loadException;
129118
}
119+
120+
ProfileFile cachedOrRefreshedProfileFile = refreshProfileFile();
121+
if (isNewProfileFile(cachedOrRefreshedProfileFile)) {
122+
currentProfileFile = cachedOrRefreshedProfileFile;
123+
handleProfileFileReload(cachedOrRefreshedProfileFile);
124+
}
125+
130126
return credentialsProvider.resolveCredentials();
131127
}
132128

129+
private void handleProfileFileReload(ProfileFile profileFile) {
130+
credentialsProvider = createCredentialsProvider(profileFile, profileName);
131+
}
132+
133+
private ProfileFile refreshProfileFile() {
134+
return profileFileSupplier.get();
135+
}
136+
137+
private boolean isNewProfileFile(ProfileFile profileFile) {
138+
return !Objects.equals(currentProfileFile, profileFile);
139+
}
140+
133141
@Override
134142
public String toString() {
135143
return ToString.builder("ProfileCredentialsProvider")
136144
.add("profileName", profileName)
137-
.add("profileFile", profileFile)
145+
.add("profileFile", currentProfileFile)
138146
.build();
139147
}
140148

141149
@Override
142150
public void close() {
151+
profileFileSupplier.close();
143152
// The delegate credentials provider may be closeable (eg. if it's an STS credentials provider). In this case, we should
144153
// clean it up when this credentials provider is closed.
145154
IoUtils.closeIfCloseable(credentialsProvider, null);
@@ -150,6 +159,17 @@ public Builder toBuilder() {
150159
return new BuilderImpl(this);
151160
}
152161

162+
private AwsCredentialsProvider createCredentialsProvider(ProfileFile profileFile, String profileName) {
163+
// Load the profile and credentials provider
164+
return profileFile.profile(profileName)
165+
.flatMap(p -> new ProfileCredentialsUtils(profileFile, p, profileFile::profile).credentialsProvider())
166+
.orElseThrow(() -> {
167+
String errorMessage = String.format("Profile file contained no credentials for " +
168+
"profile '%s': %s", profileName, profileFile);
169+
return SdkClientException.builder().message(errorMessage).build();
170+
});
171+
}
172+
153173
/**
154174
* A builder for creating a custom {@link ProfileCredentialsProvider}.
155175
*/
@@ -158,6 +178,7 @@ public interface Builder extends CopyableBuilder<Builder, ProfileCredentialsProv
158178
/**
159179
* Define the profile file that should be used by this credentials provider. By default, the
160180
* {@link ProfileFile#defaultProfileFile()} is used.
181+
* @see #profileFile(ProfileFileSupplier)
161182
*/
162183
Builder profileFile(ProfileFile profileFile);
163184

@@ -167,6 +188,14 @@ public interface Builder extends CopyableBuilder<Builder, ProfileCredentialsProv
167188
*/
168189
Builder profileFile(Consumer<ProfileFile.Builder> profileFile);
169190

191+
/**
192+
* Define the mechanism for loading profile files.
193+
*
194+
* @param profileFileSupplier Supplier interface for generating a ProfileFile instance.
195+
* @see #profileFile(ProfileFile)
196+
*/
197+
Builder profileFile(ProfileFileSupplier profileFileSupplier);
198+
170199
/**
171200
* Define the name of the profile that should be used by this credentials provider. By default, the value in
172201
* {@link ProfileFileSystemSetting#AWS_PROFILE} is used.
@@ -176,27 +205,27 @@ public interface Builder extends CopyableBuilder<Builder, ProfileCredentialsProv
176205
/**
177206
* Create a {@link ProfileCredentialsProvider} using the configuration applied to this builder.
178207
*/
208+
@Override
179209
ProfileCredentialsProvider build();
180210
}
181211

182212
static final class BuilderImpl implements Builder {
183-
private ProfileFile profileFile;
213+
private ProfileFileSupplier profileFileSupplier;
184214
private String profileName;
185215
private Supplier<ProfileFile> defaultProfileFileLoader = ProfileFile::defaultProfileFile;
186216

187217
BuilderImpl() {
188218
}
189219

190220
BuilderImpl(ProfileCredentialsProvider provider) {
191-
this.profileFile = provider.profileFile;
192221
this.profileName = provider.profileName;
193222
this.defaultProfileFileLoader = provider.defaultProfileFileLoader;
223+
this.profileFileSupplier = provider.profileFileSupplier;
194224
}
195225

196226
@Override
197227
public Builder profileFile(ProfileFile profileFile) {
198-
this.profileFile = profileFile;
199-
return this;
228+
return profileFile(ProfileFileSupplier.wrapIntoNullableSupplier(profileFile));
200229
}
201230

202231
public void setProfileFile(ProfileFile profileFile) {
@@ -208,6 +237,16 @@ public Builder profileFile(Consumer<ProfileFile.Builder> profileFile) {
208237
return profileFile(ProfileFile.builder().applyMutation(profileFile).build());
209238
}
210239

240+
@Override
241+
public Builder profileFile(ProfileFileSupplier profileFileSupplier) {
242+
this.profileFileSupplier = profileFileSupplier;
243+
return this;
244+
}
245+
246+
public void setProfileFile(ProfileFileSupplier supplier) {
247+
profileFile(supplier);
248+
}
249+
211250
@Override
212251
public Builder profileName(String profileName) {
213252
this.profileName = profileName;
@@ -225,13 +264,15 @@ public ProfileCredentialsProvider build() {
225264

226265
/**
227266
* Override the default configuration file to be used when the customer does not explicitly set
228-
* profileName(profileName);
229-
* {@link #profileFile(ProfileFile)}. Use of this method is only useful for testing the default behavior.
267+
* profileFile(ProfileFile) or profileFileSupplier(supplier);
268+
* {@link #profileFile(ProfileFile)}. Use of this method is
269+
* only useful for testing the default behavior.
230270
*/
231271
@SdkTestInternalApi
232272
Builder defaultProfileFileLoader(Supplier<ProfileFile> defaultProfileFileLoader) {
233273
this.defaultProfileFileLoader = defaultProfileFileLoader;
234274
return this;
235275
}
236276
}
277+
237278
}

0 commit comments

Comments
 (0)