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

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