Исправить передачу исходников в CI-контейнеры
Some checks failed
Проверка, сборка и развёртывание Magistr / Обязательные проверки (push) Successful in 1m23s
Проверка, сборка и развёртывание Magistr / Собрать backend image (push) Successful in 7m36s
Проверка, сборка и развёртывание Magistr / Собрать frontend image (push) Successful in 31s
Проверка, сборка и развёртывание Magistr / Проверить опубликованные образы (push) Failing after 4m56s
Проверка, сборка и развёртывание Magistr / Развернуть проверенные digests (push) Has been skipped

This commit is contained in:
Zuev
2026-07-29 23:39:35 +03:00
parent 6b4cd5b6c8
commit 7b5053f42a
7 changed files with 264 additions and 28 deletions

View File

@@ -25,20 +25,24 @@ jobs:
- name: Получить исходный код - name: Получить исходный код
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Проверить запуск CI-контейнеров без bind mount
run: bash scripts/test-run-ci-container.sh
- name: Backend unit и component tests - name: Backend unit и component tests
run: >- run: |
docker run --rm bash scripts/run-ci-container.sh \
-v "${{ github.workspace }}/backend:/workspace" backend \
-w /workspace "${{ env.MAVEN_IMAGE }}" \
"${{ env.MAVEN_IMAGE }}" -- \
mvn -Dapi.version=1.44 '-Dtest=!*IntegrationTest' test mvn -Dapi.version=1.44 '-Dtest=!*IntegrationTest' test
- name: Frontend static и unit tests - name: Frontend static и unit tests
run: >- run: |
docker run --rm bash scripts/run-ci-container.sh \
-v "${{ github.workspace }}/frontend:/workspace" frontend \
-w /workspace "${{ env.NODE_IMAGE }}" \
"${{ env.NODE_IMAGE }}" --copy backend /backend \
-- \
sh -c "npm ci && npm run check" sh -c "npm ci && npm run check"
- name: Проверить Docker Compose - name: Проверить Docker Compose
@@ -146,6 +150,9 @@ jobs:
BACKEND_REF: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}@${{ needs.build-and-push-backend.outputs.digest }} BACKEND_REF: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}@${{ needs.build-and-push-backend.outputs.digest }}
FRONTEND_REF: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}@${{ needs.build-and-push-frontend.outputs.digest }} FRONTEND_REF: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}@${{ needs.build-and-push-frontend.outputs.digest }}
steps: steps:
- name: Получить исходный код
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Войти в Container Registry - name: Войти в Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
@@ -154,19 +161,23 @@ jobs:
password: ${{ secrets.ZUEV_TOKEN }} password: ${{ secrets.ZUEV_TOKEN }}
- name: Проверить backend на HIGH и CRITICAL уязвимости - name: Проверить backend на HIGH и CRITICAL уязвимости
run: >- run: |
docker run --rm bash scripts/run-ci-container.sh \
-v "$HOME/.docker:/root/.docker:ro" "$HOME/.docker" \
"${{ env.TRIVY_IMAGE }}" "${{ env.TRIVY_IMAGE }}" \
image --scanners vuln --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --env DOCKER_CONFIG=/workspace \
-- \
image --scanners vuln --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
"$BACKEND_REF" "$BACKEND_REF"
- name: Проверить frontend на HIGH и CRITICAL уязвимости - name: Проверить frontend на HIGH и CRITICAL уязвимости
run: >- run: |
docker run --rm bash scripts/run-ci-container.sh \
-v "$HOME/.docker:/root/.docker:ro" "$HOME/.docker" \
"${{ env.TRIVY_IMAGE }}" "${{ env.TRIVY_IMAGE }}" \
image --scanners vuln --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --env DOCKER_CONFIG=/workspace \
-- \
image --scanners vuln --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
"$FRONTEND_REF" "$FRONTEND_REF"
deploy-to-k8s: deploy-to-k8s:

View File

@@ -361,13 +361,19 @@ Deployment-файлов запрещено.
Основные шаги: Основные шаги:
1. Backend unit/component tests, frontend static/unit tests, Compose validation, shell-тест 1. Backend unit/component tests, frontend static/unit tests, Compose validation, shell-тест
immutable rollout/rollback и регрессионная проверка закрепления артефактов. immutable rollout/rollback и регрессионная проверка закрепления артефактов. Исходники
backend/frontend передаются тестовым контейнерам через `docker cp`: bind mount
`${{ github.workspace }}` не используется, потому что Docker daemon runner может
находиться вне job-контейнера и не видеть его файловую систему. Frontend security-тест
дополнительно получает backend в `/backend`, сохраняя ожидаемую соседнюю структуру.
2. Только после успешных gates — login, параллельная сборка и push backend/frontend с 2. Только после успешных gates — login, параллельная сборка и push backend/frontend с
SHA-tag и release tag без `latest`. Сторонние Actions закреплены полными commit SHA. SHA-tag и release tag без `latest`. Сторонние Actions закреплены полными commit SHA.
3. BuildKit публикует для обоих образов максимальную provenance- и SBOM-attestation. Build 3. BuildKit публикует для обоих образов максимальную provenance- и SBOM-attestation. Build
jobs также возвращают registry digest каждого образа. jobs также возвращают registry digest каждого образа.
4. Отдельный обязательный job сканирует опубликованные digests закреплённым Trivy `0.63.0` и 4. Отдельный обязательный job сканирует опубликованные digests закреплённым Trivy `0.63.0` и
блокирует deploy при исправимых `HIGH`/`CRITICAL` уязвимостях. блокирует deploy при исправимых `HIGH`/`CRITICAL` уязвимостях. Docker credentials для
приватного registry копируются во временный Trivy-контейнер через Docker API; bind mount
домашнего каталога job-контейнера не используется.
5. Push в `main` выполняет проверки, сборку, публикацию и scan, но не меняет production. 5. Push в `main` выполняет проверки, сборку, публикацию и scan, но не меняет production.
Deploy job запускается только вручную через `workflow_dispatch` и устанавливает Deploy job запускается только вручную через `workflow_dispatch` и устанавливает
фиксированный `kubectl v1.33.12` после SHA-256 проверки. Java Agent также имеет точную фиксированный `kubectl v1.33.12` после SHA-256 проверки. Java Agent также имеет точную

View File

@@ -0,0 +1,25 @@
---
type: "query"
date: "2026-07-29T20:38:53.488440+00:00"
question: "The goal you specified requires a project to execute but there is no POM in this directory (/workspace)."
contributor: "graphify"
outcome: "useful"
source_nodes: ["Workflow сборки и публикации Docker-образов", "Pipeline backend-образа", "Pipeline frontend-образа", "CI/CD Gitea Actions в инфраструктуре"]
---
# Q: The goal you specified requires a project to execute but there is no POM in this directory (/workspace).
## Answer
Expanded from original query via graph vocab: [actions, backend, build, docker, frontend, gitea, pipeline, test, workflow, workspace]. Корень сбоя: workflow передавал пути job-контейнера в bind mounts внешнего Docker daemon. Исправление: scripts/run-ci-container.sh создаёт временный контейнер, копирует исходники через Docker API, запускает команду и сохраняет её exit code; frontend дополнительно получает backend в /backend, а Trivy получает Docker config через DOCKER_CONFIG=/workspace без bind mount. Проверено полным Maven gate (274 теста) и frontend gate (63 теста).
## Outcome
- Signal: useful
## Source Nodes
- Workflow сборки и публикации Docker-образов
- Pipeline backend-образа
- Pipeline frontend-образа
- CI/CD Gitea Actions в инфраструктуре

View File

@@ -4,6 +4,8 @@ set -euo pipefail
PROJECT_ROOT=${PROJECT_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)} PROJECT_ROOT=${PROJECT_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}
WORKFLOW="$PROJECT_ROOT/.gitea/workflows/docker-build.yaml" WORKFLOW="$PROJECT_ROOT/.gitea/workflows/docker-build.yaml"
CI_CONTAINER_SCRIPT="$PROJECT_ROOT/scripts/run-ci-container.sh"
CI_CONTAINER_TEST="$PROJECT_ROOT/scripts/test-run-ci-container.sh"
failures=0 failures=0
@@ -60,7 +62,7 @@ require_count() {
local description=$3 local description=$3
local actual local actual
actual=$(grep -Ec "$pattern" "$WORKFLOW" || true) actual=$(grep -Ec -- "$pattern" "$WORKFLOW" || true)
if (( actual < expected )); then if (( actual < expected )); then
fail "$description: ожидается не менее $expected, найдено $actual" fail "$description: ожидается не менее $expected, найдено $actual"
fi fi
@@ -68,6 +70,8 @@ require_count() {
check_workflow_gates() { check_workflow_gates() {
require_file "$WORKFLOW" || return require_file "$WORKFLOW" || return
require_file "$CI_CONTAINER_SCRIPT" || return
require_file "$CI_CONTAINER_TEST" || return
require_count '^[[:space:]]+sbom:[[:space:]]+true$' 2 \ require_count '^[[:space:]]+sbom:[[:space:]]+true$' 2 \
"для обоих образов должна публиковаться SBOM attestation" "для обоих образов должна публиковаться SBOM attestation"
@@ -81,6 +85,17 @@ check_workflow_gates() {
"образ Maven для CI должен быть закреплён digest" "образ Maven для CI должен быть закреплён digest"
require_count 'NODE_IMAGE: [^[:space:]]+@sha256:[0-9a-f]{64}$' 1 \ require_count 'NODE_IMAGE: [^[:space:]]+@sha256:[0-9a-f]{64}$' 1 \
"образ Node.js для CI должен быть закреплён digest" "образ Node.js для CI должен быть закреплён digest"
require_count 'bash scripts/run-ci-container\.sh' 4 \
"тесты и Trivy должны передавать файлы через Docker API"
require_count 'bash scripts/test-run-ci-container\.sh' 1 \
"workflow должен проверять helper запуска CI-контейнеров"
require_count '--env DOCKER_CONFIG=/workspace' 2 \
"оба Trivy scan должны получать registry credentials через Docker API"
if grep -Fq '${{ github.workspace }}' "$WORKFLOW" \
|| grep -Fq -- '-v "$HOME' "$WORKFLOW"; then
fail "нельзя использовать пути job-контейнера в bind mount внешнего Docker daemon"
fi
if ! grep -Eq '^ needs: \[build-and-push-backend, build-and-push-frontend, security-scan\]$' "$WORKFLOW"; then if ! grep -Eq '^ needs: \[build-and-push-backend, build-and-push-frontend, security-scan\]$' "$WORKFLOW"; then
fail "deploy обязан зависеть от успешного security-scan" fail "deploy обязан зависеть от успешного security-scan"

87
scripts/run-ci-container.sh Executable file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env bash
set -euo pipefail
if (( $# < 4 )); then
printf 'Использование: %s <каталог-исходников> <образ> [--copy <каталог> <путь>] [--env <ИМЯ=значение>] -- <команда> [аргументы...]\n' \
"${0##*/}" >&2
exit 2
fi
SOURCE_DIR=$1
IMAGE=$2
shift 2
EXTRA_SOURCE_DIRS=()
EXTRA_DESTINATIONS=()
CREATE_ARGS=()
while (( $# > 0 )); do
case "$1" in
--copy)
if (( $# < 3 )); then
printf '[CI-КОНТЕЙНЕР] Ошибка: для --copy нужны каталог и путь в контейнере\n' >&2
exit 2
fi
EXTRA_SOURCE_DIRS+=("$2")
EXTRA_DESTINATIONS+=("$3")
shift 3
;;
--env)
if (( $# < 2 )) || [[ ! "$2" =~ ^[A-Za-z_][A-Za-z0-9_]*=.*$ ]]; then
printf '[CI-КОНТЕЙНЕР] Ошибка: для --env ожидается ИМЯ=значение\n' >&2
exit 2
fi
CREATE_ARGS+=(--env "$2")
shift 2
;;
--)
shift
break
;;
*)
printf '[CI-КОНТЕЙНЕР] Ошибка: неизвестный аргумент до команды: %s\n' "$1" >&2
exit 2
;;
esac
done
if (( $# == 0 )); then
printf '[CI-КОНТЕЙНЕР] Ошибка: не указана команда контейнера\n' >&2
exit 2
fi
if [[ ! -d "$SOURCE_DIR" ]]; then
printf '[CI-КОНТЕЙНЕР] Ошибка: каталог исходников не найден: %s\n' "$SOURCE_DIR" >&2
exit 2
fi
for index in "${!EXTRA_SOURCE_DIRS[@]}"; do
extra_source=${EXTRA_SOURCE_DIRS[$index]}
extra_destination=${EXTRA_DESTINATIONS[$index]}
if [[ ! -d "$extra_source" ]]; then
printf '[CI-КОНТЕЙНЕР] Ошибка: дополнительный каталог не найден: %s\n' \
"$extra_source" >&2
exit 2
fi
if [[ "$extra_destination" != /* || "$extra_destination" == *..* ]]; then
printf '[CI-КОНТЕЙНЕР] Ошибка: путь назначения должен быть абсолютным без ..: %s\n' \
"$extra_destination" >&2
exit 2
fi
done
CONTAINER_ID=
cleanup() {
if [[ -n "$CONTAINER_ID" ]]; then
docker rm --force "$CONTAINER_ID" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
CONTAINER_ID=$(docker create --workdir /workspace "${CREATE_ARGS[@]}" "$IMAGE" "$@")
docker cp "$SOURCE_DIR/." "$CONTAINER_ID:/workspace"
for index in "${!EXTRA_SOURCE_DIRS[@]}"; do
docker cp "${EXTRA_SOURCE_DIRS[$index]}/." \
"$CONTAINER_ID:${EXTRA_DESTINATIONS[$index]}"
done
docker start --attach "$CONTAINER_ID"

View File

@@ -7,12 +7,20 @@ CHECK_SCRIPT="$ROOT_DIR/scripts/check-artifact-pinning.sh"
FIXTURE_DIR=$(mktemp -d) FIXTURE_DIR=$(mktemp -d)
trap 'rm -rf "$FIXTURE_DIR"' EXIT trap 'rm -rf "$FIXTURE_DIR"' EXIT
mkdir -p "$FIXTURE_DIR/backend" "$FIXTURE_DIR/frontend" "$FIXTURE_DIR/.gitea/workflows" mkdir -p \
"$FIXTURE_DIR/backend" \
"$FIXTURE_DIR/frontend" \
"$FIXTURE_DIR/.gitea/workflows" \
"$FIXTURE_DIR/scripts"
cp "$ROOT_DIR/backend/Dockerfile" "$FIXTURE_DIR/backend/Dockerfile" cp "$ROOT_DIR/backend/Dockerfile" "$FIXTURE_DIR/backend/Dockerfile"
cp "$ROOT_DIR/frontend/Dockerfile" "$FIXTURE_DIR/frontend/Dockerfile" cp "$ROOT_DIR/frontend/Dockerfile" "$FIXTURE_DIR/frontend/Dockerfile"
cp "$ROOT_DIR/compose.yaml" "$FIXTURE_DIR/compose.yaml" cp "$ROOT_DIR/compose.yaml" "$FIXTURE_DIR/compose.yaml"
cp "$ROOT_DIR/.gitea/workflows/docker-build.yaml" \ cp "$ROOT_DIR/.gitea/workflows/docker-build.yaml" \
"$FIXTURE_DIR/.gitea/workflows/docker-build.yaml" "$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
cp "$ROOT_DIR/scripts/run-ci-container.sh" \
"$FIXTURE_DIR/scripts/run-ci-container.sh"
cp "$ROOT_DIR/scripts/test-run-ci-container.sh" \
"$FIXTURE_DIR/scripts/test-run-ci-container.sh"
PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null
@@ -38,4 +46,13 @@ if PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null 2>&1; then
exit 1 exit 1
fi fi
cp "$ROOT_DIR/.gitea/workflows/docker-build.yaml" \
"$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
sed -i '$a\# docker run --rm -v "${{ github.workspace }}/backend:/workspace"' \
"$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
if PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null 2>&1; then
printf 'Ошибка теста: bind mount github.workspace не был отклонён\n' >&2
exit 1
fi
printf 'Проверка закрепления артефактов завершена успешно\n' printf 'Проверка закрепления артефактов завершена успешно\n'

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
RUN_SCRIPT="$ROOT_DIR/scripts/run-ci-container.sh"
FIXTURE_DIR=$(mktemp -d)
CALL_LOG="$FIXTURE_DIR/docker-calls.log"
FAKE_BIN="$FIXTURE_DIR/bin"
SOURCE_DIR="$FIXTURE_DIR/source"
EXTRA_SOURCE_DIR="$FIXTURE_DIR/extra-source"
trap 'rm -rf "$FIXTURE_DIR"' EXIT
mkdir -p "$FAKE_BIN" "$SOURCE_DIR" "$EXTRA_SOURCE_DIR"
printf 'fixture\n' > "$SOURCE_DIR/pom.xml"
printf 'extra fixture\n' > "$EXTRA_SOURCE_DIR/backend.java"
cat > "$FAKE_BIN/docker" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' "$*" >> "$DOCKER_CALL_LOG"
case "$1" in
create)
printf 'ci-container-id\n'
;;
start)
exit "${DOCKER_START_EXIT_CODE:-0}"
;;
esac
EOF
chmod +x "$FAKE_BIN/docker"
DOCKER_CALL_LOG="$CALL_LOG" PATH="$FAKE_BIN:$PATH" \
bash "$RUN_SCRIPT" "$SOURCE_DIR" 'registry.local/test@sha256:abc' \
--copy "$EXTRA_SOURCE_DIR" /backend \
--env DOCKER_CONFIG=/workspace \
-- sh -c 'exit 0'
grep --fixed-strings \
'create --workdir /workspace --env DOCKER_CONFIG=/workspace registry.local/test@sha256:abc sh -c exit 0' \
"$CALL_LOG" >/dev/null
grep --fixed-strings \
"cp $SOURCE_DIR/. ci-container-id:/workspace" \
"$CALL_LOG" >/dev/null
grep --fixed-strings \
"cp $EXTRA_SOURCE_DIR/. ci-container-id:/backend" \
"$CALL_LOG" >/dev/null
grep --fixed-strings 'start --attach ci-container-id' "$CALL_LOG" >/dev/null
grep --fixed-strings 'rm --force ci-container-id' "$CALL_LOG" >/dev/null
if grep -Eq '(^|[[:space:]])(-v|--volume)([[:space:]]|=)' "$CALL_LOG"; then
printf 'Ошибка теста: helper использовал bind mount\n' >&2
exit 1
fi
: > "$CALL_LOG"
set +e
DOCKER_CALL_LOG="$CALL_LOG" DOCKER_START_EXIT_CODE=17 PATH="$FAKE_BIN:$PATH" \
bash "$RUN_SCRIPT" "$SOURCE_DIR" 'registry.local/test@sha256:abc' \
-- sh -c 'exit 17' >/dev/null 2>&1
status=$?
set -e
if (( status != 17 )); then
printf 'Ошибка теста: код контейнера 17 превратился в %d\n' "$status" >&2
exit 1
fi
grep --fixed-strings 'rm --force ci-container-id' "$CALL_LOG" >/dev/null
if PATH="$FAKE_BIN:$PATH" bash "$RUN_SCRIPT" "$FIXTURE_DIR/missing" \
'registry.local/test@sha256:abc' -- true >/dev/null 2>&1; then
printf 'Ошибка теста: отсутствующий каталог исходников не был отклонён\n' >&2
exit 1
fi
printf 'Проверка запуска CI-контейнеров без bind mount завершена успешно\n'