From 979e1504588175cecbea474369adbae0a095c189 Mon Sep 17 00:00:00 2001 From: jcpitre Date: Tue, 4 Nov 2025 17:38:00 -0500 Subject: [PATCH 1/4] Refactored db-update into separate workflows for catalog update and release. --- .github/actions/notify-slack/action.yml | 40 ++-- .github/actions/resolve-api-meta/action.yml | 80 +++++++ .github/workflows/catalog-update.yml | 142 +++++++++++ .github/workflows/db-update-content.yml | 213 +++++++++++++++++ .github/workflows/db-update-dev.yml | 49 ++-- .github/workflows/db-update-prod.yml | 43 ++-- .github/workflows/db-update-qa.yml | 39 ++-- .github/workflows/db-update-schema.yml | 96 ++++++++ .github/workflows/db-update.yml | 247 +++----------------- 9 files changed, 659 insertions(+), 290 deletions(-) create mode 100644 .github/actions/resolve-api-meta/action.yml create mode 100644 .github/workflows/catalog-update.yml create mode 100644 .github/workflows/db-update-content.yml create mode 100644 .github/workflows/db-update-schema.yml diff --git a/.github/actions/notify-slack/action.yml b/.github/actions/notify-slack/action.yml index bf4c049d5..0de0d6cb5 100644 --- a/.github/actions/notify-slack/action.yml +++ b/.github/actions/notify-slack/action.yml @@ -6,7 +6,7 @@ inputs: required: true type: string PRIORITY: - description: 'Priority level of the alert (medium/high)' + description: 'Priority level of the alert (medium/high/test)' required: true type: string STEP: @@ -31,19 +31,29 @@ runs: id: alert_message shell: bash run: | - if [[ "${{ inputs.PRIORITY }}" == "high" ]]; then - if [[ -n "${{ inputs.STEP }}" ]]; then - message="๐Ÿšจ High Priority Alert: [${{ github.workflow }}] failed at step \"${{ inputs.STEP }}\". Immediate attention is required to avoid production impact." - else - message="๐Ÿšจ High Priority Alert: [${{ github.workflow }}] failed. Immediate attention is required to avoid production impact." - fi - else - if [[ -n "${{ inputs.STEP }}" ]]; then - message="๐Ÿšง Medium Priority Alert: [${{ github.workflow }}] encountered an issue at step \"${{ inputs.STEP }}\". This may affect ongoing integration processes." - else - message="๐Ÿšง Medium Priority Alert: [${{ github.workflow }}] encountered an issue. This may affect ongoing integration processes." - fi - fi + case "${{ inputs.PRIORITY }}" in + high) + if [[ -n "${{ inputs.STEP }}" ]]; then + message="๐Ÿšจ High Priority Alert: [${{ github.workflow }}] failed at step \"${{ inputs.STEP }}\". Immediate attention is required to avoid production impact." + else + message="๐Ÿšจ High Priority Alert: [${{ github.workflow }}] failed. Immediate attention is required to avoid production impact." + fi + ;; + test) + if [[ -n "${{ inputs.STEP }}" ]]; then + message="๐Ÿงช Test Alert: [${{ github.workflow }}] reached step \"${{ inputs.STEP }}\". This is a test notification only." + else + message="๐Ÿงช Test Alert: [${{ github.workflow }}]. This is a test notification only." + fi + ;; + *) + if [[ -n "${{ inputs.STEP }}" ]]; then + message="๐Ÿšง Medium Priority Alert: [${{ github.workflow }}] encountered an issue at step \"${{ inputs.STEP }}\". This may affect ongoing integration processes." + else + message="๐Ÿšง Medium Priority Alert: [${{ github.workflow }}] encountered an issue. This may affect ongoing integration processes." + fi + ;; + esac # Construct the JSON payload and save it to a file jq -n --arg message "$message" --arg run_url "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" '{ @@ -74,5 +84,3 @@ runs: env: SLACK_WEBHOOK_URL: ${{ env.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - - diff --git a/.github/actions/resolve-api-meta/action.yml b/.github/actions/resolve-api-meta/action.yml new file mode 100644 index 000000000..72ef05154 --- /dev/null +++ b/.github/actions/resolve-api-meta/action.yml @@ -0,0 +1,80 @@ +name: Resolve API commit/version +description: Resolve deployed API commit SHA and version from the metadata endpoint +inputs: + api_base_url: + description: Base URL host for the API (e.g. api-dev.mobilitydatabase.org) + required: false + default: api.mobilitydatabase.org + api_refresh_token: + description: API refresh token + required: false +outputs: + COMMIT_SHA: + description: Resolved commit SHA + value: ${{ steps.resolve.outputs.COMMIT_SHA }} + API_VERSION: + description: Resolved API version + value: ${{ steps.resolve.outputs.API_VERSION }} + RESOLVED: + description: Whether a commit SHA was resolved (true/false) + value: ${{ steps.resolve.outputs.RESOLVED }} +runs: + using: composite + steps: + - id: resolve + name: Resolve via API and expose outputs + shell: bash + env: + API_BASE_URL: ${{ inputs.api_base_url }} + API_REFRESH_TOKEN: ${{ inputs.api_refresh_token }} + run: | + # Do not exit on failure; this action should never abort the caller workflow. + set -u + COMMIT_SHA="" + API_VERSION="" + RESOLVED="false" + + if [[ -n "${API_REFRESH_TOKEN:-}" ]]; then + echo "Resolving API commit from https://${API_BASE_URL}/v1/metadata ..." + + # Exchange refresh token -> access token (handle failures gracefully) + REPLY_JSON=$(curl --silent --show-error --location "https://${API_BASE_URL}/v1/tokens" \ + --header 'Content-Type: application/json' \ + --data "{ \"refresh_token\": \"${API_REFRESH_TOKEN}\" }" ) || { + echo "Warning: token exchange failed; will fallback to 'main'" >&2 + REPLY_JSON="" + } + + if [[ -n "${REPLY_JSON}" ]]; then + ACCESS_TOKEN=$(echo "${REPLY_JSON}" | jq -r .access_token 2>/dev/null || echo "") + if [[ -z "${ACCESS_TOKEN}" || "${ACCESS_TOKEN}" == "null" ]]; then + echo "Warning: Could not obtain access token from reply; will fallback to 'main'" >&2 + else + META_JSON=$(curl --silent --show-error \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'accept: application/json' \ + "https://${API_BASE_URL}/v1/metadata" ) || { + echo "Warning: metadata request failed; will fallback to 'main'" >&2 + META_JSON="" + } + + if [[ -n "${META_JSON}" ]]; then + COMMIT_SHA=$(echo "${META_JSON}" | jq -r .commit_hash 2>/dev/null || echo "") + API_VERSION=$(echo "${META_JSON}" | jq -r .version 2>/dev/null || echo "") + if [[ -n "${COMMIT_SHA}" && "${COMMIT_SHA}" != "null" ]]; then + RESOLVED="true" + echo "Resolved API version: ${API_VERSION} (commit ${COMMIT_SHA})" + else + echo "Warning: commit_hash missing in metadata; will fallback to 'main'" >&2 + fi + fi + fi + fi + else + echo "No API refresh token provided; skipping API metadata resolution and falling back to 'main'." + fi + + # Expose outputs (empty COMMIT_SHA and RESOLVED=false indicate fallback to 'main') + echo "COMMIT_SHA=${COMMIT_SHA}" >> "$GITHUB_OUTPUT" + echo "API_VERSION=${API_VERSION}" >> "$GITHUB_OUTPUT" + echo "RESOLVED=${RESOLVED}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/catalog-update.yml b/.github/workflows/catalog-update.yml new file mode 100644 index 000000000..a389eb52b --- /dev/null +++ b/.github/workflows/catalog-update.yml @@ -0,0 +1,142 @@ +# Called when the Mobility Catalog is updated. +name: Mobility Catalog Update +on: + workflow_dispatch: + inputs: + DRY_RUN: + description: Dry run. Skip applying schema and content updates + required: false + default: true + type: boolean + repository_dispatch: # Update on mobility-database-catalog repo dispatch + types: [ catalog-sources-updated, gbfs-systems-updated ] + +env: + python_version: '3.11' + liquibase_version: '4.33.0' + +jobs: + resolve-api-meta-qa: + name: QA Resolve API commit/version + runs-on: ubuntu-latest + outputs: + CHECKOUT_REF: ${{ steps.resolve.outputs.COMMIT_SHA != '' && steps.resolve.outputs.COMMIT_SHA || 'main' }} + steps: + - name: Checkout repo (for scripts and local action) + uses: actions/checkout@v4 + + - name: Resolve API commit/version + id: resolve + uses: ./.github/actions/resolve-api-meta + with: + api_base_url: api-qa.mobilitydatabase.org + api_refresh_token: ${{ secrets.QA_API_TEST_REFRESH_TOKEN }} + + update-content-qa: + name: QA Update DB content + needs: [ resolve-api-meta-qa ] + uses: ./.github/workflows/db-update-content.yml + with: + PROJECT_ID: ${{ vars.QA_MOBILITY_FEEDS_PROJECT_ID }} + REGION: ${{ vars.MOBILITY_FEEDS_REGION }} + DB_NAME: ${{ vars.QA_POSTGRE_SQL_DB_NAME }} + ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }} + DB_ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }} + DRY_RUN: ${{ github.event_name == 'repository_dispatch' || inputs.DRY_RUN }} + CHECKOUT_REF: ${{ needs.resolve-api-meta-qa.outputs.CHECKOUT_REF }} + secrets: + DB_USER_PASSWORD: ${{ secrets.QA_POSTGRE_USER_PASSWORD }} + DB_USER_NAME: ${{ secrets.QA_POSTGRE_USER_NAME }} + DB_GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.QA_GCP_MOBILITY_FEEDS_SA_KEY }} + GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.QA_GCP_MOBILITY_FEEDS_SA_KEY }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }} + POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} + + resolve-api-meta-prod: + name: PROD Resolve API commit/version + runs-on: ubuntu-latest + outputs: + CHECKOUT_REF: ${{ steps.resolve.outputs.COMMIT_SHA != '' && steps.resolve.outputs.COMMIT_SHA || 'main' }} + steps: + - name: Checkout repo (for scripts and local action) + uses: actions/checkout@v4 + + - name: Resolve API commit/version + id: resolve + uses: ./.github/actions/resolve-api-meta + with: + api_base_url: api.mobilitydatabase.org + api_refresh_token: ${{ secrets.PROD_API_TEST_REFRESH_TOKEN }} + + update-content-prod: + name: PROD Update DB content + needs: [ resolve-api-meta-prod ] + uses: ./.github/workflows/db-update-content.yml + with: + PROJECT_ID: ${{ vars.PROD_MOBILITY_FEEDS_PROJECT_ID }} + REGION: ${{ vars.MOBILITY_FEEDS_REGION }} + DB_NAME: ${{ vars.PROD_POSTGRE_SQL_DB_NAME }} + ENVIRONMENT: ${{ vars.PROD_MOBILITY_FEEDS_ENVIRONMENT }} + DB_ENVIRONMENT: ${{ vars.PROD_MOBILITY_FEEDS_ENVIRONMENT }} + DRY_RUN: ${{ github.event_name == 'repository_dispatch' || inputs.DRY_RUN }} + CHECKOUT_REF: ${{ needs.resolve-api-meta-prod.outputs.CHECKOUT_REF }} + secrets: + DB_USER_PASSWORD: ${{ secrets.PROD_POSTGRE_USER_PASSWORD }} + DB_USER_NAME: ${{ secrets.PROD_POSTGRE_USER_NAME }} + DB_GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.PROD_GCP_MOBILITY_FEEDS_SA_KEY }} + GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.PROD_GCP_MOBILITY_FEEDS_SA_KEY }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }} + POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} + + resolve-api-meta-dev: + name: DEV Resolve API commit/version + runs-on: ubuntu-latest + outputs: + CHECKOUT_REF: ${{ steps.resolve.outputs.COMMIT_SHA != '' && steps.resolve.outputs.COMMIT_SHA || 'main' }} + steps: + - name: Checkout repo (for scripts and local action) + uses: actions/checkout@v4 + + - name: Resolve API commit/version + id: resolve + uses: ./.github/actions/resolve-api-meta + with: + api_base_url: api-dev.mobilitydatabase.org + api_refresh_token: ${{ secrets.DEV_API_TEST_REFRESH_TOKEN }} + + update-content-dev: + name: Dev Update DB content + needs: [ resolve-api-meta-dev ] + uses: ./.github/workflows/db-update-content.yml + with: + PROJECT_ID: ${{ vars.DEV_MOBILITY_FEEDS_PROJECT_ID }} + REGION: ${{ vars.MOBILITY_FEEDS_REGION }} + DB_NAME: ${{ vars.DEV_POSTGRE_SQL_DB_NAME }} + ENVIRONMENT: ${{ vars.DEV_MOBILITY_FEEDS_ENVIRONMENT }} + DB_ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }} + DRY_RUN: ${{ github.event_name == 'repository_dispatch' || inputs.DRY_RUN }} + CHECKOUT_REF: ${{ needs.resolve-api-meta-dev.outputs.CHECKOUT_REF }} + secrets: + DB_USER_PASSWORD: ${{ secrets.DEV_POSTGRE_USER_PASSWORD }} + DB_USER_NAME: ${{ secrets.DEV_POSTGRE_USER_NAME }} + GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.DEV_GCP_MOBILITY_FEEDS_SA_KEY }} + DB_GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.QA_GCP_MOBILITY_FEEDS_SA_KEY }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }} + POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} + + notify-slack-on-failure: + # Run after all relevant jobs and notify if any failed + needs: [ resolve-api-meta-qa, update-content-qa, resolve-api-meta-prod, update-content-prod, resolve-api-meta-dev, update-content-dev ] + if: ${{ always() && contains(join(needs.*.result, ','), 'failure') }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Notify Slack + uses: ./.github/actions/notify-slack + with: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + PRIORITY: "high" \ No newline at end of file diff --git a/.github/workflows/db-update-content.yml b/.github/workflows/db-update-content.yml new file mode 100644 index 000000000..84e168d64 --- /dev/null +++ b/.github/workflows/db-update-content.yml @@ -0,0 +1,213 @@ +# Mobility Database Content Update (split from db-update.yml) +name: Database Update - Content +on: + workflow_call: + secrets: + DB_GCP_MOBILITY_FEEDS_SA_KEY: + description: Service account key for the DB project (JSON) + required: true + GCP_MOBILITY_FEEDS_SA_KEY: + description: Service account key for the general GCP project (JSON) + required: true + DB_USER_NAME: + description: PostgreSQL user name + required: true + DB_USER_PASSWORD: + description: PostgreSQL user password + required: true + OP_SERVICE_ACCOUNT_TOKEN: + description: 1Password Service Account token + required: true + POSTGRE_SQL_INSTANCE_NAME: + description: PostgreSQL instance name + required: true + OP_FEEDS_SERVICE_ACCOUNT_TOKEN: + description: OnePassword Service Account Token + required: true + inputs: + PROJECT_ID: + description: GCP Project ID + required: true + type: string + DB_NAME: + description: PostgreSQL Database Name + required: true + type: string + ENVIRONMENT: + description: GCP ENVIRONMENT + required: true + type: string + DB_ENVIRONMENT: + description: GCP ENVIRONMENT where DB is deployed. + required: true + type: string + REGION: + description: GCP region + required: true + type: string + DRY_RUN: + description: Skip applying schema and content updates + required: false + default: false + type: boolean + CHECKOUT_REF: + description: Commit SHA or branch to checkout (provided by caller; defaults to 'main') + required: false + default: main + type: string + +env: + python_version: '3.11' + +jobs: + db-content-update: + name: 'Database Content Update' + permissions: write-all + runs-on: ubuntu-latest + # This job is intended to run for repository_dispatch or workflow_dispatch scenarios. + # The caller should decide when to invoke this reusable workflow. + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + ref: ${{ inputs.CHECKOUT_REF }} + fetch-depth: 0 + + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.python_version }} + + - name: Authenticate to Google Cloud QA/PROD + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.DB_GCP_MOBILITY_FEEDS_SA_KEY }} + + - name: Google Cloud Setup + uses: google-github-actions/setup-gcloud@v2 + + - name: Update .env file + run: | + echo "PGUSER=${{ secrets.DB_USER_NAME }}" > config/.env.local + echo "POSTGRES_USER=${{ secrets.DB_USER_NAME }}" >> config/.env.local + echo "POSTGRES_PASSWORD=${{ secrets.DB_USER_PASSWORD }}" >> config/.env.local + echo "POSTGRES_DB=${{ inputs.DB_NAME }}" >> config/.env.local + echo "FEEDS_DATABASE_URL=postgresql://${{ secrets.DB_USER_NAME }}:${{ secrets.DB_USER_PASSWORD }}@localhost:5432/${{ inputs.DB_NAME }}" >> config/.env.local + echo "POSTGRES_PORT=5432" >> config/.env.local + echo "POSTGRES_HOST=localhost" >> config/.env.local + echo "ENV=${{ inputs.ENVIRONMENT }}" >> config/.env.local + cat config/.env.local + + - name: Load secrets from 1Password + uses: 1password/load-secrets-action@v2.0.0 + with: + export-env: true # Export loaded secrets as environment variables + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + GCP_FEED_SSH_USER: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_FEED_SSH_USER/username" + GCP_FEED_BASTION_NAME: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_FEED_BASTION_NAME/username" + GCP_FEED_BASTION_SSH_KEY: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_FEED_BASTION_SSH_KEY/private key" + + - name: Tunnel + run: | + mkdir -p ~/.ssh + echo "${{ env.GCP_FEED_BASTION_SSH_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ./scripts/tunnel-create.sh -project_id ${{ inputs.PROJECT_ID }} -zone ${{ inputs.REGION }}-a -instance ${{ env.GCP_FEED_BASTION_NAME }}-${{ inputs.DB_ENVIRONMENT}} -target_account ${{ env.GCP_FEED_SSH_USER }} -db_instance ${{ secrets.POSTGRE_SQL_INSTANCE_NAME }} + sleep 10 # Wait for the tunnel to establish + + - name: Install requirements and generate db model + run: scripts/db-gen.sh + + - name: Determine update type + id: update-type + run: | + if [[ "${{ github.event.action }}" == "gbfs-systems-updated" ]]; then + echo "UPDATE_TYPE=gbfs" >> $GITHUB_ENV + elif [[ "${{ github.event.action }}" == "catalog-sources-updated" ]]; then + echo "UPDATE_TYPE=gtfs" >> $GITHUB_ENV + else + echo "UPDATE_TYPE=manual" >> $GITHUB_ENV # fallback for workflow_dispatch + fi + + - name: Download csv version of the database + if: ${{ env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual' }} + run: wget -O sources.csv https://bit.ly/catalogs-csv + + - name: Get full path of sources.csv + if: ${{ env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual' }} + id: getpath + run: echo "PATH=$(realpath sources.csv)" >> $GITHUB_OUTPUT + + - name: GTFS - Update Database Content + if: ${{ !inputs.DRY_RUN && (env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual') }} + run: scripts/populate-db.sh ${{ steps.getpath.outputs.PATH }} > populate.log + + - name: GTFS - Upload log file for verification + if: ${{ always() && !inputs.DRY_RUN && (env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual') }} + uses: actions/upload-artifact@v4 + with: + name: populate-${{ inputs.ENVIRONMENT }}.log + path: populate.log + + - name: Download systems.csv + if: ${{ env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual' }} + run: wget -O systems.csv https://raw.githubusercontent.com/MobilityData/gbfs/master/systems.csv + + - name: Get full path of systems.csv + if: ${{ env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual' }} + id: getsyspath + run: echo "PATH=$(realpath systems.csv)" >> $GITHUB_OUTPUT + + - name: GBFS - Update Database Content + if: ${{ !inputs.DRY_RUN && (env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual') }} + run: scripts/populate-db.sh ${{ steps.getsyspath.outputs.PATH }} gbfs >> populate-gbfs.log + + - name: GBFS - Upload log file for verification + if: ${{ always() && !inputs.DRY_RUN && (env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual') }} + uses: actions/upload-artifact@v4 + with: + name: populate-gbfs-${{ inputs.ENVIRONMENT }}.log + path: populate-gbfs.log + + update-gcp-secret: + name: Update GCP Secrets + runs-on: ubuntu-latest + steps: + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GCP_MOBILITY_FEEDS_SA_KEY }} + + - name: Google Cloud Setup + uses: google-github-actions/setup-gcloud@v2 + + - name: Load secrets from 1Password + id: onepw_secrets + uses: 1password/load-secrets-action@v2.0.0 + with: + export-env: true # Export loaded secrets as environment variables + env: + # This alternate service account token gives access to a vault writable by some third + # party people who can update the list of feeds requiring authorization and their tokens + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }} + JSON_FEEDS_WITH_TOKENS: "op://lijd6lj7lyw7dajea6x3zgf53m/l6sr2cnpjj3cbw3t5amlu7vui4/credential" + + - name: Create or Update Auth Secret + if: ${{ !inputs.DRY_RUN }} + env: + PROJECT_ID: ${{ inputs.PROJECT_ID }} + ENVIRONMENT: ${{ inputs.ENVIRONMENT }} + SECRET_VALUE: ${{ env.JSON_FEEDS_WITH_TOKENS }} + SECRET_NAME: FEEDS_CREDENTIALS + run: | + echo "Processing secret $SECRET_NAME in project $PROJECT_ID..." + + if gcloud secrets describe $SECRET_NAME --project=$PROJECT_ID; then + echo "Secret $SECRET_NAME already exists in project $PROJECT_ID, updating..." + echo -n "$SECRET_VALUE" | gcloud secrets versions add $SECRET_NAME --data-file=- --project=$PROJECT_ID + else + echo "Secret $SECRET_NAME does not exist in project $PROJECT_ID, creating..." + echo -n "$SECRET_VALUE" | gcloud secrets create $SECRET_NAME --data-file=- --replication-policy="automatic" --project=$PROJECT_ID + fi + diff --git a/.github/workflows/db-update-dev.yml b/.github/workflows/db-update-dev.yml index 542671489..6fe4b5893 100644 --- a/.github/workflows/db-update-dev.yml +++ b/.github/workflows/db-update-dev.yml @@ -1,5 +1,5 @@ # Update the Mobility Database Schema -name: Database Update - DEV +name: Database Schema Update - DEV on: push: # Update on merge on master if the changelog file or populate script have been updated branches: @@ -7,36 +7,43 @@ on: paths: - 'liquibase/changelog.xml' - 'api/src/scripts/populate_db*' - repository_dispatch: # Update on mobility-database-catalog repo dispatch - types: [ catalog-sources-updated, gbfs-systems-updated ] - workflow_dispatch: + workflow_dispatch: # Manual trigger + inputs: + DRY_RUN: + description: Dry run. Skip applying schema and content updates + required: false + default: true + type: boolean + workflow_call: + inputs: + DRY_RUN: + description: Dry run. Skip applying schema and content updates + required: false + default: true + type: boolean jobs: + print-event-name: + name: Print event name + runs-on: ubuntu-latest + steps: + - name: Print event name + run: echo "github.event_name=${{ github.event_name }}" + update: uses: ./.github/workflows/db-update.yml with: PROJECT_ID: ${{ vars.DEV_MOBILITY_FEEDS_PROJECT_ID }} REGION: ${{ vars.MOBILITY_FEEDS_REGION }} DB_NAME: ${{ vars.DEV_POSTGRE_SQL_DB_NAME }} - ENVIRONMENT: ${{ vars.DEV_MOBILITY_FEEDS_ENVIRONMENT }} DB_ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }} + # DRY_RUN is passed through directly from inputs + DRY_RUN: ${{ inputs.DRY_RUN }} secrets: DB_USER_PASSWORD: ${{ secrets.DEV_POSTGRE_USER_PASSWORD }} DB_USER_NAME: ${{ secrets.DEV_POSTGRE_USER_NAME }} - DB_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} - GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.DEV_GCP_MOBILITY_FEEDS_SA_KEY }} + POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} + # GCP auth (DB project service account) DB_GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.QA_GCP_MOBILITY_FEEDS_SA_KEY }} + # 1Password tokens OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }} - POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} - notify-slack-on-failure: - needs: [ update ] - if: always() && (needs.update.result == 'failure') && (github.event_name == 'repository_dispatch') - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Notify Slack - uses: ./.github/actions/notify-slack - with: - OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - PRIORITY: "high" + diff --git a/.github/workflows/db-update-prod.yml b/.github/workflows/db-update-prod.yml index cfeff46cb..12a10e6ef 100644 --- a/.github/workflows/db-update-prod.yml +++ b/.github/workflows/db-update-prod.yml @@ -1,10 +1,21 @@ # Update the Mobility Database Schema -name: Database Update - PROD +name: Database Update - Prod on: - workflow_dispatch: + workflow_dispatch: # Manual trigger + inputs: + DRY_RUN: + description: Dry run. Skip applying schema and content updates + required: false + default: true + type: boolean workflow_call: - repository_dispatch: # Update on mobility-database-catalog repo dispatch - types: [ catalog-sources-updated, gbfs-systems-updated ] + inputs: + DRY_RUN: + description: Dry run. Skip applying schema and content updates + required: false + default: true + type: boolean + jobs: update: uses: ./.github/workflows/db-update.yml @@ -12,27 +23,15 @@ jobs: PROJECT_ID: ${{ vars.PROD_MOBILITY_FEEDS_PROJECT_ID }} REGION: ${{ vars.MOBILITY_FEEDS_REGION }} DB_NAME: ${{ vars.PROD_POSTGRE_SQL_DB_NAME }} - ENVIRONMENT: ${{ vars.PROD_MOBILITY_FEEDS_ENVIRONMENT }} DB_ENVIRONMENT: ${{ vars.PROD_MOBILITY_FEEDS_ENVIRONMENT }} + # DRY_RUN is passed through directly from inputs + DRY_RUN: ${{ inputs.DRY_RUN }} secrets: + # DB auth and connectivity DB_USER_PASSWORD: ${{ secrets.PROD_POSTGRE_USER_PASSWORD }} DB_USER_NAME: ${{ secrets.PROD_POSTGRE_USER_NAME }} - DB_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} - GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.PROD_GCP_MOBILITY_FEEDS_SA_KEY }} + POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} + # GCP auth (DB project service account) DB_GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.PROD_GCP_MOBILITY_FEEDS_SA_KEY }} + # 1Password tokens OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }} - POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} - - notify-slack-on-failure: - needs: [ update ] - if: always() && (needs.update.result == 'failure') && (github.event_name == 'repository_dispatch') - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Notify Slack - uses: ./.github/actions/notify-slack - with: - OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - PRIORITY: "high" diff --git a/.github/workflows/db-update-qa.yml b/.github/workflows/db-update-qa.yml index 62262e68c..b36167cc9 100644 --- a/.github/workflows/db-update-qa.yml +++ b/.github/workflows/db-update-qa.yml @@ -1,10 +1,20 @@ # Update the Mobility Database Schema name: Database Update - QA on: - workflow_dispatch: + workflow_dispatch: # Manual trigger + inputs: + DRY_RUN: + description: Dry run. Skip applying schema and content updates + required: false + default: true + type: boolean workflow_call: - repository_dispatch: # Update on mobility-database-catalog repo dispatch - types: [ catalog-sources-updated, gbfs-systems-updated ] + inputs: + DRY_RUN: + description: Dry run. Skip applying schema and content updates + required: false + default: true + type: boolean jobs: update: @@ -13,26 +23,15 @@ jobs: PROJECT_ID: ${{ vars.QA_MOBILITY_FEEDS_PROJECT_ID }} REGION: ${{ vars.MOBILITY_FEEDS_REGION }} DB_NAME: ${{ vars.QA_POSTGRE_SQL_DB_NAME }} - ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }} DB_ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }} + # DRY_RUN is passed through directly from inputs + DRY_RUN: ${{ inputs.DRY_RUN }} secrets: + # DB auth and connectivity DB_USER_PASSWORD: ${{ secrets.QA_POSTGRE_USER_PASSWORD }} DB_USER_NAME: ${{ secrets.QA_POSTGRE_USER_NAME }} - DB_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} - GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.QA_GCP_MOBILITY_FEEDS_SA_KEY }} + POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} + # GCP auth (DB project service account) DB_GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.QA_GCP_MOBILITY_FEEDS_SA_KEY }} + # 1Password tokens OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }} - POSTGRE_SQL_INSTANCE_NAME: ${{ secrets.DB_INSTANCE_NAME }} - notify-slack-on-failure: - needs: [ update ] - if: always() && (needs.update.result == 'failure') && (github.event_name == 'repository_dispatch') - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Notify Slack - uses: ./.github/actions/notify-slack - with: - OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - PRIORITY: "high" \ No newline at end of file diff --git a/.github/workflows/db-update-schema.yml b/.github/workflows/db-update-schema.yml new file mode 100644 index 000000000..dc0c41de7 --- /dev/null +++ b/.github/workflows/db-update-schema.yml @@ -0,0 +1,96 @@ +# Mobility Database Schema Update (split from db-update.yml) +name: Database Update - Schema +on: + workflow_call: + inputs: + PROJECT_ID: # + description: GCP Project ID + required: true + type: string + DB_NAME: # + description: PostgreSQL Database Name + required: true + type: string + DB_ENVIRONMENT: # + description: GCP ENVIRONMENT where DB is deployed. + required: true + type: string + REGION: # + description: GCP region + required: true + type: string + DRY_RUN: # + description: Skip applying schema and content updates + required: false + default: false + type: boolean + +env: + python_version: '3.11' + liquibase_version: '4.33.0' + +jobs: + db-schema-update: + name: 'Database Schema Update' + permissions: write-all + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + ref: main + + - name: Authenticate to Google Cloud QA/PROD + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.DB_GCP_MOBILITY_FEEDS_SA_KEY }} + + - name: Google Cloud Setup + uses: google-github-actions/setup-gcloud@v2 + + - name: Load secrets from 1Password + uses: 1password/load-secrets-action@v2.0.0 + with: + export-env: true # Export loaded secrets as environment variables + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + GCP_FEED_SSH_USER: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_FEED_SSH_USER/username" + GCP_FEED_BASTION_NAME: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_FEED_BASTION_NAME/username" + GCP_FEED_BASTION_SSH_KEY: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_FEED_BASTION_SSH_KEY/private key" + + - name: Tunnel + run: | + mkdir -p ~/.ssh + echo "${{ env.GCP_FEED_BASTION_SSH_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ./scripts/tunnel-create.sh -project_id ${{ inputs.PROJECT_ID }} -zone ${{ inputs.REGION }}-a -instance ${{ env.GCP_FEED_BASTION_NAME }}-${{ inputs.DB_ENVIRONMENT}} -target_account ${{ env.GCP_FEED_SSH_USER }} -db_instance ${{ secrets.POSTGRE_SQL_INSTANCE_NAME }} + sleep 10 # Wait for the tunnel to establish + + - name: Test Database Connection Through Tunnel + run: | + sudo apt-get update && sudo apt-get install -y postgresql-client + PGPASSWORD=${{ secrets.DB_USER_PASSWORD }} psql -h localhost -p 5432 -U ${{ secrets.DB_USER_NAME }} -d ${{ inputs.DB_NAME }} -c "SELECT version();" + + - name: Install Liquibase + env: + LIQUIBASE_VERSION: ${{ env.liquibase_version }} + run: | + curl -sSL https://github.com/liquibase/liquibase/releases/download/v${LIQUIBASE_VERSION}/liquibase-${LIQUIBASE_VERSION}.tar.gz -o liquibase.tar.gz + rm -rf liquibase-dist + mkdir liquibase-dist + tar -xzf liquibase.tar.gz -C liquibase-dist + sudo rm -rf /usr/local/liquibase + sudo mv liquibase-dist /usr/local/liquibase + sudo ln -sf /usr/local/liquibase/liquibase /usr/local/bin/liquibase + liquibase --version + + - name: Run Liquibase + if: ${{ !inputs.DRY_RUN }} + working-directory: ${{ github.workspace }}/liquibase + run: | + export LIQUIBASE_COMMAND_CHANGELOG_FILE="changelog.xml" + export LIQUIBASE_COMMAND_URL=jdbc:postgresql://localhost:5432/${{ inputs.DB_NAME }} + export LIQUIBASE_COMMAND_USERNAME=${{ secrets.DB_USER_NAME }} + export LIQUIBASE_COMMAND_PASSWORD=${{ secrets.DB_USER_PASSWORD }} + export LIQUIBASE_LOG_LEVEL=FINE + liquibase update diff --git a/.github/workflows/db-update.yml b/.github/workflows/db-update.yml index 3ce05c37a..fda05eca4 100644 --- a/.github/workflows/db-update.yml +++ b/.github/workflows/db-update.yml @@ -1,72 +1,45 @@ -# Mobility Database Update -# -# This GitHub Action manages the Mobility Database by handling both schema and content updates in different scenarios. -# It's designed to operate under the following conditions: -# -# 1. Database Schema Update (Job: db-schema-update): -# - Triggered by either a 'push' to the main branch or a 'workflow_dispatch' event. -# - Responsible for updating the database schema using Liquibase. -# -# 2. Database Content Update (Job: db-content-update): -# - Executed on 'repository_dispatch' or 'workflow_dispatch' events. -# - Focuses on updating the content of the database. -# - Dependent on the completion of the Database Schema Update job. -# - Utilizes scripts to install requirements, generate the database model, and populate the database with new content. -# -# 3. Update GCP Secrets (Job: update-gcp-secret): -# - Runs on 'repository_dispatch' or 'workflow_dispatch' events. -# - Loads secrets from OnePassword. -# - Dynamically updates GCP secrets based on the environment (dev, qa, prod). - -name: Database Update +# Update the Mobility Database Schema +name: Database Schema Update on: workflow_call: + inputs: + DRY_RUN: # + description: Dry run. Skip applying schema and content updates + required: false + default: true + type: boolean + PROJECT_ID: # + description: GCP Project ID (forwarded to child workflows) + required: false + type: string + REGION: # + description: GCP region (forwarded to child workflows) + required: false + type: string + DB_NAME: # + description: PostgreSQL Database Name (forwarded to child workflows) + required: false + type: string + DB_ENVIRONMENT: # + description: Environment where DB is deployed (forwarded to child workflows) + required: false + type: string secrets: - GCP_MOBILITY_FEEDS_SA_KEY: - description: Service account key - required: true - DB_GCP_MOBILITY_FEEDS_SA_KEY: - description: Service account key where the DB in deployed - required: true - DB_USER_NAME: - description: PostgreSQL User Name - required: true - DB_USER_PASSWORD: + DB_USER_PASSWORD: # description: PostgreSQL User Password required: true - DB_INSTANCE_NAME: - description: PostgreSQL Database Instance Name - required: true - OP_SERVICE_ACCOUNT_TOKEN: - description: OnePassword Service Account Token - required: true - OP_FEEDS_SERVICE_ACCOUNT_TOKEN: - description: OnePassword Service Account Token + DB_USER_NAME: # + description: PostgreSQL User Name required: true - POSTGRE_SQL_INSTANCE_NAME: + POSTGRE_SQL_INSTANCE_NAME: # description: PostgreSQL Instance Name required: true - inputs: - PROJECT_ID: - description: GCP Project ID - required: true - type: string - DB_NAME: - description: PostgreSQL Database Name - required: true - type: string - ENVIRONMENT: - description: GCP ENVIRONMENT - required: true - type: string - DB_ENVIRONMENT: - description: GCP ENVIRONMENT where DB is deployed. + DB_GCP_MOBILITY_FEEDS_SA_KEY: # + description: Service account key for DB environment required: true - type: string - REGION: - description: GCP region + OP_SERVICE_ACCOUNT_TOKEN: # + description: 1Password Service Account Token required: true - type: string env: python_version: '3.11' @@ -78,8 +51,10 @@ jobs: permissions: write-all runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout repo uses: actions/checkout@v4 + with: + ref: main - name: Authenticate to Google Cloud QA/PROD uses: google-github-actions/auth@v2 @@ -126,6 +101,7 @@ jobs: liquibase --version - name: Run Liquibase + if: ${{ !inputs.DRY_RUN }} working-directory: ${{ github.workspace }}/liquibase run: | export LIQUIBASE_COMMAND_CHANGELOG_FILE="changelog.xml" @@ -135,154 +111,3 @@ jobs: export LIQUIBASE_LOG_LEVEL=FINE liquibase update - db-content-update: - name: 'Database Content Update' - permissions: write-all - runs-on: ubuntu-latest - needs: db-schema-update - if: ${{ github.event_name == 'repository_dispatch' || github.event_name == 'workflow_dispatch' }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.python_version }} - - - name: Authenticate to Google Cloud QA/PROD - uses: google-github-actions/auth@v2 - with: - credentials_json: ${{ secrets.DB_GCP_MOBILITY_FEEDS_SA_KEY }} - - - name: Google Cloud Setup - uses: google-github-actions/setup-gcloud@v2 - - - name: Update .env file - run: | - echo "PGUSER=${{ secrets.DB_USER_NAME }}" > config/.env.local - echo "POSTGRES_USER=${{ secrets.DB_USER_NAME }}" >> config/.env.local - echo "POSTGRES_PASSWORD=${{ secrets.DB_USER_PASSWORD }}" >> config/.env.local - echo "POSTGRES_DB=${{ inputs.DB_NAME }}" >> config/.env.local - echo "FEEDS_DATABASE_URL=postgresql://${{ secrets.DB_USER_NAME }}:${{ secrets.DB_USER_PASSWORD }}@localhost:5432/${{ inputs.DB_NAME }}" >> config/.env.local - echo "POSTGRES_PORT=5432" >> config/.env.local - echo "POSTGRES_HOST=localhost" >> config/.env.local - echo "ENV=${{ inputs.ENVIRONMENT }}" >> config/.env.local - cat config/.env.local - - - name: Load secrets from 1Password - uses: 1password/load-secrets-action@v2.0.0 - with: - export-env: true # Export loaded secrets as environment variables - env: - OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - GCP_FEED_SSH_USER: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_FEED_SSH_USER/username" - GCP_FEED_BASTION_NAME: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_FEED_BASTION_NAME/username" - GCP_FEED_BASTION_SSH_KEY: "op://rbiv7rvkkrsdlpcrz3bmv7nmcu/GCP_FEED_BASTION_SSH_KEY/private key" - - - name: Tunnel - run: | - mkdir -p ~/.ssh - echo "${{ env.GCP_FEED_BASTION_SSH_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ./scripts/tunnel-create.sh -project_id ${{ inputs.PROJECT_ID }} -zone ${{ inputs.REGION }}-a -instance ${{ env.GCP_FEED_BASTION_NAME }}-${{ inputs.DB_ENVIRONMENT}} -target_account ${{ env.GCP_FEED_SSH_USER }} -db_instance ${{ secrets.POSTGRE_SQL_INSTANCE_NAME }} - sleep 10 # Wait for the tunnel to establish - - - name: Install requirements and generate db model - run: scripts/db-gen.sh - - - name: Determine update type - id: update-type - run: | - if [[ "${{ github.event.action }}" == "gbfs-systems-updated" ]]; then - echo "UPDATE_TYPE=gbfs" >> $GITHUB_ENV - elif [[ "${{ github.event.action }}" == "catalog-sources-updated" ]]; then - echo "UPDATE_TYPE=gtfs" >> $GITHUB_ENV - else - echo "UPDATE_TYPE=manual" >> $GITHUB_ENV # fallback for workflow_dispatch - fi - - - name: Download csv version of the database - if: ${{ env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual' }} - run: wget -O sources.csv https://bit.ly/catalogs-csv - - - name: Get full path of sources.csv - if: ${{ env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual' }} - id: getpath - run: echo "PATH=$(realpath sources.csv)" >> $GITHUB_OUTPUT - - - name: GTFS - Update Database Content - if: ${{ env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual' }} - run: scripts/populate-db.sh ${{ steps.getpath.outputs.PATH }} > populate.log - - - name: GTFS - Upload log file for verification - if: ${{ always() && (env.UPDATE_TYPE == 'gtfs' || env.UPDATE_TYPE == 'manual') }} - uses: actions/upload-artifact@v4 - with: - name: populate-${{ inputs.ENVIRONMENT }}.log - path: populate.log - - - name: Download systems.csv - if: ${{ env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual' }} - run: wget -O systems.csv https://raw.githubusercontent.com/MobilityData/gbfs/master/systems.csv - - - name: Get full path of systems.csv - if: ${{ env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual' }} - id: getsyspath - run: echo "PATH=$(realpath systems.csv)" >> $GITHUB_OUTPUT - - - name: GBFS - Update Database Content - if: ${{ env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual' }} - run: scripts/populate-db.sh ${{ steps.getsyspath.outputs.PATH }} gbfs >> populate-gbfs.log - - - name: GBFS - Upload log file for verification - if: ${{ always() && (env.UPDATE_TYPE == 'gbfs' || env.UPDATE_TYPE == 'manual') }} - uses: actions/upload-artifact@v4 - with: - name: populate-gbfs-${{ inputs.ENVIRONMENT }}.log - path: populate-gbfs.log - - - update-gcp-secret: - name: Update GCP Secrets - if: ${{ github.event_name == 'repository_dispatch' || github.event_name == 'workflow_dispatch' }} - runs-on: ubuntu-latest - steps: - - name: Authenticate to Google Cloud - uses: google-github-actions/auth@v2 - with: - credentials_json: ${{ secrets.GCP_MOBILITY_FEEDS_SA_KEY }} - - - name: Google Cloud Setup - uses: google-github-actions/setup-gcloud@v2 - - - name: Load secrets from 1Password - id: onepw_secrets - uses: 1password/load-secrets-action@v2.0.0 - with: - export-env: true # Export loaded secrets as environment variables - env: - # This alternate service account token gives access to a vault writable by some third - # party people who can update the list of feeds requiring authorization and their tokens - OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }} - JSON_FEEDS_WITH_TOKENS: "op://lijd6lj7lyw7dajea6x3zgf53m/l6sr2cnpjj3cbw3t5amlu7vui4/credential" - - - name: Create or Update Auth Secret - env: - PROJECT_ID: ${{ inputs.PROJECT_ID }} - ENVIRONMENT: ${{ inputs.ENVIRONMENT }} - SECRET_VALUE: ${{ env.JSON_FEEDS_WITH_TOKENS }} - SECRET_NAME: FEEDS_CREDENTIALS - run: | - echo "Processing secret $SECRET_NAME in project $PROJECT_ID..." - - if gcloud secrets describe $SECRET_NAME --project=$PROJECT_ID; then - echo "Secret $SECRET_NAME already exists in project $PROJECT_ID, updating..." - echo -n "$SECRET_VALUE" | gcloud secrets versions add $SECRET_NAME --data-file=- --project=$PROJECT_ID - else - echo "Secret $SECRET_NAME does not exist in project $PROJECT_ID, creating..." - echo -n "$SECRET_VALUE" | gcloud secrets create $SECRET_NAME --data-file=- --replication-policy="automatic" --project=$PROJECT_ID - fi - - - From e3010d29b5c1c74bf26dc706f193931e2e1334ab Mon Sep 17 00:00:00 2001 From: jcpitre Date: Tue, 4 Nov 2025 17:57:27 -0500 Subject: [PATCH 2/4] Added comment --- .github/workflows/catalog-update.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/catalog-update.yml b/.github/workflows/catalog-update.yml index a389eb52b..192e404cf 100644 --- a/.github/workflows/catalog-update.yml +++ b/.github/workflows/catalog-update.yml @@ -115,6 +115,7 @@ jobs: REGION: ${{ vars.MOBILITY_FEEDS_REGION }} DB_NAME: ${{ vars.DEV_POSTGRE_SQL_DB_NAME }} ENVIRONMENT: ${{ vars.DEV_MOBILITY_FEEDS_ENVIRONMENT }} + # dev uses the QA sql instance DB_ENVIRONMENT: ${{ vars.QA_MOBILITY_FEEDS_ENVIRONMENT }} DRY_RUN: ${{ github.event_name == 'repository_dispatch' || inputs.DRY_RUN }} CHECKOUT_REF: ${{ needs.resolve-api-meta-dev.outputs.CHECKOUT_REF }} @@ -122,6 +123,7 @@ jobs: DB_USER_PASSWORD: ${{ secrets.DEV_POSTGRE_USER_PASSWORD }} DB_USER_NAME: ${{ secrets.DEV_POSTGRE_USER_NAME }} GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.DEV_GCP_MOBILITY_FEEDS_SA_KEY }} + # dev uses the QA sql instance DB_GCP_MOBILITY_FEEDS_SA_KEY: ${{ secrets.QA_GCP_MOBILITY_FEEDS_SA_KEY }} OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} OP_FEEDS_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_FEEDS_SERVICE_ACCOUNT_TOKEN }} From d02872455d8cc54e00d2bc0d550bed52013ed06a Mon Sep 17 00:00:00 2001 From: jcpitre Date: Tue, 4 Nov 2025 18:12:32 -0500 Subject: [PATCH 3/4] Set the default DRY_RUN to false in the db-update workflows. --- .github/workflows/db-update-dev.yml | 2 +- .github/workflows/db-update-prod.yml | 2 +- .github/workflows/db-update-qa.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/db-update-dev.yml b/.github/workflows/db-update-dev.yml index 6fe4b5893..1f6612fca 100644 --- a/.github/workflows/db-update-dev.yml +++ b/.github/workflows/db-update-dev.yml @@ -19,7 +19,7 @@ on: DRY_RUN: description: Dry run. Skip applying schema and content updates required: false - default: true + default: false type: boolean jobs: print-event-name: diff --git a/.github/workflows/db-update-prod.yml b/.github/workflows/db-update-prod.yml index 12a10e6ef..286b94e81 100644 --- a/.github/workflows/db-update-prod.yml +++ b/.github/workflows/db-update-prod.yml @@ -13,7 +13,7 @@ on: DRY_RUN: description: Dry run. Skip applying schema and content updates required: false - default: true + default: false type: boolean jobs: diff --git a/.github/workflows/db-update-qa.yml b/.github/workflows/db-update-qa.yml index b36167cc9..1605cac2a 100644 --- a/.github/workflows/db-update-qa.yml +++ b/.github/workflows/db-update-qa.yml @@ -13,7 +13,7 @@ on: DRY_RUN: description: Dry run. Skip applying schema and content updates required: false - default: true + default: false type: boolean jobs: From 788847be23e598634e2910ddcdbaa0bce10461ff Mon Sep 17 00:00:00 2001 From: jcpitre Date: Wed, 5 Nov 2025 12:29:53 -0500 Subject: [PATCH 4/4] Removed a useless print --- .github/workflows/db-update-dev.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/db-update-dev.yml b/.github/workflows/db-update-dev.yml index 1f6612fca..c9ebfbbef 100644 --- a/.github/workflows/db-update-dev.yml +++ b/.github/workflows/db-update-dev.yml @@ -22,13 +22,6 @@ on: default: false type: boolean jobs: - print-event-name: - name: Print event name - runs-on: ubuntu-latest - steps: - - name: Print event name - run: echo "github.event_name=${{ github.event_name }}" - update: uses: ./.github/workflows/db-update.yml with: