From 29aa9becbcc6244bc19cd3a5c70d7eed64362604 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Fri, 8 May 2026 05:18:32 +0800 Subject: [PATCH] Make LongBridge API probe credential loading flexible --- .github/workflows/longbridge-api-probe.yml | 66 ++++++++++++++++++--- README.md | 2 + tests/test_longbridge_api_probe_workflow.py | 17 ++++-- tests/test_longbridge_api_probe_workflow.sh | 14 ++++- 4 files changed, 82 insertions(+), 17 deletions(-) diff --git a/.github/workflows/longbridge-api-probe.yml b/.github/workflows/longbridge-api-probe.yml index 3295703..1620578 100644 --- a/.github/workflows/longbridge-api-probe.yml +++ b/.github/workflows/longbridge-api-probe.yml @@ -33,20 +33,29 @@ jobs: id-token: write environment: longbridge-hk env: + CLOUD_RUN_REGION: ${{ vars.CLOUD_RUN_REGION }} + CLOUD_RUN_SERVICE: ${{ vars.CLOUD_RUN_SERVICE }} LONGPORT_APP_KEY_SECRET_NAME: ${{ vars.LONGPORT_APP_KEY_SECRET_NAME }} LONGPORT_APP_SECRET_SECRET_NAME: ${{ vars.LONGPORT_APP_SECRET_SECRET_NAME }} LONGPORT_SECRET_NAME: ${{ vars.LONGPORT_SECRET_NAME }} + LONGPORT_APP_KEY_VALUE: ${{ secrets.LONGPORT_APP_KEY }} + LONGPORT_APP_SECRET_VALUE: ${{ secrets.LONGPORT_APP_SECRET }} + LONGPORT_ACCESS_TOKEN_VALUE: ${{ secrets.LONGPORT_ACCESS_TOKEN }} steps: - name: Validate inputs run: | set -euo pipefail missing_vars=() - for var_name in LONGPORT_APP_KEY_SECRET_NAME LONGPORT_APP_SECRET_SECRET_NAME LONGPORT_SECRET_NAME; do - if [ -z "${!var_name:-}" ]; then - missing_vars+=("${var_name}") - fi - done + if [ -z "${LONGPORT_APP_KEY_VALUE:-}" ] && [ -z "${LONGPORT_APP_KEY_SECRET_NAME:-}" ]; then + missing_vars+=("LONGPORT_APP_KEY secret or LONGPORT_APP_KEY_SECRET_NAME variable") + fi + if [ -z "${LONGPORT_APP_SECRET_VALUE:-}" ] && [ -z "${LONGPORT_APP_SECRET_SECRET_NAME:-}" ]; then + missing_vars+=("LONGPORT_APP_SECRET secret or LONGPORT_APP_SECRET_SECRET_NAME variable") + fi + if [ -z "${LONGPORT_ACCESS_TOKEN_VALUE:-}" ] && [ -z "${LONGPORT_SECRET_NAME:-}" ]; then + missing_vars+=("LONGPORT_ACCESS_TOKEN secret or LONGPORT_SECRET_NAME variable") + fi if [ "${#missing_vars[@]}" -gt 0 ]; then printf 'Missing longbridge-hk variables:\n' >&2 @@ -82,9 +91,50 @@ jobs: run: | set -euo pipefail - longport_app_key="$(gcloud secrets versions access latest --secret="${LONGPORT_APP_KEY_SECRET_NAME}")" - longport_app_secret="$(gcloud secrets versions access latest --secret="${LONGPORT_APP_SECRET_SECRET_NAME}")" - longport_access_token="$(gcloud secrets versions access latest --secret="${LONGPORT_SECRET_NAME}")" + read_secret_manager_value() { + local secret_name="$1" + local error_file + error_file="$(mktemp)" + if value="$(gcloud secrets versions access latest --secret="${secret_name}" 2>"${error_file}")"; then + rm -f "${error_file}" + printf '%s' "${value}" + return 0 + fi + + if [ -n "${CLOUD_RUN_REGION:-}" ] && [ -n "${CLOUD_RUN_SERVICE:-}" ]; then + runtime_service_account="$( + gcloud run services describe "${CLOUD_RUN_SERVICE}" \ + --region "${CLOUD_RUN_REGION}" \ + --format='value(spec.template.spec.serviceAccountName)' 2>/dev/null || true + )" + if [ -n "${runtime_service_account}" ] \ + && value="$(gcloud secrets versions access latest \ + --secret="${secret_name}" \ + --impersonate-service-account="${runtime_service_account}" 2>"${error_file}")"; then + rm -f "${error_file}" + printf '%s' "${value}" + return 0 + fi + fi + + cat "${error_file}" >&2 + rm -f "${error_file}" + return 1 + } + + longport_app_key="${LONGPORT_APP_KEY_VALUE:-}" + longport_app_secret="${LONGPORT_APP_SECRET_VALUE:-}" + longport_access_token="${LONGPORT_ACCESS_TOKEN_VALUE:-}" + + if [ -z "${longport_app_key}" ]; then + longport_app_key="$(read_secret_manager_value "${LONGPORT_APP_KEY_SECRET_NAME}")" + fi + if [ -z "${longport_app_secret}" ]; then + longport_app_secret="$(read_secret_manager_value "${LONGPORT_APP_SECRET_SECRET_NAME}")" + fi + if [ -z "${longport_access_token}" ]; then + longport_access_token="$(read_secret_manager_value "${LONGPORT_SECRET_NAME}")" + fi echo "::add-mask::${longport_app_key}" echo "::add-mask::${longport_app_secret}" diff --git a/README.md b/README.md index 6d174fa..1cab737 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ Important: ### Manual API probes - `.github/workflows/longbridge-api-probe.yml` can be triggered manually against the `longbridge-hk` GitHub Environment. It checks out a selected `QuantPlatformKit` ref and runs the skipped LongBridge fractional-order API probe with HK simulated-account credentials from Secret Manager. +- The workflow can also use `LONGPORT_APP_KEY`, `LONGPORT_APP_SECRET`, and `LONGPORT_ACCESS_TOKEN` as GitHub Environment secrets when the deployment service account cannot read Secret Manager versions directly. - The probe is for broker API validation only. It does not run on normal CI or `main` pushes. ### Quick deploy @@ -307,6 +308,7 @@ Secret Manager 中需存在 `LONGPORT_SECRET_NAME` 指定的密钥(默认: `lo ### 手动 API probe - `.github/workflows/longbridge-api-probe.yml` 可手动触发,固定使用 `longbridge-hk` GitHub Environment。它会 checkout 指定的 `QuantPlatformKit` ref,并用 Secret Manager 中的 HK 模拟盘 LongPort 凭证运行默认跳过的碎股下单 API probe。 +- 如果部署服务账号不能直接读取 Secret Manager version,这个 workflow 也可以改用 GitHub Environment secrets: `LONGPORT_APP_KEY`、`LONGPORT_APP_SECRET`、`LONGPORT_ACCESS_TOKEN`。 - 这个 probe 只用于券商 API 验证,不会进入普通 CI 或 `main` push 流程。 ### 快速部署 diff --git a/tests/test_longbridge_api_probe_workflow.py b/tests/test_longbridge_api_probe_workflow.py index 4b2f29f..856022f 100644 --- a/tests/test_longbridge_api_probe_workflow.py +++ b/tests/test_longbridge_api_probe_workflow.py @@ -14,15 +14,20 @@ def test_workflow_uses_hk_environment_and_gcp_secrets(self) -> None: self.assertIn("ref: ${{ inputs.qpk_ref }}", workflow) self.assertIn("google-github-actions/auth@v3", workflow) self.assertIn("google-github-actions/setup-gcloud@v3", workflow) + self.assertIn("CLOUD_RUN_REGION: ${{ vars.CLOUD_RUN_REGION }}", workflow) + self.assertIn("CLOUD_RUN_SERVICE: ${{ vars.CLOUD_RUN_SERVICE }}", workflow) self.assertIn("LONGPORT_APP_KEY_SECRET_NAME: ${{ vars.LONGPORT_APP_KEY_SECRET_NAME }}", workflow) self.assertIn("LONGPORT_APP_SECRET_SECRET_NAME: ${{ vars.LONGPORT_APP_SECRET_SECRET_NAME }}", workflow) self.assertIn("LONGPORT_SECRET_NAME: ${{ vars.LONGPORT_SECRET_NAME }}", workflow) - self.assertIn('gcloud secrets versions access latest --secret="${LONGPORT_APP_KEY_SECRET_NAME}"', workflow) - self.assertIn( - 'gcloud secrets versions access latest --secret="${LONGPORT_APP_SECRET_SECRET_NAME}"', - workflow, - ) - self.assertIn('gcloud secrets versions access latest --secret="${LONGPORT_SECRET_NAME}"', workflow) + self.assertIn("LONGPORT_APP_KEY_VALUE: ${{ secrets.LONGPORT_APP_KEY }}", workflow) + self.assertIn("LONGPORT_APP_SECRET_VALUE: ${{ secrets.LONGPORT_APP_SECRET }}", workflow) + self.assertIn("LONGPORT_ACCESS_TOKEN_VALUE: ${{ secrets.LONGPORT_ACCESS_TOKEN }}", workflow) + self.assertIn("read_secret_manager_value()", workflow) + self.assertIn('read_secret_manager_value "${LONGPORT_APP_KEY_SECRET_NAME}"', workflow) + self.assertIn('read_secret_manager_value "${LONGPORT_APP_SECRET_SECRET_NAME}"', workflow) + self.assertIn('read_secret_manager_value "${LONGPORT_SECRET_NAME}"', workflow) + self.assertIn('gcloud secrets versions access latest --secret="${secret_name}"', workflow) + self.assertIn("--impersonate-service-account", workflow) self.assertIn("python -m pip install -e quant-platform-kit pytest longport", workflow) self.assertIn('LONGBRIDGE_API_PROBE: "1"', workflow) self.assertIn("test_longbridge_fractional_order_api_probe.py", workflow) diff --git a/tests/test_longbridge_api_probe_workflow.sh b/tests/test_longbridge_api_probe_workflow.sh index bafc9ae..6a1f8b9 100755 --- a/tests/test_longbridge_api_probe_workflow.sh +++ b/tests/test_longbridge_api_probe_workflow.sh @@ -12,12 +12,20 @@ grep -Fq "repository: QuantStrategyLab/QuantPlatformKit" "$workflow_file" grep -Fq "ref: \${{ inputs.qpk_ref }}" "$workflow_file" grep -Fq "google-github-actions/auth@v3" "$workflow_file" grep -Fq "google-github-actions/setup-gcloud@v3" "$workflow_file" +grep -Fq "CLOUD_RUN_REGION: \${{ vars.CLOUD_RUN_REGION }}" "$workflow_file" +grep -Fq "CLOUD_RUN_SERVICE: \${{ vars.CLOUD_RUN_SERVICE }}" "$workflow_file" grep -Fq "LONGPORT_APP_KEY_SECRET_NAME: \${{ vars.LONGPORT_APP_KEY_SECRET_NAME }}" "$workflow_file" grep -Fq "LONGPORT_APP_SECRET_SECRET_NAME: \${{ vars.LONGPORT_APP_SECRET_SECRET_NAME }}" "$workflow_file" grep -Fq "LONGPORT_SECRET_NAME: \${{ vars.LONGPORT_SECRET_NAME }}" "$workflow_file" -grep -Fq 'gcloud secrets versions access latest --secret="${LONGPORT_APP_KEY_SECRET_NAME}"' "$workflow_file" -grep -Fq 'gcloud secrets versions access latest --secret="${LONGPORT_APP_SECRET_SECRET_NAME}"' "$workflow_file" -grep -Fq 'gcloud secrets versions access latest --secret="${LONGPORT_SECRET_NAME}"' "$workflow_file" +grep -Fq "LONGPORT_APP_KEY_VALUE: \${{ secrets.LONGPORT_APP_KEY }}" "$workflow_file" +grep -Fq "LONGPORT_APP_SECRET_VALUE: \${{ secrets.LONGPORT_APP_SECRET }}" "$workflow_file" +grep -Fq "LONGPORT_ACCESS_TOKEN_VALUE: \${{ secrets.LONGPORT_ACCESS_TOKEN }}" "$workflow_file" +grep -Fq "read_secret_manager_value()" "$workflow_file" +grep -Fq 'read_secret_manager_value "${LONGPORT_APP_KEY_SECRET_NAME}"' "$workflow_file" +grep -Fq 'read_secret_manager_value "${LONGPORT_APP_SECRET_SECRET_NAME}"' "$workflow_file" +grep -Fq 'read_secret_manager_value "${LONGPORT_SECRET_NAME}"' "$workflow_file" +grep -Fq 'gcloud secrets versions access latest --secret="${secret_name}"' "$workflow_file" +grep -Fq -- "--impersonate-service-account" "$workflow_file" grep -Fq "python -m pip install -e quant-platform-kit pytest longport" "$workflow_file" grep -Fq 'LONGBRIDGE_API_PROBE: "1"' "$workflow_file" grep -Fq "test_longbridge_fractional_order_api_probe.py" "$workflow_file"