Skip to content

Instantly share code, notes, and snippets.

@cqsd
Created September 14, 2021 05:09
Show Gist options
  • Save cqsd/95737123b8b326d64aa0e9d1485d7f9f to your computer and use it in GitHub Desktop.
Save cqsd/95737123b8b326d64aa0e9d1485d7f9f to your computer and use it in GitHub Desktop.
An AWS Lambda runtime in bash
Invoke with SQS.
{ command: "ls $1", "argv": "/tmp" }
#!/bin/bash
# TODO: add a trap in the process_payload function to fail outright and prevent
# unnecessary retries
set -eu
BUCKET=${XARGS_LAMBDA_BUCKET_NAME:-""}
PREFIX=${XARGS_LAMBDA_BUCKET_PREFIX:-"b"}
if [ -z "$BUCKET" ]; then
echo 'missing required configuration XARGS_LAMBDA_BUCKET_NAME in env'
exit 2
fi
# Parse the payload out of an SQS event.
# Looks like { command: string, output_queue: string, argv: string }
get_event_payloads() {
echo $1 | jq '.Records[].body | fromjson'
}
# Run the script and send the output to the runtime API
process_payload() {
REQUEST_ID=$1
DATA_FILE=$2
CMD_FILE=$(mktemp)
jq -cr .command < "$DATA_FILE" > "$CMD_FILE"
QUEUE=$(jq -cr .output_queue < "$DATA_FILE")
ARGV=$(jq -cr .argv < "$DATA_FILE")
echo "running $(<$CMD_FILE) $ARGV"
# run the command and collect the output
STDOUT=$(mktemp)
STDERR=$(mktemp)
# tee to files while keeping stdin and stderr as separate streams
# bash $CMD_FILE $ARGV > >(tee -a $STDOUT) 2> >(tee -a $STDERR >&2)
# never mind, /dev/fd doesn't exist in lambda, and you're not root so no ln hax
# whatever it doesn't really matter
bash $CMD_FILE $ARGV 2>"$STDERR" >"$STDOUT"
cat $STDOUT $STDERR
DATA="{\"stdout\": \"$(base64 -w 0 < $STDOUT)\", \"stderr\": \"$(base64 -w 0 < $STDERR)\"}"
# send the response to the runtime api
curl -s -X POST \
"http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" \
-d "$DATA"
}
# Report an error to the runtime API
process_invalid_payload() {
REQUEST_ID=$1
DATA_FILE=$2
echo Unknown event data format $(cat "$DATA_FILE")
DATA='{ "errorMessage": "Invalid event data", "errorType": "InvalidEventDataException" }'
curl -s -X POST \
"http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/error" \
-d "$DATA" >/dev/null
}
# Get events from the runtime API and process them in a loop
start_runtime() {
while true; do
# get an event from the runtime API
HEADERS="$(mktemp)"
DATA_FILE="$(mktemp)"
curl -sSL \
"http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next" \
-D "$HEADERS" \
-o "$DATA_FILE"
# get request ID from headers
REQUEST_ID=$(grep -i Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
if [ ! "$(jq -cr .Records < $DATA_FILE)" = "null" ]; then
# asynchronous invoke from SQS
for payload in $(get_event_payloads "$EVENT_DATA"); do
process_payload "$REQUEST_ID" "$payload"
done
elif [ ! "$(jq -cr .command < $DATA_FILE)" = "null" ]; then
# synchronous invoke
process_payload "$REQUEST_ID" "$DATA_FILE"
else
process_invalid_payload "$REQUEST_ID" "$DATA_FILE"
fi
done
}
start_runtime
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment