diff --git a/.github/workflows/invoke-cloud-run.yml b/.github/workflows/invoke-cloud-run.yml index abee9d6..c71b6c8 100644 --- a/.github/workflows/invoke-cloud-run.yml +++ b/.github/workflows/invoke-cloud-run.yml @@ -18,6 +18,7 @@ env: GCP_PROJECT_ID: firstradequant GCP_WORKLOAD_IDENTITY_PROVIDER: projects/1088907247379/locations/global/workloadIdentityPools/github-actions/providers/github-main GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: firstrade-platform-deploy@firstradequant.iam.gserviceaccount.com + GCP_SCHEDULER_SERVICE_ACCOUNT: firstrade-platform-scheduler@firstradequant.iam.gserviceaccount.com concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} @@ -123,6 +124,45 @@ jobs: scheduler_job="" scheduler_location="" scheduler_expected_path="" + ensure_invoke_bridge_job() { + local bridge_job="$1" + local bridge_uri="$2" + local bridge_location="$3" + if gcloud scheduler jobs describe "${bridge_job}" \ + --project="${GCP_PROJECT_ID}" \ + --location="${bridge_location}" >/dev/null 2>&1; then + current_uri="$( + gcloud scheduler jobs describe "${bridge_job}" \ + --project="${GCP_PROJECT_ID}" \ + --location="${bridge_location}" \ + --format='value(httpTarget.uri)' 2>/dev/null || true + )" + if [ -n "${current_uri}" ] && [ "${current_uri}" != "${bridge_uri}" ]; then + echo "Updating invoke-bridge Cloud Scheduler job ${bridge_job} to ${bridge_uri}." + gcloud scheduler jobs update http "${bridge_job}" \ + --project="${GCP_PROJECT_ID}" \ + --location="${bridge_location}" \ + --uri="${bridge_uri}" \ + --http-method=POST \ + --oidc-service-account-email="${GCP_SCHEDULER_SERVICE_ACCOUNT}" \ + --oidc-token-audience="${service_url}" \ + --quiet + fi + return 0 + fi + echo "Creating invoke-bridge Cloud Scheduler job ${bridge_job} -> ${bridge_uri}." + gcloud scheduler jobs create http "${bridge_job}" \ + --project="${GCP_PROJECT_ID}" \ + --location="${bridge_location}" \ + --uri="${bridge_uri}" \ + --http-method=POST \ + --oidc-service-account-email="${GCP_SCHEDULER_SERVICE_ACCOUNT}" \ + --oidc-token-audience="${service_url}" \ + --schedule="0 0 1 1 *" \ + --time-zone="UTC" \ + --attempt-deadline=600s \ + --quiet + } if [ "${service_ingress}" = "internal" ]; then scheduler_location="${CLOUD_SCHEDULER_LOCATION:-${CLOUD_RUN_REGION}}" case "${raw_path}" in @@ -133,10 +173,12 @@ jobs: /probe) scheduler_job="${CLOUD_RUN_SERVICE}-probe-scheduler" scheduler_expected_path="/probe" + ensure_invoke_bridge_job "${scheduler_job}" "${service_url}/probe" "${scheduler_location}" ;; /dry-run) scheduler_job="${CLOUD_RUN_SERVICE}-precheck-scheduler" scheduler_expected_path="/dry-run" + ensure_invoke_bridge_job "${scheduler_job}" "${service_url}/dry-run" "${scheduler_location}" ;; *) echo "Cloud Run service ${CLOUD_RUN_SERVICE} has internal ingress, so GitHub-hosted runners cannot curl ${raw_path} directly." >&2 @@ -147,6 +189,7 @@ jobs: scheduler_uri="$( gcloud scheduler jobs describe "${scheduler_job}" \ + --project="${GCP_PROJECT_ID}" \ --location="${scheduler_location}" \ --format='value(httpTarget.uri)' 2>/dev/null || true )" diff --git a/.github/workflows/sync-cloud-run-env.yml b/.github/workflows/sync-cloud-run-env.yml index 25e03ba..f3441cb 100644 --- a/.github/workflows/sync-cloud-run-env.yml +++ b/.github/workflows/sync-cloud-run-env.yml @@ -861,15 +861,52 @@ jobs: --quiet fi + invoke_bridge_jobs=( + "${CLOUD_RUN_SERVICE}-probe-scheduler|${service_url}/probe" + "${CLOUD_RUN_SERVICE}-precheck-scheduler|${service_url}/dry-run" + ) + if [[ "${CLOUD_RUN_SERVICE}" == *-service ]]; then + invoke_bridge_jobs+=( + "${CLOUD_RUN_SERVICE%-service}-probe-scheduler|${service_url}/probe" + "${CLOUD_RUN_SERVICE%-service}-precheck-scheduler|${service_url}/dry-run" + ) + fi + for bridge_entry in "${invoke_bridge_jobs[@]}"; do + bridge_job="${bridge_entry%%|*}" + bridge_uri="${bridge_entry#*|}" + if gcloud scheduler jobs describe "${bridge_job}" \ + --project="${GCP_PROJECT_ID}" \ + --location="${scheduler_location}" >/dev/null 2>&1; then + echo "Updating invoke-bridge Cloud Scheduler job ${bridge_job} to ${bridge_uri}." + gcloud scheduler jobs update http "${bridge_job}" \ + --project="${GCP_PROJECT_ID}" \ + --location="${scheduler_location}" \ + --uri="${bridge_uri}" \ + --http-method=POST \ + --oidc-service-account-email="${GCP_SCHEDULER_SERVICE_ACCOUNT}" \ + --oidc-token-audience="${service_url}" \ + --quiet + else + echo "Creating invoke-bridge Cloud Scheduler job ${bridge_job} at ${bridge_uri}." + gcloud scheduler jobs create http "${bridge_job}" \ + --project="${GCP_PROJECT_ID}" \ + --location="${scheduler_location}" \ + --uri="${bridge_uri}" \ + --http-method=POST \ + --oidc-service-account-email="${GCP_SCHEDULER_SERVICE_ACCOUNT}" \ + --oidc-token-audience="${service_url}" \ + --schedule="0 0 1 1 *" \ + --time-zone="UTC" \ + --attempt-deadline=600s \ + --quiet + fi + done + legacy_jobs=( - "${CLOUD_RUN_SERVICE}-probe-scheduler" - "${CLOUD_RUN_SERVICE}-precheck-scheduler" "${CLOUD_RUN_SERVICE}-session-check-scheduler" ) if [[ "${CLOUD_RUN_SERVICE}" == *-service ]]; then legacy_jobs+=( - "${CLOUD_RUN_SERVICE%-service}-probe-scheduler" - "${CLOUD_RUN_SERVICE%-service}-precheck-scheduler" "${CLOUD_RUN_SERVICE%-service}-session-check-scheduler" ) fi @@ -877,7 +914,7 @@ jobs: if gcloud scheduler jobs describe "${legacy_job}" \ --project="${GCP_PROJECT_ID}" \ --location="${scheduler_location}" >/dev/null 2>&1; then - echo "Deleting legacy Cloud Scheduler job ${legacy_job}; monitor dispatcher now owns probe/precheck." + echo "Deleting legacy Cloud Scheduler job ${legacy_job}; session checks are handled elsewhere." gcloud scheduler jobs delete "${legacy_job}" \ --project="${GCP_PROJECT_ID}" \ --location="${scheduler_location}" \ diff --git a/tests/test_invoke_cloud_run_workflow.py b/tests/test_invoke_cloud_run_workflow.py new file mode 100644 index 0000000..fa2edb1 --- /dev/null +++ b/tests/test_invoke_cloud_run_workflow.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from pathlib import Path + + +def test_invoke_cloud_run_workflow_ensures_probe_and_dry_run_scheduler_bridges(): + workflow_path = Path(__file__).resolve().parents[1] / ".github/workflows/invoke-cloud-run.yml" + workflow = workflow_path.read_text(encoding="utf-8") + + assert "GCP_SCHEDULER_SERVICE_ACCOUNT: firstrade-platform-scheduler@firstradequant.iam.gserviceaccount.com" in workflow + assert "ensure_invoke_bridge_job()" in workflow + assert 'scheduler_job="${CLOUD_RUN_SERVICE}-probe-scheduler"' in workflow + assert 'scheduler_job="${CLOUD_RUN_SERVICE}-precheck-scheduler"' in workflow + assert 'ensure_invoke_bridge_job "${scheduler_job}" "${service_url}/probe"' in workflow + assert 'ensure_invoke_bridge_job "${scheduler_job}" "${service_url}/dry-run"' in workflow + assert '--schedule="0 0 1 1 *"' in workflow diff --git a/tests/test_sync_cloud_run_env_workflow.py b/tests/test_sync_cloud_run_env_workflow.py index 973ee64..38d68f3 100644 --- a/tests/test_sync_cloud_run_env_workflow.py +++ b/tests/test_sync_cloud_run_env_workflow.py @@ -157,6 +157,10 @@ def test_sync_cloud_run_env_workflow_syncs_scheduler_from_runtime_target(): assert 'gcloud scheduler jobs create http "${job_name}"' in workflow assert 'monitor_job_name="firstrade-monitor-dispatcher-scheduler"' in workflow assert 'monitor_uri="${service_url}/monitor-dispatch"' in workflow + assert 'invoke_bridge_jobs=(' in workflow + assert '"${CLOUD_RUN_SERVICE}-probe-scheduler|${service_url}/probe"' in workflow + assert '"${CLOUD_RUN_SERVICE}-precheck-scheduler|${service_url}/dry-run"' in workflow + assert 'Creating invoke-bridge Cloud Scheduler job ${bridge_job} at ${bridge_uri}.' in workflow assert '"${CLOUD_RUN_SERVICE}-session-check-scheduler"' in workflow assert 'gcloud scheduler jobs delete "${legacy_job}"' in workflow assert '--schedule="${desired_schedule}"' in workflow