Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make aws client creation parameters configurable #453

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
9 changes: 9 additions & 0 deletions src/main/java/gyro/aws/AwsCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class AwsCredentials extends Credentials {
private String profileName;
private String region;
private AwsCredentialsProvider provider;
private String clientConfig;

public AwsCredentials() {
this.provider = AwsCredentialsProviderChain.builder()
Expand Down Expand Up @@ -61,6 +62,14 @@ public AwsCredentialsProvider provider() {
return provider;
}

public String getClientConfig() {
return clientConfig;
}

public void setClientConfig(String clientConfig) {
this.clientConfig = clientConfig;
}

@Override
public void refresh() {
provider().resolveCredentials();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/gyro/aws/AwsFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected C newClient() {
Class<C> clientClass = (Class<C>) TypeDefinition.getInstance(getClass())
.getInferredGenericTypeArgumentClass(AwsFinder.class, 0);

return AwsResource.createClient(clientClass, credentials(AwsCredentials.class), getRegion(), getEndpoint());
return AwsResource.createClient(clientClass, credentials(AwsCredentials.class), getRegion(), getEndpoint(), null);
}

protected String getRegion() {
Expand Down
54 changes: 43 additions & 11 deletions src/main/java/gyro/aws/AwsResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@
import java.util.HashMap;
import java.util.Map;

import com.psddev.dari.util.ObjectUtils;
import gyro.aws.clientconfiguration.ClientConfiguration;
import gyro.aws.clientconfiguration.ClientConfigurationSettings;
import gyro.core.GyroException;
import gyro.core.resource.Diffable;
import gyro.core.resource.DiffableInternals;
import gyro.core.resource.Resource;
import gyro.core.scope.DiffableScope;
import gyro.core.scope.Scope;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder;
import software.amazon.awssdk.core.SdkClient;
Expand All @@ -44,7 +50,7 @@ protected <T extends SdkClient> T createClient(Class<T> clientClass) {
return ((AwsResource) parent).createClient(clientClass);
}

return createClient(clientClass, null, null);
return createClient(clientClass, null, "");
}

@SuppressWarnings("unchecked")
Expand All @@ -55,15 +61,16 @@ protected <T extends SdkClient> T createClient(Class<T> clientClass, String regi
}

AwsCredentials credentials = credentials(AwsCredentials.class);
client = createClient(clientClass, credentials, region, endpoint);
DiffableScope scope = DiffableInternals.getScope(this);
client = createClient(clientClass, credentials, region, endpoint, scope);
return (T) client;
}

public static synchronized <T extends SdkClient> T createClient(Class<T> clientClass, AwsCredentials credentials) {
return createClient(clientClass, credentials, null, null);
public static synchronized <T extends SdkClient> T createClient(Class<T> clientClass, AwsCredentials credentials, Scope scope) {
return createClient(clientClass, credentials, null, null, scope);
}

public static synchronized <T extends SdkClient> T createClient(Class<T> clientClass, AwsCredentials credentials, String region, String endpoint) {
public static synchronized <T extends SdkClient> T createClient(Class<T> clientClass, AwsCredentials credentials, String region, String endpoint, Scope scope) {
if (credentials == null) {
throw new GyroException(String.format(
"Unable to create %s, no credentials specified!",
Expand All @@ -81,22 +88,47 @@ public static synchronized <T extends SdkClient> T createClient(Class<T> clientC

if (clients.get(key) == null) {
try {
AwsCredentialsProvider provider = credentials.provider();

ClientOverrideConfiguration.Builder retryPolicy = ClientOverrideConfiguration.builder()
ClientOverrideConfiguration overrideConfiguration = ClientOverrideConfiguration.builder()
.retryPolicy(RetryPolicy.builder()
.numRetries(20)
.retryCapacityCondition(RetryOnThrottlingCondition.create())
.build());
.build()).build();

ApacheHttpClient.Builder httpClientBuilder = ApacheHttpClient.builder();

String clientConfig = credentials.getClientConfig();
if (ObjectUtils.isBlank(clientConfig)) {
clientConfig = "default";
}

if (scope != null) {
ClientConfiguration clientConfiguration = scope.getRootScope().getSettings(ClientConfigurationSettings.class)
.getClientConfigurations()
.get(clientConfig);

if (clientConfiguration != null) {
if (clientConfiguration.getHttpClientConfiguration() != null) {
httpClientBuilder = clientConfiguration.getHttpClientConfiguration().toApacheHttpClient();
}

if (clientConfiguration.getOverrideConfiguration() != null) {
overrideConfiguration = clientConfiguration.getOverrideConfiguration()
.toClientOverrideConfiguration();
}
}
}

AwsCredentialsProvider provider = credentials.provider();

Method method = clientClass.getMethod("builder");
AwsDefaultClientBuilder builder = (AwsDefaultClientBuilder) method.invoke(null);
builder.credentialsProvider(provider);
builder.region(Region.of(region != null ? region : credentials.getRegion()));
builder.httpClientBuilder(ApacheHttpClient.builder());
builder.overrideConfiguration(retryPolicy.build());
builder.httpClientBuilder(httpClientBuilder);
builder.overrideConfiguration(overrideConfiguration);

if (endpoint != null) {
if (!ObjectUtils.isBlank(endpoint)) {
builder.endpointOverride(URI.create(endpoint));
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/gyro/aws/DynamoDbLockBackend.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ private Optional<Map<String, AttributeValue>> getCurrentLock() {
}

private DynamoDbClient client() {
return AwsResource.createClient(DynamoDbClient.class, credentials());
return AwsResource.createClient(DynamoDbClient.class, credentials(), getRootScope());
}

private AwsCredentials credentials() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/gyro/aws/S3FileBackend.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ private S3Client client() {
.get(String.format("%s::%s", "aws", credentialName));
}

return AwsResource.createClient(S3Client.class, (AwsCredentials) credentials);
return AwsResource.createClient(S3Client.class, (AwsCredentials) credentials, getRootScope());
}

private String prefixed(String file) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2021, Brightspot, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package gyro.aws.clientconfiguration;

public class ClientConfiguration implements ClientConfigurationInterface {

private ClientOverrideConfiguration overrideConfiguration;
private HttpClientConfiguration httpClientConfiguration;

public ClientOverrideConfiguration getOverrideConfiguration() {
return overrideConfiguration;
}

public void setOverrideConfiguration(ClientOverrideConfiguration overrideConfiguration) {
this.overrideConfiguration = overrideConfiguration;
}

public HttpClientConfiguration getHttpClientConfiguration() {
return httpClientConfiguration;
}

public void setHttpClientConfiguration(HttpClientConfiguration httpClientConfiguration) {
this.httpClientConfiguration = httpClientConfiguration;
}

@Override
public void validate() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright 2021, Brightspot, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package gyro.aws.clientconfiguration;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import com.google.common.base.CaseFormat;
import com.psddev.dari.util.ObjectUtils;
import gyro.core.GyroException;
import gyro.core.Reflections;
import gyro.core.Type;
import gyro.core.directive.DirectiveProcessor;
import gyro.core.scope.RootScope;
import gyro.core.scope.Scope;
import gyro.lang.ast.Node;
import gyro.lang.ast.block.DirectiveNode;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

@Type("client-configuration")
public class ClientConfigurationDirectiveProcessor extends DirectiveProcessor<RootScope> {

@Override
public void process(RootScope scope, DirectiveNode node) throws Exception {
validateArguments(node, 0, 1);

String name = getArgument(scope, node, String.class, 0);

if (ObjectUtils.isBlank(name)) {
name = "default";
}

Scope bodyScope = evaluateBody(scope, node);

ClientConfigurationSettings settings = scope.getSettings(ClientConfigurationSettings.class);

ClientConfiguration clientConfiguration = (ClientConfiguration) process(
ClientConfiguration.class,
scope,
bodyScope);

settings.getClientConfigurations().put(name, clientConfiguration);
}

// Automate config consumption mapping fields to class parameters
private Object process(Class<?> configClass, RootScope scope, Scope bodyScope) {
Object configClassObj = Reflections.newInstance(configClass);
if (bodyScope != null) {
for (PropertyDescriptor property : Reflections.getBeanInfo(configClass).getPropertyDescriptors()) {
Method setter = property.getWriteMethod();
if (setter != null) {
java.lang.reflect.Type genericParameterType = setter.getGenericParameterTypes()[0];
Object configVal = bodyScope.get(CaseFormat.LOWER_CAMEL.to(
CaseFormat.LOWER_HYPHEN,
property.getName()));

// Recursively assign values for complex gyro types
if (genericParameterType.getTypeName().contains("gyro")) {
if (configVal instanceof List) {
if (genericParameterType.getTypeName().contains("java.util.List")) {
if (genericParameterType instanceof ParameterizedTypeImpl) {
List<?> configVals = (List<?>) configVal;
List<Object> processedConfigs = new ArrayList<>();
for (Object obj : configVals) {
if (obj instanceof Scope) {
processedConfigs.add(process(
(Class<?>) ((ParameterizedTypeImpl) genericParameterType).getActualTypeArguments()[0],
scope,
(Scope) obj));
}
}
configVal = processedConfigs;
}
} else if (((List<?>) configVal).size() > 0) {
Object obj = ((List<?>) configVal).get(0);
if (obj instanceof Scope) {
configVal = process(setter.getParameterTypes()[0], scope, (Scope) obj);
}
}
}
}

if (configVal != null) {
Reflections.invoke(setter, configClassObj, scope.convertValue(
setter.getGenericParameterTypes()[0],
configVal));
}
}
}
}

if (ClientConfigurationInterface.class.isAssignableFrom(configClass)) {
try {
((ClientConfigurationInterface) configClassObj).validate();
} catch (ClientConfigurationException ex) {
if (!ObjectUtils.isBlank(ex.getField())) {
Node node = bodyScope.getLocation(ex.getField());
if (node != null) {
throw new GyroException(bodyScope.getLocation(ex.getField()), ex.getFormattedMessage());
}
}

Node parent = bodyScope.getParent()
.getLocation(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, configClass.getSimpleName()));
if (parent != null) {
throw new GyroException(parent, ex.getFormattedMessage());
} else {
throw new GyroException(ex.getFormattedMessage());
}
}
}

return configClassObj;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2021, Brightspot, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package gyro.aws.clientconfiguration;

public class ClientConfigurationException extends RuntimeException {

private final String field;

public ClientConfigurationException(String field, String message, Throwable cause) {
super(message, cause);
this.field = field;
}

public ClientConfigurationException(String field, String message) {
this(field, message, null);
}

public String getField() {
return field;
}

public String getFormattedMessage() {
return String.format("%s%s", (getField() != null ? String.format("%s: ", getField()) : ""), getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2021, Brightspot, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package gyro.aws.clientconfiguration;

public interface ClientConfigurationInterface {

void validate();
}
Loading