br.com.ingenieux.mojo.lambda.DeployMojo Maven / Gradle / Ivy
package br.com.ingenieux.mojo.lambda;
import br.com.ingenieux.mojo.aws.util.GlobUtil;
import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient;
import com.amazonaws.services.identitymanagement.model.ListRolesRequest;
import com.amazonaws.services.identitymanagement.model.ListRolesResult;
import com.amazonaws.services.identitymanagement.model.Role;
import com.amazonaws.services.lambda.AWSLambdaClient;
import com.amazonaws.services.lambda.model.*;
import com.amazonaws.services.lambda.model.Runtime;
import com.amazonaws.services.s3.AmazonS3URI;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import java.io.File;
import java.io.FileInputStream;
import java.util.*;
import java.util.regex.Pattern;
import static java.lang.String.format;
import static org.codehaus.plexus.util.StringUtils.isBlank;
/**
* Represents the AWS Lambda Deployment Process, which means:
*
*
* - Parsing the function-definition file
* - For each declared function, tries to update the function
* - if the function is missing, create it
* - Otherwise, compare the function definition with the expected parameters, and changes the function configuration if needed
*
*/
@Mojo(name = "deploy-functions")
public class DeployMojo extends AbstractLambdaMojo {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* Lambda Function URL on S3, e.g. s3://somebucket/object/key/path.zip
*/
@Parameter(required = true, property = "lambda.s3url")
String s3Url;
/**
* AWS Lambda Default Timeout, in seconds (used when missing in function definition)
*/
@Parameter(required = true, property = "lambda.default.timeout", defaultValue = "5")
Integer defaultTimeout;
/**
* AWS Lambda Default Memory Size, in MB (used when missing in function definition)
*/
@Parameter(required = true, property = "lambda.default.memorySize", defaultValue = "128")
Integer defaultMemorySize;
/**
* AWS Lambda Default IAM Role (used when missing in function definition)
*
* Allows wildcards like '*' and '?' - will be looked up upon when deploying
*/
@Parameter(required = true, property = "lambda.default.role", defaultValue = "arn:aws:iam::*:role/lambda_basic_execution")
String defaultRole;
/**
* Definition File
*
* Consists of a JSON file array as such:
*
* [ {
* "name": "AWS Function Name",
* "handler": "AWS Function Handler ref",
* "timeout": 5,
* "memorySize": 128,
* "role": "aws role"
* }
* ]
*
* Where:
*
*
* - Name is the AWS Lambda Function Name
* - Handler is the Handler Ref (for Java, it is
classname::functionName
)
* - Timeout is the timeout
* - memorySize is the memory
* - Role is the AWS Service Role
*
*
* Of those, only name
and handler
are obligatory.
*/
@Parameter(required = true, property = "lambda.definition.file", defaultValue = "${project.build.outputDirectory}/META-INF/lambda-definitions.json")
File definitionFile;
private AWSLambdaClient lambdaClient;
private AmazonIdentityManagementClient iamClient;
private Set roles;
private AmazonS3URI s3Uri;
// /**
// * Glob of Functions to Include (default: all)
// */
// @Parameter(property="lambda.function.includes")
// List includes = Collections.singletonList("*");
//
// /**
// * Glob of Functions to Exclude (default: empty)
// */
// @Parameter(property="lambda.function.excludes")
// List excludes = Collections.emptyList();
@Override
protected void configure() {
super.configure();
try {
configureInternal();
} catch (Exception exc) {
throw new RuntimeException(exc);
}
}
private void configureInternal() throws MojoExecutionException {
lambdaClient = this.getService();
iamClient = createServiceFor(AmazonIdentityManagementClient.class);
s3Uri = new AmazonS3URI(s3Url);
roles = loadRoles();
defaultRole = lookupRoleGlob(defaultRole);
}
private Set loadRoles() {
Set result = new TreeSet();
boolean done = false;
String marker = null;
do {
final ListRolesRequest listRolesRequest = new ListRolesRequest();
listRolesRequest.setMarker(marker);
final ListRolesResult listRolesResult = iamClient.listRoles(listRolesRequest);
for (Role r : listRolesResult.getRoles()) {
result.add(r.getArn());
}
done = (!listRolesResult.isTruncated());
marker = listRolesResult.getMarker();
} while (!done);
return result;
}
@Override
protected Object executeInternal() throws Exception {
Map functionDefinitions = parseFunctionDefinions();
String s3Bucket = s3Uri.getBucket();
String s3Key = s3Uri.getKey();
for (LambdaFunctionDefinition d : functionDefinitions.values()) {
getLog().info(format("Deploying Function: %s (handler: %s)", d.getName(), d.getHandler()));
try {
final UpdateFunctionCodeResult updateFunctionCodeResult = lambdaClient.updateFunctionCode(new UpdateFunctionCodeRequest().withFunctionName(d.getName()).withS3Bucket(s3Bucket).withS3Key(s3Key));
updateIfNeeded(d, updateFunctionCodeResult);
} catch (ResourceNotFoundException exc) {
getLog().info("Function does not exist. Creating it instead.");
createFunction(d);
}
}
return functionDefinitions;
}
private CreateFunctionResult createFunction(LambdaFunctionDefinition d) {
CreateFunctionRequest req = new CreateFunctionRequest().
withCode(new FunctionCode().withS3Bucket(s3Uri.getBucket()).withS3Key(s3Uri.getKey())).
withDescription(d.getDescription()).
withFunctionName(d.getName()).
withHandler(d.getHandler()).
withMemorySize(d.getMemorySize()).
withRole(d.getRole()).
withRuntime(Runtime.Java8).
withTimeout(d.getTimeout());
final CreateFunctionResult createFunctionResult = lambdaClient.createFunction(req);
return createFunctionResult;
}
private UpdateFunctionConfigurationResult updateIfNeeded(LambdaFunctionDefinition d, UpdateFunctionCodeResult curFc) {
boolean bEquals = new EqualsBuilder().
append(d.getDescription(), curFc.getDescription()).
append(d.getHandler(), curFc.getHandler()).
append(d.getMemorySize(), curFc.getMemorySize().intValue()).
append(d.getRole(), curFc.getRole()).
append(d.getTimeout(), curFc.getTimeout().intValue()).isEquals();
if (!bEquals) {
final UpdateFunctionConfigurationRequest updRequest = new UpdateFunctionConfigurationRequest();
updRequest.setFunctionName(d.getName());
updRequest.setDescription(d.getDescription());
updRequest.setHandler(d.getHandler());
updRequest.setMemorySize(d.getMemorySize());
updRequest.setRole(d.getRole());
updRequest.setTimeout(d.getTimeout());
getLog().info(format("Function Configuration doesn't match expected defaults. Updating it to %s.", updRequest));
final UpdateFunctionConfigurationResult result = lambdaClient.updateFunctionConfiguration(updRequest);
return result;
}
return null;
}
private Map parseFunctionDefinions() throws Exception {
String source = IOUtils.toString(new FileInputStream(definitionFile));
source = new StrSubstitutor(this.getPluginContext()).replace(source);
getLog().info(format("Loaded and replaced definitions from file '%s'", definitionFile.getPath()));
List definitionList = OBJECT_MAPPER.readValue(source, new TypeReference>() {});
getLog().info(format("Found %d definitions: ", definitionList.size()));
Map result = new TreeMap();
for (LambdaFunctionDefinition d : definitionList) {
if (0 == d.getMemorySize()) {
d.setMemorySize(defaultMemorySize);
}
if (isBlank(d.getRole())) {
d.setRole(defaultRole);
} else {
d.setRole(lookupRoleGlob(d.getRole()));
}
if (0 == d.getTimeout()) {
d.setTimeout(defaultTimeout);
}
result.put(d.getName(), d);
}
getLog().info(format("Merged into %d definitions: ", result.size()));
return result;
}
private String lookupRoleGlob(String role) {
if (GlobUtil.hasWildcards(role)) {
getLog().info(format("Looking up IAM Role '%s'", role));
Pattern p = GlobUtil.globify(role);
for (String s : roles) {
if (p.matcher(s).matches()) {
getLog().info(format("Found Role: '%s'", s));
return s;
}
}
throw new IllegalStateException("Unable to lookup role '" + role + "': Not found");
} else {
getLog().info(format("Using Role as is: '%s'", role));
return role;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy