Skip to content

Commit d8c8a4d

Browse files
authored
Merge pull request #16 from VectorInstitute/add_firestore_token_service
Add firestore token service
2 parents b245b5b + 306ce91 commit d8c8a4d

File tree

5 files changed

+496
-0
lines changed

5 files changed

+496
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#!/bin/bash
2+
#
3+
# Deploy Firebase Token Generation Service to Cloud Run
4+
#
5+
# This script builds and deploys the token service that generates fresh
6+
# Firebase custom tokens for workspace service accounts.
7+
#
8+
# Usage:
9+
# ./deploy_token_service.sh [OPTIONS]
10+
#
11+
# Options:
12+
# --project PROJECT_ID GCP project ID (default: coderd)
13+
# --region REGION Cloud Run region (default: us-central1)
14+
# --service-name NAME Service name (default: firebase-token-service)
15+
# --allow-unauthenticated Allow unauthenticated requests (NOT recommended)
16+
# --dry-run Show commands without executing
17+
#
18+
19+
set -euo pipefail
20+
21+
# Default configuration
22+
PROJECT_ID="${GCP_PROJECT:-coderd}"
23+
REGION="us-central1"
24+
SERVICE_NAME="firebase-token-service"
25+
ALLOW_UNAUTHENTICATED="false"
26+
DRY_RUN="false"
27+
FIRESTORE_DATABASE="onboarding"
28+
29+
# Colors for output
30+
RED='\033[0;31m'
31+
GREEN='\033[0;32m'
32+
YELLOW='\033[1;33m'
33+
BLUE='\033[0;34m'
34+
NC='\033[0m' # No Color
35+
36+
# Parse command line arguments
37+
while [[ $# -gt 0 ]]; do
38+
case $1 in
39+
--project)
40+
PROJECT_ID="$2"
41+
shift 2
42+
;;
43+
--region)
44+
REGION="$2"
45+
shift 2
46+
;;
47+
--service-name)
48+
SERVICE_NAME="$2"
49+
shift 2
50+
;;
51+
--allow-unauthenticated)
52+
ALLOW_UNAUTHENTICATED="true"
53+
shift
54+
;;
55+
--dry-run)
56+
DRY_RUN="true"
57+
shift
58+
;;
59+
*)
60+
echo -e "${RED}Unknown option: $1${NC}"
61+
exit 1
62+
;;
63+
esac
64+
done
65+
66+
# Get script directory
67+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
68+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
69+
SERVICE_DIR="${PROJECT_ROOT}/services/token-service"
70+
71+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
72+
echo -e "${BLUE}Firebase Token Service Deployment${NC}"
73+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
74+
echo ""
75+
echo -e "${YELLOW}Configuration:${NC}"
76+
echo " Project ID: ${PROJECT_ID}"
77+
echo " Region: ${REGION}"
78+
echo " Service Name: ${SERVICE_NAME}"
79+
echo " Firestore Database: ${FIRESTORE_DATABASE}"
80+
echo " Service Directory: ${SERVICE_DIR}"
81+
echo " Dry Run: ${DRY_RUN}"
82+
echo ""
83+
84+
# Function to execute or print commands
85+
run_cmd() {
86+
if [[ "${DRY_RUN}" == "true" ]]; then
87+
echo -e "${YELLOW}[DRY RUN]${NC} $*"
88+
else
89+
echo -e "${GREEN}${NC} $*"
90+
"$@"
91+
fi
92+
}
93+
94+
# Verify service directory exists
95+
if [[ ! -d "${SERVICE_DIR}" ]]; then
96+
echo -e "${RED}✗ Service directory not found: ${SERVICE_DIR}${NC}"
97+
exit 1
98+
fi
99+
100+
echo -e "${GREEN}${NC} Service directory found"
101+
102+
# Check required files
103+
REQUIRED_FILES=("main.py" "requirements.txt" "Dockerfile")
104+
for file in "${REQUIRED_FILES[@]}"; do
105+
if [[ ! -f "${SERVICE_DIR}/${file}" ]]; then
106+
echo -e "${RED}✗ Required file not found: ${file}${NC}"
107+
exit 1
108+
fi
109+
done
110+
echo -e "${GREEN}${NC} All required files present"
111+
echo ""
112+
113+
# Step 1: Set GCP project
114+
echo -e "${BLUE}Step 1: Configure GCP Project${NC}"
115+
run_cmd gcloud config set project "${PROJECT_ID}"
116+
echo ""
117+
118+
# Step 2: Enable required APIs
119+
echo -e "${BLUE}Step 2: Enable Required APIs${NC}"
120+
REQUIRED_APIS=(
121+
"run.googleapis.com"
122+
"cloudbuild.googleapis.com"
123+
"artifactregistry.googleapis.com"
124+
)
125+
126+
for api in "${REQUIRED_APIS[@]}"; do
127+
echo -e "${GREEN}${NC} Enabling ${api}..."
128+
run_cmd gcloud services enable "${api}" --project="${PROJECT_ID}"
129+
done
130+
echo ""
131+
132+
# Step 3: Build and deploy to Cloud Run
133+
echo -e "${BLUE}Step 3: Build and Deploy to Cloud Run${NC}"
134+
echo -e "${GREEN}${NC} Building container image and deploying..."
135+
136+
DEPLOY_CMD=(
137+
gcloud run deploy "${SERVICE_NAME}"
138+
--source="${SERVICE_DIR}"
139+
--platform=managed
140+
--region="${REGION}"
141+
--project="${PROJECT_ID}"
142+
--set-env-vars="GCP_PROJECT=${PROJECT_ID},FIRESTORE_DATABASE=${FIRESTORE_DATABASE}"
143+
--memory=512Mi
144+
--cpu=1
145+
--timeout=60s
146+
--max-instances=10
147+
--min-instances=0
148+
--concurrency=80
149+
)
150+
151+
if [[ "${ALLOW_UNAUTHENTICATED}" == "false" ]]; then
152+
DEPLOY_CMD+=(--no-allow-unauthenticated)
153+
else
154+
DEPLOY_CMD+=(--allow-unauthenticated)
155+
echo -e "${YELLOW}⚠ Warning: Allowing unauthenticated access${NC}"
156+
fi
157+
158+
run_cmd "${DEPLOY_CMD[@]}"
159+
echo ""
160+
161+
# Step 4: Get service URL
162+
if [[ "${DRY_RUN}" == "false" ]]; then
163+
echo -e "${BLUE}Step 4: Retrieve Service URL${NC}"
164+
SERVICE_URL=$(gcloud run services describe "${SERVICE_NAME}" \
165+
--platform=managed \
166+
--region="${REGION}" \
167+
--project="${PROJECT_ID}" \
168+
--format='value(status.url)')
169+
170+
echo -e "${GREEN}${NC} Service deployed successfully!"
171+
echo ""
172+
echo -e "${GREEN}Service URL:${NC} ${SERVICE_URL}"
173+
echo ""
174+
175+
# Step 5: Set IAM permissions for workspace service accounts
176+
echo -e "${BLUE}Step 5: Configure IAM Permissions${NC}"
177+
echo -e "${YELLOW}Note: You need to grant Cloud Run Invoker role to workspace service accounts${NC}"
178+
echo ""
179+
echo "Run the following command for each workspace service account:"
180+
echo ""
181+
echo -e "${BLUE}gcloud run services add-iam-policy-binding ${SERVICE_NAME} \\${NC}"
182+
echo -e "${BLUE} --region=${REGION} \\${NC}"
183+
echo -e "${BLUE} --project=${PROJECT_ID} \\${NC}"
184+
echo -e "${BLUE} --member='serviceAccount:WORKSPACE_SA_EMAIL' \\${NC}"
185+
echo -e "${BLUE} --role='roles/run.invoker'${NC}"
186+
echo ""
187+
echo "Or grant access to the default compute service account:"
188+
echo ""
189+
190+
# Get project number
191+
PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format='value(projectNumber)')
192+
DEFAULT_SA="${PROJECT_NUMBER}[email protected]"
193+
194+
echo -e "${BLUE}gcloud run services add-iam-policy-binding ${SERVICE_NAME} \\${NC}"
195+
echo -e "${BLUE} --region=${REGION} \\${NC}"
196+
echo -e "${BLUE} --project=${PROJECT_ID} \\${NC}"
197+
echo -e "${BLUE} --member='serviceAccount:${DEFAULT_SA}' \\${NC}"
198+
echo -e "${BLUE} --role='roles/run.invoker'${NC}"
199+
echo ""
200+
201+
# Step 6: Test the service
202+
echo -e "${BLUE}Step 6: Test the Service${NC}"
203+
echo "Test the health endpoint:"
204+
echo ""
205+
echo -e "${BLUE}curl ${SERVICE_URL}/health${NC}"
206+
echo ""
207+
echo "Test token generation (requires authentication):"
208+
echo ""
209+
echo -e "${BLUE}curl -X POST ${SERVICE_URL}/generate-token \\${NC}"
210+
echo -e "${BLUE} -H \"Authorization: Bearer \$(gcloud auth print-identity-token)\" \\${NC}"
211+
echo -e "${BLUE} -H \"Content-Type: application/json\" \\${NC}"
212+
echo -e "${BLUE} -d '{\"github_handle\": \"your-github-username\"}'${NC}"
213+
echo ""
214+
215+
# Save service URL to config file
216+
CONFIG_FILE="${PROJECT_ROOT}/.token-service-url"
217+
echo "${SERVICE_URL}" > "${CONFIG_FILE}"
218+
echo -e "${GREEN}${NC} Service URL saved to ${CONFIG_FILE}"
219+
echo ""
220+
fi
221+
222+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
223+
echo -e "${GREEN}Deployment Complete!${NC}"
224+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
__pycache__
2+
*.pyc
3+
*.pyo
4+
*.pyd
5+
.Python
6+
*.so
7+
*.egg
8+
*.egg-info
9+
dist
10+
build
11+
.git
12+
.gitignore
13+
README.md
14+
.env
15+
.venv
16+
venv/

services/token-service/Dockerfile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Use official Python runtime as base image
2+
FROM python:3.12-slim
3+
4+
# Set working directory
5+
WORKDIR /app
6+
7+
# Install dependencies
8+
COPY requirements.txt .
9+
RUN pip install --no-cache-dir -r requirements.txt
10+
11+
# Copy application code
12+
COPY main.py .
13+
14+
# Create non-root user for security
15+
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
16+
USER appuser
17+
18+
# Expose port
19+
EXPOSE 8080
20+
21+
# Run the application with gunicorn for production
22+
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app

0 commit comments

Comments
 (0)