Files
EvoBGP/.gitea/workflows/quality.yaml
T
DenozordecandCursor f8e0520817 feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь.

Co-authored-by: Cursor <[email protected]>
2026-11-02 17:23:59 +07:00

519 lines
18 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Quality gates (reusable). Callers: ci.yaml (PR), cd.yaml (push main).
name: quality
on:
workflow_call:
inputs:
is_pull_request:
type: boolean
required: true
base_sha:
type: string
required: false
default: ""
head_sha:
type: string
required: false
default: ""
before_sha:
type: string
required: false
default: ""
allow_registry_login:
type: boolean
required: false
default: false
secrets:
ACTIONS_PAT:
required: false
docker_hub_token:
required: false
docker_hub_username:
required: false
permissions:
contents: read
jobs:
changes:
runs-on: ubuntu-latest
outputs:
openapi: ${{ steps.detect.outputs.openapi }}
go: ${{ steps.detect.outputs.go }}
web: ${{ steps.detect.outputs.web }}
bird_conf: ${{ steps.detect.outputs.bird_conf }}
docker_go: ${{ steps.detect.outputs.docker_go }}
docker_web: ${{ steps.detect.outputs.docker_web }}
docker_bird: ${{ steps.detect.outputs.docker_bird }}
steps:
- if: ${{ inputs.is_pull_request }}
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- if: ${{ inputs.is_pull_request == false }}
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Полная история: BEFORE_SHA = github.event.before при multi-commit push
# лежит глубже shallow-среза, и git diff падает с "bad object".
with:
fetch-depth: 0
- id: detect
name: Detect changed paths per module
env:
IS_PR: ${{ inputs.is_pull_request }}
BASE_SHA: ${{ inputs.base_sha }}
HEAD_SHA: ${{ inputs.head_sha }}
BEFORE_SHA: ${{ inputs.before_sha }}
run: |
set -euo pipefail
openapi=false
go=false
web=false
bird_conf=false
docker_go=false
docker_web=false
docker_bird=false
set_all_flags_true() {
openapi=true
go=true
web=true
bird_conf=true
docker_go=true
docker_web=true
docker_bird=true
}
write_outputs() {
for v in openapi go web bird_conf docker_go docker_web docker_bird; do
eval "echo \"\$v=\$$v\"" >> "$GITHUB_OUTPUT"
done
}
if [ "$IS_PR" = "true" ]; then
FILES="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")"
else
after="${HEAD_SHA:-$(git rev-parse HEAD)}"
before="$BEFORE_SHA"
if [ -n "$before" ] && [ "$before" != "0000000000000000000000000000000000000000" ]; then
# Force-push мог отбросить before; отсутствие объекта — не ошибка,
# а сигнал уйти в полный прогон через пустой diff.
if git cat-file -e "$before^{commit}" 2>/dev/null; then
FILES="$(git diff --name-only "$before" "$after")"
else
echo "BEFORE_SHA $before not found in checkout — full pipeline fallback"
FILES=""
fi
elif git rev-parse --verify HEAD~1 >/dev/null 2>&1; then
FILES="$(git diff --name-only HEAD~1 HEAD)"
else
set_all_flags_true
write_outputs
echo "No parent commit — full pipeline (all modules)"
exit 0
fi
fi
if [ -z "$(printf '%s' "$FILES" | tr -d '[:space:]')" ]; then
set_all_flags_true
write_outputs
echo "Empty diff — full pipeline fallback"
exit 0
fi
full_pipeline=false
while IFS= read -r f || [ -n "${f:-}" ]; do
[ -z "${f:-}" ] && continue
case "$f" in
.gitea/workflows/*|.golangci.yml|.pre-commit-config.yaml|scripts/*)
full_pipeline=true
;;
docs/openapi.yaml|redocly.yaml)
openapi=true
;;
docs/api.md|docs/access.md)
openapi=true
go=true
;;
.cursor/*|.claude/*|.codegraph/*|memory-bank/*)
;;
*.md|AGENTS.md)
;;
apps/web/README.md|apps/web/components.json|packages/ui/components.json)
;;
apps/web/*|packages/ui/*|packages/shared/*)
web=true
;;
deploy/bird/*)
bird_conf=true
go=true
;;
deploy/compose/*|deploy/docker/*|.dockerignore)
docker_go=true
docker_web=true
docker_bird=true
go=true
;;
go.mod|go.sum|go.work)
go=true
;;
migrations/*)
go=true
;;
cmd/*|internal/*|*.go)
go=true
bird_conf=true
;;
docs/*)
;;
package.json|package-lock.json|pnpm-lock.yaml|pnpm-workspace.yaml|.releaserc.json)
full_pipeline=true
;;
*)
;;
esac
done <<< "$FILES"
if $full_pipeline; then
set_all_flags_true
fi
write_outputs
echo "Changed files (first 30):"
printf '%s\n' "$FILES" | head -n 30
echo "--- flags ---"
echo "openapi=$openapi go=$go web=$web bird_conf=$bird_conf"
echo "docker_go=$docker_go docker_web=$docker_web docker_bird=$docker_bird full_pipeline=$full_pipeline"
openapi:
needs: [changes]
if: needs.changes.outputs.openapi == 'true' || needs.changes.outputs.web == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
- name: Export cache paths
run: sh scripts/ci/export-cache-env.sh
- id: pnpm-hash
run: echo "key=$(sha256sum pnpm-lock.yaml | awk '{print $1}')" >> "$GITHUB_OUTPUT"
- id: pnpm-cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ env.PNPM_STORE_DIR }}
${{ env.COREPACK_HOME }}
node_modules
apps/web/node_modules
packages/ui/node_modules
key: pnpm-${{ runner.os }}-${{ steps.pnpm-hash.outputs.key }}
restore-keys: |
pnpm-${{ runner.os }}-
- name: pnpm install, Redocly, codegen check
env:
PNPM_CACHE_HIT: ${{ steps.pnpm-cache.outputs.cache-hit }}
run: |
set -euxo pipefail
sh scripts/ci/pnpm-ci.sh
pnpm exec redocly lint docs/openapi.yaml
chmod +x scripts/check-openapi-gen.sh
sh scripts/check-openapi-gen.sh
web:
needs: [changes]
if: needs.changes.outputs.web == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
- name: Export cache paths
run: sh scripts/ci/export-cache-env.sh
- id: pnpm-hash
run: echo "key=$(sha256sum pnpm-lock.yaml | awk '{print $1}')" >> "$GITHUB_OUTPUT"
- id: pnpm-cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ env.PNPM_STORE_DIR }}
${{ env.COREPACK_HOME }}
node_modules
apps/web/node_modules
packages/ui/node_modules
key: pnpm-${{ runner.os }}-${{ steps.pnpm-hash.outputs.key }}
restore-keys: |
pnpm-${{ runner.os }}-
- name: pnpm install, typecheck, lint, test, build
env:
PNPM_CACHE_HIT: ${{ steps.pnpm-cache.outputs.cache-hit }}
run: |
set -euxo pipefail
sh scripts/ci/pnpm-ci.sh
pnpm --filter @evobgp/web run typecheck
pnpm --filter @evobgp/web run lint
pnpm --filter @evobgp/web run test
pnpm --filter @evobgp/web run build
go:
needs: [changes]
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.24"
cache: false
- name: Export cache paths
run: sh scripts/ci/export-cache-env.sh
- id: go-hash
run: echo "key=$(sha256sum go.sum | awk '{print $1}')" >> "$GITHUB_OUTPUT"
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ env.GOMODCACHE }}
${{ env.GOCACHE }}
${{ env.GOBIN }}
${{ env.GOLANGCI_LINT_CACHE }}
key: go-${{ runner.os }}-1.24-gl1.64.8-${{ steps.go-hash.outputs.key }}
restore-keys: |
go-${{ runner.os }}-1.24-gl1.64.8-
go-${{ runner.os }}-1.24-
- name: Download modules
env:
GOMODCACHE: ${{ env.GOMODCACHE }}
GOCACHE: ${{ env.GOCACHE }}
run: go mod download
- name: Vet
env:
GOFLAGS: -mod=readonly
run: go vet ./...
- name: Lint httpapi (ERR-01 / ARCH-01)
run: sh scripts/lint-httpapi.sh
- name: Check migration pairs (DEP-03)
run: sh scripts/check-migrations-pair.sh
- name: Validate remote speaker compose
run: sh scripts/validate-remote-speaker-compose.sh
- name: golangci-lint
env:
GOLANGCI_LINT_VERSION: v1.64.8
run: sh scripts/ci/golangci-lint.sh
- name: Test
env:
GOFLAGS: -mod=readonly
GOMODCACHE: ${{ env.GOMODCACHE }}
GOCACHE: ${{ env.GOCACHE }}
run: go test ./... -race -count=1
- name: Build all commands
env:
GOFLAGS: -mod=readonly
GOMODCACHE: ${{ env.GOMODCACHE }}
GOCACHE: ${{ env.GOCACHE }}
run: |
set -euxo pipefail
out="${RUNNER_TEMP}/evobgp-bin"
mkdir -p "$out"
for d in cmd/*/; do
name="$(basename "$d")"
go build -o "$out/$name" "./$d"
done
postgres-integration:
needs: [changes]
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: evobgp
POSTGRES_PASSWORD: evobgp_ci
POSTGRES_DB: evobgp_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U evobgp -d evobgp_test"
--health-interval 5s
--health-timeout 5s
--health-retries 12
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.24"
cache: false
- name: Export cache paths
run: sh scripts/ci/export-cache-env.sh
- id: go-hash
run: echo "key=$(sha256sum go.sum | awk '{print $1}')" >> "$GITHUB_OUTPUT"
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ env.GOMODCACHE }}
${{ env.GOCACHE }}
key: go-${{ runner.os }}-1.24-postgres-${{ steps.go-hash.outputs.key }}
restore-keys: |
go-${{ runner.os }}-1.24-postgres-
- name: Run migrations and verify PostgreSQL integration tests executed
env:
GOFLAGS: -mod=readonly
EVOBGP_TEST_DATABASE_URL: postgres://evobgp:[email protected]:5432/evobgp_test?sslmode=disable
run: bash scripts/ci/postgres-integration.sh
legacy-compatibility:
needs: [changes]
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
legacy_version: ["2.56.2", "1.0.0"]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.24"
cache: false
- name: Build pinned legacy executables and test current control plane
env:
EVOBGP_LEGACY_VERSION: ${{ matrix.legacy_version }}
run: bash scripts/ci/legacy-node-compatibility.sh
bird2:
runs-on: ubuntu-latest
needs: [changes, go]
if: >-
always() &&
needs.changes.result == 'success' &&
needs.go.result != 'failure' &&
(needs.changes.outputs.go == 'true' ||
needs.changes.outputs.bird_conf == 'true' ||
needs.changes.outputs.docker_bird == 'true' ||
needs.changes.outputs.docker_go == 'true')
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.24"
cache: false
- name: Install bird2 (репозиторий Ubuntu runner, как в образе evobgp-bird2)
run: |
set -euxo pipefail
if command -v sudo >/dev/null 2>&1; then SUDO=sudo; else SUDO=""; fi
$SUDO apt-get update -qq
DEBIAN_FRONTEND=noninteractive $SUDO apt-get install -y -qq bird2
bird --version
- name: bird -p on all scenario bird.conf files
env:
WORKSPACE: ${{ github.workspace }}
run: |
set -euxo pipefail
WS="${WORKSPACE:-$PWD}"
cd "$WS"
if [ ! -f internal/birdfmt/testdata/scenarios/minimal/bird.conf ]; then
echo "Нет сценариев BIRD в checkout. Проверьте, что internal/birdfmt/testdata/scenarios закоммичен и push в remote."
ls -la internal/birdfmt/testdata/ 2>/dev/null || ls -la
exit 1
fi
for conf in internal/birdfmt/testdata/scenarios/*/bird.conf; do
echo "==> $conf"
bird -c "$WS/$conf" -p
done
- name: Parse staged birddeploy generation with real BIRD
env:
GOFLAGS: -mod=readonly
EVOBGP_TEST_BIRD_BIN: bird
run: go test ./internal/birddeploy -run '^TestRealBirdParseGeneration$' -count=1 -v
commitlint:
if: inputs.is_pull_request
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
- name: Export cache paths
run: sh scripts/ci/export-cache-env.sh
- id: pnpm-hash
run: echo "key=$(sha256sum pnpm-lock.yaml | awk '{print $1}')" >> "$GITHUB_OUTPUT"
- id: pnpm-cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ env.PNPM_STORE_DIR }}
${{ env.COREPACK_HOME }}
node_modules
apps/web/node_modules
packages/ui/node_modules
key: pnpm-${{ runner.os }}-${{ steps.pnpm-hash.outputs.key }}
restore-keys: |
pnpm-${{ runner.os }}-
- name: Lint commit messages
env:
BASE_SHA: ${{ inputs.base_sha }}
HEAD_SHA: ${{ inputs.head_sha }}
PNPM_CACHE_HIT: ${{ steps.pnpm-cache.outputs.cache-hit }}
run: |
set -euxo pipefail
sh scripts/ci/pnpm-ci.sh
pnpm exec commitlint --from "$BASE_SHA" --to "$HEAD_SHA"
docker-check:
needs: [changes]
if: >-
inputs.is_pull_request &&
(needs.changes.outputs.docker_go == 'true' ||
needs.changes.outputs.docker_web == 'true' ||
needs.changes.outputs.docker_bird == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Offline mirror behavior tests
run: bash scripts/tests/mirror-base-images.test.sh
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
with:
name: evobgp
driver: docker-container
cleanup: false
- name: Log in to Docker Hub
if: secrets.docker_hub_token != ''
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
username: ${{ secrets.docker_hub_username || gitea.actor }}
password: ${{ secrets.docker_hub_token }}
- name: Log in to Gitea Registry
if: inputs.allow_registry_login
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: git.shx.one
username: ${{ gitea.actor }}
password: ${{ secrets.ACTIONS_PAT }}
- name: bake --print
working-directory: deploy/docker
env:
BUILDX_BAKE_ENTITLEMENTS_FS: "0"
BUILDX_BAKE_FILE_RELATIVE_PATHS: "1"
run: docker buildx bake --allow=fs.read="${{ github.workspace }}" -f docker-bake.hcl --print default
- name: bake (no push)
if: inputs.allow_registry_login
working-directory: deploy/docker
env:
BUILDX_BAKE_ENTITLEMENTS_FS: "0"
BUILDX_BAKE_FILE_RELATIVE_PATHS: "1"
CACHE_REF_GO: git.shx.one/${{ github.repository_owner }}/evobgp-buildcache:go-buildcache
CACHE_REF_WEB: git.shx.one/${{ github.repository_owner }}/evobgp-buildcache:web-buildcache
CACHE_REF_BIRDC: git.shx.one/${{ github.repository_owner }}/evobgp-buildcache:birdc-buildcache
run: |
set -euxo pipefail
owner_lc="$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')"
export CACHE_REF_GO="git.shx.one/${owner_lc}/evobgp-buildcache:go-buildcache"
export CACHE_REF_WEB="git.shx.one/${owner_lc}/evobgp-buildcache:web-buildcache"
export CACHE_REF_BIRDC="git.shx.one/${owner_lc}/evobgp-buildcache:birdc-buildcache"
docker buildx bake --allow=fs.read="${{ github.workspace }}" -f docker-bake.hcl default