Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions sample-apps/apigateway-lambda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
## API Gateway + Lambda Sample Application

The directory contains the source code and the Infrastructure as Code (IaC) to create the sample app in your AWS account.

### Prerequisite
Before you begin, ensure you have the following installed:
- Java 17
- Gradle
- Terraform
- AWS CLI configured with appropriate credentials

### Getting Started

#### 1. Build the application
```bash
# Change to the project directory
cd sample-apps/apigateway-lambda

# Build the application using Gradle
gradle clean build

# Prepare the Lambda deployment package
gradle createLambdaZip
```

#### 2. Deploy the application
```bash
# Change to the terraform directory
cd terraform

# Initialize Terraform
terraform init

# (Optional) Review the deployment plan for better understanding of the components
terraform plan

# Deploy
terraform apply
```

#### 3. Testing the applicating
After successful deployment, Terraform will output the API Gateway endpoint URL. You can test the application using:
```bash
curl <API_Gateway_URL>
```

#### 4. Clean Up
To avoid incurring unnecessary charges, remember to destroy the resources when you are done:
```bash
terraform destroy
```

#### (Optional) Instrumenting with Application Signals Lambda Layer
You can choose to instrument the Lambda function with Application Signals Lambda Layer upon deployment by passing in the layer ARN to the `adot_layer_arn` variable.
You must have the layer already published to your account before executing the following command.
```bash
terraform apply -var "adot_layer_arn=<APPLICATION_SIGNALS_LAYER_FULL_ARN_WITH_VERSION>"
```
40 changes: 40 additions & 0 deletions sample-apps/apigateway-lambda/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
plugins {
java
application
}

application {
mainClass.set("com.amazon.sampleapp.LambdaHandler")
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}

dependencies {
implementation("com.amazonaws:aws-lambda-java-core:1.2.2")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("software.amazon.awssdk:s3:2.29.23")
implementation("org.json:json:20240303")
implementation("org.slf4j:jcl-over-slf4j:2.0.16")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}

tasks.jar {
manifest {
attributes["Main-Class"] = "com.amazon.sampleapp.LambdaHandler"
}
}

tasks.register<Zip>("createLambdaZip") {
dependsOn("build")
from(tasks.compileJava.get())
from(tasks.processResources.get())
into("lib") {
from(configurations.runtimeClasspath.get())
}
archiveFileName.set("lambda-function.zip")
destinationDirectory.set(layout.buildDirectory.dir("distributions"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.amazon.sampleapp;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.io.IOException;
import java.util.Map;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONObject;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;

public class LambdaHandler implements RequestHandler<Object, Map<String, Object>> {

private final OkHttpClient client = new OkHttpClient();
private final S3Client s3Client = S3Client.create();

@Override
public Map<String, Object> handleRequest(Object input, Context context) {
System.out.println("Executing LambdaHandler");

// https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
// try and get the trace id from environment variable _X_AMZN_TRACE_ID. If it's not present
// there
// then try the system property.
String traceId =
System.getenv("_X_AMZN_TRACE_ID") != null
? System.getenv("_X_AMZN_TRACE_ID")
: System.getProperty("com.amazonaws.xray.traceHeader");

System.out.println("Trace ID: " + traceId);

JSONObject responseBody = new JSONObject();
responseBody.put("traceId", traceId);

// Make a remote call using OkHttp
System.out.println("Making a remote call using OkHttp");
String url = "https://www.amazon.com";
Request request = new Request.Builder().url(url).build();

try (Response response = client.newCall(request).execute()) {
responseBody.put("httpRequest", "Request successful");
} catch (IOException e) {
context.getLogger().log("Error: " + e.getMessage());
responseBody.put("httpRequest", "Request failed");
}
System.out.println("Remote call done");

// Make a S3 HeadBucket call to check whether the bucket exists
System.out.println("Making a S3 HeadBucket call");
String bucketName = "SomeDummyBucket";
try {
HeadBucketRequest headBucketRequest = HeadBucketRequest.builder().bucket(bucketName).build();
s3Client.headBucket(headBucketRequest);
responseBody.put("s3Request", "Bucket exists and is accessible: " + bucketName);
} catch (S3Exception e) {
if (e.statusCode() == 403) {
responseBody.put("s3Request", "Access denied to bucket: " + bucketName);
} else if (e.statusCode() == 404) {
responseBody.put("s3Request", "Bucket does not exist: " + bucketName);
} else {
System.err.println("Error checking bucket: " + e.awsErrorDetails().errorMessage());
responseBody.put(
"s3Request", "Error checking bucket: " + e.awsErrorDetails().errorMessage());
}
}
System.out.println("S3 HeadBucket call done");

// return a response in the ApiGateway proxy format
return Map.of(
"isBase64Encoded",
false,
"statusCode",
200,
"body",
responseBody.toString(),
"headers",
Map.of("Content-Type", "application/json"));
}
}
119 changes: 119 additions & 0 deletions sample-apps/apigateway-lambda/terraform/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
### Lambda function
locals {
architecture = var.architecture == "x86_64" ? "amd64" : "arm64"
}

resource "aws_iam_role" "lambda_role" {
name = "lambda_execution_role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}

resource "aws_iam_policy" "s3_access" {
name = "S3ListBucketPolicy"
description = "Allow Lambda to check a given S3 bucket exists"
policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Action = ["s3:ListBucket"],
Resource = "*"
}]
})
}

resource "aws_iam_role_policy_attachment" "attach_execution_role_policy" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_iam_role_policy_attachment" "attach_s3_policy" {
role = aws_iam_role.lambda_role.name
policy_arn = aws_iam_policy.s3_access.arn
}

resource "aws_iam_role_policy_attachment" "attach_xray_policy" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
}

resource "aws_lambda_function" "sampleLambdaFunction" {
function_name = var.function_name
runtime = var.runtime
timeout = 300
handler = "com.amazon.sampleapp.LambdaHandler::handleRequest"
role = aws_iam_role.lambda_role.arn
filename = "${path.module}/../build/distributions/lambda-function.zip"
source_code_hash = filebase64sha256("${path.module}/../build/distributions/lambda-function.zip")
architectures = [var.architecture]
memory_size = 512
tracing_config {
mode = var.lambda_tracing_mode
}
layers = var.adot_layer_arn != null && var.adot_layer_arn != "" ? [var.adot_layer_arn] : []
environment {
variables = var.adot_layer_arn != null && var.adot_layer_arn != "" ? {
AWS_LAMBDA_EXEC_WRAPPER = "/opt/otel-instrument"
} : {}
}
}

### API Gateway proxy to Lambda function
resource "aws_api_gateway_rest_api" "apigw_lambda_api" {
name = var.api_gateway_name
}

resource "aws_api_gateway_resource" "apigw_lambda_resource" {
rest_api_id = aws_api_gateway_rest_api.apigw_lambda_api.id
parent_id = aws_api_gateway_rest_api.apigw_lambda_api.root_resource_id
path_part = "lambda"
}

resource "aws_api_gateway_method" "apigw_lambda_method" {
rest_api_id = aws_api_gateway_rest_api.apigw_lambda_api.id
resource_id = aws_api_gateway_resource.apigw_lambda_resource.id
http_method = "ANY"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "apigw_lambda_integration" {
rest_api_id = aws_api_gateway_rest_api.apigw_lambda_api.id
resource_id = aws_api_gateway_resource.apigw_lambda_resource.id
http_method = aws_api_gateway_method.apigw_lambda_method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.sampleLambdaFunction.invoke_arn
}

resource "aws_api_gateway_deployment" "apigw_lambda_deployment" {
depends_on = [
aws_api_gateway_integration.apigw_lambda_integration
]
rest_api_id = aws_api_gateway_rest_api.apigw_lambda_api.id
}

resource "aws_api_gateway_stage" "test" {
stage_name = "default"
rest_api_id = aws_api_gateway_rest_api.apigw_lambda_api.id
deployment_id = aws_api_gateway_deployment.apigw_lambda_deployment.id
xray_tracing_enabled = var.apigw_tracing_enabled
}

resource "aws_lambda_permission" "apigw_lambda_invoke" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.sampleLambdaFunction.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.apigw_lambda_api.execution_arn}/*/*"
}

# Output the API Gateway URL
output "invoke_url" {
value = "${aws_api_gateway_stage.test.invoke_url}/lambda"
}
43 changes: 43 additions & 0 deletions sample-apps/apigateway-lambda/terraform/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## Lambda function related configurations
variable "function_name" {
type = string
description = "Name of sample app function"
default = "aws-opentelemetry-distro-java"
}

variable "architecture" {
type = string
description = "Lambda function architecture, either arm64 or x86_64"
default = "x86_64"
}

variable "runtime" {
type = string
description = "Java runtime version used for Lambda Function"
default = "java17"
}

variable "lambda_tracing_mode" {
type = string
description = "Lambda function tracing mode"
default = "Active"
}

variable "adot_layer_arn" {
type = string
description = "ARN of the ADOT JAVA layer"
default = null
}

## API Gateway related configurations
variable "api_gateway_name" {
type = string
description = "Name of API gateway to create"
default = "aws-opentelemetry-distro-java"
}

variable "apigw_tracing_enabled" {
type = string
description = "API Gateway REST API tracing enabled or not"
default = "true"
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ include(":smoke-tests:spring-boot")
include(":sample-apps:springboot")
include(":sample-apps:spark")
include(":sample-apps:spark-awssdkv1")
include(":sample-apps:apigateway-lambda")

// Used for contract tests
include("appsignals-tests:images:mock-collector")
Expand Down
Loading