Skip to content

Align LongBridge runtime strategy profiles #111

Align LongBridge runtime strategy profiles

Align LongBridge runtime strategy profiles #111

name: Sync Cloud Run Env
on:
push:
branches: [ main ]
env:
GCP_PROJECT_ID: longbridgequant
GCP_WORKLOAD_IDENTITY_PROVIDER: projects/252919773759/locations/global/workloadIdentityPools/github-actions/providers/github-main
GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: longbridge-platform-deploy@longbridgequant.iam.gserviceaccount.com
jobs:
sync-hk:
name: Sync HK Cloud Run Env
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
environment: longbridge-hk
env:
ENABLE_GITHUB_ENV_SYNC: ${{ vars.ENABLE_GITHUB_ENV_SYNC }}
# Set CLOUD_RUN_REGION per Environment so HK/SG can target different regions.
CLOUD_RUN_REGION: ${{ vars.CLOUD_RUN_REGION }}
CLOUD_RUN_SERVICE: ${{ vars.CLOUD_RUN_SERVICE }}
ACCOUNT_PREFIX: ${{ vars.ACCOUNT_PREFIX }}
TELEGRAM_TOKEN_SECRET_NAME: ${{ vars.TELEGRAM_TOKEN_SECRET_NAME }}
LONGPORT_APP_KEY_SECRET_NAME: ${{ vars.LONGPORT_APP_KEY_SECRET_NAME }}
LONGPORT_APP_SECRET_SECRET_NAME: ${{ vars.LONGPORT_APP_SECRET_SECRET_NAME }}
STRATEGY_PROFILE: ${{ vars.STRATEGY_PROFILE || 'soxl_soxx_trend_income' }}
ACCOUNT_REGION: ${{ vars.ACCOUNT_REGION || 'HK' }}
LONGPORT_SECRET_NAME: ${{ vars.LONGPORT_SECRET_NAME }}
LONGBRIDGE_FEATURE_SNAPSHOT_PATH: ${{ vars.LONGBRIDGE_FEATURE_SNAPSHOT_PATH }}
LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH: ${{ vars.LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH }}
LONGBRIDGE_STRATEGY_CONFIG_PATH: ${{ vars.LONGBRIDGE_STRATEGY_CONFIG_PATH }}
INCOME_THRESHOLD_USD: ${{ vars.INCOME_THRESHOLD_USD }}
QQQI_INCOME_RATIO: ${{ vars.QQQI_INCOME_RATIO }}
NOTIFY_LANG: ${{ vars.NOTIFY_LANG }}
EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }}
LONGBRIDGE_DRY_RUN_ONLY: ${{ vars.LONGBRIDGE_DRY_RUN_ONLY }}
GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }}
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
steps:
- name: Check whether env sync is enabled
id: config
run: |
set -euo pipefail
if [ "${ENABLE_GITHUB_ENV_SYNC:-}" != "true" ]; then
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "Skipping HK Cloud Run env sync because ENABLE_GITHUB_ENV_SYNC is not set to true." >&2
exit 0
fi
echo "enabled=true" >> "$GITHUB_OUTPUT"
- name: Checkout repository
if: steps.config.outputs.enabled == 'true'
uses: actions/checkout@v6
- name: Set up Python for strategy requirement resolution
if: steps.config.outputs.enabled == 'true'
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install strategy status dependencies
if: steps.config.outputs.enabled == 'true'
run: |
set -euo pipefail
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
- name: Resolve selected strategy runtime requirements
id: strategy_requirements
if: steps.config.outputs.enabled == 'true'
run: |
set -euo pipefail
python - <<'PY'
import json
import os
import subprocess
import sys
from us_equity_strategies import resolve_canonical_profile
profile = os.environ.get("STRATEGY_PROFILE", "").strip().lower()
if not profile:
raise SystemExit("STRATEGY_PROFILE is required")
canonical_profile = resolve_canonical_profile(profile)
raw_status = subprocess.check_output(
[sys.executable, "scripts/print_strategy_profile_status.py", "--json"],
text=True,
)
rows = json.loads(raw_status)
selected = next((row for row in rows if row["canonical_profile"] == canonical_profile), None)
if selected is None:
supported = ", ".join(sorted(row["canonical_profile"] for row in rows))
raise SystemExit(f"Unsupported STRATEGY_PROFILE={profile!r}; supported: {supported}")
if not selected.get("eligible") or not selected.get("enabled"):
raise SystemExit(f"STRATEGY_PROFILE={profile!r} is not eligible/enabled: {selected}")
output_path = os.environ["GITHUB_OUTPUT"]
with open(output_path, "a", encoding="utf-8") as output:
output.write(
f"requires_snapshot_artifacts={str(bool(selected.get('requires_snapshot_artifacts'))).lower()}\n"
)
output.write(
f"requires_snapshot_manifest_path={str(bool(selected.get('requires_snapshot_manifest_path'))).lower()}\n"
)
output.write(
f"requires_strategy_config_path={str(bool(selected.get('requires_strategy_config_path'))).lower()}\n"
)
output.write(
f"config_source_policy={str(selected.get('config_source_policy') or 'none')}\n"
)
PY
- name: Validate HK env sync inputs
if: steps.config.outputs.enabled == 'true'
env:
REQUIRES_SNAPSHOT_ARTIFACTS: ${{ steps.strategy_requirements.outputs.requires_snapshot_artifacts }}
REQUIRES_SNAPSHOT_MANIFEST_PATH: ${{ steps.strategy_requirements.outputs.requires_snapshot_manifest_path }}
REQUIRES_STRATEGY_CONFIG_PATH: ${{ steps.strategy_requirements.outputs.requires_strategy_config_path }}
CONFIG_SOURCE_POLICY: ${{ steps.strategy_requirements.outputs.config_source_policy }}
run: |
set -euo pipefail
required_vars=(
CLOUD_RUN_REGION
CLOUD_RUN_SERVICE
ACCOUNT_PREFIX
LONGPORT_SECRET_NAME
NOTIFY_LANG
GLOBAL_TELEGRAM_CHAT_ID
)
missing_vars=()
for var_name in "${required_vars[@]}"; do
if [ -z "${!var_name:-}" ]; then
missing_vars+=("${var_name}")
fi
done
if [ -z "${TELEGRAM_TOKEN_SECRET_NAME:-}" ] && [ -z "${TELEGRAM_TOKEN:-}" ]; then
missing_vars+=("TELEGRAM_TOKEN_SECRET_NAME or TELEGRAM_TOKEN")
fi
if [ -z "${LONGPORT_APP_KEY_SECRET_NAME:-}" ]; then
missing_vars+=("LONGPORT_APP_KEY_SECRET_NAME")
fi
if [ -z "${LONGPORT_APP_SECRET_SECRET_NAME:-}" ]; then
missing_vars+=("LONGPORT_APP_SECRET_SECRET_NAME")
fi
if [ "${REQUIRES_SNAPSHOT_ARTIFACTS:-}" = "true" ] && [ -z "${LONGBRIDGE_FEATURE_SNAPSHOT_PATH:-}" ]; then
missing_vars+=("LONGBRIDGE_FEATURE_SNAPSHOT_PATH")
fi
if [ "${REQUIRES_SNAPSHOT_MANIFEST_PATH:-}" = "true" ] && [ -z "${LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH:-}" ]; then
missing_vars+=("LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH")
fi
if [ "${REQUIRES_STRATEGY_CONFIG_PATH:-}" = "true" ] \
&& [ "${CONFIG_SOURCE_POLICY:-}" = "env_only" ] \
&& [ -z "${LONGBRIDGE_STRATEGY_CONFIG_PATH:-}" ]; then
missing_vars+=("LONGBRIDGE_STRATEGY_CONFIG_PATH")
fi
if [ "${#missing_vars[@]}" -gt 0 ]; then
echo "HK Cloud Run env sync is enabled, but these values are missing:" >&2
echo " - If HK and SG run in different regions, set CLOUD_RUN_REGION on the longbridge-hk Environment." >&2
echo " - Set LONGPORT_APP_KEY_SECRET_NAME and LONGPORT_APP_SECRET_SECRET_NAME on the longbridge-hk Environment so HK does not fall back to shared repository defaults." >&2
printf ' - %s\n' "${missing_vars[@]}" >&2
exit 1
fi
- name: Authenticate to Google Cloud
id: auth
if: steps.config.outputs.enabled == 'true'
uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ env.GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT }}
- name: Set up gcloud
if: steps.config.outputs.enabled == 'true'
uses: google-github-actions/setup-gcloud@v3
with:
project_id: ${{ env.GCP_PROJECT_ID }}
version: ">= 416.0.0"
- name: Wait for Cloud Run deployment of current commit
if: steps.config.outputs.enabled == 'true'
run: |
set -euo pipefail
target_sha="${GITHUB_SHA}"
deadline=$((SECONDS + 1800))
while true; do
deployed_sha="$(gcloud run services describe "${CLOUD_RUN_SERVICE}" --region "${CLOUD_RUN_REGION}" --format='value(spec.template.metadata.labels.commit-sha)' 2>/dev/null || true)"
if [ -n "${deployed_sha}" ] && [ "${deployed_sha}" = "${target_sha}" ]; then
echo "Cloud Run service ${CLOUD_RUN_SERVICE} is on commit ${deployed_sha}."
break
fi
if [ "${SECONDS}" -ge "${deadline}" ]; then
echo "Timed out waiting for Cloud Run service ${CLOUD_RUN_SERVICE} to deploy commit ${target_sha}. Last seen commit: ${deployed_sha:-<none>}" >&2
exit 1
fi
echo "Waiting for Cloud Run service ${CLOUD_RUN_SERVICE} to deploy commit ${target_sha}. Last seen commit: ${deployed_sha:-<none>}" >&2
sleep 10
done
- name: Sync Cloud Run environment
if: steps.config.outputs.enabled == 'true'
run: |
set -euo pipefail
env_pairs=(
"GLOBAL_TELEGRAM_CHAT_ID=${GLOBAL_TELEGRAM_CHAT_ID}"
"NOTIFY_LANG=${NOTIFY_LANG}"
"LONGPORT_SECRET_NAME=${LONGPORT_SECRET_NAME}"
"ACCOUNT_PREFIX=${ACCOUNT_PREFIX}"
"STRATEGY_PROFILE=${STRATEGY_PROFILE}"
"ACCOUNT_REGION=${ACCOUNT_REGION}"
)
secret_pairs=()
remove_env_vars=(
"TELEGRAM_CHAT_ID"
"SERVICE_NAME"
)
remove_secret_vars=()
if [ -n "${TELEGRAM_TOKEN_SECRET_NAME:-}" ]; then
secret_pairs+=("TELEGRAM_TOKEN=${TELEGRAM_TOKEN_SECRET_NAME}:latest")
remove_env_vars+=("TELEGRAM_TOKEN")
else
env_pairs+=("TELEGRAM_TOKEN=${TELEGRAM_TOKEN}")
remove_secret_vars+=("TELEGRAM_TOKEN")
fi
secret_pairs+=("LONGPORT_APP_KEY=${LONGPORT_APP_KEY_SECRET_NAME}:latest")
remove_env_vars+=("LONGPORT_APP_KEY")
secret_pairs+=("LONGPORT_APP_SECRET=${LONGPORT_APP_SECRET_SECRET_NAME}:latest")
remove_env_vars+=("LONGPORT_APP_SECRET")
if [ -n "${EXECUTION_REPORT_GCS_URI:-}" ]; then
env_pairs+=("EXECUTION_REPORT_GCS_URI=${EXECUTION_REPORT_GCS_URI}")
else
remove_env_vars+=("EXECUTION_REPORT_GCS_URI")
fi
if [ -n "${LONGBRIDGE_FEATURE_SNAPSHOT_PATH:-}" ]; then
env_pairs+=("LONGBRIDGE_FEATURE_SNAPSHOT_PATH=${LONGBRIDGE_FEATURE_SNAPSHOT_PATH}")
else
remove_env_vars+=("LONGBRIDGE_FEATURE_SNAPSHOT_PATH")
fi
if [ -n "${LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH:-}" ]; then
env_pairs+=("LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH=${LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH}")
else
remove_env_vars+=("LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH")
fi
if [ -n "${LONGBRIDGE_STRATEGY_CONFIG_PATH:-}" ]; then
env_pairs+=("LONGBRIDGE_STRATEGY_CONFIG_PATH=${LONGBRIDGE_STRATEGY_CONFIG_PATH}")
else
remove_env_vars+=("LONGBRIDGE_STRATEGY_CONFIG_PATH")
fi
if [ -n "${LONGBRIDGE_DRY_RUN_ONLY:-}" ]; then
env_pairs+=("LONGBRIDGE_DRY_RUN_ONLY=${LONGBRIDGE_DRY_RUN_ONLY}")
else
remove_env_vars+=("LONGBRIDGE_DRY_RUN_ONLY")
fi
if [ -n "${INCOME_THRESHOLD_USD:-}" ]; then
env_pairs+=("INCOME_THRESHOLD_USD=${INCOME_THRESHOLD_USD}")
else
remove_env_vars+=("INCOME_THRESHOLD_USD")
fi
if [ -n "${QQQI_INCOME_RATIO:-}" ]; then
env_pairs+=("QQQI_INCOME_RATIO=${QQQI_INCOME_RATIO}")
else
remove_env_vars+=("QQQI_INCOME_RATIO")
fi
gcloud_args=(
run services update "${CLOUD_RUN_SERVICE}"
--region "${CLOUD_RUN_REGION}"
--remove-env-vars "$(IFS=,; echo "${remove_env_vars[*]}")"
--update-env-vars "$(IFS=,; echo "${env_pairs[*]}")"
)
if [ "${#remove_secret_vars[@]}" -gt 0 ]; then
gcloud_args+=(--remove-secrets "$(IFS=,; echo "${remove_secret_vars[*]}")")
fi
if [ "${#secret_pairs[@]}" -gt 0 ]; then
gcloud_args+=(--update-secrets "$(IFS=,; echo "${secret_pairs[*]}")")
fi
gcloud "${gcloud_args[@]}"
sync-sg:
name: Sync SG Cloud Run Env
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
environment: longbridge-sg
env:
ENABLE_GITHUB_ENV_SYNC: ${{ vars.ENABLE_GITHUB_ENV_SYNC }}
# Set CLOUD_RUN_REGION per Environment so HK/SG can target different regions.
CLOUD_RUN_REGION: ${{ vars.CLOUD_RUN_REGION }}
CLOUD_RUN_SERVICE: ${{ vars.CLOUD_RUN_SERVICE }}
ACCOUNT_PREFIX: ${{ vars.ACCOUNT_PREFIX }}
TELEGRAM_TOKEN_SECRET_NAME: ${{ vars.TELEGRAM_TOKEN_SECRET_NAME }}
LONGPORT_APP_KEY_SECRET_NAME: ${{ vars.LONGPORT_APP_KEY_SECRET_NAME }}
LONGPORT_APP_SECRET_SECRET_NAME: ${{ vars.LONGPORT_APP_SECRET_SECRET_NAME }}
STRATEGY_PROFILE: ${{ vars.STRATEGY_PROFILE || 'soxl_soxx_trend_income' }}
ACCOUNT_REGION: ${{ vars.ACCOUNT_REGION || 'SG' }}
LONGPORT_SECRET_NAME: ${{ vars.LONGPORT_SECRET_NAME }}
LONGBRIDGE_FEATURE_SNAPSHOT_PATH: ${{ vars.LONGBRIDGE_FEATURE_SNAPSHOT_PATH }}
LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH: ${{ vars.LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH }}
LONGBRIDGE_STRATEGY_CONFIG_PATH: ${{ vars.LONGBRIDGE_STRATEGY_CONFIG_PATH }}
INCOME_THRESHOLD_USD: ${{ vars.INCOME_THRESHOLD_USD }}
QQQI_INCOME_RATIO: ${{ vars.QQQI_INCOME_RATIO }}
NOTIFY_LANG: ${{ vars.NOTIFY_LANG }}
EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }}
LONGBRIDGE_DRY_RUN_ONLY: ${{ vars.LONGBRIDGE_DRY_RUN_ONLY }}
GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }}
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
steps:
- name: Check whether env sync is enabled
id: config
run: |
set -euo pipefail
if [ "${ENABLE_GITHUB_ENV_SYNC:-}" != "true" ]; then
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "Skipping SG Cloud Run env sync because ENABLE_GITHUB_ENV_SYNC is not set to true." >&2
exit 0
fi
echo "enabled=true" >> "$GITHUB_OUTPUT"
- name: Checkout repository
if: steps.config.outputs.enabled == 'true'
uses: actions/checkout@v6
- name: Set up Python for strategy requirement resolution
if: steps.config.outputs.enabled == 'true'
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install strategy status dependencies
if: steps.config.outputs.enabled == 'true'
run: |
set -euo pipefail
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
- name: Resolve selected strategy runtime requirements
id: strategy_requirements
if: steps.config.outputs.enabled == 'true'
run: |
set -euo pipefail
python - <<'PY'
import json
import os
import subprocess
import sys
from us_equity_strategies import resolve_canonical_profile
profile = os.environ.get("STRATEGY_PROFILE", "").strip().lower()
if not profile:
raise SystemExit("STRATEGY_PROFILE is required")
canonical_profile = resolve_canonical_profile(profile)
raw_status = subprocess.check_output(
[sys.executable, "scripts/print_strategy_profile_status.py", "--json"],
text=True,
)
rows = json.loads(raw_status)
selected = next((row for row in rows if row["canonical_profile"] == canonical_profile), None)
if selected is None:
supported = ", ".join(sorted(row["canonical_profile"] for row in rows))
raise SystemExit(f"Unsupported STRATEGY_PROFILE={profile!r}; supported: {supported}")
if not selected.get("eligible") or not selected.get("enabled"):
raise SystemExit(f"STRATEGY_PROFILE={profile!r} is not eligible/enabled: {selected}")
output_path = os.environ["GITHUB_OUTPUT"]
with open(output_path, "a", encoding="utf-8") as output:
output.write(
f"requires_snapshot_artifacts={str(bool(selected.get('requires_snapshot_artifacts'))).lower()}\n"
)
output.write(
f"requires_snapshot_manifest_path={str(bool(selected.get('requires_snapshot_manifest_path'))).lower()}\n"
)
output.write(
f"requires_strategy_config_path={str(bool(selected.get('requires_strategy_config_path'))).lower()}\n"
)
output.write(
f"config_source_policy={str(selected.get('config_source_policy') or 'none')}\n"
)
PY
- name: Validate SG env sync inputs
if: steps.config.outputs.enabled == 'true'
env:
REQUIRES_SNAPSHOT_ARTIFACTS: ${{ steps.strategy_requirements.outputs.requires_snapshot_artifacts }}
REQUIRES_SNAPSHOT_MANIFEST_PATH: ${{ steps.strategy_requirements.outputs.requires_snapshot_manifest_path }}
REQUIRES_STRATEGY_CONFIG_PATH: ${{ steps.strategy_requirements.outputs.requires_strategy_config_path }}
CONFIG_SOURCE_POLICY: ${{ steps.strategy_requirements.outputs.config_source_policy }}
run: |
set -euo pipefail
required_vars=(
CLOUD_RUN_REGION
CLOUD_RUN_SERVICE
ACCOUNT_PREFIX
LONGPORT_SECRET_NAME
NOTIFY_LANG
GLOBAL_TELEGRAM_CHAT_ID
)
missing_vars=()
for var_name in "${required_vars[@]}"; do
if [ -z "${!var_name:-}" ]; then
missing_vars+=("${var_name}")
fi
done
if [ -z "${TELEGRAM_TOKEN_SECRET_NAME:-}" ] && [ -z "${TELEGRAM_TOKEN:-}" ]; then
missing_vars+=("TELEGRAM_TOKEN_SECRET_NAME or TELEGRAM_TOKEN")
fi
if [ -z "${LONGPORT_APP_KEY_SECRET_NAME:-}" ]; then
missing_vars+=("LONGPORT_APP_KEY_SECRET_NAME")
fi
if [ -z "${LONGPORT_APP_SECRET_SECRET_NAME:-}" ]; then
missing_vars+=("LONGPORT_APP_SECRET_SECRET_NAME")
fi
if [ "${REQUIRES_SNAPSHOT_ARTIFACTS:-}" = "true" ] && [ -z "${LONGBRIDGE_FEATURE_SNAPSHOT_PATH:-}" ]; then
missing_vars+=("LONGBRIDGE_FEATURE_SNAPSHOT_PATH")
fi
if [ "${REQUIRES_SNAPSHOT_MANIFEST_PATH:-}" = "true" ] && [ -z "${LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH:-}" ]; then
missing_vars+=("LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH")
fi
if [ "${REQUIRES_STRATEGY_CONFIG_PATH:-}" = "true" ] \
&& [ "${CONFIG_SOURCE_POLICY:-}" = "env_only" ] \
&& [ -z "${LONGBRIDGE_STRATEGY_CONFIG_PATH:-}" ]; then
missing_vars+=("LONGBRIDGE_STRATEGY_CONFIG_PATH")
fi
if [ "${#missing_vars[@]}" -gt 0 ]; then
echo "SG Cloud Run env sync is enabled, but these values are missing:" >&2
echo " - If HK and SG run in different regions, set CLOUD_RUN_REGION on the longbridge-sg Environment." >&2
echo " - Set LONGPORT_APP_KEY_SECRET_NAME and LONGPORT_APP_SECRET_SECRET_NAME on the longbridge-sg Environment so SG does not fall back to shared repository defaults." >&2
printf ' - %s\n' "${missing_vars[@]}" >&2
exit 1
fi
- name: Authenticate to Google Cloud
id: auth
if: steps.config.outputs.enabled == 'true'
uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ env.GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT }}
- name: Set up gcloud
if: steps.config.outputs.enabled == 'true'
uses: google-github-actions/setup-gcloud@v3
with:
project_id: ${{ env.GCP_PROJECT_ID }}
version: ">= 416.0.0"
- name: Wait for Cloud Run deployment of current commit
if: steps.config.outputs.enabled == 'true'
run: |
set -euo pipefail
target_sha="${GITHUB_SHA}"
deadline=$((SECONDS + 1800))
while true; do
deployed_sha="$(gcloud run services describe "${CLOUD_RUN_SERVICE}" --region "${CLOUD_RUN_REGION}" --format='value(spec.template.metadata.labels.commit-sha)' 2>/dev/null || true)"
if [ -n "${deployed_sha}" ] && [ "${deployed_sha}" = "${target_sha}" ]; then
echo "Cloud Run service ${CLOUD_RUN_SERVICE} is on commit ${deployed_sha}."
break
fi
if [ "${SECONDS}" -ge "${deadline}" ]; then
echo "Timed out waiting for Cloud Run service ${CLOUD_RUN_SERVICE} to deploy commit ${target_sha}. Last seen commit: ${deployed_sha:-<none>}" >&2
exit 1
fi
echo "Waiting for Cloud Run service ${CLOUD_RUN_SERVICE} to deploy commit ${target_sha}. Last seen commit: ${deployed_sha:-<none>}" >&2
sleep 10
done
- name: Sync Cloud Run environment
if: steps.config.outputs.enabled == 'true'
run: |
set -euo pipefail
env_pairs=(
"GLOBAL_TELEGRAM_CHAT_ID=${GLOBAL_TELEGRAM_CHAT_ID}"
"NOTIFY_LANG=${NOTIFY_LANG}"
"LONGPORT_SECRET_NAME=${LONGPORT_SECRET_NAME}"
"ACCOUNT_PREFIX=${ACCOUNT_PREFIX}"
"STRATEGY_PROFILE=${STRATEGY_PROFILE}"
"ACCOUNT_REGION=${ACCOUNT_REGION}"
)
secret_pairs=()
remove_env_vars=(
"TELEGRAM_CHAT_ID"
"SERVICE_NAME"
)
remove_secret_vars=()
if [ -n "${TELEGRAM_TOKEN_SECRET_NAME:-}" ]; then
secret_pairs+=("TELEGRAM_TOKEN=${TELEGRAM_TOKEN_SECRET_NAME}:latest")
remove_env_vars+=("TELEGRAM_TOKEN")
else
env_pairs+=("TELEGRAM_TOKEN=${TELEGRAM_TOKEN}")
remove_secret_vars+=("TELEGRAM_TOKEN")
fi
secret_pairs+=("LONGPORT_APP_KEY=${LONGPORT_APP_KEY_SECRET_NAME}:latest")
remove_env_vars+=("LONGPORT_APP_KEY")
secret_pairs+=("LONGPORT_APP_SECRET=${LONGPORT_APP_SECRET_SECRET_NAME}:latest")
remove_env_vars+=("LONGPORT_APP_SECRET")
if [ -n "${EXECUTION_REPORT_GCS_URI:-}" ]; then
env_pairs+=("EXECUTION_REPORT_GCS_URI=${EXECUTION_REPORT_GCS_URI}")
else
remove_env_vars+=("EXECUTION_REPORT_GCS_URI")
fi
if [ -n "${LONGBRIDGE_FEATURE_SNAPSHOT_PATH:-}" ]; then
env_pairs+=("LONGBRIDGE_FEATURE_SNAPSHOT_PATH=${LONGBRIDGE_FEATURE_SNAPSHOT_PATH}")
else
remove_env_vars+=("LONGBRIDGE_FEATURE_SNAPSHOT_PATH")
fi
if [ -n "${LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH:-}" ]; then
env_pairs+=("LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH=${LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH}")
else
remove_env_vars+=("LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH")
fi
if [ -n "${LONGBRIDGE_STRATEGY_CONFIG_PATH:-}" ]; then
env_pairs+=("LONGBRIDGE_STRATEGY_CONFIG_PATH=${LONGBRIDGE_STRATEGY_CONFIG_PATH}")
else
remove_env_vars+=("LONGBRIDGE_STRATEGY_CONFIG_PATH")
fi
if [ -n "${LONGBRIDGE_DRY_RUN_ONLY:-}" ]; then
env_pairs+=("LONGBRIDGE_DRY_RUN_ONLY=${LONGBRIDGE_DRY_RUN_ONLY}")
else
remove_env_vars+=("LONGBRIDGE_DRY_RUN_ONLY")
fi
if [ -n "${INCOME_THRESHOLD_USD:-}" ]; then
env_pairs+=("INCOME_THRESHOLD_USD=${INCOME_THRESHOLD_USD}")
else
remove_env_vars+=("INCOME_THRESHOLD_USD")
fi
if [ -n "${QQQI_INCOME_RATIO:-}" ]; then
env_pairs+=("QQQI_INCOME_RATIO=${QQQI_INCOME_RATIO}")
else
remove_env_vars+=("QQQI_INCOME_RATIO")
fi
gcloud_args=(
run services update "${CLOUD_RUN_SERVICE}"
--region "${CLOUD_RUN_REGION}"
--remove-env-vars "$(IFS=,; echo "${remove_env_vars[*]}")"
--update-env-vars "$(IFS=,; echo "${env_pairs[*]}")"
)
if [ "${#remove_secret_vars[@]}" -gt 0 ]; then
gcloud_args+=(--remove-secrets "$(IFS=,; echo "${remove_secret_vars[*]}")")
fi
if [ "${#secret_pairs[@]}" -gt 0 ]; then
gcloud_args+=(--update-secrets "$(IFS=,; echo "${secret_pairs[*]}")")
fi
gcloud "${gcloud_args[@]}"