diff --git a/.github/workflows/capture-screen.yml b/.github/workflows/capture-screen.yml index 9e93705..a02eeb3 100644 --- a/.github/workflows/capture-screen.yml +++ b/.github/workflows/capture-screen.yml @@ -55,9 +55,9 @@ jobs: } const selectedName = ${{ toJSON(github.event.inputs.target || 'all') }}; - const selected = selectedName === "all" - ? targets - : targets.filter((target) => target.name === selectedName); + const selected = targets + .map((target, targetIndex) => ({target, targetIndex})) + .filter(({target}) => selectedName === "all" || target.name === selectedName); if (!selected.length) { core.setFailed("Unknown gateway target; choose one of the configured targets"); return; @@ -67,13 +67,18 @@ jobs: const digits = String(name).replace(/\D/g, ""); return digits.length >= 4 ? `U***${digits.slice(-4)}` : ""; }; - core.info(`Targets: ${selected.map((target) => maskedTarget(target.name)).join(", ")}`); - core.setOutput("matrix", JSON.stringify({target: selected})); + core.info(`Targets: ${selected.map(({target}) => maskedTarget(target.name)).join(", ")}`); + const crypto = require("crypto"); + const matrixTargets = selected.map(({target, targetIndex}) => ({ + target_index: targetIndex, + target_digest: crypto.createHash("sha256").update(JSON.stringify(target)).digest("hex") + })); + core.setOutput("matrix", JSON.stringify({include: matrixTargets})); capture: name: Capture gateway screen needs: resolve - if: needs.resolve.outputs.matrix != '{"target":[]}' + if: needs.resolve.outputs.matrix != '{"include":[]}' runs-on: ubuntu-latest timeout-minutes: 8 strategy: @@ -87,39 +92,81 @@ jobs: WAIT_FOR_SECOND_FACTOR: ${{ github.event.inputs.wait_for_second_factor }} steps: - name: Mask target metadata + id: metadata uses: actions/github-script@v8 with: script: | + const raw = ${{ toJSON(vars.IB_GATEWAY_TARGETS_JSON) }}; + if (!raw || !raw.trim()) { + core.setFailed("IB_GATEWAY_TARGETS_JSON is required"); + return; + } + + const loaded = JSON.parse(raw); + const targets = Array.isArray(loaded) + ? loaded + : Object.entries(loaded).map(([name, config]) => ({...config, name})); + const target = targets[Number(${{ toJSON(matrix.target_index) }})]; + if (!target) { + core.setFailed("Resolved gateway target is unavailable"); + return; + } + const crypto = require("crypto"); + const targetDigest = crypto.createHash("sha256").update(JSON.stringify(target)).digest("hex"); + if (targetDigest !== ${{ toJSON(matrix.target_digest) }}) { + core.setFailed("Resolved gateway target changed between jobs"); + return; + } + const metadata = { - TARGET_NAME: ${{ toJSON(matrix.target.name) }}, - GCP_PROJECT_ID: ${{ toJSON(matrix.target.gcp_project_id) }}, - GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ toJSON(matrix.target.gcp_workload_identity_provider) }}, - GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: ${{ toJSON(matrix.target.gcp_workload_identity_service_account) }}, - GCE_USER: ${{ toJSON(matrix.target.gce_user) }}, - GCE_INSTANCE_NAME: ${{ toJSON(matrix.target.gce_instance_name) }}, - GCE_ZONE: ${{ toJSON(matrix.target.gce_zone) }}, - DEPLOY_PATH: ${{ toJSON(matrix.target.deploy_path) }}, - SSH_PRIVATE_KEY_SECRET_NAME: ${{ toJSON(matrix.target.ssh_private_key_secret_name) }}, - IB_GATEWAY_MODE: ${{ toJSON(matrix.target.mode) }}, - IB_GATEWAY_CONTAINER_NAME: ${{ toJSON(matrix.target.container_name) }}, - IB_GATEWAY_COMPOSE_SERVICE_NAME: ${{ toJSON(matrix.target.compose_service_name) }}, - IB_GATEWAY_UNIT_SUFFIX: ${{ toJSON(matrix.target.unit_suffix || '') }} + TARGET_NAME: target.name, + GCP_PROJECT_ID: target.gcp_project_id, + GCP_WORKLOAD_IDENTITY_PROVIDER: target.gcp_workload_identity_provider, + GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: target.gcp_workload_identity_service_account, + GCE_USER: target.gce_user, + GCE_INSTANCE_NAME: target.gce_instance_name, + GCE_ZONE: target.gce_zone, + DEPLOY_PATH: target.deploy_path, + SSH_PRIVATE_KEY_SECRET_NAME: target.ssh_private_key_secret_name, + IB_GATEWAY_MODE: target.mode, + IB_GATEWAY_CONTAINER_NAME: target.container_name, + IB_GATEWAY_COMPOSE_SERVICE_NAME: target.compose_service_name, + IB_GATEWAY_UNIT_SUFFIX: target.unit_suffix || "" }; + const authenticationOutputs = new Set([ + "GCP_PROJECT_ID", + "GCP_WORKLOAD_IDENTITY_PROVIDER", + "GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT" + ]); for (const [name, value] of Object.entries(metadata)) { - if (value) core.setSecret(String(value)); + if (value && !authenticationOutputs.has(name)) core.setSecret(String(value)); core.exportVariable(name, value || ""); + if (authenticationOutputs.has(name)) core.setOutput(name.toLowerCase(), value || ""); + } + + - name: Mask authentication metadata + uses: actions/github-script@v8 + with: + script: | + const values = [ + ${{ toJSON(steps.metadata.outputs.gcp_project_id) }}, + ${{ toJSON(steps.metadata.outputs.gcp_workload_identity_provider) }}, + ${{ toJSON(steps.metadata.outputs.gcp_workload_identity_service_account) }} + ]; + for (const value of values) { + if (value) core.setSecret(String(value)); } - name: Authenticate to Google Cloud uses: google-github-actions/auth@v3 with: - workload_identity_provider: ${{ matrix.target.gcp_workload_identity_provider }} - service_account: ${{ matrix.target.gcp_workload_identity_service_account }} + workload_identity_provider: ${{ steps.metadata.outputs.gcp_workload_identity_provider }} + service_account: ${{ steps.metadata.outputs.gcp_workload_identity_service_account }} - name: Set up gcloud uses: google-github-actions/setup-gcloud@v3 with: - project_id: ${{ matrix.target.gcp_project_id }} + project_id: ${{ steps.metadata.outputs.gcp_project_id }} version: '>= 416.0.0' - name: Prepare SSH key diff --git a/.github/workflows/diagnose.yml b/.github/workflows/diagnose.yml index 9e403ef..92569af 100644 --- a/.github/workflows/diagnose.yml +++ b/.github/workflows/diagnose.yml @@ -44,9 +44,9 @@ jobs: } const selectedName = ${{ toJSON(github.event.inputs.target || 'all') }}; - const selected = selectedName === "all" - ? targets - : targets.filter((target) => target.name === selectedName); + const selected = targets + .map((target, targetIndex) => ({target, targetIndex})) + .filter(({target}) => selectedName === "all" || target.name === selectedName); if (!selected.length) { core.setFailed("Unknown gateway target; choose one of the configured targets"); return; @@ -56,13 +56,18 @@ jobs: const digits = String(name).replace(/\D/g, ""); return digits.length >= 4 ? `U***${digits.slice(-4)}` : ""; }; - core.info(`Targets: ${selected.map((target) => maskedTarget(target.name)).join(", ")}`); - core.setOutput("matrix", JSON.stringify({target: selected})); + core.info(`Targets: ${selected.map(({target}) => maskedTarget(target.name)).join(", ")}`); + const crypto = require("crypto"); + const matrixTargets = selected.map(({target, targetIndex}) => ({ + target_index: targetIndex, + target_digest: crypto.createHash("sha256").update(JSON.stringify(target)).digest("hex") + })); + core.setOutput("matrix", JSON.stringify({include: matrixTargets})); diagnose: name: Diagnose gateway target needs: resolve - if: needs.resolve.outputs.matrix != '{"target":[]}' + if: needs.resolve.outputs.matrix != '{"include":[]}' runs-on: ubuntu-latest timeout-minutes: 8 strategy: @@ -73,40 +78,82 @@ jobs: id-token: write steps: - name: Mask target metadata + id: metadata uses: actions/github-script@v8 with: script: | + const raw = ${{ toJSON(vars.IB_GATEWAY_TARGETS_JSON) }}; + if (!raw || !raw.trim()) { + core.setFailed("IB_GATEWAY_TARGETS_JSON is required"); + return; + } + + const loaded = JSON.parse(raw); + const targets = Array.isArray(loaded) + ? loaded + : Object.entries(loaded).map(([name, config]) => ({...config, name})); + const target = targets[Number(${{ toJSON(matrix.target_index) }})]; + if (!target) { + core.setFailed("Resolved gateway target is unavailable"); + return; + } + const crypto = require("crypto"); + const targetDigest = crypto.createHash("sha256").update(JSON.stringify(target)).digest("hex"); + if (targetDigest !== ${{ toJSON(matrix.target_digest) }}) { + core.setFailed("Resolved gateway target changed between jobs"); + return; + } + const metadata = { - TARGET_NAME: ${{ toJSON(matrix.target.name) }}, - GCP_PROJECT_ID: ${{ toJSON(matrix.target.gcp_project_id) }}, - GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ toJSON(matrix.target.gcp_workload_identity_provider) }}, - GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: ${{ toJSON(matrix.target.gcp_workload_identity_service_account) }}, - GCE_USER: ${{ toJSON(matrix.target.gce_user) }}, - GCE_INSTANCE_NAME: ${{ toJSON(matrix.target.gce_instance_name) }}, - GCE_ZONE: ${{ toJSON(matrix.target.gce_zone) }}, - DEPLOY_PATH: ${{ toJSON(matrix.target.deploy_path) }}, - SSH_PRIVATE_KEY_SECRET_NAME: ${{ toJSON(matrix.target.ssh_private_key_secret_name) }}, - IB_GATEWAY_MODE: ${{ toJSON(matrix.target.mode) }}, - IB_GATEWAY_CONTAINER_NAME: ${{ toJSON(matrix.target.container_name) }}, - IB_GATEWAY_COMPOSE_SERVICE_NAME: ${{ toJSON(matrix.target.compose_service_name) }}, - IB_GATEWAY_UNIT_SUFFIX: ${{ toJSON(matrix.target.unit_suffix || '') }} + TARGET_NAME: target.name, + GCP_PROJECT_ID: target.gcp_project_id, + GCP_WORKLOAD_IDENTITY_PROVIDER: target.gcp_workload_identity_provider, + GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: target.gcp_workload_identity_service_account, + GCE_USER: target.gce_user, + GCE_INSTANCE_NAME: target.gce_instance_name, + GCE_ZONE: target.gce_zone, + DEPLOY_PATH: target.deploy_path, + SSH_PRIVATE_KEY_SECRET_NAME: target.ssh_private_key_secret_name, + IB_GATEWAY_MODE: target.mode, + IB_GATEWAY_CONTAINER_NAME: target.container_name, + IB_GATEWAY_COMPOSE_SERVICE_NAME: target.compose_service_name, + IB_GATEWAY_UNIT_SUFFIX: target.unit_suffix || "" }; + const authenticationOutputs = new Set([ + "GCP_PROJECT_ID", + "GCP_WORKLOAD_IDENTITY_PROVIDER", + "GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT" + ]); for (const [name, value] of Object.entries(metadata)) { - if (value) core.setSecret(String(value)); + if (value && !authenticationOutputs.has(name)) core.setSecret(String(value)); core.exportVariable(name, value || ""); + if (authenticationOutputs.has(name)) core.setOutput(name.toLowerCase(), value || ""); + } + + - name: Mask authentication metadata + uses: actions/github-script@v8 + with: + script: | + const values = [ + ${{ toJSON(steps.metadata.outputs.gcp_project_id) }}, + ${{ toJSON(steps.metadata.outputs.gcp_workload_identity_provider) }}, + ${{ toJSON(steps.metadata.outputs.gcp_workload_identity_service_account) }} + ]; + for (const value of values) { + if (value) core.setSecret(String(value)); } - name: Authenticate to Google Cloud id: auth uses: google-github-actions/auth@v3 with: - workload_identity_provider: ${{ matrix.target.gcp_workload_identity_provider }} - service_account: ${{ matrix.target.gcp_workload_identity_service_account }} + workload_identity_provider: ${{ steps.metadata.outputs.gcp_workload_identity_provider }} + service_account: ${{ steps.metadata.outputs.gcp_workload_identity_service_account }} - name: Set up gcloud uses: google-github-actions/setup-gcloud@v3 with: - project_id: ${{ matrix.target.gcp_project_id }} + project_id: ${{ steps.metadata.outputs.gcp_project_id }} version: '>= 416.0.0' - name: Prepare SSH key @@ -155,6 +202,7 @@ jobs: REMOTE_DIAG_COMMAND=$(cat <<'EOF' set -euo pipefail cd __DEPLOY_PATH__ + CONTAINER_NAME=__CONTAINER_NAME__ section() { printf '\n===== %s =====\n' "$1" @@ -169,8 +217,8 @@ jobs: section "clock" date -u || true - sudo docker exec ib-gateway bash -lc 'date -u || true' || true - sudo docker exec ib-gateway bash -lc 'python3 - <<'"'"'PY'"'"' + sudo docker exec "$CONTAINER_NAME" bash -lc 'date -u || true' || true + sudo docker exec "$CONTAINER_NAME" bash -lc 'python3 - <<'"'"'PY'"'"' import os import time @@ -181,6 +229,53 @@ jobs: print("totp_secret_present", False) PY' || true + section "runtime executable metadata" + printf 'host_arch=%s\n' "$(uname -m)" + if sudo docker inspect "$CONTAINER_NAME" >/dev/null 2>&1; then + sudo docker inspect --format \ + 'container_state={{.State.Status}} exit_code={{.State.ExitCode}} oom_killed={{.State.OOMKilled}} restart_count={{.RestartCount}}' \ + "$CONTAINER_NAME" || true + image_id="$(sudo docker inspect --format '{{.Image}}' "$CONTAINER_NAME" 2>/dev/null || true)" + if [ -n "$image_id" ]; then + sudo docker image inspect --format 'image_os={{.Os}} image_arch={{.Architecture}}' "$image_id" || true + fi + runtime_metadata_dir="$(mktemp -d)" + if sudo docker cp "$CONTAINER_NAME":/home/ibgateway/scripts/run.sh "$runtime_metadata_dir/run.sh" >/dev/null 2>&1; then + if command -v file >/dev/null 2>&1; then + printf 'run_file_type=%s\n' "$(file -b "$runtime_metadata_dir/run.sh" || true)" + else + echo "run_file_type_unavailable" + fi + stat -c 'run_mode=%a run_size=%s' "$runtime_metadata_dir/run.sh" 2>/dev/null || echo "run_stat_unavailable" + if command -v od >/dev/null 2>&1; then + printf 'run_first_bytes=' + od -An -tx1 -N16 "$runtime_metadata_dir/run.sh" 2>/dev/null | tr -d ' \n' || true + printf '\n' + else + echo "run_first_bytes_unavailable" + fi + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$runtime_metadata_dir/run.sh" 2>/dev/null | awk '{print "run_sha256=" $1}' || true + else + echo "run_sha256_unavailable" + fi + else + echo "run_file_unavailable" + fi + if sudo docker cp "$CONTAINER_NAME":/bin/bash "$runtime_metadata_dir/bash" >/dev/null 2>&1; then + if command -v file >/dev/null 2>&1; then + printf 'bash_file_type=%s\n' "$(file -b "$runtime_metadata_dir/bash" || true)" + else + echo "bash_file_type_unavailable" + fi + else + echo "bash_file_unavailable" + fi + rm -rf "$runtime_metadata_dir" + else + echo "gateway_container_unavailable" + fi + section "docker compose ps" sudo docker compose ps || true @@ -194,13 +289,13 @@ jobs: sudo journalctl -u ibkr-2fa-bot.service -n 100 --no-pager || true section "container processes" - sudo docker exec ib-gateway bash -lc 'pgrep -a -f "2fa_bot.py|ibgateway|socat|Xvfb|x11vnc|ibcstart" || true' || true + sudo docker exec "$CONTAINER_NAME" bash -lc 'pgrep -a -f "2fa_bot.py|ibgateway|socat|Xvfb|x11vnc|ibcstart" || true' || true section "container listening ports" - sudo docker exec ib-gateway bash -lc 'ss -ltnp 2>/dev/null | grep -E ":(4001|4002|4003|4004|5900)\b" || netstat -ltnp 2>/dev/null | grep -E ":(4001|4002|4003|4004|5900)\b" || true' || true + sudo docker exec "$CONTAINER_NAME" bash -lc 'ss -ltnp 2>/dev/null | grep -E ":(4001|4002|4003|4004|5900)\b" || netstat -ltnp 2>/dev/null | grep -E ":(4001|4002|4003|4004|5900)\b" || true' || true section "x11 windows" - sudo docker exec ib-gateway bash -lc ' + sudo docker exec "$CONTAINER_NAME" bash -lc ' export DISPLAY=:1 ids=$(xdotool search --name ".*" 2>/dev/null || true) if [ -z "$ids" ]; then @@ -221,14 +316,16 @@ jobs: ' || true section "2fa bot log" - sudo docker exec ib-gateway bash -lc 'tail -n 160 /home/ibgateway/2fa.log 2>/dev/null || echo "2fa.log missing"' || true + sudo docker exec "$CONTAINER_NAME" bash -lc 'tail -n 160 /home/ibgateway/2fa.log 2>/dev/null || echo "2fa.log missing"' || true section "ib gateway docker log" - sudo docker logs --tail 220 ib-gateway 2>&1 || true + sudo docker logs --tail 220 "$CONTAINER_NAME" 2>&1 || true EOF ) deploy_path_quoted="$(printf '%q' "${DEPLOY_PATH}")" + container_name_quoted="$(printf '%q' "${IB_GATEWAY_CONTAINER_NAME:-ib-gateway}")" REMOTE_DIAG_COMMAND="${REMOTE_DIAG_COMMAND/__DEPLOY_PATH__/${deploy_path_quoted}}" + REMOTE_DIAG_COMMAND="${REMOTE_DIAG_COMMAND/__CONTAINER_NAME__/${container_name_quoted}}" redact_diagnostics() { python3 -c "$(cat <<'PY' diff --git a/.github/workflows/remote-maintenance.yml b/.github/workflows/remote-maintenance.yml index 922301f..fbb6bda 100644 --- a/.github/workflows/remote-maintenance.yml +++ b/.github/workflows/remote-maintenance.yml @@ -51,9 +51,9 @@ jobs: } const selectedName = ${{ toJSON(github.event.inputs.target || 'all') }}; - const selected = selectedName === "all" - ? targets - : targets.filter((target) => target.name === selectedName); + const selected = targets + .map((target, targetIndex) => ({target, targetIndex})) + .filter(({target}) => selectedName === "all" || target.name === selectedName); if (!selected.length) { core.setFailed("Unknown gateway target; choose one of the configured targets"); return; @@ -63,13 +63,18 @@ jobs: const digits = String(name).replace(/\D/g, ""); return digits.length >= 4 ? `U***${digits.slice(-4)}` : ""; }; - core.info(`Targets: ${selected.map((target) => maskedTarget(target.name)).join(", ")}`); - core.setOutput("matrix", JSON.stringify({target: selected})); + core.info(`Targets: ${selected.map(({target}) => maskedTarget(target.name)).join(", ")}`); + const crypto = require("crypto"); + const matrixTargets = selected.map(({target, targetIndex}) => ({ + target_index: targetIndex, + target_digest: crypto.createHash("sha256").update(JSON.stringify(target)).digest("hex") + })); + core.setOutput("matrix", JSON.stringify({include: matrixTargets})); maintenance: name: Maintain gateway target needs: resolve - if: needs.resolve.outputs.matrix != '{"target":[]}' + if: needs.resolve.outputs.matrix != '{"include":[]}' runs-on: ubuntu-latest timeout-minutes: 8 strategy: @@ -82,39 +87,81 @@ jobs: MAINTENANCE_ACTION: ${{ github.event.inputs.action }} steps: - name: Mask target metadata + id: metadata uses: actions/github-script@v8 with: script: | + const raw = ${{ toJSON(vars.IB_GATEWAY_TARGETS_JSON) }}; + if (!raw || !raw.trim()) { + core.setFailed("IB_GATEWAY_TARGETS_JSON is required"); + return; + } + + const loaded = JSON.parse(raw); + const targets = Array.isArray(loaded) + ? loaded + : Object.entries(loaded).map(([name, config]) => ({...config, name})); + const target = targets[Number(${{ toJSON(matrix.target_index) }})]; + if (!target) { + core.setFailed("Resolved gateway target is unavailable"); + return; + } + const crypto = require("crypto"); + const targetDigest = crypto.createHash("sha256").update(JSON.stringify(target)).digest("hex"); + if (targetDigest !== ${{ toJSON(matrix.target_digest) }}) { + core.setFailed("Resolved gateway target changed between jobs"); + return; + } + const metadata = { - TARGET_NAME: ${{ toJSON(matrix.target.name) }}, - GCP_PROJECT_ID: ${{ toJSON(matrix.target.gcp_project_id) }}, - GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ toJSON(matrix.target.gcp_workload_identity_provider) }}, - GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: ${{ toJSON(matrix.target.gcp_workload_identity_service_account) }}, - GCE_USER: ${{ toJSON(matrix.target.gce_user) }}, - GCE_INSTANCE_NAME: ${{ toJSON(matrix.target.gce_instance_name) }}, - GCE_ZONE: ${{ toJSON(matrix.target.gce_zone) }}, - DEPLOY_PATH: ${{ toJSON(matrix.target.deploy_path) }}, - SSH_PRIVATE_KEY_SECRET_NAME: ${{ toJSON(matrix.target.ssh_private_key_secret_name) }}, - IB_GATEWAY_MODE: ${{ toJSON(matrix.target.mode) }}, - IB_GATEWAY_CONTAINER_NAME: ${{ toJSON(matrix.target.container_name) }}, - IB_GATEWAY_COMPOSE_SERVICE_NAME: ${{ toJSON(matrix.target.compose_service_name) }}, - IB_GATEWAY_UNIT_SUFFIX: ${{ toJSON(matrix.target.unit_suffix || '') }} + TARGET_NAME: target.name, + GCP_PROJECT_ID: target.gcp_project_id, + GCP_WORKLOAD_IDENTITY_PROVIDER: target.gcp_workload_identity_provider, + GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: target.gcp_workload_identity_service_account, + GCE_USER: target.gce_user, + GCE_INSTANCE_NAME: target.gce_instance_name, + GCE_ZONE: target.gce_zone, + DEPLOY_PATH: target.deploy_path, + SSH_PRIVATE_KEY_SECRET_NAME: target.ssh_private_key_secret_name, + IB_GATEWAY_MODE: target.mode, + IB_GATEWAY_CONTAINER_NAME: target.container_name, + IB_GATEWAY_COMPOSE_SERVICE_NAME: target.compose_service_name, + IB_GATEWAY_UNIT_SUFFIX: target.unit_suffix || "" }; + const authenticationOutputs = new Set([ + "GCP_PROJECT_ID", + "GCP_WORKLOAD_IDENTITY_PROVIDER", + "GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT" + ]); for (const [name, value] of Object.entries(metadata)) { - if (value) core.setSecret(String(value)); + if (value && !authenticationOutputs.has(name)) core.setSecret(String(value)); core.exportVariable(name, value || ""); + if (authenticationOutputs.has(name)) core.setOutput(name.toLowerCase(), value || ""); + } + + - name: Mask authentication metadata + uses: actions/github-script@v8 + with: + script: | + const values = [ + ${{ toJSON(steps.metadata.outputs.gcp_project_id) }}, + ${{ toJSON(steps.metadata.outputs.gcp_workload_identity_provider) }}, + ${{ toJSON(steps.metadata.outputs.gcp_workload_identity_service_account) }} + ]; + for (const value of values) { + if (value) core.setSecret(String(value)); } - name: Authenticate to Google Cloud uses: google-github-actions/auth@v3 with: - workload_identity_provider: ${{ matrix.target.gcp_workload_identity_provider }} - service_account: ${{ matrix.target.gcp_workload_identity_service_account }} + workload_identity_provider: ${{ steps.metadata.outputs.gcp_workload_identity_provider }} + service_account: ${{ steps.metadata.outputs.gcp_workload_identity_service_account }} - name: Set up gcloud uses: google-github-actions/setup-gcloud@v3 with: - project_id: ${{ matrix.target.gcp_project_id }} + project_id: ${{ steps.metadata.outputs.gcp_project_id }} version: '>= 416.0.0' - name: Prepare SSH key diff --git a/tests/test_workflow_shared_config.sh b/tests/test_workflow_shared_config.sh index 7a26f80..5dde48c 100644 --- a/tests/test_workflow_shared_config.sh +++ b/tests/test_workflow_shared_config.sh @@ -147,10 +147,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: ${{ toJSON(matrix.target.deploy_path) }}' "$maintenance_workflow_file" -grep -Fq 'IB_GATEWAY_MODE: ${{ toJSON(matrix.target.mode) }}' "$maintenance_workflow_file" -grep -Fq 'IB_GATEWAY_CONTAINER_NAME: ${{ toJSON(matrix.target.container_name) }}' "$maintenance_workflow_file" -grep -Fq 'IB_GATEWAY_COMPOSE_SERVICE_NAME: ${{ toJSON(matrix.target.compose_service_name) }}' "$maintenance_workflow_file" +grep -Fq 'DEPLOY_PATH: target.deploy_path' "$maintenance_workflow_file" +grep -Fq 'IB_GATEWAY_MODE: target.mode' "$maintenance_workflow_file" +grep -Fq 'IB_GATEWAY_CONTAINER_NAME: target.container_name' "$maintenance_workflow_file" +grep -Fq 'IB_GATEWAY_COMPOSE_SERVICE_NAME: 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" @@ -162,6 +162,12 @@ grep -Fq 'redact_diagnostics()' "$diagnose_workflow_file" grep -Fq 'sensitive_assignment_pattern.sub(r"\1\2", line)' "$diagnose_workflow_file" grep -Fq 'set -o pipefail' "$diagnose_workflow_file" grep -Fq '| redact_diagnostics' "$diagnose_workflow_file" +grep -Fq 'section "runtime executable metadata"' "$diagnose_workflow_file" +grep -Fq 'image_os={{.Os}} image_arch={{.Architecture}}' "$diagnose_workflow_file" +grep -Fq 'run_first_bytes=' "$diagnose_workflow_file" +grep -Fq 'CONTAINER_NAME=__CONTAINER_NAME__' "$diagnose_workflow_file" +grep -Fq 'sudo docker inspect "$CONTAINER_NAME"' "$diagnose_workflow_file" +grep -Fq 'sudo docker cp "$CONTAINER_NAME":/home/ibgateway/scripts/run.sh' "$diagnose_workflow_file" ! grep -Fq 'actions/checkout' "$diagnose_workflow_file" ! grep -R -Fxq ' shell: python3' "$repo_dir/.github/workflows" @@ -225,6 +231,14 @@ do grep -Fq 'const raw = ${{ toJSON(vars.IB_GATEWAY_TARGETS_JSON) }};' "$resolver_workflow" grep -Fq 'return digits.length >= 4 ? `U***${digits.slice(-4)}` : "";' "$resolver_workflow" grep -Fq 'if (value) core.setSecret(String(value));' "$resolver_workflow" + grep -Fq 'core.setOutput("matrix", JSON.stringify({include: matrixTargets}));' "$resolver_workflow" + grep -Fq 'const target = targets[Number(${{ toJSON(matrix.target_index) }})];' "$resolver_workflow" + grep -Fq 'target_digest: crypto.createHash("sha256").update(JSON.stringify(target)).digest("hex")' "$resolver_workflow" + grep -Fq 'if (targetDigest !== ${{ toJSON(matrix.target_digest) }}) {' "$resolver_workflow" + grep -Fq 'core.setFailed("Resolved gateway target changed between jobs")' "$resolver_workflow" + grep -Fq 'core.setOutput(name.toLowerCase(), value || "");' "$resolver_workflow" + grep -Fq 'workload_identity_provider: ${{ steps.metadata.outputs.gcp_workload_identity_provider }}' "$resolver_workflow" + ! grep -Fq 'matrix.target.' "$resolver_workflow" grep -Fq 'core.setFailed("Unknown gateway target; choose one of the configured targets")' "$resolver_workflow" ! grep -Fq 'Unknown gateway target: ${selectedName}' "$resolver_workflow" ! grep -Fq 'TARGETS_JSON: ${{ vars.IB_GATEWAY_TARGETS_JSON }}' "$resolver_workflow"