Cleaning up

This commit is contained in:
Kenny Bastani
2016-12-20 17:30:24 -08:00
parent 878e40a2e5
commit 2af2788bc7
154 changed files with 2507 additions and 451 deletions

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-starters</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-boot-starters</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>event-stream-processing-microservices</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modules>
<module>spring-boot-starter-aws-lambda</module>
</modules>
</project>

View File

@@ -0,0 +1,92 @@
# Spring Boot Starter AWS Lambda
This starter project provides auto-configuration support classes to easily invoke _AWS Lambda Functions_ from a Spring Boot application.
* Configuration property classes for externalizing AWS credentials as IAM access tokens
* Automatic authenticated session management for securely invoking Lambda functions
* Provides a Spring Boot friendly config adapter for easily registering Lambda function invocation interfaces
## Usage
In your Spring Boot project, add the starter project dependency to your class path. For Maven, add the following dependency to your `pom.xml`.
```xml
<dependencies>
<dependency>
<groupId>org.kbastani</groupId>
<artifactId>spring-boot-starter-aws-lambda</artifactId>
<version>${spring-boot-starter-aws-lambda.version}</version>
</dependency>
...
</dependencies>
```
Next, inject your AWS IAM credentials safely into the `application.properties|yaml` file for your Spring Boot application. The snippet below shows an example of how to source the access credentials from the environment.
```yaml
spring:
profiles: development
server:
port: 8081
amazon:
aws:
access-key-id: ${AWS_ACCESS_KEY_ID}
access-key-secret: ${AWS_ACCESS_KEY_SECRET}
```
You can also set the properties using command line arguments with the Maven Spring Boot plugin, shown in the snippet below.
```bash
$ mvn spring-boot:run -Drun.arguments="--amazon.aws.access-key-id=ABCDEFG,--amazon.aws.access-key-secret=ZYXKGFWG"
```
You can now begin to invoke AWS Lambda functions from your AWS account. The next thing you'll need to do is to define an interface of lambda function references to invoke.
```java
public interface LambdaFunctions {
@LambdaFunction(functionName="account-created-13P0EDGLDE399", logType = LogType.Tail)
Account accountCreated(AccountEvent event);
@LambdaFunction(functionName="account-activated-1P0I6FTFCMHKH", logType = LogType.Tail)
Account accountActivated(AccountEvent event);
}
```
To start invoking your AWS Lambda functions, you can define a new bean that creates a proxy instance of your lambda interface.
```
@Configuration
public class AwsLambdaConfig {
@Bean
public LambdaFunctions lambdaFunctions(AWSLambdaConfigurerAdapter configurerAdapter) {
return configurerAdapter.getFunctionInstance(LambdaFunctions.class);
}
}
```
In the example above, we inject the auto-configured `AWSLambdaConfigurerAdapter` dependency from `spring-boot-starter-aws-lambda` into a new Spring bean definition named `lambdaFunctions`. The configurer adapter will create an instance of an interface that contains `@LambdaFunction` annotated methods—like in the example snippet for the `LambdaFunctions` interface.
We can now inject the `LambdaFunctions` as a dependency into other Spring components in our application in order to easily invoke remote Lambda functions on AWS.
```
@Service
public class AccountService {
final private LambdaFunctions lambdaFunctions;
public AccountService(LambdaFunctions lambdaFunctions) {
this.lambdaFunctions = lambdaFunctions;
}
public Account createAccount(Account account) {
// Trigger the new event by invoking AWS lambda
Account result = lambdaFunctions
.accountCreated(new AccountEvent(account, EventType.ACCOUNT_CREATED));
return result;
}
}
```

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-starter-aws-lambda</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-lambda</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sts</artifactId>
<version>${aws-java-sdk-sts.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bom</artifactId>
<version>${aws-java-sdk-sts.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -0,0 +1,95 @@
package amazon.aws;
import com.amazonaws.auth.*;
import com.amazonaws.services.lambda.AWSLambdaClientBuilder;
import com.amazonaws.services.lambda.invoke.LambdaFunction;
import com.amazonaws.services.lambda.invoke.LambdaInvokerFactory;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient;
import com.amazonaws.services.securitytoken.model.Credentials;
import com.amazonaws.services.securitytoken.model.GetSessionTokenRequest;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* Provides a configurer for invoking remote AWS Lambda functions using {@link LambdaInvokerFactory}.
* This component also manages the authenticated session for an IAM user that has provided valid
* access keys to access AWS resources.
*
* @author kbastani
*/
@Component
public class AWSLambdaConfigurerAdapter {
private String accessKeyId;
private String accessKeySecret;
private Credentials sessionCredentials;
/**
* Create a new instance of the {@link AWSLambdaConfigurerAdapter} with the bucket name and access credentials
*
* @param accessKeyId is the access key id credential for the specified bucket name
* @param accessKeySecret is the access key secret for the specified bucket name
*/
public AWSLambdaConfigurerAdapter(String accessKeyId,
String accessKeySecret) {
this.accessKeyId = accessKeyId;
this.accessKeySecret = accessKeySecret;
}
/**
* Creates a proxy instance of a supplied interface that contains methods annotated with
* {@link LambdaFunction}. Provides automatic credential support to authenticate with an IAM
* access keys using {@link BasicSessionCredentials} auto-configured from Spring Boot
* configuration properties in {@link AmazonProperties}.
*
* @param type
* @param <T>
* @return
*/
public <T> T getFunctionInstance(Class<T> type) {
return LambdaInvokerFactory.builder()
.lambdaClient(AWSLambdaClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(
getBasicSessionCredentials()))
.build())
.build(type);
}
/**
* Get the basic session credentials for the template's configured IAM authentication keys
*
* @return a {@link BasicSessionCredentials} instance with a valid authenticated session token
*/
private BasicSessionCredentials getBasicSessionCredentials() {
// Create a new session token if the session is expired or not initialized
if (sessionCredentials == null || sessionCredentials.getExpiration().before(new Date()))
sessionCredentials = getSessionCredentials();
// Create basic session credentials using the generated session token
return new BasicSessionCredentials(sessionCredentials.getAccessKeyId(),
sessionCredentials.getSecretAccessKey(),
sessionCredentials.getSessionToken());
}
/**
* Creates a new session credential that is valid for 12 hours
*
* @return an authenticated {@link Credentials} for the new session token
*/
private Credentials getSessionCredentials() {
// Create a new session with the user credentials for the service instance
AWSSecurityTokenServiceClient stsClient =
new AWSSecurityTokenServiceClient(new BasicAWSCredentials(accessKeyId, accessKeySecret));
// Start a new session for managing a service instance's bucket
GetSessionTokenRequest getSessionTokenRequest =
new GetSessionTokenRequest().withDurationSeconds(43200);
// Get the session token for the service instance's bucket
sessionCredentials = stsClient.getSessionToken(getSessionTokenRequest).getCredentials();
return sessionCredentials;
}
}

View File

@@ -0,0 +1,28 @@
package amazon.aws;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* This class auto-configures a {@link AWSLambdaConfigurerAdapter} bean.
*
* @author kbastani
*/
@Configuration
@ConditionalOnMissingBean(AWSLambdaConfigurerAdapter.class)
@EnableConfigurationProperties(AmazonProperties.class)
public class AmazonAutoConfiguration {
@Autowired
private AmazonProperties amazonProperties;
@Bean
protected AWSLambdaConfigurerAdapter lambdaAdapter() {
return new AWSLambdaConfigurerAdapter(
amazonProperties.getAws().getAccessKeyId(),
amazonProperties.getAws().getAccessKeySecret());
}
}

View File

@@ -0,0 +1,89 @@
package amazon.aws;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.context.annotation.Configuration;
/**
* Provides a configuration properties model for authenticating with AWS.
*
* @author kbastani
*/
@Configuration
@ConfigurationProperties(prefix = "amazon")
public class AmazonProperties {
@NestedConfigurationProperty
private Aws aws;
/**
* A property group for Amazon Web Service (AWS) configurations
*
* @return a property group for AWS configurations
*/
public Aws getAws() {
return aws;
}
/**
* A property group for Amazon Web Service (AWS) configurations
*
* @param aws is a property group for AWS configurations
*/
public void setAws(Aws aws) {
this.aws = aws;
}
/**
* A property group for Amazon Web Service (AWS) configurations
*/
public static class Aws {
private String accessKeyId;
private String accessKeySecret;
/**
* A valid AWS account's access key id.
*
* @return an AWS access key id
*/
public String getAccessKeyId() {
return accessKeyId;
}
/**
* A valid AWS account's access key id.
*
* @param accessKeyId is a valid AWS account's access key id.
*/
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
/**
* A valid AWS account's secret access token.
*
* @return an AWS account's secret access key
*/
public String getAccessKeySecret() {
return accessKeySecret;
}
/**
* A valid AWS account's secret access token.
*
* @param accessKeySecret is a valid AWS account's secret access token.
*/
public void setAccessKeySecret(String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
@Override
public String toString() {
return "Aws{" +
"accessKeyId='" + accessKeyId + '\'' +
", accessKeySecret='" + accessKeySecret + '\'' +
'}';
}
}
}

View File

@@ -0,0 +1,9 @@
{
"groups": [
{
"name": "amazon",
"type": "amazon.aws.AmazonProperties",
"sourceType": "amazon.aws.AmazonProperties"
}
]
}

View File

@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=amazon.aws.AmazonAutoConfiguration

View File

@@ -0,0 +1,2 @@
amazon.aws.access-key-id=replace
amazon.aws.access-key-secret=replace

View File

@@ -0,0 +1,44 @@
package amazon.aws;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import static junit.framework.TestCase.assertNotNull;
public class AmazonConfigurationTest {
private AnnotationConfigApplicationContext context;
@After
public void tearDown() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void defaultAdapter() {
load(EmptyConfiguration.class,
"amazon.aws.access-key-id=AJGLDLSXKDFLS",
"amazon.aws.access-key-secret=XSDFSDFLKKHASDFJALASDF");
AWSLambdaConfigurerAdapter amazonS3Template = this.context.getBean(AWSLambdaConfigurerAdapter.class);
assertNotNull(amazonS3Template);
}
@Configuration
static class EmptyConfiguration {
}
private void load(Class<?> config, String... environment) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(applicationContext, environment);
applicationContext.register(config);
applicationContext.register(AmazonAutoConfiguration.class);
applicationContext.refresh();
this.context = applicationContext;
}
}