quality / commitlint (push) Skipped
quality / changes (push) Successful in 7s
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 26s
quality / web (push) Successful in 1m6s
quality / go (push) Successful in 55s
quality / bird2 (push) Successful in 16s
CD / quality (push) Successful in 2m59s
CD / publish (push) Successful in 5m17s
Push-чекаут в quality-воркфлоу делался с fetch-depth: 2, поэтому BEFORE_SHA (github.event.before) при пуше нескольких коммитов отсутствовал в чекауте, и git diff падал с "fatal: bad object" (exit 128). Чекаут переведён на fetch-depth: 0; перед диффом добавлен guard git cat-file -e: если before недоступен (force-push), шаг не падает, а уходит в полный прогон через empty-diff fallback.
448 lines
16 KiB
YAML
448 lines
16 KiB
YAML
# 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
|
||
|
||
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
|
||
- 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
|
||
|
||
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
|
||
- 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
|