ci: deploy workflow matrix

This commit is contained in:
pragmaxim
2026-03-04 08:42:25 +01:00
parent 73b3f2a2a1
commit 3bc29ea8d8
4 changed files with 239 additions and 19 deletions

121
.github/scripts/prepare_deploy_plan.py vendored Executable file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python3
import json
import os
import re
import sys
from pathlib import Path
def fail(message: str) -> None:
print(f"error: {message}", file=sys.stderr)
raise SystemExit(1)
def matchable_name(coin: str) -> str:
marker = "_testnet"
idx = coin.find(marker)
if idx != -1:
return coin[:idx] + "=test"
return coin + "=main"
def load_runner_map(vars_map: dict) -> dict:
prefix = "BB_RUNNER_"
mapping = {}
for key, value in vars_map.items():
if not key.startswith(prefix):
continue
coin = key[len(prefix):].strip()
runner = "" if value is None else str(value).strip()
if coin and runner:
mapping[coin] = runner
return mapping
def parse_requested_coins(raw: str, available: dict) -> list[str]:
text = raw.strip()
if not text:
fail("coins input is empty")
if text.upper() == "ALL":
coins = sorted(available.keys())
if not coins:
fail("no BB_RUNNER_* variables found")
return coins
tokens = [part.strip() for part in re.split(r"[\s,]+", text) if part.strip()]
if not tokens:
fail("coins input resolved to an empty list")
if any(token.upper() == "ALL" for token in tokens):
fail("ALL must be used alone")
seen = set()
result = []
for coin in tokens:
if coin in seen:
continue
seen.add(coin)
result.append(coin)
return result
def main() -> None:
workspace = Path(os.environ.get("GITHUB_WORKSPACE", ".")).resolve()
vars_map = json.loads(os.environ.get("VARS_JSON", "{}"))
coins_input = os.environ.get("COINS_INPUT", "")
runner_map = load_runner_map(vars_map)
if not runner_map:
fail("no BB_RUNNER_* variables found")
requested = parse_requested_coins(coins_input, runner_map)
tests_path = workspace / "tests" / "tests.json"
configs_dir = workspace / "configs" / "coins"
try:
tests_cfg = json.loads(tests_path.read_text(encoding="utf-8"))
except Exception as exc:
fail(f"cannot read {tests_path}: {exc}")
deploy_matrix = []
e2e_names = []
for coin in requested:
if coin not in runner_map:
fail(f"missing BB_RUNNER_{coin}")
coin_cfg_path = configs_dir / f"{coin}.json"
if not coin_cfg_path.exists():
fail(f"unknown coin '{coin}' (missing {coin_cfg_path})")
test_cfg = tests_cfg.get(coin)
if not isinstance(test_cfg, dict) or "connectivity" not in test_cfg:
fail(f"coin '{coin}' has no connectivity tests in tests/tests.json")
deploy_matrix.append({"coin": coin, "runner": runner_map[coin]})
e2e_names.append(matchable_name(coin))
unique_names = sorted(set(e2e_names))
if not unique_names:
fail("no coins selected after validation")
escaped = [re.escape(name) for name in unique_names]
e2e_regex = "TestIntegration/(" + "|".join(escaped) + ")/api"
output_file = os.environ.get("GITHUB_OUTPUT")
if not output_file:
fail("GITHUB_OUTPUT is not set")
with open(output_file, "a", encoding="utf-8") as out:
out.write(f"deploy_matrix={json.dumps(deploy_matrix, separators=(',', ':'))}\n")
out.write(f"e2e_regex={e2e_regex}\n")
out.write(f"coins_csv={','.join(requested)}\n")
print("Selected coins:", ", ".join(requested))
print("E2E regex:", e2e_regex)
if __name__ == "__main__":
main()

79
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,79 @@
name: Deploy
on:
workflow_dispatch:
inputs:
coins:
description: "Comma-separated coin aliases from configs/coins, or ALL"
required: true
ref:
description: "Git ref to deploy (leave empty for current ref)"
required: false
default: ""
permissions:
contents: read
jobs:
prepare:
name: Prepare Plan
runs-on: ubuntu-latest
outputs:
deploy_matrix: ${{ steps.plan.outputs.deploy_matrix }}
e2e_regex: ${{ steps.plan.outputs.e2e_regex }}
coins_csv: ${{ steps.plan.outputs.coins_csv }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref != '' && inputs.ref || github.ref }}
- name: Build deploy/e2e plan
id: plan
env:
VARS_JSON: ${{ toJSON(vars) }}
COINS_INPUT: ${{ inputs.coins }}
run: ./.github/scripts/prepare_deploy_plan.py
deploy:
name: Deploy (${{ matrix.coin }})
needs: prepare
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.prepare.outputs.deploy_matrix) }}
runs-on: [self-hosted, Linux, X64, "${{ matrix.runner }}"]
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref != '' && inputs.ref || github.ref }}
- name: Export repository variables
uses: ./.github/actions/export-repository-variables
with:
vars_json: ${{ toJSON(vars) }}
- name: Deploy blockbook package
run: ./contrib/scripts/deploy-blockbook-local.sh "${{ matrix.coin }}"
e2e-tests:
name: E2E Tests (post-deploy)
needs: [prepare, deploy]
if: ${{ needs.deploy.result == 'success' }}
runs-on: [self-hosted, Linux, X64]
env:
E2E_REGEX: ${{ needs.prepare.outputs.e2e_regex }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref != '' && inputs.ref || github.ref }}
- name: Export repository variables
uses: ./.github/actions/export-repository-variables
with:
vars_json: ${{ toJSON(vars) }}
- name: Run e2e tests
run: make test-e2e ARGS="-v -run ${E2E_REGEX}"

View File

@@ -1,4 +1,4 @@
name: CI
name: Testing
on:
push:
@@ -53,21 +53,3 @@ jobs:
- name: Run integration tests
run: make test-integration ARGS="-v"
e2e-tests:
name: E2E Tests (Blockbook API)
runs-on: [self-hosted, Linux, X64]
needs: integration-tests
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Export repository variables
uses: ./.github/actions/export-repository-variables
with:
vars_json: ${{ toJSON(vars) }}
- name: Run e2e tests
run: make test-e2e ARGS="-v"

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 1 ]]; then
echo "Usage: $(basename "$0") <coin-alias>" >&2
exit 1
fi
coin="$1"
config="configs/coins/${coin}.json"
if [[ ! -f "$config" ]]; then
echo "error: missing coin config $config" >&2
exit 1
fi
command -v jq >/dev/null 2>&1 || { echo "error: jq is required" >&2; exit 1; }
package_name="$(jq -r '.blockbook.package_name // empty' "$config")"
if [[ -z "$package_name" ]]; then
echo "error: coin '$coin' does not define blockbook.package_name" >&2
exit 1
fi
rm -f build/${package_name}_*.deb
make "deb-blockbook-${coin}"
package_file="$(ls -1t build/${package_name}_*.deb 2>/dev/null | head -n1 || true)"
if [[ -z "$package_file" ]]; then
echo "error: built package for '$coin' was not found (pattern build/${package_name}_*.deb)" >&2
exit 1
fi
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --reinstall "./${package_file}"
sudo systemctl restart "${package_name}.service"
sudo systemctl is-active --quiet "${package_name}.service"
echo "deployed ${coin} via ${package_file}"