diff --git a/.github/workflows/capture-screen.yml b/.github/workflows/capture-screen.yml index 9695647..fe15a11 100644 --- a/.github/workflows/capture-screen.yml +++ b/.github/workflows/capture-screen.yml @@ -3,6 +3,11 @@ name: Capture Gateway Screen on: workflow_dispatch: inputs: + target: + description: Gateway target (from IB_GATEWAY_TARGETS_JSON) + required: false + default: all + type: string screen_action: description: Optional interaction before capturing the screen required: false @@ -19,27 +24,74 @@ on: default: false type: boolean -env: - GCP_PROJECT_ID: interactivebrokersquant - GCP_WORKLOAD_IDENTITY_PROVIDER: projects/303168642265/locations/global/workloadIdentityPools/github-actions/providers/github-ibkr-gateway-main - GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: ibkr-gateway-deploy@interactivebrokersquant.iam.gserviceaccount.com - concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: false jobs: + resolve: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.resolve.outputs.matrix }} + steps: + - name: Resolve gateway targets + id: resolve + env: + TARGETS_JSON: ${{ vars.IB_GATEWAY_TARGETS_JSON }} + SELECTED_TARGET: ${{ github.event.inputs.target || 'all' }} + run: | + import json, os, sys + + raw = os.environ.get("TARGETS_JSON", "").strip() + if not raw: + raise SystemExit("IB_GATEWAY_TARGETS_JSON is required") + + loaded = json.loads(raw) + targets = [] + if isinstance(loaded, dict): + for name, config in loaded.items(): + cfg = dict(config) + cfg["name"] = name + targets.append(cfg) + elif isinstance(loaded, list): + targets = loaded + else: + raise SystemExit("IB_GATEWAY_TARGETS_JSON must be an object or list") + + selected_name = os.environ.get("SELECTED_TARGET", "all") + if selected_name == "all": + selected = targets + else: + selected = [t for t in targets if t["name"] == selected_name] + if not selected: + known = ", ".join(t["name"] for t in targets) + raise SystemExit(f"Unknown target {selected_name!r}. Known: {known}") + + with open(os.environ["GITHUB_OUTPUT"], "a") as f: + f.write("matrix=" + json.dumps({"target": selected}, separators=(",", ":")) + "\n") + print("Targets: " + ", ".join(t["name"] for t in selected)) + shell: python3 + capture: + needs: resolve + if: needs.resolve.outputs.matrix != '{"target":[]}' runs-on: ubuntu-latest timeout-minutes: 8 + strategy: + matrix: ${{ fromJson(needs.resolve.outputs.matrix) }} + fail-fast: false permissions: contents: read id-token: write env: - GCE_USER: ${{ vars.IB_GATEWAY_GCE_USER }} - GCE_INSTANCE_NAME: ${{ vars.IB_GATEWAY_INSTANCE_NAME }} - GCE_ZONE: ${{ vars.IB_GATEWAY_ZONE }} - SSH_PRIVATE_KEY_SECRET_NAME: ${{ vars.IB_GATEWAY_SSH_PRIVATE_KEY_SECRET_NAME }} + TARGET_NAME: ${{ matrix.target.name }} + GCP_PROJECT_ID: ${{ matrix.target.gcp_project_id }} + GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ matrix.target.gcp_workload_identity_provider }} + GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: ${{ matrix.target.gcp_workload_identity_service_account }} + GCE_USER: ${{ matrix.target.gce_user }} + GCE_INSTANCE_NAME: ${{ matrix.target.gce_instance_name }} + GCE_ZONE: ${{ matrix.target.gce_zone }} + SSH_PRIVATE_KEY_SECRET_NAME: ${{ matrix.target.ssh_private_key_secret_name }} SCREEN_ACTION: ${{ github.event.inputs.screen_action }} WAIT_FOR_SECOND_FACTOR: ${{ github.event.inputs.wait_for_second_factor }} steps: diff --git a/.github/workflows/diagnose.yml b/.github/workflows/diagnose.yml index 4c593a8..f53d128 100644 --- a/.github/workflows/diagnose.yml +++ b/.github/workflows/diagnose.yml @@ -5,30 +5,83 @@ on: branches: [ main ] paths: - '.github/workflows/diagnose.yml' - workflow_dispatch: {} - -env: - GCP_PROJECT_ID: interactivebrokersquant - GCP_WORKLOAD_IDENTITY_PROVIDER: projects/303168642265/locations/global/workloadIdentityPools/github-actions/providers/github-ibkr-gateway-main - GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: ibkr-gateway-deploy@interactivebrokersquant.iam.gserviceaccount.com + workflow_dispatch: + inputs: + target: + description: Gateway target (from IB_GATEWAY_TARGETS_JSON) + required: false + default: all + type: string concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: false jobs: + resolve: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.resolve.outputs.matrix }} + steps: + - name: Resolve gateway targets + id: resolve + env: + TARGETS_JSON: ${{ vars.IB_GATEWAY_TARGETS_JSON }} + SELECTED_TARGET: ${{ github.event.inputs.target || 'all' }} + run: | + import json, os, sys + + raw = os.environ.get("TARGETS_JSON", "").strip() + if not raw: + raise SystemExit("IB_GATEWAY_TARGETS_JSON is required") + + loaded = json.loads(raw) + targets = [] + if isinstance(loaded, dict): + for name, config in loaded.items(): + cfg = dict(config) + cfg["name"] = name + targets.append(cfg) + elif isinstance(loaded, list): + targets = loaded + else: + raise SystemExit("IB_GATEWAY_TARGETS_JSON must be an object or list") + + selected_name = os.environ.get("SELECTED_TARGET", "all") + if selected_name == "all": + selected = targets + else: + selected = [t for t in targets if t["name"] == selected_name] + if not selected: + known = ", ".join(t["name"] for t in targets) + raise SystemExit(f"Unknown target {selected_name!r}. Known: {known}") + + with open(os.environ["GITHUB_OUTPUT"], "a") as f: + f.write("matrix=" + json.dumps({"target": selected}, separators=(",", ":")) + "\n") + print("Targets: " + ", ".join(t["name"] for t in selected)) + shell: python3 + diagnose: + needs: resolve + if: needs.resolve.outputs.matrix != '{"target":[]}' runs-on: ubuntu-latest timeout-minutes: 8 + strategy: + matrix: ${{ fromJson(needs.resolve.outputs.matrix) }} + fail-fast: false permissions: contents: read id-token: write env: - GCE_USER: ${{ vars.IB_GATEWAY_GCE_USER }} - GCE_INSTANCE_NAME: ${{ vars.IB_GATEWAY_INSTANCE_NAME }} - GCE_ZONE: ${{ vars.IB_GATEWAY_ZONE }} - DEPLOY_PATH: ${{ vars.IB_GATEWAY_DEPLOY_PATH }} - SSH_PRIVATE_KEY_SECRET_NAME: ${{ vars.IB_GATEWAY_SSH_PRIVATE_KEY_SECRET_NAME }} + TARGET_NAME: ${{ matrix.target.name }} + GCP_PROJECT_ID: ${{ matrix.target.gcp_project_id }} + GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ matrix.target.gcp_workload_identity_provider }} + GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: ${{ matrix.target.gcp_workload_identity_service_account }} + GCE_USER: ${{ matrix.target.gce_user }} + GCE_INSTANCE_NAME: ${{ matrix.target.gce_instance_name }} + GCE_ZONE: ${{ matrix.target.gce_zone }} + DEPLOY_PATH: ${{ matrix.target.deploy_path }} + SSH_PRIVATE_KEY_SECRET_NAME: ${{ matrix.target.ssh_private_key_secret_name }} steps: - name: Authenticate to Google Cloud id: auth