Skip to content
Open
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
17 changes: 8 additions & 9 deletions docker/.env
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
# Activate LocalStack Pro: https://docs.localstack.cloud/getting-started/auth-token/
LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN:-} # required for Pro, not processed via template due to security reasons
LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY:-}
# LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN:-} # required for Pro, not processed via template due to security reasons
# LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY:-}
# LocalStack configuration: https://docs.localstack.cloud/references/configuration/
FLOCI_SERVICES_LAMBDA_HOT_RELOAD_ENABLED=true
ACTIVATE_PRO=false
DEBUG=false
LS_LOG=info
PERSISTENCE=false
AWS_ENDPOINT_URL=http://localhost.localstack.cloud:4566
LOCALSTACK_HOST=localhost.localstack.cloud:4566
# AWS_ENDPOINT_URL=http://localhost:4566
# FLOCI_HOSTNAME=floci
AUTO_LOAD_POD=
ENFORCE_IAM=false
AWS_REGION=eu-west-2
AWS_DEFAULT_REGION=eu-west-2
# LocalStack community edition is sufficient
IMAGE_NAME=localstack/localstack:latest
# AWS_REGION=eu-west-2
# FLOCI_DEFAULT_REGION=eu-west-2
LAMBDA_TIMEOUT=9999999
# Comment the line below and create a new containerised development environment to disable debugging of Lambda functions.
# IMPORTANT - If cloning the remote repository into a container volume, the change must be pushed to a pull request
# branch from which a new containerised development environment is created. If a new containerised development environment
# is not created, running multiple Lambda functions without remote debug limitations will not be possible.
LAMBDA_DOCKER_FLAGS="-e NODE_OPTIONS=--inspect-brk=0.0.0.0:9229 -p 9229:9229"
# LAMBDA_DOCKER_FLAGS="-e NODE_OPTIONS=--inspect-brk=0.0.0.0:9229 -p 9229:9229"
# Let Docker select a matching image based on the host operating system and architecture.
LAMBDA_IGNORE_ARCHITECTURE=1

Expand Down
167 changes: 167 additions & 0 deletions docker/floci-issue-20260730.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Title
[BUG] apigateway --type AWS putIntegration endpoint invocation fails

## Service

API Gateway

## AWS API Action

PutIntegration / execute-api (invoke)

## Expected behavior

When an API Gateway REST API method is configured with `--type AWS` and a Lambda integration URI (`arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{fnArn}/invocations`), invoking the endpoint should execute the Lambda function with the VTL-rendered request template as the payload and return the response after applying any configured VTL response template mappings.

This is the standard non-proxy Lambda integration pattern - it provides full request/response VTL mapping, unlike `AWS_PROXY` which bypasses it.

## Actual behaviour

Invoking the endpoint returns a 500:

`{"message": "The request must contain the parameter Action"}`

Floci incorrectly treats the Lambda path-style URI (`lambda:path/...`) as a query-protocol (form-urlencoded) integration. It attempts to parse the VTL-rendered Lambda payload as AWS query protocol and dispatch it through invokeQuery, which fails because the body contains no `Action` parameter.

`AWS_PROXY` integrations against the same Lambda function work correctly.

## Reproduction

```
#!/usr/bin/env bash
set -euo pipefail

export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_PAGER=""
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=eu-west-2

# =============================================================================
# Lambda function code - update the handler body below to change the response
# =============================================================================
LAMBDA_CODE=$(cat <<'LAMBDA'
exports.handler = async (event) => {
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "Hello from my-function!", event }),
};
};
LAMBDA
)

# Package and create the Lambda function
LAMBDA_DIR=$(mktemp -d)
echo "$LAMBDA_CODE" > "$LAMBDA_DIR/index.js"
(cd "$LAMBDA_DIR" && zip -q function.zip index.js)

aws lambda create-function \
--function-name my-function \
--runtime nodejs20.x \
--handler index.handler \
--role arn:aws:iam::000000000000:role/lambda-role \
--zip-file "fileb://$LAMBDA_DIR/function.zip" \
--endpoint-url "$AWS_ENDPOINT_URL"

rm -rf "$LAMBDA_DIR"
echo "Lambda function 'my-function' created"

# =============================================================================
# API Gateway setup
# =============================================================================

# Create a REST API
API_ID=$(aws apigateway create-rest-api \
--name "My API" \
--query id --output text \
--endpoint-url "$AWS_ENDPOINT_URL")
echo "API_ID: $API_ID"

# Get the root resource
ROOT_ID=$(aws apigateway get-resources \
--rest-api-id "$API_ID" \
--query 'items[?path==`/`].id' --output text \
--endpoint-url "$AWS_ENDPOINT_URL")
echo "ROOT_ID: $ROOT_ID"

# Create a resource
RESOURCE_ID=$(aws apigateway create-resource \
--rest-api-id "$API_ID" \
--parent-id "$ROOT_ID" \
--path-part users \
--query id --output text \
--endpoint-url "$AWS_ENDPOINT_URL")
echo "RESOURCE_ID: $RESOURCE_ID"

# Add a GET method
aws apigateway put-method \
--rest-api-id "$API_ID" \
--resource-id "$RESOURCE_ID" \
--http-method GET \
--authorization-type NONE \
--endpoint-url "$AWS_ENDPOINT_URL"

# Add a Lambda integration (non-proxy AWS type)
aws apigateway put-integration \
--rest-api-id "$API_ID" \
--resource-id "$RESOURCE_ID" \
--http-method GET \
--type AWS \
--integration-http-method POST \
--uri "arn:aws:apigateway:eu-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-2:000000000000:function:my-function/invocations" \
--endpoint-url "$AWS_ENDPOINT_URL"

# Add a method response (required for non-proxy integrations)
aws apigateway put-method-response \
--rest-api-id "$API_ID" \
--resource-id "$RESOURCE_ID" \
--http-method GET \
--status-code 200 \
--endpoint-url "$AWS_ENDPOINT_URL"

# Add an integration response (required for non-proxy integrations)
aws apigateway put-integration-response \
--rest-api-id "$API_ID" \
--resource-id "$RESOURCE_ID" \
--http-method GET \
--status-code 200 \
--selection-pattern "" \
--endpoint-url "$AWS_ENDPOINT_URL"

# Create a deployment
DEPLOYMENT_ID=$(aws apigateway create-deployment \
--rest-api-id "$API_ID" \
--query id --output text \
--endpoint-url "$AWS_ENDPOINT_URL")
echo "DEPLOYMENT_ID: $DEPLOYMENT_ID"

# Create the stage explicitly
aws apigateway create-stage \
--rest-api-id "$API_ID" \
--stage-name dev \
--deployment-id "$DEPLOYMENT_ID" \
--endpoint-url "$AWS_ENDPOINT_URL"

echo ""
echo "=== Curl Commands ==="
echo "GET /users:"
echo " curl http://localhost:4566/restapis/$API_ID/dev/_user_request_/users"
echo ""
echo "=== IDs Summary ==="
echo " API_ID: $API_ID"
echo " ROOT_ID: $ROOT_ID"
echo " RESOURCE_ID: $RESOURCE_ID"
echo " Stage: dev"
echo ""

# Call the deployed API
# Invoke — returns 500 with "The request must contain the parameter Action" (Floci bug)
curl http://localhost:4566/restapis/$API_ID/dev/_user_request_/users
```

## Environment

- Floci version / image tag: latest
- Java SDK version (if applicable): AWS CLI v2
- How you're running Floci (Docker / native / mvn quarkus:dev): Docker
6 changes: 3 additions & 3 deletions docker/infrastructure.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
services:
localstack:
container_name: "localstack-main"
image: localstack/localstack:4.14.0
floci:
container_name: "floci-main"
image: floci/floci:2.0.1
ports:
- "127.0.0.1:4566:4566" # LocalStack Gateway
Comment on lines +2 to 6
- "127.0.0.1:4510-4559:4510-4559" # external services port range
Expand Down
2 changes: 1 addition & 1 deletion docker/scripts/load-dummy-data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set -e
BASE_GUID="4eb3b7350ab7aa443650fc9351f02940E"
BASE_AREA="TESTAREA"
DATA_FILE="test/lib/functions/data/nws-alert.xml"
LAMBDA_URL=http://$(awslocal apigateway get-rest-apis | jq -r ".items[0].id").execute-api.localhost.localstack.cloud:4566/local/message
LAMBDA_URL=http://localhost:4566/restapis/$(awslocal apigateway get-rest-apis | jq -r ".items[0].id")/local/_user_request_/message

Comment on lines 10 to 12
# Calculate tomorrow's date
TOMORROW=$(date -u -d "+1 day" +"%Y-%m-%dT%H:%M:%S+00:00")
Expand Down
26 changes: 22 additions & 4 deletions docker/scripts/register-api-gateway.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ set -e
main() {
# Reference - https://docs.localstack.cloud/user-guide/aws/apigateway/
echo "Creating API Gateway"
echo $AWS_ENDPOINT_URL

cap_xml_rest_api_id=$(awslocal apigateway create-rest-api --name "FWS API Gateway" | jq -r '.id')
cap_xml_rest_api_id=$(awslocal apigateway create-rest-api --name "CPX API Gateway" | jq -r '.id')
cap_xml_rest_api_root_resource_id=$(awslocal apigateway get-resources --rest-api-id $cap_xml_rest_api_id | jq -r '.items[0].id')
lambda_functions_dir="lib/functions"

Expand Down Expand Up @@ -37,9 +38,13 @@ main() {

done

awslocal apigateway create-deployment \
--rest-api-id $cap_xml_rest_api_id \
--stage-name local
deployment_id=$(awslocal apigateway create-deployment \
--rest-api-id $cap_xml_rest_api_id | jq -r '.id')
Comment on lines +41 to +42

awslocal apigateway create-stage \
--rest-api-id $cap_xml_rest_api_id \
--stage-name local \
--deployment-id $deployment_id

echo "Created API Gateway deployment"
return 0
Expand Down Expand Up @@ -96,6 +101,19 @@ register_api_gateway_support_for_process_message() {
create_resource() {
cap_xml_rest_api_root_resource_id=$1
cap_xml_rest_api_path_part=$2

# A resource with the same parent and path part may already have been created
# by another lambda function (e.g. GET /message/{id} and POST /message share /message).
existing_resource_id=$(awslocal apigateway get-resources \
--rest-api-id $cap_xml_rest_api_id \
| jq -r --arg parent "$cap_xml_rest_api_root_resource_id" --arg path "$cap_xml_rest_api_path_part" \
'.items[] | select(.parentId == $parent and .pathPart == $path) | .id')

if [ -n "$existing_resource_id" ]; then
echo $existing_resource_id
return 0
fi

echo $(awslocal apigateway create-resource \
--rest-api-id $cap_xml_rest_api_id \
--parent-id $cap_xml_rest_api_root_resource_id \
Expand Down
11 changes: 7 additions & 4 deletions docker/scripts/register-lambda-functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
set -e

lambda_functions_dir="lib/functions"
deployed_cpx_agw_url=http://$(awslocal apigateway get-rest-apis | jq -r ".items[0].id").execute-api.localhost.localstack.cloud:4566/local
deployed_cpx_agw_url=http://localhost:4566/restapis/$(awslocal apigateway get-rest-apis | jq -r ".items[0].id")/local/_user_request_

Comment on lines 6 to 8
# Prepare a comma separated list of custom environment variables required by
# each Lambda function.
Expand All @@ -24,6 +24,9 @@ node_tls_reject_unauthorized=$(echo NODE_TLS_REJECT_UNAUTHORIZED=$NODE_TLS_REJEC
set -- $cpx_db_username $cpx_db_password $cpx_db_name $cpx_db_host $cpx_agw_url $cpx_redis_host $cpx_redis_port $cpx_redis_tls $cpx_meteoalarm_api_url $cpx_meteoalarm_api_username $cpx_meteoalarm_api_password $node_tls_reject_unauthorized $cpx_meteoalarm_disable
custom_environment_variables=$(printf '%s,' "$@" | sed 's/,*$//g')

# Create the hot-reload bucket so Floci can serve Lambda code from the local filesystem.
awslocal s3 mb s3://hot-reload 2>/dev/null || true

# Iterate over each file in lambda_functions_dir
find "$lambda_functions_dir" -type f -name "*.js" | while read -r lambda_function; do
if [ -f "$lambda_function" ]; then
Expand All @@ -43,7 +46,7 @@ find "$lambda_functions_dir" -type f -name "*.js" | while read -r lambda_functio
;;
esac

echo Registering $function_name with LocalStack
echo Registering $function_name with Floci

awslocal lambda create-function \
--function-name "$function_name" \
Expand All @@ -59,12 +62,12 @@ find "$lambda_functions_dir" -type f -name "*.js" | while read -r lambda_functio
fi
done

echo "All Lambda functions have been registered with LocalStack."
echo "All Lambda functions have been registered with Floci."

awslocal lambda create-function-url-config --function-name archiveMessages --auth-type NONE

echo "Created function URL config for archiveMessages function"

echo Function URL for archiveMessages is $(awslocal lambda get-function-url-config --function-name archiveMessages | jq -r .FunctionUrl)
echo API Gateway root URL is http://$(awslocal apigateway get-rest-apis | jq -r ".items[0].id").execute-api.localhost.localstack.cloud:4566/local
echo API Gateway base URL is http://localhost:4566/restapis/$(awslocal apigateway get-rest-apis | jq -r ".items[0].id")/local/_user_request_

29 changes: 29 additions & 0 deletions docker/scripts/test-teardown.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_PAGER=""

Comment on lines +1 to +4
# Delete the Lambda function
echo "Deleting Lambda function 'my-function'..."
awslocal lambda delete-function \
--function-name my-function \
--endpoint-url $AWS_ENDPOINT_URL 2>/dev/null && echo "Lambda function deleted" || echo "Lambda function not found"

# Get the API ID (assumes the test API is named "My API")
API_ID=$(awslocal apigateway get-rest-apis \
--query 'items[?name==`My API`].id' --output text \
--endpoint-url $AWS_ENDPOINT_URL)
Comment on lines +12 to +14

if [ -z "$API_ID" ]; then

Check failure on line 16 in docker/scripts/test-teardown.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=DEFRA_cap-xml&issues=AZ-yLFS6OJxdzS1acQaC&open=AZ-yLFS6OJxdzS1acQaC&pullRequest=105
echo "No API found with name 'My API'"
echo "Teardown complete"
exit 0
fi

echo "Deleting API: $API_ID"

# Delete the REST API (removes all resources, methods, integrations, and deployments)
awslocal apigateway delete-rest-api \
--rest-api-id $API_ID \
--endpoint-url $AWS_ENDPOINT_URL

echo "Teardown complete"
Loading
Loading