15
15
16
16
package software .amazon .awssdk .auth .credentials ;
17
17
18
- import static java .util .Collections .singletonMap ;
19
18
import static java .util .Collections .unmodifiableSet ;
20
19
21
20
import java .io .IOException ;
22
21
import java .net .URI ;
22
+ import java .time .Instant ;
23
+ import java .time .temporal .ChronoUnit ;
23
24
import java .util .Arrays ;
24
- import java .util .Collections ;
25
+ import java .util .HashMap ;
25
26
import java .util .HashSet ;
26
27
import java .util .Map ;
27
28
import java .util .Set ;
28
29
import software .amazon .awssdk .annotations .SdkPublicApi ;
29
- import software .amazon .awssdk .annotations .SdkTestInternalApi ;
30
30
import software .amazon .awssdk .auth .credentials .internal .ContainerCredentialsRetryPolicy ;
31
+ import software .amazon .awssdk .auth .credentials .internal .HttpCredentialsLoader ;
32
+ import software .amazon .awssdk .auth .credentials .internal .HttpCredentialsLoader .LoadedCredentials ;
31
33
import software .amazon .awssdk .core .SdkSystemSetting ;
32
34
import software .amazon .awssdk .core .exception .SdkClientException ;
35
+ import software .amazon .awssdk .core .util .SdkUserAgent ;
33
36
import software .amazon .awssdk .regions .util .ResourcesEndpointProvider ;
34
37
import software .amazon .awssdk .regions .util .ResourcesEndpointRetryPolicy ;
38
+ import software .amazon .awssdk .utils .ComparableUtils ;
35
39
import software .amazon .awssdk .utils .StringUtils ;
36
40
import software .amazon .awssdk .utils .ToString ;
41
+ import software .amazon .awssdk .utils .Validate ;
42
+ import software .amazon .awssdk .utils .cache .CachedSupplier ;
43
+ import software .amazon .awssdk .utils .cache .NonBlocking ;
44
+ import software .amazon .awssdk .utils .cache .RefreshResult ;
37
45
38
46
/**
39
47
* {@link AwsCredentialsProvider} implementation that loads credentials from a local metadata service.
52
60
* Service (ECS)</a>
53
61
*/
54
62
@ SdkPublicApi
55
- public final class ContainerCredentialsProvider extends HttpCredentialsProvider {
56
- private final ResourcesEndpointProvider credentialsEndpointProvider ;
63
+ public final class ContainerCredentialsProvider implements HttpCredentialsProvider {
64
+ private static final Set <String > ALLOWED_HOSTS = unmodifiableSet (new HashSet <>(Arrays .asList ("localhost" , "127.0.0.1" )));
65
+
66
+ private final String endpoint ;
67
+ private final HttpCredentialsLoader httpCredentialsLoader ;
68
+ private final CachedSupplier <AwsCredentials > credentialsCache ;
57
69
58
70
/**
59
71
* @see #builder()
60
72
*/
61
73
private ContainerCredentialsProvider (BuilderImpl builder ) {
62
- super (builder );
63
- this .credentialsEndpointProvider = builder .credentialsEndpointProvider ;
74
+ this .endpoint = builder .endpoint ;
75
+ this .httpCredentialsLoader = HttpCredentialsLoader .create ();
76
+
77
+ if (Boolean .TRUE .equals (builder .asyncCredentialUpdateEnabled )) {
78
+ Validate .paramNotBlank (builder .asyncThreadName , "asyncThreadName" );
79
+ this .credentialsCache = CachedSupplier .builder (this ::refreshCredentials )
80
+ .prefetchStrategy (new NonBlocking (builder .asyncThreadName ))
81
+ .build ();
82
+ } else {
83
+ this .credentialsCache = CachedSupplier .builder (this ::refreshCredentials ).build ();
84
+ }
64
85
}
65
86
66
87
/**
@@ -71,21 +92,60 @@ public static Builder builder() {
71
92
}
72
93
73
94
@ Override
74
- protected ResourcesEndpointProvider getCredentialsEndpointProvider () {
75
- return credentialsEndpointProvider ;
95
+ public String toString () {
96
+ return ToString .create ("ContainerCredentialsProvider" );
97
+ }
98
+
99
+ private RefreshResult <AwsCredentials > refreshCredentials () {
100
+ LoadedCredentials loadedCredentials =
101
+ httpCredentialsLoader .loadCredentials (new ContainerCredentialsEndpointProvider (endpoint ));
102
+ Instant expiration = loadedCredentials .getExpiration ().orElse (null );
103
+
104
+ return RefreshResult .builder (loadedCredentials .getAwsCredentials ())
105
+ .staleTime (staleTime (expiration ))
106
+ .prefetchTime (prefetchTime (expiration ))
107
+ .build ();
108
+ }
109
+
110
+ private Instant staleTime (Instant expiration ) {
111
+ if (expiration == null ) {
112
+ return null ;
113
+ }
114
+
115
+ return expiration .minus (1 , ChronoUnit .MINUTES );
116
+ }
117
+
118
+ private Instant prefetchTime (Instant expiration ) {
119
+ Instant oneHourFromNow = Instant .now ().plus (1 , ChronoUnit .HOURS );
120
+
121
+ if (expiration == null ) {
122
+ return oneHourFromNow ;
123
+ }
124
+
125
+ Instant fifteenMinutesBeforeExpiration = expiration .minus (15 , ChronoUnit .MINUTES );
126
+
127
+ return ComparableUtils .minimum (oneHourFromNow , fifteenMinutesBeforeExpiration );
76
128
}
77
129
78
130
@ Override
79
- public String toString () {
80
- return ToString .create ("ContainerCredentialsProvider" );
131
+ public AwsCredentials resolveCredentials () {
132
+ return credentialsCache .get ();
133
+ }
134
+
135
+ @ Override
136
+ public void close () {
137
+ credentialsCache .close ();
81
138
}
82
139
83
140
static final class ContainerCredentialsEndpointProvider implements ResourcesEndpointProvider {
84
- private static final Set <String > ALLOWED_HOSTS = unmodifiableSet (new HashSet <>(Arrays .asList ("localhost" , "127.0.0.1" )));
141
+ private final String endpoint ;
142
+
143
+ ContainerCredentialsEndpointProvider (String endpoint ) {
144
+ this .endpoint = endpoint ;
145
+ }
85
146
86
147
@ Override
87
148
public URI endpoint () throws IOException {
88
-
89
149
if (!SdkSystemSetting .AWS_CONTAINER_CREDENTIALS_RELATIVE_URI .getStringValue ().isPresent () &&
90
150
!SdkSystemSetting .AWS_CONTAINER_CREDENTIALS_FULL_URI .getStringValue ().isPresent ()) {
91
151
throw SdkClientException .builder ()
@@ -117,26 +177,28 @@ public ResourcesEndpointRetryPolicy retryPolicy() {
117
177
118
178
@ Override
119
179
public Map <String , String > headers () {
120
- return SdkSystemSetting .AWS_CONTAINER_AUTHORIZATION_TOKEN .getStringValue ()
121
- .filter (StringUtils ::isNotBlank )
122
- .map (t -> singletonMap ("Authorization" , t ))
123
- .orElseGet (Collections ::emptyMap );
180
+ Map <String , String > requestHeaders = new HashMap <>();
181
+ requestHeaders .put ("User-Agent" , SdkUserAgent .create ().userAgent ());
182
+ SdkSystemSetting .AWS_CONTAINER_AUTHORIZATION_TOKEN .getStringValue ()
183
+ .filter (StringUtils ::isNotBlank )
184
+ .ifPresent (t -> requestHeaders .put ("Authorization" , t ));
185
+ return requestHeaders ;
124
186
}
125
187
126
188
private URI createUri (String relativeUri ) {
127
- return URI .create (SdkSystemSetting .AWS_CONTAINER_SERVICE_ENDPOINT .getStringValueOrThrow () + relativeUri );
189
+ String host = endpoint != null ? endpoint : SdkSystemSetting .AWS_CONTAINER_SERVICE_ENDPOINT .getStringValueOrThrow ();
190
+ return URI .create (host + relativeUri );
128
191
}
129
192
130
193
private URI createGenericContainerUrl () {
131
194
URI uri = URI .create (SdkSystemSetting .AWS_CONTAINER_CREDENTIALS_FULL_URI .getStringValueOrThrow ());
132
195
if (!ALLOWED_HOSTS .contains (uri .getHost ())) {
133
-
196
+ String envVarName = SdkSystemSetting . AWS_CONTAINER_CREDENTIALS_FULL_URI . environmentVariable ();
134
197
throw SdkClientException .builder ()
135
198
.message (String .format ("The full URI (%s) contained within environment " +
136
- "variable %s has an invalid host. Host can only be one of [%s]." ,
137
- uri ,
138
- SdkSystemSetting .AWS_CONTAINER_CREDENTIALS_FULL_URI
139
- .environmentVariable (),
199
+ "variable %s has an invalid host. Host can only be one of [%s]." ,
200
+ uri ,
201
+ envVarName ,
140
202
String .join ("," , ALLOWED_HOSTS )))
141
203
.build ();
142
204
}
@@ -148,29 +210,47 @@ private URI createGenericContainerUrl() {
148
210
* A builder for creating a custom a {@link ContainerCredentialsProvider}.
149
211
*/
150
212
public interface Builder extends HttpCredentialsProvider .Builder <ContainerCredentialsProvider , Builder > {
213
+ }
214
+
215
+ private static final class BuilderImpl implements Builder {
216
+ private String endpoint ;
217
+ private Boolean asyncCredentialUpdateEnabled ;
218
+ private String asyncThreadName ;
219
+
220
+ BuilderImpl () {
221
+ asyncThreadName ("container-credentials-provider" );
222
+ }
151
223
152
- /**
153
- * Build a {@link ContainerCredentialsProvider} from the provided configuration.
154
- */
155
224
@ Override
156
- ContainerCredentialsProvider build ();
157
- }
225
+ public Builder endpoint (String endpoint ) {
226
+ this .endpoint = endpoint ;
227
+ return this ;
228
+ }
158
229
159
- static final class BuilderImpl extends HttpCredentialsProvider .BuilderImpl <ContainerCredentialsProvider , Builder >
160
- implements Builder {
230
+ public void setEndpoint (String endpoint ) {
231
+ endpoint (endpoint );
232
+ }
161
233
162
- private ResourcesEndpointProvider credentialsEndpointProvider = new ContainerCredentialsEndpointProvider ();
234
+ @ Override
235
+ public Builder asyncCredentialUpdateEnabled (Boolean asyncCredentialUpdateEnabled ) {
236
+ this .asyncCredentialUpdateEnabled = asyncCredentialUpdateEnabled ;
237
+ return this ;
238
+ }
163
239
164
- BuilderImpl ( ) {
165
- super . asyncThreadName ( "container-credentials-provider" );
240
+ public void setAsyncCredentialUpdateEnabled ( boolean asyncCredentialUpdateEnabled ) {
241
+ asyncCredentialUpdateEnabled ( asyncCredentialUpdateEnabled );
166
242
}
167
243
168
- @ SdkTestInternalApi
169
- Builder credentialsEndpointProvider ( ResourcesEndpointProvider credentialsEndpointProvider ) {
170
- this .credentialsEndpointProvider = credentialsEndpointProvider ;
244
+ @ Override
245
+ public Builder asyncThreadName ( String asyncThreadName ) {
246
+ this .asyncThreadName = asyncThreadName ;
171
247
return this ;
172
248
}
173
249
250
+ public void setAsyncThreadName (String asyncThreadName ) {
251
+ asyncThreadName (asyncThreadName );
252
+ }
253
+
174
254
@ Override
175
255
public ContainerCredentialsProvider build () {
176
256
return new ContainerCredentialsProvider (this );
0 commit comments