Skip to content

Commit 69ccfab

Browse files
authored
feat: Install the right DB version when updating the DB contents (#1426)
1 parent 0b4e1ef commit 69ccfab

File tree

5 files changed

+205
-13
lines changed

5 files changed

+205
-13
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Resolve API commit/version
2+
description: Resolve deployed API commit SHA and version from the metadata endpoint
3+
inputs:
4+
api_base_url:
5+
description: Base URL host for the API (e.g. api-dev.mobilitydatabase.org)
6+
required: false
7+
default: api.mobilitydatabase.org
8+
api_refresh_token:
9+
description: API refresh token
10+
required: false
11+
outputs:
12+
COMMIT_SHA:
13+
description: Resolved commit SHA
14+
value: ${{ steps.resolve.outputs.COMMIT_SHA }}
15+
API_VERSION:
16+
description: Resolved API version
17+
value: ${{ steps.resolve.outputs.API_VERSION }}
18+
RESOLVED:
19+
description: Whether a commit SHA was resolved (true/false)
20+
value: ${{ steps.resolve.outputs.RESOLVED }}
21+
runs:
22+
using: composite
23+
steps:
24+
- id: resolve
25+
name: Resolve via API and expose outputs
26+
shell: bash
27+
env:
28+
API_BASE_URL: ${{ inputs.api_base_url }}
29+
API_REFRESH_TOKEN: ${{ inputs.api_refresh_token }}
30+
run: |
31+
# Do not exit on failure; this action should never abort the caller workflow.
32+
set -u
33+
COMMIT_SHA=""
34+
API_VERSION=""
35+
RESOLVED="false"
36+
37+
if [[ -n "${API_REFRESH_TOKEN:-}" ]]; then
38+
echo "Resolving API commit from https://${API_BASE_URL}/v1/metadata ..."
39+
40+
# Exchange refresh token -> access token (handle failures gracefully)
41+
REPLY_JSON=$(curl --silent --show-error --location "https://${API_BASE_URL}/v1/tokens" \
42+
--header 'Content-Type: application/json' \
43+
--data "{ \"refresh_token\": \"${API_REFRESH_TOKEN}\" }" ) || {
44+
echo "Warning: token exchange failed; will fallback to 'main'" >&2
45+
REPLY_JSON=""
46+
}
47+
48+
if [[ -n "${REPLY_JSON}" ]]; then
49+
ACCESS_TOKEN=$(echo "${REPLY_JSON}" | jq -r .access_token 2>/dev/null || echo "")
50+
if [[ -z "${ACCESS_TOKEN}" || "${ACCESS_TOKEN}" == "null" ]]; then
51+
echo "Warning: Could not obtain access token from reply; will fallback to 'main'" >&2
52+
else
53+
META_JSON=$(curl --silent --show-error \
54+
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
55+
-H 'accept: application/json' \
56+
"https://${API_BASE_URL}/v1/metadata" ) || {
57+
echo "Warning: metadata request failed; will fallback to 'main'" >&2
58+
META_JSON=""
59+
}
60+
61+
if [[ -n "${META_JSON}" ]]; then
62+
COMMIT_SHA=$(echo "${META_JSON}" | jq -r .commit_hash 2>/dev/null || echo "")
63+
API_VERSION=$(echo "${META_JSON}" | jq -r .version 2>/dev/null || echo "")
64+
if [[ -n "${COMMIT_SHA}" && "${COMMIT_SHA}" != "null" ]]; then
65+
RESOLVED="true"
66+
echo "Resolved API version: ${API_VERSION} (commit ${COMMIT_SHA})"
67+
else
68+
echo "Warning: commit_hash missing in metadata; will fallback to 'main'" >&2
69+
fi
70+
fi
71+
fi
72+
fi
73+
else
74+
echo "No API refresh token provided; skipping API metadata resolution and falling back to 'main'."
75+
fi
76+
77+
# Expose outputs (empty COMMIT_SHA and RESOLVED=false indicate fallback to 'main')
78+
echo "COMMIT_SHA=${COMMIT_SHA}" >> "$GITHUB_OUTPUT"
79+
echo "API_VERSION=${API_VERSION}" >> "$GITHUB_OUTPUT"
80+
echo "RESOLVED=${RESOLVED}" >> "$GITHUB_OUTPUT"

.github/workflows/db-update-dev.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ on:
1010
repository_dispatch: # Update on mobility-database-catalog repo dispatch
1111
types: [ catalog-sources-updated, gbfs-systems-updated ]
1212
workflow_dispatch:
13+
inputs:
14+
DRY_RUN:
15+
description: Dry run. Skip applying schema and content updates
16+
required: false
17+
default: false
18+
type: boolean
19+
INSTALL_LATEST:
20+
description: Install the latest (main) API version when true; when false install the currently deployed version.
21+
required: false
22+
default: false
23+
type: boolean
1324
jobs:
1425
update:
1526
uses: ./.github/workflows/db-update.yml
@@ -19,6 +30,14 @@ jobs:
1930
DB_NAME: ${{ vars.DEV_POSTGRE_SQL_DB_NAME }}
2031
ENVIRONMENT: ${{ vars.DEV_MOBILITY_FEEDS_ENVIRONMENT }}
2132
DB_ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }}
33+
API_BASE_URL: api-dev.mobilitydatabase.org
34+
# DRY_RUN is only if requested by the user in a workflow_dispatch
35+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.DRY_RUN }}
36+
# We want to use the currently installed version (not the latest) if we received a dispatch from
37+
# mobility_database_catalog.
38+
# For a workflow_dispatch (manual trigger), we use the value set by the user.
39+
INSTALL_LATEST: ${{ (github.event_name == 'repository_dispatch' && false) || (github.event_name == 'workflow_dispatch' && inputs.INSTALL_LATEST) }}
40+
2241
secrets:
2342
DB_USER_PASSWORD: ${{ secrets.DEV_POSTGRE_USER_PASSWORD }}
2443
DB_USER_NAME: ${{ secrets.DEV_POSTGRE_USER_NAME }}
@@ -28,6 +47,7 @@ jobs:
2847
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
2948
OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }}
3049
POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }}
50+
API_TEST_REFRESH_TOKEN: ${{ secrets.DEV_API_TEST_REFRESH_TOKEN }}
3151
notify-slack-on-failure:
3252
needs: [ update ]
3353
if: always() && (needs.update.result == 'failure') && (github.event_name == 'repository_dispatch')

.github/workflows/db-update-prod.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
# Update the Mobility Database Schema
22
name: Database Update - PROD
33
on:
4-
workflow_dispatch:
4+
workflow_dispatch: # Manual trigger
5+
inputs:
6+
DRY_RUN:
7+
description: Dry run. Skip applying schema and content updates
8+
required: false
9+
default: false
10+
type: boolean
11+
INSTALL_LATEST:
12+
description: Install the latest (main) API version when true; when false install the currently deployed version.
13+
required: false
14+
default: false
15+
type: boolean
516
workflow_call:
617
repository_dispatch: # Update on mobility-database-catalog repo dispatch
718
types: [ catalog-sources-updated, gbfs-systems-updated ]
19+
820
jobs:
921
update:
1022
uses: ./.github/workflows/db-update.yml
@@ -14,6 +26,16 @@ jobs:
1426
DB_NAME: ${{ vars.PROD_POSTGRE_SQL_DB_NAME }}
1527
ENVIRONMENT: ${{ vars.PROD_MOBILITY_FEEDS_ENVIRONMENT }}
1628
DB_ENVIRONMENT: ${{ vars.PROD_MOBILITY_FEEDS_ENVIRONMENT }}
29+
API_BASE_URL: api.mobilitydatabase.org
30+
# DRY_RUN is only if requested by the user in a workflow_dispatch
31+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.DRY_RUN }}
32+
# We want to use the currently installed version (not the latest) if we received a dispatch from
33+
# mobility_database_catalog.
34+
# We want to use the latest version if the trigger was workflow_call, because currently it's used for
35+
# upgrading (e.g. release.yml)
36+
# For a workflow_dispatch (manual trigger), we use the value set by the user.
37+
INSTALL_LATEST: ${{ (github.event_name == 'repository_dispatch' && false) || (github.event_name == 'workflow_call' && true) || (github.event_name == 'workflow_dispatch' && inputs.INSTALL_LATEST) }}
38+
1739
secrets:
1840
DB_USER_PASSWORD: ${{ secrets.PROD_POSTGRE_USER_PASSWORD }}
1941
DB_USER_NAME: ${{ secrets.PROD_POSTGRE_USER_NAME }}
@@ -23,6 +45,7 @@ jobs:
2345
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
2446
OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }}
2547
POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }}
48+
API_TEST_REFRESH_TOKEN: ${{ secrets.PROD_API_TEST_REFRESH_TOKEN }}
2649

2750
notify-slack-on-failure:
2851
needs: [ update ]

.github/workflows/db-update-qa.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
name: Database Update - QA
33
on:
44
workflow_dispatch:
5+
inputs:
6+
DRY_RUN:
7+
description: Dry run. Skip applying schema and content updates
8+
required: false
9+
default: false
10+
type: boolean
11+
INSTALL_LATEST:
12+
description: Install the latest (main) API version when true; when false install the currently deployed version.
13+
required: false
14+
default: false
15+
type: boolean
516
workflow_call:
617
repository_dispatch: # Update on mobility-database-catalog repo dispatch
718
types: [ catalog-sources-updated, gbfs-systems-updated ]
@@ -15,6 +26,16 @@ jobs:
1526
DB_NAME: ${{ vars.QA_POSTGRE_SQL_DB_NAME }}
1627
ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }}
1728
DB_ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }}
29+
API_BASE_URL: api-qa.mobilitydatabase.org
30+
# DRY_RUN is only if requested by the user in a workflow_dispatch
31+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.DRY_RUN }}
32+
# We want to use the currently installed version (not the latest) if we received a dispatch from
33+
# mobility_database_catalog.
34+
# We want to use the latest version if the trigger was workflow_call, because currently it's used for
35+
# upgrading (e.g. release.yml)
36+
# For a workflow_dispatch (manual trigger), we use the value set by the user.
37+
INSTALL_LATEST: ${{ (github.event_name == 'repository_dispatch' && false) || (github.event_name == 'workflow_call' && true) || (github.event_name == 'workflow_dispatch' && inputs.INSTALL_LATEST) }}
38+
1839
secrets:
1940
DB_USER_PASSWORD: ${{ secrets.QA_POSTGRE_USER_PASSWORD }}
2041
DB_USER_NAME: ${{ secrets.QA_POSTGRE_USER_NAME }}
@@ -24,6 +45,7 @@ jobs:
2445
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
2546
OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }}
2647
POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }}
48+
API_TEST_REFRESH_TOKEN: ${{ secrets.QA_API_TEST_REFRESH_TOKEN }}
2749
notify-slack-on-failure:
2850
needs: [ update ]
2951
if: always() && (needs.update.result == 'failure') && (github.event_name == 'repository_dispatch')

.github/workflows/db-update.yml

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ on:
4646
POSTGRE_SQL_INSTANCE_NAME:
4747
description: PostgreSQL Instance Name
4848
required: true
49+
API_TEST_REFRESH_TOKEN:
50+
description: API refresh token used to resolve deployed API commit (used on repository_dispatch)
51+
required: false
4952
inputs:
5053
PROJECT_ID:
5154
description: GCP Project ID
@@ -67,19 +70,61 @@ on:
6770
description: GCP region
6871
required: true
6972
type: string
73+
API_BASE_URL:
74+
description: Base URL host for the API used to resolve version/commit (e.g. api-dev.mobilitydatabase.org)
75+
required: false
76+
default: api.mobilitydatabase.org
77+
type: string
78+
INSTALL_LATEST:
79+
description: Install the latest (main) API version when true; when false keep the currently deployed version.
80+
required: false
81+
default: false
82+
type: boolean
83+
DRY_RUN:
84+
description: Skip applying schema and content updates
85+
required: false
86+
default: false
87+
type: boolean
7088

7189
env:
7290
python_version: '3.11'
7391
liquibase_version: '4.33.0'
7492

7593
jobs:
94+
resolve-api-meta:
95+
name: 'Resolve API commit/version'
96+
runs-on: ubuntu-latest
97+
# Run this job for all triggers; the action itself will skip resolution when API_BASE_URL or token is not provided.
98+
# Keeping it unconditional ensures CHECKOUT_REF is always set (defaults to 'main') for downstream jobs.
99+
outputs:
100+
# Use resolved commit when available; otherwise default to 'main'.
101+
CHECKOUT_REF: ${{ steps.resolve.outputs.COMMIT_SHA != '' && steps.resolve.outputs.COMMIT_SHA || 'main' }}
102+
steps:
103+
- name: Checkout repo (for scripts and local action)
104+
uses: actions/checkout@v4
105+
- name: Resolve API commit/version
106+
id: resolve
107+
if: ${{ inputs.INSTALL_LATEST == false }}
108+
uses: ./.github/actions/resolve-api-meta
109+
with:
110+
api_base_url: ${{ inputs.API_BASE_URL }}
111+
api_refresh_token: ${{ secrets.API_TEST_REFRESH_TOKEN }}
112+
76113
db-schema-update:
77114
name: 'Database Schema Update'
78115
permissions: write-all
79116
runs-on: ubuntu-latest
117+
needs: [resolve-api-meta]
118+
# Run the schema update when the resolved checkout target is 'main' (install latest/main).
119+
# This covers both explicit INSTALL_LATEST runs and cases where resolution failed and CHECKOUT_REF fell back to 'main'.
120+
if: ${{ needs.resolve-api-meta.outputs.CHECKOUT_REF == 'main' }}
80121
steps:
81-
- name: Checkout code
122+
- name: Checkout repo
82123
uses: actions/checkout@v4
124+
with:
125+
# Use the job-level CHECKOUT_REF (already resolves to COMMIT_SHA or 'main')
126+
ref: ${{ needs.resolve-api-meta.outputs.CHECKOUT_REF }}
127+
fetch-depth: 0
83128

84129
- name: Authenticate to Google Cloud QA/PROD
85130
uses: google-github-actions/auth@v2
@@ -126,6 +171,7 @@ jobs:
126171
liquibase --version
127172
128173
- name: Run Liquibase
174+
if: ${{ !inputs.DRY_RUN }}
129175
working-directory: ${{ github.workspace }}/liquibase
130176
run: |
131177
export LIQUIBASE_COMMAND_CHANGELOG_FILE="changelog.xml"
@@ -139,11 +185,15 @@ jobs:
139185
name: 'Database Content Update'
140186
permissions: write-all
141187
runs-on: ubuntu-latest
142-
needs: db-schema-update
143-
if: ${{ github.event_name == 'repository_dispatch' || github.event_name == 'workflow_dispatch' }}
188+
needs: [resolve-api-meta, db-schema-update]
189+
if: ${{ always() }}
144190
steps:
145-
- name: Checkout code
191+
- name: Checkout repo
146192
uses: actions/checkout@v4
193+
with:
194+
# Use the job-level CHECKOUT_REF (already resolves to COMMIT_SHA or 'main')
195+
ref: ${{ needs.resolve-api-meta.outputs.CHECKOUT_REF }}
196+
fetch-depth: 0
147197

148198
- name: Setup python
149199
uses: actions/setup-python@v5
@@ -212,11 +262,11 @@ jobs:
212262
run: echo "PATH=$(realpath sources.csv)" >> $GITHUB_OUTPUT
213263

214264
- name: GTFS - Update Database Content
215-
if: ${{ env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual' }}
265+
if: ${{ !inputs.DRY_RUN && (env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual') }}
216266
run: scripts/populate-db.sh ${{ steps.getpath.outputs.PATH }} > populate.log
217267

218268
- name: GTFS - Upload log file for verification
219-
if: ${{ always() && (env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual') }}
269+
if: ${{ always() && !inputs.DRY_RUN && (env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual') }}
220270
uses: actions/upload-artifact@v4
221271
with:
222272
name: populate-${{ inputs.ENVIRONMENT }}.log
@@ -232,11 +282,11 @@ jobs:
232282
run: echo "PATH=$(realpath systems.csv)" >> $GITHUB_OUTPUT
233283

234284
- name: GBFS - Update Database Content
235-
if: ${{ env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual' }}
285+
if: ${{ !inputs.DRY_RUN && (env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual') }}
236286
run: scripts/populate-db.sh ${{ steps.getsyspath.outputs.PATH }} gbfs >> populate-gbfs.log
237287

238288
- name: GBFS - Upload log file for verification
239-
if: ${{ always() && (env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual') }}
289+
if: ${{ always() && !inputs.DRY_RUN && (env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual') }}
240290
uses: actions/upload-artifact@v4
241291
with:
242292
name: populate-gbfs-${{ inputs.ENVIRONMENT }}.log
@@ -245,7 +295,7 @@ jobs:
245295

246296
update-gcp-secret:
247297
name: Update GCP Secrets
248-
if: ${{ github.event_name == 'repository_dispatch' || github.event_name == 'workflow_dispatch' }}
298+
if: ${{ contains('repository_dispatch,workflow_dispatch', github.event_name) && !inputs.DRY_RUN }}
249299
runs-on: ubuntu-latest
250300
steps:
251301
- name: Authenticate to Google Cloud
@@ -283,6 +333,3 @@ jobs:
283333
echo "Secret $SECRET_NAME does not exist in project $PROJECT_ID, creating..."
284334
echo -n "$SECRET_VALUE" | gcloud secrets create $SECRET_NAME --data-file=- --replication-policy="automatic" --project=$PROJECT_ID
285335
fi
286-
287-
288-

0 commit comments

Comments
 (0)