diff --git a/.github/workflows/remote-maintenance.yml b/.github/workflows/remote-maintenance.yml index 7d23717..d37297c 100644 --- a/.github/workflows/remote-maintenance.yml +++ b/.github/workflows/remote-maintenance.yml @@ -3,6 +3,11 @@ name: Remote Gateway Maintenance on: workflow_dispatch: inputs: + target: + description: Gateway target (from IB_GATEWAY_TARGETS_JSON) + required: false + default: all + type: string action: description: Maintenance action to run on the gateway VM required: true @@ -15,32 +20,72 @@ on: - restart-gateway - status -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 + maintenance: + 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 }} - IB_GATEWAY_MODE: ${{ vars.IB_GATEWAY_MODE }} - IB_GATEWAY_CONTAINER_NAME: ${{ vars.IB_GATEWAY_CONTAINER_NAME }} - IB_GATEWAY_COMPOSE_SERVICE_NAME: ${{ vars.IB_GATEWAY_COMPOSE_SERVICE_NAME }} - IB_GATEWAY_UNIT_SUFFIX: ${{ vars.IB_GATEWAY_UNIT_SUFFIX }} - 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 }} + IB_GATEWAY_MODE: ${{ matrix.target.mode }} + IB_GATEWAY_CONTAINER_NAME: ${{ matrix.target.container_name }} + IB_GATEWAY_COMPOSE_SERVICE_NAME: ${{ matrix.target.compose_service_name }} + IB_GATEWAY_UNIT_SUFFIX: ${{ matrix.target.unit_suffix || '' }} + SSH_PRIVATE_KEY_SECRET_NAME: ${{ matrix.target.ssh_private_key_secret_name }} MAINTENANCE_ACTION: ${{ github.event.inputs.action }} steps: - name: Authenticate to Google Cloud diff --git a/tests/test_workflow_shared_config.sh b/tests/test_workflow_shared_config.sh index feaee4f..b9a8cfb 100644 --- a/tests/test_workflow_shared_config.sh +++ b/tests/test_workflow_shared_config.sh @@ -146,10 +146,10 @@ done grep -Fq 'stop-gateway' "$maintenance_workflow_file" grep -Fq 'restart-gateway' "$maintenance_workflow_file" grep -Fq 'status' "$maintenance_workflow_file" -grep -Fq 'DEPLOY_PATH: ${{ vars.IB_GATEWAY_DEPLOY_PATH }}' "$maintenance_workflow_file" -grep -Fq 'IB_GATEWAY_MODE: ${{ vars.IB_GATEWAY_MODE }}' "$maintenance_workflow_file" -grep -Fq 'IB_GATEWAY_CONTAINER_NAME: ${{ vars.IB_GATEWAY_CONTAINER_NAME }}' "$maintenance_workflow_file" -grep -Fq 'IB_GATEWAY_COMPOSE_SERVICE_NAME: ${{ vars.IB_GATEWAY_COMPOSE_SERVICE_NAME }}' "$maintenance_workflow_file" +grep -Fq 'DEPLOY_PATH: ${{ matrix.target.deploy_path }}' "$maintenance_workflow_file" +grep -Fq 'IB_GATEWAY_MODE: ${{ matrix.target.mode }}' "$maintenance_workflow_file" +grep -Fq 'IB_GATEWAY_CONTAINER_NAME: ${{ matrix.target.container_name }}' "$maintenance_workflow_file" +grep -Fq 'IB_GATEWAY_COMPOSE_SERVICE_NAME: ${{ matrix.target.compose_service_name }}' "$maintenance_workflow_file" grep -Fq 'sudo systemctl disable --now' "$maintenance_workflow_file" grep -Fq 'sudo docker update --restart=no "${container_name}"' "$maintenance_workflow_file" grep -Fq 'sudo docker compose down' "$maintenance_workflow_file"