All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.seanroy.plugins.AbstractLambdaMojo Maven / Gradle / Ivy

package com.github.seanroy.plugins;

import static com.amazonaws.util.CollectionUtils.isNullOrEmpty;
import static java.util.Collections.emptyList;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;

import com.amazonaws.AmazonWebServiceClient;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.cloudwatchevents.AmazonCloudWatchEvents;
import com.amazonaws.services.cloudwatchevents.AmazonCloudWatchEventsClientBuilder;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBStreams;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBStreamsClientBuilder;
import com.amazonaws.services.kinesis.AmazonKinesis;
import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder;
import com.amazonaws.services.lambda.AWSLambda;
import com.amazonaws.services.lambda.AWSLambdaClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.AmazonSNSClientBuilder;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

/**
 * Abstracts all common parameter handling and initiation of AWS service clients.
 *
 * @author sean, Krzysztof Grodzicki 11/08/16.
 */
@SuppressWarnings("ClassWithTooManyFields")
public abstract class AbstractLambdaMojo extends AbstractMojo {
    public static final String TRIG_INT_LABEL_CLOUDWATCH_EVENTS = "CloudWatch Events - Schedule";
    public static final String TRIG_INT_LABEL_DYNAMO_DB = "DynamoDB";
    public static final String TRIG_INT_LABEL_KINESIS = "Kinesis";
    public static final String TRIG_INT_LABEL_SNS = "SNS";
    public static final String TRIG_INT_LABEL_ALEXA_SK = "Alexa Skills Kit";
    public static final String TRIG_INT_LABEL_LEX = "Lex";
    
    public static final String PERM_LAMBDA_INVOKE = "lambda:InvokeFunction";
    
    public static final String PRINCIPAL_ALEXA  = "alexa-appkit.amazon.com";
    public static final String PRINCIPAL_LEX    = "lex.amazonaws.com";
    public static final String PRINCIPAL_SNS    = "sns.amazonaws.com";
    public static final String PRINCIPAL_EVENTS = "events.amazonaws.com"; // Cloudwatch events
    
    /**
     * 

The AWS access key.

*/ @Parameter(property = "accessKey", defaultValue = "${accessKey}") public String accessKey; /** *

The AWS secret access key.

*/ @Parameter(property = "secretKey", defaultValue = "${secretKey}") public String secretKey; /** *

The path to deliverable.

*/ @Parameter(property = "functionCode", defaultValue = "${functionCode}", required = true) public String functionCode; /** *

The version of deliverable. Example value can be 1.0-SNAPSHOT.

*/ @Parameter(property = "version", defaultValue = "${version}", required = true) public String version; /** *

Amazon region. Default value is us-east-1.

*/ @Parameter(property = "region", alias = "region", defaultValue = "us-east-1") public String regionName; /** *

* Amazon S3 bucket name where the .zip file containing your deployment * package is stored. This bucket must reside in the same AWS region where * you are creating the Lambda function. *

*/ @Parameter(property = "s3Bucket", defaultValue = "lambda-function-code") public String s3Bucket; /** *

* The runtime environment for the Lambda function. *

*

* To use the Node.js runtime v4.3, set the value to "nodejs4.3". To use * earlier runtime (v0.10.42), set the value to "nodejs". *

*/ @Parameter(property = "runtime", defaultValue = "java8") public String runtime; /** *

The Amazon Resource Name (ARN) of the IAM role that Lambda will assume when it executes your function.

*/ @Parameter(property = "lambdaRoleArn", defaultValue = "${lambdaRoleArn}", required = true) public String lambdaRoleArn; /** *

The JSON confuguration for Lambda functions. @see {@link LambdaFunction}.

*/ @Parameter(property = "lambdaFunctionsJSON") public String lambdaFunctionsJSON; /** *

The confuguration for Lambda functions. @see {@link LambdaFunction}. Can be configured in pom.xml. Automaticall parsed from JSON configuration.

*/ @Parameter(property = "lambdaFunctions", defaultValue = "${lambdaFunctions}") public List lambdaFunctions; /** *

* The function execution time at which AWS Lambda should terminate the * function. Because the execution time has cost implications, we recommend * you set this value based on your expected execution time. The default is 30 seconds. *

*/ @Parameter(property = "timeout", defaultValue = "30") public int timeout; /** *

* The amount of memory, in MB, your Lambda function is given. AWS Lambda * uses this memory size to infer the amount of CPU allocated to your * function. Your function use-case determines your CPU and memory * requirements. For example, a database operation might need less memory * compared to an image processing function. The default value is 1024 MB. * The value must be a multiple of 64 MB. *

*/ @Parameter(property = "memorySize", defaultValue = "1024") public int memorySize; /** *

A list of one or more security groups IDs in your VPC.

*/ @Parameter(property = "vpcSecurityGroupIds", defaultValue = "${vpcSecurityGroupIds}") public List vpcSecurityGroupIds; /** *

A list of one or more subnet IDs in your VPC.

*/ @Parameter(property = "vpcSubnetIds", defaultValue = "${vpcSubnetIds}") public List vpcSubnetIds; /** *

This boolean parameter can be used to request AWS Lambda to update the * Lambda function and publish a version as an atomic operation.

*/ @Parameter(property = "publish", defaultValue = "true") public boolean publish; /** *

The suffix for the lambda function.

*/ @Parameter(property = "functionNameSuffix") public String functionNameSuffix; /** *

This boolean parameter can be used to force update of existing configuration. Use it when you don't publish a function and want to replece code in your Lambda function.

*/ @Parameter(property = "forceUpdate", defaultValue = "false") public boolean forceUpdate; /** *

This map parameter can be used to define environment variables for Lambda functions enable you to dynamically pass settings to your function code and libraries, without making changes to your code. Deployment functionality merges those variables with the one provided in json configuration.

*/ @Parameter(property = "environmentVariables", defaultValue = "${environmentVariables}") public Map environmentVariables; @Parameter(property = "passThrough") public String passThrough; public String fileName; public AWSCredentials credentials; public AmazonS3 s3Client; public AWSLambda lambdaClient; public AmazonSNS snsClient; public AmazonCloudWatchEvents eventsClient; public AmazonDynamoDBStreams dynamoDBStreamsClient; public AmazonKinesis kinesisClient; public AmazonCloudWatchEvents cloudWatchEventsClient; @Override public void execute() throws MojoExecutionException { initAWSCredentials(); initAWSClients(); try { initFileName(); initVersion(); initLambdaFunctionsConfiguration(); lambdaFunctions.forEach(lambdaFunction -> getLog().debug(lambdaFunction.toString())); } catch (Exception e) { getLog().error("Initialization of configuration failed", e); throw new MojoExecutionException(e.getMessage()); } } private void initAWSCredentials() throws MojoExecutionException { DefaultAWSCredentialsProviderChain defaultChain = new DefaultAWSCredentialsProviderChain(); if (accessKey != null && secretKey != null) { credentials = new BasicAWSCredentials(accessKey, secretKey); } else if (defaultChain.getCredentials() != null) { credentials = defaultChain.getCredentials(); } if (credentials == null) { getLog().error("Unable to initialize AWS Credentials. Set BasicAWSCredentials with accessKey and secretKey or configure DefaultAWSCredentialsProviderChain"); throw new MojoExecutionException("AWS Credentials config error"); } } private void initFileName() { String pattern = Pattern.quote(File.separator); String[] pieces = functionCode.split(pattern); fileName = pieces[pieces.length - 1]; } private void initVersion() { version = version.replace(".", "-"); } @SuppressWarnings("rawtypes") Function clientFactory = b -> { Regions region = Regions.fromName(regionName); return (AmazonWebServiceClient) of(credentials) .map(credentials -> b.withCredentials(new AWSStaticCredentialsProvider(credentials)).withRegion(region).build()) .orElse(b.withRegion(region).withCredentials(new DefaultAWSCredentialsProviderChain()).build()); }; private void initAWSClients() { s3Client = (AmazonS3) clientFactory.apply(AmazonS3ClientBuilder.standard()); lambdaClient = (AWSLambda) clientFactory.apply(AWSLambdaClientBuilder.standard()); snsClient = (AmazonSNS) clientFactory.apply(AmazonSNSClientBuilder.standard()); eventsClient = (AmazonCloudWatchEvents) clientFactory.apply(AmazonCloudWatchEventsClientBuilder.standard()); dynamoDBStreamsClient = (AmazonDynamoDBStreams) clientFactory.apply(AmazonDynamoDBStreamsClientBuilder.standard()); kinesisClient = (AmazonKinesis) clientFactory.apply(AmazonKinesisClientBuilder.standard()); cloudWatchEventsClient = (AmazonCloudWatchEvents) clientFactory.apply(AmazonCloudWatchEventsClientBuilder.standard()); } private void initLambdaFunctionsConfiguration() throws MojoExecutionException, IOException { if (lambdaFunctionsJSON != null) { this.lambdaFunctions = JsonUtil.fromJson(lambdaFunctionsJSON); } validate(lambdaFunctions); lambdaFunctions = lambdaFunctions.stream().map(lambdaFunction -> { String functionName = ofNullable(lambdaFunction.getFunctionName()).orElseThrow(() -> new IllegalArgumentException("Configuration error. LambdaFunction -> 'functionName' is required")); lambdaFunction.withFunctionName(addSuffix(functionName)) .withHandler(ofNullable(lambdaFunction.getHandler()).orElseThrow(() -> new IllegalArgumentException("Configuration error. LambdaFunction -> 'handler' is required"))) .withDescription(ofNullable(lambdaFunction.getDescription()).orElse("")) .withTimeout(ofNullable(lambdaFunction.getTimeout()).orElse(timeout)) .withMemorySize(ofNullable(lambdaFunction.getMemorySize()).orElse(memorySize)) .withSubnetIds(ofNullable(vpcSubnetIds).orElse(new ArrayList<>())) .withSecurityGroupsIds(ofNullable(vpcSecurityGroupIds).orElse(new ArrayList<>())) .withVersion(version) .withPublish(ofNullable(lambdaFunction.isPublish()).orElse(publish)) .withAliases(aliases(lambdaFunction.isPublish())) .withTriggers(ofNullable(lambdaFunction.getTriggers()).map(triggers -> triggers.stream() .map(trigger -> { trigger.withRuleName(addSuffix(trigger.getRuleName())); trigger.withSNSTopic(addSuffix(trigger.getSNSTopic())); trigger.withDynamoDBTable(addSuffix(trigger.getDynamoDBTable())); trigger.withLexBotName(addSuffix(trigger.getLexBotName())); return trigger; }) .collect(toList())) .orElse(new ArrayList<>())) .withEnvironmentVariables(environmentVariables(lambdaFunction)); return lambdaFunction; }).collect(toList()); } private Map environmentVariables(LambdaFunction lambdaFunction) { Map envVar0 = ofNullable(environmentVariables).orElse(new HashMap<>()); Map envVar1 = ofNullable(lambdaFunction.getEnvironmentVariables()).orElse(new HashMap<>()); Type type = new TypeToken>(){}.getType(); Map passThroughEnvironmentVariables = new GsonBuilder().create().fromJson(ofNullable(passThrough).orElse("{}"), type); return Stream.of(envVar0, envVar1, passThroughEnvironmentVariables) .map(Map::entrySet) .flatMap(Collection::stream) .collect(toMap(Entry::getKey, Entry::getValue)); } private String addSuffix(String functionName) { return ofNullable(functionNameSuffix).map(suffix -> Stream.of(functionName, suffix).collect(Collectors.joining())) .orElse(functionName); } private List aliases(boolean publish) { if (publish) { return Collections.singletonList(version); } return emptyList(); } private void validate(List lambdaFunctions) throws MojoExecutionException { if (isNullOrEmpty(lambdaFunctions)) { getLog().error("At least one function has to be provided in configuration"); throw new MojoExecutionException("Illegal configuration. Configuration for at least one Lambda function has to be provided"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy