Compare commits
4 Commits
6b4cd5b6c8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a759155b15 | ||
|
|
0180b450b3 | ||
|
|
c3f477788e | ||
|
|
7b5053f42a |
@@ -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
|
||||||
@@ -141,11 +145,14 @@ jobs:
|
|||||||
name: Проверить опубликованные образы
|
name: Проверить опубликованные образы
|
||||||
needs: [build-and-push-backend, build-and-push-frontend]
|
needs: [build-and-push-backend, build-and-push-frontend]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 35
|
||||||
env:
|
env:
|
||||||
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 --timeout 15m --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 --timeout 15m --scanners vuln --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
|
||||||
"$FRONTEND_REF"
|
"$FRONTEND_REF"
|
||||||
|
|
||||||
deploy-to-k8s:
|
deploy-to-k8s:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## Обзор проекта
|
## Обзор проекта
|
||||||
|
|
||||||
Проект представляет собой систему управления университетским расписанием.
|
Проект представляет собой систему управления университетским расписанием.
|
||||||
- **Backend**: Java 17, Spring Boot 3.2.5 (Мультитенантная архитектура: отдельная БД для каждого клиента)
|
- **Backend**: Java 17, Spring Boot 3.5.16 (Мультитенантная архитектура: отдельная БД для каждого клиента)
|
||||||
- **Frontend**: Vanilla JavaScript + HTML/CSS (без фреймворков)
|
- **Frontend**: Vanilla JavaScript + HTML/CSS (без фреймворков)
|
||||||
- **Database**: PostgreSQL (множество БД, управляются через Flyway)
|
- **Database**: PostgreSQL (множество БД, управляются через Flyway)
|
||||||
- **Локальный URL**: localhost:80
|
- **Локальный URL**: localhost:80
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ RUN mvn org.apache.maven.plugins:maven-dependency-plugin:3.6.1:copy \
|
|||||||
|
|
||||||
FROM eclipse-temurin:17-jre-alpine@sha256:02320dd4ce20e243dfb915c686089cf9315c763084fafbb12d5c9993aee18b57
|
FROM eclipse-temurin:17-jre-alpine@sha256:02320dd4ce20e243dfb915c686089cf9315c763084fafbb12d5c9993aee18b57
|
||||||
|
|
||||||
# Best practice: run as a non-root user
|
# Устанавливаем исправления безопасности, опубликованные после сборки базового образа.
|
||||||
|
RUN apk upgrade --no-cache
|
||||||
|
|
||||||
|
# Запускаем приложение от непривилегированного пользователя.
|
||||||
RUN addgroup -S spring && adduser -S spring -G spring
|
RUN addgroup -S spring && adduser -S spring -G spring
|
||||||
USER spring:spring
|
USER spring:spring
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.2.5</version>
|
<version>3.5.16</version>
|
||||||
<relativePath/>
|
<relativePath/>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
|
<postgresql.version>42.7.12</postgresql.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -44,6 +45,10 @@
|
|||||||
<groupId>org.flywaydb</groupId>
|
<groupId>org.flywaydb</groupId>
|
||||||
<artifactId>flyway-core</artifactId>
|
<artifactId>flyway-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-database-postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.magistr.app.repository.DepartmentRepository;
|
|||||||
import com.magistr.app.repository.TeacherCreationRequestRepository;
|
import com.magistr.app.repository.TeacherCreationRequestRepository;
|
||||||
import com.magistr.app.repository.TeacherDepartmentAssignmentRepository;
|
import com.magistr.app.repository.TeacherDepartmentAssignmentRepository;
|
||||||
import com.magistr.app.repository.UserRepository;
|
import com.magistr.app.repository.UserRepository;
|
||||||
|
import com.magistr.app.service.BusinessTimeService;
|
||||||
import com.magistr.app.service.ScheduleQueryService;
|
import com.magistr.app.service.ScheduleQueryService;
|
||||||
import com.magistr.app.service.SubjectImportService;
|
import com.magistr.app.service.SubjectImportService;
|
||||||
import com.magistr.app.service.TeacherDepartmentService;
|
import com.magistr.app.service.TeacherDepartmentService;
|
||||||
@@ -22,7 +23,10 @@ import org.junit.jupiter.api.AfterEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -34,6 +38,12 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
class DepartmentWorkspaceControllerTest {
|
class DepartmentWorkspaceControllerTest {
|
||||||
|
|
||||||
|
private static final LocalDate TODAY = LocalDate.of(2026, 7, 29);
|
||||||
|
private static final BusinessTimeService BUSINESS_TIME = new BusinessTimeService(
|
||||||
|
Clock.fixed(Instant.parse("2026-07-29T09:00:00Z"), ZoneOffset.UTC),
|
||||||
|
ZoneOffset.UTC
|
||||||
|
);
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void clearAuthContext() {
|
void clearAuthContext() {
|
||||||
AuthContext.clear();
|
AuthContext.clear();
|
||||||
@@ -71,7 +81,7 @@ class DepartmentWorkspaceControllerTest {
|
|||||||
TeacherDepartmentService teacherDepartmentService = mock(TeacherDepartmentService.class);
|
TeacherDepartmentService teacherDepartmentService = mock(TeacherDepartmentService.class);
|
||||||
User assignedTeacher = user(10L, "assigned", "Петров Препод Петрович", 2L);
|
User assignedTeacher = user(10L, "assigned", "Петров Препод Петрович", 2L);
|
||||||
User directTeacher = user(11L, "direct", "Препод Тест Тестович", 1L);
|
User directTeacher = user(11L, "direct", "Препод Тест Тестович", 1L);
|
||||||
when(teacherDepartmentService.findTeachersForDepartmentAtDate(1L, LocalDate.now()))
|
when(teacherDepartmentService.findTeachersForDepartmentAtDate(1L, TODAY))
|
||||||
.thenReturn(List.of(directTeacher, assignedTeacher));
|
.thenReturn(List.of(directTeacher, assignedTeacher));
|
||||||
DepartmentWorkspaceController controller = new DepartmentWorkspaceController(
|
DepartmentWorkspaceController controller = new DepartmentWorkspaceController(
|
||||||
mock(SubjectRepository.class),
|
mock(SubjectRepository.class),
|
||||||
@@ -82,7 +92,8 @@ class DepartmentWorkspaceControllerTest {
|
|||||||
mock(TeacherCreationRequestRepository.class),
|
mock(TeacherCreationRequestRepository.class),
|
||||||
mock(ScheduleQueryService.class),
|
mock(ScheduleQueryService.class),
|
||||||
teacherDepartmentService,
|
teacherDepartmentService,
|
||||||
mock(SubjectImportService.class)
|
mock(SubjectImportService.class),
|
||||||
|
BUSINESS_TIME
|
||||||
);
|
);
|
||||||
|
|
||||||
List<UserResponse> teachers = controller.getTeachers(1L);
|
List<UserResponse> teachers = controller.getTeachers(1L);
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ import com.magistr.app.model.User;
|
|||||||
import com.magistr.app.repository.SubjectRepository;
|
import com.magistr.app.repository.SubjectRepository;
|
||||||
import com.magistr.app.repository.TeacherSubjectRepository;
|
import com.magistr.app.repository.TeacherSubjectRepository;
|
||||||
import com.magistr.app.repository.UserRepository;
|
import com.magistr.app.repository.UserRepository;
|
||||||
|
import com.magistr.app.service.BusinessTimeService;
|
||||||
import com.magistr.app.service.TeacherDepartmentService;
|
import com.magistr.app.service.TeacherDepartmentService;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.Clock;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -25,6 +28,11 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
class TeacherSubjectControllerTest {
|
class TeacherSubjectControllerTest {
|
||||||
|
|
||||||
|
private static final BusinessTimeService BUSINESS_TIME = new BusinessTimeService(
|
||||||
|
Clock.fixed(Instant.parse("2026-07-29T09:00:00Z"), ZoneOffset.UTC),
|
||||||
|
ZoneOffset.UTC
|
||||||
|
);
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void clearAuthContext() {
|
void clearAuthContext() {
|
||||||
AuthContext.clear();
|
AuthContext.clear();
|
||||||
@@ -46,13 +54,14 @@ class TeacherSubjectControllerTest {
|
|||||||
when(teacherDepartmentService.hasAssignmentAtDate(
|
when(teacherDepartmentService.hasAssignmentAtDate(
|
||||||
teacher.getId(),
|
teacher.getId(),
|
||||||
1L,
|
1L,
|
||||||
LocalDate.now()
|
BUSINESS_TIME.today()
|
||||||
)).thenReturn(true);
|
)).thenReturn(true);
|
||||||
TeacherSubjectController controller = new TeacherSubjectController(
|
TeacherSubjectController controller = new TeacherSubjectController(
|
||||||
teacherSubjectRepository,
|
teacherSubjectRepository,
|
||||||
userRepository,
|
userRepository,
|
||||||
subjectRepository,
|
subjectRepository,
|
||||||
teacherDepartmentService
|
teacherDepartmentService,
|
||||||
|
BUSINESS_TIME
|
||||||
);
|
);
|
||||||
AuthContext.setCurrentUser(new AuthenticatedUser(1L, "кафедра", Role.DEPARTMENT, 1L));
|
AuthContext.setCurrentUser(new AuthenticatedUser(1L, "кафедра", Role.DEPARTMENT, 1L));
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
graph TD
|
graph TD
|
||||||
Client["🌐 Браузер"] -->|HTTPS| Caddy["Caddy Proxy"]
|
Client["🌐 Браузер"] -->|HTTPS| Caddy["Caddy Proxy"]
|
||||||
Caddy -->|:80| Frontend["Frontend<br/>(Apache httpd + строгий CSP)"]
|
Caddy -->|:80| Frontend["Frontend<br/>(Apache httpd + строгий CSP)"]
|
||||||
Caddy -->|/api/*| Backend["Backend<br/>(Spring Boot 3.2.5)"]
|
Caddy -->|/api/*| Backend["Backend<br/>(Spring Boot 3.5.16)"]
|
||||||
|
|
||||||
Backend --> TenantRouter{"TenantRoutingDataSource"}
|
Backend --> TenantRouter{"TenantRoutingDataSource"}
|
||||||
TenantRouter -->|swsu.zuev.company| DB1["PostgreSQL<br/>swsu_db"]
|
TenantRouter -->|swsu.zuev.company| DB1["PostgreSQL<br/>swsu_db"]
|
||||||
@@ -27,7 +27,7 @@ graph TD
|
|||||||
- **JS-модули:** Vanilla JavaScript с ES6 Modules (`import`/`export`)
|
- **JS-модули:** Vanilla JavaScript с ES6 Modules (`import`/`export`)
|
||||||
- **Browser security:** same-origin runtime-ресурсы и CSP без `unsafe-inline`/`unsafe-eval`
|
- **Browser security:** same-origin runtime-ресурсы и CSP без `unsafe-inline`/`unsafe-eval`
|
||||||
|
|
||||||
### Backend (Spring Boot 3.2.5)
|
### Backend (Spring Boot 3.5.16)
|
||||||
- **Тип:** REST API сервер
|
- **Тип:** REST API сервер
|
||||||
- **Язык:** Java 17
|
- **Язык:** Java 17
|
||||||
- **Порт:** 8080 (внутренний)
|
- **Порт:** 8080 (внутренний)
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ Backend собирается через multi-stage сборку Maven:
|
|||||||
1. Этап сборки: `maven:3.9.9-eclipse-temurin-17@sha256:f58d59b...` → `mvn package`
|
1. Этап сборки: `maven:3.9.9-eclipse-temurin-17@sha256:f58d59b...` → `mvn package`
|
||||||
2. OpenTelemetry Java Agent `2.28.1` загружается как фиксированный Maven-артефакт и
|
2. OpenTelemetry Java Agent `2.28.1` загружается как фиксированный Maven-артефакт и
|
||||||
проверяется по закреплённому SHA-256
|
проверяется по закреплённому SHA-256
|
||||||
3. Этап запуска: `eclipse-temurin:17-jre-alpine@sha256:02320dd4...` → `java -jar app.jar`
|
3. Этап запуска: `eclipse-temurin:17-jre-alpine@sha256:02320dd4...`; перед добавлением
|
||||||
|
непривилегированного пользователя устанавливаются доступные исправления Alpine через
|
||||||
|
`apk upgrade --no-cache`, затем выполняется `java -jar app.jar`
|
||||||
|
|
||||||
### Dockerfile (Frontend)
|
### Dockerfile (Frontend)
|
||||||
|
|
||||||
@@ -96,6 +98,7 @@ RUN npm ci
|
|||||||
RUN npm run build:vendor
|
RUN npm run build:vendor
|
||||||
|
|
||||||
FROM httpd:alpine3.23@sha256:4a15e9c...
|
FROM httpd:alpine3.23@sha256:4a15e9c...
|
||||||
|
RUN apk upgrade --no-cache
|
||||||
COPY --from=frontend-assets /build/dist/vendor/ /usr/local/apache2/htdocs/vendor/
|
COPY --from=frontend-assets /build/dist/vendor/ /usr/local/apache2/htdocs/vendor/
|
||||||
COPY security.conf /usr/local/apache2/conf/extra/magistr-security.conf
|
COPY security.conf /usr/local/apache2/conf/extra/magistr-security.conf
|
||||||
COPY proxy.conf /usr/local/apache2/conf/extra/magistr-proxy.conf
|
COPY proxy.conf /usr/local/apache2/conf/extra/magistr-proxy.conf
|
||||||
@@ -106,7 +109,8 @@ tag и manifest digest. Первый этап frontend собирает зафи
|
|||||||
OpenTelemetry bundle; второй раздаёт только runtime-
|
OpenTelemetry bundle; второй раздаёт только runtime-
|
||||||
файлы, без `node_modules`, тестов и build-исходников. Apache подключает `mod_headers`,
|
файлы, без `node_modules`, тестов и build-исходников. Apache подключает `mod_headers`,
|
||||||
`mod_proxy` и `mod_proxy_http`; proxy сохраняет исходный `Host`, чтобы `localhost` корректно
|
`mod_proxy` и `mod_proxy_http`; proxy сохраняет исходный `Host`, чтобы `localhost` корректно
|
||||||
маршрутизировался в tenant `default`. Сервер
|
маршрутизировался в tenant `default`. Перед копированием runtime-файлов устанавливаются
|
||||||
|
доступные исправления Alpine через `apk upgrade --no-cache`. Сервер
|
||||||
возвращает CSP с `script-src 'self'`, `script-src-attr 'none'`, `style-src 'self'` и точными
|
возвращает CSP с `script-src 'self'`, `script-src-attr 'none'`, `style-src 'self'` и точными
|
||||||
SHA-256 для оставшихся статических style-атрибутов. `unsafe-inline` и `unsafe-eval` не
|
SHA-256 для оставшихся статических style-атрибутов. `unsafe-inline` и `unsafe-eval` не
|
||||||
используются. Дополнительно выставляются `nosniff`, `DENY`, строгий referrer policy и
|
используются. Дополнительно выставляются `nosniff`, `DENY`, строгий referrer policy и
|
||||||
@@ -361,13 +365,21 @@ 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-контейнера не используется. Для каждого scan задан таймаут `15m`,
|
||||||
|
чтобы загрузка vulnerability/Java DB и анализ Java-слоёв не прерывались стандартным
|
||||||
|
пятиминутным лимитом Trivy; общий лимит job составляет 35 минут.
|
||||||
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 также имеет точную
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
| Компонент | Технология |
|
| Компонент | Технология |
|
||||||
|-----------|-----------|
|
|-----------|-----------|
|
||||||
| **Backend** | Java 17, Spring Boot 3.2.5 |
|
| **Backend** | Java 17, Spring Boot 3.5.16 |
|
||||||
| **Frontend** | Vanilla JavaScript (ES6 Modules) + HTML/CSS |
|
| **Frontend** | Vanilla JavaScript (ES6 Modules) + HTML/CSS |
|
||||||
| **База данных** | PostgreSQL (через Flyway миграции) |
|
| **База данных** | PostgreSQL (через Flyway миграции) |
|
||||||
| **Контейнеризация** | Docker, Docker Compose |
|
| **Контейнеризация** | Docker, Docker Compose |
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ RUN npm run build:vendor
|
|||||||
|
|
||||||
FROM httpd:alpine3.23@sha256:4a15e9c73f25334bc03cfb3c692c9adfc103bb46ca89cee1f0b9a5fcbc7b21f6
|
FROM httpd:alpine3.23@sha256:4a15e9c73f25334bc03cfb3c692c9adfc103bb46ca89cee1f0b9a5fcbc7b21f6
|
||||||
|
|
||||||
|
# Устанавливаем исправления безопасности, опубликованные после сборки базового образа.
|
||||||
|
RUN apk upgrade --no-cache
|
||||||
|
|
||||||
COPY admin/ /usr/local/apache2/htdocs/admin/
|
COPY admin/ /usr/local/apache2/htdocs/admin/
|
||||||
COPY department/ /usr/local/apache2/htdocs/department/
|
COPY department/ /usr/local/apache2/htdocs/department/
|
||||||
COPY edu-office/ /usr/local/apache2/htdocs/edu-office/
|
COPY edu-office/ /usr/local/apache2/htdocs/edu-office/
|
||||||
|
|||||||
@@ -1,23 +1,101 @@
|
|||||||
{
|
{
|
||||||
"generated_at": "2026-07-29T20:02:59.230356+00:00",
|
"generated_at": "2026-07-29T22:16:42.858165+00:00",
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"_gitea_workflows_docker_build_build_and_push_docker_images": {
|
"_gitea_workflows_docker_build_backend_image_pipeline": {
|
||||||
"code_fingerprint": "80373e004c326a786715725be21ddb9214b6ff382a02cb829acad1bb9c9098f8",
|
"code_fingerprint": "3b51aee192b5af2b63ee1b7f62beca1b4524fbbd2b143c9bd29eb039db90f7bd",
|
||||||
"label": "Workflow сборки и публикации Docker-образов",
|
"label": "Pipeline backend-образа",
|
||||||
"last": "2026-07-29T19:59:04.551881+00:00",
|
"last": "2026-07-29T21:50:22.465033+00:00",
|
||||||
"provenance": [
|
"provenance": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T21:50:22.465033+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "Проверить backend на HIGH и CRITICAL уязвимости: context deadline exceeded после загрузки Trivy DB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T21:27:26.637336+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "Проверить опубликованные образы: backend Trivy HIGH/CRITICAL scan завершился exitcode 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T20:38:53.488440+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "The goal you specified requires a project to execute but there is no POM in this directory (/workspace)."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"score": 2.997218887,
|
||||||
|
"source_file": ".gitea/workflows/docker-build.yaml",
|
||||||
|
"status": "preferred",
|
||||||
|
"uses": 3
|
||||||
|
},
|
||||||
|
"_gitea_workflows_docker_build_build_and_push_docker_images": {
|
||||||
|
"code_fingerprint": "3b51aee192b5af2b63ee1b7f62beca1b4524fbbd2b143c9bd29eb039db90f7bd",
|
||||||
|
"label": "Workflow сборки и публикации Docker-образов",
|
||||||
|
"last": "2026-07-29T21:50:22.465033+00:00",
|
||||||
|
"provenance": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T21:50:22.465033+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "Проверить backend на HIGH и CRITICAL уязвимости: context deadline exceeded после загрузки Trivy DB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T21:27:26.637336+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "Проверить опубликованные образы: backend Trivy HIGH/CRITICAL scan завершился exitcode 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T20:38:53.488440+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "The goal you specified requires a project to execute but there is no POM in this directory (/workspace)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T20:12:38.880629+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "сам можешь выполнить эти действия?"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-07-29T19:59:04.551881+00:00",
|
"date": "2026-07-29T19:59:04.551881+00:00",
|
||||||
"outcome": "useful",
|
"outcome": "useful",
|
||||||
"q": "новая схема на проде на удалённом сервере создана через CREATE SCHEMA public. я проверил, давай дальше"
|
"q": "новая схема на проде на удалённом сервере создана через CREATE SCHEMA public. я проверил, давай дальше"
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "2026-07-29T19:26:44.188240+00:00",
|
|
||||||
"outcome": "useful",
|
|
||||||
"q": "теперь напиши мне инструкцию как это всё выыкатить на прод. у мня уже поднят на проде этот сервис со старой базой данных, но я могу её затереть. ci/cd тоже настроен, по сути я прямо сейчас могу залить это в ветку мейн и оно должно раскататься. каие есть подводные камни после изменений безопасности которые мы внесли?"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.999355769,
|
"score": 5.990300641,
|
||||||
|
"source_file": ".gitea/workflows/docker-build.yaml",
|
||||||
|
"status": "preferred",
|
||||||
|
"uses": 6
|
||||||
|
},
|
||||||
|
"_gitea_workflows_docker_build_deploy_to_k8s": {
|
||||||
|
"code_fingerprint": "3b51aee192b5af2b63ee1b7f62beca1b4524fbbd2b143c9bd29eb039db90f7bd",
|
||||||
|
"label": "Job деплоя в Kubernetes",
|
||||||
|
"last": "2026-07-29T20:12:38.880629+00:00",
|
||||||
|
"provenance": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T20:12:38.880629+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "сам можешь выполнить эти действия?"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"score": 0.998011327,
|
||||||
|
"source_file": ".gitea/workflows/docker-build.yaml",
|
||||||
|
"status": "tentative",
|
||||||
|
"uses": 1
|
||||||
|
},
|
||||||
|
"_gitea_workflows_docker_build_frontend_image_pipeline": {
|
||||||
|
"code_fingerprint": "3b51aee192b5af2b63ee1b7f62beca1b4524fbbd2b143c9bd29eb039db90f7bd",
|
||||||
|
"label": "Pipeline frontend-образа",
|
||||||
|
"last": "2026-07-29T21:50:22.465033+00:00",
|
||||||
|
"provenance": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T21:50:22.465033+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "Проверить backend на HIGH и CRITICAL уязвимости: context deadline exceeded после загрузки Trivy DB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T20:38:53.488440+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "The goal you specified requires a project to execute but there is no POM in this directory (/workspace)."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"score": 1.998009121,
|
||||||
"source_file": ".gitea/workflows/docker-build.yaml",
|
"source_file": ".gitea/workflows/docker-build.yaml",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 2
|
"uses": 2
|
||||||
@@ -43,7 +121,7 @@
|
|||||||
"q": "Почему RequireRoles связывает множество контроллеров, моделей и сервисов как cross-community bridge?"
|
"q": "Почему RequireRoles связывает множество контроллеров, моделей и сервисов как cross-community bridge?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.861135007,
|
"score": 1.857145922,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/config/auth/AuthorizationInterceptor.java",
|
"source_file": "backend/src/main/java/com/magistr/app/config/auth/AuthorizationInterceptor.java",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 3
|
"uses": 3
|
||||||
@@ -59,7 +137,7 @@
|
|||||||
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.661231273,
|
"score": 0.659814015,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/config/auth/JwtTokenService.java",
|
"source_file": "backend/src/main/java/com/magistr/app/config/auth/JwtTokenService.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -75,7 +153,7 @@
|
|||||||
"q": "Почему RequireRoles связывает множество контроллеров, моделей и сервисов как cross-community bridge?"
|
"q": "Почему RequireRoles связывает множество контроллеров, моделей и сервисов как cross-community bridge?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.556344408,
|
"score": 0.555151961,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/config/auth/RequireRoles.java",
|
"source_file": "backend/src/main/java/com/magistr/app/config/auth/RequireRoles.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -91,7 +169,7 @@
|
|||||||
"q": "Как безопасно обновлять tenant DataSource без потери рабочего подключения?"
|
"q": "Как безопасно обновлять tenant DataSource без потери рабочего подключения?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.676664767,
|
"score": 0.67521443,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/config/DataInitializer.java",
|
"source_file": "backend/src/main/java/com/magistr/app/config/DataInitializer.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -107,7 +185,7 @@
|
|||||||
"q": "проанализируй весь проект на баги и ошибки и дополни '/mnt/HDD/ProjectMagistr/magistr/BUG_REPORT.md'"
|
"q": "проанализируй весь проект на баги и ошибки и дополни '/mnt/HDD/ProjectMagistr/magistr/BUG_REPORT.md'"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.643559327,
|
"score": 0.642179947,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/config/tenant/ConfigMapUpdater.java",
|
"source_file": "backend/src/main/java/com/magistr/app/config/tenant/ConfigMapUpdater.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -123,7 +201,7 @@
|
|||||||
"q": "Как безопасно заменить tenant DataSource без потери рабочего подключения?"
|
"q": "Как безопасно заменить tenant DataSource без потери рабочего подключения?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.676550037,
|
"score": 0.675099946,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/config/tenant/TenantConfig.java",
|
"source_file": "backend/src/main/java/com/magistr/app/config/tenant/TenantConfig.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -149,7 +227,7 @@
|
|||||||
"q": "проанализируй весь проект на баги и ошибки и дополни '/mnt/HDD/ProjectMagistr/magistr/BUG_REPORT.md'"
|
"q": "проанализируй весь проект на баги и ошибки и дополни '/mnt/HDD/ProjectMagistr/magistr/BUG_REPORT.md'"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.99677413,
|
"score": 1.992494322,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/config/tenant/TenantConfigWatcher.java",
|
"source_file": "backend/src/main/java/com/magistr/app/config/tenant/TenantConfigWatcher.java",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 3
|
"uses": 3
|
||||||
@@ -175,7 +253,7 @@
|
|||||||
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 2.014446076,
|
"score": 2.01012839,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/config/tenant/TenantRoutingDataSource.java",
|
"source_file": "backend/src/main/java/com/magistr/app/config/tenant/TenantRoutingDataSource.java",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 3
|
"uses": 3
|
||||||
@@ -191,7 +269,7 @@
|
|||||||
"q": "Почему в выборе аудитории показаны корпус и этаж?"
|
"q": "Почему в выборе аудитории показаны корпус и этаж?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.831862505,
|
"score": 0.830079523,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/controller/ClassroomController.java",
|
"source_file": "backend/src/main/java/com/magistr/app/controller/ClassroomController.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -207,7 +285,7 @@
|
|||||||
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.661231273,
|
"score": 0.659814015,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/controller/DatabaseController.java",
|
"source_file": "backend/src/main/java/com/magistr/app/controller/DatabaseController.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -228,7 +306,7 @@
|
|||||||
"q": "Как безопасно заменить tenant DataSource без потери рабочего подключения?"
|
"q": "Как безопасно заменить tenant DataSource без потери рабочего подключения?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.353214804,
|
"score": 1.350314375,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/controller/DatabaseController.java",
|
"source_file": "backend/src/main/java/com/magistr/app/controller/DatabaseController.java",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 2
|
"uses": 2
|
||||||
@@ -244,7 +322,7 @@
|
|||||||
"q": "проанализируй весь проект на баги и ошибки и дополни '/mnt/HDD/ProjectMagistr/magistr/BUG_REPORT.md'"
|
"q": "проанализируй весь проект на баги и ошибки и дополни '/mnt/HDD/ProjectMagistr/magistr/BUG_REPORT.md'"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.643559327,
|
"score": 0.642179947,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/controller/GlobalExceptionHandler.java",
|
"source_file": "backend/src/main/java/com/magistr/app/controller/GlobalExceptionHandler.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -265,7 +343,7 @@
|
|||||||
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.492325672,
|
"score": 1.489127079,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/controller/ScheduleController.java",
|
"source_file": "backend/src/main/java/com/magistr/app/controller/ScheduleController.java",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 2
|
"uses": 2
|
||||||
@@ -281,7 +359,7 @@
|
|||||||
"q": "Как безопасно валидировать и сохранять точечные изменения расписания?"
|
"q": "Как безопасно валидировать и сохранять точечные изменения расписания?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.671815777,
|
"score": 0.670375833,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/controller/ScheduleOverrideController.java",
|
"source_file": "backend/src/main/java/com/magistr/app/controller/ScheduleOverrideController.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -302,7 +380,7 @@
|
|||||||
"q": "проанализируй весь проект на баги и ошибки и дополни '/mnt/HDD/ProjectMagistr/magistr/BUG_REPORT.md'"
|
"q": "проанализируй весь проект на баги и ошибки и дополни '/mnt/HDD/ProjectMagistr/magistr/BUG_REPORT.md'"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.319879771,
|
"score": 1.317050791,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/controller/ScheduleRuleAdminController.java",
|
"source_file": "backend/src/main/java/com/magistr/app/controller/ScheduleRuleAdminController.java",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 2
|
"uses": 2
|
||||||
@@ -318,7 +396,7 @@
|
|||||||
"q": "хотим поменять название сервиса, придумай что-то короткое одним словом подходящее по смыслу"
|
"q": "хотим поменять название сервиса, придумай что-то короткое одним словом подходящее по смыслу"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.851581632,
|
"score": 0.849756385,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/model/AcademicCalendar.java",
|
"source_file": "backend/src/main/java/com/magistr/app/model/AcademicCalendar.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -334,7 +412,7 @@
|
|||||||
"q": "Почему в выборе аудитории показаны корпус и этаж?"
|
"q": "Почему в выборе аудитории показаны корпус и этаж?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.831862505,
|
"score": 0.830079523,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/model/Classroom.java",
|
"source_file": "backend/src/main/java/com/magistr/app/model/Classroom.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -350,7 +428,7 @@
|
|||||||
"q": "хотим поменять название сервиса, придумай что-то короткое одним словом подходящее по смыслу"
|
"q": "хотим поменять название сервиса, придумай что-то короткое одним словом подходящее по смыслу"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.851581632,
|
"score": 0.849756385,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/model/Department.java",
|
"source_file": "backend/src/main/java/com/magistr/app/model/Department.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -371,7 +449,7 @@
|
|||||||
"q": "Корректны ли inferred-связи вокруг ScheduleRule?"
|
"q": "Корректны ли inferred-связи вокруг ScheduleRule?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.23328582,
|
"score": 1.230642443,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/model/ScheduleRule.java",
|
"source_file": "backend/src/main/java/com/magistr/app/model/ScheduleRule.java",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 2
|
"uses": 2
|
||||||
@@ -387,7 +465,7 @@
|
|||||||
"q": "Какие связи затрагивает полная валидация правил расписания в исправлении №7?"
|
"q": "Какие связи затрагивает полная валидация правил расписания в исправлении №7?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.676320444,
|
"score": 0.674870845,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/model/ScheduleRuleSlot.java",
|
"source_file": "backend/src/main/java/com/magistr/app/model/ScheduleRuleSlot.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -408,7 +486,7 @@
|
|||||||
"q": "в дополнительные фильры просмотра расписаний нужно добавить выбор семестра"
|
"q": "в дополнительные фильры просмотра расписаний нужно добавить выбор семестра"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.682676032,
|
"score": 1.679069449,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/model/Semester.java",
|
"source_file": "backend/src/main/java/com/magistr/app/model/Semester.java",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 2
|
"uses": 2
|
||||||
@@ -424,7 +502,7 @@
|
|||||||
"q": "хотим поменять название сервиса, придумай что-то короткое одним словом подходящее по смыслу"
|
"q": "хотим поменять название сервиса, придумай что-то короткое одним словом подходящее по смыслу"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.851581632,
|
"score": 0.849756385,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/model/StudentGroup.java",
|
"source_file": "backend/src/main/java/com/magistr/app/model/StudentGroup.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -440,7 +518,7 @@
|
|||||||
"q": "Какие связи затрагивает полная валидация правил расписания в исправлении №7?"
|
"q": "Какие связи затрагивает полная валидация правил расписания в исправлении №7?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.676320444,
|
"score": 0.674870845,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/repository/SemesterRepository.java",
|
"source_file": "backend/src/main/java/com/magistr/app/repository/SemesterRepository.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -461,7 +539,7 @@
|
|||||||
"q": "Как безопасно валидировать и сохранять точечные изменения расписания?"
|
"q": "Как безопасно валидировать и сохранять точечные изменения расписания?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.502910177,
|
"score": 1.499688898,
|
||||||
"source_file": "backend/src/main/java/com/magistr/app/service/ScheduleQueryService.java",
|
"source_file": "backend/src/main/java/com/magistr/app/service/ScheduleQueryService.java",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 2
|
"uses": 2
|
||||||
@@ -477,7 +555,7 @@
|
|||||||
"q": "Корректны ли inferred-связи вокруг ScheduleRule?"
|
"q": "Корректны ли inferred-связи вокруг ScheduleRule?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.556965376,
|
"score": 0.555771598,
|
||||||
"source_file": "backend/src/test/java/com/magistr/app/model/ScheduleRuleTest.java",
|
"source_file": "backend/src/test/java/com/magistr/app/model/ScheduleRuleTest.java",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -493,7 +571,7 @@
|
|||||||
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
"q": "Какие подсистемы связывают исправления аутентификации, tenant-конфигурации, расписания и инфраструктуры?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.661231273,
|
"score": 0.659814015,
|
||||||
"source_file": "compose.yaml",
|
"source_file": "compose.yaml",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -509,7 +587,7 @@
|
|||||||
"q": "новая схема на проде на удалённом сервере создана через CREATE SCHEMA public. я проверил, давай дальше"
|
"q": "новая схема на проде на удалённом сервере создана через CREATE SCHEMA public. я проверил, давай дальше"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.999937245,
|
"score": 0.997794018,
|
||||||
"source_file": "compose.yaml",
|
"source_file": "compose.yaml",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
@@ -525,32 +603,68 @@
|
|||||||
"q": "теперь напиши мне инструкцию как это всё выыкатить на прод. у мня уже поднят на проде этот сервис со старой базой данных, но я могу её затереть. ci/cd тоже настроен, по сути я прямо сейчас могу залить это в ветку мейн и оно должно раскататься. каие есть подводные камни после изменений безопасности которые мы внесли?"
|
"q": "теперь напиши мне инструкцию как это всё выыкатить на прод. у мня уже поднят на проде этот сервис со старой базой данных, но я могу её затереть. ci/cd тоже настроен, по сути я прямо сейчас могу залить это в ветку мейн и оно должно раскататься. каие есть подводные камни после изменений безопасности которые мы внесли?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.999418524,
|
"score": 0.997276409,
|
||||||
"source_file": "docs/DATABASE.md",
|
"source_file": "docs/DATABASE.md",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
},
|
},
|
||||||
"docs_infrastructure_jwt_secret_policy": {
|
"docs_infrastructure_gitea_actions_cicd": {
|
||||||
"code_fingerprint": "fbce586f3b64dba203d336f5744d9895924e86e122cdde1939c0dec1600e1af1",
|
"code_fingerprint": "038c159537c77b4bfe631532511e807aac2677f3a26d722cdc9048402cf58f4b",
|
||||||
"label": "Политика JWT-секрета",
|
"label": "CI/CD Gitea Actions в инфраструктуре",
|
||||||
"last": "2026-07-29T19:26:44.188240+00:00",
|
"last": "2026-07-29T21:50:22.465033+00:00",
|
||||||
"provenance": [
|
"provenance": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T21:50:22.465033+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "Проверить backend на HIGH и CRITICAL уязвимости: context deadline exceeded после загрузки Trivy DB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T21:27:26.637336+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "Проверить опубликованные образы: backend Trivy HIGH/CRITICAL scan завершился exitcode 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T20:38:53.488440+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "The goal you specified requires a project to execute but there is no POM in this directory (/workspace)."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"score": 2.997218887,
|
||||||
|
"source_file": "docs/INFRASTRUCTURE.md",
|
||||||
|
"status": "preferred",
|
||||||
|
"uses": 3
|
||||||
|
},
|
||||||
|
"docs_infrastructure_jwt_secret_policy": {
|
||||||
|
"code_fingerprint": "038c159537c77b4bfe631532511e807aac2677f3a26d722cdc9048402cf58f4b",
|
||||||
|
"label": "Политика JWT-секрета",
|
||||||
|
"last": "2026-07-29T20:12:38.880629+00:00",
|
||||||
|
"provenance": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T20:12:38.880629+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "сам можешь выполнить эти действия?"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-07-29T19:26:44.188240+00:00",
|
"date": "2026-07-29T19:26:44.188240+00:00",
|
||||||
"outcome": "useful",
|
"outcome": "useful",
|
||||||
"q": "теперь напиши мне инструкцию как это всё выыкатить на прод. у мня уже поднят на проде этот сервис со старой базой данных, но я могу её затереть. ci/cd тоже настроен, по сути я прямо сейчас могу залить это в ветку мейн и оно должно раскататься. каие есть подводные камни после изменений безопасности которые мы внесли?"
|
"q": "теперь напиши мне инструкцию как это всё выыкатить на прод. у мня уже поднят на проде этот сервис со старой базой данных, но я могу её затереть. ci/cd тоже настроен, по сути я прямо сейчас могу залить это в ветку мейн и оно должно раскататься. каие есть подводные камни после изменений безопасности которые мы внесли?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.999418524,
|
"score": 1.995287736,
|
||||||
"source_file": "docs/INFRASTRUCTURE.md",
|
"source_file": "docs/INFRASTRUCTURE.md",
|
||||||
"status": "tentative",
|
"status": "preferred",
|
||||||
"uses": 1
|
"uses": 2
|
||||||
},
|
},
|
||||||
"docs_infrastructure_kubernetes_production": {
|
"docs_infrastructure_kubernetes_production": {
|
||||||
"code_fingerprint": "fbce586f3b64dba203d336f5744d9895924e86e122cdde1939c0dec1600e1af1",
|
"code_fingerprint": "038c159537c77b4bfe631532511e807aac2677f3a26d722cdc9048402cf58f4b",
|
||||||
"label": "Production-инфраструктура Kubernetes",
|
"label": "Production-инфраструктура Kubernetes",
|
||||||
"last": "2026-07-29T19:59:04.551881+00:00",
|
"last": "2026-07-29T20:12:38.880629+00:00",
|
||||||
"provenance": [
|
"provenance": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T20:12:38.880629+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "сам можешь выполнить эти действия?"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-07-29T19:59:04.551881+00:00",
|
"date": "2026-07-29T19:59:04.551881+00:00",
|
||||||
"outcome": "useful",
|
"outcome": "useful",
|
||||||
@@ -562,13 +676,13 @@
|
|||||||
"q": "теперь напиши мне инструкцию как это всё выыкатить на прод. у мня уже поднят на проде этот сервис со старой базой данных, но я могу её затереть. ci/cd тоже настроен, по сути я прямо сейчас могу залить это в ветку мейн и оно должно раскататься. каие есть подводные камни после изменений безопасности которые мы внесли?"
|
"q": "теперь напиши мне инструкцию как это всё выыкатить на прод. у мня уже поднят на проде этот сервис со старой базой данных, но я могу её затереть. ci/cd тоже настроен, по сути я прямо сейчас могу залить это в ветку мейн и оно должно раскататься. каие есть подводные камни после изменений безопасности которые мы внесли?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 1.999355769,
|
"score": 2.993081754,
|
||||||
"source_file": "docs/INFRASTRUCTURE.md",
|
"source_file": "docs/INFRASTRUCTURE.md",
|
||||||
"status": "preferred",
|
"status": "preferred",
|
||||||
"uses": 2
|
"uses": 3
|
||||||
},
|
},
|
||||||
"docs_infrastructure_tenants_configmap": {
|
"docs_infrastructure_tenants_configmap": {
|
||||||
"code_fingerprint": "fbce586f3b64dba203d336f5744d9895924e86e122cdde1939c0dec1600e1af1",
|
"code_fingerprint": "038c159537c77b4bfe631532511e807aac2677f3a26d722cdc9048402cf58f4b",
|
||||||
"label": "Tenants ConfigMap",
|
"label": "Tenants ConfigMap",
|
||||||
"last": "2026-07-29T19:26:44.188240+00:00",
|
"last": "2026-07-29T19:26:44.188240+00:00",
|
||||||
"provenance": [
|
"provenance": [
|
||||||
@@ -578,10 +692,26 @@
|
|||||||
"q": "теперь напиши мне инструкцию как это всё выыкатить на прод. у мня уже поднят на проде этот сервис со старой базой данных, но я могу её затереть. ci/cd тоже настроен, по сути я прямо сейчас могу залить это в ветку мейн и оно должно раскататься. каие есть подводные камни после изменений безопасности которые мы внесли?"
|
"q": "теперь напиши мне инструкцию как это всё выыкатить на прод. у мня уже поднят на проде этот сервис со старой базой данных, но я могу её затереть. ci/cd тоже настроен, по сути я прямо сейчас могу залить это в ветку мейн и оно должно раскататься. каие есть подводные камни после изменений безопасности которые мы внесли?"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"score": 0.999418524,
|
"score": 0.997276409,
|
||||||
"source_file": "docs/INFRASTRUCTURE.md",
|
"source_file": "docs/INFRASTRUCTURE.md",
|
||||||
"status": "tentative",
|
"status": "tentative",
|
||||||
"uses": 1
|
"uses": 1
|
||||||
|
},
|
||||||
|
"springbootapplication": {
|
||||||
|
"code_fingerprint": "",
|
||||||
|
"label": "SpringBootApplication",
|
||||||
|
"last": "2026-07-29T21:27:26.637336+00:00",
|
||||||
|
"provenance": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-29T21:27:26.637336+00:00",
|
||||||
|
"outcome": "useful",
|
||||||
|
"q": "Проверить опубликованные образы: backend Trivy HIGH/CRITICAL scan завершился exitcode 1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"score": 0.999209766,
|
||||||
|
"source_file": "",
|
||||||
|
"status": "tentative",
|
||||||
|
"uses": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version": 1
|
"version": 1
|
||||||
|
|||||||
@@ -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 в инфраструктуре
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
type: "query"
|
||||||
|
date: "2026-07-29T21:27:26.637336+00:00"
|
||||||
|
question: "Проверить опубликованные образы: backend Trivy HIGH/CRITICAL scan завершился exitcode 1"
|
||||||
|
contributor: "graphify"
|
||||||
|
outcome: "useful"
|
||||||
|
source_nodes: ["Workflow сборки и публикации Docker-образов", "Pipeline backend-образа", "CI/CD Gitea Actions в инфраструктуре", "SpringBootApplication"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Q: Проверить опубликованные образы: backend Trivy HIGH/CRITICAL scan завершился exitcode 1
|
||||||
|
|
||||||
|
## Answer
|
||||||
|
|
||||||
|
CI дошёл до сканирования опубликованного backend-образа, поэтому передача исходников в контейнеры уже исправлена. HIGH/CRITICAL находились в устаревших пакетах Alpine и Java-зависимостях Spring Boot 3.2.5. Backend обновлён до Spring Boot 3.5.16, PostgreSQL JDBC закреплён на 42.7.12, для Flyway 11 добавлен flyway-database-postgresql, runtime устанавливает исправления Alpine. Локальный образ прошёл тот же Trivy gate: Alpine 0, app.jar 0, OpenTelemetry agent 0. Полный backend-набор: 300 тестов без ошибок; обязательные CI, frontend, Compose и supply-chain проверки также прошли.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- Signal: useful
|
||||||
|
|
||||||
|
## Source Nodes
|
||||||
|
|
||||||
|
- Workflow сборки и публикации Docker-образов
|
||||||
|
- Pipeline backend-образа
|
||||||
|
- CI/CD Gitea Actions в инфраструктуре
|
||||||
|
- SpringBootApplication
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
type: "query"
|
||||||
|
date: "2026-07-29T21:50:22.465033+00:00"
|
||||||
|
question: "Проверить backend на HIGH и CRITICAL уязвимости: context deadline exceeded после загрузки Trivy DB"
|
||||||
|
contributor: "graphify"
|
||||||
|
outcome: "useful"
|
||||||
|
source_nodes: ["Workflow сборки и публикации Docker-образов", "Pipeline backend-образа", "Pipeline frontend-образа", "CI/CD Gitea Actions в инфраструктуре"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Q: Проверить backend на HIGH и CRITICAL уязвимости: context deadline exceeded после загрузки Trivy DB
|
||||||
|
|
||||||
|
## Answer
|
||||||
|
|
||||||
|
Expanded from original query via graph vocab: [backend, build, cache, container, docker, gitea, pipeline, registry, workflow]. Первый traversal по cache попал в frontend cache и был отброшен; уточнённый traversal вывел Workflow сборки и публикации Docker-образов, Pipeline backend-образа и Pipeline frontend-образа. Прямой разбор CI-лога подтвердил: Trivy не сформировал vulnerability report и завершился по стандартному timeout 5m во время Java layer analysis после загрузки vulnerability/Java DB. В workflow обоим scan добавлен --timeout 15m, security-scan job увеличен до 35 минут; severity HIGH,CRITICAL, exit-code 1, ignore-unfixed, Java DB и digest pinning сохранены. Локальный Trivy 0.63.0 с новой командой завершился успешно: Alpine 0, app.jar 0, OpenTelemetry agent 0. Regression gates и YAML validation прошли.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- Signal: useful
|
||||||
|
|
||||||
|
## Source Nodes
|
||||||
|
|
||||||
|
- Workflow сборки и публикации Docker-образов
|
||||||
|
- Pipeline backend-образа
|
||||||
|
- Pipeline frontend-образа
|
||||||
|
- CI/CD Gitea Actions в инфраструктуре
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
type: "query"
|
||||||
|
date: "2026-07-29T22:22:22.155742+00:00"
|
||||||
|
question: "Проверить frontend на HIGH и CRITICAL уязвимости: 8 HIGH в Alpine runtime"
|
||||||
|
contributor: "graphify"
|
||||||
|
outcome: "useful"
|
||||||
|
source_nodes: ["Workflow сборки и публикации Docker-образов", "Pipeline frontend-образа", "CI/CD Gitea Actions в инфраструктуре"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Q: Проверить frontend на HIGH и CRITICAL уязвимости: 8 HIGH в Alpine runtime
|
||||||
|
|
||||||
|
## Answer
|
||||||
|
|
||||||
|
Expanded from original query via graph vocab: [build, container, docker, frontend, gitea, pipeline, registry, workflow]. Traversal вывел Workflow сборки и публикации Docker-образов и Pipeline frontend-образа; фактическая причина проверена по frontend/Dockerfile и Trivy-логу. В финальном httpd:alpine 3.23.4 найдены 8 исправимых HIGH: c-ares, libcrypto3/libssl3, libcurl и libexpat. В runtime-stage добавлен RUN apk upgrade --no-cache. Локальная Docker-сборка подтвердила обновление ровно до fixed versions из отчёта: c-ares 1.34.8-r0, OpenSSL 3.5.7-r0, libcurl 8.20.0-r0, libexpat 2.8.2-r0, Alpine 3.23.5. Supply-chain regression gates и frontend 11/11 прошли. Отдельный локальный Trivy-процесс не стартовал из-за таймаута механизма разрешений Docker socket; окончательный scan выполняет новый CI-run.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- Signal: useful
|
||||||
|
|
||||||
|
## Source Nodes
|
||||||
|
|
||||||
|
- Workflow сборки и публикации Docker-образов
|
||||||
|
- Pipeline frontend-образа
|
||||||
|
- CI/CD Gitea Actions в инфраструктуре
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
# Lessons
|
# Lessons
|
||||||
|
|
||||||
_Auto-generated by `graphify reflect` from 14 session memories in graphify-out/memory/. Deterministic; no LLM. Use for orientation — verify before relying, and revisit dead ends if the code has changed since._
|
_Auto-generated by `graphify reflect` from 18 session memories in graphify-out/memory/. Deterministic; no LLM. Use for orientation — verify before relying, and revisit dead ends if the code has changed since._
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- 13 useful · 1 dead ends · 0 corrected · 0 unmarked
|
- 17 useful · 1 dead ends · 0 corrected · 0 unmarked
|
||||||
|
|
||||||
## Lessons
|
## Lessons
|
||||||
|
|
||||||
**Preferred sources** — corroborated by ≥2 useful results; start here.
|
**Preferred sources** — corroborated by ≥2 useful results; start here.
|
||||||
|
|
||||||
|
- `Workflow сборки и публикации Docker-образов` (6× useful)
|
||||||
|
- `CI/CD Gitea Actions в инфраструктуре` (3× useful)
|
||||||
|
- `Pipeline backend-образа` (3× useful)
|
||||||
|
- `Production-инфраструктура Kubernetes` (3× useful)
|
||||||
- `ScheduleGeneratorService` (3× useful)
|
- `ScheduleGeneratorService` (3× useful)
|
||||||
- `TenantRoutingDataSource` (3× useful)
|
- `TenantRoutingDataSource` (3× useful)
|
||||||
- `Production-инфраструктура Kubernetes` (2× useful)
|
- `Pipeline frontend-образа` (2× useful)
|
||||||
- `Workflow сборки и публикации Docker-образов` (2× useful)
|
- `Политика JWT-секрета` (2× useful)
|
||||||
- `TenantConfigWatcher` (3× useful)
|
- `TenantConfigWatcher` (3× useful)
|
||||||
- `AuthorizationInterceptor` (3× useful)
|
- `AuthorizationInterceptor` (3× useful)
|
||||||
- `Semester` (2× useful)
|
- `Semester` (2× useful)
|
||||||
@@ -25,10 +29,11 @@ _Auto-generated by `graphify reflect` from 14 session memories in graphify-out/m
|
|||||||
|
|
||||||
**Tentative** — useful in fewer than 2 results; verify before relying.
|
**Tentative** — useful in fewer than 2 results; verify before relying.
|
||||||
|
|
||||||
|
- `SpringBootApplication` (1× useful)
|
||||||
|
- `Job деплоя в Kubernetes` (1× useful)
|
||||||
- `PostgreSQL-сервис Docker Compose` (1× useful)
|
- `PostgreSQL-сервис Docker Compose` (1× useful)
|
||||||
- `Flyway-миграции` (1× useful)
|
- `Flyway-миграции` (1× useful)
|
||||||
- `Tenants ConfigMap` (1× useful)
|
- `Tenants ConfigMap` (1× useful)
|
||||||
- `Политика JWT-секрета` (1× useful)
|
|
||||||
- `AcademicCalendar` (1× useful)
|
- `AcademicCalendar` (1× useful)
|
||||||
- `Department` (1× useful)
|
- `Department` (1× useful)
|
||||||
- `StudentGroup` (1× useful)
|
- `StudentGroup` (1× useful)
|
||||||
|
|||||||
@@ -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,27 +62,59 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
check_workflow_gates() {
|
check_workflow_gates() {
|
||||||
|
local security_timeout
|
||||||
|
|
||||||
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"
|
||||||
require_count '^[[:space:]]+provenance:[[:space:]]+mode=max$' 2 \
|
require_count '^[[:space:]]+provenance:[[:space:]]+mode=max$' 2 \
|
||||||
"для обоих образов должна публиковаться provenance attestation"
|
"для обоих образов должна публиковаться provenance attestation"
|
||||||
require_count 'image --scanners vuln --exit-code 1 --severity HIGH,CRITICAL' 2 \
|
require_count 'image --timeout 15m --scanners vuln --exit-code 1 --severity HIGH,CRITICAL' 2 \
|
||||||
"оба образа должны проходить блокирующее HIGH/CRITICAL-сканирование"
|
"оба образа должны проходить блокирующее HIGH/CRITICAL-сканирование с достаточным таймаутом"
|
||||||
require_count 'TRIVY_IMAGE: [^[:space:]]+@sha256:[0-9a-f]{64}$' 1 \
|
require_count 'TRIVY_IMAGE: [^[:space:]]+@sha256:[0-9a-f]{64}$' 1 \
|
||||||
"образ Trivy должен быть закреплён digest"
|
"образ Trivy должен быть закреплён digest"
|
||||||
require_count 'MAVEN_IMAGE: [^[:space:]]+@sha256:[0-9a-f]{64}$' 1 \
|
require_count 'MAVEN_IMAGE: [^[:space:]]+@sha256:[0-9a-f]{64}$' 1 \
|
||||||
"образ 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"
|
||||||
|
|
||||||
|
security_timeout=$(awk '
|
||||||
|
$0 == " security-scan:" {
|
||||||
|
in_job = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
in_job && $0 ~ /^ [[:alnum:]_-]+:$/ {
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
in_job && $1 == "timeout-minutes:" {
|
||||||
|
print $2
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "$WORKFLOW")
|
||||||
|
if [[ "$security_timeout" != "35" ]]; then
|
||||||
|
fail "security-scan job должен иметь таймаут 35 минут"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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"
|
||||||
@@ -108,6 +142,19 @@ check_downloaded_backend_artifact() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_runtime_updates() {
|
||||||
|
local component
|
||||||
|
local dockerfile
|
||||||
|
|
||||||
|
for component in backend frontend; do
|
||||||
|
dockerfile="$PROJECT_ROOT/$component/Dockerfile"
|
||||||
|
require_file "$dockerfile" || continue
|
||||||
|
if ! grep -Fq 'RUN apk upgrade --no-cache' "$dockerfile"; then
|
||||||
|
fail "runtime-образ $component должен устанавливать доступные исправления Alpine"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
check_testcontainer_images() {
|
check_testcontainer_images() {
|
||||||
local image_catalog="$PROJECT_ROOT/backend/src/test/java/com/magistr/app/testing/TestContainerImages.java"
|
local image_catalog="$PROJECT_ROOT/backend/src/test/java/com/magistr/app/testing/TestContainerImages.java"
|
||||||
|
|
||||||
@@ -145,6 +192,7 @@ check_compose_images
|
|||||||
check_actions
|
check_actions
|
||||||
check_workflow_gates
|
check_workflow_gates
|
||||||
check_downloaded_backend_artifact
|
check_downloaded_backend_artifact
|
||||||
|
check_runtime_updates
|
||||||
check_testcontainer_images
|
check_testcontainer_images
|
||||||
check_optional_kubernetes_images
|
check_optional_kubernetes_images
|
||||||
|
|
||||||
|
|||||||
87
scripts/run-ci-container.sh
Executable file
87
scripts/run-ci-container.sh
Executable 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"
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -29,6 +37,41 @@ if PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cp "$ROOT_DIR/backend/Dockerfile" "$FIXTURE_DIR/backend/Dockerfile"
|
||||||
|
cp "$ROOT_DIR/.gitea/workflows/docker-build.yaml" \
|
||||||
|
"$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
|
||||||
|
sed -i '/RUN apk upgrade --no-cache/d' "$FIXTURE_DIR/backend/Dockerfile"
|
||||||
|
if PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null 2>&1; then
|
||||||
|
printf 'Ошибка теста: отсутствие Alpine security updates в backend не было отклонено\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$ROOT_DIR/backend/Dockerfile" "$FIXTURE_DIR/backend/Dockerfile"
|
||||||
|
sed -i '/RUN apk upgrade --no-cache/d' "$FIXTURE_DIR/frontend/Dockerfile"
|
||||||
|
if PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null 2>&1; then
|
||||||
|
printf 'Ошибка теста: отсутствие Alpine security updates во frontend не было отклонено\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$ROOT_DIR/frontend/Dockerfile" "$FIXTURE_DIR/frontend/Dockerfile"
|
||||||
|
cp "$ROOT_DIR/.gitea/workflows/docker-build.yaml" \
|
||||||
|
"$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
|
||||||
|
sed -i '0,/image --timeout 15m --scanners vuln/{s/image --timeout 15m --scanners vuln/image --scanners vuln/;}' \
|
||||||
|
"$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
|
||||||
|
if PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null 2>&1; then
|
||||||
|
printf 'Ошибка теста: недостаточный таймаут Trivy не был отклонён\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$ROOT_DIR/.gitea/workflows/docker-build.yaml" \
|
||||||
|
"$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
|
||||||
|
sed -i '0,/timeout-minutes: 35/s//timeout-minutes: 20/' \
|
||||||
|
"$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
|
||||||
|
if PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null 2>&1; then
|
||||||
|
printf 'Ошибка теста: недостаточный таймаут security-scan job не был отклонён\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
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"
|
||||||
sed -i "/gitea.event_name == 'workflow_dispatch'/d" \
|
sed -i "/gitea.event_name == 'workflow_dispatch'/d" \
|
||||||
@@ -38,4 +81,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'
|
||||||
|
|||||||
75
scripts/test-run-ci-container.sh
Executable file
75
scripts/test-run-ci-container.sh
Executable 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'
|
||||||
Reference in New Issue
Block a user