Skip to content

Commit d4098b8

Browse files
committed
Add tests; rename create to setup; cleanup
1 parent f9ce018 commit d4098b8

29 files changed

Lines changed: 1353 additions & 58 deletions

File tree

aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/ChainIdentityProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* SPI for registering an identity provider into a credential/token chain.
1313
*
1414
* <p>Implementations are discovered via the language's plugin mechanism (e.g., {@code ServiceLoader}
15-
* in Java) and sorted by {@link #ordering()} before {@link #create} is called. A provider
15+
* in Java) and sorted by {@link #ordering()} before {@link #setup} is called. A provider
1616
* registers its resolver by calling {@link ChainSetup#addResolver} or
1717
* {@link ChainSetup#addTerminalResolver} from within {@code create()}.
1818
*/
@@ -47,5 +47,5 @@ default Set<CredentialFeatureId> featureIds() {
4747
* @param identityType the identity class the chain is resolving.
4848
* @param setup the chain setup context.
4949
*/
50-
void create(Class<? extends Identity> identityType, ChainSetup setup);
50+
void setup(Class<? extends Identity> identityType, ChainSetup setup);
5151
}

aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/ChainSetup.java

Lines changed: 180 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,106 +9,256 @@
99
import java.util.List;
1010
import java.util.Set;
1111
import java.util.concurrent.ScheduledExecutorService;
12-
import software.amazon.smithy.java.auth.api.identity.Identity;
12+
import java.util.function.Function;
1313
import software.amazon.smithy.java.auth.api.identity.IdentityResolver;
1414
import software.amazon.smithy.java.aws.config.AwsProfile;
1515
import software.amazon.smithy.java.aws.config.AwsProfileFile;
1616
import software.amazon.smithy.java.context.Context;
17+
import software.amazon.smithy.utils.SmithyInternalApi;
1718

1819
/**
19-
* Mutable assembly context passed to each {@link ChainIdentityProvider#create} during chain
20-
* construction. Providers use this to read shared state and register resolvers.
20+
* Mutable assembly context passed to each {@link ChainIdentityProvider#setup} during
21+
* credential chain construction.
2122
*
22-
* <p>When {@link #addTerminalResolver} is called, assembly stops — no further providers are invoked.
23+
* <p>Providers use this to:
24+
* <ul>
25+
* <li>Read shared state ({@link #profile()}, {@link #profileFile()}, {@link #executor()})</li>
26+
* <li>Write shared state ({@link #setProfileFile(AwsProfileFile)}, {@link #setProfile(AwsProfile)})</li>
27+
* <li>Register resolvers ({@link #addResolver(IdentityResolver)}, {@link #addTerminalResolver(IdentityResolver)})</li>
28+
* <li>Read environment variables ({@link #getenv(String)})</li>
29+
* </ul>
30+
*
31+
* <p>When {@link #addTerminalResolver(IdentityResolver)} is called, assembly stops immediately
32+
* and no further providers are invoked.
33+
*
34+
* <p>This class is not thread-safe. It is used only during the single-threaded assembly phase.
35+
* Resolvers MUST NOT retain a reference to this object.
2336
*/
2437
public final class ChainSetup {
2538
private final ScheduledExecutorService executor;
2639
private final String profileNameOverride;
2740
private final Context properties;
2841
private final List<NamedResolver> resolvers = new ArrayList<>();
42+
private final Function<String, String> envFn;
2943
private AwsProfileFile profileFile;
3044
private AwsProfile profile;
3145
private boolean terminal;
32-
33-
// Current provider being assembled (set by the chain before calling create())
3446
private ChainIdentityProvider currentProvider;
3547

36-
public ChainSetup(ScheduledExecutorService executor) {
37-
this(executor, null);
48+
private ChainSetup(Builder builder) {
49+
this.executor = builder.executor;
50+
this.profileNameOverride = builder.profileNameOverride;
51+
this.properties = Context.create();
52+
this.envFn = builder.envFn;
3853
}
3954

40-
public ChainSetup(ScheduledExecutorService executor, String profileNameOverride) {
41-
this.executor = executor;
42-
this.profileNameOverride = profileNameOverride;
43-
this.properties = Context.create();
55+
/**
56+
* Creates a new builder for {@link ChainSetup}.
57+
*
58+
* @return a new builder.
59+
*/
60+
public static Builder builder() {
61+
return new Builder();
4462
}
4563

46-
/** Shared executor for background credential refresh. */
64+
/**
65+
* Returns the shared executor for scheduling background credential refresh tasks.
66+
*
67+
* @return the scheduled executor, or {@code null} if none was configured.
68+
*/
4769
public ScheduledExecutorService executor() {
4870
return executor;
4971
}
5072

51-
/** Client-specified profile name override, or {@code null} for default resolution. */
73+
/**
74+
* Returns the client-specified profile name override, or {@code null} to use the
75+
* default resolution order ({@code AWS_PROFILE} env var, {@code aws.profile} system
76+
* property, then {@code "default"}).
77+
*
78+
* @return the profile name override, or {@code null}.
79+
*/
5280
public String profileNameOverride() {
5381
return profileNameOverride;
5482
}
5583

56-
/** General-purpose property bag for sharing state between providers. */
84+
/**
85+
* Returns the value of the given environment variable, or {@code null} if not set.
86+
*
87+
* <p>In production this delegates to {@link System#getenv(String)}. In tests, a custom
88+
* function can be provided via {@link Builder#env(Function)} for isolation.
89+
*
90+
* @param name the environment variable name.
91+
* @return the value, or {@code null}.
92+
*/
93+
public String getenv(String name) {
94+
return envFn.apply(name);
95+
}
96+
97+
/**
98+
* Returns a general-purpose typed property bag for sharing additional state between
99+
* providers during assembly.
100+
*
101+
* @return the shared context properties.
102+
*/
57103
public Context properties() {
58104
return properties;
59105
}
60106

61-
/** The parsed AWS config/credentials file, or {@code null} if not yet loaded. */
107+
/**
108+
* Returns the parsed AWS config/credentials file, or {@code null} if not yet loaded.
109+
*
110+
* <p>Populated by the {@code SHARED_CONFIG} provider during assembly.
111+
*
112+
* @return the parsed profile file, or {@code null}.
113+
*/
62114
public AwsProfileFile profileFile() {
63115
return profileFile;
64116
}
65117

66-
/** Sets the parsed profile file. Called by the SHARED_CONFIG provider. */
118+
/**
119+
* Sets the parsed AWS config/credentials file. Called by the {@code SHARED_CONFIG}
120+
* provider during assembly.
121+
*
122+
* @param profileFile the parsed profile file.
123+
*/
67124
public void setProfileFile(AwsProfileFile profileFile) {
68125
this.profileFile = profileFile;
69126
}
70127

71-
/** The active profile, or {@code null} if not yet loaded. */
128+
/**
129+
* Returns the active AWS profile, or {@code null} if not yet loaded.
130+
*
131+
* <p>Populated by the {@code SHARED_CONFIG} provider during assembly.
132+
*
133+
* @return the active profile, or {@code null}.
134+
*/
72135
public AwsProfile profile() {
73136
return profile;
74137
}
75138

76-
/** Sets the active profile. Called by the SHARED_CONFIG provider. */
139+
/**
140+
* Sets the active AWS profile. Called by the {@code SHARED_CONFIG} provider during assembly.
141+
*
142+
* @param profile the active profile.
143+
*/
77144
public void setProfile(AwsProfile profile) {
78145
this.profile = profile;
79146
}
80147

81148
/**
82-
* Registers a resolver at the current provider's position. Assembly continues.
149+
* Registers a resolver at the current provider's position. Assembly continues after
150+
* this call. May be called multiple times to register multiple resolvers that stack
151+
* at this position.
152+
*
153+
* @param resolver the identity resolver to register.
83154
*/
84155
public void addResolver(IdentityResolver<?> resolver) {
85156
resolvers.add(new NamedResolver(currentProvider.name(), currentProvider.featureIds(), resolver));
86157
}
87158

88159
/**
89-
* Registers a resolver and stops assembly. No further providers will be called.
160+
* Registers a resolver and stops assembly immediately. No further providers will be
161+
* called. Use when the credential source is authoritative once detected (e.g.,
162+
* environment variables contain a complete set of credentials, or a profile explicitly
163+
* configures assume-role).
164+
*
165+
* @param resolver the identity resolver to register.
90166
*/
91167
public void addTerminalResolver(IdentityResolver<?> resolver) {
92168
resolvers.add(new NamedResolver(currentProvider.name(), currentProvider.featureIds(), resolver));
93169
this.terminal = true;
94170
}
95171

96-
// --- Package-private, used by CredentialChain ---
97-
172+
/**
173+
* Sets the current provider being assembled. Called by the chain before invoking
174+
* each provider's {@link ChainIdentityProvider#setup} method so that
175+
* {@link #addResolver} and {@link #addTerminalResolver} can associate the resolver
176+
* with the correct provider name and feature IDs.
177+
*
178+
* <p>This method is public to support unit testing of individual providers in
179+
* isolation. Production code should not call this directly.
180+
*
181+
* @param provider the provider currently being assembled.
182+
*/
183+
@SmithyInternalApi
98184
public void setCurrentProvider(ChainIdentityProvider provider) {
99185
this.currentProvider = provider;
100186
}
101187

188+
/**
189+
* Returns the list of resolvers registered during assembly, in the order they were added.
190+
*
191+
* @return the ordered list of named resolvers.
192+
*/
193+
public List<NamedResolver> resolvers() {
194+
return resolvers;
195+
}
196+
102197
boolean isTerminal() {
103198
return terminal;
104199
}
105200

106-
public List<NamedResolver> resolvers() {
107-
return resolvers;
108-
}
201+
/**
202+
* A resolver paired with the name and feature IDs of the provider that registered it.
203+
*
204+
* @param name the canonical name of the provider that registered this resolver.
205+
* @param featureIds the feature IDs to emit on successful resolution.
206+
* @param resolver the identity resolver.
207+
*/
208+
public record NamedResolver(String name, Set<CredentialFeatureId> featureIds, IdentityResolver<?> resolver) {}
209+
210+
/**
211+
* Builder for {@link ChainSetup}.
212+
*/
213+
public static final class Builder {
214+
private ScheduledExecutorService executor;
215+
private String profileNameOverride;
216+
private Function<String, String> envFn = System::getenv;
109217

110-
public record NamedResolver<I extends Identity>(
111-
String name,
112-
Set<CredentialFeatureId> featureIds,
113-
IdentityResolver<I> resolver) {}
218+
private Builder() {}
219+
220+
/**
221+
* Sets the shared executor for background credential refresh.
222+
*
223+
* @param executor the scheduled executor.
224+
* @return this builder.
225+
*/
226+
public Builder executor(ScheduledExecutorService executor) {
227+
this.executor = executor;
228+
return this;
229+
}
230+
231+
/**
232+
* Sets the profile name override. When set, the {@code SHARED_CONFIG} provider
233+
* uses this name instead of resolving from {@code AWS_PROFILE} or system properties.
234+
*
235+
* @param profileNameOverride the profile name to use.
236+
* @return this builder.
237+
*/
238+
public Builder profileNameOverride(String profileNameOverride) {
239+
this.profileNameOverride = profileNameOverride;
240+
return this;
241+
}
242+
243+
/**
244+
* Sets the function used to resolve environment variables. Defaults to
245+
* {@link System#getenv(String)}. Override in tests for isolation.
246+
*
247+
* @param envFn the environment variable lookup function.
248+
* @return this builder.
249+
*/
250+
public Builder env(Function<String, String> envFn) {
251+
this.envFn = envFn;
252+
return this;
253+
}
254+
255+
/**
256+
* Builds the {@link ChainSetup}.
257+
*
258+
* @return the constructed setup.
259+
*/
260+
public ChainSetup build() {
261+
return new ChainSetup(this);
262+
}
263+
}
114264
}

aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/CredentialChain.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,17 @@ static <I extends Identity> CredentialChain<I> assemble(
105105
List<ChainIdentityProvider> sorted = sortByOrdering(registrations);
106106

107107
// Call create() on each provider in sorted order.
108-
ChainSetup setup = new ChainSetup(executor);
108+
ChainSetup setup = ChainSetup.builder().executor(executor).build();
109109

110110
for (ChainIdentityProvider provider : sorted) {
111111
setup.setCurrentProvider(provider);
112-
provider.create(identityType, setup);
112+
provider.setup(identityType, setup);
113113
if (setup.isTerminal()) {
114114
break;
115115
}
116116
}
117117

118-
List<ChainSetup.NamedResolver> ordered = setup.resolvers();
118+
var ordered = setup.resolvers();
119119

120120
if (LOGGER.isDebugEnabled()) {
121121
LOGGER.debug("Assembled credential chain: {}",
@@ -207,6 +207,7 @@ private static void warnDetectedButUnclaimed(Set<StandardProvider> claimed) {
207207
}
208208

209209
@Override
210+
@SuppressWarnings("unchecked")
210211
public IdentityResult<I> resolveIdentity(Context requestProperties) {
211212
if (resolvers.isEmpty()) {
212213
return IdentityResult.ofError(getClass(),
@@ -218,16 +219,15 @@ public IdentityResult<I> resolveIdentity(Context requestProperties) {
218219
List<Object> errors = new ArrayList<>();
219220

220221
for (var nr : resolvers) {
221-
@SuppressWarnings("unchecked")
222-
var result = (IdentityResult<I>) nr.resolver().resolveIdentity(requestProperties);
222+
var result = nr.resolver().resolveIdentity(requestProperties);
223223
if (result.identity() != null) {
224224
if (!nr.featureIds().isEmpty()) {
225225
var ids = requestProperties.get(CallContext.FEATURE_IDS);
226226
if (ids != null) {
227227
ids.addAll(nr.featureIds());
228228
}
229229
}
230-
return result;
230+
return (IdentityResult<I>) result;
231231
}
232232
errors.add(nr.name());
233233
errors.add(result.error());

aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/StandardProvider.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ public boolean isDetected() {
5858

5959
/**
6060
* Parses AWS shared config/credentials files and stores the result on the
61-
* {@link ProviderContext} for downstream providers.
61+
* {@link ChainSetup} for downstream providers.
6262
*
6363
* <p>This provider does not itself resolve credentials — it returns {@code null} from
6464
* {@code create()}. Its purpose is to make the parsed profile available via
65-
* {@link ProviderContext#profile()} for all subsequent profile-based slots.
65+
* {@link ChainSetup#profile()} for all subsequent profile-based slots.
6666
*/
6767
SHARED_CONFIG(null) {
6868
@Override
@@ -76,11 +76,6 @@ public boolean isDetected() {
7676
}
7777
},
7878

79-
/**
80-
* Profile-based web identity token ({@code web_identity_token_file} + {@code role_arn}).
81-
*
82-
* <p>Requires the STS module. Reads the active profile from {@link ProviderContext#profile()}.
83-
*/
8479
/**
8580
* Profile-based static keys ({@code aws_access_key_id} + {@code aws_secret_access_key}).
8681
*

aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/config/CredentialProcessHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public Set<CredentialFeatureId> featureIds() {
6464
}
6565

6666
@Override
67-
public void create(Class<? extends Identity> identityType, ChainSetup setup) {
67+
public void setup(Class<? extends Identity> identityType, ChainSetup setup) {
6868
if (identityType != AwsCredentialsIdentity.class) {
6969
return;
7070
}

aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/config/SessionKeysHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public Set<CredentialFeatureId> featureIds() {
4848
}
4949

5050
@Override
51-
public void create(Class<? extends Identity> identityType, ChainSetup setup) {
51+
public void setup(Class<? extends Identity> identityType, ChainSetup setup) {
5252
if (identityType != AwsCredentialsIdentity.class) {
5353
return;
5454
}

0 commit comments

Comments
 (0)