LambdaEdgeManager.java
/*
* This file is part of the pl.wrzasq.lambda.
*
* @license http://mit-license.org/ The MIT license
* @copyright 2018 - 2019 © by Rafał Wrzeszcz - Wrzasq.pl.
*/
package pl.wrzasq.lambda.edgedeploy.service;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.zip.ZipInputStream;
import com.amazonaws.services.lambda.AWSLambda;
import com.amazonaws.services.lambda.model.CreateFunctionRequest;
import com.amazonaws.services.lambda.model.DeleteFunctionRequest;
import com.amazonaws.services.lambda.model.FunctionCode;
import com.amazonaws.services.lambda.model.PublishVersionRequest;
import com.amazonaws.services.lambda.model.PublishVersionResult;
import com.amazonaws.services.lambda.model.ResourceNotFoundException;
import com.amazonaws.services.lambda.model.TracingConfig;
import com.amazonaws.services.lambda.model.UpdateFunctionCodeRequest;
import com.amazonaws.services.lambda.model.UpdateFunctionConfigurationRequest;
import com.amazonaws.services.s3.AmazonS3;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.wrzasq.commons.aws.cloudformation.CustomResourceResponse;
import pl.wrzasq.lambda.edgedeploy.model.EdgeDeployRequest;
import pl.wrzasq.lambda.edgedeploy.zip.ZipBuilder;
/**
* Lambda API implementation.
*/
public class LambdaEdgeManager {
/**
* Logger.
*/
private Logger logger = LoggerFactory.getLogger(LambdaEdgeManager.class);
/**
* AWS Lambda API client.
*/
private AWSLambda lambda;
/**
* AWS S3 API client.
*/
private AmazonS3 s3;
/**
* JSON generator.
*/
private ObjectMapper objectMapper;
/**
* Initializes object with given Lambda client.
*
* @param lambda AWS Lambda client.
* @param s3 AWS S3 client.
* @param objectMapper JSON generator.
*/
public LambdaEdgeManager(AWSLambda lambda, AmazonS3 s3, ObjectMapper objectMapper) {
this.lambda = lambda;
this.s3 = s3;
this.objectMapper = objectMapper;
}
/**
* Handles function creation.
*
* @param input Resource creation request.
* @param physicalResourceId Physical ID of existing resource (if present).
* @return Data about published version.
*/
public CustomResourceResponse<PublishVersionResult> create(EdgeDeployRequest input, String physicalResourceId) {
var result = this.lambda.createFunction(
new CreateFunctionRequest()
.withFunctionName(input.getFunctionName())
.withDescription(input.getFunctionDescription())
.withRuntime(input.getRuntime())
.withCode(
new FunctionCode()
.withZipFile(this.buildZipFile(input))
)
.withHandler(input.getHandler())
.withMemorySize(input.getMemory())
.withTimeout(input.getTimeout())
.withRole(input.getRoleArn())
.withTracingConfig(
new TracingConfig()
.withMode(input.getTracingMode())
)
);
return new CustomResourceResponse<>(
this.publishLambdaVersion(input.getFunctionName()),
result.getFunctionArn()
);
}
/**
* Handles function update.
*
* @param input Resource update request.
* @param physicalResourceId Physical ID of existing resource (if present).
* @return Data about published version.
*/
public CustomResourceResponse<PublishVersionResult> update(EdgeDeployRequest input, String physicalResourceId) {
this.lambda.updateFunctionCode(
new UpdateFunctionCodeRequest()
.withFunctionName(input.getFunctionName())
.withZipFile(this.buildZipFile(input))
);
this.lambda.updateFunctionConfiguration(
new UpdateFunctionConfigurationRequest()
.withFunctionName(input.getFunctionName())
.withDescription(input.getFunctionDescription())
.withRuntime(input.getRuntime())
.withHandler(input.getHandler())
.withMemorySize(input.getMemory())
.withTimeout(input.getTimeout())
.withRole(input.getRoleArn())
.withTracingConfig(
new TracingConfig()
.withMode(input.getTracingMode())
)
);
return new CustomResourceResponse<>(
this.publishLambdaVersion(input.getFunctionName()),
physicalResourceId
);
}
/**
* Handles function deletion.
*
* @param input Resource delete request.
* @param physicalResourceId Physical ID of existing resource (if present).
* @return Data about deleted version.
*/
public CustomResourceResponse<PublishVersionResult> delete(EdgeDeployRequest input, String physicalResourceId) {
try {
this.lambda.deleteFunction(
new DeleteFunctionRequest()
.withFunctionName(input.getFunctionName())
);
} catch (ResourceNotFoundException error) {
this.logger.warn("Attempt to delete non-existing Lambda {}.", input.getFunctionName(), error);
}
return new CustomResourceResponse<>(
new PublishVersionResult()
.withFunctionName(input.getFunctionName()),
physicalResourceId
);
}
/**
* Publishes new version of Lambda.
*
* @param functionName Function stackSetName.
* @return Published version data.
*/
private PublishVersionResult publishLambdaVersion(String functionName) {
return this.lambda.publishVersion(
new PublishVersionRequest()
.withFunctionName(functionName)
);
}
/**
* Builds deployment ZIP package.
*
* @param input Function setup.
* @return ZIP file buffer.
*/
private ByteBuffer buildZipFile(EdgeDeployRequest input) {
var zip = new ZipBuilder();
try (
var archive = new ZipInputStream(
this.s3.getObject(input.getPackageBucket(), input.getPackageKey()).getObjectContent()
)
) {
zip.copyFrom(archive);
// dump custom configuration from request
zip.writeEntry(
input.getConfigFile(),
this.objectMapper.writeValueAsBytes(input.getConfig())
);
return zip.dump();
} catch (IOException error) {
throw new RuntimeException(error);
}
}
}