1111 GCP_WORKLOAD_IDENTITY_PROVIDER : projects/1088907247379/locations/global/workloadIdentityPools/github-actions/providers/github-main
1212 GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT : firstrade-platform-deploy@firstradequant.iam.gserviceaccount.com
1313 GCP_RUNTIME_SERVICE_ACCOUNT : firstrade-platform-runtime@firstradequant.iam.gserviceaccount.com
14+ GCP_SCHEDULER_SERVICE_ACCOUNT : firstrade-platform-scheduler@firstradequant.iam.gserviceaccount.com
1415 GCP_ARTIFACT_REGISTRY_HOSTNAME : us-central1-docker.pkg.dev
1516 GCP_ARTIFACT_REGISTRY_REPOSITORY : cloud-run-source-deploy
1617
@@ -587,6 +588,33 @@ jobs:
587588 add_optional_secret FIRSTRADE_MFA_PHONE FIRSTRADE_MFA_PHONE_SECRET_NAME FIRSTRADE_MFA_PHONE
588589 add_optional_secret FIRSTRADE_MFA_CODE FIRSTRADE_MFA_CODE_SECRET_NAME FIRSTRADE_MFA_CODE
589590
591+ service_url="$(gcloud run services describe "${CLOUD_RUN_SERVICE}" \
592+ --project="${GCP_PROJECT_ID}" \
593+ --region="${CLOUD_RUN_REGION}" \
594+ --format='value(status.url)' 2>/dev/null || true)"
595+ if [ -z "${service_url}" ]; then
596+ echo "Unable to resolve Cloud Run service URL for ${CLOUD_RUN_SERVICE}; cannot sync monitor dispatcher targets." >&2
597+ exit 1
598+ fi
599+ monitor_targets_json="$(
600+ SERVICE_URL="${service_url}" python - <<'PY'
601+ import json
602+ import os
603+
604+ runtime_target = json.loads(os.environ.get("RUNTIME_TARGET_JSON") or "{}")
605+ target = {
606+ "service_name": os.environ.get("CLOUD_RUN_SERVICE"),
607+ "service_url": os.environ.get("SERVICE_URL"),
608+ "strategy_profile": runtime_target.get("strategy_profile"),
609+ "account_scope": runtime_target.get("account_scope"),
610+ "runtime_target_enabled": os.environ.get("RUNTIME_TARGET_ENABLED", "true"),
611+ "scheduler": runtime_target.get("scheduler") if isinstance(runtime_target.get("scheduler"), dict) else {},
612+ }
613+ print(json.dumps({"targets": [target]}, separators=(",", ":")))
614+ PY
615+ )"
616+ env_pairs+=("MONITOR_DISPATCH_TARGETS_JSON=${monitor_targets_json}")
617+
590618 gcloud_args=(
591619 run services update "${CLOUD_RUN_SERVICE}"
592620 --region "${CLOUD_RUN_REGION}"
@@ -655,46 +683,42 @@ jobs:
655683 exit 1
656684 fi
657685
658- for suffix in scheduler probe-scheduler precheck-scheduler; do
659- case "${suffix}" in
660- scheduler)
661- schedule_time="${main_time}"
662- scheduler_path="/run"
663- ;;
664- probe-scheduler)
665- schedule_time="${probe_time}"
666- scheduler_path="/probe"
667- ;;
668- precheck-scheduler)
669- schedule_time="${precheck_time}"
670- scheduler_path="/dry-run"
671- ;;
672- esac
673-
674- scheduler_job_candidates=("${CLOUD_RUN_SERVICE}-${suffix}")
675- if [[ "${CLOUD_RUN_SERVICE}" == *-service ]]; then
676- scheduler_job_candidates+=("${CLOUD_RUN_SERVICE%-service}-${suffix}")
677- fi
686+ gcloud run services add-iam-policy-binding "${CLOUD_RUN_SERVICE}" \
687+ --project="${GCP_PROJECT_ID}" \
688+ --region="${CLOUD_RUN_REGION}" \
689+ --member="serviceAccount:${GCP_SCHEDULER_SERVICE_ACCOUNT}" \
690+ --role="roles/run.invoker" \
691+ --quiet
692+ gcloud run services add-iam-policy-binding "${CLOUD_RUN_SERVICE}" \
693+ --project="${GCP_PROJECT_ID}" \
694+ --region="${CLOUD_RUN_REGION}" \
695+ --member="serviceAccount:${GCP_RUNTIME_SERVICE_ACCOUNT}" \
696+ --role="roles/run.invoker" \
697+ --quiet
678698
679- job_name=""
680- current_schedule=""
681- for candidate_job in "${scheduler_job_candidates[@]}"; do
682- current_schedule="$(gcloud scheduler jobs describe "${candidate_job}" \
683- --project="${GCP_PROJECT_ID}" \
684- --location="${scheduler_location}" \
685- --format='value(schedule)' 2>/dev/null || true)"
686- if [ -n "${current_schedule}" ]; then
687- job_name="${candidate_job}"
688- break
689- fi
690- done
699+ scheduler_job_candidates=("${CLOUD_RUN_SERVICE}-scheduler")
700+ if [[ "${CLOUD_RUN_SERVICE}" == *-service ]]; then
701+ scheduler_job_candidates+=("${CLOUD_RUN_SERVICE%-service}-scheduler")
702+ fi
691703
692- if [ -z "${current_schedule}" ]; then
693- echo "Cloud Scheduler job for ${CLOUD_RUN_SERVICE} ${suffix} was not found in ${scheduler_location}; skipping schedule sync."
694- continue
704+ job_name=""
705+ current_schedule=""
706+ for candidate_job in "${scheduler_job_candidates[@]}"; do
707+ current_schedule="$(gcloud scheduler jobs describe "${candidate_job}" \
708+ --project="${GCP_PROJECT_ID}" \
709+ --location="${scheduler_location}" \
710+ --format='value(schedule)' 2>/dev/null || true)"
711+ if [ -n "${current_schedule}" ]; then
712+ job_name="${candidate_job}"
713+ break
695714 fi
715+ done
716+ if [ -z "${job_name}" ]; then
717+ job_name="${scheduler_job_candidates[0]}"
718+ fi
696719
697- desired_schedule="$(CURRENT_SCHEDULE="${current_schedule}" SCHEDULE_TIME="${schedule_time}" python - <<'PY'
720+ if [ -n "${current_schedule}" ]; then
721+ desired_schedule="$(CURRENT_SCHEDULE="${current_schedule}" SCHEDULE_TIME="${main_time}" python - <<'PY'
698722 import os
699723
700724 current_fields = os.environ["CURRENT_SCHEDULE"].split()
@@ -711,8 +735,25 @@ jobs:
711735 )
712736 PY
713737 )"
738+ else
739+ desired_schedule="$(SCHEDULE_TIME="${main_time}" python - <<'PY'
740+ import os
714741
715- scheduler_uri="${service_url}${scheduler_path}"
742+ fields = os.environ["SCHEDULE_TIME"].split()
743+ if len(fields) == 5:
744+ print(" ".join(fields))
745+ elif len(fields) == 2:
746+ print(" ".join([*fields, "*", "*", "*"]))
747+ else:
748+ raise SystemExit(
749+ f"Cloud Scheduler override must have 2 time fields or 5 cron fields: {os.environ['SCHEDULE_TIME']!r}"
750+ )
751+ PY
752+ )"
753+ fi
754+
755+ scheduler_uri="${service_url}/run"
756+ if [ -n "${current_schedule}" ]; then
716757 echo "Updating Cloud Scheduler job ${job_name} schedule to ${desired_schedule}, timezone to ${market_timezone}, and URI to ${scheduler_uri}."
717758 gcloud scheduler jobs update http "${job_name}" \
718759 --project="${GCP_PROJECT_ID}" \
@@ -721,6 +762,75 @@ jobs:
721762 --schedule="${desired_schedule}" \
722763 --time-zone="${market_timezone}" \
723764 --quiet
765+ else
766+ echo "Creating Cloud Scheduler job ${job_name} schedule ${desired_schedule}, timezone ${market_timezone}, and URI ${scheduler_uri}."
767+ gcloud scheduler jobs create http "${job_name}" \
768+ --project="${GCP_PROJECT_ID}" \
769+ --location="${scheduler_location}" \
770+ --uri="${scheduler_uri}" \
771+ --http-method=POST \
772+ --oidc-service-account-email="${GCP_SCHEDULER_SERVICE_ACCOUNT}" \
773+ --oidc-token-audience="${service_url}" \
774+ --schedule="${desired_schedule}" \
775+ --time-zone="${market_timezone}" \
776+ --attempt-deadline=600s \
777+ --quiet
778+ fi
779+
780+ monitor_job_name="firstrade-monitor-dispatcher-scheduler"
781+ monitor_uri="${service_url}/monitor-dispatch"
782+ if gcloud scheduler jobs describe "${monitor_job_name}" \
783+ --project="${GCP_PROJECT_ID}" \
784+ --location="${scheduler_location}" >/dev/null 2>&1; then
785+ echo "Updating Cloud Scheduler job ${monitor_job_name} to ${monitor_uri}."
786+ gcloud scheduler jobs update http "${monitor_job_name}" \
787+ --project="${GCP_PROJECT_ID}" \
788+ --location="${scheduler_location}" \
789+ --uri="${monitor_uri}" \
790+ --http-method=POST \
791+ --oidc-service-account-email="${GCP_SCHEDULER_SERVICE_ACCOUNT}" \
792+ --oidc-token-audience="${service_url}" \
793+ --schedule="*/5 * * * *" \
794+ --time-zone="UTC" \
795+ --attempt-deadline=180s \
796+ --quiet
797+ else
798+ echo "Creating Cloud Scheduler job ${monitor_job_name} at ${monitor_uri}."
799+ gcloud scheduler jobs create http "${monitor_job_name}" \
800+ --project="${GCP_PROJECT_ID}" \
801+ --location="${scheduler_location}" \
802+ --uri="${monitor_uri}" \
803+ --http-method=POST \
804+ --oidc-service-account-email="${GCP_SCHEDULER_SERVICE_ACCOUNT}" \
805+ --oidc-token-audience="${service_url}" \
806+ --schedule="*/5 * * * *" \
807+ --time-zone="UTC" \
808+ --attempt-deadline=180s \
809+ --quiet
810+ fi
811+
812+ legacy_jobs=(
813+ "${CLOUD_RUN_SERVICE}-probe-scheduler"
814+ "${CLOUD_RUN_SERVICE}-precheck-scheduler"
815+ "${CLOUD_RUN_SERVICE}-session-check-scheduler"
816+ )
817+ if [[ "${CLOUD_RUN_SERVICE}" == *-service ]]; then
818+ legacy_jobs+=(
819+ "${CLOUD_RUN_SERVICE%-service}-probe-scheduler"
820+ "${CLOUD_RUN_SERVICE%-service}-precheck-scheduler"
821+ "${CLOUD_RUN_SERVICE%-service}-session-check-scheduler"
822+ )
823+ fi
824+ for legacy_job in "${legacy_jobs[@]}"; do
825+ if gcloud scheduler jobs describe "${legacy_job}" \
826+ --project="${GCP_PROJECT_ID}" \
827+ --location="${scheduler_location}" >/dev/null 2>&1; then
828+ echo "Deleting legacy Cloud Scheduler job ${legacy_job}; monitor dispatcher now owns probe/precheck."
829+ gcloud scheduler jobs delete "${legacy_job}" \
830+ --project="${GCP_PROJECT_ID}" \
831+ --location="${scheduler_location}" \
832+ --quiet
833+ fi
724834 done
725835
726836 - name : Clean up old Cloud Run revisions and images
0 commit comments