From d67e61bfa0415b886678a938caebe19fb7405b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Wrzeszcz?= Date: Thu, 10 Oct 2019 03:10:47 +0200 Subject: [PATCH] Enhanced Lambda function macro. --- lambda-cform/lambda-cform-account/pom.xml | 2 +- .../lambda-cform-logretention/pom.xml | 2 +- .../lambda-cform-organization-unit/pom.xml | 2 +- .../lambda-cform-organization/pom.xml | 2 +- .../lambda-cform-passwordpolicy/pom.xml | 2 +- .../lambda-cform-stackset-instance/pom.xml | 2 +- lambda-cform/lambda-cform-stackset/pom.xml | 2 +- lambda-edgedeploy/pom.xml | 6 +- lambda-json/pom.xml | 4 +- .../lambda-macro-lambda-function/pom.xml | 110 +++++ .../src/main/checkstyle/java.header | 8 + .../lambda/macro/lambda/function/Handler.java | 48 ++ .../model/CloudFormationMacroRequest.java | 30 ++ .../model/CloudFormationMacroResponse.java | 40 ++ .../template/LambdaFunctionResource.java | 409 ++++++++++++++++++ .../function/template/ProcessedTemplate.java | 204 +++++++++ .../template/ResourcesDefinition.java | 31 ++ .../src/main/resources/logback.xml | 29 ++ .../src/site/markdown/guide/usage.md | 128 ++++++ .../src/site/site.xml | 23 + .../macro/lambda/function/HandlerTest.java | 76 ++++ .../template/LambdaFunctionResourceTest.java | 30 ++ .../template/ProcessedTemplateTest.java | 39 ++ .../src/test/resources/input.json | 114 +++++ .../src/test/resources/output.json | 267 ++++++++++++ lambda-macro/pom.xml | 36 ++ lambda-macro/src/main/checkstyle/java.header | 8 + lambda-macro/src/site/site.xml | 19 + .../lambda-metrics-dynamodb/pom.xml | 2 +- .../lambda/metrics/dynamodb/Handler.java | 2 +- pom.xml | 1 + 31 files changed, 1664 insertions(+), 14 deletions(-) create mode 100644 lambda-macro/lambda-macro-lambda-function/pom.xml create mode 100644 lambda-macro/lambda-macro-lambda-function/src/main/checkstyle/java.header create mode 100644 lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/Handler.java create mode 100644 lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/model/CloudFormationMacroRequest.java create mode 100644 lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/model/CloudFormationMacroResponse.java create mode 100644 lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/LambdaFunctionResource.java create mode 100644 lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/ProcessedTemplate.java create mode 100644 lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/ResourcesDefinition.java create mode 100644 lambda-macro/lambda-macro-lambda-function/src/main/resources/logback.xml create mode 100644 lambda-macro/lambda-macro-lambda-function/src/site/markdown/guide/usage.md create mode 100644 lambda-macro/lambda-macro-lambda-function/src/site/site.xml create mode 100644 lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/HandlerTest.java create mode 100644 lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/template/LambdaFunctionResourceTest.java create mode 100644 lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/template/ProcessedTemplateTest.java create mode 100644 lambda-macro/lambda-macro-lambda-function/src/test/resources/input.json create mode 100644 lambda-macro/lambda-macro-lambda-function/src/test/resources/output.json create mode 100644 lambda-macro/pom.xml create mode 100644 lambda-macro/src/main/checkstyle/java.header create mode 100644 lambda-macro/src/site/site.xml diff --git a/lambda-cform/lambda-cform-account/pom.xml b/lambda-cform/lambda-cform-account/pom.xml index cc700d082..1f963c51c 100644 --- a/lambda-cform/lambda-cform-account/pom.xml +++ b/lambda-cform/lambda-cform-account/pom.xml @@ -112,7 +112,7 @@ pl.wrzasq.commons commons-aws - 1.0.21 + 1.0.24 diff --git a/lambda-cform/lambda-cform-logretention/pom.xml b/lambda-cform/lambda-cform-logretention/pom.xml index 1f20e3c84..0cf78b993 100644 --- a/lambda-cform/lambda-cform-logretention/pom.xml +++ b/lambda-cform/lambda-cform-logretention/pom.xml @@ -112,7 +112,7 @@ pl.wrzasq.commons commons-aws - 1.0.21 + 1.0.24 diff --git a/lambda-cform/lambda-cform-organization-unit/pom.xml b/lambda-cform/lambda-cform-organization-unit/pom.xml index 8b1d22ccf..e2b0e6e01 100644 --- a/lambda-cform/lambda-cform-organization-unit/pom.xml +++ b/lambda-cform/lambda-cform-organization-unit/pom.xml @@ -112,7 +112,7 @@ pl.wrzasq.commons commons-aws - 1.0.21 + 1.0.24 diff --git a/lambda-cform/lambda-cform-organization/pom.xml b/lambda-cform/lambda-cform-organization/pom.xml index 7d7e84b13..0755d6fd6 100644 --- a/lambda-cform/lambda-cform-organization/pom.xml +++ b/lambda-cform/lambda-cform-organization/pom.xml @@ -118,7 +118,7 @@ pl.wrzasq.commons commons-aws - 1.0.21 + 1.0.24 diff --git a/lambda-cform/lambda-cform-passwordpolicy/pom.xml b/lambda-cform/lambda-cform-passwordpolicy/pom.xml index 47e554f15..97a87a89d 100644 --- a/lambda-cform/lambda-cform-passwordpolicy/pom.xml +++ b/lambda-cform/lambda-cform-passwordpolicy/pom.xml @@ -112,7 +112,7 @@ pl.wrzasq.commons commons-aws - 1.0.21 + 1.0.24 diff --git a/lambda-cform/lambda-cform-stackset-instance/pom.xml b/lambda-cform/lambda-cform-stackset-instance/pom.xml index 0f7ff3d98..b81355e6c 100644 --- a/lambda-cform/lambda-cform-stackset-instance/pom.xml +++ b/lambda-cform/lambda-cform-stackset-instance/pom.xml @@ -118,7 +118,7 @@ pl.wrzasq.commons commons-aws - 1.0.21 + 1.0.24 diff --git a/lambda-cform/lambda-cform-stackset/pom.xml b/lambda-cform/lambda-cform-stackset/pom.xml index 87f79d211..fc16e08cb 100644 --- a/lambda-cform/lambda-cform-stackset/pom.xml +++ b/lambda-cform/lambda-cform-stackset/pom.xml @@ -112,7 +112,7 @@ pl.wrzasq.commons commons-aws - 1.0.21 + 1.0.24 diff --git a/lambda-edgedeploy/pom.xml b/lambda-edgedeploy/pom.xml index 37a75955a..7a24c602e 100644 --- a/lambda-edgedeploy/pom.xml +++ b/lambda-edgedeploy/pom.xml @@ -99,13 +99,13 @@ com.fasterxml.jackson.core jackson-core - 2.9.9 + 2.10.0 com.fasterxml.jackson.core jackson-databind - 2.9.9.3 + 2.10.0 @@ -135,7 +135,7 @@ pl.wrzasq.commons commons-aws - 1.0.21 + 1.0.24 diff --git a/lambda-json/pom.xml b/lambda-json/pom.xml index 1aa84f9f2..3245adb4e 100644 --- a/lambda-json/pom.xml +++ b/lambda-json/pom.xml @@ -34,13 +34,13 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9.3 + 2.10.0 com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.9.9 + 2.10.0 diff --git a/lambda-macro/lambda-macro-lambda-function/pom.xml b/lambda-macro/lambda-macro-lambda-function/pom.xml new file mode 100644 index 000000000..5ae73607a --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/pom.xml @@ -0,0 +1,110 @@ + + + + 4.0.0 + + + lambda-macro-lambda-function + jar + + pl.wrzasq.lambda + lambda-macro + 1.0.36-SNAPSHOT + ../ + + + + WrzasqPl CloudFormation Lambda function macro + https://rafalwrzeszcz-wrzasqpl.github.io/pl.wrzasq.lambda/lambda-macro/lambda-macro-lambda-function/ + Macro for generating enhanced Lambda function setup. + 2019 + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + com.amazonaws:aws-xray-recorder-sdk-aws-sdk + com.amazonaws:aws-xray-recorder-sdk-aws-sdk-instrumentor + io.symphonia:lambda-logging + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + + + + + + ${project.groupId} + lambda-json + ${project.version} + + + + com.amazonaws + aws-lambda-java-core + 1.2.0 + + + + com.amazonaws + aws-xray-recorder-sdk-aws-sdk + 2.2.1 + + + + com.amazonaws + aws-xray-recorder-sdk-aws-sdk-instrumentor + 2.2.1 + + + + com.fasterxml.jackson.core + jackson-annotations + 2.10.0 + + + + com.fasterxml.jackson.core + jackson-core + 2.10.0 + + + + com.fasterxml.jackson.core + jackson-databind + 2.10.0 + + + + io.symphonia + lambda-logging + 1.0.3 + + + + org.slf4j + slf4j-api + 1.7.26 + + + diff --git a/lambda-macro/lambda-macro-lambda-function/src/main/checkstyle/java.header b/lambda-macro/lambda-macro-lambda-function/src/main/checkstyle/java.header new file mode 100644 index 000000000..84497f2f3 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/main/checkstyle/java.header @@ -0,0 +1,8 @@ +^/\*$ +^ \* This file is part of the pl\.wrzasq\.lambda\.$ +^ \*$ +^ \* @license http://mit-license\.org/ The MIT license$ +^ \* @copyright \d{4}[0-9, -]* © by Rafał Wrzeszcz - Wrzasq\.pl\.$ +^ \*/$ + +^package pl\.wrzasq\.lambda\.macro\.lambda\.function(\..+)?;$ diff --git a/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/Handler.java b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/Handler.java new file mode 100644 index 000000000..94fb90e0b --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/Handler.java @@ -0,0 +1,48 @@ +/* + * This file is part of the pl.wrzasq.lambda. + * + * @license http://mit-license.org/ The MIT license + * @copyright 2019 © by Rafał Wrzeszcz - Wrzasq.pl. + */ + +package pl.wrzasq.lambda.macro.lambda.function; + +import java.util.Map; +import java.util.function.Function; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import pl.wrzasq.lambda.macro.lambda.function.model.CloudFormationMacroRequest; +import pl.wrzasq.lambda.macro.lambda.function.model.CloudFormationMacroResponse; +import pl.wrzasq.lambda.macro.lambda.function.template.ProcessedTemplate; + +/** + * CloudFormation macro handler. + * + *

Recommended memory: 256MB.

+ */ +public class Handler implements RequestHandler { + /** + * CloudFormation template provider. + */ + private Function, ProcessedTemplate> templateFactory; + + /** + * Default constructor. + */ + public Handler() { + this.templateFactory = ProcessedTemplate::new; + } + + /** + * {@inheritDoc} + */ + @Override + public CloudFormationMacroResponse handleRequest(CloudFormationMacroRequest event, Context context) { + return new CloudFormationMacroResponse( + event.getRequestId(), + CloudFormationMacroResponse.STATUS_SUCCESS, + this.templateFactory.apply(event.getFragment()).getTemplate() + ); + } +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/model/CloudFormationMacroRequest.java b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/model/CloudFormationMacroRequest.java new file mode 100644 index 000000000..7e4d89fe2 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/model/CloudFormationMacroRequest.java @@ -0,0 +1,30 @@ +/* + * This file is part of the pl.wrzasq.lambda. + * + * @license http://mit-license.org/ The MIT license + * @copyright 2019 © by Rafał Wrzeszcz - Wrzasq.pl. + */ + +package pl.wrzasq.lambda.macro.lambda.function.model; + +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * CloudFormation event request structure. + */ +@AllArgsConstructor +@Data +public class CloudFormationMacroRequest { + /** + * Request ID. + */ + private String requestId; + + /** + * Template fragment. + */ + private Map fragment; +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/model/CloudFormationMacroResponse.java b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/model/CloudFormationMacroResponse.java new file mode 100644 index 000000000..efe5a298d --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/model/CloudFormationMacroResponse.java @@ -0,0 +1,40 @@ +/* + * This file is part of the pl.wrzasq.lambda. + * + * @license http://mit-license.org/ The MIT license + * @copyright 2019 © by Rafał Wrzeszcz - Wrzasq.pl. + */ + +package pl.wrzasq.lambda.macro.lambda.function.model; + +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * CloudFormation event response structure. + */ +@AllArgsConstructor +@Data +public class CloudFormationMacroResponse { + /** + * OK status. + */ + public static final String STATUS_SUCCESS = "SUCCESS"; + + /** + * Request ID. + */ + private String requestId; + + /** + * Operation status. + */ + private String status; + + /** + * Template fragment. + */ + private Map fragment; +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/LambdaFunctionResource.java b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/LambdaFunctionResource.java new file mode 100644 index 000000000..494ac750d --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/LambdaFunctionResource.java @@ -0,0 +1,409 @@ +/* + * This file is part of the pl.wrzasq.lambda. + * + * @license http://mit-license.org/ The MIT license + * @copyright 2019 © by Rafał Wrzeszcz - Wrzasq.pl. + */ + +package pl.wrzasq.lambda.macro.lambda.function.template; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import lombok.AllArgsConstructor; + +/** + * Model for handled resource. + */ +@AllArgsConstructor +public class LambdaFunctionResource { + /** + * Resource type identifier. + */ + public static final String RESOURCE_TYPE = "WrzasqPl::Lambda::Function"; + + /** + * Metrics namespace. + */ + private static final String METRICS_NAMESPACE = "WrzasqPl/Lambda"; + + /** + * Default retention period for logs (in days). + */ + private static final Number DEFAULT_LOGS_RETENTION_DAYS = 7; + + /** + * Default alarm check period (in seconds). + */ + private static final Number DEFAULT_ALERT_PERIOD = 300; + + /** + * Value used for counting metrics (each metric record is single occurange). + */ + private static final String COUNTER_METRIC_VALUE = "1"; + + /** + * LogGroupName property name. + */ + private static final String PROPERTY_LOG_GROUP_NAME = "LogGroupName"; + + /** + * LogsRetentionInDays property name. + */ + private static final String PROPERTY_LOGS_RETENTION_IN_DAYS = "LogsRetentionInDays"; + + /** + * Namespace property name. + */ + private static final String PROPERTY_NAMESPACE = "Namespace"; + + /** + * MetricName property name. + */ + private static final String PROPERTY_METRIC_NAME = "MetricName"; + + /** + * Statistic property name. + */ + private static final String PROPERTY_STATISTIC = "Statistic"; + + /** + * ComparisonOperator property name. + */ + private static final String PROPERTY_COMPARISON_OPERATOR = "ComparisonOperator"; + + /** + * Threshold property name. + */ + private static final String PROPERTY_THRESHOLD = "Threshold"; + + /** + * EvaluationPeriods property name. + */ + private static final String PROPERTY_EVALUATION_PERIODS = "EvaluationPeriods"; + + /** + * Period property name. + */ + private static final String PROPERTY_PERIOD = "Period"; + + /** + * TreatMissingData property name. + */ + private static final String PROPERTY_TREAT_MISSING_DATA = "TreatMissingData"; + + /** + * MetricValue property name. + */ + private static final String PROPERTY_METRIC_VALUE = "MetricValue"; + + /** + * MetricNamespace property name. + */ + private static final String PROPERTY_METRIC_NAMESPACE = "MetricNamespace"; + + /** + * MetricTransformations property name. + */ + private static final String PROPERTY_METRIC_TRANSFORMATIONS = "MetricTransformations"; + + /** + * FilterPattern property name. + */ + private static final String PROPERTY_FILTER_PATTERN = "FilterPattern"; + + /** + * Resource name. + */ + private String logicalId; + + /** + * Builds definition of physical resources. + * + * @param properties Properties for our custom resource. + * @return Definitions of all resources. + */ + public Map buildDefinitions(Map properties) { + Map resources = new HashMap<>(); + + // generate all sub-resources + this.createLogGroup(resources, properties); + this.createMemoryMetricFilter(resources); + this.createErrorsMetricFilter(resources, properties); + this.createErrorsAlarm(resources, properties); + this.createWarningsMetricFilter(resources, properties); + + // leave all properties as function properties + this.createLambdaFunction(resources, properties); + + return resources; + } + + /** + * Creates log group resource definition. + * + * @param resources Resources container. + * @param properties Resource properties. + */ + private void createLogGroup(Map resources, Map properties) { + // default setup + Map resourceProperties = new HashMap<>(); + resourceProperties.put( + LambdaFunctionResource.PROPERTY_LOG_GROUP_NAME, + LambdaFunctionResource.sub(String.format("/aws/lambda/${%s}", this.logicalId)) + ); + + LambdaFunctionResource.popProperty( + properties, + LambdaFunctionResource.PROPERTY_LOGS_RETENTION_IN_DAYS, + (Object value) -> resourceProperties.put("RetentionInDays", value), + LambdaFunctionResource.DEFAULT_LOGS_RETENTION_DAYS + ); + + this.generateResource( + resources, + "LogGroup", + "Logs::LogGroup", + resourceProperties + ); + } + + /** + * Creates memory metric filter resource definition. + * + * @param resources Resources container. + */ + private void createMemoryMetricFilter(Map resources) { + this.createMetricFilter( + resources, + "MemoryMetricFilter", + "Memory", + "$max_memory_used", + "[label=\"REPORT\", " + + "..., " + + "memory_label=\"Used:\", " + + "max_memory_used, unit=\"MB\", " + + "xray_label=\"XRAY\", " + + "trace_label=\"TraceId:\", " + + "traced, " + + "segment_label=\"SegmentId:\", " + + "segment]" + ); + } + + /** + * Creates errors metric filter resource definition. + * + * @param resources Resources container. + * @param properties Resource properties. + */ + private void createErrorsMetricFilter(Map resources, Map properties) { + LambdaFunctionResource.popProperty( + properties, + "ErrorsFilterPattern", + (Object filter) -> this.createMetricFilter( + resources, + "ErrorsMetricFilter", + "Errors", + LambdaFunctionResource.COUNTER_METRIC_VALUE, + filter + ), + "ERROR -LOG_ERROR" + ); + } + + /** + * Creates errors alarm resource definition. + * + * @param resources Resources container. + * @param properties Resource properties. + */ + private void createErrorsAlarm(Map resources, Map properties) { + Map resourceProperties = new HashMap<>(); + resourceProperties.put(LambdaFunctionResource.PROPERTY_NAMESPACE, LambdaFunctionResource.METRICS_NAMESPACE); + resourceProperties.put( + LambdaFunctionResource.PROPERTY_METRIC_NAME, + LambdaFunctionResource.sub(String.format("${%s}-Errors", this.logicalId)) + ); + resourceProperties.put(LambdaFunctionResource.PROPERTY_STATISTIC, "Sum"); + resourceProperties.put(LambdaFunctionResource.PROPERTY_COMPARISON_OPERATOR, "GreaterThanThreshold"); + resourceProperties.put(LambdaFunctionResource.PROPERTY_THRESHOLD, 0); + resourceProperties.put(LambdaFunctionResource.PROPERTY_EVALUATION_PERIODS, 1); + resourceProperties.put(LambdaFunctionResource.PROPERTY_PERIOD, LambdaFunctionResource.DEFAULT_ALERT_PERIOD); + resourceProperties.put(LambdaFunctionResource.PROPERTY_TREAT_MISSING_DATA, "notBreaching"); + + LambdaFunctionResource.popProperty( + properties, + "ErrorsAlarmActions", + (Object value) -> resourceProperties.put("AlarmActions", value), + null + ); + + this.generateResource( + resources, + "ErrorsAlarm", + "CloudWatch::Alarm", + resourceProperties + ); + } + + /** + * Creates warnings metric filter resource definition. + * + * @param resources Resources container. + * @param properties Resource properties. + */ + private void createWarningsMetricFilter(Map resources, Map properties) { + LambdaFunctionResource.popProperty( + properties, + "WarningsFilterPattern", + (Object filter) -> this.createMetricFilter( + resources, + "WarningsMetricFilter", + "Warnings", + LambdaFunctionResource.COUNTER_METRIC_VALUE, + filter + ), + "WARN" + ); + } + + /** + * Generic method for metric filter creation. + * + * @param resources Resources container. + * @param resourceName Resource logical ID. + * @param metricNameSuffix Metric name postfix. + * @param metricValue Metric value. + * @param filterPattern Metric log filter pattern. + */ + private void createMetricFilter( + Map resources, + String resourceName, + String metricNameSuffix, + String metricValue, + Object filterPattern + ) { + Map transformation = new HashMap<>(); + transformation.put(LambdaFunctionResource.PROPERTY_METRIC_NAMESPACE, LambdaFunctionResource.METRICS_NAMESPACE); + transformation.put( + LambdaFunctionResource.PROPERTY_METRIC_NAME, + LambdaFunctionResource.sub(String.format("${%s}-%s", this.logicalId, metricNameSuffix)) + ); + transformation.put(LambdaFunctionResource.PROPERTY_METRIC_VALUE, metricValue); + + Map resourceProperties = new HashMap<>(); + resourceProperties.put( + LambdaFunctionResource.PROPERTY_LOG_GROUP_NAME, + LambdaFunctionResource.ref(this.getLogGroupLogicalId()) + ); + resourceProperties.put( + LambdaFunctionResource.PROPERTY_METRIC_TRANSFORMATIONS, + Collections.singletonList(transformation) + ); + // note that this is not a string - it may be defined as a CloudFormation function call! + resourceProperties.put(LambdaFunctionResource.PROPERTY_FILTER_PATTERN, filterPattern); + + this.generateResource( + resources, + resourceName, + "Logs::MetricFilter", + resourceProperties + ); + } + + /** + * Creates function resource definition. + * + * @param resources Resources container. + * @param properties Resource properties. + */ + private void createLambdaFunction(Map resources, Map properties) { + this.generateResource( + resources, + "", + "Lambda::Function", + properties + ); + } + + /** + * Builds logical ID of LogGroup resource. + * + * @return LogGroup logical ID. + */ + public String getLogGroupLogicalId() { + return String.format("%sLogGroup", this.logicalId); + } + + /** + * Adds resource to collection. + * + * @param resources Resources container. + * @param suffix Resource logical ID suffix. + * @param type CloudFormation resource type. + * @param properties Resource configuration. + */ + private void generateResource( + Map resources, + String suffix, + String type, + Map properties + ) { + Map resource = new HashMap<>(); + resource.put("Type", String.format("AWS::%s", type)); + + if (!properties.isEmpty()) { + resource.put("Properties", properties); + } + + resources.put( + String.format("%s%s", this.logicalId, suffix), + resource + ); + } + + /** + * Handles optional property by removing it from generic properties pool. + * + * @param properties Main properties container. + * @param key Property key. + * @param then Property action. + * @param defaultValue Default property value. + */ + private static void popProperty( + Map properties, + String key, + Consumer then, + Object defaultValue + ) { + if (properties.containsKey(key)) { + then.accept(properties.get(key)); + properties.remove(key); + } else if (defaultValue != null) { + then.accept(defaultValue); + } + } + + /** + * Returns !Ref reference call. + * + * @param reference Referred object ID. + * @return !Ref call. + */ + private static Object ref(String reference) { + return Collections.singletonMap("Ref", reference); + } + + /** + * Returns !Sub reference call. + * + * @param params Call parameters. + * @return !Sub call. + */ + private static Object sub(Object params) { + return Collections.singletonMap("Fn::Sub", params); + } +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/ProcessedTemplate.java b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/ProcessedTemplate.java new file mode 100644 index 000000000..2d0267578 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/ProcessedTemplate.java @@ -0,0 +1,204 @@ +/* + * This file is part of the pl.wrzasq.lambda. + * + * @license http://mit-license.org/ The MIT license + * @copyright 2019 © by Rafał Wrzeszcz - Wrzasq.pl. + */ + +package pl.wrzasq.lambda.macro.lambda.function.template; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import pl.wrzasq.lambda.json.ObjectMapperFactory; + +/** + * Contains template structure with handled resources references. + */ +public class ProcessedTemplate { + /** + * Resources section key. + */ + private static final String SECTION_RESOURCES = "Resources"; + + /** + * DependsOn clause key. + */ + private static final String CLAUSE_DEPENDS_ON = "DependsOn"; + + /** + * Values converter. + */ + private static ObjectMapper objectMapper = ObjectMapperFactory.createObjectMapper(); + + /** + * Logger. + */ + private static Logger logger = LoggerFactory.getLogger(ProcessedTemplate.class); + + /** + * Mapping of handled resources. + */ + private Map resources = new HashMap<>(); + + /** + * Template structure. + */ + @Getter + private Map template; + + /** + * Template initializer. + * + * @param input Initial template structure. + */ + public ProcessedTemplate(Map input) { + this.template = this.replaceReferences( + this.createResources(input) + ); + } + + /** + * Finds our custom resources and creates a replacements. + * + * @param input Initial template structure. + * @return Template state after processing. + */ + private Map createResources(Map input) { + Map output = new HashMap<>(input); + + // re-create resources section + Map section = ProcessedTemplate.asMap(input.get(ProcessedTemplate.SECTION_RESOURCES)); + Map resources = new HashMap<>(); + for (Map.Entry entry : section.entrySet()) { + ResourcesDefinition definition = ProcessedTemplate.objectMapper.convertValue( + entry.getValue(), + ResourcesDefinition.class + ); + if (definition.getType().equals(LambdaFunctionResource.RESOURCE_TYPE)) { + resources.putAll( + this.createResource(entry.getKey(), definition.getProperties()) + ); + } else { + resources.put(entry.getKey(), section.get(entry.getKey())); + } + } + + output.put(ProcessedTemplate.SECTION_RESOURCES, resources); + + return output; + } + + /** + * Creates our custom resource in place of virtual one. + * + * @param key Resource logical ID. + * @param properties Resource initial properties. + * @return Physical resources definitions. + */ + private Map createResource(String key, Map properties) { + ProcessedTemplate.logger.info("Creating resources for {}.", key); + + LambdaFunctionResource resource = new LambdaFunctionResource(key); + this.resources.put(key, resource); + return resource.buildDefinitions(properties); + } + + /** + * Replaces references to virtual resources. + * + * @param input Input template structure. + * @return Template state after processing. + */ + private Map replaceReferences(Map input) { + Map output = new HashMap<>(input); + + Map section = ProcessedTemplate.asMap(input.get(ProcessedTemplate.SECTION_RESOURCES)); + section = this.replaceDependencies(section); + + output.put(ProcessedTemplate.SECTION_RESOURCES, this.replaceDependencies(section)); + + return output; + } + + /** + * Handles DependsOn clauses. + * + * @param resources Resources section. + * @return New clause value. + */ + private Map replaceDependencies(Map resources) { + Map output = new HashMap<>(); + + for (Map.Entry resource : resources.entrySet()) { + String logicalId = resource.getKey(); + Map config = ProcessedTemplate.asMap(resource.getValue()); + + if (config.containsKey(ProcessedTemplate.CLAUSE_DEPENDS_ON)) { + Object dependsOn = config.get(ProcessedTemplate.CLAUSE_DEPENDS_ON); + config.put(ProcessedTemplate.CLAUSE_DEPENDS_ON, this.replaceDependenciesIn(dependsOn)); + } + + output.put(logicalId, config); + } + + return output; + } + + /** + * Handles DependsOn clause of single resource. + * + * @param dependsOn Depends on clause. + * @return Computed new DependsOn clause. + */ + private Object replaceDependenciesIn(Object dependsOn) { + if (dependsOn instanceof List) { + return ((List) dependsOn).stream() + .map(Object::toString) + .map(this::resolveDependency) + .collect(Collectors.toList()); + } else { + return this.resolveDependency(dependsOn.toString()); + } + } + + /** + * Tries to resolve dependencies against lambda functions. + * + *

+ * Effective dependency will be LogGroup, to ensure no Lambda execution happens before log group creation. + *

+ * + * @param dependency Source dependency ID. + * @return Resolved dependency ID. + */ + private String resolveDependency(String dependency) { + return this.resources.containsKey(dependency) + ? this.resources.get(dependency).getLogGroupLogicalId() + : dependency; + } + + /** + * Safely converts value to a typed map. + * + * @param value Input object. + * @return Typed map. + */ + private static Map asMap(Object value) { + Map output = new HashMap<>(); + + if (value instanceof Map) { + for (Map.Entry entry : ((Map) value).entrySet()) { + output.put(entry.getKey().toString(), entry.getValue()); + } + } + + return output; + } +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/ResourcesDefinition.java b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/ResourcesDefinition.java new file mode 100644 index 000000000..ab0e59af2 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/main/java/pl/wrzasq/lambda/macro/lambda/function/template/ResourcesDefinition.java @@ -0,0 +1,31 @@ +/* + * This file is part of the pl.wrzasq.lambda. + * + * @license http://mit-license.org/ The MIT license + * @copyright 2019 © by Rafał Wrzeszcz - Wrzasq.pl. + */ + +package pl.wrzasq.lambda.macro.lambda.function.template; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * Generic structure of resources section. + */ +@Data +public class ResourcesDefinition { + /** + * Resource type. + */ + @JsonProperty("Type") + private String type; + + /** + * Resource properties. + */ + @JsonProperty("Properties") + private Map properties; +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/main/resources/logback.xml b/lambda-macro/lambda-macro-lambda-function/src/main/resources/logback.xml new file mode 100644 index 000000000..27f1ea8bc --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n%ex{full} + + + + + + + + + + + + + + + + + diff --git a/lambda-macro/lambda-macro-lambda-function/src/site/markdown/guide/usage.md b/lambda-macro/lambda-macro-lambda-function/src/site/markdown/guide/usage.md new file mode 100644 index 000000000..0123e81bc --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/site/markdown/guide/usage.md @@ -0,0 +1,128 @@ + + +# Using in CloudFormation + +This macro handler automates setup of additional Lambda function resources: + +- managed log group with customizable retention period; +- memory consumption metric; +- warnings logs filter metric; +- errors logs filter metric; +- errors metric alert. + +# Required permissions + +`lambda-macro-lambda-function` Needs no specific permissions, you may want to add following policies to it's role: + +- `arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole` (if you want to see **CloudWatch** logs of +resource handler execution); +- `arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess` (if you want more detailed tracing, package is built with +**X-Ray** instrumentor). + +# Definition + +Type: `WrzasqPl::Lambda::Function`. + +## Properties + +All of the properties are transparently forwarder to +[AWS::Lambda::Function](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) +resource. Additionally following properties are available to customize extra resources: + +## `LogsRetentionInDays` - number + +Number of days for log retention (by default `7`). + +## `ErrorsFilterPattern` - string + +Log filter to detect error events (by default `ERROR -LOG_ERROR`). + +## `WarningsFilterPattern` - string + +Log filter to detect warning events (by default `WARN`). + +## `ErrorsAlarmActions` - string[] + +List of target ARNs that will receive notification when errors alarm is turned on. + +## Output values + +Any reference to `Ref` or `GetAtt` on this virtual resource will be replace by analogical call to physical +`AWS::Lambda::Function` creation in place of it. + +**Note:** When you add this virtual resource to any `DependsOn` claus it will be replaced by log group dependency, to +ensure any execution of created Lambda function will occur after creation of it's managed log group. + +# Example + +```yaml +Transform: + - "WrzasqPlLambdaFunction" + +Resources: + EnhancedLambda: + Type: "WrzasqPl::Lambda::Function" + Properties: + Runtime: "java8" + Code: + # put your source bucket + S3Bucket: "your-bucket" + S3Key: "lambda-cform-account-1.0.4-standalone.jar" + Handler: "pl.wrzasq.lambda.cform.account.Handler::handle" + MemorySize: 256 + Description: "AWS Account manager deployment." + Timeout: 300 + TracingConfig: + Mode: "Active" + Role: !GetAtt "AccountManagerRole.Arn" + LogsRetentionInDays: 10 + ErrorsFilterPattern: "error" + WarningsFilterPattern: "warning" + ErrorsAlarmActions: + - !Ref AlarmsQueue +``` + +# Installation + +```yaml + MacroFunctionRole: + Type: "AWS::IAM::Role" + Properties: + AssumeRolePolicyDocument: + Statement: + - + Action: "sts:AssumeRole" + Effect: "Allow" + Principal: + Service: + - "lambda.amazonaws.com" + ManagedPolicyArns: + - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + + MacroFunction: + Type: "AWS::Serverless::Function" + Properties: + Runtime: "java8" + Code: + # put your source bucket + S3Bucket: "your-bucket" + S3Key: "lambda-macro-lambda-function-1.0.36-standalone.jar" + Handler: "pl.wrzasq.lambda.macro.lambda.function.Handler::handle" + MemorySize: 256 + Description: "Custom CloudFormation macro handler." + Timeout: 300 + TracingConfig: + Mode: "Active" + Role: !GetAtt "MacroFunctionRole.Arn" + + Macro: + Type: "AWS::CloudFormation::Macro" + Properties: + Name: "WrzasqPlLambdaFunction" + FunctionName: !GetAtt "MacroFunction.Arn" +``` diff --git a/lambda-macro/lambda-macro-lambda-function/src/site/site.xml b/lambda-macro/lambda-macro-lambda-function/src/site/site.xml new file mode 100644 index 000000000..6d5cf9464 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/HandlerTest.java b/lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/HandlerTest.java new file mode 100644 index 000000000..5c6426cc5 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/HandlerTest.java @@ -0,0 +1,76 @@ +/* + * This file is part of the pl.wrzasq.lambda. + * + * @license http://mit-license.org/ The MIT license + * @copyright 2019 © by Rafał Wrzeszcz - Wrzasq.pl. + */ + +package test.pl.wrzasq.lambda.macro.lambda.function; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import pl.wrzasq.lambda.macro.lambda.function.Handler; +import pl.wrzasq.lambda.macro.lambda.function.model.CloudFormationMacroRequest; +import pl.wrzasq.lambda.macro.lambda.function.model.CloudFormationMacroResponse; +import pl.wrzasq.lambda.macro.lambda.function.template.ProcessedTemplate; + +@ExtendWith(MockitoExtension.class) +public class HandlerTest { + @Mock + private ProcessedTemplate template; + + @Mock + private Function, ProcessedTemplate> templateFactory; + + @Test + public void handle() throws NoSuchFieldException, IllegalAccessException { + // for code coverage + Handler handler = new Handler(); + Field hack = Handler.class.getDeclaredField("templateFactory"); + hack.setAccessible(true); + hack.set(handler, templateFactory); + + Map source = new HashMap<>(); + Map result = new HashMap<>(); + + Mockito + .when(this.templateFactory.apply(source)) + .thenReturn(template); + + Mockito + .when(this.template.getTemplate()) + .thenReturn(result); + + String requestId = "abc"; + + CloudFormationMacroResponse output = handler.handleRequest( + new CloudFormationMacroRequest(requestId, source), + null + ); + + Assertions.assertEquals( + requestId, + output.getRequestId(), + "Handler.handleRequest() should return response for same request ID." + ); + Assertions.assertEquals( + CloudFormationMacroResponse.STATUS_SUCCESS, + output.getStatus(), + "Handler.handleRequest() should set successful status of response." + ); + Assertions.assertSame( + result, + output.getFragment(), + "Handler.handleRequest() should use processor to modify template structure." + ); + } +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/template/LambdaFunctionResourceTest.java b/lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/template/LambdaFunctionResourceTest.java new file mode 100644 index 000000000..650208de6 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/template/LambdaFunctionResourceTest.java @@ -0,0 +1,30 @@ +/* + * This file is part of the pl.wrzasq.lambda. + * + * @license http://mit-license.org/ The MIT license + * @copyright 2019 © by Rafał Wrzeszcz - Wrzasq.pl. + */ + +package test.pl.wrzasq.lambda.macro.lambda.function.template; + +import java.util.Collections; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import pl.wrzasq.lambda.macro.lambda.function.template.LambdaFunctionResource; + +public class LambdaFunctionResourceTest { + @Test + public void getLogGroupLogicalId() { + String logicalId = "TestFunction"; + LambdaFunctionResource resource = new LambdaFunctionResource(logicalId); + // this is just for code coverage now, we can make more detailed tests in future based on error-cases + // it will be tested in ProcessedTemplateTest that involves full JSON files + resource.buildDefinitions(Collections.emptyMap()); + + Assertions.assertTrue( + resource.getLogGroupLogicalId().startsWith(logicalId), + "LambdaFunctionResource.getLogGroupLogicalId() should return ID of created log group." + ); + } +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/template/ProcessedTemplateTest.java b/lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/template/ProcessedTemplateTest.java new file mode 100644 index 000000000..cf9ced265 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/test/java/test/pl/wrzasq/lambda/macro/lambda/function/template/ProcessedTemplateTest.java @@ -0,0 +1,39 @@ +/* + * This file is part of the pl.wrzasq.lambda. + * + * @license http://mit-license.org/ The MIT license + * @copyright 2019 © by Rafał Wrzeszcz - Wrzasq.pl. + */ + +package test.pl.wrzasq.lambda.macro.lambda.function.template; + +import java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import pl.wrzasq.lambda.macro.lambda.function.template.ProcessedTemplate; + +public class ProcessedTemplateTest { + private ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void processTemplate() throws IOException { + Map input = this.objectMapper.readValue( + this.getClass().getResourceAsStream("/input.json"), + new TypeReference>() {} + ); + Map output = this.objectMapper.readValue( + this.getClass().getResourceAsStream("/output.json"), + new TypeReference>() {} + ); + + Assertions.assertEquals( + output, + new ProcessedTemplate(input).getTemplate(), + "ProcessedTemplate should handle WrzasqPl::Lambda::Function resources." + ); + } +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/test/resources/input.json b/lambda-macro/lambda-macro-lambda-function/src/test/resources/input.json new file mode 100644 index 000000000..28d9f1501 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/test/resources/input.json @@ -0,0 +1,114 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Template description", + "Parameters": { + "ServiceName": { + "Type": "String", + "Default": "pl.wrzasq.lambda" + } + }, + "Resources": { + "SimpleResourceWithoutDependsOn": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "Test" + } + }, + "SimpleResourceWithDependsOnSimple": { + "Type": "AWS::S3::BucketPolicy", + "DependsOn": "SimpleResourceWithoutDependsOn", + "Properties": { + "PolicyDocument": {}, + "Bucket": { + "Ref": "SimpleResourceWithoutDependsOn" + } + } + }, + "EnhancedDefaultLambda": { + "Type": "WrzasqPl::Lambda::Function", + "Properties": { + "Runtime": "java8", + "Code": { + "S3Bucket": "RepositoryBucket", + "S3Key": "lambda-standalone.jar" + }, + "Handler": "pl.wrzasq.lambda.macro.lambda.function.Handler::handleRequest", + "MemorySize": 256, + "Description": "Test lambda.", + "Timeout": 300, + "TracingConfig": { + "Mode": "Active" + }, + "Role": "arn:aws:iam::role/OrganizationUnitManagerRole" + } + }, + "EnhancedConfiguredLambda": { + "Type": "WrzasqPl::Lambda::Function", + "Properties": { + "Runtime": "nodejs10", + "Code": { + "S3Bucket": "RepositoryBucket", + "S3Key": "lambda-standalone.zip" + }, + "Handler": "index.handler", + "MemorySize": 256, + "Timeout": 300, + "Role": "arn:aws:iam::role/OrganizationUnitManagerRole", + "ErrorsFilterPattern": "error", + "ErrorsAlarmActions": [ + { + "Ref": "SimpleResourceWithDependsOn" + } + ], + "WarningsFilterPattern": "warning", + "LogsRetentionInDays": 10 + } + }, + "SimpleResourceWithSingleDependsOn": { + "Type": "AWS::SNS::Topic", + "DependsOn": "EnhancedDefaultLambda", + "Properties": { + "TopicName": "First", + "Subscription": [ + { + "Endpoint": { + "Fn::GetAtt": [ + "EnhancedConfiguredLambda", + "Arn" + ] + }, + "Protocol": "Lambda" + } + ] + } + }, + "SimpleResourceWithDependsOn": { + "Type": "AWS::SNS::Topic", + "DependsOn": [ + "SimpleResourceWithDependsOnSimple", + "EnhancedConfiguredLambda" + ], + "Properties": { + "TopicName": "Second" + } + } + }, + "Outputs": { + "BucketName": { + "Value": { + "Ref": "SimpleResourceWithoutDependsOn" + } + }, + "FunctionArn": { + "Value": { + "Fn::GetAtt": [ + "EnhancedConfiguredLambda", + "Arn" + ] + }, + "Export": { + "Name": "EndpointFunctionArn" + } + } + } +} diff --git a/lambda-macro/lambda-macro-lambda-function/src/test/resources/output.json b/lambda-macro/lambda-macro-lambda-function/src/test/resources/output.json new file mode 100644 index 000000000..8846d4a11 --- /dev/null +++ b/lambda-macro/lambda-macro-lambda-function/src/test/resources/output.json @@ -0,0 +1,267 @@ +{ + "AWSTemplateFormatVersion" : "2010-09-09", + "Description": "Template description", + "Parameters": { + "ServiceName": { + "Type": "String", + "Default": "pl.wrzasq.lambda" + } + }, + "Resources": { + "SimpleResourceWithoutDependsOn": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "Test" + } + }, + "SimpleResourceWithDependsOnSimple": { + "Type": "AWS::S3::BucketPolicy", + "DependsOn": "SimpleResourceWithoutDependsOn", + "Properties": { + "PolicyDocument": {}, + "Bucket": { + "Ref": "SimpleResourceWithoutDependsOn" + } + } + }, + "EnhancedDefaultLambda": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "java8", + "Code": { + "S3Bucket": "RepositoryBucket", + "S3Key": "lambda-standalone.jar" + }, + "Handler": "pl.wrzasq.lambda.macro.lambda.function.Handler::handleRequest", + "MemorySize": 256, + "Description": "Test lambda.", + "Timeout": 300, + "TracingConfig": { + "Mode": "Active" + }, + "Role": "arn:aws:iam::role/OrganizationUnitManagerRole" + } + }, + "EnhancedDefaultLambdaLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${EnhancedDefaultLambda}" + }, + "RetentionInDays": 7 + } + }, + "EnhancedDefaultLambdaMemoryMetricFilter": { + "Type": "AWS::Logs::MetricFilter", + "Properties": { + "LogGroupName": { + "Ref": "EnhancedDefaultLambdaLogGroup" + }, + "FilterPattern": "[label=\"REPORT\", ..., memory_label=\"Used:\", max_memory_used, unit=\"MB\", xray_label=\"XRAY\", trace_label=\"TraceId:\", traced, segment_label=\"SegmentId:\", segment]", + "MetricTransformations": [ + { + "MetricValue": "$max_memory_used", + "MetricNamespace": "WrzasqPl/Lambda", + "MetricName": { + "Fn::Sub": "${EnhancedDefaultLambda}-Memory" + } + } + ] + } + }, + "EnhancedDefaultLambdaErrorsMetricFilter": { + "Type": "AWS::Logs::MetricFilter", + "Properties": { + "LogGroupName": { + "Ref": "EnhancedDefaultLambdaLogGroup" + }, + "FilterPattern": "ERROR -LOG_ERROR", + "MetricTransformations": [ + { + "MetricValue": "1", + "MetricNamespace": "WrzasqPl/Lambda", + "MetricName": { + "Fn::Sub": "${EnhancedDefaultLambda}-Errors" + } + } + ] + } + }, + "EnhancedDefaultLambdaErrorsAlarm": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "Namespace": "WrzasqPl/Lambda", + "MetricName": { + "Fn::Sub": "${EnhancedDefaultLambda}-Errors" + }, + "Statistic": "Sum", + "ComparisonOperator": "GreaterThanThreshold", + "Threshold": 0, + "EvaluationPeriods": 1, + "Period": 300, + "TreatMissingData": "notBreaching" + } + }, + "EnhancedDefaultLambdaWarningsMetricFilter": { + "Type": "AWS::Logs::MetricFilter", + "Properties": { + "LogGroupName": { + "Ref": "EnhancedDefaultLambdaLogGroup" + }, + "FilterPattern": "WARN", + "MetricTransformations": [ + { + "MetricValue": "1", + "MetricNamespace": "WrzasqPl/Lambda", + "MetricName": { + "Fn::Sub": "${EnhancedDefaultLambda}-Warnings" + } + } + ] + } + }, + "EnhancedConfiguredLambda": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs10", + "Code": { + "S3Bucket": "RepositoryBucket", + "S3Key": "lambda-standalone.zip" + }, + "Handler": "index.handler", + "MemorySize": 256, + "Timeout": 300, + "Role": "arn:aws:iam::role/OrganizationUnitManagerRole" + } + }, + "EnhancedConfiguredLambdaLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${EnhancedConfiguredLambda}" + }, + "RetentionInDays": 10 + } + }, + "EnhancedConfiguredLambdaMemoryMetricFilter": { + "Type": "AWS::Logs::MetricFilter", + "Properties": { + "LogGroupName": { + "Ref": "EnhancedConfiguredLambdaLogGroup" + }, + "FilterPattern": "[label=\"REPORT\", ..., memory_label=\"Used:\", max_memory_used, unit=\"MB\", xray_label=\"XRAY\", trace_label=\"TraceId:\", traced, segment_label=\"SegmentId:\", segment]", + "MetricTransformations": [ + { + "MetricValue": "$max_memory_used", + "MetricNamespace": "WrzasqPl/Lambda", + "MetricName": { + "Fn::Sub": "${EnhancedConfiguredLambda}-Memory" + } + } + ] + } + }, + "EnhancedConfiguredLambdaErrorsMetricFilter": { + "Type": "AWS::Logs::MetricFilter", + "Properties": { + "LogGroupName": { + "Ref": "EnhancedConfiguredLambdaLogGroup" + }, + "FilterPattern": "error", + "MetricTransformations": [ + { + "MetricValue": "1", + "MetricNamespace": "WrzasqPl/Lambda", + "MetricName": { + "Fn::Sub": "${EnhancedConfiguredLambda}-Errors" + } + } + ] + } + }, + "EnhancedConfiguredLambdaErrorsAlarm": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "Namespace": "WrzasqPl/Lambda", + "MetricName": { + "Fn::Sub": "${EnhancedConfiguredLambda}-Errors" + }, + "Statistic": "Sum", + "ComparisonOperator": "GreaterThanThreshold", + "Threshold": 0, + "EvaluationPeriods": 1, + "Period": 300, + "AlarmActions": [ + { + "Ref": "SimpleResourceWithDependsOn" + } + ], + "TreatMissingData": "notBreaching" + } + }, + "EnhancedConfiguredLambdaWarningsMetricFilter": { + "Type": "AWS::Logs::MetricFilter", + "Properties": { + "LogGroupName": { + "Ref": "EnhancedConfiguredLambdaLogGroup" + }, + "FilterPattern": "warning", + "MetricTransformations": [ + { + "MetricValue": "1", + "MetricNamespace": "WrzasqPl/Lambda", + "MetricName": { + "Fn::Sub": "${EnhancedConfiguredLambda}-Warnings" + } + } + ] + } + }, + "SimpleResourceWithSingleDependsOn": { + "Type": "AWS::SNS::Topic", + "DependsOn": "EnhancedDefaultLambdaLogGroup", + "Properties": { + "TopicName": "First", + "Subscription": [ + { + "Endpoint": { + "Fn::GetAtt": [ + "EnhancedConfiguredLambda", + "Arn" + ] + }, + "Protocol": "Lambda" + } + ] + } + }, + "SimpleResourceWithDependsOn": { + "Type": "AWS::SNS::Topic", + "DependsOn": [ + "SimpleResourceWithDependsOnSimple", + "EnhancedConfiguredLambdaLogGroup" + ], + "Properties": { + "TopicName": "Second" + } + } + }, + "Outputs": { + "BucketName": { + "Value": { + "Ref": "SimpleResourceWithoutDependsOn" + } + }, + "FunctionArn": { + "Value": { + "Fn::GetAtt": [ + "EnhancedConfiguredLambda", + "Arn" + ] + }, + "Export": { + "Name": "EndpointFunctionArn" + } + } + } +} diff --git a/lambda-macro/pom.xml b/lambda-macro/pom.xml new file mode 100644 index 000000000..acaa25b0a --- /dev/null +++ b/lambda-macro/pom.xml @@ -0,0 +1,36 @@ + + + + 4.0.0 + + + lambda-macro + pom + + pl.wrzasq.lambda + lambda + 1.0.36-SNAPSHOT + ../ + + + + WrzasqPl Lambda - CloudFormation Macros + https://rafalwrzeszcz-wrzasqpl.github.io/pl.wrzasq.lambda/lambda-macro/ + Custom CloudFormation Macros. + 2019 + + + + lambda-macro-lambda-function + + diff --git a/lambda-macro/src/main/checkstyle/java.header b/lambda-macro/src/main/checkstyle/java.header new file mode 100644 index 000000000..6ec4129bd --- /dev/null +++ b/lambda-macro/src/main/checkstyle/java.header @@ -0,0 +1,8 @@ +^/\*$ +^ \* This file is part of the pl\.wrzasq\..lambda\.$ +^ \*$ +^ \* @license http://mit-license\.org/ The MIT license$ +^ \* @copyright \d{4}[0-9, -]* © by Rafał Wrzeszcz - Wrzasq\.pl\.$ +^ \*/$ + +^package pl\.wrzasq\.lambda\.macro(\..+)?;$ diff --git a/lambda-macro/src/site/site.xml b/lambda-macro/src/site/site.xml new file mode 100644 index 000000000..d46a4c1da --- /dev/null +++ b/lambda-macro/src/site/site.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/lambda-metrics/lambda-metrics-dynamodb/pom.xml b/lambda-metrics/lambda-metrics-dynamodb/pom.xml index 2259be4bd..03753406b 100644 --- a/lambda-metrics/lambda-metrics-dynamodb/pom.xml +++ b/lambda-metrics/lambda-metrics-dynamodb/pom.xml @@ -87,7 +87,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9.1 + 2.10.0 diff --git a/lambda-metrics/lambda-metrics-dynamodb/src/main/java/pl/wrzasq/lambda/metrics/dynamodb/Handler.java b/lambda-metrics/lambda-metrics-dynamodb/src/main/java/pl/wrzasq/lambda/metrics/dynamodb/Handler.java index e64a86040..0e6c18fc1 100644 --- a/lambda-metrics/lambda-metrics-dynamodb/src/main/java/pl/wrzasq/lambda/metrics/dynamodb/Handler.java +++ b/lambda-metrics/lambda-metrics-dynamodb/src/main/java/pl/wrzasq/lambda/metrics/dynamodb/Handler.java @@ -49,7 +49,7 @@ public class Handler { private CloudWatchDynamoDbMetricGenerator metricGenerator; /** - * Default constructure. + * Default constructor. */ public Handler() { this( diff --git a/pom.xml b/pom.xml index fe5fc1ae0..c38ae53c2 100644 --- a/pom.xml +++ b/pom.xml @@ -123,6 +123,7 @@ lambda-dynamodb lambda-edgedeploy lambda-json + lambda-macro lambda-metrics