diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml
index 73ae67553..79945f237 100644
--- a/.github/workflows/pr_build.yml
+++ b/.github/workflows/pr_build.yml
@@ -53,6 +53,9 @@ jobs:
env:
JAVA: ${{ matrix.java }}
AWS_REGION: eu-west-1
+ permissions:
+ id-token: write # needed to interact with GitHub's OIDC Token endpoint.
+ contents: read
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Setup java
@@ -68,6 +71,32 @@ jobs:
run: |
cd examples/powertools-examples-core/gradle
./gradlew build
+ - name: Setup Terraform
+ if: ${{ matrix.java == '11' }}
+ uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 #v2.0.3
+ - name: Setup AWS credentials
+ uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_ARN_TO_ASSUME }}
+ aws-region: ${{ env.AWS_REGION }}
+ - name: Terraform validate
+ if: ${{ matrix.java == '11' }}
+ run: |
+ terraform -version
+ cd examples/powertools-examples-core/terraform
+ terraform init -backend=false
+ terraform validate
+ terraform plan
+ - name: Setup Terraform lint
+ if: ${{ matrix.java == '11' }}
+ uses: terraform-linters/setup-tflint@a5a1af8c6551fb10c53f1cd4ba62359f1973746f # v3.1.1
+ - name: Terraform lint
+ if: ${{ matrix.java == '11' }}
+ run: |
+ tflint --version
+ cd examples/powertools-examples-core/terraform
+ tflint --init
+ tflint -f compact
- name: Upload coverage to Codecov
uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
if: ${{ matrix.java == '11' }} # publish results once
diff --git a/.gitignore b/.gitignore
index b404d2cb2..6615ac729 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,4 +108,6 @@ example/HelloWorldFunction/build
/example/.gradle/
/example/.java-version
.gradle
-build/
\ No newline at end of file
+build/
+.terraform*
+terraform.tfstate*
\ No newline at end of file
diff --git a/examples/pom.xml b/examples/pom.xml
index 810ec1b36..900f095f8 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -33,6 +33,7 @@
powertools-examples-core/cdk/app
powertools-examples-core/cdk/infra
powertools-examples-core/serverless
+ powertools-examples-core/terraform
powertools-examples-idempotency
powertools-examples-parameters
powertools-examples-serialization
diff --git a/examples/powertools-examples-core/README.md b/examples/powertools-examples-core/README.md
index 1d1dd031f..a0ffb125d 100644
--- a/examples/powertools-examples-core/README.md
+++ b/examples/powertools-examples-core/README.md
@@ -10,6 +10,7 @@ We provide examples for the following infrastructure-as-code tools:
* [AWS SAM](sam/)
* [AWS CDK](cdk/)
* [Serverless framework](serverless/)
+* [Terraform](terraform/)
We also provide an example showing the integration of SAM, Powertools, and Gradle:
diff --git a/examples/powertools-examples-core/terraform/.tflint.hcl b/examples/powertools-examples-core/terraform/.tflint.hcl
new file mode 100644
index 000000000..18e69352b
--- /dev/null
+++ b/examples/powertools-examples-core/terraform/.tflint.hcl
@@ -0,0 +1,3 @@
+rule "terraform_required_version" {
+ enabled = false
+}
\ No newline at end of file
diff --git a/examples/powertools-examples-core/terraform/README.md b/examples/powertools-examples-core/terraform/README.md
new file mode 100644
index 000000000..71c78d437
--- /dev/null
+++ b/examples/powertools-examples-core/terraform/README.md
@@ -0,0 +1,27 @@
+# Powertools for AWS Lambda (Java) - Core Utilities Example with Terraform
+
+This project demonstrates the Lambda for Powertools Java module deployed using [Terraform](https://www.terraform.io/).
+For general information on the deployed example itself, you can refer to the parent [README](../README.md).
+To install Terraform if you don't have it yet, you can follow the [Install Terraform Guide](https://developer.hashicorp.com/terraform/downloads?product_intent=terraform).
+
+## Configuration
+Terraform uses [main.tf](./main.tf) to define the application's AWS resources.
+This file defines the Lambda function to be deployed as well as API Gateway for it.
+
+It is a [Maven](https://maven.apache.org/) based project, so you can open this project with any Maven compatible Java IDE to build and run tests.
+
+
+## Deploy the sample application
+
+To deploy the app, simply run the following commands:
+```bash
+terraform init
+mvn package && terraform apply
+```
+
+## Useful commands
+
+To destroy the app
+```bash
+terraform destroy
+```
diff --git a/examples/powertools-examples-core/terraform/infra/api-gateway.tf b/examples/powertools-examples-core/terraform/infra/api-gateway.tf
new file mode 100644
index 000000000..dba1c3616
--- /dev/null
+++ b/examples/powertools-examples-core/terraform/infra/api-gateway.tf
@@ -0,0 +1,94 @@
+resource "aws_api_gateway_rest_api" "hello_world_api" {
+ name = "hello_world_api"
+ description = "API Gateway endpoint URL for Prod stage for Hello World function"
+}
+
+resource "aws_api_gateway_resource" "hello_resource" {
+ rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}"
+ parent_id = "${aws_api_gateway_rest_api.hello_world_api.root_resource_id}"
+ path_part = "hello"
+}
+
+resource "aws_api_gateway_resource" "hello_stream_resource" {
+ rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}"
+ parent_id = "${aws_api_gateway_rest_api.hello_world_api.root_resource_id}"
+ path_part = "hellostream"
+}
+
+resource "aws_api_gateway_method" "hello_get_method" {
+ rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}"
+ resource_id = "${aws_api_gateway_resource.hello_resource.id}"
+ http_method = "GET"
+ authorization = "NONE"
+}
+
+resource "aws_api_gateway_method" "hello_stream_get_method" {
+ rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}"
+ resource_id = "${aws_api_gateway_resource.hello_stream_resource.id}"
+ http_method = "GET"
+ authorization = "NONE"
+}
+
+resource "aws_api_gateway_integration" "java_lambda_integration" {
+ rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}"
+ resource_id = "${aws_api_gateway_resource.hello_resource.id}"
+ http_method = "${aws_api_gateway_method.hello_get_method.http_method}"
+
+ integration_http_method = "POST"
+ type = "AWS_PROXY"
+ uri = "${aws_lambda_function.hello_world_lambda.invoke_arn}"
+}
+
+resource "aws_api_gateway_integration" "java_stream_lambda_integration" {
+ rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}"
+ resource_id = "${aws_api_gateway_resource.hello_stream_resource.id}"
+ http_method = "${aws_api_gateway_method.hello_stream_get_method.http_method}"
+
+ integration_http_method = "POST"
+ type = "AWS_PROXY"
+ uri = "${aws_lambda_function.hello_world_stream_lambda.invoke_arn}"
+}
+
+resource "aws_api_gateway_deployment" "prod_deployment" {
+ depends_on = [aws_api_gateway_integration.java_lambda_integration, aws_api_gateway_integration.java_stream_lambda_integration]
+ rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}"
+ stage_name = "prod"
+}
+
+# Allows API gateway to invoke lambda
+resource "aws_lambda_permission" "hello_world_lambda_invoke" {
+ statement_id = "AllowAPIGatewayInvoke"
+ action = "lambda:InvokeFunction"
+ function_name = "${aws_lambda_function.hello_world_lambda.function_name}"
+ principal = "apigateway.amazonaws.com"
+ source_arn = "${aws_api_gateway_rest_api.hello_world_api.execution_arn}/${aws_api_gateway_deployment.prod_deployment.stage_name}/GET/hello"
+}
+
+# Allows API gateway to invoke lambda
+resource "aws_lambda_permission" "hello_world_lambda_testinvoke" {
+ statement_id = "AllowAPIGatewayTestInvoke"
+ action = "lambda:InvokeFunction"
+ function_name = "${aws_lambda_function.hello_world_lambda.function_name}"
+ principal = "apigateway.amazonaws.com"
+ source_arn = "${aws_api_gateway_rest_api.hello_world_api.execution_arn}/test-invoke-stage/GET/hello"
+}
+
+# Allows API gateway to invoke lambda
+resource "aws_lambda_permission" "hello_world_stream_lambda_invoke" {
+ statement_id = "AllowAPIGatewayInvoke"
+ action = "lambda:InvokeFunction"
+ function_name = "${aws_lambda_function.hello_world_stream_lambda.function_name}"
+ principal = "apigateway.amazonaws.com"
+ source_arn = "${aws_api_gateway_rest_api.hello_world_api.execution_arn}/${aws_api_gateway_deployment.prod_deployment.stage_name}/GET/hellostream"
+}
+
+# Allows API gateway to invoke lambda
+resource "aws_lambda_permission" "hello_world_stream_lambda_testinvoke" {
+ statement_id = "AllowAPIGatewayTestInvoke"
+ action = "lambda:InvokeFunction"
+ function_name = "${aws_lambda_function.hello_world_stream_lambda.function_name}"
+ principal = "apigateway.amazonaws.com"
+ source_arn = "${aws_api_gateway_rest_api.hello_world_api.execution_arn}/test-invoke-stage/GET/hellostream"
+}
+
+output "invoke" {value=aws_api_gateway_deployment.prod_deployment.invoke_url}
\ No newline at end of file
diff --git a/examples/powertools-examples-core/terraform/infra/lambda.tf b/examples/powertools-examples-core/terraform/infra/lambda.tf
new file mode 100644
index 000000000..abebccf54
--- /dev/null
+++ b/examples/powertools-examples-core/terraform/infra/lambda.tf
@@ -0,0 +1,95 @@
+resource "aws_lambda_function" "hello_world_lambda" {
+ runtime = "java11"
+ filename = "target/helloworld-lambda.jar"
+ source_code_hash = filebase64sha256("target/helloworld-lambda.jar")
+ function_name = "hello_world_lambda"
+
+ handler = "helloworld.App"
+ description = "Powertools example, deployed by Terraform"
+ timeout = 20
+ memory_size = 512
+ role = "${aws_iam_role.iam_role_for_lambda.arn}"
+ tracing_config {
+ mode = "Active"
+ }
+ depends_on = [aws_cloudwatch_log_group.log_group]
+}
+
+resource "aws_lambda_function" "hello_world_stream_lambda" {
+ runtime = "java11"
+ filename = "target/helloworld-lambda.jar"
+ source_code_hash = filebase64sha256("target/helloworld-lambda.jar")
+ function_name = "hello_world_stream_lambda"
+
+ handler = "helloworld.AppStream"
+ description = "Powertools example, deployed by Terraform"
+ timeout = 20
+ memory_size = 512
+ role = "${aws_iam_role.iam_role_for_lambda.arn}"
+ tracing_config {
+ mode = "Active"
+ }
+ depends_on = [aws_cloudwatch_log_group.log_group]
+}
+
+# Create a log group for the lambda
+resource "aws_cloudwatch_log_group" "log_group" {
+ name = "/aws/lambda/hello_world_lambda"
+}
+
+# Create a log group for the lambda
+resource "aws_cloudwatch_log_group" "log_group_stream" {
+ name = "/aws/lambda/hello_world_stream_lambda"
+}
+
+# lambda role
+resource "aws_iam_role" "iam_role_for_lambda" {
+ name = "lambda-invoke-role"
+ assume_role_policy = <
+ 4.0.0
+
+ software.amazon.lambda.examples
+ 1.18.0-SNAPSHOT
+ powertools-examples-core-terraform
+ jar
+
+ Powertools for AWS Lambda (Java) library Examples - Core
+
+
+ 2.20.0
+ 1.8
+ 1.8
+
+
+
+
+ software.amazon.lambda
+ powertools-tracing
+ ${project.version}
+
+
+ software.amazon.lambda
+ powertools-logging
+ ${project.version}
+
+
+ software.amazon.lambda
+ powertools-metrics
+ ${project.version}
+
+
+ com.amazonaws
+ aws-lambda-java-core
+ 1.2.2
+
+
+ com.amazonaws
+ aws-lambda-java-events
+ 3.11.2
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-api
+ ${log4j.version}
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+
+ helloworld-lambda
+
+
+ dev.aspectj
+ aspectj-maven-plugin
+ 1.13.1
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+ ${maven.compiler.target}
+
+
+ software.amazon.lambda
+ powertools-tracing
+
+
+ software.amazon.lambda
+ powertools-logging
+
+
+ software.amazon.lambda
+ powertools-metrics
+
+
+
+
+
+
+ compile
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.5.0
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
+
+
+ org.apache.logging.log4j
+ log4j-transform-maven-shade-plugin-extensions
+ 0.1.0
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.1.2
+
+
+ handler
+
+
+
+
+
+
+
+
+ jdk8
+
+ (,11)
+
+
+ 1.9.7
+
+
+
+
+ org.aspectj
+ aspectjtools
+ ${aspectj.version}
+
+
+
+
+
+
+
+ dev.aspectj
+ aspectj-maven-plugin
+ ${aspectj.plugin.version}
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+ ${maven.compiler.target}
+
+
+ software.amazon.lambda
+ powertools-tracing
+
+
+ software.amazon.lambda
+ powertools-logging
+
+
+ software.amazon.lambda
+ powertools-metrics
+
+
+
+
+
+
+ compile
+ test-compile
+
+
+
+
+
+
+ org.aspectj
+ aspectjtools
+ ${aspectj.version}
+
+
+
+
+
+
+
+
+
diff --git a/examples/powertools-examples-core/terraform/src/main/java/helloworld/App.java b/examples/powertools-examples-core/terraform/src/main/java/helloworld/App.java
new file mode 100644
index 000000000..dacd7f1d4
--- /dev/null
+++ b/examples/powertools-examples-core/terraform/src/main/java/helloworld/App.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package helloworld;
+
+import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger;
+import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric;
+import static software.amazon.lambda.powertools.tracing.TracingUtils.putMetadata;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
+import software.amazon.cloudwatchlogs.emf.model.Unit;
+import software.amazon.lambda.powertools.logging.Logging;
+import software.amazon.lambda.powertools.logging.LoggingUtils;
+import software.amazon.lambda.powertools.metrics.Metrics;
+import software.amazon.lambda.powertools.tracing.CaptureMode;
+import software.amazon.lambda.powertools.tracing.Tracing;
+import software.amazon.lambda.powertools.tracing.TracingUtils;
+
+/**
+ * Handler for requests to Lambda function.
+ */
+public class App implements RequestHandler {
+ private final static Logger log = LogManager.getLogger(App.class);
+
+ // This is controlled by POWERTOOLS_LOGGER_SAMPLE_RATE environment variable
+ // @Logging(logEvent = true, samplingRate = 0.7)
+ // This is controlled by POWERTOOLS_METRICS_NAMESPACE environment variable
+ // @Metrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true)
+ // This is controlled by POWERTOOLS_TRACER_CAPTURE_ERROR environment variable
+ @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR)
+ public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
+ Map headers = new HashMap<>();
+
+ headers.put("Content-Type", "application/json");
+ headers.put("X-Custom-Header", "application/json");
+
+ metricsLogger().putMetric("CustomMetric1", 1, Unit.COUNT);
+
+ withSingleMetric("CustomMetrics2", 1, Unit.COUNT, "Another", (metric) ->
+ {
+ metric.setDimensions(DimensionSet.of("AnotherService", "CustomService"));
+ metric.setDimensions(DimensionSet.of("AnotherService1", "CustomService1"));
+ });
+
+ LoggingUtils.appendKey("test", "willBeLogged");
+
+ APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
+ .withHeaders(headers);
+ try {
+ final String pageContents = this.getPageContents("https://checkip.amazonaws.com");
+ log.info(pageContents);
+ TracingUtils.putAnnotation("Test", "New");
+ String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents);
+
+ TracingUtils.withSubsegment("loggingResponse", subsegment ->
+ {
+ String sampled = "log something out";
+ log.info(sampled);
+ log.info(output);
+ });
+
+ log.info("After output");
+ return response
+ .withStatusCode(200)
+ .withBody(output);
+ } catch (RuntimeException | IOException e) {
+ return response
+ .withBody("{}")
+ .withStatusCode(500);
+ }
+ }
+
+ @Tracing(namespace = "getPageContents", captureMode = CaptureMode.DISABLED)
+ private String getPageContents(String address) throws IOException {
+ URL url = new URL(address);
+ putMetadata("getPageContents", address);
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
+ return br.lines().collect(Collectors.joining(System.lineSeparator()));
+ }
+ }
+}
diff --git a/examples/powertools-examples-core/terraform/src/main/java/helloworld/AppStream.java b/examples/powertools-examples-core/terraform/src/main/java/helloworld/AppStream.java
new file mode 100644
index 000000000..401ef8c48
--- /dev/null
+++ b/examples/powertools-examples-core/terraform/src/main/java/helloworld/AppStream.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package helloworld;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+import software.amazon.lambda.powertools.logging.Logging;
+import software.amazon.lambda.powertools.metrics.Metrics;
+
+public class AppStream implements RequestStreamHandler {
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ @Override
+ @Logging(logEvent = true)
+ @Metrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true)
+ public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
+ Map map = mapper.readValue(input, Map.class);
+
+ System.out.println(map.size());
+ }
+}
diff --git a/examples/powertools-examples-core/terraform/src/main/resources/log4j2.xml b/examples/powertools-examples-core/terraform/src/main/resources/log4j2.xml
new file mode 100644
index 000000000..0cc0953f0
--- /dev/null
+++ b/examples/powertools-examples-core/terraform/src/main/resources/log4j2.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/powertools-examples-core/terraform/src/test/java/helloworld/AppTest.java b/examples/powertools-examples-core/terraform/src/test/java/helloworld/AppTest.java
new file mode 100644
index 000000000..0ca4f264d
--- /dev/null
+++ b/examples/powertools-examples-core/terraform/src/test/java/helloworld/AppTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package helloworld;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
+import org.junit.Test;
+
+public class AppTest {
+
+ @Test
+ public void successfulResponse() {
+ App app = new App();
+ APIGatewayProxyResponseEvent result = app.handleRequest(null, null);
+ assertEquals(result.getStatusCode().intValue(), 200);
+ assertEquals(result.getHeaders().get("Content-Type"), "application/json");
+ String content = result.getBody();
+ assertNotNull(content);
+ assertTrue(content.contains("\"message\""));
+ assertTrue(content.contains("\"hello world\""));
+ assertTrue(content.contains("\"location\""));
+ }
+}