Skip to content

Commit 4837109

Browse files
authored
feat: add user flag implementation (#1722)
1 parent 152c80e commit 4837109

26 files changed

Lines changed: 2081 additions & 127 deletions

File tree

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
# Deploys a developer Feeds API (Cloud Run) and Operations API (Cloud Function) to the DEV project.
2+
#
3+
# Completely isolated — no Terraform, no shared infrastructure changes.
4+
# Only two new named resources are created/updated (both scoped to your suffix):
5+
# - Cloud Run: developer-feed-api-<name_suffix>
6+
# - Cloud Function: operations-api-developer-<name_suffix>
7+
#
8+
# Both use the same dev secrets/databases as the shared dev environment (read-safe).
9+
#
10+
# Prerequisites: none — reuses all existing dev environment vars and secrets:
11+
# Secrets (already set for api-dev.yml): DEV_GCP_MOBILITY_FEEDS_SA_KEY, OP_SERVICE_ACCOUNT_TOKEN
12+
# Variables (already set for api-dev.yml): DEV_MOBILITY_FEEDS_PROJECT_ID,
13+
# DEV_MOBILITY_FEEDS_DEPLOYER_SERVICE_ACCOUNT, MOBILITY_FEEDS_REGION
14+
#
15+
# Usage: Actions → "Deploy Developer Feeds API" → Run workflow → fill in name_suffix
16+
name: Deploy Developer Feeds API
17+
18+
on:
19+
# PR for testing
20+
push:
21+
branches:
22+
- 'user_feature_flag'
23+
workflow_dispatch:
24+
inputs:
25+
name_suffix:
26+
description: >
27+
Developer identifier suffix. Creates:
28+
Cloud Run: mobility-feed-api-developer-<suffix>
29+
Cloud Function: operations-api-developer-<suffix>
30+
Use your name or branch (e.g. "david", "feat-1723").
31+
required: true
32+
default: 'x'
33+
deploy_feeds_api:
34+
description: 'Deploy Feeds API (Cloud Run)'
35+
required: false
36+
default: 'true'
37+
deploy_operations_api:
38+
description: 'Deploy Operations API (Cloud Function)'
39+
required: false
40+
default: 'true'
41+
42+
env:
43+
python_version: '3.11'
44+
java_version: '11'
45+
REGION: ${{ vars.MOBILITY_FEEDS_REGION }}
46+
PROJECT_ID: ${{ vars.DEV_MOBILITY_FEEDS_PROJECT_ID }}
47+
ARTIFACT_REPO: feeds-dev
48+
# Dev VPC connector lives in the QA project (matches Terraform logic in infra/main.tf)
49+
VPC_CONNECTOR: vpc-connector-qa
50+
VPC_CONNECTOR_PROJECT: mobility-feeds-qa
51+
52+
jobs:
53+
# ── Shared setup: generate code stubs needed by both deploy jobs ──────────
54+
generate-and-build:
55+
runs-on: ubuntu-latest
56+
permissions: write-all
57+
needs: []
58+
steps:
59+
- name: Checkout code
60+
uses: actions/checkout@v4
61+
62+
- name: Extract commit hash and version from git
63+
run: ./scripts/extract-hash-and-version.sh
64+
65+
- name: Set up JDK ${{ env.java_version }}
66+
uses: actions/setup-java@v4
67+
with:
68+
java-version: ${{ env.java_version }}
69+
distribution: 'temurin'
70+
71+
- uses: actions/setup-python@v5
72+
with:
73+
python-version: ${{ env.python_version }}
74+
75+
- name: Start PostgreSQL (needed for db-gen)
76+
run: docker compose --env-file ./config/.env.local up -d --wait postgres postgres-test
77+
78+
- name: Install Liquibase
79+
env:
80+
LIQUIBASE_VERSION: 4.33.0
81+
run: |
82+
curl -sSL https://github.com/liquibase/liquibase/releases/download/v${LIQUIBASE_VERSION}/liquibase-${LIQUIBASE_VERSION}.tar.gz -o liquibase.tar.gz
83+
mkdir liquibase-dist && tar -xzf liquibase.tar.gz -C liquibase-dist
84+
sudo mv liquibase-dist /usr/local/liquibase
85+
sudo ln -sf /usr/local/liquibase/liquibase /usr/local/bin/liquibase
86+
87+
- name: Run Liquibase migrations
88+
working-directory: ${{ github.workspace }}/liquibase
89+
run: |
90+
export LIQUIBASE_COMMAND_USERNAME=postgres
91+
export LIQUIBASE_COMMAND_PASSWORD=postgres
92+
export LIQUIBASE_COMMAND_URL=jdbc:postgresql://localhost:54320/MobilityDatabaseTest
93+
export LIQUIBASE_COMMAND_CHANGELOG_FILE=changelog.xml
94+
liquibase update
95+
export LIQUIBASE_COMMAND_URL=jdbc:postgresql://localhost:54320/MobilityDatabaseUsersTest
96+
export LIQUIBASE_COMMAND_CHANGELOG_FILE=changelog_user.xml
97+
liquibase update
98+
99+
- name: Generate API stubs and DB models
100+
run: |
101+
scripts/setup-openapi-generator.sh
102+
scripts/api-gen.sh
103+
scripts/api-user-service-gen.sh
104+
scripts/api-operations-gen.sh
105+
export USE_TEST_DB=true
106+
scripts/db-gen.sh
107+
scripts/db-gen-user.sh
108+
109+
- name: Build operations API function zip
110+
if: ${{ github.event.inputs.deploy_operations_api != 'false' }}
111+
run: scripts/function-python-build.sh --function_name operations_api
112+
113+
- name: Upload generated feeds_gen
114+
uses: actions/upload-artifact@v4
115+
with:
116+
name: feeds_gen
117+
path: api/src/feeds_gen/
118+
119+
- name: Upload generated database_gen
120+
uses: actions/upload-artifact@v4
121+
with:
122+
name: database_gen
123+
path: api/src/shared/database_gen/
124+
125+
- name: Upload generated users_database_gen
126+
uses: actions/upload-artifact@v4
127+
with:
128+
name: users_database_gen
129+
path: api/src/shared/users_database_gen/
130+
131+
- name: Upload generated user_service_gen
132+
uses: actions/upload-artifact@v4
133+
with:
134+
name: user_service_gen
135+
path: api/src/user_service_gen/
136+
137+
- name: Upload operations API build
138+
if: ${{ github.event.inputs.deploy_operations_api != 'false' }}
139+
uses: actions/upload-artifact@v4
140+
with:
141+
name: operations_api_build
142+
path: functions-python/operations_api/.dist/
143+
include-hidden-files: true
144+
145+
# ── Feeds API: build Docker image and deploy to developer Cloud Run ─────────
146+
deploy-feeds-api:
147+
if: ${{ github.event.inputs.deploy_feeds_api != 'false' }}
148+
runs-on: ubuntu-latest
149+
permissions: write-all
150+
needs: [generate-and-build]
151+
outputs:
152+
service_name: ${{ steps.names.outputs.service_name }}
153+
service_url: ${{ steps.get_url.outputs.url }}
154+
steps:
155+
- name: Checkout code
156+
uses: actions/checkout@v4
157+
158+
- name: Set service name and image tag
159+
id: names
160+
run: |
161+
echo "service_name=mobility-feed-api-developer${{ github.event.inputs.name_suffix || 'x' }}" >> "$GITHUB_OUTPUT"
162+
echo "image_tag=developer-$(date +%s)-${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT"
163+
164+
- name: Download generated artifacts
165+
uses: actions/download-artifact@v4
166+
with:
167+
name: feeds_gen
168+
path: api/src/feeds_gen/
169+
170+
- uses: actions/download-artifact@v4
171+
with:
172+
name: database_gen
173+
path: api/src/shared/database_gen/
174+
175+
- uses: actions/download-artifact@v4
176+
with:
177+
name: users_database_gen
178+
path: api/src/shared/users_database_gen/
179+
180+
- uses: actions/download-artifact@v4
181+
with:
182+
name: user_service_gen
183+
path: api/src/user_service_gen/
184+
185+
- name: Create local .env (required by Dockerfile)
186+
run: |
187+
echo "ENVIRONMENT=dev" > config/.env.local
188+
189+
- name: Authenticate to Google Cloud
190+
uses: google-github-actions/auth@v2
191+
with:
192+
credentials_json: ${{ secrets.DEV_GCP_MOBILITY_FEEDS_SA_KEY }}
193+
194+
- name: Login to Artifact Registry
195+
uses: docker/login-action@v3
196+
with:
197+
registry: ${{ env.REGION }}-docker.pkg.dev
198+
username: _json_key_base64
199+
password: ${{ secrets.DEV_GCP_MOBILITY_FEEDS_SA_KEY }}
200+
201+
- name: Build & Push Docker image
202+
run: |
203+
scripts/docker-build-push.sh \
204+
-project_id "${{ env.PROJECT_ID }}" \
205+
-repo_name "${{ env.ARTIFACT_REPO }}" \
206+
-service "${{ steps.names.outputs.service_name }}" \
207+
-region "${{ env.REGION }}" \
208+
-version "${{ steps.names.outputs.image_tag }}"
209+
210+
- name: GCloud Setup
211+
uses: google-github-actions/setup-gcloud@v2
212+
213+
- name: Deploy to developer Cloud Run
214+
# Creates/updates ONLY developer-feed-api-<suffix> — shared dev Cloud Run is untouched.
215+
# Secret names follow the same DEV_<KEY> pattern used by Terraform (infra/feed-api/main.tf).
216+
run: |
217+
IMAGE="${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.ARTIFACT_REPO }}/${{ steps.names.outputs.service_name }}:${{ steps.names.outputs.image_tag }}"
218+
gcloud run deploy "${{ steps.names.outputs.service_name }}" \
219+
--image "$IMAGE" \
220+
--platform managed \
221+
--region "${{ env.REGION }}" \
222+
--allow-unauthenticated \
223+
--project "${{ env.PROJECT_ID }}" \
224+
--service-account "${{ vars.DEV_MOBILITY_FEEDS_DEPLOYER_SERVICE_ACCOUNT }}" \
225+
--update-secrets "FEEDS_DATABASE_URL=DEV_FEEDS_DATABASE_URL:latest,USERS_DATABASE_URL=DEV_USERS_DATABASE_URL:latest,S2S_JWT_SECRET=DEV_S2S_JWT_SECRET:latest" \
226+
--set-env-vars "PROJECT_ID=${{ env.PROJECT_ID }}" \
227+
--vpc-connector "projects/${{ env.VPC_CONNECTOR_PROJECT }}/locations/${{ env.REGION }}/connectors/${{ env.VPC_CONNECTOR }}" \
228+
--vpc-egress all
229+
230+
- name: Get service URL
231+
id: get_url
232+
run: |
233+
URL=$(gcloud run services describe "${{ steps.names.outputs.service_name }}" \
234+
--platform managed \
235+
--region "${{ env.REGION }}" \
236+
--project "${{ env.PROJECT_ID }}" \
237+
--format 'value(status.url)')
238+
echo "url=${URL}" >> "$GITHUB_OUTPUT"
239+
240+
# ── Operations API: build zip and deploy to developer Cloud Function ────────
241+
deploy-operations-api:
242+
if: ${{ github.event.inputs.deploy_operations_api != 'false' }}
243+
runs-on: ubuntu-latest
244+
permissions: write-all
245+
needs: [generate-and-build]
246+
outputs:
247+
function_name: ${{ steps.names.outputs.function_name }}
248+
function_url: ${{ steps.get_url.outputs.url }}
249+
steps:
250+
- name: Checkout code
251+
uses: actions/checkout@v4
252+
253+
- name: Set function name
254+
id: names
255+
run: |
256+
echo "function_name=operations-api-developer-${{ github.event.inputs.name_suffix || 'dev' }}" >> "$GITHUB_OUTPUT"
257+
258+
- name: Download operations API build
259+
uses: actions/download-artifact@v4
260+
with:
261+
name: operations_api_build
262+
path: functions-python/operations_api/.dist/
263+
264+
- name: Authenticate to Google Cloud
265+
uses: google-github-actions/auth@v2
266+
with:
267+
credentials_json: ${{ secrets.DEV_GCP_MOBILITY_FEEDS_SA_KEY }}
268+
269+
- name: Load OPERATIONS_OAUTH2_CLIENT_ID from 1Password
270+
uses: 1password/load-secrets-action@v2
271+
with:
272+
export-env: true
273+
env:
274+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
275+
OPERATIONS_OAUTH2_CLIENT_ID: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_RETOOL_OAUTH2_CREDS/username"
276+
277+
- name: GCloud Setup
278+
uses: google-github-actions/setup-gcloud@v2
279+
280+
- name: Deploy to developer Cloud Function
281+
# Creates/updates ONLY operations-api-developer-<suffix> — shared operations-api is untouched.
282+
# Secret names follow the same DEV_<KEY> pattern used by Terraform (infra/functions-python/main.tf).
283+
run: |
284+
gcloud functions deploy "${{ steps.names.outputs.function_name }}" \
285+
--gen2 \
286+
--project "${{ env.PROJECT_ID }}" \
287+
--region "${{ env.REGION }}" \
288+
--runtime python311 \
289+
--entry-point main \
290+
--source functions-python/operations_api/.dist/build \
291+
--service-account "${{ vars.DEV_MOBILITY_FEEDS_DEPLOYER_SERVICE_ACCOUNT }}" \
292+
--memory 1Gi \
293+
--timeout 540s \
294+
--max-instances 10 \
295+
--min-instances 0 \
296+
--concurrency 100 \
297+
--cpu 1 \
298+
--ingress-settings all \
299+
--trigger-http \
300+
--allow-unauthenticated \
301+
--set-env-vars "ENVIRONMENT=dev,PROJECT_ID=${{ env.PROJECT_ID }},GCP_REGION=${{ env.REGION }},GOOGLE_CLIENT_ID=${OPERATIONS_OAUTH2_CLIENT_ID}" \
302+
--set-secrets "FEEDS_DATABASE_URL=DEV_FEEDS_DATABASE_URL:latest,USERS_DATABASE_URL=DEV_USERS_DATABASE_URL:latest"
303+
304+
- name: Get function URL
305+
id: get_url
306+
run: |
307+
URL=$(gcloud functions describe "${{ steps.names.outputs.function_name }}" \
308+
--gen2 \
309+
--region "${{ env.REGION }}" \
310+
--project "${{ env.PROJECT_ID }}" \
311+
--format 'value(serviceConfig.uri)')
312+
echo "url=${URL}" >> "$GITHUB_OUTPUT"
313+
314+
# ── Summary ───────────────────────────────────────────────────────────────
315+
summary:
316+
runs-on: ubuntu-latest
317+
needs: [deploy-feeds-api, deploy-operations-api]
318+
if: always()
319+
steps:
320+
- name: Write job summary
321+
run: |
322+
echo "## 🚀 Developer API deployment" >> "$GITHUB_STEP_SUMMARY"
323+
echo "" >> "$GITHUB_STEP_SUMMARY"
324+
echo "| Service | Name | URL |" >> "$GITHUB_STEP_SUMMARY"
325+
echo "|---------|------|-----|" >> "$GITHUB_STEP_SUMMARY"
326+
327+
FEEDS_URL="${{ needs.deploy-feeds-api.outputs.service_url }}"
328+
FEEDS_NAME="${{ needs.deploy-feeds-api.outputs.service_name }}"
329+
if [ -n "$FEEDS_URL" ]; then
330+
echo "| Feeds API | \`${FEEDS_NAME}\` | [${FEEDS_URL}/docs/](${FEEDS_URL}/docs/) |" >> "$GITHUB_STEP_SUMMARY"
331+
fi
332+
333+
OPS_URL="${{ needs.deploy-operations-api.outputs.function_url }}"
334+
OPS_NAME="${{ needs.deploy-operations-api.outputs.function_name }}"
335+
if [ -n "$OPS_URL" ]; then
336+
echo "| Operations API | \`${OPS_NAME}\` | [${OPS_URL}](${OPS_URL}) |" >> "$GITHUB_STEP_SUMMARY"
337+
fi
338+
339+
echo "" >> "$GITHUB_STEP_SUMMARY"
340+
echo "> ⚠️ These are developer services sharing the **dev databases** (read/write)." >> "$GITHUB_STEP_SUMMARY"
341+
echo "> No shared Cloud Run, Terraform state, load balancer, or IAM was modified." >> "$GITHUB_STEP_SUMMARY"

0 commit comments

Comments
 (0)