-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")) | ||
} |
82 changes: 82 additions & 0 deletions
82
sample-apps/apigateway-lambda/src/main/java/com/amazon/sampleapp/LambdaHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters