Skip to content

Commit 71c37c8

Browse files
baonguyenNavaclaude
andcommitted
Add scripted AWS CLI deploy for strata-qa Lambda
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1d2e99a commit 71c37c8

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

strata-qa/deploy.sh

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env bash
2+
# Deploy strata-qa as a container-image Lambda with an IAM-authed Function URL.
3+
# Prereqs: aws CLI v2, docker, and CURSOR_API_KEY exported (personal or
4+
# service-account key). Run from the REPO ROOT — the image build context is the
5+
# repo root, because the image needs both docs/ and strata-qa/.
6+
set -euo pipefail
7+
8+
AWS_REGION="${AWS_REGION:-us-east-1}"
9+
FUNCTION_NAME="${FUNCTION_NAME:-strata-qa}"
10+
ECR_REPO="${ECR_REPO:-strata-qa-lambda}"
11+
IMAGE_TAG="${IMAGE_TAG:-latest}"
12+
ARCH="${ARCH:-arm64}" # arm64 | x86_64
13+
DOCKER_PLATFORM="linux/${ARCH/x86_64/amd64}"
14+
MEMORY_MB="${MEMORY_MB:-2048}"
15+
TIMEOUT_S="${TIMEOUT_S:-120}"
16+
AGENT_TIMEOUT_MS="${AGENT_TIMEOUT_MS:-90000}"
17+
# At ~190k tokens per question (NOTES.md), an endpoint with unreserved
18+
# concurrency is an unbounded spend on the Cursor API -- and a 504 at 90s is
19+
# exactly the response a client library retries. Cap it.
20+
RESERVED_CONCURRENCY="${RESERVED_CONCURRENCY:-3}"
21+
SECRET_NAME="${SECRET_NAME:-strata-qa/cursor-api-key}"
22+
ROLE_NAME="${ROLE_NAME:-strata-qa-lambda-role}"
23+
24+
: "${CURSOR_API_KEY:?export CURSOR_API_KEY (personal or service-account key) before deploying}"
25+
26+
# The agent's own timeout must fire first, so the handler returns a clean 504
27+
# rather than Lambda hard-killing the invocation mid-flight.
28+
if (( AGENT_TIMEOUT_MS >= TIMEOUT_S * 1000 )); then
29+
echo "AGENT_TIMEOUT_MS (${AGENT_TIMEOUT_MS}) must be less than TIMEOUT_S (${TIMEOUT_S}s)" >&2
30+
exit 1
31+
fi
32+
33+
ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
34+
ECR_URI="${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}"
35+
IMAGE_URI="${ECR_URI}:${IMAGE_TAG}"
36+
GIT_SHA="$(git rev-parse HEAD)"
37+
38+
echo "==> ECR repo"
39+
aws ecr describe-repositories --repository-names "$ECR_REPO" --region "$AWS_REGION" >/dev/null 2>&1 \
40+
|| aws ecr create-repository --repository-name "$ECR_REPO" --region "$AWS_REGION" >/dev/null
41+
42+
echo "==> Secret"
43+
if aws secretsmanager describe-secret --secret-id "$SECRET_NAME" --region "$AWS_REGION" >/dev/null 2>&1; then
44+
aws secretsmanager put-secret-value --secret-id "$SECRET_NAME" \
45+
--secret-string "$CURSOR_API_KEY" --region "$AWS_REGION" >/dev/null
46+
else
47+
aws secretsmanager create-secret --name "$SECRET_NAME" \
48+
--secret-string "$CURSOR_API_KEY" --region "$AWS_REGION" >/dev/null
49+
fi
50+
SECRET_ARN="$(aws secretsmanager describe-secret --secret-id "$SECRET_NAME" \
51+
--region "$AWS_REGION" --query ARN --output text)"
52+
53+
echo "==> IAM execution role"
54+
if ! aws iam get-role --role-name "$ROLE_NAME" >/dev/null 2>&1; then
55+
aws iam create-role --role-name "$ROLE_NAME" \
56+
--assume-role-policy-document '{
57+
"Version":"2012-10-17",
58+
"Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]
59+
}' >/dev/null
60+
aws iam attach-role-policy --role-name "$ROLE_NAME" \
61+
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole >/dev/null
62+
fi
63+
# Read just this one secret, nothing else.
64+
aws iam put-role-policy --role-name "$ROLE_NAME" --policy-name read-cursor-secret \
65+
--policy-document "{
66+
\"Version\":\"2012-10-17\",
67+
\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"secretsmanager:GetSecretValue\",\"Resource\":\"${SECRET_ARN}\"}]
68+
}" >/dev/null
69+
ROLE_ARN="$(aws iam get-role --role-name "$ROLE_NAME" --query Role.Arn --output text)"
70+
71+
echo "==> Build & push image (${DOCKER_PLATFORM}, context = repo root)"
72+
aws ecr get-login-password --region "$AWS_REGION" \
73+
| docker login --username AWS --password-stdin "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
74+
docker build -f strata-qa/Dockerfile --platform "$DOCKER_PLATFORM" \
75+
--build-arg GIT_SHA="$GIT_SHA" -t "$IMAGE_URI" .
76+
docker push "$IMAGE_URI"
77+
78+
ENV_VARS="Variables={HOME=/tmp,DOCS_ROOT=/var/task,QA_LOG_DIR=/tmp/qa,AGENT_TIMEOUT_MS=${AGENT_TIMEOUT_MS},CURSOR_API_KEY_SECRET_ID=${SECRET_ARN}}"
79+
80+
echo "==> Lambda function"
81+
if aws lambda get-function --function-name "$FUNCTION_NAME" --region "$AWS_REGION" >/dev/null 2>&1; then
82+
aws lambda update-function-code --function-name "$FUNCTION_NAME" \
83+
--image-uri "$IMAGE_URI" --region "$AWS_REGION" >/dev/null
84+
aws lambda wait function-updated --function-name "$FUNCTION_NAME" --region "$AWS_REGION"
85+
aws lambda update-function-configuration --function-name "$FUNCTION_NAME" \
86+
--timeout "$TIMEOUT_S" --memory-size "$MEMORY_MB" \
87+
--environment "$ENV_VARS" --region "$AWS_REGION" >/dev/null
88+
aws lambda wait function-updated --function-name "$FUNCTION_NAME" --region "$AWS_REGION"
89+
else
90+
# IAM role propagation can lag; retry create briefly.
91+
for i in 1 2 3 4 5; do
92+
aws lambda create-function --function-name "$FUNCTION_NAME" \
93+
--package-type Image --code "ImageUri=${IMAGE_URI}" \
94+
--role "$ROLE_ARN" --architectures "$ARCH" \
95+
--timeout "$TIMEOUT_S" --memory-size "$MEMORY_MB" \
96+
--environment "$ENV_VARS" --region "$AWS_REGION" >/dev/null && break
97+
echo " create failed (role may still be propagating); retry $i..." && sleep 10
98+
done
99+
fi
100+
aws lambda wait function-active-v2 --function-name "$FUNCTION_NAME" --region "$AWS_REGION"
101+
102+
echo "==> Reserved concurrency (${RESERVED_CONCURRENCY}) — cost ceiling"
103+
aws lambda put-function-concurrency --function-name "$FUNCTION_NAME" \
104+
--reserved-concurrent-executions "$RESERVED_CONCURRENCY" --region "$AWS_REGION" >/dev/null
105+
106+
echo "==> Function URL (AWS_IAM auth)"
107+
aws lambda get-function-url-config --function-name "$FUNCTION_NAME" --region "$AWS_REGION" >/dev/null 2>&1 \
108+
|| aws lambda create-function-url-config --function-name "$FUNCTION_NAME" \
109+
--auth-type AWS_IAM --region "$AWS_REGION" >/dev/null
110+
FUNCTION_URL="$(aws lambda get-function-url-config --function-name "$FUNCTION_NAME" \
111+
--region "$AWS_REGION" --query FunctionUrl --output text)"
112+
113+
echo "==> Deployed ${GIT_SHA:0:12}. Function URL: ${FUNCTION_URL}"
114+
echo " Invoke with SigV4, e.g.:"
115+
echo " awscurl --service lambda --region ${AWS_REGION} -X POST \\"
116+
echo " -d '{\"question\":\"What does the nava-platform CLI wrap to install templates?\"}' \\"
117+
echo " ${FUNCTION_URL}"

0 commit comments

Comments
 (0)