Files
mdm-ordinis/.gitlab-ci.yml
T
Zimin A.N. 6805c70a6b ci: semantic-release automation for tag/changelog (Option D)
Закрывает эту тему окончательно. После merge в main CI анализирует
conventional commits с последнего tag'а, бампит package.json + CHANGELOG.md
и пушит git tag vX.Y.Z. Tag pipeline далее собирает images и ждёт
manual gate на trigger-deploy-prod.

Что добавил:
- ordinis-admin-ui/.releaserc.json — config (commit-analyzer, release-notes,
  changelog, npm[no-publish], git, gitlab). branches: ['main']. Маппинг
  conventional-commit types к bump levels.
- ordinis-admin-ui/package.json — devDeps: semantic-release@24.2 + 6 plugins.
- .gitlab-ci.yml — new 'release' stage + release job:
  - needs: trigger-deploy-staging (release ТОЛЬКО после зелёного staging)
  - image: node:22-alpine + pnpm@9.15.4
  - GL_TOKEN из CI variable GITLAB_TOKEN (semantic-release/gitlab требует
    именно GL_TOKEN env name)
  - allow_failure: true (нет релизных commits = OK, не блокирует pipeline)
- RELEASE.md — operator docs:
  - One-time setup: создать Project Access Token + GITLAB_TOKEN CI var
  - Commit conventions table (feat→minor, fix→patch, breaking→major)
  - Skip release (use chore: type или [skip ci])
  - Dry-run command для local validation

Prod safety: trigger-deploy-prod остаётся manual (when: manual). Автоматизация
ТОЛЬКО tag-создания, не deploy. Tag → CI runs → human кликает Deploy.

ACTION REQUIRED от user (one-time):
  1. GitLab → Settings → Access Tokens → создать Project Access Token
     scopes: write_repository + api, role: Maintainer
  2. GitLab → Settings → CI/CD → Variables → GITLAB_TOKEN (Masked, Protected)

Без этого release job будет fail'ить (но pipeline не покраснеет — allow_failure).
2026-05-11 14:51:03 +03:00

418 lines
17 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================
# Ordinis — build & push images.
# Path-aware CI: backend и frontend jobs запускаются только когда меняются
# соответствующие пути. Trigger downstream передаёт BACKEND_IMAGE_TAG и/или
# FRONTEND_IMAGE_TAG; infra-pipeline применяет helm upgrade с per-service
# overrides — сервисы которые не пересобирались не катятся.
# =============================================================
stages:
- test
- build
- publish
- trigger-deploy
- release
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
MAVEN_CLI_OPTS: "-B -ntp --batch-mode"
IMAGE_TAG: $CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
REGISTRY: repo.nstart.cloud
IMAGE_REPO: terravault
DOCKER_BUILDKIT: "1"
cache:
key:
files: ["pom.xml"]
paths:
- .m2/repository
default:
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
- script_failure
# Backend changes триггерят maven + docker для всех Java-сервисов + миграции.
.backend-changes: &backend-changes
- "ordinis-app/**/*"
- "ordinis-read-api/**/*"
- "ordinis-projection-writer/**/*"
- "ordinis-rest-api/**/*"
- "ordinis-domain/**/*"
- "ordinis-validation/**/*"
- "ordinis-events-api/**/*"
- "ordinis-outbox/**/*"
- "ordinis-auth/**/*"
- "ordinis-cuod-bundle/**/*"
- "ordinis-migrations/**/*"
- "pom.xml"
- ".gitlab-ci.yml"
# Frontend changes — только admin-ui.
.frontend-changes: &frontend-changes
- "ordinis-admin-ui/**/*"
# Documentation changes — Diplodoc-built integrator guide.
# docs-internal/ исключено явно: оно НЕ build'ится Diplodoc'ом и не должно
# триггерить docs job.
.docs-changes: &docs-changes
- "docs/**/*"
# ─── TEST ──────────────────────────────────────
maven-test:
stage: test
image: repo.nstart.cloud/library/maven:3.9.9-eclipse-temurin-21
script:
- mvn $MAVEN_CLI_OPTS verify
artifacts:
when: always
reports:
junit: '**/target/surefire-reports/TEST-*.xml'
expire_in: 1 week
rules:
- changes: *backend-changes
# Е2Е тесты через Testcontainers (Postgres+Kafka). Профиль `e2e` в
# ordinis-app/pom.xml включает их (default surefire excludes e2e директорию).
# Требует docker:dind service для testcontainers — обычный maven-test runner
# не имеет Docker.
maven-e2e:
stage: test
image: repo.nstart.cloud/library/maven:3.9.9-eclipse-temurin-21
services:
- name: repo.nstart.cloud/library/docker:29.1.2-dind
alias: docker
command: ["--tls=false"]
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
TESTCONTAINERS_HOST_OVERRIDE: "docker"
TESTCONTAINERS_RYUK_DISABLED: "true"
script:
- for i in $(seq 1 30); do docker -H tcp://docker:2375 info >/dev/null 2>&1 && break; sleep 1; done
- mvn $MAVEN_CLI_OPTS -pl ordinis-app -am -P e2e test
artifacts:
when: always
reports:
junit: '**/target/surefire-reports/TEST-*.xml'
expire_in: 1 week
rules:
- changes: *backend-changes
# Можно сделать allow_failure: true если e2e будут флакать — пока нет.
# ─── BENCH (manual JMH) ────────────────────────
# Manual trigger only — slow (~5-10 min) + noisy в CI history. Trigger через
# pipeline run UI с variable RUN_BENCH=true. Catches алгоритмическую регрессию
# на hot paths (parseRef, OnCloseAction, schema traversal).
maven-bench:
stage: test
image: repo.nstart.cloud/library/maven:3.9.9-eclipse-temurin-21
script:
- mvn $MAVEN_CLI_OPTS -P bench -pl ordinis-bench -am package -DskipTests
# Quick smoke: 1 fork, 2 warmups, 3 measurements × 2s. Total ~5 min.
# Если нужно надёжнее — bump до -f 2 -wi 5 -i 5 -r 5 (15 min).
- java -jar ordinis-bench/target/benchmarks.jar -f 1 -wi 2 -i 3 -w 1 -r 2
-rf json -rff bench-results.json | tee bench-results.txt
artifacts:
paths:
- bench-results.json
- bench-results.txt
expire_in: 5 days
rules:
- if: '$RUN_BENCH == "true"'
when: on_success
- if: '$CI_PIPELINE_SOURCE == "web"'
when: manual
allow_failure: true
# ─── BUILD (Maven jars) ────────────────────────
# maven-package всегда запускается: docker backend jobs нуждаются в jar даже
# при frontend-only PR (иначе backend образы не пересобрать → ImagePullBackOff
# в кластере при rolling update). BuildKit layer cache делает повторную сборку
# идентичных jar'ов ~30 sec.
maven-package:
stage: build
image: repo.nstart.cloud/library/maven:3.9.9-eclipse-temurin-21
script:
- mvn $MAVEN_CLI_OPTS package -DskipTests
artifacts:
paths:
- "**/target/*.jar"
- ordinis-migrations/src/main/resources/db/changelog/**
expire_in: 1 day
# ─── BUILD: Integrator Guide (sanity check) ────
# Sanity-check build для PRs и non-main branches. Полный image build
# идёт в publish stage (docker-docs). Здесь только verify что yfm
# не выдаёт errors на изменения docs/**.
build-docs:
stage: build
image: repo.nstart.cloud/library/node:20-alpine
before_script:
# npm install pnpm — corepack может hang на CI runner integrity checks.
- npm install -g --silent pnpm@9.15.4
script:
- cd docs
- pnpm install --frozen-lockfile
- pnpm build
- echo "Built $(find _dist -name '*.html' | wc -l) HTML pages, total size:"
- du -sh _dist
artifacts:
name: "ordinis-docs-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA"
paths:
- docs/_dist
expire_in: 1 week
rules:
- if: '$CI_COMMIT_BRANCH != "main"'
changes: *docs-changes
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes: *docs-changes
# ─── PUBLISH (Docker images) ───────────────────
# resource_group: serializes docker-* jobs across all concurrent pipelines на
# одном runner'е. 5 parallel docker builds (app/read-api/projection-writer/
# admin-ui/migrations) каждый поднимает свой DinD container с buildx — суммарно
# выжирали runner disk (~25Gi+) → "no space left on device" в /var/lib/docker
# на тяжёлых stages (см. pipeline 6066). Serialize'я добавляет ~10 min к
# pipeline'у, но вместо retry-roulette мы получаем стабильно зелёный CI.
# При scaling до multi-runner setup можно увеличить parallelism per-runner и
# переименовать group в per-runner identifier.
.docker_base:
stage: publish
image: repo.nstart.cloud/nstart/docker-cli-buildx:1.0.0
services:
- name: repo.nstart.cloud/library/docker:29.1.2-dind
alias: docker
command: ["--tls=false"]
resource_group: docker-builds
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
DOCKER_BUILDKIT: "1"
.publish_script: &publish_script |
for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break; sleep 1; done
# Lighter cleanup: dangling layers only, build cache сохраняем чтобы BuildKit
# переиспользовал между serialized jobs. Раньше был `prune -af --volumes`
# после Nexus blob loss recovery (см. ниже) — сжирал inline cache, каждая
# сборка с нуля. Сейчас Nexus стабилен → cache сохраняем.
docker image prune -f >/dev/null 2>&1 || true
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
IMG="$REGISTRY/$IMAGE_REPO/$SVC"
# Cache strategy:
# - --cache-from $IMG:latest — BuildKit читает inline cache из previously
# pushed manifest'а. На повторных сборках cached слои переиспользуются,
# полная сборка падает с ~70-90s до 15-25s.
# - --build-arg BUILDKIT_INLINE_CACHE=1 — записывает cache metadata в
# pushed image manifest для следующего pipeline'а.
# - --pull — Nexus docker-proxy лениво re-fetch'ит base image с Docker Hub.
#
# Раньше был --no-cache как temporary recovery после Nexus blob loss
# incident (pipeline 6066). Nexus blob storage стабилизирован, --no-cache
# убран → -2 мин/service × 5 services = -10 мин на pipeline.
# Если Nexus снова потеряет blob refs — симптом: "blob unknown to registry"
# на manifest push. Recovery: вернуть --no-cache temporarily, сделать
# `nexus-cli script run cleanup-orphan-blobs` (или ручной touch через UI),
# потом убрать --no-cache обратно.
#
# Git info passed как build args чтобы Spring Boot build-info goal получил
# реальные значения (внутри Docker context .git/ исключён через .dockerignore,
# git-commit-id-maven-plugin не может прочесть). Backend Dockerfile'ы
# используют эти ARG'и в `mvn -Dgit.commit.id.abbrev=... -Dgit.branch=...`.
# Frontend / docs / migrations Dockerfile'ы их игнорируют (no-op в context'е).
docker pull "$IMG:latest" >/dev/null 2>&1 || true
docker build --pull -f "$DOCKERFILE" \
-t "$IMG:$IMAGE_TAG" -t "$IMG:latest" -t "$IMG:$CI_COMMIT_REF_SLUG" \
--cache-from "$IMG:latest" \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--build-arg GIT_COMMIT="${CI_COMMIT_SHORT_SHA:-unknown}" \
--build-arg GIT_BRANCH="${CI_COMMIT_REF_NAME:-unknown}" \
--build-arg GIT_TAG="${CI_COMMIT_TAG:-none}" \
--build-arg GIT_TIME="${CI_COMMIT_TIMESTAMP:-unknown}" \
"$BUILD_CONTEXT"
docker push "$IMG:$IMAGE_TAG"
docker push "$IMG:latest"
docker push "$IMG:$CI_COMMIT_REF_SLUG"
docker-app:
extends: .docker_base
needs: [maven-package]
variables:
SVC: ordinis-app
DOCKERFILE: ordinis-app/Dockerfile
BUILD_CONTEXT: .
script:
- *publish_script
docker-read-api:
extends: .docker_base
needs: [maven-package]
variables:
SVC: ordinis-read-api
DOCKERFILE: ordinis-read-api/Dockerfile
BUILD_CONTEXT: .
script:
- *publish_script
docker-projection-writer:
extends: .docker_base
needs: [maven-package]
variables:
SVC: ordinis-projection-writer
DOCKERFILE: ordinis-projection-writer/Dockerfile
BUILD_CONTEXT: .
script:
- *publish_script
docker-migrations:
extends: .docker_base
needs: [maven-package]
variables:
SVC: ordinis-migrations
DOCKERFILE: ordinis-migrations/Dockerfile
BUILD_CONTEXT: ordinis-migrations/
script:
- *publish_script
docker-admin-ui:
extends: .docker_base
needs: []
variables:
SVC: ordinis-admin-ui
DOCKERFILE: ordinis-admin-ui/Dockerfile
BUILD_CONTEXT: ordinis-admin-ui/
script:
- *publish_script
# Раньше был path-filter, но это создавало дыру: если backend менялся, а
# frontend нет — резолвили общий тег и helm пытался катить admin-ui:<commit>
# которого не существовало. Layer-cache делает повторную сборку ~30 sec.
# Integrator Guide (Diplodoc) — multi-stage Dockerfile (yfm build → nginx serving).
# Path-filtered: rebuild только при изменении docs/. Если docs не менялся,
# helm берёт previous tag через --reset-then-reuse-values.
docker-docs:
extends: .docker_base
needs: []
variables:
SVC: ordinis-docs
DOCKERFILE: docs/Dockerfile
BUILD_CONTEXT: docs/
script:
- *publish_script
rules:
# На main + tag собираем БЕЗ path-filter. Иначе resolve-tag emit'ит
# docs=main-<sha>, helm пытается pull этого тега, image отсутствует
# (docker-docs не запускался) → atomic rollback с context deadline
# exceeded. Layer cache при отсутствии docs/* changes делает rebuild
# ~30 sec — приемлемая цена за гарантию что image для tag существует.
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
- if: '$CI_PIPELINE_SOURCE == "web"'
when: manual
# ─── TRIGGER DEPLOY ───────────────────────────
# Single resolve-tag (always runs) → один UPSTREAM_IMAGE_TAG. Path-filter
# работает на уровне docker-* jobs (backend skip если бэк не менялся,
# layers cache hit делает rebuild ~30 sec). Полное per-service splitting
# через dual dotenv не работает с GitLab trigger var interpolation.
resolve-tag:
stage: trigger-deploy
image: repo.nstart.cloud/library/alpine:3.20
script:
- echo "RESOLVED_IMAGE_TAG=$IMAGE_TAG" > resolved.env
# DOCS_IMAGE_TAG может быть пустым если docker-docs не пересобирался
# (path-filter no-match). Helm в infra оставит старый tag через
# --reset-then-reuse-values если переменная пустая.
- echo "RESOLVED_DOCS_TAG=$IMAGE_TAG" >> resolved.env
- cat resolved.env
artifacts:
reports:
dotenv: resolved.env
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
# На tag-пайплайне $CI_COMMIT_BRANCH пустой — но trigger-deploy-prod
# (тоже tag-only) needs: resolve-tag. Без этого правила prod deploy
# не запускается с ошибкой "resolve-tag does not exist in the pipeline".
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
trigger-deploy-staging:
stage: trigger-deploy
needs: [resolve-tag]
trigger:
project: 2-6/2-6-4/terravault/ordinis-infra
branch: main
strategy: depend
variables:
UPSTREAM_IMAGE_TAG: $RESOLVED_IMAGE_TAG
DOCS_IMAGE_TAG: $RESOLVED_DOCS_TAG
DEPLOY_ENV: staging
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
trigger-deploy-prod:
stage: trigger-deploy
needs: [resolve-tag]
trigger:
project: 2-6/2-6-4/terravault/ordinis-infra
branch: main
strategy: depend
variables:
UPSTREAM_IMAGE_TAG: $RESOLVED_IMAGE_TAG
DOCS_IMAGE_TAG: $RESOLVED_DOCS_TAG
DEPLOY_ENV: prod
rules:
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
when: manual
# =============================================================
# Release automation — semantic-release.
# =============================================================
# Анализирует conventional commits с последнего tag'а, решает bump (patch/
# minor/major), генерирует CHANGELOG.md, bump'ит package.json, создаёт git tag
# vX.Y.Z и GitLab Release. Tag pipeline далее автоматически собирает images
# и ждёт manual gate на trigger-deploy-prod.
#
# Запускается ТОЛЬКО на main, ТОЛЬКО после успешного staging deploy.
# Если нет релизных commits (только chore/test/ci/docs/style) — exit 0 без
# tag'а. Loop prevention: коммит `chore(release): vX.Y.Z [skip ci]` —
# `[skip ci]` стандартный GitLab marker, новый pipeline не триггерится.
#
# Setup ONE-TIME (см. RELEASE.md):
# 1. Settings → Access Tokens → create Project Access Token
# - role: Maintainer
# - scopes: write_repository, api
# - expiry: 1 год (renew раз в год)
# 2. Settings → CI/CD → Variables → add:
# GITLAB_TOKEN = <token> (Masked, Protected)
#
# Tuning: чтобы НЕ релизить feat: автоматом — добавь в commit message
# `[skip release]` и semantic-release пропустит этот commit (см. parserOpts).
release:
stage: release
image: node:22-alpine
needs:
- job: trigger-deploy-staging
artifacts: false
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: on_success
variables:
# semantic-release требует full git history для commit analysis.
GIT_DEPTH: "0"
# GL_TOKEN — @semantic-release/gitlab plugin requires this name specifically.
GL_TOKEN: $GITLAB_TOKEN
before_script:
- apk add --no-cache git ca-certificates
- npm install -g --silent pnpm@9.15.4
- cd ordinis-admin-ui
- pnpm install --frozen-lockfile --silent
script:
- npx semantic-release
# Не блокирует следующие pipelines если release fail'нул (e.g. нет
# релизных commits). Manual investigation если нужно.
allow_failure: true