Merge pull request #2 from ethkatnic/provider-decoder-improvements
Multilang - Provider Decoder Improvements
This commit is contained in:
commit
f91140bb04
3 changed files with 171 additions and 61 deletions
|
|
@ -27,6 +27,8 @@ import java.util.stream.Stream;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
|
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
|
||||||
|
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;
|
||||||
|
import software.amazon.kinesis.multilang.auth.KclStsAssumeRoleCredentialsProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get AwsCredentialsProvider property.
|
* Get AwsCredentialsProvider property.
|
||||||
|
|
@ -79,58 +81,17 @@ class AwsCredentialsProviderPropertyValueDecoder implements IPropertyValueDecode
|
||||||
|
|
||||||
for (String providerName : providerNames) {
|
for (String providerName : providerNames) {
|
||||||
final String[] nameAndArgs = providerName.split("\\" + ARG_DELIMITER);
|
final String[] nameAndArgs = providerName.split("\\" + ARG_DELIMITER);
|
||||||
final Class<? extends AwsCredentialsProvider> clazz;
|
final Class<? extends AwsCredentialsProvider> clazz = getClass(nameAndArgs[0]);
|
||||||
try {
|
if (clazz == null) {
|
||||||
final Class<?> c = Class.forName(nameAndArgs[0]);
|
|
||||||
if (!AwsCredentialsProvider.class.isAssignableFrom(c)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
clazz = (Class<? extends AwsCredentialsProvider>) c;
|
|
||||||
} catch (ClassNotFoundException cnfe) {
|
|
||||||
// Providers are a product of prefixed Strings to cover multiple
|
|
||||||
// namespaces (e.g., "Foo" -> { "some.auth.Foo", "kcl.auth.Foo" }).
|
|
||||||
// It's expected that many class names will not resolve.
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
log.info("Attempting to construct {}", clazz);
|
log.info("Attempting to construct {}", clazz);
|
||||||
|
final String[] varargs =
|
||||||
AwsCredentialsProvider provider = null;
|
nameAndArgs.length > 1 ? Arrays.copyOfRange(nameAndArgs, 1, nameAndArgs.length) : new String[0];
|
||||||
if (nameAndArgs.length > 1) {
|
AwsCredentialsProvider provider = tryConstructor(providerName, clazz, varargs);
|
||||||
final String[] varargs = Arrays.copyOfRange(nameAndArgs, 1, nameAndArgs.length);
|
|
||||||
|
|
||||||
// attempt to invoke an explicit N-arg constructor of FooClass(String, String, ...)
|
|
||||||
provider = constructProvider(providerName, () -> {
|
|
||||||
Class<?>[] argTypes = new Class<?>[nameAndArgs.length - 1];
|
|
||||||
Arrays.fill(argTypes, String.class);
|
|
||||||
return clazz.getConstructor(argTypes).newInstance(varargs);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (provider == null) {
|
|
||||||
// attempt to invoke a public varargs/array constructor of FooClass(String[])
|
|
||||||
provider = constructProvider(providerName, () -> clazz.getConstructor(String[].class)
|
|
||||||
.newInstance((Object) varargs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
// regardless of parameters, fallback to invoke a public no-arg constructor
|
provider = tryCreate(providerName, clazz, varargs);
|
||||||
provider = constructProvider(providerName, clazz::newInstance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider == null) {
|
|
||||||
// if still not found, try empty create() method
|
|
||||||
try {
|
|
||||||
Method createMethod = clazz.getDeclaredMethod("create");
|
|
||||||
if (Modifier.isStatic(createMethod.getModifiers())) {
|
|
||||||
provider = constructProvider(providerName, () -> clazz.cast(createMethod.invoke(null)));
|
|
||||||
} else {
|
|
||||||
log.warn("Found non-static create() method in {}", providerName);
|
|
||||||
}
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
// No create() method found for class
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (provider != null) {
|
if (provider != null) {
|
||||||
credentialsProviders.add(provider);
|
credentialsProviders.add(provider);
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +99,101 @@ class AwsCredentialsProviderPropertyValueDecoder implements IPropertyValueDecode
|
||||||
return credentialsProviders;
|
return credentialsProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AwsCredentialsProvider tryConstructor(
|
||||||
|
String providerName, Class<? extends AwsCredentialsProvider> clazz, String[] varargs) {
|
||||||
|
AwsCredentialsProvider provider =
|
||||||
|
constructProvider(providerName, () -> getConstructorWithVarArgs(clazz, varargs));
|
||||||
|
if (provider == null) {
|
||||||
|
provider = constructProvider(providerName, () -> getConstructorWithArgs(clazz, varargs));
|
||||||
|
}
|
||||||
|
if (provider == null) {
|
||||||
|
provider = constructProvider(providerName, clazz::newInstance);
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AwsCredentialsProvider tryCreate(
|
||||||
|
String providerName, Class<? extends AwsCredentialsProvider> clazz, String[] varargs) {
|
||||||
|
AwsCredentialsProvider provider =
|
||||||
|
constructProvider(providerName, () -> getCreateMethod(clazz, (Object) varargs));
|
||||||
|
if (provider == null) {
|
||||||
|
provider = constructProvider(providerName, () -> getCreateMethod(clazz, varargs));
|
||||||
|
}
|
||||||
|
if (provider == null) {
|
||||||
|
provider = constructProvider(providerName, () -> getCreateMethod(clazz));
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AwsCredentialsProvider getConstructorWithVarArgs(
|
||||||
|
Class<? extends AwsCredentialsProvider> clazz, String[] varargs) {
|
||||||
|
try {
|
||||||
|
return clazz.getConstructor(String[].class).newInstance((Object) varargs);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AwsCredentialsProvider getConstructorWithArgs(
|
||||||
|
Class<? extends AwsCredentialsProvider> clazz, String[] varargs) {
|
||||||
|
try {
|
||||||
|
Class<?>[] argTypes = new Class<?>[varargs.length];
|
||||||
|
Arrays.fill(argTypes, String.class);
|
||||||
|
return clazz.getConstructor(argTypes).newInstance((Object[]) varargs);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AwsCredentialsProvider getCreateMethod(
|
||||||
|
Class<? extends AwsCredentialsProvider> clazz, Object... args) {
|
||||||
|
try {
|
||||||
|
Class<?>[] argTypes = new Class<?>[args.length];
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
argTypes[i] = args[i].getClass();
|
||||||
|
}
|
||||||
|
Method createMethod = clazz.getDeclaredMethod("create", argTypes);
|
||||||
|
if (Modifier.isStatic(createMethod.getModifiers())) {
|
||||||
|
return clazz.cast(createMethod.invoke(null, args));
|
||||||
|
} else {
|
||||||
|
log.warn("Found non-static create() method in {}", clazz.getName());
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// No matching create method found for class
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to invoke create() method in {}", clazz.getName(), e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the class for the given provider name.
|
||||||
|
*
|
||||||
|
* @param providerName A string containing the provider name.
|
||||||
|
*
|
||||||
|
* @return The Class object representing the resolved AwsCredentialsProvider implementation,
|
||||||
|
* or null if the class cannot be resolved or does not extend AwsCredentialsProvider.
|
||||||
|
*/
|
||||||
|
private static Class<? extends AwsCredentialsProvider> getClass(String providerName) {
|
||||||
|
// Convert any form of StsAssumeRoleCredentialsProvider string to KclStsAssumeRoleCredentialsProvider
|
||||||
|
if (providerName.equals(StsAssumeRoleCredentialsProvider.class.getSimpleName())
|
||||||
|
|| providerName.equals(StsAssumeRoleCredentialsProvider.class.getName())) {
|
||||||
|
providerName = KclStsAssumeRoleCredentialsProvider.class.getName();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final Class<?> c = Class.forName(providerName);
|
||||||
|
if (!AwsCredentialsProvider.class.isAssignableFrom(c)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (Class<? extends AwsCredentialsProvider>) c;
|
||||||
|
} catch (ClassNotFoundException cnfe) {
|
||||||
|
// Providers are a product of prefixed Strings to cover multiple
|
||||||
|
// namespaces (e.g., "Foo" -> { "some.auth.Foo", "kcl.auth.Foo" }).
|
||||||
|
// It's expected that many class names will not resolve.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static List<String> getProviderNames(String property) {
|
private static List<String> getProviderNames(String property) {
|
||||||
// assume list delimiter is ","
|
// assume list delimiter is ","
|
||||||
String[] elements = property.split(LIST_DELIMITER);
|
String[] elements = property.split(LIST_DELIMITER);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||||
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
||||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
|
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
|
||||||
|
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;
|
||||||
import software.amazon.kinesis.multilang.auth.KclStsAssumeRoleCredentialsProvider;
|
import software.amazon.kinesis.multilang.auth.KclStsAssumeRoleCredentialsProvider;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
|
@ -40,6 +41,7 @@ public class AWSCredentialsProviderPropertyValueDecoderTest {
|
||||||
|
|
||||||
private final String credentialName1 = AlwaysSucceedCredentialsProvider.class.getName();
|
private final String credentialName1 = AlwaysSucceedCredentialsProvider.class.getName();
|
||||||
private final String credentialName2 = ConstructorCredentialsProvider.class.getName();
|
private final String credentialName2 = ConstructorCredentialsProvider.class.getName();
|
||||||
|
private final String createCredentialClass = CreateProvider.class.getName();
|
||||||
private final AwsCredentialsProviderPropertyValueDecoder decoder = new AwsCredentialsProviderPropertyValueDecoder();
|
private final AwsCredentialsProviderPropertyValueDecoder decoder = new AwsCredentialsProviderPropertyValueDecoder();
|
||||||
|
|
||||||
@ToString
|
@ToString
|
||||||
|
|
@ -119,13 +121,32 @@ public class AWSCredentialsProviderPropertyValueDecoderTest {
|
||||||
public void testKclAuthProvider() {
|
public void testKclAuthProvider() {
|
||||||
for (final String className : Arrays.asList(
|
for (final String className : Arrays.asList(
|
||||||
KclStsAssumeRoleCredentialsProvider.class.getName(), // fully-qualified name
|
KclStsAssumeRoleCredentialsProvider.class.getName(), // fully-qualified name
|
||||||
KclStsAssumeRoleCredentialsProvider.class.getSimpleName() // name-only; needs prefix
|
KclStsAssumeRoleCredentialsProvider.class.getSimpleName(), // name-only; needs prefix
|
||||||
)) {
|
StsAssumeRoleCredentialsProvider.class.getName(), // user passes full sts package path
|
||||||
|
StsAssumeRoleCredentialsProvider.class.getSimpleName())) {
|
||||||
final AwsCredentialsProvider provider = decoder.decodeValue(className + "|arn|sessionName");
|
final AwsCredentialsProvider provider = decoder.decodeValue(className + "|arn|sessionName");
|
||||||
assertNotNull(className, provider);
|
assertNotNull(className, provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that OneArgCreateProvider in the SDK v2 can process a create() method
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testEmptyCreateProvider() {
|
||||||
|
AwsCredentialsProvider provider = decoder.decodeValue(createCredentialClass);
|
||||||
|
assertThat(provider, hasCredentials(TEST_ACCESS_KEY_ID, TEST_SECRET_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that OneArgCreateProvider in the SDK v2 can process a create(arg1) method
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOneArgCreateProvider() {
|
||||||
|
AwsCredentialsProvider provider = decoder.decodeValue(createCredentialClass + "|testCreateProperty");
|
||||||
|
assertThat(provider, hasCredentials("testCreateProperty", TEST_SECRET_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that a provider can be instantiated by its varargs constructor.
|
* Test that a provider can be instantiated by its varargs constructor.
|
||||||
*/
|
*/
|
||||||
|
|
@ -188,4 +209,28 @@ public class AWSCredentialsProviderPropertyValueDecoderTest {
|
||||||
return AwsBasicCredentials.create(flattenedArgs, flattenedArgs);
|
return AwsBasicCredentials.create(flattenedArgs, flattenedArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credentials provider to test AWS SDK v2 create() methods for providers like ProfileCredentialsProvider
|
||||||
|
*/
|
||||||
|
public static class CreateProvider implements AwsCredentialsProvider {
|
||||||
|
private String accessKeyId;
|
||||||
|
|
||||||
|
private CreateProvider(String accessKeyId) {
|
||||||
|
this.accessKeyId = accessKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CreateProvider create() {
|
||||||
|
return new CreateProvider(TEST_ACCESS_KEY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CreateProvider create(String accessKeyId) {
|
||||||
|
return new CreateProvider(accessKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AwsCredentials resolveCredentials() {
|
||||||
|
return AwsBasicCredentials.create(accessKeyId, TEST_SECRET_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,28 @@ However, KCL now provides better extensibility to handle, and be enhanced to han
|
||||||
This document should help multilang customers configure a suitable `CredentialProvider` (or contribute changes to support a new use case!).
|
This document should help multilang customers configure a suitable `CredentialProvider` (or contribute changes to support a new use case!).
|
||||||
|
|
||||||
## Sample Provider Configuration
|
## Sample Provider Configuration
|
||||||
DEPRECATED: StsAssumeRoleCredentialsProvider can no longer be constructed in this way:
|
|
||||||
```
|
|
||||||
AWSCredentialsProvider = StsAssumeRoleCredentialsProvider|<arn>|<sessionName>`
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a [StsAssumeRoleCredentialsProvider][sts-assume-provider], see KclStsAssumeRoleCredentialsProvider below.
|
In a Properties file, an `AWSCredentialsProperty` configuration might look like:
|
||||||
|
```
|
||||||
|
AWSCredentialsProvider = StsAssumeRoleCredentialsProvider|<arn>|<sessionName>
|
||||||
|
```
|
||||||
|
This basic configuration creates an [StsAssumeRoleCredentialsProvider][sts-assume-provider] with an ARN and session name.
|
||||||
|
|
||||||
You can create a default [DefaultCredentialsProvider][default-credentials-provider] or [AnonymousCredentialsProvider][anonymous-credentials-provider]
|
While functional, this configuration is limited.
|
||||||
by passing it in the config like:
|
For example, this configuration cannot set a regional endpoint (e.g., VPC use case).
|
||||||
|
|
||||||
|
Leveraging nested properties, an `AWSCredentialsProperty` value might change to:
|
||||||
|
```
|
||||||
|
AWSCredentialsProvider = KclSTSAssumeRoleSessionCredentialsProvider|<arn>|<sessionName>\
|
||||||
|
|endpointRegion=us-east-1|externalId=spartacus
|
||||||
|
```
|
||||||
|
N.B. Backslash (`\`) is for multi-line legibility and is not required.
|
||||||
|
|
||||||
|
You can create a default [DefaultCredentialsProvider][default-credentials-provider] by passing it in the config like:
|
||||||
```
|
```
|
||||||
AWSCredentialsProvider = DefaultCredentialsProvider
|
AWSCredentialsProvider = DefaultCredentialsProvider
|
||||||
```
|
```
|
||||||
|
|
||||||
If you wish to customize properties on an AWS SDK provider that uses a builder, like the StsASsumeRoleCredentialsProvider,
|
|
||||||
you will need to wrap this provider class, provide a constructor, and manage the build of the provider.
|
|
||||||
See implementation of [KclStsAssumeRoleCredentialsProvider][kcl-sts-provider]
|
|
||||||
|
|
||||||
## Nested Properties
|
## Nested Properties
|
||||||
|
|
||||||
KCL multilang supports "nested properties" on the `AWSCredentialsProvider` key in the properties file.
|
KCL multilang supports "nested properties" on the `AWSCredentialsProvider` key in the properties file.
|
||||||
|
|
@ -37,6 +42,10 @@ The [Backus-Naur form][bnf] of the value:
|
||||||
<nested-value ::= <string> # this depends on the nested key
|
<nested-value ::= <string> # this depends on the nested key
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In general, required parameters are passed directly to the class' constructor or .create() method
|
||||||
|
(e.g., [ProfileCredentialsProvider(String)][profile-credentials-provider-create]). However, most of these providers
|
||||||
|
require builders and will require a custom implementation similar to `KclStsAssumeRoleCredentialsProvider` for customization
|
||||||
|
|
||||||
Nested properties are a custom mapping provided by KCL multilang, and do not exist in the AWS SDK.
|
Nested properties are a custom mapping provided by KCL multilang, and do not exist in the AWS SDK.
|
||||||
See [NestedPropertyKey][nested-property-key] for the supported keys, and details on their expected values.
|
See [NestedPropertyKey][nested-property-key] for the supported keys, and details on their expected values.
|
||||||
|
|
||||||
|
|
@ -73,5 +82,5 @@ AWSCredentialsProvider = KclStsAssumeRoleCredentialsProvider|<arn>|<sessionName>
|
||||||
[nested-property-key]: /amazon-kinesis-client-multilang/src/main/java/software/amazon/kinesis/multilang/NestedPropertyKey.java
|
[nested-property-key]: /amazon-kinesis-client-multilang/src/main/java/software/amazon/kinesis/multilang/NestedPropertyKey.java
|
||||||
[nested-property-processor]: /amazon-kinesis-client-multilang/src/main/java/software/amazon/kinesis/multilang/NestedPropertyProcessor.java
|
[nested-property-processor]: /amazon-kinesis-client-multilang/src/main/java/software/amazon/kinesis/multilang/NestedPropertyProcessor.java
|
||||||
[sts-assume-provider]: https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.html
|
[sts-assume-provider]: https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.html
|
||||||
|
[profile-credentials-provider-create]: https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/auth/credentials/ProfileCredentialsProvider.html#create(java.lang.String)
|
||||||
[default-credentials-provider]: https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.html
|
[default-credentials-provider]: https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.html
|
||||||
[anonymous-credentials-provider]: https://sdk.amazonaws.com/java/api/2.0.0-preview-11/software/amazon/awssdk/auth/credentials/AnonymousCredentialsProvider.html
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue