Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ static class DefaultAwsClientFactory implements AwsClientFactory {
@Override
public S3Client s3() {
return S3Client.builder()
.applyMutation(awsProperties::applyClientRegionConfiguration)
.applyMutation(awsProperties::applyHttpClientConfigurations)
.applyMutation(awsProperties::applyS3EndpointConfigurations)
.applyMutation(awsProperties::applyS3ServiceConfigurations)
Expand All @@ -109,22 +110,28 @@ public S3Client s3() {
@Override
public GlueClient glue() {
return GlueClient.builder()
.applyMutation(awsProperties::applyClientRegionConfiguration)
.applyMutation(awsProperties::applyHttpClientConfigurations)
.applyMutation(awsProperties::applyGlueEndpointConfigurations)
.applyMutation(awsProperties::applyClientCredentialConfigurations)
.build();
}

@Override
public KmsClient kms() {
return KmsClient.builder()
.applyMutation(awsProperties::applyClientRegionConfiguration)
.applyMutation(awsProperties::applyHttpClientConfigurations)
.applyMutation(awsProperties::applyClientCredentialConfigurations)
.build();
}

@Override
public DynamoDbClient dynamo() {
return DynamoDbClient.builder()
.applyMutation(awsProperties::applyClientRegionConfiguration)
.applyMutation(awsProperties::applyHttpClientConfigurations)
.applyMutation(awsProperties::applyClientCredentialConfigurations)
.applyMutation(awsProperties::applyDynamoDbEndpointConfigurations)
.build();
}
Expand Down
140 changes: 132 additions & 8 deletions aws/src/main/java/org/apache/iceberg/aws/AwsProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.iceberg.aws.lakeformation.LakeFormationAwsClientFactory;
import org.apache.iceberg.aws.s3.S3FileIO;
import org.apache.iceberg.aws.s3.signer.S3V4RestSignerClient;
import org.apache.iceberg.common.DynClasses;
import org.apache.iceberg.common.DynMethods;
import org.apache.iceberg.exceptions.ValidationException;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
Expand All @@ -44,6 +45,7 @@
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
import software.amazon.awssdk.awscore.client.builder.AwsSyncClientBuilder;
import software.amazon.awssdk.core.client.builder.SdkClientBuilder;
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
Expand Down Expand Up @@ -350,6 +352,38 @@ public class AwsProperties implements Serializable {
*/
public static final String CLIENT_ASSUME_ROLE_SESSION_NAME = "client.assume-role.session-name";

/**
* Configure the AWS credentials provider used to create AWS clients. A fully qualified concrete
* class with package that implements the {@link AwsCredentialsProvider} interface is required.
*
* <p>Additionally, the implementation class must also have a create() or create(Map) method
* implemented, which returns an instance of the class that provides aws credentials provider.
*
* <p>Example:
* client.credentials-provider=software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider
*
* <p>When set, the default client factory {@link
* org.apache.iceberg.aws.AwsClientFactories.DefaultAwsClientFactory} and also other client
* factory classes will use this provider to get AWS credentials provided instead of reading the
* default credential chain to get AWS access credentials.
*/
public static final String CLIENT_CREDENTIALS_PROVIDER = "client.credentials-provider";

/**
* Used by the client.credentials-provider configured value that will be used by {@link
* org.apache.iceberg.aws.AwsClientFactories.DefaultAwsClientFactory} and also other client
* factory classes to pass provider-specific properties. Each property consists of a key name and
* an associated value.
*/
private static final String CLIENT_CREDENTIAL_PROVIDER_PREFIX = "client.credentials-provider.";

/**
* Used by {@link org.apache.iceberg.aws.AwsClientFactories.DefaultAwsClientFactory} and also
* other client factory classes. If set, all AWS clients except STS client will use the given
* region instead of the default region chain.
*/
public static final String CLIENT_REGION = "client.region";

/**
* The type of {@link software.amazon.awssdk.http.SdkHttpClient} implementation used by {@link
* AwsClientFactory} If set, all AWS clients will use this specified HTTP client. If not set,
Expand Down Expand Up @@ -676,6 +710,9 @@ public class AwsProperties implements Serializable {
private int clientAssumeRoleTimeoutSec;
private String clientAssumeRoleRegion;
private String clientAssumeRoleSessionName;
private String clientRegion;
private String clientCredentialsProvider;
private final Map<String, String> clientCredentialsProviderProperties;

private String s3FileIoSseType;
private String s3FileIoSseKey;
Expand Down Expand Up @@ -732,6 +769,9 @@ public AwsProperties() {
this.clientAssumeRoleExternalId = null;
this.clientAssumeRoleRegion = null;
this.clientAssumeRoleSessionName = null;
this.clientRegion = null;
this.clientCredentialsProvider = null;
this.clientCredentialsProviderProperties = null;

this.s3FileIoSseType = S3FILEIO_SSE_TYPE_NONE;
this.s3FileIoSseKey = null;
Expand Down Expand Up @@ -793,6 +833,10 @@ public AwsProperties(Map<String, String> properties) {
this.clientAssumeRoleExternalId = properties.get(CLIENT_ASSUME_ROLE_EXTERNAL_ID);
this.clientAssumeRoleRegion = properties.get(CLIENT_ASSUME_ROLE_REGION);
this.clientAssumeRoleSessionName = properties.get(CLIENT_ASSUME_ROLE_SESSION_NAME);
this.clientRegion = properties.get(CLIENT_REGION);
this.clientCredentialsProvider = properties.get(CLIENT_CREDENTIALS_PROVIDER);
this.clientCredentialsProviderProperties =
PropertyUtil.propertiesWithPrefix(properties, CLIENT_CREDENTIAL_PROVIDER_PREFIX);

this.s3FileIoSseType = properties.getOrDefault(S3FILEIO_SSE_TYPE, S3FILEIO_SSE_TYPE_NONE);
this.s3FileIoSseKey = properties.get(S3FILEIO_SSE_KEY);
Expand Down Expand Up @@ -1115,6 +1159,14 @@ public Map<String, String> httpClientProperties() {
return httpClientProperties;
}

public String clientRegion() {
return clientRegion;
}

public void setClientRegion(String clientRegion) {
this.clientRegion = clientRegion;
}

/**
* Configure the credentials for an S3 client.
*
Expand All @@ -1129,6 +1181,36 @@ public <T extends S3ClientBuilder> void applyS3CredentialConfigurations(T builde
credentialsProvider(s3AccessKeyId, s3SecretAccessKey, s3SessionToken));
}

/**
* Configure a client AWS region.
*
* <p>Sample usage:
*
* <pre>
* S3Client.builder().applyMutation(awsProperties::applyClientRegionConfiguration)
* </pre>
*/
public <T extends AwsClientBuilder> void applyClientRegionConfiguration(T builder) {
if (clientRegion != null) {
builder.region(Region.of(clientRegion));
}
}

/**
* Configure the credential provider for AWS clients.
*
* <p>Sample usage:
*
* <pre>
* DynamoDbClient.builder().applyMutation(awsProperties::applyClientCredentialConfigurations)
* </pre>
*/
public <T extends AwsClientBuilder> void applyClientCredentialConfigurations(T builder) {
if (!Strings.isNullOrEmpty(this.clientCredentialsProvider)) {
builder.credentialsProvider(credentialsProvider(this.clientCredentialsProvider));
}
}

/**
* Configure services settings for an S3 client. The settings include: s3DualStack,
* s3UseArnRegion, s3PathStyleAccess, and s3Acceleration
Expand Down Expand Up @@ -1185,14 +1267,12 @@ public <T extends AwsSyncClientBuilder> void applyHttpClientConfigurations(T bui
switch (httpClientType) {
case HTTP_CLIENT_TYPE_URLCONNECTION:
UrlConnectionHttpClientConfigurations urlConnectionHttpClientConfigurations =
(UrlConnectionHttpClientConfigurations)
loadHttpClientConfigurations(UrlConnectionHttpClientConfigurations.class.getName());
loadHttpClientConfigurations(UrlConnectionHttpClientConfigurations.class.getName());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for refactoring this.

urlConnectionHttpClientConfigurations.configureHttpClientBuilder(builder);
break;
case HTTP_CLIENT_TYPE_APACHE:
ApacheHttpClientConfigurations apacheHttpClientConfigurations =
(ApacheHttpClientConfigurations)
loadHttpClientConfigurations(ApacheHttpClientConfigurations.class.getName());
loadHttpClientConfigurations(ApacheHttpClientConfigurations.class.getName());
apacheHttpClientConfigurations.configureHttpClientBuilder(builder);
break;
default:
Expand Down Expand Up @@ -1288,8 +1368,52 @@ private AwsCredentialsProvider credentialsProvider(
return StaticCredentialsProvider.create(
AwsSessionCredentials.create(accessKeyId, secretAccessKey, sessionToken));
}
} else {
return DefaultCredentialsProvider.create();
}

if (!Strings.isNullOrEmpty(this.clientCredentialsProvider)) {
return credentialsProvider(this.clientCredentialsProvider);
}

return DefaultCredentialsProvider.create();
}

private AwsCredentialsProvider credentialsProvider(String credentialsProviderClass) {
Comment thread
RussellSpitzer marked this conversation as resolved.
Class<?> providerClass;
try {
providerClass = DynClasses.builder().impl(credentialsProviderClass).buildChecked();
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(
String.format(
"Cannot load class %s, it does not exist in the classpath", credentialsProviderClass),
e);
}

Preconditions.checkArgument(
AwsCredentialsProvider.class.isAssignableFrom(providerClass),
String.format(
"Cannot initialize %s, it does not implement %s.",
credentialsProviderClass, AwsCredentialsProvider.class.getName()));

AwsCredentialsProvider provider;
try {
try {
provider =
DynMethods.builder("create")
.hiddenImpl(providerClass, Map.class)
.buildStaticChecked()
.invoke(clientCredentialsProviderProperties);
} catch (NoSuchMethodException e) {
provider =
DynMethods.builder("create").hiddenImpl(providerClass).buildStaticChecked().invoke();
}

return provider;
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
String.format(
"Cannot create an instance of %s, it does not contain a static 'create' or 'create(Map<String, String>)' method",
credentialsProviderClass),
e);
}
}

Expand All @@ -1305,15 +1429,15 @@ private <T extends SdkClientBuilder> void configureEndpoint(T builder, String en
* software.amazon.awssdk.http.apache.ApacheHttpClient}, since including both will cause error
* described in <a href="https://github.com/apache/iceberg/issues/6715">issue#6715</a>
*/
private Object loadHttpClientConfigurations(String impl) {
private <T> T loadHttpClientConfigurations(String impl) {
Object httpClientConfigurations;
try {
httpClientConfigurations =
DynMethods.builder("create")
.hiddenImpl(impl, Map.class)
.buildStaticChecked()
.invoke(httpClientProperties);
return httpClientConfigurations;
return (T) httpClientConfigurations;
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
String.format("Cannot create %s to generate and configure the http client builder", impl),
Expand Down
Loading