|
32 | 32 | ENABLE_MAIN_PUSH_CLOUD_RUN_AUTOMATION: ${{ vars.ENABLE_MAIN_PUSH_CLOUD_RUN_AUTOMATION }} |
33 | 33 | CLOUD_RUN_REGION: ${{ vars.CLOUD_RUN_REGION }} |
34 | 34 | CLOUD_RUN_SERVICE: ${{ vars.CLOUD_RUN_SERVICE }} |
| 35 | + CLOUD_SCHEDULER_LOCATION: ${{ vars.CLOUD_SCHEDULER_LOCATION }} |
| 36 | + CLOUD_SCHEDULER_MAIN_TIME: ${{ vars.CLOUD_SCHEDULER_MAIN_TIME }} |
| 37 | + CLOUD_SCHEDULER_PROBE_TIME: ${{ vars.CLOUD_SCHEDULER_PROBE_TIME }} |
| 38 | + CLOUD_SCHEDULER_PRECHECK_TIME: ${{ vars.CLOUD_SCHEDULER_PRECHECK_TIME }} |
35 | 39 | TELEGRAM_TOKEN_SECRET_NAME: ${{ vars.TELEGRAM_TOKEN_SECRET_NAME }} |
36 | 40 | FIRSTRADE_USERNAME_SECRET_NAME: ${{ vars.FIRSTRADE_USERNAME_SECRET_NAME }} |
37 | 41 | FIRSTRADE_PASSWORD_SECRET_NAME: ${{ vars.FIRSTRADE_PASSWORD_SECRET_NAME }} |
@@ -600,6 +604,125 @@ jobs: |
600 | 604 |
|
601 | 605 | gcloud "${gcloud_args[@]}" |
602 | 606 |
|
| 607 | + - name: Sync Cloud Scheduler schedule |
| 608 | + if: steps.env_sync_config.outputs.enabled == 'true' |
| 609 | + env: |
| 610 | + RUNTIME_TARGET_JSON: ${{ steps.strategy_requirements.outputs.runtime_target_json }} |
| 611 | + run: | |
| 612 | + set -euo pipefail |
| 613 | +
|
| 614 | + scheduler_location="${CLOUD_SCHEDULER_LOCATION:-${CLOUD_RUN_REGION}}" |
| 615 | + if [ -z "${scheduler_location}" ]; then |
| 616 | + echo "Cloud Scheduler schedule sync requires CLOUD_RUN_REGION or CLOUD_SCHEDULER_LOCATION." >&2 |
| 617 | + exit 1 |
| 618 | + fi |
| 619 | +
|
| 620 | + mapfile -t scheduler_config < <(python - <<'PY' |
| 621 | + import json |
| 622 | + import os |
| 623 | +
|
| 624 | + raw_runtime_target = os.environ.get("RUNTIME_TARGET_JSON", "").strip() |
| 625 | + runtime_scheduler = {} |
| 626 | + if raw_runtime_target: |
| 627 | + try: |
| 628 | + runtime_target = json.loads(raw_runtime_target) |
| 629 | + except json.JSONDecodeError: |
| 630 | + runtime_target = {} |
| 631 | + scheduler = runtime_target.get("scheduler") if isinstance(runtime_target, dict) else {} |
| 632 | + if isinstance(scheduler, dict): |
| 633 | + runtime_scheduler = scheduler |
| 634 | +
|
| 635 | + def configured_time(key: str, name: str, default: str) -> str: |
| 636 | + return str(runtime_scheduler.get(key) or os.environ.get(name, "").strip() or default) |
| 637 | +
|
| 638 | + print(str(runtime_scheduler.get("timezone") or "America/New_York").strip()) |
| 639 | + print(configured_time("main_time", "CLOUD_SCHEDULER_MAIN_TIME", "45 15")) |
| 640 | + print(configured_time("probe_time", "CLOUD_SCHEDULER_PROBE_TIME", "35 9,15")) |
| 641 | + print(configured_time("precheck_time", "CLOUD_SCHEDULER_PRECHECK_TIME", "45 9")) |
| 642 | + PY |
| 643 | + ) |
| 644 | + market_timezone="${scheduler_config[0]}" |
| 645 | + main_time="${scheduler_config[1]}" |
| 646 | + probe_time="${scheduler_config[2]}" |
| 647 | + precheck_time="${scheduler_config[3]}" |
| 648 | +
|
| 649 | + service_url="$(gcloud run services describe "${CLOUD_RUN_SERVICE}" \ |
| 650 | + --project="${GCP_PROJECT_ID}" \ |
| 651 | + --region="${CLOUD_RUN_REGION}" \ |
| 652 | + --format='value(status.url)' 2>/dev/null || true)" |
| 653 | + if [ -z "${service_url}" ]; then |
| 654 | + echo "Unable to resolve Cloud Run service URL for ${CLOUD_RUN_SERVICE}; cannot sync scheduler URI." >&2 |
| 655 | + exit 1 |
| 656 | + fi |
| 657 | +
|
| 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 |
| 678 | +
|
| 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 |
| 691 | +
|
| 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 |
| 695 | + fi |
| 696 | +
|
| 697 | + desired_schedule="$(CURRENT_SCHEDULE="${current_schedule}" SCHEDULE_TIME="${schedule_time}" python - <<'PY' |
| 698 | + import os |
| 699 | +
|
| 700 | + current_fields = os.environ["CURRENT_SCHEDULE"].split() |
| 701 | + time_fields = os.environ["SCHEDULE_TIME"].split() |
| 702 | + if len(current_fields) != 5: |
| 703 | + raise SystemExit(f"Cloud Scheduler schedule must have 5 fields: {os.environ['CURRENT_SCHEDULE']!r}") |
| 704 | + if len(time_fields) == 5: |
| 705 | + print(" ".join(time_fields)) |
| 706 | + elif len(time_fields) == 2: |
| 707 | + print(" ".join([*time_fields, *current_fields[2:]])) |
| 708 | + else: |
| 709 | + raise SystemExit( |
| 710 | + f"Cloud Scheduler override must have 2 time fields or 5 cron fields: {os.environ['SCHEDULE_TIME']!r}" |
| 711 | + ) |
| 712 | + PY |
| 713 | + )" |
| 714 | +
|
| 715 | + scheduler_uri="${service_url}${scheduler_path}" |
| 716 | + echo "Updating Cloud Scheduler job ${job_name} schedule to ${desired_schedule}, timezone to ${market_timezone}, and URI to ${scheduler_uri}." |
| 717 | + gcloud scheduler jobs update http "${job_name}" \ |
| 718 | + --project="${GCP_PROJECT_ID}" \ |
| 719 | + --location="${scheduler_location}" \ |
| 720 | + --uri="${scheduler_uri}" \ |
| 721 | + --schedule="${desired_schedule}" \ |
| 722 | + --time-zone="${market_timezone}" \ |
| 723 | + --quiet |
| 724 | + done |
| 725 | +
|
603 | 726 | - name: Clean up old Cloud Run revisions and images |
604 | 727 | if: steps.deploy_config.outputs.enabled == 'true' |
605 | 728 | run: | |
|
0 commit comments